AVR120: Снятие характеристик и калибровка АЦП микроконтроллеров AVR. Общий принцип работы ацп Atmega8 на каких ножках вход ацп

На средние волосы 12.04.2023
На средние волосы

Часто бывает потребность замерять напряжения. Для этих целей в микроконтроллере есть АЦП (аналого-цифровой преобразователь). АЦП - это устройство, которое преобразует аналоговый сигнал в его цифровое представление. На вход АЦП подается аналоговый сигнал, а на выходе мы получаем эквивалентный цифровой сигнал.

Основные характеристики АЦП

  • Частота преобразования - это сколько раз в секунду АЦП сможет измерить напряжение
  • Разрядность - количество дискретных значений напряжения, на который делится весь рабочий диапазон входных напряжений. АЦП в AVR десяти разрядные. То есть, максимальное напряжение на входе АЦП будет переводиться в 2 10 =1024
  • Диапазон входных напряжений - это минимальное и максимальное напряжение, которое можно подавать на входы АЦП. Для avr это диапазон от 0 до напряжения питания микроконтроллера
Для работы АЦП необходим источник опорного напряжения (ИОН). Это эталон, по отношению к которому он измеряет напряжение на входе. В AVR в качестве источника опорного напряжения может выступать напряжения питания МК, источник опорного напряжения, подключенный к ножке ARef и внутренний ИОН на 2,56 в. ИОН должен быть как можно стабильней, от этого зависит точность измерений. Чтобы пощупать все это, давайте сделаем простой вольтметр на 5в. Запускаем CVAVR, на вопрос запустить CodeWizardAVR кликаем "да" и переходим во вкладку ADC

Нам для нашего вольтметра нужно установить источник опорного напряжения на ножке AVCC (ножка питание АЦП ), частота преобразования 500 килогерц

Мы наши измерения с АЦП будем выводить на lcd-дисплей, для его инициализации переходим во вкладку LCD и устанавливаем все, как на скриншоте

Теперь все настройки выполнены, кликаем file->Generate. save and exit . Дописываем код, который сгенерировал CWAVR, и убираем в нём инициализации периферии МК, которые мы не используем, получается следующий код:

#include #include #include // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x12 ;PORTD #endasm #include #define ADC_VREF_TYPE 0x40 // Read the AD conversion result unsigned int read_adc(unsigned char adc_input) { ADMUX=adc_input | (ADC_VREF_TYPE & 0xff); // Delay needed for the stabilization of the ADC input voltage delay_us(10); // Start the AD conversion ADCSRA|=0x40; // Wait for the AD conversion to complete while ((ADCSRA & 0x10)==0); ADCSRA|=0x10; return ADCW; } void main(void) { char lcd_buffer; unsigned int u; // ADC initialization // ADC Clock frequency: 500,000 kHz // ADC Voltage Reference: AVCC pin ADMUX=ADC_VREF_TYPE & 0xff; ADCSRA=0x81; // LCD module initialization lcd_init(16); while (1) { /*так как АЦП у нас 10-битный, то максимальное число, которое вернет функция, read_adc() будет равно 1024, это число будет эквивалентом напряжения на входе adc0. Например, если read_adc() вернул 512, то это значит, что на вход adc0 мы подали половину опорного напряжения Чтобы вычислить реальное напряжение, нам нужно составить пропорцию опорное напряжение - 1024 искомое напряжение - adc У нас опорное напряжение = 5 Искомое напряжение = 5 * adc/1024, или Искомое напряжение = 0,005*adc для простоты переведём вольты в миливольты, домножив на 1000 Искомое напряжение = 0,005*adc*1000 */ u=read_adc(0) * 5;//вызываем функцию для измерения напряжения и передаем ей номер ножки, на которой нужно измерить напряжение lcd_clear(); //чистим дисплей перед выводом lcd_gotoxy(0,0); // перевод курсор в положение x=0 y=0 sprintf(lcd_buffer,"U = %i mv",u); // формируем строку для вывода lcd_puts(lcd_buffer); //выводим строку на дисплей delay_us(500); //делаем задержку 500 мл }; }

Программа готова, дело за схемой

Схема очень простая, на ней мы видим микроконтроллер atmega8 и lcd-дисплей знакосинтезирующий 16х2 (пример работы с lcd описан ). Наш простой вольтметр измеряет напряжения до 5 в. Как измерять напряжения больше 5 в Схема выполнена в Proteus, все необходимые файлы для этого урока находятся в архиве

Основные особенности АЦП

Микроконтроллер stm32f1xx имеет на борту 3 12-ти разрядных АЦП. Каждое АЦП может быть подключено к любому из 16-ти аналоговых входов. Более того, каждое из АЦП может сканировать эти входы, снимая с них данные в заданном пользователем порядке.
По окончании преобразования АЦП может выдать прерывание. Вообще АЦП может выдать одно из трёх прерываний: Об окончании преобразования обычного (регулярного) канала, об окончании преобразования по инжекторному каналу и событие по Watchdog.
В режиме сканирования прерывание об окончании преобразования выдаётся только по завершении всего сканирования. И при использовании регулярных каналов, в которых данные записываются всегда в один и тот же регистр, вы будете получать результаты только последнего преобразования.
Что бы этого не происходило, в микроконтроллере предусмотрено наличие так называемых инжекторных каналом, имеющих в своём наличии 4 разных регистра для записи данных. Т.е. если вам надо сканировать не более 4-х каналов, то результаты преобразований вы не потеряете. Т.к. каждый канал будет писать данные в свой регистр.
Для параллельного снятия данных сразу по нескольким каналам, предусмотрена возможность одновременного запуска нескольких АЦП. Данный режим получил название Dual Mode.

Подключение АЦП

Прежде всего рассмотрим подключение АЦП. Для чего нужна каждая ножка показано в таблице 1.

Таблица 1

Из перечисленных ножек интересны -Vоп и +Vоп. Они определяют диапазон напряжений, воспринимаемых АЦП. Если подключить -Vоп к земле, а +Vоп к питанию, то АЦП сможет оцифровать аналоговые сигналы во всём диапазоне от 0, до питания. Т.к. питания МК составляет 3,3В, а разрядность АЦП равна 12-ти, т.е. мы имеем 2^12=4096 уровней квантовая, шум АЦП составит 3,3/4096=0,8 мВ.

Виды АЦП

В микроконтроллере существует 2 вида каналов АЦП: регулярные и инжекторные. Эти 2 канала настраиваются независимо. Но работать может только один из них для каждого канала. Основным различием этих каналов является то, что для хранения данных, получаемых с помощью регулярного канала используется только один регистр. Это не плохо, если вам надо снять за один раз данные только с одного канала для каждого АЦП. Но, если Вам надо производить сканирование данных, то все снятые данные будут записываться с один и тот же регистр. Т.о. при чтении данных в прерывании по окончании преобразования Вы будете получать только последние снятые данные. Эту проблему призваны исправить инжекторные каналы. У них предусмотрены 4 регистра для хранения данных. Т.е. Вы сможете хранить данные с 4-х каналов сканирования. Недостатком инжекторных каналов является несколько более сложная система настройки, в которой надо описать данные, с какого канала в какой регистр будут записаны.

Настройка регулярного канала

Рассмотрим настройку регулярного канала АЦП. Настроим АЦП на ножке А4. Прежде всего, надо узнать какие АЦП имеют доступ к этой ножке и какие каналы на неё выведены. В частности это 4-й канал первого АЦП.
Как обычно используем стандартную схему:
1) Включить тактирование порта
2) Настроить вывод
3) Включить тактирование АЦП
4) Настроить АЦП
5) Включить нужные прерывания
6) Включить глобальные прерывания
7) Включить АЦП

При настройке порта главное в режиме задать аналоговый режим.

Настройка вывода в аналоговом режиме

GPIO_InitTypeDef GPIO_Init_user;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

GPIO_Init_user.GPIO_Pin = GPIO_Pin_4;
GPIO_Init_user.GPIO_Mode = GPIO_Mode_AN;
GPIO_Init_user.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init_user.GPIO_OType = GPIO_OType_PP;
GPIO_Init_user.GPIO_PuPd = GPIO_PuPd_NOPULL;

GPIO_Init(GPIOA, & GPIO_Init_user);


Включаем тактирование АЦП:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

Настраиваем АЦП:

Настройка регулярного канала АЦП

ADC_InitTypeDef ADC_InitType;

ADC_InitType.ADC_ContinuousConvMode = DISABLE;
ADC_InitType.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitType.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
ADC_InitType.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitType.ADC_NbrOfConversion = 1;
ADC_InitType.ADC_Resolution = ADC_Resolution_12b;
ADC_InitType.ADC_ScanConvMode = DISABLE;

ADC_Init(ADC1, &ADC_InitType);


Рассмотрим настройки подробнее:
ContinuousConvMode – Этот режим, если включен, запускает следующее преобразование сразу по окончании предыдущего. Так можно добиться максимальной скорости работы АЦП. В нашем случае это не надо и данная функция отключена.
DataAlign – выравнивание данных в 2-хбайтном слове. Есть 2 варианта. ADC_DataAlign_Right при котором данные выравниваются по правому краю, а неиспользуемые биты при этом равны нулю. Т.е. мы получаем обычные числа в 2-х байтах от 0 до 8192. При ADC_DataAlign_Left данные выравниваются по левому краю. Т.е. фактически для 12-ти битного преобразования они увеличиваются в 16 раз. Это может быть использовано например при передаче их через SPI, поддерживающий 12-ти битную передачу данных. Если настроить SPI на передачу начиная со старшего разряда. ExternalTrigConvEdge – настраивает запуск преобразования по какому либо событию, например переполнению таймера. В нашем случае не требуется.
ExternalTrigConv – Устанавливает какие именно события запустят АЦП. Т.к. триггер отключен, то эта функция не используется.
NbrOfConversion – число каналов, которые будет сканировать МК. Сюда записывается требуемое значение, а ниже, если это число больше 1 и ADC_ ScanConvMode=ENABLE, описывается какие каналы и в какой последовательности они будут сканироваться
ScanConvMode – Этот параметр определяет будет ли АЦП сканировать несколько каналов. Если этот режим включен, то АЦП будет последовательно оцифровывать данные с заданных каналов в заданной последовательности. И каналы и последовательность легко можно задать. Но возникает небольшая проблема со снятием данных.

Настраиваем конкретный канал. В нашем случае это всего один канал, потому настройка будет выглядеть так:

ADC_RegularChannelConfig(ADC1,ADC_Channel_4,1, DC_SampleTime_56Cycles);

Из параметров тут:
ADC1 – номер настраиваемого АЦП.
ADC_Channel_4 задаёт снимаемый канал.
1 – так называемый rank. Показывает в каком порядке этот канал будет оцифровываться. В нашем случае канал один, потому и rank=1.
DC_SampleTime_56Cycles – задаёт за какое время будет произведена оцифровка. Чем медленнее, тем точнее.

Теперь осталось настроить прерывания и включить:

NVIC_EnableIRQ(ADC_IRQn);
ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);

ADC_Cmd(ADC1, ENABLE);

На этом настройка закончена.

Чтобы запустить преобразование, используйте функцию:

ADC_SoftwareStartConv(ADC1);

По окончании преобразования программа попадёт в функцию прерывания:

Void ADC_IRQHandler(void)
{
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
ADC_result = ADC_GetConversionValue(ADC1);
}

Сбрасываем флаг и считываем результат преобразования.
Можно скачать пример работы от

Аналого-цифровые преобразователи (АЦП) являются устройствами, которые принимают входные аналоговые сигналы и генерируют соответствующие им цифровые сигналы, пригодные для обработки микропроцессорами и другими цифровыми устройствами. АЦП входит во многие современные модели МК AVR , он многоканальный. Обычно число каналов равно 8, но в разных моделях оно может варьировать от 4 каналов в младших моделях семейства Tiny, 6 в ATmega8, до 16 каналов в ATmega2560.

Многоканальность означает, что на входе единственного модуля АЦП установлен аналоговый мультиплексор, который может подключать этот вход к различным выводам МК для осуществления измерений нескольких независимых аналоговых величин с разнесением по времени. Входы мультиплексора могут работать по отдельности (в несимметричном режиме для измерения напряжения относительно "земли") или (в некоторых моделях) объединяться в пары для измерения дифференциальных сигналов. Иногда АЦП дополнительно снабжается усилителем напряжения с фиксированными значениями коэффициента усиления 10 и 200.

Сам АЦП представляет собой преобразователь последовательного приближения с устройством выборки-хранения и фиксированным числом тактов преобразования, равным 13 (или 14 для дифференциального входа; первое преобразование после включения потребует 25 тактов для инициализации АЦП ). Тактовая частота формируется аналогично тому, как это делается для таймеров- с помощью специального предделителя тактовой частоты МК, который может иметь коэффициенты деления от 1 до 128. Но в отличие от таймеров, выбор тактовой частоты АЦП не совсем произволен, т. к. быстродействие аналоговых компонентов ограничено. Поэтому коэффициент деления следует выбирать таким, чтобы при заданном "кварце" тактовая частота АЦП укладывалась в рекомендованный диапазон 50-200 кГц (т. е. максимум около 15 тыс. измерений в секунду). Увеличение частоты выборки допустимо, если не требуется достижение наивысшей точности преобразования.

Разрешающая способность АЦП в МК AVR - 10 двоичных разрядов, чего для большинства типовых применений достаточно. Абсолютная погрешность преобразования зависит от ряда факторов и в идеальном случае не превышает ±2 младших разрядов, что соответствует общей точности измерения примерно 8 двоичных разрядов. Для достижения этого результата необходимо принимать специальные меры: не только "вгонять" тактовую частоту в рекомендованный диапазон, но и снижать по максимуму интенсивность цифровых шумов. Для этого рекомендуется, как минимум, не использовать оставшиеся выводы того же порта, к которому подключен АЦП, для обработки цифровых сигналов, правильно разводить платы, а как максимум - дополнительно к тому еще и включать специальный режим ADC Noise Reduction .

Регистры управления АЦП

ADCSR

Режим непрерывных измерений активизируется установкой бита ADFR (бит 5) этого же регистра. В ряде моделей Mega этот бит носит наименование ADATE , и управление режимом работы производится сложнее: там добавляются несколько режимов запуска через различные прерывания (в т. ч. прерывание от компаратора, при наступлении различных событий от таймера и т. п.), и выбирать их следует, задавая биты ADTS регистра SFIOR , а установка бита ADATE разрешает запуск АЦП по этим событиям.

Разряд Название Описание
5 ADFR(ADATE) Выбор режима работы АЦП

Так как нулевые значения всех битов ADTS (по умолчанию) означают режим непрерывного преобразования, то в случае, когда вы их значения не трогали, функции битов ADATE и ADFR в других моделях будут совпадать.

ADTS2 ADTS1 ADTS0 Источник стартового сигнала
0 0 0 Режим непрерывного преобразования
0 0 1 Прерывание от аналогового компаратора
0 1 0 Внешнее прерывание INT0
0 1 1 Прерывание по событию "Совпадение" таймера/счетчика Т0
1 0 0 Прерывание по переполнению таймера/счетчика Т0
1 0 1 Прерывание по событию "Совпадение" таймера/счетчика Т1
1 1 0 Прерывание по переполнению таймера/счетчика Т1
1 1 1 Прерывание по событию "Захват" таймера/счетчика Т1

Если выбран режим запуска не от внешнего источника, то преобразование запускается установкой бита ADSС (бит 6). При непрерывном режиме установка этого бита запустит первое преобразование, затем они будут автоматически повторяться. В режиме однократного преобразования, а также независимо от установленного режима при запуске через прерывания (в тех моделях, где это возможно) установка бита ADSС просто запускает одно преобразование. При наступлении прерывания, запускающего преобразование, бит ADSС устанавливается аппаратно. Отметим, что преобразование начинается по-фронту первого тактового импульса (тактового сигнала АЦП, а не самого контроллера!) после установки ADSС . По окончании любого преобразования (и в одиночном, и в непрерывном режиме) устанавливается бит ADIF (бит 4. флаг прерывания). Разрешение прерывания АЦП осуществляется установкой бита ADIE (бит 3) все того же регистра ADCSR/ADCSRA .

Для работы с АЦП необходимо еще установить его тактовую частоту. Это делается тремя младшими битами регистра ADCSR/ADCSRA под названием ADPS0..2. Коэффициент деления частоты тактового генератора МК устанавливается по степеням двойки, все нули в этих трех битах соответствуют коэффициенту 2, все единицы - 128. Оптимальная частота преобразования лежит в диапазоне 50-200 кГц, так что, например, для тактовой частоты МК, равной 4 МГц, коэффициент может иметь значение только 32 (состояние битов ADPS0..2 = 101, частота 125 кГц) или 64 (состояние битов ADPS0..2 = 110, частота 62,5 кГц). При тактовой частоте 16 МГц в допустимый диапазон укладывается только коэффициент 128.

ADPS2 ADPS1 ADPS0 Коэффициент деления
0 0 0 2
0 0 1 2
0 1 0 4
0 1 1 8
1 0 0 16
1 0 1 32
1 1 0 64
1 1 1 128

Ниже приведена таблица с описанием регистра ADMUX.



Выборка источника опорного напряжения производится битами REFS1..0 регистра ADMUX (старшие биты 7 и 6), причем их нулевое значение (по умолчанию) соответствует внешнему источнику. Напряжение этого внешнего источника может лежать в пределах от 2 В до напряжения питания аналоговой части AVcc (а оно, в свою очередь, не должно отличаться от питания цифровой части более чем на 0,3 В в большую или меньшую сторону). Можно выбрать в качестве опорного и питание самой аналоговой части, причем двояким способом: либо просто соединить выводы AREF и AVcc микросхемы, либо установить биты REFS1..0 в состояние 01 (тогда соединение осуществляется внутренними схемами, но заметим, что внешний опорный источник при этом должен быть отключен). Предусмотрен и встроенный источник (задается REFS1..0 в состоянии 11, при этом к выводу AREF рекомендуется подключать фильтрующий конденсатор), имеющий номинальное напряжение 2,56В с большим разбросом от 2,4 до 2,7 В.

REFS1 REFS0 Источник опорного напряжения
0 0 Внешний ИОН, подключенный к выводу AREF, внутренний ИОН отключен
0 1 Напряжение питания AVcc*
1 0 Зарезервировано
1 1 Внутренний ИОН напряжением 2,56V, подключенный к ввыводу AREF*
*Если к выводу AREF подключен источник напряжения, данные варианты использоваться не могут

Результат преобразования АЦП оказывается в регистрах ADCH:ADCL . Поскольку результат 10-разрядный, то по умолчанию старшие 6 битов в регистре ADCH оказываются равными нулю. Чтение этих регистров производится, начиная с младшего ADCL , после чего регистр ADCH блокируется, пока не будет прочитан. Следовательно, даже если момент между чтением регистров попал на фронт 14 (15) такта АЦП, когда данные в них должны меняться, значения прочитанной пары будут соответствовать друг другу, пусть и результат этого преобразования пропадет. В противоположном порядке читать эти регистры не рекомендуется. Но бит ADLAR (бит 5 регистра ADMUX ) предоставляет интересную возможность: если его установить в 1, то результат преобразования в регистрах ADCH:ADCL выравнивается влево: бит 9 результата окажется в старшем бите ADCH , а незначащими будут младшие 6 битов регистра ADCL . В этом случае, если хватает 8-разрядного разрешения результата, можно прочесть только значение ADCH .

class="eliadunit">

Выбор каналов и режимов их взаимодействия в АЦП производится битами MUX0..3 в регистре ADMUX . Их значения выбирают нужный канал в обычном (недифференциальном) режиме, когда измеряемое напряжение отсчитывается от "земли". Последние два значения этих битов для семейства Mega (11110 и 11111 в большинстве моделей или 1110 и 1111 для ATmega8) выбирают режимы, когда вход АЦП подсоединяется к опорному источнику компаратора (1,22 В) или к "земле" соответственно, что может использоваться для автокалибровки устройства.

Управление входным мультиплексором в моделях Atmega8x

MUX3-MUX0 Несимметричный вход
0000 ADC0
0001 ADC1
0010 ADC2
0011 ADC3
0100 ADC4*
0101 ADC5*
0110 ADC6**
0111 ADC7**
1000-1101 Зарезервировано
1110 1,22V
1111 0V(GND)

*8-ми разрядное преобразование

**Имеются только в корпусах TQFP-32 и MLF-32.

Остальные комбинации разрядов MUX предназначены для установки различных дифференциальных режимов - в тех моделях, где они присутствуют, в других случаях эти биты зарезервированы (как в моделях Atmega8, ATmega163 и др.). В дифференциальном режиме АЦП измеряет напряжение между двумя выбранными выводами (например, между ADC0 и ADC1 ), причем не все выводы могут быть в таком режиме задействованы. В том числе дифференциальные входы АЦП можно подключать к одному и тому же входу для коррекции нуля. Дело в том, что в ряде моделей на входе АЦП имеется встроенный усилитель, с коэффициентом 1х, 10х и 200х (коэффициент выбирается теми же битами MUX0..4 ), и такой режим используется для его калибровки - в дальнейшем значение выхода при соединенных входах можно просто вычесть.

После завершения преобразования (при установке в «1» флага ADIF регистра ADCSR ) его результат сохраняется в регистре данных АЦП . Поскольку АЦП имеет 10 разрядов, этот регистр физически размещен в двух регистрах ввода/вывода ADCH:ADCL , доступных только для чтения. По умолчанию результат преобразования выравнивается вправо (старшие 6 разрядов регистра ADCH - незначащие). Однако он может выравниваться и влево (младшие 6 разрядов регистра ADCL - незначащие). Для управления выравниванием результата преобразования служит разряд ADLAR регистра ADMUX . Если этот разряд установлен в «1», результат преобразования выравнивается по левой границе 16-разрядного слова, если сброшен в «0» - по правой границе.

Обращение к регистрам ADCH и ADCL для получения результата преобразования должно выполняться в определенной последовательности: сначала необходимо прочитать регистр ADCL , а затем ADCH . Это требование связано с тем, что после обращения к регистру ADCL процессор блокирует доступ к регистрам данных со стороны АЦП до тех пор, пока не будет прочитан регистр ADCH. Благодаря этому можно быть уверенным, что при чтении регистров в них будут находиться составляющие одного и того же результата. Соответственно, если очередное преобразование завершится до обращения к регистру ADCH , результат преобразования будет потерян. С другой стороны, если результат преобразования выравнивается влево и достаточно точности 8-разрядного значения, для получения результата можно прочитать только содержимое регистра ADCH .

Для недифференциального режима АЦП, когда напряжение отсчитывается от "земли", результат преобразования определяется формулой:

Ка = 1024Uвх/Uref

Где Ка - значение выходного кода АЦП, Uвх и Uref - входное и опорное напряжения.

Дифференциальному измерению соответствует такая формула:

Ка = 512(Upos - Uneg)/Uref

Где Upos и Uneg - напряжения на положительном и отрицательном входах соответственно. Если напряжение на отрицательном входе больше, чем на положительном, то результат в дифференциальном режиме становится отрицательным и выражается в дополнительном коде от $200 (-512) до $3FF (-1). Реальная точность преобразования в дифференциальном режиме равна 8 разрядам.

Делаем светодиодный индикатор напряжения

Для практического изучения АЦП напишем программу светодиодного индикатора напряжения. Как и в прошлых примерах будем использовать микроконтроллер Atmega8. Восемь индикаторов подключаем к порту D контроллера, это будет линейная шкала уровня сигнала от 0 до 5V. Входом АЦП у нас будет вывод PC0(ADC0), к которому через переменный резистор сопротивлением 10кОм подается напряжение. Схема устройства представлена ниже:

К точности АЦП в этом устройстве предъявляются наименьшие требования. Источником опорного напряжения служит напряжение питания микроконтроллера - 5 Вольт, для этого вывод AREF соединяем с выводом Vcc микроконтроллера, также поступаем с выводами питания аналоговой части AVcc и AGND , подключаем их к плюсу и минусу соответственно, в программе битами REFS1 и REFS0 задаем источник ИОН .

Режим индикации работает следующим образом: после окончания преобразования, которое работает в непрерывном режиме, считываем биты ADCH и ADCL . Это значение потом сравниваем с предварительно расчитанными константами. Если значение ADC больше константы загорается один светодиод, если значение ADC больше второй константы загораются уже два светодиода и т.д.

Константы высчитываются так: так как АЦП 10-ти битный, число 1024 раскладываем на 8 равных частей, а по формуле уже вычисляем эти значения в Вольтах.

1020...5V(приблизительно)

Полный код программы показан ниже. Частота тактового генератора контроллера 8MHz.

/*** Использование АЦП. Светодиодная шкала ***/ #include #include int main (void) { DDRD = 0xFF; PORTD = 0x00; /*** Настройка АЦП ***/ ADCSRA |= (1 << ADEN) // Включение АЦП |(1 << ADPS1)|(1 << ADPS0); // предделитель преобразователя на 8 ADMUX |= (0 << REFS1)|(0 << REFS0) // внешний ИОН |(0 << MUX0)|(0 << MUX1)|(0 << MUX2)|(0 << MUX3); // вход PC0 while(1) { unsigned int u; ADCSRA |= (1 << ADSC); // Начинаем преобразование while ((ADCSRA&(1 << ADIF))== 0); // Ждем флага окончания преобразования u = (ADCL|ADCH << 8); // Считываем ADC if (u > 128) // 0.625V PORTD = 0b00000001; else PORTD = 0b00000000; if (u > 256) // 1.25V PORTD = 0b00000011; if (u > 384) // 1.875V PORTD = 0b00000111; if (u > 512) // 2.5V PORTD = 0b00001111; if (u > 640) // 3.125V PORTD = 0b00011111; if (u > 768) // 3.75V PORTD = 0b00111111; if (u > 896) // 4.375V PORTD = 0b01111111; if (u > 1020) // 5V PORTD = 0b11111111; _delay_ms(30); } }

В следующем примере мы разберем принципы создания вольтметра 0-30V на микроконтроллере Atmega8.

Аналого-цифровые преобразователи (АЦП) являются устройствами, которые принимают входные аналоговые сигналы и генерируют соответствующие им цифровые сигналы, пригодные для обработки микропроцессорами и другими цифровыми устройствами.

АЦП входит во многие современные модели МК AVR, он многоканальный. Обычно число каналов равно 8, но в разных моделях оно может варьировать от 4 каналов в младших моделях семейства Tiny, 6 в ATmega8, до 16 каналов в ATmega2560. Многоканальность означает, что на входе единственного модуля АЦП установлен аналоговый мультиплексор, который может подключать этот вход к различным выводам МК для осуществления измерений нескольких независимых аналоговых величин с разнесением по времени. Входы мультиплексора могут работать по отдельности (в несимметричном режиме для измерения напряжения относительно "земли") или (в некоторых моделях) объединяться в пары для измерения дифференциальных сигналов. Иногда АЦП дополнительно снабжается усилителем напряжения с фиксированными значениями коэффициента усиления 10 и 200.

Сам АЦП представляет собой преобразователь последовательного приближения с устройством выборки-хранения и фиксированным числом тактов преобразования, равным 13 (или 14 для дифференциального входа; первое преобразование после включения потребует 25 тактов для инициализации АЦП). Тактовая частота формируется аналогично тому, как это делается для таймеров- с помощью специального предделителя тактовой частоты МК, который может иметь коэффициенты деления от 1 до 128. Но в отличие от таймеров, выбор тактовой частоты АЦП не совсем произволен, т. к. быстродействие аналоговых компонентов ограничено. Поэтому коэффициент деления следует выбирать таким, чтобы при заданном "кварце" тактовая частота АЦП укладывалась в рекомендованный диапазон 50-200 кГц (т. е. максимум около 15 тыс. измерений в секунду). Увеличение частоты выборки допустимо, если не требуется достижение наивысшей точности преобразования.

Разрешающая способность АЦП в МК AVR - 10 двоичных разрядов, чего для большинства типовых применений достаточно. Абсолютная погрешность преобразования зависит от ряда факторов и в идеальном случае не превышает ±2 младших разрядов, что соответствует общей точности измерения примерно 8 двоичных разрядов. Для достижения этого результата необходимо принимать специальные меры: не только "вгонять" тактовую частоту в рекомендованный диапазон, но и снижать по максимуму интенсивность цифровых шумов. Для этого рекомендуется, как минимум, не использовать оставшиеся выводы того же порта, к которому подключен АЦП, для обработки цифровых сигналов, правильно разводить платы, а как максимум - дополнительно к тому еще и включать специальный режим ADC Noise Reduction.

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

Регистры управления АЦП

Для разрешения работы АЦП необходимо записать лог. 1 в разряд ADEN регистра ADCSR, а для выключения - лог. 0. Если АЦП будет выключено во время цикла преобразования, то преобразование завершено не будет (в регистре данных АЦП останется результат предыдущего преобразования).

Режим непрерывных измерений активизируется установкой бита ADFR (бит 5) этого же регистра. В ряде моделей Mega этот бит носит наименование ADATE, и управление режимом работы производится сложнее: там добавляются несколько режимов запуска через различные прерывания (в т. ч. прерывание от компаратора, при наступлении различных событий от таймера и т. п.), и выбирать их следует, задавая биты ADTS регистра SFIOR, а установка бита ADATE разрешает запуск АЦП по этим событиям. Так как нулевые значения всех битов ADTS (по умолчанию) означают режим непрерывного преобразования, то в случае, когда вы их значения не трогали, функции битов ADATE и ADFR в других моделях будут совпадать.

Если выбран режим запуска не от внешнего источника, то преобразование запускается установкой бита ADTS (бит 6 того же регистра ADCSR/ADCSRA). При непрерывном режиме установка этого бита запустит первое преобразование, затем они будут автоматически повторяться. В режиме однократного преобразования, а также независимо от установленного режима при запуске через прерывания (в тех моделях, где это возможно) установка бита ADCS просто запускает одно преобразование. При наступлении прерывания, запускающего преобразование, бит ADCS устанавливается аппаратно. Отметим, что преобразование начинается по-фронту первого тактового импульса (тактового сигнала АЦП, а не самого контроллера!) после установки ADCS. По окончании любого преобразования (и в одиночном, и в непрерывном режиме) устанавливается бит ADIF (бит 4. флаг прерывания). Разрешение прерывания АЦП осуществляется установкой бита ADIE (бит 3) все того же регистра ADCSR/ADCSRA.

Для работы с АЦП необходимо еще установить его тактовую частоту. Это делается тремя младшими битами регистра ADCSR/ADCSRA под названием ADPS0..2. Коэффициент деления частоты тактового генератора МК устанавливается по степеням двойки, все нули в этих трех битах соответствуют коэффициенту 2, все единицы - 128. Оптимальная частота преобразования лежит в диапазоне 50-200 кГц, так что, например, для тактовой частоты МК, равной 4 МГц, коэффициент может иметь значение только 32 (состояние битов ADPS0..2 = 101, частота 125 кГц) или 64 (состояние битов ADPS0..2 = 110, частота 62,5 кГц). При тактовой частоте 16 МГц в допустимый диапазон укладывается только коэффициент 128.

Выборка источника опорного напряжения производится битами REFS1..0 регистра ADMUX (старшие биты 7 и 6), причем их нулевое значение (по умолчанию) соответствует внешнему источнику. Напряжение этого внешнего источника может лежать в пределах от 2 В до напряжения питания аналоговой части AVcc (а оно, в свою очередь, не должно отличаться от питания цифровой части более чем на 0,3 В в большую или меньшую сторону). Можно выбрать в качестве опорного и питание самой аналоговой части, причем двояким способом: либо просто соединить выводы AREF и AVcc микросхемы, либо установить биты REFS1..0 в состояние 01 (тогда соединение осуществляется внутренними схемами, но заметим, что внешний опорный источник при этом должен быть отключен). Предусмотрен и встроенный источник (задается REFS1..0 в состоянии 11, при этом к выводу AREF рекомендуется подключать фильтрующий конденсатор), имеющий номинальное напряжение 2,56В с большим разбросом от 2,4 до 2,7 В.

*****REFS*******

Результат преобразования АЦП оказывается в регистрах ADCH:ADCL. Поскольку результат 10-разрядный, то по умолчанию старшие 6 битов в регистре ADCH оказываются равными нулю. Чтение этих регистров производится, начиная с младшего ADCL, после чего регистр ADCH блокируется, пока не будет прочитан. Следовательно, даже если момент между чтением регистров попал на фронт 14 (15) такта АЦП, когда данные в них должны меняться, значения прочитанной пары будут соответствовать друг другу, пусть и результат этого преобразования пропадет. В противоположном порядке читать эти регистры не рекомендуется. Но бит ADLAR (бит 5 регистра ADMUX) предоставляет интересную возможность: если его установить в 1, то результат преобразования в регистрах ADCH:ADCL выравнивается влево: бит 9 результата окажется в старшем бите ADCH, а незначащими будут младшие 6 битов регистра ADCL. В этом случае, если хватает 8-разрядного разрешения результата, можно прочесть только значение ADCH.

Выбор каналов и режимов их взаимодействия в АЦП производится битами MUX0..3 в регистре ADMUX. Их значения выбирают нужный канал в обычном (недифференциальном) режиме, когда измеряемое напряжение отсчитывается от "земли". Последние два значения этих битов для семейства Mega (11110 и 11111 в большинстве моделей или 1110 и 1111 для ATmega8) выбирают режимы, когда вход АЦП подсоединяется к опорному источнику компаратора (1,22 В) или к "земле" соответственно, что может использоваться для автокалибровки устройства.

Остальные комбинации разрядов MUX предназначены для установки различных дифференциальных режимов - в тех моделях, где они присутствуют, в других случаях эти биты зарезервированы (как в моделях Atmega8, ATmegal63 и др.). В дифференциальном режиме АЦП измеряет напряжение между двумя выбранными выводами (например, между ADC0 и ADC1), причем не все выводы могут быть в таком режиме задействованы. В том числе дифференциальные входы АЦП можно подключать к одному и тому же входу для коррекции нуля. Дело в том, что в ряде моделей на входе АЦП имеется встроенный усилитель, с коэффициентом 1х, 10х и 200х (коэффициент выбирается теми же битами MUX0..4), и такой режим используется для его калибровки - в дальнейшем значение выхода при соединенных входах можно просто вычесть.

Для недифференциального режима АЦП, когда напряжение отсчитывается от "земли", результат преобразования определяется формулой: Ка = 1024Uвх/Uref, где Ка - значение выходного кода АЦП, Uвх и Uref - входное и опорное напряжения. Дифференциальному измерению соответствует такая формула: Ка = 512(Upos - Uneg)/Uref, где Upos и Uneg - напряжения на положительном и отрицательном входах соответственно. Если напряжение на отрицательном входе больше, чем на положительном, то результат в дифференциальном режиме становится отрицательным и выражается в дополнительном коде от $200 (-512) до $3FF (-1). Реальная точность преобразования в дифференциальном режиме равна 8 разрядам.

На прошлом уроке мы собрали свой первый программатор и прошили с его помощью МК. Ну что, поздравляю вас с первым реальным проектом. Мигание светодиодом это конечно хорошо, но давайте займемся чем-нибудь по интереснее. Сегодня я расскажу как работать с АЦП(аналогово-цифровой преобразователь). Ну поехали. Перед тем как браться за программирование, давайте вернемся в школу. Да, да именно в школу, на урок алгебры и вспомним что же такое график. Из своего детства я помню, что при построении графика по той или иной функции многие из класса выпадали в осадок. Да, да именно графики были не понятны как и векторы. Хотя что там не понять. А может просто преподаватель плохо объяснял. Ну я попробую удалить это белое пятно. Те кто могут с ходу нарисовать график Х = У могут не читать эту часть текста. Для тех кто не понимает как это делать, лучше внимательно прочесть. Давайте рассмотрим выражение Х = У . У нас есть неизвестная Х и неизвестная У . Если мы возьмем любое число и подставим скажем под Х , то соответственно У примет то же значение. Ну это само собой, ведь у нас знак равенства. А как теперь по этому уравнению построить график. А очень просто. Первое что нужно понять, так это то что любой график является прямой или кривой состоящие из множества точек. Что это значит. А значит это то, что если вы возьмете фломастер и попытаетесь нарисовать прямую, только не проведя по листу, а ставя точки одну за другой, то вы получите некую прямую или кривую. Вот она больше всего подходит к понятию график. Но просто нарисованная линия на листе это еще не все. Ведь нам надо строить линию точно по уравнению. А как это сделать? Вот тут нам поможет координатная ось. Что это такое. Вот что можно сделать при помощи простой деревянной линейки? Правильно, измерить длину. А в чем? Конечно в мм или см . Глупый вопрос скажите вы, но будете не правы. Та шкала на которую вы смотрите для определения длины и есть координатная ось. А в каких единицах она, это все равно. Ведь у англичан она в дюймах. Теперь давайте представим что наша линейка имеет значения в Х -ах. И равна она от 0 до скажем 20. Это означает что на всю длину линейки мы имеем 20 рисок от 0 до 20 соответственно. И назовем ее ось Х . Так же возьмем еще одну линейку на 20 рисок и назовем ее ось У . Теперь соединим эти линейки нулевыми рисками так чтобы линейка Х лежала горизонтально, а У вертикально. Рисунок 1. И так мы получили координатную сетку. Теперь давайте попробуем с помощью данной координатной сетки нарисовать наш график по уравнению Х = У . Подставим под Х число 2. Теперь проведем от риски 2 оси Х вверх линию. Так как у нас икс равен игрику, то проведем еще одну линию от риски 2, но уже от оси У . Теперь посмотрев на эти две линии, найдем место их пересечения. Таким образом мы нашли одну из множества точек нашего будущего графика. Давайте найдем еще одну точку. Скажем что Х равен 4. Значит и У равен 4. Чисто теоретически мы можем на этом закончить. Так как видно, то что, при любом значении Х , У принимает то же значение, и график будет выглядеть простой прямой. Но мы не верим. Давайте проверим. Давайте найдем еще три точки. К примеру 6, 8 и ну скажем 18. Найдя все точки давайте соединим их прямой. Вот что у меня получилось. Рисунок 2. Как видите, прямая оказалось прямой. И если внимательно присмотреться, то можно сделать вывод, что эта прямая наклонена от обеих осей на 45°. Это вытекает из равенства обеих неизвестных. А давайте по химичим. Ну к примеру удвоим значение X . Теперт наше равенство примет вид Y = 2X . Попробуем построить график. Возьмем к примеру значения Х равным 2, 4 и 9. Вот что у меня получилось. Рисунок 3. Отсюда вытекает замечание, чем больше коэффициент переменной, тем больше он удаляет от себе прямую. Ну это видно из построенного графика. Видите как прямая прижалась к оси У. Ну с прямыми вроде как разобрались. Давайте теперь построим что-нибудь кривое. Это не в смысле плохого здания. Берем классическое квадратное уравнение. Y = X² . (Квадратное, это не потому что выглядит как квадрат, а потому что имеет одну из неизвестных во второй степени.) Теперь давайте найдем четыре точки со значением Х равным 1, 2, 3 и 4. Не пугайтесь, график получится большой не смотря на такие маленькие иксы. Ведь они у нас в квадрате. Вот что у меня получилось. Рисунок 4. Ничего не напоминает? Конечно, ветвь параболы. Если мы возьмем еще иксы и с отрицательным значением, то получим полноценную параболу. Теперь давайте построим более сложную вещь. Синусойду. Уравнение имеет вид У = sinХ . Здесь надо помнить лишь одно, что значение икса есть угол, который меняется от 0° до 360°. То есть полный оборот. Значение может быть и больше, но это будет лишь следующий оборот, а за ним следующий и так далее. Теперь давайте подставим под Х значения 0°, 90°, 180°, 270° и 360°. Да, да, это полный оборот. Вот что у меня получилось. Рисунок 5.
Ну на этом с графиками мы закончим. Я надеюсь что все было понятно. Что такое отцифровка сигнала? Сразу отмечу, мы не будем разбирать теорему Котельникова, так как это займет очень много времени и появятся много вопросов. Будем разбирать все на пальцах. И так, отцифровка - это разбитие некого сигнала на множество маленьких прямых. Вспомним нашу параболу, она была построена из четырех прямых. Это маловато, оно и видно. Ветвь какая-то ломаная. А вот если взять не пять точек, а скажем сто, то понятно что ветвь будет выглядеть более менее плавная. А если взять тысячу точек? Хорошо, ну разбили мы наш сигнал на прямые и что? А тут нам понадобятся знания которые мы получили выше. Только мы пойдем от обратного. Если мы имеем график и одно из неизвестных, то мы можем смело найти второе неизвестное графическим путем. Для наглядности смотрим на рисунок ниже. Рисунок 6.
Что мы здесь видим. По оси Х отложено время в секундах, а по оси У напряжение в вольтах. Нам известна форма сигнала и время. Теперь мы договариваемся что через каждую секунду будем узнавать величину напряжения. Что собственно и видно из графика. Теперь собрав в последовательность наши напряжения, а именно: 0.3, 0.9, 1.9, 4, 6, 7.9, 8.9, 9.5, 10, 9.9, 9.6, 9.1, 8.1, 6. Мы можем в принципе заявить, что мы отцифровали сигнал. Ведь мы знаем несколько точек находящихся на кривой сигнала. Но вот если мы обратно попробуем собрать наш сигнал, то вот что получим. Рисунок 7.
Но как вы видите это не совсе то что мы хотели увидеть. А почему так получилось? А потому что мы очень редко снимали значение напряжения. Чтобы график был более плавным, нужно уменьшить период времени опроса. То есть смотреть не через 1 секунду, а через 1 сотую секунды. И тогда у нас будет не 13 точек, а 1300. Есть разница? Теперь давайте перейдем к аппаратной части. Как реализована данная схема в железе. АЦП не может запоминать напряжение. А как же тогда, спросите вы. А я скажу что достаточно просто. Вот смотрите. Берем конденсатор и подаем на него наш сигнал. Пока конденсатор заряжается, мы отсчитываем время, ну например 1 миллисекунду. По истечении времени прекращаем подавать сигнал. Теперь все наоборот, начинаем разряжать наш конденсатор, а в это время считаем от нуля до конца разряда конденсатора. Получившееся число и есть та точка которая попадает на график по истечении 1 миллисекунды. В МК эта процедура называется "выборка". Ну с теорией мы вроде закончили, переходим к практике. Нам понадобится МК Atmega8. Данный МК имеет 6 каналов 10-ти разрядных АЦП, два из которых 8-ми разрядные. Для того чтобы начать работать с АЦП, нам понадобиться лишь опорное напряжение. Для начала возьмем 5 вольт. Это означает что отцифровка сигнала возможна от 0 до 5 вольт. Так как канал 10-ти разрядный то получается 5/2 10 = 0.0048 В на единицу. Значит если у нас АЦП выдаст при выборке число 482, то напряжение будет равно 482*0.0048 = 2.3136 В. В принципе неплохая точность. По заявленным параметрам, МК делает 15000 выборок в секунду. Отсюда наш график можно разбить на 14999 прямых. Давайте для начала соберем схему чтоб было немного по понятнее. Рисунок 8.
Немного пояснений. На порт В МК подсоединен LCD 16х2. Сейчас я вдаваться в подробности как он работает не буду, это не в этом уроке. Просто подсоедините как на рисунке. Выводы AREF и AVCC присоединены к 5В. Это как раз есть то самое апорное напряжение. На порт С к нулевому разряду подсоединен контакт с вольтметром и переменным резистором для изменения входного напряжения. Наша задача такова. Мы должны вывести на экран величину напряжения ту которую показывает вольтметр. Приступаем к программированию. Запускаем новый проект. В настройках Chip выбираем Atmega8, 4,00000000 MHz. Во вкладке LCD выбираем PORTB. И сохраняем проект под названием, в моем случае, ADC. Вы можете его назвать как вам угодно. Первым делом мы добавляем две директивы препроцессора для работы с текстом и задержки. Для этого после директивы LCD добавим эти две строки. #include #include Первая из них нужна для создания задержек, а вторая для работы с текстом. Далее нам нужно создать массив для временного хранения форматированного текста. После надписи "// Declare your global variables here" пишем char string;. // Declare your global variables here char string; Теперь после открытия главной функции main, мы должны объявит две переменные. Одна для хранения значения после выборки, а другая для хранения выводимого значения. Для этого запишем так. void main(void) { // Declare your local variables here int data; // Переменная для хранения данных выборки. int так как регистр 10 разрядов. float V; // Переменная для выводимого значения. float так как у нас точность до 2 знаков. Теперь давайте займемся настройкой самого АЦП. Для этого после настройка компаратора сразу запишем две строчки. // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; SFIOR=0x00; ADMUX=0; // Первая строка, № порта. ADCSR=0x85; // Вторая строка настройка АЦП. Ну с номером порта все ясно, какой номер прописан с тем и работаем, а вот с настройкой тут давайте по подробнее. Для того чтоб начать работу с АЦП у МК есть такой регистр который называется ADCSR . Что в нем находится. 0-й бит ADPS0 Выбор частоты преобразования 1-й бит ADPS1 Выбор частоты преобразования 2-й бит ADPS2 Выбор частоты преобразования 3-й бит ADIE Разрешение прерывания 4-й бит ADIF Флаг прерывания 5-й бит ADFR Выбор работы АЦП. 1-непрерывный 0-по запуску ADSC 6-й бит ADSC Запуск преобразование 1-старт. После преобразования сбрасывается в ноль аппаратно. 7-й бит ADEN Разрешение работы АЦП 1-да 0-нет Теперь давайте его настроим. Чтобы включить АЦП нам надо выставить в 1 7-й разряд. Далее выставим 0 для старта преобразования. Мы его потом дергать будем. После выставим в 0 5-й разряд. Будем сами запускать преобразование. 3-й и 4-й разряды выставим в 0. Мы не будем работать с прерыванием. Теперь осталось подобрать частоту. Если почитать мануал на МК, то там сказано что для более стабильной работы АЦП его необходимо тактировать частотой в пределах 50 кГц - 200 кГц. Так как у нас кварц на 4000 кГц, то нам его надо поделить. Вот для этого мы и воспользуемся первыми тремя разрядами. Смотрим ниже на таблицу коэффициентов деления. Что нам выбрать. Берем нашу частоту кварца и делим на коэффициент. 4000 кГц делим на 8, получаем 500 кГц. Много. Давайте теперь на 16, получим 250 кГц, тоже многовато. Делим на 32 и получаем 125 кГц. Во то что надо. Мы уложились в заданные пределы. Смотрим в таблицу и видим что коэффициент 32 задается значением разрядов 101. Ну вроде все собрали. теперь давайте посмотрим на наш регистр. Вот его значения. 10000101 здесь собраны все ранее рассмотренные значения всех разрядов. Если перевести это число в HEX то получим 0х85. Теперь вам понятно почему я записал в регистр ADCSR значение 0х85. С настройками АЦП закончено. Теперь давайте выведем в первой строке наши намерения. Для этого после инициализации LCD запишем строчку. lcd_putsf("Work with ADC"); // LCD module initialization lcd_init(16); lcd_putsf("Work with ADC"); // Выводим запись Теперь при старте программы мы в первой строке увидим нашу надпись. Далее в бесконечном цикле пишем тело самой программы. while (1) { delay_ms(20); // Задаем задержку в 20 миллисекунд ADCSR |= 0x40; // Записываем 1 в ADSC data = ADCW; // Вычитываем значение V = (float) data*0.0048828; // Переводим в вольты sprintf(string, "Data: %1.2f", V); // форматируем lcd_gotoxy(0,1); // Выставляем курсор lcd_puts(string); // Выводим значение }; Как вы заметили вся программа состоит из семи строк. Давайте все по порядку. delay_ms(20); Эта функция которая создаст задержку на 20 миллисекунд. ADCSR |= 0x40; Здесь мы делаем по битное ИЛИ. Число 0х40 в бинаре выглядит так 0b01000000. Если мы проведем по битное ИЛИ с 0х85 (0b10000101), то у нас в 6-й разряд запишется 1. Помните, что надо сделать чтобы началось преобразование. Да, да, именно в 6-й разряд нужно записать 1. А после преобразование он сбросится в 0 аппаратно. data = ADCW; После преобразование МК записывает полученное значение в регистр ADCW. Вот мы от туда его и выдергиваем. V = (float) data*0.0048828; Здесь мы преобразуем полученное число в вольты. Так как у нас опорное напряжение 5В, а значение регистра 1024, то мы 5/1024=0.0048828 Это коэффициент напряжения. Ну или минимальная величина напряжения при минимальном значении регистра ADCW. То есть если в регистре будет значение 1, то велечина напряжения будет равна 0.0048828 В. Поэтому мы в строке, данные ADCW перемножаем с 0.0048828. Слово float в скобке нужно для того чтобы преобразовать переменную data из целочисленной в вещественную с плавающей точкой. sprintf(string, "Data: %1.2f", V); Здесь мы заносим значение напряжения в массив string с форматированием. Сначала мы впишем Data: . После ставится знак процента. Он говорит о том сколько знаков будет выведено. 1.2f говорит о том что мы хотим вывести один знак до запятой и 2 знака после, а буква f говорит что мы имеем дело со значением вещественным с плавающей точкой. lcd_gotoxy(0,1); Ну тут все ясно. Выставляем курсор в нулевую позицию во второй строке. lcd_puts(string); Выводим значение на экран. Перед тем как собрать проект нужно сделать небольшие настройки. Зайдите в настройки проекта "Project->Configure" и в открывшемся окне перейдите во вкладку "C Compiler" Далее в левом нижнем углу поменяйте значение (s)printf Features: с int, width на float, width, precision . Зачем это нужно я расскажу в статье по работе с ЖК дисплеями, а сейчас просто поменяйте. Вот и вся программа. Ниже на рисунке видно как это работает. Рисунок 9.

Урок 22

Часть 2

Изучаем АЦП

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

Дальнейшая задача — реализация АЦП в нашем проекта.

Ну и чтобы нам данную задачу выполнить, нам нужны будут определённые функции для обращения к АЦП контроллера.

Для этого зайдём в файл adc.c и создадим функцию инициализацию нашего АЦП

#include "adc.h"

//—————————————-

void ADC_Init ( void )

{

}

Также создадим на данную функцию прототип в хедер-файле adc.h для видимости её из внешних модулей, а также заодно и посмотрим всё содержимое данного файла

#ifndef ADC_H_

#define ADC_H_

#include "main.h"

void ADC_Init ( void );

#endif /* ADC_H_ */

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

Начнем с управляющего регистра

void ADC_Init ( void )

ADCSRA |= (1<< ADEN )

|(1<< ADPS2 )|(1<< ADPS1 )|(1<< ADPS0 ); //Делитель 128 = 64 кГц

Это не две строки, а одна, так писать в студии можно и даже нужно, так как код становится понятнее. А одна, потому что нет символа конца строки — точки с запятой.

Здесь мы включили бит ADEN , тем самым включили вообще модуль АЦП, а также установили делитель на 128, тем самым, помня то, что частота тактирования у нас 8 МГц и разделив её значение на 128, мы получили работу АЦП на частоте 64 кГц, что вполне нормально и надёжно, до 200 граничных далеко. Как видим, ничего сложного в инициализации регистра нет.

Также ещё в данной функции нам необходимо выбрать канал, к которому мы будем подключать измеряемое напряжение. У нас судя по схеме канал 0, поэтому соответствующий MUX мы и включим. А соответствующий MUX — это все нули в данных битах, поэтому ничего-то и включать не надо. Но мы ещё помним, что в регистре ADMUX у нас помимо всего прочего есть и управляющие биты, а именно биты REFS1 и REFS0, с помощью которых мы установим в качестве источника опорного напряжения внутренний источник на 2,56 вольта, а ADLAR мы не используем

ADCSRA |= (1<< ADEN ) // Разрешение использования АЦП

|(1<< ADPS2 )|(1<< ADPS1 )|(1<< ADPS0 ); //Делитель 128 = 64 кГц

ADMUX |= (1<< REFS1 )|(1<< REFS0 ); //Внутренний Источник ОН 2,56в, вход ADC0

Ну вот, в принципе, и вся инициализация.

Вызовем эту функцию в главном модуле программы в функции main() где-нибудь вот тут

LCD_ini (); //Инициализируем дисплей

ADC_Init (); //Инициализируем АЦП

clearlcd (); //Очистим дисплей

Ну и также нам нужна будет в модуле adc.c ещё одна функция, которая будет инициализировать непосредственно начало процесса аналого-цифрового преобразования в нашем ADC

unsigned int ADC_convert ( void )

{

}

Само собой нужен будет в хедер-файле прототип на неё

void ADC_Init ( void );

unsigned int ADC_convert ( void );

Данная функция нам вернёт значение из регистровой пары ADC , которая и будет содержать величину нашего электрического сигнала в единицах, выражающих отношение измеряемого сигнала к опорному и умноженных на количество возможных отрезков, которых у нас 1023, ну или 1024. Насчёт этого ходят много слухов, но в технической документации на контроллер в расчетной формуле содержится именно 1024. Но это нам не так важно.

Включим преобразование с помощью бита ADSC

unsigned int ADC_convert ( void )

ADCSRA |= (1<< ADSC ); //Начинаем преобразование

Теперь нам надо как-то отследить тот момент, когда данное преобразование закончится. А делается это достаточно легко с помощью мониторинга того же бита ADSC, который по окончании процесса преобразования сам сбрасывается в 0 (When the conversion is complete, it returns to zero). Отслеживается данный бит с помощью условного цикла

ADCSRA |= (1<< ADSC ); //Начинаем преобразование

while (( ADCSRA & (1<< ADSC )));

Ну и по окончании вернём результат в виде беззнаковой величины

while (( ADCSRA & (1<< ADSC ))); //проверим закончилось ли аналого-цифровое преобразование

return ( unsigned int ) ADC ;

Вернёмся теперь в нашу главную функцию main() и создадим там локальную переменную для хранения результата преобразования для дальнейшей с ним работы

int main ( void )

unsigned int adc_value ;

Вызовем функцию преобразования, которая нам положит в нашу переменную результат преобразования

while (1)

adc_value = ADC_convert (); //Вызовем преобразование

Setpos (0,0);

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

Setpos (0,0);

sendcharlcd ( adc_value /1000+0x30);

sendcharlcd (( adc_value %1000)/100+0x30); //Преобразуем число в код числа

sendcharlcd (( adc_value %100)/10+0x30); //Преобразуем число в код числа

sendcharlcd ( adc_value %10+0x30); //Преобразуем число в код числа

Delay_ms (500);

Здесь мы разбиваем по цифрам четырёхзначную величину.

Теперь мы соберём код, прошьём контроллер и посмотрим наши результаты, покрутив резистор на 10 килоом

Вот так вот оно всё и работает.

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

unsigned int adc_value ;

float n ;

Также забудем про существование функции sprintf и попробуем получить плавающий тип на дисплее программным путём. Для этого сначала преобразуем наш сырой результат в плавающий тип явным образом, то есть та же цифра будет, но только тип другой, не забыв, конечно, перед этим поставить курсор в нужное место на дисплее. Для этого существует понятие в языке СИ явного преобразования типов и разделим преобразованный результат на 400

sendcharlcd ( adc_value %10+0x30); //Преобразуем число в код числа

setpos (8,0);

n = ( float ) adc_value / 400;

Тут, конечно, возникает вопрос, а почему мы делим именно на 400. А вот почему.

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

Вот поэтому и мы и вычислили её самую последнюю часть. Осталось теперь лишь только перевернуть ещё наоборот, выразив отсюда входное напряжение, так как неизвестное у нас именно оно. И мы получим, что оно будет у нас равно ADC, делённому на 400, что мы, собственно и сделали выше в коде. Я думаю, всё предельно стало теперь всем понятно.

Осталось самое интересное — отобразить всё это на экран, зная, то.что мы не можем работать с дисплеем с плавающим типом. А оказывается всё просто. Всё решается вот таким кусочком кода

N = ( float ) adc_value / 400;

sendcharlcd (( unsigned char ) n +0x30); //Преобразуем число в код числа

sendcharlcd ("."); //Преобразуем число в код числа

sendcharlcd ((( unsigned char ) ( n *10))%10 +0x30); //Преобразуем число в код числа

sendcharlcd ((( unsigned char ) ( n *100))%10 +0x30); //Преобразуем число в код числа

Delay_ms (500);

Не пугайтесь, сейчас мы всё тут разрулим.

Сначала мы обратным преобразованием типов отсекаем вооще всю дробь и, зная, что дальше 9 мы не уйдём и у нас будет только одна цифра, да мы даже и дальше 2 тут не уйдём, у нас максимум 2,56, мы просто отображаем данную цифру.

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

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

Вот и всё!

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

Post Views: 6 917

Рекомендуем почитать

Наверх