Вторник, 19.03.2024, 17:00
RC - Мастерская
Главная | Каталог статей | Регистрация | Вход
Меню
Статистика
Главная » Статьи » Чертежи и проекты » Микроконтроллеры

Аналоговые входы Arduino.
Аналоговые входы Arduino.


  В микроконтроллерах Atmel (на которых построена Arduino) имеется встроенный аналогово-цифровой преобразователь (АЦП). Именно он отвечает за оцифровку сигнала с аналоговых входов. Причем в один момент времени АЦП может оцифровывать сигнал только с одного аналогового входа. Сам процесс оцифровки сводится к последовательному подбору наиболее близкого к входному напряжения с известным (референсным) значением. К слову, у микроконтроллера так же есть дополнительный вход AREF для подачи внешнего референсного напряжения. В Arduino по умолчанию референсным является напряжение питания микроконтроллера. Но если, например, Ваш МК питается от пяти вольт, а датчик на вход АЦП возвращает максимум 3.3В, то стоит подключить напряжение 3.3В к входу AREF и до преобразования вызвать функцию analogReference().
  В IDE Arduino за работу с АЦП отвечает функция analogRead(pin). Эта функция включает АЦП, выбирает заданный вход, производит преобразование и возвращает результат с 10-ти битной точностью. Понятно, что такая операция не выполняется за один такт. Не выполняется она и за два, и за десять тактов… И резонно появляется следующий вопрос: как быстро работает analogRead? Для оценки времени преобразования я сделал простой тест.
1.    К аналоговому входу 5 подключим переменный резистор.
2.    Напишем скетч, в котором будем в основном цикле вызывать analogRead и писать полученное значение в UART.
3.    Будем засекать время по таймеру непосредственно перед и сразу после вызова analogRead. Разница этих двух значений поможет оценить скорость преобразования.
  Для эксперимента я взял FreeDuino на Atmega168, загрузил в нее скетч и… результат мне не понравился совершенно. Микроконтроллеру для оцифровки аналогового сигнала понадобилось в среднем 111мкс. Это много! Но почему так и можно ли что-то сделать для увеличения скорости?
  Для этого необходимо разобраться как микроконтроллер управляет АЦП.

Общее представление.

Управление АЦП производится с помощью следующих 8-ми битных регистров:
 наименование7
6
5
4
3
2
1
0
 ADCSRA ADENADSC
ADATE(ADFR2)
ADIF
ADIE
ADPS2:0
 ADMUX REFS1:0 ADLAR MUX4:0
 ADCSRB ADTS2:0 XX
X
X
X
ADCLРезультат преобразования. Значение имеют только 10 бит. Старшие или младшие - зависит от значения бита ADLAR.
ADCH
Назначение битов:
ADEN – Включение АЦП
ADSC - Запуск преобразования
ADATE - Выбор режима работы АЦП. 0 – разовое преобразование; 1 – режим преобразований задается битами ADTS2:0 регистра ADCSRB.
ADIF - Флаг прерывания от компаратора
ADIE - Разрешение прерывания от компаратора
ADPS2:0 - Выбор делителя частоты для преобразования. Частота, на которой производятся преобразования напрямую влияет на точность. Чем выше частота, тем ниже точность. При этом АЦП тактируется через делитель от частоты ядра микроконтроллера.
 ADPS2ADPS1
ADPS0
Коэффициент деления
0
0
0
 2
001 2
010 4
011 8
1
00 16
101 32
110 64
111 128

REFS1:0 - Выбор источника опорного напряжения.
REFS1
REFS0
Источник опорного напряжения ADC
 0 0Внешний источник, подключенный к AREF, внутренний VREF отключен
 0 1 AVCC с внешним конденсатором на выводе AREF
 1 0 Зарезервировано
 1 1 Внутренний источник опорного напряжения 2.56В с внешним конденсатором на выводе AREF
ADLAR - Выравнивание результата преобразования (лево/право)
MUX4:0 - Выбор входного канала
 MUX4:0 Вход
 00000 ADC0
 00001 ADC1
 00010 ADC2
 00011 ADC3
 00100 ADC4
 00101 ADC5
 00110 ADC6
 00111 ADC7
Здесь надо отметить, что АЦП умеет оцифровывать сигнал не только относительно нуля, но и оцифровывать разность сигналов между двумя входами.
Примечание: Эта таблица приведена далеко не полностью. Для получения информации о дифференциальных каналах см. соответствующий datasheet.
Примечание:  Дифференциальные каналы не тестировались для микроконтроллеров в корпусе PDIP40. Работа в таком режиме гарантируется только для микроконтроллеров в корпусах TQFP и QFN/MLF.
ADTDS2:0 – Выбор режима работы АЦП
ADTS2
ADTS1
ADTS0
Источник запуска преобразования ADC
 0 0 0
 Постоянное преобразование (Free Running mode)
 0 0 1 Аналоговый компаратор
 0 1 0 Внешний запрос на прерывание 0
 0 1 1 Timer/Counter0 Compare Match
 1 0 0 Timer/Counter0 Overflow
 1 0 1 Timer/Counter1 Compare Match B
 1 1 0 Timer/Counter1 Overflow
 1 1 1 Timer/Counter1 Capture Event

Как ускорить analogRead?

Да все очень просто! Надо только увеличить частоту работы АЦП понизив делитель. На atmega168 5В/16МГц мне удалось добиться 17мкс на преобразование.
Вот тестовый скетч:
#include <PinChangeInt.h>
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

void setup()
{
  Serial.begin(9600);
  Serial.println("Start");
  TCCR1B   =   0;   //stop timer
  TCCR1A   =   0;
  TCNT1   =    0;   //setup
  TCCR1A   =   0;  
  TCCR1B   =   0<<CS12 | 1<<CS11 | 0<<CS10;//0x1A; //start timer with 1/8
 
 
sbi(ADCSRA,ADPS2);
cbi(ADCSRA,ADPS1);
cbi(ADCSRA,ADPS0);
}

uint16_t val;
uint16_t t1;
uint16_t t2;
void loop()
{
  t1 = TCNT1;
  val = analogRead(5);
  t2 = TCNT1;
  Serial.print("d = "); 
  Serial.print((t2-t1)>>1);
  Serial.print(" val = ");
  Serial.println(val);
}

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

Увеличиваем количество входов

Приступим к следующей задаче: у моей Arduino всего 5 аналоговых портов, из которых под нужды задачи свободны только два или три. Задача состоит в том, чтобы получать данные с десяти аналоговых датчиков. Даже если бы я мог использовать все аналоговые входы атмеги, их было бы всего 8. Как быть? Воспользуемся аналоговым мультиплексором/демультиплексором КР1561КП2(импортный аналог – микросхема 4051). Эта маленькая микросхемка стоимостью всего в 17рублей позволяет переключать 8 аналоговых входов на один выход при помощи трех цифровых портов. Установив два таких мультиплексора и использовав 3 цифровых пина мы можем получить 16 аналоговых входов задействовав всего два входа Arduino.
 
 
* Z ----- общий сигнал ввода или вывода (соединенный с входом/выходом Arduino)
* E ----- вход разрешения (активный лог «0») (подключен к земле (GND))
* Vee --- отрицательное напряжение питания (подключен к земле (GND))
* GND --- общий минус (0 V)
* S0-S2 — выбор входов (подключены к трем цифровым выводам Arduino)
* y0-Y7 — независимые входы/выходы
* Vcc --- положительное напряжение питания (5 В)

Еще об увеличении скорости.

  Каждый раз, когда мы вызываем analogRead(), программа не движется дальше пока не получит данные с АЦП. Если заглянуть в код этой функции, то мы увидим пустой цикл while (bit_is_set(ADCSRA, ADSC));. По сути это пустая трата времени ядра, пока АЦП выполняет преобразование.
  Теперь допустим, что нам надо не просто оцифровать 10 аналоговых сигналов, а делать это постоянно по кругу. В таком случае вызывать в цикле analogRead() – просто расточительство.
  Обратимся опять к регистрам управления АЦП. Из приведенных выше данных несложно увидеть, что АЦП может работать в непрерывном режиме без привлечения центрального ядра микроконтроллера. А об окончании очередного преобразования наша программа узнает из прерывания. Ниже приведен скетч постоянного опроса двух аналоговых входов. На каждое преобразование тут затрачивается 26мкс. Именно 26, а не 17 из-за того, что АЦП начинает новое преобразование сразу после окончания предыдущего. Мы же в это время переключаем аналоговый вход и результат становится недостоверным. Поэтому приходится дожидаться каждого второго преобразования после переключения входа. Надо так же учесть, что из этих 26мкс на обработку результата тратится всего несколько тактов. Все остальное время пока АЦП выполняет свою работу, наш скетч может заниматься прочими функциями.

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

uint8_t analog_reference = DEFAULT;
volatile bool ADC_update = false;
volatile uint16_t val[6];
volatile uint8_t cur_in;

void analog_init(uint8_t aref)
{
  sbi(ADCSRA,ADPS2);
  cbi(ADCSRA,ADPS1);
  cbi(ADCSRA,ADPS0);

  cur_in = 4;
  ADMUX = (analog_reference << 6) | (cur_in & 0x07);
 
  sbi(ADCSRA,ADATE);
  sbi(ADCSRA,ACIE);
  sbi(ADCSRA,ADSC);
}

ISR(ADC_vect)
{
  if (ADC_update){
    val[cur_in] = ADCL|(ADCH << 8);
    if(cur_in=4)cur_in++;
    else cur_in--;

    ADMUX = (analog_reference << 6) | (cur_in & 0x07);
    ADC_update = false;
  }
  else
    ADC_update    = true;    
}

void setup()
{
  Serial.begin(9600);
  Serial.println("Start");
  TCCR1B   =   0;   //stop timer
  TCCR1A   =   0;
  TCNT1   =    0;   //setup
  TCCR1A   =   0;  
  TCCR1B   =   0<<CS12 | 1<<CS11 | 0<<CS10;//0x1A; //start timer with 1/8
 
  analog_init(DEFAULT);
}

void loop()
{
  for(int i=0;i<6;i++){
    Serial.print("i = ");
    Serial.print(i);
    Serial.print(" val = ");
    Serial.println(val[i]);
  }
}

Категория: Микроконтроллеры | Добавил: Mactep (11.03.2013)
Просмотров: 14209 | Теги: АЦП, AVR, Arduino, ADC | Рейтинг: 5.0/1
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск