Функция чтения АЦП не работает должным образом

Я работаю над школьным проектом, где мне нужно прочитать кучу аналоговых значений и преобразовать их в цифровые с помощью PIC16F1788 ( техническое описание ). Я написал функцию, используя компилятор C и xc8. Проблема в том, что когда я тестирую его, он ведет себя не так, как должен. Когда я удаляю потенциометр, светодиод остается включенным, хотя все должно быть выключено, поскольку аналогового входа больше нет. Вот мой код, спасибо за помощь.

 #include <pic16f1788.h>
 #include <stdio.h>
 #include <stdlib.h>

 #include <xc.h>

// Config word
#define _XTAL_FREQ 32000000


/*config1 and config2 settings*/
void InitADC(void)
 {
 // CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O f   unction on CLKIN pin)
#pragma config WDTE = ON // Watchdog Timer Enable (WDT enabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
 #pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
 #pragma config VCAPEN = OFF // Voltage Regulator Capacitor Enable bit (Vcap functionality is disabled on RA6.)
 #pragma config PLLEN = ON // PLL Enable (4x PLL enabled)
 #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
 #pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
 #pragma config LPBOR = OFF // Low Power Brown-Out Reset Enable Bit (Low power brown-out is disabled)
 #pragma config LVP = ON // Low-Voltage Programming Enable (Low-voltage programming enabled)





     //** Initalise Ports FOR ADC **//
     PORTA = 0x00; //Set ports to low
     PORTB = 0x00;
     TRISA = 0xFF; //Port A is all inputs. (set to 1)
     //** Set Up ADC Parameters **//
     ANSELA =0x2F ; // (0b00101111)All AN0-AN4(RA4 and RA7 have no ADC) of register A are set to analog input

     //ANSELH = 0x00; //Set the analog high bits to 0
     ADCON1 = 0x00; // Sets ADRESL to contain the first 7 bits of conversion, ADRESH will have the final 3 bits. And all the rest to default (FOSC/2 2 prescaler)
 } // void InitADC(void)



// Do the ADC convertion only for the channel indicated
// result of ADC is returned
 unsigned int ReadADC(unsigned char channel)
{
  unsigned int AN_Val;

     // automaticaly determines the input channel it will read the value from
     switch (channel)
         {
         case 1: // AN1 1000 0101
             ADCON0 = 0x85;
             break;
         case 2: // AN2 1000 1001
             ADCON0 = 0x89;
             break;
         case 3: //AN3 1000 1101
             ADCON0 = 0x8D;
             break;
  case 4: //AN4 1001 0001
             ADCON0 = 0x91;
             break;
         default: // Any other value will default to AN0
             ADCON0 = 0x81; // (1000 0001) - AN1 set up ADC ADCON0

 // sets the ADCON0 register for each port depending on the value of channel.
 // the result is 10-bit , The ADC is set to on, No convertion is in progress

         } // switch (channel)

     // Channel selected proceed with ADC convertion
     __delay_us(10); // sampling time
     ADCON0 = ADCON0 | 0x02; //This sets the go/!done bit that starts conversion. Bit will be cleared when ADC is complete
     while (ADCON0 & 0x02); //wait here until conversion is complete
    AN_Val = ((ADRESH << 8) + ADRESL) & 0x03ff; //result is 16 bits with 10-bits for measurement. Shift upper 8 bits left 8 bits into high byte and add low byte.
     return AN_Val;
} // int ADC(unsigned char channel)


 void main (void){

 //** Initalise Ports FOR ADC **//


  unsigned int AN1_Result;
  unsigned int AN0_Result;
  unsigned int AN2_Result;

   PORTC = 0x00;
   TRISC = 0x00; //Port C all output
  // TRISB = 0x00;

// in the following function I am trying to test and see if the code works by using a POT and changing the value and see if LEDs in port C would react to that. 
  do{
    AN0_Result = ReadADC(0);
    PORTC = AN0_Result; 

    }while(1);
}

Обновление №1:

основываясь на некоторых предложениях, я обновил свой код следующим образом:

Проблема, с которой я все еще сталкиваюсь, заключается в том, что даже при отсутствии аналогового ввода светодиод, подключенный к PORTC, остается включенным. Даже когда я заземляю вход с помощью резистора на землю. Это просто говорит мне, что с моей функцией ReadADC что-то не так. Я снова просмотрел свой код и подозреваю, что что-то не так со следующими строками, которых я еще не получил:

ADCON0 = ADCON0 | 0x02; //This sets the go/!done bit that starts conversion. Bit will be cleared when ADC is complete
while (ADCON0 & 0x02);  //wait here until conversion is complete
AN_Val = ((ADRESH << 6) + ADRESL)& 0x03ff ;  // shift the ADRESh register by 6 bits to get rid of extra 0's and then add the bottom 8bits to it which gives the whole 10bit answer. (somebody suggested to & with 0x03ff still dont know why this could be wrong)

кроме того, я изменил код, как некоторые из вас заметили, и переместил инициализацию adc внутрь функции ReadADC, и я добавил пару строк, которые имели для меня смысл, вот это

 #include <xc.h>

// Config word
#define _XTAL_FREQ   500000 // set it to match internal oscillator
#DEVICE ADC=10  // set ADC to 10 bit resolution

/*config1 and config2 settings*/
 // CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O f   unction on CLKIN pin)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT enabled)
#pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
 #pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
 #pragma config VCAPEN = OFF // Voltage Regulator Capacitor Enable bit (Vcap functionality is disabled on RA6.)
 #pragma config PLLEN = ON // PLL Enable (4x PLL enabled)
 #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
 #pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
 #pragma config LPBOR = OFF // Low Power Brown-Out Reset Enable Bit (Low power brown-out is disabled)
 #pragma config LVP = ON // Low-Voltage Programming Enable (Low-voltage programming enabled)


// Do the ADC convertion only for the channel indicated
// result of ADC is returned
 unsigned int ReadADC(unsigned char channel)
{
  unsigned int AN_Val;
  unsigned int bit_val;

    //** Initalise Ports FOR ADC **//
     PORTA = 0x00; //Set ports to low
     PORTB = 0x00;
     TRISA = 0xFF; //Port A is all inputs. (set to 1)
     //** Set Up ADC Parameters **//
     ANSELA =0x2F ; // (0b00101111)All AN0-AN4(RA4 and RA7 have no ADC) of register A are set to analog input

     ADCON1 = 0x00; // Sets ADRESL to contain the first 7 bits of conversion, ADRESH will have the final 3 bits. And all the rest to default (FOSC/2 2 prescaler)
     // automaticaly determines the input channel it will read the value from
     switch (channel)
         {
         case 1: // AN1 1000 0101
             ADCON0 = 0x85;
             break;
         case 2: // AN2 1000 1001
             ADCON0 = 0x89;
             break;
         case 3: //AN3 1000 1101
             ADCON0 = 0x8D;
             break;
  case 4: //AN4 1001 0001
             ADCON0 = 0x91;
             break;
         default: // Any other value will default to AN0
             ADCON0 = 0x81; // (1000 0001) - AN1 set up ADC ADCON0

 // sets the ADCON0 register for each port depending on the value of channel.
 // the result is 10-bit , The ADC is set to on, No convertion is in progress

         } // switch (channel)

     // Channel selected proceed with ADC convertion
     __delay_us(10); // sampling time
     ADCON0 = ADCON0 | 0x02; //This sets the go/!done bit that starts conversion. Bit will be cleared when ADC is complete
     while (ADCON0 & 0x02); //wait here until conversion is complete
    bit_val= ADRESH
    AN_Val = ((bit_val << 8) | ADRESL); //store the ADRESH into the 16 bit int then shit it up 8 spaces after that add the ADRESL values using the and operator.
     return AN_Val;
} // int ADC(unsigned char channel)


 void main (void){

 //** Initalise Ports FOR ADC **//


  unsigned int AN1_Result;
  unsigned int AN0_Result;
  unsigned int AN2_Result;

   PORTC = 0x00;
   TRISC = 0x00; //Port C all output


// in the following function I am trying to test and see if the code works by using a POT and changing the value and see if LEDs in port C would react to that. 
  do{
    AN0_Result = ReadADC(0);
    LATC = AN0_Result; 

    }while(1);
}

ОБНОВЛЕНИЕ № 2

Теперь АЦП работает, но вывод не совсем правильный. Я протестировал его, используя 3 светодиода. Два светодиода загорались при напряжении выше 2,5 В, а один светодиод загорался при напряжении ниже 2,5 В. Проблема в том, что происходит наоборот. Два светодиода загораются, если напряжение ниже, и один загорается, если напряжение превышает 2,5 В. Я также проверил свою схему, и все кажется правильным.

#include <xc.h>

 // Config word
  #define _XTAL_FREQ   2000000 // set it to match internal oscillator

 // Do the ADC convertion only for the channel indicated
 // result of ADC is returned
  unsigned int ReadADC(unsigned char channel)
  {
 unsigned int AN_Val;
 unsigned int bit_val;

//** Initalise Ports FOR ADC **************************************************************//
 PORTA = 0x00; //Set ports to low
 PORTB = 0x00;
 TRISA = 0xFF; //Port A is all inputs. (set to 1)

 //** Set Up ADC Parameters **//
 ANSELA =0x2F ; // (0b00101111)All AN0-AN4(RA4 and RA7 have no ADC) of register A are set to analog input


 ADCON1 = 0xD0; // (0b11010000)
                //format setup see page 182 of datasheet
                // bit7: set for 2'complement format
                // bit6-4 : set FOSC/16
    //********************************************************************************************//

 // automaticaly determines the input channel it will read the value from
 switch (channel)
     {
     case 1: // AN1 1000 0101
         ADCON0 = 0x85;                  // bit0: ADC enabled
                                        //bit6-2: AN1 enabled for analog input
                                       //bit7: set for a 10-bit result(when its 1)
         break;
     case 2: // AN2 1000 1001
         ADCON0 = 0x89;
         break;
     case 3: //AN3 1000 1101
         ADCON0 = 0x8D;
         break;
     case 4: //AN4 1001 0001
         ADCON0 = 0x91;
         break;
     default: // Any other value will default to AN0
         ADCON0 = 0x81; // (1000 0001) - AN1 set up ADC ADCON0

    // sets the ADCON0 register for each port depending on the value of channel.
    // the result is 10-bit , The ADC is set to on, No convertion is in progress

     } // switch (channel)

 // Channel selected proceed with ADC convertion
__delay_us(10); // sampling time
ADCON0 = ADCON0 | 0x02; //This sets the go/!done bit that starts conversion. Bit will be cleared when ADC is complete
while (ADCON0 & 0x02); //wait here until conversion is complete
bit_val= ADRESH; // store upper 2 bits in a 16 bit int
AN_Val = ((bit_val << 8) | ADRESL); //store the ADRESH into the 16 bit int then shit it up 8 spaces after that add the ADRESL values using the and operator.
return AN_Val;
} // int ADC(unsigned char channel)



void main (void){

//** Initalise Ports FOR ADC **//


 unsigned int AN1_Result;
 unsigned int AN0_Result;
 unsigned int AN2_Result;

 PORTC = 0x00;
 TRISC = 0x00; //Port B all output
 //TRISB = 0x00;
 //ANSELB = 0x00; // all as port b digital as in/out
 ANSELC = 0x00;
while(1){
AN0_Result = ReadADC(1);
if(AN0_Result > 512){    // when the voltage is passed 2.5 volts these two LEDS should come on.
            /*Turn these 2 LED on*/
            LATCbits.LATC2 = 1;
            LATCbits.LATC3 = 1;
            LATCbits.LATC4 = 0;
            }
 if (AN0_Result < 512){
            /*turn one LED on*/
            LATCbits.LATC2 = 0;
            LATCbits.LATC3 = 0;
            LATCbits.LATC4 = 1;
            }
}
}
Почему ваши настройки конфигурации находятся внутри InitADC()?
У вас это уже работает? Я нашел еще несколько странностей в вашем коде.
Преобразование работает, но вывод неправильный. Я прохожу его и также нашел пару ошибок. Дайте мне знать, какие ошибки вы нашли, спасибо.
Вернемся в чат
Какие значения вы считываете с АЦП, когда вы заземляете вход (и ожидаете, что показания АЦП будут близки к нулю)? По моему опыту, нет ничего необычного в том, чтобы получить значение в несколько отсчетов (0-10 на 10-битном АЦП), когда вход предположительно имеет потенциал земли.

Ответы (2)

Пара проблем с вашим кодом:

  1. Ваши настройки конфигурации по какой-то причине определены внутри InitADC(). Согласно руководству пользователя XC8, они должны идти в самом верху вашего кода (я полагаю, ниже #include xc.h).

  2. На самом деле вы нигде не вызываете InitADC().

  3. Вы оставили сторожевой таймер (WDT) включенным, но не очищаете таймер в своем коде. Это приведет к постоянному сбросу 16F1788. Выключите ВДТ.

  4. Ваши настройки конфигурации настроены на использование внутреннего генератора. Частота внутреннего генератора по умолчанию составляет 500 кГц. Тем не менее, вы определили _XTAL_FREQ равным 32 МГц. Я считаю, что XC8 использует значение _XTAL_FREQ для расчета времени для функции __delay_us(), поэтому ваши задержки будут крайне неточными. Другими словами, компилятор думает, что программа работает на частоте 32 МГц, но на самом деле она работает на частоте 500 кГц.

  5. Всегда используйте регистры LAT для записи значений в порты. Используйте регистры PORT только для чтения из портов. Регистры LAT были введены для преодоления проблемы чтения-модификации-записи.

  6. Компилятор определяет ADRESH как 8-битный символ без знака. Сдвиг влево 8-битного значения 8 раз приведет к тому, что значение каждый раз будет равняться 0x00. Поэтому ваше назначение AN_Val всегда будет неверным.

обновление Сэма Бростейна:

  1. Самое главное установить регистр ADCON2. Младший полубайт регистра ADCON2 выбирает источник отрицательного канала, который является отрицательным опорным напряжением (Vref-) для аналого-цифрового преобразователя. По умолчанию POR использует напряжение от контакта AN0 в качестве Vref-. ADCON2=0xF устанавливает Vref- на значение, выбранное битом ADNREF регистра ADCON1, который в вашем коде настроен на соединение Vref- с Vss. Другими словами, вы преобразуете несимметричный аналоговый входной сигнал относительно земли (Vss).
Я обновил свой пост, приняв во внимание то, что вы сказали, спасибо
Я вижу, вы исправили 1, 4 и 5. Однако вы до сих пор не вызвали функцию InitADC() нигде в своем коде, не отключили WDT и не исправили назначение AN_Val. Все эти вещи по отдельности фатальны.
Хорошо, я вижу, вы только что скопировали содержимое InitADC() и вставили его в ReadADC(). Не лучший способ сделать это, но это не сломает ваш код. Другие проблемы в моем комментарии выше все еще актуальны.
Извините за WDT, я сделал это в своем коде и забыл отразить это здесь, что касается initADC, я удалил его и инициализировал ADC внутри функции ReadADC, это нормально ??. Также я действительно запутался в части AN_Val. Я сдвинул биты на 6, чтобы избавиться от верхних 6 0 ', а затем добавил к ним младшие 8 бит, это правильно?
Сдвиг 8-битного значения влево на 6 с последующим добавлением младшего байта не даст вам 10-битного значения, которое вы пытаетесь получить. Ваш исходный код был в основном правильным. Единственная часть, которую вам не хватило, это сначала преобразовать ADRESH в 16-битное значение, а затем сдвинуть его влево.
будет ли это работать, если я сделаю это следующим образом: AN_Val = ((ADRESH << 8) & ADRESL); //сохраняем ADRESH в 16-битном int, затем испоганим его на 8 пробелов, после чего добавим значения ADRESL с помощью оператора and. или я должен сначала переместить ADRESH в int, а затем выполнить побитовые операции? Спасибо
Эй, Дэн, я заставил его работать, но это дает мне противоположное тому, что должно быть на выходе. как я проверил это, поставив 3 светодиода. Два светодиода загорятся, если мой результат больше 512 (2,5 вольта), и один светодиод загорится, если вход меньше этого. Я работаю, но происходит наоборот. Я думал, что это моя схема, но я все проверил. Я опубликую обновленный код, чтобы вы могли его посмотреть, если у вас есть время. Спасибо
@sambrostain, какие значения ADRESH и ADRESL сейчас в отладчике?
привет, Дэн, по какой-то причине я могу получить только значения вещей с моей основной функцией. Во время отладки кода я считывал выходные значения AN0_Result и сравнивал их со значением, которое я ожидал на основе приложенного напряжения, и я должен сказать, что они соответствуют моим расчетам. Таким образом, код работает на 100% (тоже довольно точно), но теперь я знаю, что это схема. Завтра я переделаю всю схему и проверю, где я ошибся. Я не могу отблагодарить вас за вашу помощь! Действительно !. Вы показали мне этот отладчик, и теперь он делает мою жизнь намного проще. Спасибо, Дэн!! Пусть Бог вознаградит вас за вашу помощь!
Я дам вам знать завтра, если я заработаю схему.
Я заставил его работать, наконец. Я забыл настроить регистр ADCON2. Мне это было нужно, чтобы установить опорное напряжение на Vref-. Вот почему все работало в обратном направлении. Спасибо за вашу помощь! Дэн

Всегда есть аналоговый вход.

Когда потенциометр подключен, входное значение равно тому, на что установлен потенциометр.

Когда потенциометр отсоединен, входное значение будет таким, каким оно должно быть. Обычно где-то в середине диапазона, может чуть ниже.

Когда вход отключен, он просто улавливает шум из окружающей области и использует его в качестве входа.

Вам нужно заменить потенциометр резистором на землю, когда потенциометр не подключен.