Главная » Программирование » PIC - микроконтроллеры » АЦП в PIC18F45xx - Аналого-Цифровое преобразование

АЦП в PIC18F45xx — Аналого-Цифровое преобразование

АЦП в PIC18F45xx

В прошлых статьях я рассказал как работать с кнопкой, как зажигать светодиоды, как использовать прерывания периферийных модулей и даже как получить ЦАП с помощью ШИМ. Пришло время посмотреть как можно измерять аналоговые величины при помощи модуля АЦП.

АЦП
Аналого цифровой преобразователь (АЦП), на английском Analog-to-digital converter (ADC). АЦП предназначен для преобразования аналогового сигнала в цифровой код.

АЦП является самым распространенным периферийным блоком, его стараются запихнуть даже в шестиножечные PIC10. Модулей АЦП в МК обычно один, реже два. Больше двух модулей АЦП — совсем большая редкость. В установленном на моей отладочной плате МК PIC18F45K20 один модуль АЦП, но если посмотреть на распиновку МК с функциями, увидим, что ножек на которых можно измерить аналоговый сигнал целых 13, функция на обозначена как AN0..AN12. На рисунке ниже эти ножки помечены зеленым:

ЦАП - распиновка

ЦАП — распиновка

Это совершенно не значит, что там установлено 13 модулей АЦП! Модуль АЦП — один, но его вход подключен к мультиплексору, соединенному с ножками AN0..AN12. Мультиплексор поочередно подключает ножки МК с функцией ANx к АЦП.

Из всего этого богатства аналоговых входов, будем использовать два канала — AN0 и AN1. Ко входу AN0 подключен уже установленный на отладочной плате переменный резистор (схема платы DV164131), ко входу AN1 подключен конденсатор RC-цепочки, на которую заведен ШИМ. Особенности схемы с которой будем работать выделены цветом:

Доработка схемы для работы с АЦП (AN0, AN1)

Доработка схемы для работы с АЦП

Важнейшей характеристикой АЦП является разрядность. АЦП примененного МК может преобразовывать аналоговый сигнал с разрядностью 10 бит. Измерения с точностью выше 8 бит имеет смысл производить с помощью ИОН, т.к. низкая точность напряжения источника питания, его дрейф и шумы сведут на нет все преимущества точного преобразования.

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

Желтым цветом показаны контакты, которые можно использовать для подключения внешнего ИОН.

Структурная схема АЦП МК. Синим показан мультиплексор.

АЦП - структурная схема

АЦП — структурная схема

Настройка АЦП МК PIC18 состоит из этапов:

  • Настройка портов I/O для работы с аналоговым сигналом
  • Выбор каналов
  • Настройка опорного сигнала
  • Выбор источника тактирования и времени измерения
  • Настройка прерываний АЦП

Вариант настройки АЦП приведен ниже, с подробными комментариями:

/* Настройка АЦП. Измеряем два канала: AN0 и AN1 */
ANSEL               = 0; // Все каналы ANx как цифровые
ANSELH              = 0; // Все каналы ANx как цифровые
TRISAbits.RA0       = 1; // Как вход
TRISAbits.RA1       = 1; // Как вход
ANSELbits.ANS0      = 1; // AN0 как аналоговый вход
ANSELbits.ANS1      = 1; // AN1 как аналоговый вход
ADCON0bits.CHS      = 0b00000000; // Выбрали канал AN0 ( = 0)
ADCON1bits.VCFG0    = 0; // +ИОН = источник питания Vdd
ADCON1bits.VCFG1    = 0; // -ИОН = источник питания GND
PIR1bits.ADIF       = 0; // Сброс прерываний АЦП
PIE1bits.ADIE       = 1; // Разрешили прерывание АЦП
IPR1bits.ADIP       = 1; // Установить высокий приоритет для прерывания АЦП
ADCON2bits.ADCS     = 0b00000110; // Часта тактирования FOSC/64
ADCON2bits.ADFM     = 0; // Левое выравнивание (удобно при использовании 8 бит)
ADCON2bits.ACQT     = 0b00000111; // Время ACQT = 20TAD (33us/4.1us)
ADCON0bits.ADON     = 1; // Включили модуль АЦП

Немного о бите ADFM (выравнивание). Десять бит из АЦП читаются в два 8-ми разрядных регистра, которые совместно содержат шестнадцать бит. Разумеется 10 бит нужно как-то расположить в 16-ти битах. Бит ADFM дает выбор, как это сделать:

Влияние бита ADFM в регистре ADCON2

Влияние бита ADFM в регистре ADCON2

Если из данных АЦП предполагается использовать только восемь бит, лучше использовать выравнивание влево (ADFM = 0) и просто считывать регистр ADRESH в 8-битную переменную (u8adc = ADRESH). Если же из данных АЦП предполагается использовать все десять бит, то имеет смысл сделать выравнивание вправо (ADFM = 1), в 16-битную переменную считать сначала ADRESH, сделать сдвиг влево на восемь бит, прибавить в переменной значение ADRESL (u16adc = ADRESH; u16adc = u16adc << 8; u16adc = u16adc + ADRESL), а если проще, то можно обратиться к ADRESH и ADRESL как к одному 16-битному регистру ADRES (u16adc = ADRES). На рисунке ниже показаны определения этих переменных:

adc_dop_01

Объявления ADRES, ADRESL, ADRESH

Еще одна полезная особенность АЦП МК — это возможность выбрать канал CHS = 15 (0b00001111) к которому подключен сигнал FVR. FVR является внутренним ИОН МК, имеющим фиксированное напряжение 1,2В. Казалось бы — зачем измерять уже известное напряжение? Однако это имеет практический смысл, поскольку это мы из даташита знаем, что там 1,2В, а МК этого не знает. Для него там будет значение, которое зависит от величины опорного напряжения. Если в качестве опорного напряжения выступает источник питания, то измерив канал CHS = 15 можно узнать величину напряжения питания. Это бывает полезно при батарейном питании, самодиагностике устройства, а так же при использовании нестабильного питания как опорного. Как рассчитывать:

Пример расчета питания используя FVR

Пример расчета питания используя FVR

Кстати, пример реальный, совпало с показаниями мультиметра PMM-600 до сотых :).

Примеры программ с АЦП

1. Программа для измерения аналогового сигнала на канале AN0 (где переменный резистор). АЦП запускается в прерывании низкого приоритета. Поскольку это прерывание вызывается только таймером 1, можно считать, что АЦП запускается по таймеру 1 с частотой 50 раз в секунду. По готовности данных АЦП вызывает прерывание высокого приоритета, в котором и считываются показания АЦП в переменную adresult1. В основном цикле программы, значение переменной adresult1 присваивается порту D, к которому подключены светодиоды. Таким образом, светодиоды показывают значение старших восьми бит АЦП в двоичном виде.

На рисунке сверху синяя линия — аналоговое напряжение на входе AN0, нижние графики — соответствующие разряды порта D.

Значения АЦП графически.

Значения АЦП графически.

/*
 * File: main_dac.c
 * Author: PRO-DIOD.RU
 *
 */

#include <xc.h> // В этом проекте можно заменить строкой #include <pic18f45k20.h>
#include "config_bits.h"

/*######################################################################################################################
 * Глобальные переменные
 */
// Переменные для кнопки
unsigned char but_ok = 0; // Признак нажатия кнопки:
 // 0 - не нажата
 // 1 - нажата, не обработана
 // 2 - нажата, но уже была обработана
// Переменные для АЦП
unsigned char adresult0 = 0; // Будет для хранения содержимого считанного с регистра ADRESL
unsigned char adresult1 = 0; // Будет для хранения содержимого считанного с регистра ADRESH
//unsigned short adresult = 0; // 10-разрядный результат АЦП

/*######################################################################################################################
 * Г Л А В Н А Я Ф У Н К Ц И Я
 */
int main(void)
{
/* Переменные. Действуют внутри функции main() */
unsigned char but_click = 0; // Номер нажатия на кнопку

/* Включаем генератор */
OSCCON = 0b01110001; // Internal; 16MHz;
OSCTUNEbits.PLLEN = 1; // PLL включен! 16MHz * 4 = 64MHz;

/* Настройка таймера таймера 2 для CCP */
T2CONbits.T2CKPS = 0; // Prescaler = 0b00000000 (1:1)
T2CONbits.T2OUTPS = 0; // Postscaler= 0b00000000 (1:1)
PR2 = 99; // 64M / 4 / 1 / 1 / 99 = 161616Hz (161.6 кГц)
T2CONbits.TMR2ON = 1; // Старт таймера

/* Настройка CCP1 */
CCP1CONbits.CCP1M = 0b00001100; // режим PWM
CCPR1L = 20; // При текущей настройке таймера 2 - это процент заполнения ШИМ

/* Настройка таймера таймера 1 для опроса кнопок */
PIE1bits.TMR1IE = 1; // Включить прерывание (interrupt) TMR1
IPR1bits.TMR1IP = 0; // Установить низкий приоритет для прерывания TMR1
T1CONbits.T1CKPS = 3; // Prescaler = 0b00000011 (1:8)
T1CONbits.RD16 = 1; // 16-бит режим
TMR1 = 25536; // 64M / 4 / 8 / (65536 - 25536) = 50Hz
T1CONbits.TMR1ON = 1; // Старт таймера

/* Настройка АЦП. Измеряем два канала: AN0 и AN1 */
ANSEL = 0; // Все каналы ANx как цифровые
ANSELH = 0; // Все каналы ANx как цифровые
TRISAbits.RA0 = 1; // Как вход
TRISAbits.RA1 = 1; // Как вход
ANSELbits.ANS0 = 1; // AN0 как аналоговый вход
ANSELbits.ANS1 = 1; // AN1 как аналоговый вход
ADCON0bits.CHS = 0b00000000; // Выбрали канал AN0 (CHS = 0)
ADCON1bits.VCFG0 = 0; // +ИОН = источник питания Vdd
ADCON1bits.VCFG1 = 0; // -ИОН = источник питания GND
PIR1bits.ADIF = 0; // Сброс прерываний АЦП
PIE1bits.ADIE = 1; // Разрешили прерывание АЦП
IPR1bits.ADIP = 1; // Установить высокий приоритет для прерывания АЦП
ADCON2bits.ADCS = 0b00000110; // Часта тактирования FOSC/64
ADCON2bits.ADFM = 0; // Выравниваение в лево (удобно при использовании 8 бит)
ADCON2bits.ACQT = 0b00000111; // Время ACQT = 20TAD (33us/4.1us)
ADCON0bits.ADON = 1; // Включили модуль АЦП

/* Настройки прерываний */
INTCONbits.PEIE = 1; // Периферийные прерывания разрешены
INTCONbits.GIE = 1; // Глобальные прерывания разрешены
RCONbits.IPEN = 1; // Разрешить двуприоритетные прерывания

/* Настройка портов I/O */
TRISD = 0x00;// Порт D как выход
LATD = 0x00; // Гасим все светодиоды на PORTD

TRISCbits.RC2 = 0; // Вывод для CCP1 как выход

TRISB = 0x00000001; // RB0 как вход (для кнопки)

TRISCbits.RC1 = 0; // Вывод как выход, используется для отладки

while(1)
 {
 if (but_ok == 1)
 {
 if (but_click < 8)
 {
 but_click++;
 }else
 {
 but_click = 0;
 }
 but_ok = 2; // Кнопка обработана
 }

 LATD = adresult1;

 if (but_click == 0) {CCPR1L = 10;}
 if (but_click == 1) {CCPR1L = 20;}
 if (but_click == 2) {CCPR1L = 30;}
 if (but_click == 3) {CCPR1L = 40;}
 if (but_click == 4) {CCPR1L = 50;}
 if (but_click == 5) {CCPR1L = 60;}
 if (but_click == 6) {CCPR1L = 70;}
 if (but_click == 7) {CCPR1L = 80;}
 if (but_click == 8) {CCPR1L = 90;}

 } // while(1) end
} // main end

/*######################################################################################################################
 * ОБРАБОТЧИК ПРЕРЫВАНИЙ с НИЗКИМ ПРИОРИТЕТОМ
 */
void interrupt low_priority low_isr (void)
{
ADCON0bits.GODONE = 1;
LATCbits.LATC1 = 1; // Метка начала преобразования
if (PIR1bits.TMR1IF && PIE1bits.TMR1IE) // Обработали прерывание от TMR1 (Таймер 1)
 {
 PIR1bits.TMR1IF = 0; // Сбросили флаг прерывания
 TMR1 = 25536; // 64M / 8 / (65536 - 25536) = 200Hz
 // Сканируем кнопку
 if ((but_ok == 0) && (!PORTBbits.RB0))
 {
 but_ok = 1;
 }
 if ((but_ok == 2) && (PORTBbits.RB0))
 {
 but_ok = 0;
 }
 } // if (PIR1bits.TMR1IF && PIE1bits.TMR1IE)
} // low_isr END

/*######################################################################################################################
 * ОБРАБОТЧИК ПРЕРЫВАНИЙ с ВЫСОКИМ ПРИОРИТЕТОМ
 */
void interrupt high_isr (void)
{
// ------------- Прерывание АЦП --------------
if (PIR1bits.ADIF && PIE1bits.ADIE) // Обработали прерывание от AD (АЦП)
 {
 LATCbits.LATC1 = 0; // Метка конца преобразования
 PIR1bits.ADIF = 0; // Сброс прерывания АЦП
 adresult0 = ADRESL;
 adresult1 = ADRESH;
 } // if (PIR1bits.ADIF && PIE1bits.ADIE)
} // high_isr END

Готовый проект:

АЦП, AN0 - LED
АЦП, AN0 - LED
project_adc_led_1.zip
103.3 KiB
101 Downloads
Детали

2. Программа для измерения аналогового сигнала на канале AN1 (где конденсатор RC-цепочки ЦАП на ШИМ). Абсолютно все то же самое, что и в прерыдущем примере, за исключением строчки 60.

Было: ADCON0bits.CHS      = 0b00000000; // Выбрали канал AN0 (CHS = 0)

Стало: ADCON0bits.CHS      = 0b00000001; // Выбрали канал AN1 (CHS = 1)

На AN1 заведено аналоговое напряжение с RC-цепи. В ранее показанном примере, коэффициент заполнения ШИМ меняется от нажатия на кнопку. В этом примере аналогично, но при этом получившееся аналоговое напряжение измеряется АЦП и результат показывается светодиодами в двоичном виде.

АЦП и ЦАП на ШИМ

АЦП и ЦАП на ШИМ

Готовый проект:

АЦП, ШИМ->AN1
АЦП, ШИМ->AN1
project_adc_led_2.zip
103.3 KiB
75 Downloads
Детали

3. Программа для измерения FVR. Данные АЦП в этом случае зависят от величины питающего напряжения. Для этого необходимо выбрать для АЦП канал 15:

ADCON0bits.CHS      = 0b00001111; // Выбрали канал FVR (CHS = 15)

Вывод аналогично другим примерам — в двоичном виде на светодиоды. Готовый проект:

АЦП, FVR
АЦП, FVR
project_adc_led_3.zip
103.3 KiB
89 Downloads
Детали

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

Вопросы, пожелания, критика, что-то еще? Пишите в комментариях! 🙂

Метки:: , ,

Ваш отзыв