Мы продолжаем серию наших обучающих статье по микроконтроллерам PIC. Ранее мы уже рассмотрели использование в них таймеров, подключение к ним ЖК дисплея 16х2 и семисегментного индикатора. В этой же статье мы рассмотрим использование аналого-цифрового преобразования (АЦП, в англ. ADC) в микроконтроллере PICF877A.
Использование АЦП широко востребовано в современной микроконтроллерной технике, поскольку микроконтроллеры умеют работать только с цифровыми сигналами, а многие датчики имеют аналоговый выход: датчик изгиба, датчик давления, датчик тока, акселерометры и т.д. В связи с этим актуальна задача преобразования этих аналоговых сигналов в цифровые для их дальнейшей обработки в микроконтроллерах.
Ранее на нашем сайте мы рассматривали использование АЦП в следующих микроконтроллерах (платах):
- в микроконтроллерах AVR;
- в плате Arduino;
- в плате Raspberry Pi;
- в Raspberry Pi Pico на MicroPython;
- в плате STM32 Blue Pill.
АЦП в микроконтроллере PIC16F877A
В настоящее время существует много типов различных АЦП, и каждый из них имеет свою собственную скорость и разрешение. Наиболее распространенными типами АЦП являются АЦП с последовательным приближением и типа "сигма-дельта". АЦП, используемый в микроконтроллере PIC16F877A, называется АЦП последовательного приближения или сокращенно SAR (Successive approximation).
Последовательный аппроксимационный АЦП (SAR) работает с помощью компаратора и нескольких логических преобразований. Данный тип АЦП использует опорное напряжение (которое является переменным) и сравнивает входное напряжение с опорным напряжением с помощью компаратора, и разность этих напряжений, которая и будет цифровым выходом данного АЦП, сохраняется из самого значащего (старшего) бита (MSB). Скорость сравнения зависит от тактовой частоты (Fosc), на которой работает микроконтроллер PIC.
Теперь, когда мы знаем основы АЦП, давайте откроем даташит на микроконтроллер PIC16F877A и посмотрим какие возможности аналого-цифрового преобразования есть у него. Как мы можем увидеть из даташита, микроконтроллер PIC16F877A содержит 10-битный 8-канальный АЦП. Это значит, что у него есть 8 каналов АЦП, представленных на рисунке ниже. Разрешение каждого канала АЦП составляет 10 бит, что означает что диапазон его выходных значений составляет 0-1024 (2^10).
На представленном рисунке подсвечены каналы АЦП микроконтроллера PIC16F877A – с AN0 до AN7. Только на этих контактах микроконтроллер может считывать аналоговые значения напряжений. В нашем проекте мы для демонстрации возможностей использования АЦП в микроконтроллере PIC16F877A будем использовать его канал АЦП под номером 4.
Настройка режима работы в микроконтроллере PIC производится с помощью следующих четырех регистров:
- A/D Result High Register (ADRESH);
- A/D Result Low Register (ADRESL);
- A/D Control Register 0 (ADCON0);
- A/D Control Register 1 (ADCON1).
Программирование работы АЦП в микроконтроллере PIC
Запрограммировать работу АЦП в микроконтроллере PIC достаточно просто – необходимо научиться правильно настраивать 4 ранее указанных регистра.
После настройки битов конфигурации далее, в функции void main() нам необходимо инициализировать наш АЦП с помощью регистров ADCON1 и ADCON0.
Структура регистра ADCON0 представлена на следующем рисунке.
В этом регистре нам необходимо включить использование модуля АЦП при помощи установки ADON=1 и установить частоту преобразования (A/D Conversion Clock) с помощью битов ADCS1 и ADCS0. В нашем проекте мы будем использовать частоту преобразования равную Fosc/16. Вы можете попробовать использовать и другие частоты и посмотреть как при этом изменятся результаты работы АЦП. Более подробно про эти процессы вы можете прочитать на странице 127 даташита на микроконтроллер PIC16F877A. В результате в нашем проекте регистр ADCON0 будет инициализирован следующим образом:
1 |
ADCON0 = 0b01000001; |
Структура регистра ADCON1 представлена на следующем рисунке.
В этом регистре нам необходимо установить ADFM=1 (A/D Result Format Select bit – бит установки формата преобразования) и ADCS2 =1 чтобы еще раз выбрать частоту Fosc/16. Остальные биты данного регистра необходимо установить в 0 поскольку мы будем использовать внутреннее опорное напряжение. Более подробно про конфигурацию регистра ADCON1 вы можете прочитать на странице 128 даташита на микроконтроллер PIC16F877A. В результате в нашем проекте регистр ADCON1 будет инициализирован следующим образом:
1 |
ADCON1 = 0x11000000; |
Теперь, после конфигурации настроек модуля АЦП внутри функции main, перейдем в цикл while и начнем в нем считывать значения с выхода АЦП. Для этого необходимо выполнить следующую последовательность шагов:
- Инициализировать модуль АЦП.
- Выбрать аналоговый канал.
- Начать аналого-цифровое преобразование установив бит Go/Done регистра ADCON0 в high.
- Подождать пока состояние бита Go/Done станет low.
- Считать результат АЦП из регистров ADRESH и ADRESL.
Рассмотрим эти шаги более подробно.
1. Инициализация модуля АЦП. Она осуществляется с помощью установки значений регистров ADCON0 и ADCON1. Этот вопрос мы уже рассмотрели, поэтому в нашем случае функция для инициализации модуля АЦП будет выглядеть следующим образом:
1 2 3 4 5 |
void ADC_Initialize() { ADCON0 = 0b01000001; //ADC ON and Fosc/16 is selected ADCON1 = 0b11000000; // Internal reference voltage is selected } |
2. Выбор канала АЦП (аналогового канала). Запрограммируем для этого специальную функцию чтобы в дальнейшем нам было легко переключаться между каналами АЦП.
1 2 3 4 5 6 7 |
unsigned int ADC_Read(unsigned char channel) { //****Selecting the channel**/// ADCON0 &= 0x11000101; //Clearing the Channel Selection Bits ADCON0 |= channel<<3; //Setting the required Bits //**Channel selection complete***/// } |
В этой функции номер канала АЦП будет задаваться переменной channel. При этом с помощью команды
1 |
ADCON0 &= 0x1100101; |
мы очищаем предыдущий выбранный канал АЦП. Для этого мы используем команду поразрядного “&”. В результате ее выполнения биты 3, 4 и 5 будут установлены в 0, а остальные биты будут оставлены в своем исходном состоянии. После этого необходимый канал выбирается с помощью оператора циклического сдвига влево на 3 разряда и операции поразрядного “|” (“или”).
1 |
ADCON0 |= channel<<3; //Setting the required Bits |
3. Начало аналого-цифрового преобразования с помощью установки бита Go/Done в high. Для этого используем следующую команду:
1 |
GO_nDONE = 1; //Initializes A/D Conversion |
4. Ожидание пока значение бита Go/DONE не станет low. Бит GO/DONE будет оставаться в состоянии high до тех пор пока процесс аналого-цифрового преобразования не закончится. Поэтому нам нужно подождать пока значение этого бита станет low. Это можно сделать с помощью цикла:
1 |
while(GO_nDONE); //Wait for A/D Conversion to complete |
5. Считывание результата АЦП из регистров ADRESH и ADRESL. Когда значение бита Go/DONE станет low это будет означать что процесс АЦП завершился. Результатом АЦП является 10-битное значение. Поскольку наш микроконтроллер является 8-битным, то данный результат АЦП разделяется на старшие 8 бит и младшие 2 бита. Старшие 8 бит записываются в регистр ADRESH, а младшие 2 бита – в регистр ADRESL. Поэтому нам необходимо считать результат АЦП из этих двух регистров. Это можно сделать с помощью функции:
1 |
return ((ADRESH<<8)+ADRESL); //Returns Result |
Полностью код функции для выбора канала АЦП, запуска процесса АЦП и считывания результата АЦП будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 |
unsigned int ADC_Read(unsigned char channel) { ADCON0 &= 0x11000101; //Clearing the Channel Selection Bits ADCON0 |= channel<<3; //Setting the required Bits __delay_ms(2); //Acquisition time to charge hold capacitor GO_nDONE = 1; //Initializes A/D Conversion while(GO_nDONE); //Wait for A/D Conversion to complete return ((ADRESH<<8)+ADRESL); //Returns Result } |
Теперь у нас есть функция, на вход которой мы подаем номер канала АЦП, а на ее выходе мы получаем считанное значение АЦП. В нашем случае мы будем использовать канал АЦП под номером 4, поэтому вызов данной функции будет выглядеть следующим образом:
1 |
i = (ADC_Read(4)); //store the result of adc in “i”. |
Полный код программы нашего проекта приведен в конце статьи.
Для визуализации результата АЦП мы будем использовать 4-х разрядный семисегментный дисплей, более подробно о его подключении к микроконтроллеру PIC можно прочитать в данной статье.
Схема проекта
Схема проекта для демонстрации возможностей АЦП в микроконтроллере PIC представлена на следующем рисунке.
Подключение 4-х разрядного семисегментного дисплея к микроконтроллеру PIC точно такое же как и в предыдущем проекте. По сравнению с ним мы добавили в схему проекта потенциометр, подключенный к аналоговому контакту 4 микроконтроллера PIC. Считываемое модулем АЦП с данного потенциометра аналоговое значение напряжения будет отображаться на семисегментном индикаторе.
После загрузки кода программы в микроконтроллер PIC вы можете подключить мультиметр к нашей схеме и протестировать работу проекта.
В нашем проекте мы считываем значение с выхода АЦП в диапазоне 0-1024 и преобразуем его в диапазон 0-5 Вольт. Результат отображается на семисегментном индикаторе и проверяется с помощью мультиметра.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
// CONFIG #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) // #pragma config statements should precede project file includes. // Use project enums instead of #define for ON and OFF. #include <xc.h> #define _XTAL_FREQ 20000000 //***Define the signal pins of all four displays***// #define s1 RC0 #define s2 RC1 #define s3 RC2 #define s4 RC3 //***End of definition**//// void ADC_Initialize() { ADCON0 = 0b01000001; //ADC ON and Fosc/16 is selected (включаем АЦП и устанавливаем частоту Fosc/16) ADCON1 = 0b11000000; // Internal reference voltage is selected (выбор внутреннего опорного напряжения) } unsigned int ADC_Read(unsigned char channel) { ADCON0 &= 0x11000101; //очищаем ранее выбранный канал АЦП ADCON0 |= channel<<3; //устанавливаем необходимый нам канал АЦП __delay_ms(2); //Acquisition time to charge hold capacitor GO_nDONE = 1; //запускаем процесс АЦП while(GO_nDONE); //ждем пока процесс АЦП не завершится return ((ADRESH<<8)+ADRESL); //возвращаем результат } void main() { int a,b,c,d,e,f,g,h,adc; //just variables int i = 0; //the 4-digit value that is to be displayed int flag =0; //for creating delay unsigned int seg[]={0X3F, //Hex value to display the number 0 0X06, //Hex value to display the number 1 0X5B, //Hex value to display the number 2 0X4F, //Hex value to display the number 3 0X66, //Hex value to display the number 4 0X6D, //Hex value to display the number 5 0X7C, //Hex value to display the number 6 0X07, //Hex value to display the number 7 0X7F, //Hex value to display the number 8 0X6F //Hex value to display the number 9 }; //End of Array for displaying numbers from 0 to 9 //*****конфигурация портов ввода/вывода****// TRISC=0X00; PORTC=0X00; TRISD=0x00; PORTD=0X00; //***End of I/O configuration**/// ADC_Initialize(); #define _XTAL_FREQ 20000000 while(1) { if(flag>=50) //ждем пока значение flag достигнет 50 { adc = (ADC_Read(4)); i = adc*0.488281; flag=0; //только если flag равен 50 в "i" записываем результат АЦП } flag++; //increment flag for each flash //***разделяем значение переменной "i" на 4 цифры***// a=i%10;//4th digit is saved here b=i/10; c=b%10;//3rd digit is saved here d=b/10; e=d%10; //2nd digit is saved here f=d/10; g=f%10; //1st digit is saved here h=f/10; //***End of splitting***// PORTD=seg[g];s1=1; //Turn ON display 1 and print 4th digit __delay_ms(5);s1=0; //Turn OFF display 1 after 5ms delay PORTD=seg[e];RD7=1;s2=1; //Turn ON display 2 and print 3rd digit __delay_ms(5);s2=0; //Turn OFF display 2 after 5ms delay PORTD=seg[c];s3=1; //Turn ON display 3 and print 2nd digit __delay_ms(5);s3=0; //Turn OFF display 3 after 5ms delay PORTD=seg[a];s4=1; //Turn ON display 4 and print 1st digit __delay_ms(5);s4=0; //Turn OFF display 4 after 5ms delay } } |
Пытаюсь заставить работать АЦП на pic12f683, но в протеусе, не выходит почему-то.
Есть вопрос, касательно, условия проверки сброса бита, while(GO_nDONE);
Я так пишу,
ADCON0|=(1<<0); // установка бита в 1
while(ADCON0&(1<<0)); //ждём сброса бита.
Правильно? Я для вашего контроллера написал, в моем примере просто бит будет не 0, 1.
Ой ошибка, там где написал (1<<0); везде будет (1<<2);
Добрый вечер. А протеус не понимает если ему написать как в программе GO_nDONE = 1?
ADCON0|=(1<<2);
// установка бита в 1 - это правильноwhile(ADCON0&(1<<2))
- с этим у меня есть сомнения. Вам же нужно состояние отдельного бита проверить, а вы здесь всему регистру делаете операцию "И" и потом, получается проверяете состояние всего регистра, а не его отдельного бита