• (Обучение бесплатно)
  • Приглашаем учащихся 2-11кл попробовать свои силы на занятиях объединения "Радиотехническое конструирование"
  •  (Обучение бесплатно)
  • Запись проводится с 22 августа 2017г
  •    по адресу г. Комсомольск-на-Амуре МБОУ ДО КВАНТОРИУМ
  •    ул Краснофлотская, д 22, корп 2. Телефон: (4217) 54-79-88
  •    Начало занятий - сентябрь 2017 г.
  •    *OK
  •    JUNRADIO*
                           










                                               


  • I






      
           

Научно-популярный образовательный ресурс для юных и начинающих радиолюбителей - Popular science educational resource for young and novice hams

Основы электричества, учебные материалы и опыт профессионалов - Basics of electricity, educational materials and professional experience

КОНКУРС
language
 
Поиск junradio

Радиодетали
Искать на DESSY.RU
Сервисы

Stock Images
Покупка - продажа
Фото и изображений


 
Выгодный обмен
электронных валют

Интересно
Немного подработать
Есть свободное время?
Можешь немного подработать.
Загрузка...
Друзья JR



JUNIOR RADIO


 

Аналоговые входы 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

 ADEN

ADSC

ADATE(ADFR2)

ADIF

ADIE

ADPS2:0

 ADMUX

 REFS1:0

 ADLAR

 MUX4:0

 ADCSRB

 ADTS2:0

 X

X

X

X

X

ADCL

Результат преобразования. Значение имеют только 10 бит. Старшие или младшие - зависит от значения бита ADLAR.

ADCH

 

Назначение битов:
ADEN – Включение АЦП
ADSC - Запуск преобразования
ADATE - Выбор режима работы АЦП. 0 – разовое преобразование; 1 – режим преобразований задается битами ADTS2:0 регистра ADCSRB.
ADIF - Флаг прерывания от компаратора
ADIE - Разрешение прерывания от компаратора
ADPS2:0 - Выбор делителя частоты для преобразования. Частота, на которой производятся преобразования напрямую влияет на точность. Чем выше частота, тем ниже точность. При этом АЦП тактируется через делитель от частоты ядра микроконтроллера.

 

 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

 


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]);
  }
}

 

По материалам rc-master,  автор Зайчиков Александр

 

В начало обзора



Купить радиодетали для ремонта




Необходимо добавить материалы...
Результат опроса Результаты Все опросы нашего сайта Архив опросов
Всего голосовало: 267



          

Радио для всех© 2017