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

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

 

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

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

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

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