PIC18 Lab. Experiment #3. ADC.

Задумался у меня один небольшой проектик, где есть задача отлавливать нажатие определенной кнопки. Вроде как все проще некуда, только вот этих кнопок 10 штук, 10 ног заводить не хочется, расширители портов юзать тоже скучно. Я подумал, почему бы мне не сварганить подобие цапа на этих кнопках, и завести его выход на АЦП в pic18f14k50, получим одну ногу вместо 10, но с небольшим усложнением кода.

380px-ADC_Symbol.svg

Как пользоваться этим зверем – под катом.

Начинаем как обычно – читаем даташит.

Сначала нам рассказывают, что мы имеем 9 каналов для ацп, а сам АЦП имеет аж 10 бит. Значит при питании 5 вольт, шаг составляет примерно 5 мВ. Для наших нужд этого более чем достаточно, да и в случае если у вас небольшое изменение сигнала и вы знаете эти границы можно поднять минимальное и опустить максимальное опорное напряжение АЦП, а значит и улучшить разрешение.

Вот как это примерно выглядит:

adc_pic18

На картинке кстати хорошо видно, чтобы работал АЦП, не забывайте установить ADON = 1.

Первым делом нас просят сконфигурировать порты – установить TRIS соответствующего пина в 1 если он будет использоваться как аналоговый вход (при 0 включает цифровой буфер и нет возможности использовать этот пин как аналоговый). Здесь также необходимо сконфигурить регистры ANSEL и ANSELH (если не помните: 1 – аналоговый режим, 0 – цифровой).

ansel

anselh

Следующий шаг – выбор канала. Сделать это можно в регистре ADCON0:

adcon0

Про остальные биты чуть позже, а пока ниже возможные конфигурации:

chs

Про каналы особо говорить нечего, а вот нижние два интересные фичи, насколько я понял DAC здесь и есть дак (ЦАП), то есть мы можем вывести заданное аналоговое напряжение или FVR напряжение 1.024В (независимо от питания) на аналоговый пин ацп. Вот правда на какой и каким образом его выбрать я так и не понял, пока у нас стоит другая цель, будет нужно – разберемся (:.

Я решил использовать в качестве аналогового пина RC7, это канал AN9. На данном этапе все что у нас есть по коду:

 TRISC7 = 1;
 ANSEL = 0;                                                                   //here all analog buffers off
 ANSELH = 2;																  //turn on analog input for RC7(AN9)	
 ADCON0 = 0b00100100;														  //Channel AN9 select

В регистре ADCON1 вы можете настроить нужные референс уровни:

adcon1

Меня устраивает VDD и VSS так что я оставляю все по дефолту.

Дальше идет рассказ про то, что можно настроить количество прогонов, по которым ацп выведет усредненное значение, но здесь есть нюанс – прерывания и статусный бит все равно будут срабатывать каждый раз, и отдельной индикации о окончании сбора данных здесь нет, так что пока не пользуем.

Занимательный параметр Tad – время конверсии на 1 бит, для полной конверсии необходимо выждать 11Tad:

Tad

Чтобы задать необходимое время для конверсии используют биты ADCS из ADCON2:

adcon2

clockselect

У меня кварц 12МГц, красиво делится только на 4, и получим клок для АЦП с периодом в 3мкс.

В этом же регистре присутствует бит ADFM, который отвечает за выравнивание результирующего байта, я уже привык как то к правому, так что его надо в 1:

 ADCON2 = 0b10000100;														  //Right justification, clock = Fosc/4

 

Дальше нам осталось лишь включить АЦП (ADON = 1) и запустить процедуру измерения (GO = 1), дальше если мы работаем без прерываний, то можно просто подождать пока бит GO не сбросится в 0:

void main()
{

 TRISC7 = 1;
 ANSEL = 0;                                                                   //here all analog buffers off
 ANSELH = 2;																  //turn on analog input for RC7(AN9)	
 ADCON0 = 0b00100100;														  //Channel AN9 select		
 ADCON2 = 0b10000100;														  //Right justification, clock = Fosc/4	
 ADON = 1;																	  //ADC is enabled!!!
 TRISB7 = 0; //TX pin set as output
 TRISB5 = 1; //RX pin set as input
 
 Vmeas = 0;

 UARTConfig = USART_TX_INT_OFF & USART_RX_INT_OFF & USART_ASYNCH_MODE & USART_EIGHT_BIT & USART_BRGH_HIGH ;    //прерывания выключены, асинхронный режим, 8 бит, высокоскоростной режим
 baud = 77;                 //Focs/(9600*16) - 1
 OpenUSART(UARTConfig,baud);


 putsUSART( (char *) "Welcome to Diymicro.ru\r\n" );
 GO = 1;																	   //go, go, go!!! (:

 while(GO){};
 Vmeas = (ADRESH << 8) | ADRESL;
 NumToUart(Vmeas);

 while(1){};

}

Ниже я подал на аналоговый вход напряжение 2.3 В и вот, что получилось:

Polling_probe

 

Итак 471*5000/1024 = 2299.80 мВ, очень даже неплохо. Попробуем ка теперь это сделать с помощью прерываний.

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

void main()
{

 TRISC7 = 1;
 ANSEL = 0;                                                                   //here all analog buffers off
 ANSELH = 2;																  //turn on analog input for RC7(AN9)	
 ADCON0 = 0b00100100;														  //Channel AN9 select		
 ADCON2 = 0b10000100;														  //Right justification, clock = Fosc/4	
 ADON = 1;																	  //ADC is enabled!!!
 TRISB7 = 0; //TX pin set as output
 TRISB5 = 1; //RX pin set as input
 
 Vmeas = 0;

 UARTConfig = USART_TX_INT_OFF & USART_RX_INT_OFF & USART_ASYNCH_MODE & USART_EIGHT_BIT & USART_BRGH_HIGH ;    //прерывания выключены, асинхронный режим, 8 бит, высокоскоростной режим
 baud = 77;                 //Focs/(9600*16) - 1
 OpenUSART(UARTConfig,baud);
 

 putsUSART( (char *) "Welcome to Diymicro.ru\r\n" );

 ADIF = 0;
 ADIE = 1;
 PEIE = 1;
 GIE = 1;


 GO = 1;																	   //go, go, go!!! (:



 while(1)
 {
 		if (ADCstatus)
 		{
 			Vmeas = (ADRESH << 8) | ADRESL;
 			NumToUart(Vmeas);
 			ADCstatus = 0;
 		}


 }

}



void interrupt isr()
{
	if (ADIF)
	{
		ADCstatus = 1;
		ADIF = 0;

	}
}

Отрабатывает точно также и естественно с таким же значением (:

Это все интересно, но хочется чего то более практичного, давайте попробуем загнать синус на вход с размахом до питания, но для начала надо посчитать какую частоту мы сможем нормально оцифровать на наших настройках.

Опытным путем я установил, что если убрать запихивание в уарт по каждой конверсии, то время между конверсиями составит ~13 мкс, я хочу взять 128 отсчетов на период – 128 * 13 = 1664 мкс, что соотвествует примерно 601 Гц, но чего уж тут баловаться с этими герцами, округляем до 600 Гц.

Дальше я завел 128 байтный массив, в который сгружал оцифрованные значения:

#include <xc.h>
#include <plib/usart.h>
#include <plib/delays.h>


#define _XTAL_FREQ 12000000 //The speed of your internal(or)external oscillator

#pragma config WDTEN = OFF, LVP = OFF, FOSC = HS  

unsigned char UARTConfig = 0;
unsigned char baud = 0;
unsigned int Vmeas[128] = 0;
volatile bit ADCstatus = 0;
volatile unsigned char i = 0;

void Delay1us();          //1us delay for 12MHz xtall
void DelayUs(unsigned char Us);
void DelayMs(unsigned int Ms);
void NumToUart(unsigned int Num);  

/*
 * 
 */
void main()
{

 TRISC7 = 1;
 ANSEL = 0;                                                                   //here all analog buffers off
 ANSELH = 2;																  //turn on analog input for RC7(AN9)	
 ADCON0 = 0b00100100;														  //Channel AN9 select		
 ADCON2 = 0b10000100;														  //Right justification, clock = Fosc/4	
 ADON = 1;																	  //ADC is enabled!!!
 TRISB7 = 0; //TX pin set as output
 TRISB5 = 1; //RX pin set as input
 

 TRISC5 = 0;

// Vmeas = 0;



 UARTConfig = USART_TX_INT_OFF & USART_RX_INT_OFF & USART_ASYNCH_MODE & USART_EIGHT_BIT & USART_BRGH_HIGH ;    //прерывания выключены, асинхронный режим, 8 бит, высокоскоростной режим
 baud = 77;                 //Focs/(9600*16) - 1
 OpenUSART(UARTConfig,baud);
 

 putsUSART( (char *) "Welcome to Diymicro.ru\r\n" );

 ADIF = 0;
 ADIE = 1;
 PEIE = 1;
 GIE = 1;


 GO = 1;																	   //go, go, go!!! (:



 while(1)
 {
 		if ((ADCstatus)&&(i<128))
 		{
 			Vmeas[i] = (ADRESH << 8) | ADRESL;
 			//NumToUart(Vmeas);
 			//putsUSART( (char *) "\r\n" );
 			i++;
 			ADCstatus = 0;
 			GO = 1;
  		} else
  			{
  				if ((i == 128)&&(ADCstatus))
  				{
  					ADCstatus = 0;
  					for (i=0;i<128;i++)
  					{
  						 NumToUart(Vmeas[i]);	
  						 putsUSART( (char *) "\r\n" );
  					}
  				}
  			}


 }

}



void interrupt isr()
{
	if (ADIF)
	{
		ADCstatus = 1;
		ADIF = 0;
		

	}
}


void Delay1us()         //delay approx 1 us
{
  Delay1TCY();
  Delay1TCY();
  Delay1TCY();
}

void DelayUs(unsigned char Us)      //delay for a given number of microseconds
{
  for (unsigned char i = 0; i<Us; i++)
    Delay1us(); 
}

void DelayMs(unsigned int Ms)      //approx delay for a given number of miliseconds
{
  for (unsigned int i=0; i<Ms; i++)
    Delay1KTCYx(3);
}

void NumToUart(unsigned int Num)                                                //Число в уарт
{

  unsigned int bignum = 10000;
  unsigned char numtemp = 5;

  if (!Num)
  {
      WriteUSART('0');         //Выталкиеваем все разряды - от старшего к младшему
      while(BusyUSART());                                                       //Ждем пока освободится модуль иначе будут прострелы
  }
  else 
  {
	  while(numtemp>0)                                                             //Определяем сколько разрядов имеет наше число
	  {
	    if (Num/bignum)
	        break;
	    numtemp--;
	    bignum = bignum / 10;  
	  }  
	
	
	
	  for (unsigned char i = numtemp; i>0; i--)
	    {
	      WriteUSART( (Num - (Num/(bignum*10))*bignum*10 )/bignum + '0');         //Выталкиеваем все разряды - от старшего к младшему
	      while(BusyUSART());                                                       //Ждем пока освободится модуль иначе будут прострелы
	      bignum = bignum/10;
	    }
   } 
}

Как только счетчик досчитал 128 конверсию все данные вылетели в терминал, откуда я их и зацепил:

made with ChartBoot

Построив график в первый раз я понял, что косячнул с определением времени опытным путем и пришлось ввести поправку, чтобы частоты совпали, вышло что время конверсии одного значения занимает примерно 18,2 мкс.

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

made with ChartBoot

Ну вроде как неплохо получилось.

 

Исходники как обычно.

PIC18 Lab. Experiment #3. ADC.: 74 комментария

    • Для синуса просто выгнал 128 сэмплов по уарту, нормировал по времени (зная частоту входного сигнала либо зная время оцифровки 1 сэмпла).
      Для прямой так вообще просто выводил пару сэмплов и брал среднее значение для каждого входного напряжения.

  1. Здравствуйте. Объясните на пальцах пожалуйста про ADCS.
    К примеру у меня внутренний генератор 4МГц, то какой мне следует выбрать делитель и почему? Для чего это вообще?

    • Доброй ночи,

      Это выбор частоты тактирования АЦП, то есть теоретически если используется АЦП параллельного типа, то вы можете получать значения на каждом такте.

      Выбрать частоту тактирования АЦП исходя только из частоты кварца не получится, все зависит от частоты входного сигнала – есть такая штука – критерий Найквиста, по которому частота тактирования должна быть минимум в два раза большей обрабатываемой частоты. Ну и естественно – чем выше частота тактирования находится от вашего сигнала, тем качественней сигнал на выходе.

  2. Т.е. чем больше установить значение ADCS, тем стабильнее? Или наоборот меньше?
    Сигнал собираюсь измерять постоянный. Какой тогда получается нужен? Fosc/2? Хотя это ведь делитель, а надо же в 2 раза больше вы написали.

    • Не стабильнее, а точнее реконструированный сигнал (у синуса например будет меньше гармоник в спектре, что будет приближать его к реальному синусу) плюс ко всему существуют ряд параметров – SNR, SFDR, INL DNL, ENOB…

      Для ваших целей для постоянного сигнала нужно определиться с некоторыми вещами:
      1. Как часто вы собираетесь снимать показания ацп?
      2. Важно ли вам отслеживать изменения вашего сигнала (ступеньку) или же важны только итоговые значения?

      Если ваше время довольно велико и сама ступенька вас не интересует, + ко всему вы не гонитесь за офигенной точностью и не занимаетесь цифровой обработко сигналов (ну сосна у постоянного сигнала то и обрабатывать по сути нечего) то думаю, что вы не ощутите особой разнице при любой настройке.

      P.S. Обратная сторона увеличения частоты тактирование – рост потребляемой мощности.

  3. Ну как обычный вольтметр. Чтоб только еще возможно было зафиксировать вдруг напряжение окажется не стабильным и\или переменным (50Гц – розетка). Для этого вполне пойдет \32? Я такой выставил просто так и не знаю нормально ли….
    +
    Не знаете почему переменные могут хранить больше положенного?
    Т.е. например 8 битный МК. 8 бит =256, а в нем может храниться, в переменной и 10 битное значение…. и более

    • 1. 4Мгц/32 = 125 KHz прилично больше 50 Гц, думаю хватит.

      2. Насколько я понимаю в pic микроконтроллерах используется гарвардская архитектура, и то что вы видите 8 бит, означает что разрядность шины данных = 8 бит, то есть это разрядность – разрядность внутренних регистров мк, а ваш код хранится в отделе programm memory, который имеет уже совсем другую разрядность.

      http://www.microcontrollerboard.com/pic_memory_organization.html

      • А сколько надо времени выжидать? Написано Tad для 1 бита. А это самое Tad не написано сколько мкс…. (в даташите)

  4. Здравствуйте.
    Когда я вытаскиваю провод тестера от ножки, то вместо пустоты у меня высвечиваются значения: 0, 20, 5, 20, 0 и тп. И так постоянно меркают. Почему так?

    • это в каких единицах измерения? может там микровольты, да и миливольты мне кажется вполне могут быть на дешевых китайских тестерах, кто знает что там за шумы наводятся на высокоомный вход

  5. А, а еще данные не статичны о_О
    Т.е. подаю с БП компьютера (а ведь оно стабилизированное) напряжение и там пишит к примеру 3.3, 3.4,3.5,3.3 и тп.

    • 100 мВ очень приличная разница, возможно ваше питание для мк нестабильно. Кстати земля бп компьютера и земля платы с ацп объединены?

  6. Какие мВ? Какие еще щупы? Я Вольты измеряю и такая ерунда происходит…
    Питаю микросхему от БП ПК от 12В через стабилизатор на 5В, выдает правда 5.09-5.10В, хотя конденсаторы поставил рекомендуемые, но они правда старые.
    И проверяю напряжение с того же БП.
    Да, земля +12В, что для питания и +3.3В от одного БП.

    • Откуда я могу знать какие щупы, я не телепат, вы написали про какой то провод тестера и какие то невнятные значения, которые где-то там меркают, безо всякого вступления, как я из этого могу что-то понять по вашему?

      Проверяйте ваш БП осциллографом (хотя бы тестером на худой конец), у меня максимальная ошибка была 40 мВ, как можно увидеть из графика в статье. Хотите убрать эту ошибку (INL к слову называется, можно погуглить) делайте усреднение измерений, заводите температуронезависимый Vref и т.д.
      В чем конкретно ваша проблема вам никто не скажет, вернее пошлют скорее всего сразу за осциллом, чтобы вы доказали что ваше питание стабильно и измеряемая точка тоже.

  7. То, что немного не совпадает это пофиг. У меня тестер самый дешевый из всех, что были в магазине. Может он что-то не так показывает или же это из-за не очень точной корректировки переменного резистора.
    Меня больше волнует почему значения меняются?
    Осцилографа тоже нет…

  8. Если вот померить тестером просто блок питания, то оно немного скачет. Где-то 3.39 – 3.4.
    Стоит делитель с коэффициентом 4. Десятые не должны меняться, а у меня они изменяются тоже…. Там 13.3, 13.4 к примеру….

  9. А если поставить Fosc\32 на более менее, ну там на /2 к примеру, то будет на много реже меняться ведь? У меня итак впринципе не быстро меняется.
    Где-то так:
    13.3 горит 1-2 секунды, потом 13.4 загорается и сразу же становится опять 13.3

  10. Вы вот выше писали, что чтобы измерять переменное напряжение, то вполне пойдет 5МГц у МК при Fosc/32. А вот если у меня частота МГц, то какой Fosc поставить, чтобы резко не менялось, а ну может 1 раз обновился где-то в 3-5 сек.?
    Может у меня что-то в программе… Я вот так сделал:
    ADCON1=0b00100000;
    ANSEL=0b00110111;
    lcd_init();

    while(1){

    lcd_clear();
    lcd_gotoxy(0,0);
    lcd_puts(“Value “);

    do{
    __delay_ms(30); // может тут недостаточно времени выждал? Хотя наверное это еще и от БП зависит… Темболее если у меня и питаниется от него… То тогда будут дергаться значения и при напряжении на МК…
    ADCON0=0b00000011;
    while(GO);
    celoe=ADRESH*4/51;
    ostatok=ADRESH*4*10/51-celoe*10;
    lcd_gotoxy(0,7);
    sprintf(otvet, “%d.%d “, celoe, ostatok);
    lcd_puts(otvet);
    __delay_ms(150);
    }
    while(RA3!=0);
    lcd_clear();
    lcd_gotoxy(0,0);
    lcd_puts(“нажми на кнопку “);
    while(RA3!=0);
    }

    • Из этого кода я могу понять лишь то, что я вообще ничего не понимаю.
      Где вы тут вообще настраиваете время конверсии? Где регистр ADCON2?
      Что за конверсия такая с какими-то делениями? Почему в ней используется только ADRESH? ну и опять таки ни хрена не понятно по какой стороне выравнивание adcon2 не фигурирует так что я даже прикинуть не могу каким образом конверсию вы реализовываете.

      И еще, у вас изменение раз в 200мс примерно, это обусловлено вашим кодом, а не временем конверсии.
      У вас в протеусе то хоть работает ваш код?

  11. Что такое время конверсии?
    Ой, извините. Забыл написать — у меня 8 разрядный. там только ADCON0 и ADCON1.
    МК PIC16F688. Выравнивание по левому (8бит). — т.к. мороки меньше, точность как я понял отличается в 0.01, что мне не важно и нет шумов.

    • Посмотрел даташит, там стоит 10 битный ацп, а вы говорите 8 бит, да еще и в протеусе как часы работает, как так?

      Пусть у вас половина шкалы из 5 вольт, с левым выравниванием в итоге вы из ADRESH берете цифру 64, по вашей конверсии выходит число 5 в целом. Под версию делителя на 4 ну никак не подходит.

  12. Что тогда Fosc\32 делает? Если я сделаю Fosc\2, то что изменится?
    Как тогда увеличить стабильность+ время обновления?

    • Это и есть время конверсии одного бита, полный цикл в pic18f14k50 занимает 12 Tad или 11, не помню уже точно.
      То есть если у вас например частота кварца 12МГц, то с Fosc/32 время Tad = 1/0.375M = 2.66 мкс * 12 ~ 32 мкс займет ваша оцифровка. Во втором случае соответственно в 16 раз медленнее, то есть за 512 мкс. Но это в любом случае гораздо меньше 200 мс.

      Ну и по конверсии, ок пусть у вас 8 бит, предположим на входе у вас 2.5 Вольта при питании 5 Вольт, ацп вам выдает число 128, идем к вашей конверсии:
      celoe = 128*4/51 = 10
      ostatok = 128*4*10/51 – celoe*10 = 100 – 100
      итого 10.0, где я не прав?

      Повторю главный вопрос, ваша схема работает в протеусе или мы ща просто воздух сотрясаем?

  13. Нигде. Все правильно Вы сосчитали.
    Эта формула учитывает делитель напряжения в 4 раза.
    Конечно работает, раз в живую даже получилось. Только в протеусе все работает как часы, оно и понятно – симулятор.
    А вот на практике в живую проблемки.
    Можете что-либо посоветовать? Я ведь вроде все правильно написал.
    Чтоб не так часто хотябы обновлялся. Неужто это делается тупо ожиданием, а не через Fosc.
    Как я Вас понял — эта Fosc действительно замедляет обновление, но только для нашего случая оно не существенное. Не понял откуда Вы взяли 1/0.375M.

    • Я бы как минимум убрал обновление пока нажата кнопка, лучше бы просто таймер на прерываниях запустили и обновляли себе раз в пару секунд. Зачем вам кстати обновления раз в 150 мс?

      Второе запитаться от заведомо стабильного источника (батарейка например) и завести делителем напряжения от нее же напряжение на измерение и все проверить.

      Третье, не знаю как там в вашем мк, но в том, о котором идет речь в статье есть усреднения (биты ACQT в ADCON2) я с этим не разбирался еще, но они по идее предназначены как раз для того чтобы убрать ошибки, выбивающиеся из общей статистики. Но за третий пункт я бы брался только после проверки второго.

  14. 150мс это я задержку для кнопки поставил. У меня кнопка.
    А как Тогда будет выглядеть мой код как вы предлагаете с таймером?
    Нет такого регистра. Только ANSEL, ADCON0, ADCON1

    • Ну как минимум потому что в макросе делэй используется char переменная 🙂
      С таймером просто точнее и серьезнее.

  15. Я на сегодня уже договорился прошивать, а чтоб мне сделать с таймером и прерыванием — потребуется наверное несколько дней, т.к. не работал с этим и не знаю как делать.
    Посмотрел побыстрому Ваш урок по прерыванию и мало что понял.
    У меня вроде есть часть программы с таймером, которую Вы мне поясняли. А вот как сделать прерывание по нажатию кнопки я не знаю, т.к. не понятен Ваш пример в уроке.

  16. А что если так:
    while(кнопка нажата?){
    программа….
    //ниже задержка таймером на 1 сек.
    OPTION_REG = 0b11010111; // считаем по 256 мкс
    for (;;) {

    for (i=1;i<17;i++)
    {
    TMR0 = 0;
    while (TMR0 < 250) { }; //задержка пока тикает таймер
    }
    } // и переходит наверх пока кнопка не будет нажатой.
    Или так плохо?
    Вот прерывание. — если кнопка нажата будет во время цикла, то какая бы строка не выполнялась, сработает прерывание и можно сделать, чтобы после прерывания по кнопки возвращалась не в main(), а просто переходило в другой цикл?
    В конце тогда программы добавить:
    interrupt isr() { //не знаю что такое interrupt, такого типа данных не встречал….

    if (кнопка нажата?) {
    и сделать как то переход на следующий цикл. а вот как это сделать не знаю. У Вас как я понял переходит в начало программы main
    }

    • Читайте мои прошлые комментарии, там где про 8 бит вашего ацп я спрашиваю.
      Прерывания вам сейчас не помогут, особенно с учетом того, что вы не понимаете как они работают.

        • Нет

          sarge Ответил:
          Апрель 22nd, 2015 в 12:20

          Посмотрел даташит, там стоит 10 битный ацп, а вы говорите 8 бит, да еще и в протеусе как часы работает, как так?

          Пусть у вас половина шкалы из 5 вольт, с левым выравниванием в итоге вы из ADRESH берете цифру 64, по вашей конверсии выходит число 5 в целом. Под версию делителя на 4 ну никак не подходит.

  17. Там же написано, что это если выравнивание в право, то 10, а у меня в лево, а это 8…. бит.
    Ну выражение такое, боже… 🙂 Отлично значит в протеусе работает)

    Второй абзац вообще не понял что Вы написали)
    Все у меня работает, только проблемы с “дерганием” напряжения, измерения.

    • Где это написано?
      https://dl.dropboxusercontent.com/u/82053027/adcpic.PNG

      Второй абзац я считал что у вас должно получаться по вашему коду, с непонятными делителями напряжения на 4, зачем вам они кстати?

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

    • Хотя соглашусь, при 5 вольтах референса вы теряете максимум около 20 мВ, без использования последних двух бит.
      Тогда я в общем сдаюсь, вам сначала надо проверить или осциллом ваши земли питания референсы, либо же запитаться от батареек и смотреть что будет.

    • И в догонку, вы не следуете рекомендуемой последовательности в даташите:

      1. Configure the A/D module:
      • Configure analog/digital I/O (ANSEL)
      • Configure voltage reference (ADCON0)
      • Select A/D input channel (ADCON0)
      • Select A/D conversion clock (ADCON1)
      • Turn on A/D module (ADCON0)
      2. Configure A/D interrupt (if desired):
      • Clear ADIF bit (PIR1<6>)
      • Set ADIE bit (PIE1<6>)
      • Set PEIE and GIE bits (INTCON<7:6>)
      3. Wait the required acquisition time.
      4. Start conversion:
      • Set GO/DONE bit (ADCON0<0>)
      5. Wait for A/D conversion to complete, by either:
      • Polling for the GO/DONE bit to be cleared
      (with interrupts disabled); OR
      • Waiting for the A/D interrupt
      6. Read A/D Result register pair
      (ADRESH:ADRESL), clear bit ADIF if required.
      7. For next conversion, go to step 1 or step 2 as
      required. The A/D conversion time per bit is
      defined as TAD. A minimum wait of 2 TAD is
      required before the next acquisition starts

      У вас и GO и ADON бит устанавливаются одновременно, а там советуют выждать определенное время и в примере с того же даташита, эти две установки также разнесены по времени.

  18. Все, я запутался. Жаль, что нет на русском к нему даташита…
    {
    __delay_ms(30); — не оно? время задержки
    ADCON0=0b00000011;
    while(GO); — а тут как раз ждем GO
    ________________________________________________________________
    Хорошо, что Вы сами все поняли про напряжения, потому, что я начинаю путаться.
    Делитель напряжения — чтобы увеличить границу. Иначе ведь как я понял от 0 до 5 можно измерить. А с делителем — больше.
    ПРо сдвиг влево — 8 бит, это я читал у какого-то другого МК на русском. Тут я полагаю также. Или же нет? О_о С этим может быть чтоли тогда связана эта проблема?
    Осцилографа нет, повторюсь.
    ________________________________________________________________
    Поступаю тогда так:
    – завтра добываю батарейки, питаю от нее и МК и на аналоговый вход.
    – если эффект тот же, то в надежде что заработает выставляю большее время ожидания (__delay) и там где еще это надо. + если уменьшить скорость на Fosc\2, то не поможет?

    Если и после этого не получится, то что? Статическое измерение делать не хочется….

    • 1. Вы ждете когда эта переменная обнулится, это не то, вы должны выждать пару микросекунд (см. даташит) между установкой ADON = 1 и GO = 1.

      2. Так у вас как то не в ту сторону вы считаете, у вас при 2.5 на выходе 10 отображается, покажите ваши расчеты как это у вас получится больше.
      Единственный вариант когда вы на ВХОД ацп подаете сигнал поделенный на 4, тогда вы сможете измерить до 20 Вольт, но с 4х кратной потерей точности.
      Проблемы с 8 битами вряд ли, т.к. это максимум 20 мВ ошибки.

      3. Если эффект тот же, то добавляете делэй из пункта 1, пробуйте увеличить задержку между измерениями.
      Дальше, если это не помогает, делаете сами усреднение, например, 20 измерений и усредняете величину измеренную, и выводите ее.

  19. 2. Еще раз)
    Я же написал, что у меня делитель с коэффициентом деления 4.
    Хочу измерить я источник питания с напряжением 16В, делитель напряжения делает 4В, мой МК умножает на коэффициент деления и выводит измеренное напряжение.
    Все правильно Вы сосчитали. 2.5, 10В. Т.к. повторюсь: стоит делитель напряжения с коэффициентом деления 4. Я так это в расчете учел в программе поэтому.
    __________________________________________________
    Афигеть… Я просто задержку просто пихану тогда 10мкс, тогда надеюсь нормально будет.
    __________________________________________________
    Сейчас сделал следующее: подал от батарейки 3В, питалось МК через стабилизатор, выводится число на экран и я тестер поставил на напряжения МК. И вроде как, ВРОДЕ — когда меняется напряжение на МК, то и меняется результат. Но это чудно, потому, как поле стабилизатора напряжения меняется в районе 5.07-5.1В всего, а разнос где-то 13.3-13.7….
    Правда и умножается это на 4….
    Завтра короче надо от батарейки пробовать.
    Как же Вы питаете? Я на стабилизатор согласно даташиту пиханул 2.2мкФ и 1мкФ. Может большей емкости купить и попробовать…

    • 10 мкс может быть мало, в даташите рассчитали 17.5 мкс, вам что жалко этого времени, я бы какую 1 мс вообще пихнул, для измерений на глаз вообще не критично.

      ____________________________________________________________________

      13,3/4 = 3.325 13.7/4 = 3.425, разница в 100 мВ, вполне можно списать на всякие ошибки усреднения, а если вспомнить что отсюда можно выпилить 20 мВ так и вообще.

      Обычно для референс уровня берется высокостабильное напряжение (не питание), которое меняется на пару миливольт от изменения питания на пару вольт и температуры от -40 до 125 (это в микроэлектронике). Берете реф от питания будьте готовы платить за это погрешностью в измерениях.
      Кстати в протеусе ведь можно проверить будет ли так влиять изменение питания.

      Согласно даташиту вы пихнули емкости, в даташите указана определенная нагрузка и характер лоада, а он может быть очень разным, емкостным, резистивным, индуктивным, разные мощности в пике, плюс ко всему от БП питания может так адские импульсы идут (в разряде емкости большие токи текут в короткий промежуток времени). Есть всякие параметры типа дропаутов, psrr, время установления – стабилизаторы это одна большая и не очень простая тема, то что вы пихнули простой 7805 не значит что у вас будет идеальное питание.

      PS. Если вы хотите делать точные измерения в пару миливольт, то придется обзавестись осцилом.

  20. Да мне бы хоть чтоб он десятые нормально показывал, а не как сейчас…
    20мВ это откуда Вы взяли?
    Про усреднения я вообще впервые вижу от Вас. Остальные же как-то собирают вольтметры и без этого…
    Какой Вы знаете самый дешевый МК со стабилизатором у АЦП? (конечно хотелось бы добиться нормального результата на своем уж…)

    • Кто собирает без усреднений? 🙂 Дядя Вася из соседнего блога может и собирает, а самые точные ацп насколько я понимаю в принципе основываются на усреднениях.

      20 мв это потеря ваших младших 2 бит, 5000/1024 ~ 15 мВ ну и 5 я от себя накинул 🙂
      Никаких не знаю, я этим не занимаюсь профессионально и нахожусь в таком же положении как и вы, надо гуглить, смотреть даташиты и т.д.

  21. Как программно сделать усреднение, как Вы предлагали?
    По поводу задержки:
    Я ведь вручную GO=1 не ставлю, а ожидаю когда он сам поставится. А надо значит еще перед while(GO); поставить еще задержку на пару десятков мкс?

    • Вы понимаете принцип работы конструкции while()?

      while(GO){}; зациклено само в себе пока GO = 1, вы выходите из цикла по сбросу GO в 0.
      А вручную GO в 1 вы ставите вот здесь ADCON0=0b00000011;

      Про усреднения надо думать, сходу я не могу так, да и проверили ли вы все остальные пункты которые расписывали недавно, у вас же даже программатора нет

  22. Ведь если будет усреднение, то тогда ведь плохо просматривается не стабилизированное напряжение? Т.е. чтоб проверить не стабильное напряжение или переменное.

  23. Все теперь ведь правильно? Даже с усреднением. Времени нормально выждано?
    Если сделать сюда еще таймер, ну что-бы обновлялось каждые 0.5с как раз результат, то как тогда сделать, чтоб по кнопке прерывалось? Попроще примера нету у Вас на прерывания?
    Ну это все мне не нравится…. Это все для постоянного напряжения… Если надо измерять переменное, то никакие усреднения ненужны. Не понимаю почему у меня тогда так работает… Все же может быть из-за не правильно выжданной задержки?
    while(RA3!=0){
    ADCON0=0b00000001;
    __delay_ms(100);
    GO=1;
    __delay_ms(100);
    while(GO);
    i=0;
    while (i<7){
    celoe=ADRESH*4/51;
    ostatok=ADRESH*4*10/51-celoe*10;
    //ostatok=ADRESH*0.78431373-celoe*10;
    celoe2=celoe+celoe2;
    ostatok2=ostatok+ostatok2;
    }

    lcd_gotoxy(1,12);
    sprintf(otvet, "%d.%d ", celoe2, ostatok2);
    lcd_puts(otvet);
    __delay_ms(150);
    }

    • Вы пробовали без усреднения ваш код в железе, с добавление вышеуказанных задержек?
      Вы пробовали ваш код для батарейки как раньше писали?

      • Я хочу написать несколько версий программ и чтоб сразу 1 раз съездить и наверняка прошить.
        Подавал батарейкой 4.5В — не запустилось даже…
        Задержку в этот раз я правильно поставил?

        • Второй делей можно было бы убрать.

          4.5 Вольта должно работать вплоть до частоты 20 МГц, может там пониже таки напряжение итоговое вышло, и соответственно требования к частоте в этом случае стали 10 МГц и ниже.

          и вообще еще добавил антидребезг на кнопку и организовал цикл по другому, если хотите знать как присылайте архив с проектом под мплабх и протеусовскими файлами.

  24. забыл поделить…

    ADCON1=0; // чтобы еще не быстро производилось Fosc
    while(RA3!=0){
    celoe2=0;
    ostet2=0;
    ADCON0=0b00000001;
    __delay_ms(100);
    GO=1;
    __delay_ms(100);
    while(GO);
    i=0;
    while (i<7){
    celoe=ADRESH*4/51;
    ostatok=ADRESH*4*10/51-celoe*10;
    //ostatok=ADRESH*0.78431373-celoe*10;
    celoe2=celoe+celoe2;
    ostatok2=ostatok+ostatok2;
    }
    celoe2=celoe2/7;
    ostatok2=ostatok2/7;
    lcd_gotoxy(1,12);
    sprintf(otvet, "%d.%d ", celoe2, ostatok2);
    lcd_puts(otvet);
    __delay_ms(150);
    }

    • Смотрю я на ваш код и симуляцию и ни фига не понимаю.
      Зачем подано 3.3 на другой пин?
      Почему вы используете RA3? Зачем себе создавать дополнительные сложности используя пин для ресета? У вас что все остальные заняты?

      • Я бы в общем пробовал как то так, если опять будет прыгать, тогда бы конфигурил Vref на внешний пин и ставил туда отдельный стабилизатор на сколько нужно, там зафильтровать проще чем питание.

        #include &lt;stdio.h&gt;
        #include &lt;lcd.h&gt;
        #include &lt;xc.h&gt;
        #define _XTAL_FREQ 8000000 //4MHz
        #pragma config MCLRE = OFF, FOSC = INTOSCIO, WDTE = OFF, PWRTE = ON
        
        int celoe, ostatok, i, celoe2, ostatok2;
        char otvet[10];
        
        main(){
        OSCCON=0b01110001; //????? ??????????????? 8 ???
        TRISA=0b001111;
        TRISC=0b000011;
        PORTA=0b000000;
        PORTC=0b000000;
        //ADCON1=0b00100000;
        ADCON1=0b01110000;
        ANSEL=0b00110111;
        
        lcd_init();
        lcd_clear();
        lcd_gotoxy(0,0);
        lcd_puts(&quot;test&quot;);
        
        while(1){
        
        
        
        
        
        if(!RA3)
        {
         __delay_ms(20);
         if (!RA3)
         {	
          ADCON0=0b00000001;
          __delay_ms(100);
          GO=1;
          while(GO){};
          lcd_gotoxy(0,12);
          sprintf(otvet, &quot;%d &quot;, ADRESH);
          lcd_puts(otvet);
          __delay_ms(150);
         }
        }
        
        }
        }
        
        

        И, да, нафиг все конверсии пока не убедитесь, что у вас все работает как надо.

  25. Здравствуйте. Забыл, не подскажите как погрешность определить? Если исполльзуется первые 8 бит у АЦП, а питания 5В.

    • Добрый вечер,

      Зависит от погрешности питания, например питание может варьироваться от 4.9 до 5.1, соответственно минимальная ступенька АЦП будет варьироваться от 19.14 мВ до 19.9 мВ.
      Если я правильно понял то о чем вы спрашиваете.

  26. DOBRIY DEN . PROBOVAL ADC NA PIC 16F676 . POCHEMUTO NE RABOTAET V PRERIVANII . V PROTEUSE OPREDELYAET PRI PERVOM VKLUCHENII . PRI IZMENENII NAPRYAJENII NA VHODE ASP NEIZMENYAETSYA ZNACHENII ‘X’ . PRI OTKLUCHENII I POVTORNOM VKLUCHENII PROTEUSA BERET NOVUYU ZNACHENIYU ‘X’ . V PROTEUSE PROVERIL POCHEMUTA ‘ADIF’ NE STANOVITSYA ‘0’

    void main(void)
    {
    
       TMR0=0;
       OPTION_REG= 0b11010000; //таймер 1:2
    //   T0IE=1;             //разрешаем прерывания от таймера
        CMCON = 0b00000111; //Отключить компаратор
       TRISA=0b00010100;//RA2 I RA4 VHOD 
       PORTA=0;
       TRISC=0b00000001;
       PORTC=0;
        ADON=1;
        ANSEL=0b00010000;
    	ADCON0=0b10010001;
    
       /*
          правое выравнивание
          RC0 - аналог, все остальные из PORTA - цифра
       */
    
        ADCON1=0b01000000;  //bit 6-4: ADCS: A/D Conversion Clock Select bits
                     //100 =FOSC/4
               
    
    
       /*
          тактовый сигнал Fosc/32
        
          GO-/DONE = 0
          АЦП включен
       */
    
    
      ADIF = 0;
     ADIE = 1;
     PEIE = 1;
     GIE = 1;
      // GO = 1;
    
       	GO_DONE =1;
       while(1)
       {      
      if(x&gt;500)led_2=1;
    if(x&lt;500) led_2=0;
    
       }   
    }
    
    void interrupt IntFun(void)
    {
    if (ADIF)
    	{
    		ADCstatus = 1;
    		ADIF = 0;
     
    	}
     		if (ADCstatus)
     		{
              //   x = (ADRESH&lt;&lt;8) + ADRESL;
     			x = (ADRESH &lt;&lt; 8) | ADRESL;
    
     			ADCstatus = 0;
     		}
    
    }
    
    • Добрый день,

      Сейчас нет возможности подробно потестить и вникнуть, напишите пожалуйста после выходных, чтобы я не забыл.

  27. Dobriy den . Vi prosili napomnit vam . ADC Proveryal rabotaet kod bez prerivanii . A s prerivaniem pochemuto ne rabotaet ne proishodit prerivaniya

    • Добрый день

      Пока не особо вникая, попробуйте вот этот код:

             if (ADCstatus)
              {
                //   x = (ADRESH&amp;lt;&amp;lt;8) + ADRESL;
                  x = (ADRESH &amp;lt;&amp;lt; 8) | ADRESL;
       
                  ADCstatus = 0;
              }
      

      Убрать из прерываний в главный цикл.

      • А и кстати этот код только один раз и отработать может, вы не включаете нигде повторно бит GO_DONE, который автоматически сбрасывается.

  28. Dobriy den . Ya proboval vstavit GO_DONE=1; v prerivanii i V glavnom sikle . Prerivaniya prerivaet odin raz v nachale i dalshwe programma krutitsya v prerivanie no ne idet na glavniy sikl . Neprerivaet

    • И как мне его достать? Это ваш яндекс диск насколько я понимаю, ссылку давайте или шлите мне на мыло.

    • Копайте в сторону таймера TMR0 – у него по ходу слишком мелкий период срабатывания и он каким-то образом влияет на работу АЦП в прерываниях

      #include &lt;htc.h&gt;
      //#include &quot;delay.h&quot;
      //#include &quot;adc.h&quot;
      //__CONFIG(0x03F72);
      volatile bit ADCstatus = 0;
      unsigned char b=0;
      unsigned long a=0;
        unsigned int x=0;
        int   bt=0;
      #pragma config FOSC =  HS   //INTOSC        // Oscillator Selection bits (HS oscillator: High-speed crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN)
      
      //#pragma config FOSC =  INTOSCIO   //INTOSC        // Oscillator Selection bits (HS oscillator: High-speed crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN)
      #pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
      #pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
      #pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
      #pragma config BOREN = OFF      // Brown-out Detect Enable bit (BOD disabled)
      //#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
      #pragma config CPD = OFF        // Data EE Memory Code Protection bit (Data memory code protection off)
      #pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)
      
      #define _XTAL_FREQ          4000000 // ÷àñòîòà ãåíåðàòîðà â Ãö
      
       #define   in_1   RA2 
       #define   in_2   RA3 
       #define   led_1  RC5 
       #define   led_2  RC4 
       #define   led_3  RC3 
       #define   led_4  RC2 
       #define   led_5  RC1 
       #define   led_6  RA0 
       #define   led_7  RA5 
       #define   led_8  RA4
      
      
      
      
      
      void main(void)
      {
          __delay_ms(200);
      
      
         CMCON = 0b00000111; //Îòêëþ÷èòü êîìïàðàòîð
         TRISA=0b00010100;//RA2 I RA4 VHOD 
         PORTA=0;
         TRISC=0b00000001;
         PORTC=0;
          ADON=1;
          ANSEL=0b00010000;
      	ADCON0=0b10010001;
      
         /*
            ïðàâîå âûðàâíèâàíèå
            RC0 - àíàëîã, âñå îñòàëüíûå èç PORTA - öèôðà
         */
      
          ADCON1=0b01000000;  //bit 6-4: ADCS&lt;2:0&gt;: A/D Conversion Clock Select bits
                             // 000 =FOSC/2
                            //001 =FOSC/8
                            //010 =FOSC/32
                          //x11 =FRC (clock derived from a dedicated internal oscillator = 500 kHz max)
                          //100 =FOSC/4
                         //101 =FOSC/16
                         //110 =FOSC/64
      
       __delay_ms(200);
       ADIF = 0;
       ADIE = 1;
       PEIE = 1;
       GIE = 1;
      
         	GO =1;
      
      
              
         while(1)
         {   
      
       		if (ADCstatus)
       		{
             
       			x = (ADRESH &lt;&lt; 8) | ADRESL;
                              __delay_ms(50); 
       			ADCstatus = 0;
                              GO = 1;
       		}
      
      
      
          
      }
      }
      
      void interrupt isr()
      {
      
      
      if (ADIF)
      	{
      		ADCstatus = 1;
      		ADIF = 0;
       	
      	}
      
      }
      

      Этот код постоянно входит в прерывание и обновляет значение переменной х.

  29. DA VI PRAVI , KOGDA OTKLUCHAYU TMR0 RABOTAET PRERIVANIYA OT ADC KOGDA TMR0 VKLUCHEN NESRABATIVAET . DO ETOGO NA ETO NE OBRATIL VNIMANIYA . SPASIBO ZA PODDERJKU

  30. Уведомление: Pic18 Lab. Experiment #5. USB | PIC микроконтроллеры

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.