Учебный курс. АЦП микроконтроллера AVR или как подключить 4 кнопки к одному выводу. Часть 1

05/10/2009 - 21:00

АЦП микроконтроллера AVR

   Микроконтроллер общается с внешним миром посредством портов ввода/вывода. В общем случае он может “воспринимать” только цифровые сигналы – логический ноль или логическую единицу. Например, для микроконтроллера ATmega8535 при напряжении питания 5 В логический ноль – это напряжение от 0 до 1,3 В, а логическая единица – от 1,8 до 5 В. Довольно часто возникает потребность измерять напряжения, которые могут принимать любое значение в диапазоне от 0 до напряжения питания. Для этих целей в составе микроконтроллеров AVR есть аналого-цифровой преобразователь (АЦП).
  Не вдаваясь в подробности внутреннего устройства АЦП, представим его в виде черного ящика. На вход АЦП подается непрерывный аналоговый сигнал, а на выходе получается последовательность цифровых значений. АЦП имеет много характеристик, но в качестве основных можно назвать  разрешающую способность, абсолютную точность, предельную частоту дискретизации и диапазон входных напряжений.
   Разрешающая способность (разрешение)  – это способность АЦП различать два значения входного сигнала. Определяется как величина обратная максимальному числу кодовых комбинаций на выходе АЦП. У AVRа АЦП 10-ти разрядный. Максимальное число кодовых комбинаций будет равно 210 = 1024. Разрешающая способность равна 1/1024 от всей шкалы допустимых входных напряжений.
  Для работы АЦП необходим источник опорного напряжения (ИОН). Для него это эталон, по отношению к которому он измеряет входные сигналы. Микроконтроллеры AVR позволяют в качестве ИОНа использовать напряжение питания, внутренний опорный источник на 2,56 В и напряжение на выводе AREF (внешний ИОН).
   Напряжение питания в нашей схеме 5 В, тогда 1/1024 от всей шкалы это 5 * 1/1024 = 0,0048 В или примерно 5 мВ. С таким шагом (это называется шаг квантования) АЦП будет измерять входное напряжение. Если два ближайших значения сигнала на входе АЦП будут отличаться между собой на величину < 5 мВ, АЦП воспримет их как одинаковые. На практике разрешающая способность АЦП ограничена его шумами.
   Абсолютная точность – отклонение реального преобразования от идеального. Это составной результат нескольких погрешностей АЦП. Выражается в количестве младших значащих разрядов (LSB - least significant bit) АЦП. Для AVRа абсолютная погрешность АЦП  =  ±2LSB. Для нашего примера абсолютная точность будет равна 2 * 5 мВ = ±10 мВ.
  Предельная частота дискретизации определяет быстродействие АЦП и измеряется в герцах или количестве выборок в секунду (SPS – samples per second). Для микроконтроллеров AVR эта величина равна 15 kSPS (килло семплов в секунду). Практически АЦП AVRа работает и быстрее, но при этом  его точность ухудшается.
   Теорема Котельникова (теорема Найквиста-Шеннона, теорема о выборке) гласит, что аналоговый сигнал имеющий ограниченный спектр, может быть восстановлен однозначно и без потерь по своим дискретным отсчётам, если частота выборки (дискретизации) превышает максимальную частоту спектра  сигнала более чем в 2 раза. Выражаясь по-простому - если вам нужно оцифровать аналоговый сигнал с полосой спектра 0 - 7 КГц, то в идеальном случае частота дискретизации должна быть > удвоенной максимальной частоты спектра этого сигнала, то есть > 14 КГц. На практике все намного сложнее. Перед входом АЦП всегда ставят НЧ фильтр, чтобы ограничить спектр сигнала, а частота дискретизации выбирается еще более высокой. 
   Диапазон входных напряжений – это минимальное и максимальное значение напряжения, которое можно подавать на вход АЦП. Для микроконтроллера AVR он равен 0 – Vcc (напряжение питания)


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


Пояснения к схеме

   Для знакомства с модулем АЦП микроконтроллера AVR я выбрал простую, но интересную схему. Взгляните на рисунок. Четыре кнопки подключены к делителю напряжения (резисторы R8…R11). При нажатии они коммутируют на вход нулевого канала АЦП разные напряжения. Измеряя эти напряжения в прерывании АЦП и определяя в какой диапазон они попадают, микроконтроллер будет распознавать номер нажатой кнопки.
   Резистор R7 нужен чтобы вход АЦП не “болтался в воздухе”, когда все кнопки отпущены. Если этого не сделать АЦП будет ловить помехи. Номинал резистора R7 выбран большим, чтобы не оказывать влияние на делитель напряжения.
   R6 и C7 - низкочастотный фильтр для защита от дребезга кнопок и помех. (Вообще НЧ фильтры перед АЦП обычно используются для защиты от такого явления как наложение, но это отдельная история). Дополнительно резистор R6 играет функцию токоограничения, без него при нажатии кнопки S4 вывод микроконтроллера напрямую соединялся бы с плюсом питания. А это нежелательно.
   Для индикации номера нажатой кнопки в схеме используются 4 светодиода.
   Микроконтроллер - ATMega8535. Описание регистров АЦП ниже по тексту приводится именно для него. Для других микроконтроллеров могут быть некоторые отличия.
схема подключения кнопок к АЦП AVRа

Задача

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

Алгоритм программы такой:

Основная программа
   Инициализация портов
   Инициализация АЦП
   Разрешение прерываний
   Бесконечный цикл
   {
    Если кнопка нажата, зажечь нужный светодиод
    Если нет, погасить все светодиоды
   }
 
Обработчик прерывания АЦП
     Считать напряжение на входе АЦП
     Определить в какой диапазон оно попадает
     Записать номер кнопки в буфер

 

Начальный код программы


//программирование микроконтроллеров AVR на Си - осваиваем АЦП

#include <ioavr.h>
#include <intrinsics.h>
 
//макрос для запуска преобразования
#define StartConvAdc()  ADCSRA |= (1<<ADSC)

int main(void)
{

//ион - напряжение питания, выравнивание влево, нулевой канал
ADMUX = (0<<REFS1)|(1<<REFS0)|(1<<ADLAR)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0);
//вкл. ацп, режим одиночного преобр., разрешение прерывания,частота преобр. = FCPU/128
ADCSRA = (1<<ADEN)|(0<<ADSC)|(0<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);


__enable_interrupt
();
StartConvAdc();
  while(1)
  {

    //какой-нибудь код

  }
  return 0;
}

//обработчик прерывания АЦП
#pragma vector=ADC_vect
__interrupt void adc_my(void)
{
   unsigned char AdcBuf = ADCH;
 
  //какой-нибудь код

  StartConvAdc();
}


Пояснения к коду

Макрос для запуска преобразования

StartConvAdc() – это макрос для запуска преобразования АЦП. Он устанавливает бит ADSC в регистре управления и состояния ADCSRA.
 
Инициализация АЦП

Чтобы запустить модуль АЦП, его нужно предварительно настроить. За это отвечают три регистра:
Регистр управления мультиплексором - ADMUX
Регистр управления и состояния - ADCSRA
Регистр специальных функций - SFIOR

Регистр ADMUX

   Модулю АЦП для работы нужен источник опорного напряжения (ИОН). За выбор ИОНа отвечают  биты REFS1, REFS0. В нашем случае опорное напряжение это напряжение питания (именно поэтому мы завели вывод AVcc  на +5В) поэтому REFS1 - 0, REFS0 - 1
   АЦП микроконтроллера AVR 10-ти разрядный, а сам камень 8-ми. Поэтому результат преобразования хранится в двух регистрах (ADCH, ADCL). Бит ADLAR задает направление выравнивания результата преобразования. 0 – выравнивание вправо (в ADCH заняты 2 младших бита,  ADCL занят весь), 1 – выравнивание влево (ADCH занят весь, в ADCL только 2 старших бита).  В нашем примере не требуется высокой точности преобразования, поэтому было бы удобно выровнять результат влево и работать только с регистром ADCH. Устанавливаем ADLAR - 1
  Чисто физически в микроконтроллере AVR всего один АЦП. Но перед ним стоит мультиплексор, который позволяет подключать любой из 8-ми выводов порта к входу АЦП. Номер выбранного в данный момент канала задается битами ADMUX3,  ADMUX2,  ADMUX1,  ADMUX0. Мы используем нулевой канал, поэтому все биты равны 0.
 
//ион - напряжение питания, выравнивание влево, нулевой канал
ADMUX = (0<<REFS1)|(1<<REFS0)|(1<<ADLAR)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0);

Регистр ADCSRA

   Чтобы АЦП заработал его надо включить, то есть установить бит ADEN – 1
    Запуск преобразования осуществляется установкой бита ADSC -1. Во время инициализации мы не будем запускать АЦП, поэтому ADSC – 0
   АЦП может работать в двух режимах: одиночное преобразование - когда каждое преобразование запускается программно и непрерывное - когда преобразование запускается один раз программно, а перезапускается автоматически. В нашем примере преобразование одиночное, бит ADATE – 0.
   Когда АЦП закончил преобразование, он подает запрос на прерывание. Чтобы разрешить прерывание нужно установить бит ADIE – 1
  Модулю АЦП для работы требуется тактовый сигнал. Он формируется из тактового сигнала микроконтроллера путем деления на фиксированные коэффициенты. Для установки коэффициентов предделителя предназначены биты ADSP2, ADSP1, ADSP0. Поскольку мы будем опрашивать с помощью АЦП кнопки, а это очень медленное устройство,  выбираем самый большой коэффициент (то есть самую низкую частоту). Все биты в 1.

//вкл. ацп, режим одиночного преобр., разрешение прерывания, F преобр. = FCPU/128
  ADCSRA = (1<<ADEN)|(0<<ADSC)|(0<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);

Про все биты рассказал, кроме ADIF. ADIF - это флаг прерывания. Он устанавливается аппаратно, когда преобразование завершено.

Регистр SFIOR

   Биты ADTS2, ADTS1, ADTS0 определяют источник, запускающий процедуру преобразования. Устанавливать эти биты нужно только в режиме непрерывного преобразования. У нас режим одиночный, поэтому регистр не трогаем.
 
Обработчик прерывания АЦП

#pragma vector=ADC_vect
__interrupt void adc_my(void)
{
  unsigned char AdcBuf = ADCH;

  //какой-нибудь код

  StartConvAdc();
}

  Здесь все должно быть понятно. Синтаксис обработчика прерывания разбирался. ADCH – старший регистр данных модуля АЦП. StartConvAdc() – запуск преобразования перед выходом из обработчика.
   Для особо нетерпеливых выкладываю файлы проекта. АЦП микроконтроллера AVR с чего начать.
 
 

Комментарии   

# skiff 04.10.2009 06:22
Спасибо тебе Pashgan за статью!
Ответить | Ответить с цитатой | Цитировать
# Frf 08.11.2009 15:31
Действительно хорошая статья.Спасибо!
Ответить | Ответить с цитатой | Цитировать
# alexandershahbazov 08.12.2009 21:51
Побольше про экономию ножек .
У меня одни ATmega8 .
Например про буферные микросхемы .
В статье "Учебный курс. Подключение lcd к микроконтроллер у. Получение первых результатов " все идет напрямую на ножки . После этого мало ножек остается
для других дел . Я до сих пор делал
напрямую при подключении ЖКИ на ATmega16
хотя и знал про 74HC164 .
Ответить | Ответить с цитатой | Цитировать
# Guest 11.01.2010 00:01
А как вывести данные с АЦП на LCD?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 14.01.2010 06:12
Считать результат преобразования АЦП, перевести в двоично-десятич ные числа, затем в символы и вывести на lcd. В разделе "Библиотеки для IARa" есть для этого исходник - bcd.rar
Ответить | Ответить с цитатой | Цитировать
# Guest 28.04.2010 09:10
Pashgan,добрый день, прочитал Ваши статьи, и все очень понравилось.
Я вообще начинающий в этом деле. И хотелось бы узнать, а как можно сделать так, чтобы считывать сигналы не с одного входа АЦП, а, например, с 2х входов? Сильно ли при этом изменится программа?
Спасибо!
Ответить | Ответить с цитатой | Цитировать
# Pashgan 29.04.2010 14:49
misha, не сильно. В прерывание АЦП нужно добавить код, который будет менять номер канала. Он задается с помощью регистра ADMUX.
Ответить | Ответить с цитатой | Цитировать
# Guest 29.04.2010 16:11
Pashgan, просто вот не особо понятно, что как в теле прерывания ацп, где происходит присваивание результата преобр-я в какой-то регистр, например на PORTC, и там же ,получается, прописывается уже ADMUX с другим каналом. А тогда где нужно прописать в программе, чтобы происходила запись результата 2го преобразования в другой регистр, например PORTB? (( чтобы не было путаницы?
спасибо !!!
Ответить | Ответить с цитатой | Цитировать
# Pashgan 03.05.2010 14:02
Misha, сделай тему на форуме. Я там выложу какой-нибудь код.
Ответить | Ответить с цитатой | Цитировать
# Guest 01.07.2010 18:20
Цитата:
Дополнительно резистор R7 играет функцию токоограничения, без него при нажатии кнопки S4 вывод микроконтроллера напрямую соединялся бы с плюсом питания.
А R6 разве не ограничит ток при нажатии S4?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 30.07.2010 16:12
ага.. опечатался, исправил.
Ответить | Ответить с цитатой | Цитировать
# Botan 19.08.2010 13:53
Добрый день, Misha тему на форуме так и не создал, тогда я с вашего позволения это сделаю.
Ответить | Ответить с цитатой | Цитировать
# Яков 25.02.2011 14:41
Благодарю Автору за статью все просто и доходчиво побольше бы таких пока не дочитал до конца но весь вечер впереди :-)
Ответить | Ответить с цитатой | Цитировать
# Pashgan 27.02.2011 09:38
Спасибо
Ответить | Ответить с цитатой | Цитировать
# Guardian 06.03.2011 21:28
как можно использовать ацп для преобразования одновременно двух источников
Ответить | Ответить с цитатой | Цитировать
# Димка 10.07.2011 18:39
У меня компилятор дал ошибку identifier "ADCSRA" is undefined что это может быть?
Ответить | Ответить с цитатой | Цитировать
# Димка 12.07.2011 10:41
Во, нашёл причину, вместо ADCSRA надо писать ADCSR - эта хрень на mega8
Ответить | Ответить с цитатой | Цитировать
# sadmi 23.08.2011 04:17
Спасибо, благодаря статье разобрался с АЦП. Но не могли Вы рассказать про вариант с непрерывным преобразованием ?
Ответить | Ответить с цитатой | Цитировать
# Artempre 26.11.2011 22:39
Pashgan Спасибо тебе огромное! Я уверился что Си всё таки не сложнее китайского языка! Ранее было наоборот! :-)
Ответить | Ответить с цитатой | Цитировать
# Асель 15.01.2012 06:04
полезная инфа без лишнего красноречия.спа сибо!помогли
Ответить | Ответить с цитатой | Цитировать
# MS 19.03.2012 14:24
Спасибо тебе ГИГАНТСКОЕ!!! ПРОСТО ТЕРА - ЗЕТА СПАСИБО!!! =))
Ответить | Ответить с цитатой | Цитировать
# Bac9 20.03.2012 08:18
А можно как нибудь на один порт только 2 кнопки подключить? У меня напряжение в цепи 12 вольт.
Ответить | Ответить с цитатой | Цитировать
# radiolomaster 26.12.2012 17:55
Подскажите пожалуйста, вот я хочу перенести эту программу на AT90S4434, у него некоторые регистры по другому называются, но я вроде все поменял как надо. Но у этого контроллера нет возможности сдвигать результат влево или вправо, т.е. ADCH всегда содержит 2 старших бита, а ADCL - 8 младших. У меня результат выводится на 7-сегментный индикатор. Так вот, если в индикатор отправлять значение из ADCH - все работает, только на индикаторе выводится число от 0 до 3, при изменении напряжения от 0 до 5 В. А если отправлять туда значение ADCL - то результат выводится один раз при запуске, и потом не реагирует на изменение напряжения. Измеряет вроде правильно - от 0 до 255 число выводится. Это все проверялось в протеусе, в железе пока не собирал. Протеус ругается "adc result lost due pending read", и если не отключить предупреждения то схема вообще виснет. Думал может конфликтует с таймером (он управляет индикатором), но нет, с выключенным таймером тоже ругается.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 26.12.2012 18:47
Тогда сохраняй значение ADCH и ADCL в 16-ти разрядную переменную и в ней сдвигай результат.
Ответить | Ответить с цитатой | Цитировать
# radiolomaster 27.12.2012 17:45
Так тоже ругается, как только пытаюсь прочитать значение из ADCL ругается.
Ответить | Ответить с цитатой | Цитировать
# radiolomaster 27.12.2012 18:25
А, я кажется разобрался, нужно читать не ADCL и ADCH, а регистр ADC, который состоит из этих двух. Тогда все работает. Но все-таки не понятно почему нельзя читать только ADCL, а ADCH - можно.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 27.12.2012 18:30
При чтении регистров данных АЦП нужно соблюдать определенный порядок. Сначала читается ADCL, затем ADCH. Когда читается АDC, компилятор на самом деле вставляет на ассемблере код чтения ADCL и ADCH в нужной последовательно сти.
Ответить | Ответить с цитатой | Цитировать
# radiolomaster 27.12.2012 18:38
Да, точно, если сначала прочитать ADCL, а затем ADCH то все работает, а отдельно ADCL не хочет. Просто если например значение старших разрядов не интересует, то их все-равно придется прочитать.
Спасибо большое!
Ответить | Ответить с цитатой | Цитировать
# РусланР. 27.05.2013 22:46
Да исправьте уже наконец ссылку на вторую страницу. Она ведет на первую страницу.
Ответить | Ответить с цитатой | Цитировать
# Илья21 13.08.2013 09:41
Очень хорошо все описано! Спасибо автору, ведь на таких статьях выростают настоящие технари и программисты!!!
Ответить | Ответить с цитатой | Цитировать
# Иван Смит 22.09.2013 12:14
RC фильтр у меня вызывает сомнения... у него частота среза ~8kHz, при том, что "период" дребезга 20мс. Это действительно работает?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 23.09.2013 00:28
Работает. В коде еще сделана программная защита от дребезга.
Ответить | Ответить с цитатой | Цитировать
# eugen 05.01.2014 08:30
У меня все равно не хочет на вторую страницу переходить(а хотелось бы дочитать!
Ответить | Ответить с цитатой | Цитировать
# Pashgan 07.01.2014 05:30
Поправил.
Ответить | Ответить с цитатой | Цитировать
# марс 16.12.2014 06:30
сп огромное хоть чеуть стало ясно с ацп а можно еще про шим и т.д.
Ответить | Ответить с цитатой | Цитировать
# 12val12 26.01.2015 14:26
удобно использовать блоки кнопок от телевизоров
схема такая .90% кнопок от тв однопроводные и идут на вход ацп MICOM TV
1-4 кОм от порта к +5В
а модуль кнопок корпус к корпусу и выходс кнопок на порт
Ответить | Ответить с цитатой | Цитировать
# jackbl 29.12.2016 13:48
В реальной схеме логично было бы R11 исключить, а R7 сделать 2К. Это бы снизило энергопотреблен ие при не нажатых кнопках.
Ответить | Ответить с цитатой | Цитировать
# Василий 27.08.2017 19:03
Не подскажете, как должны быть сконфигурирован ы выводы порта (в данном случае PORTC) которые потом подключаются к АЦП? как вход или как выход? заранее благодарю
Ответить | Ответить с цитатой | Цитировать

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

Защитный код
Обновить