Не отступай и не сдавайся !!!

+7(495)233-9079
arduino4home@gmail.com

Урок 13
Аналоговые входы платы Ардуино. Чтение аналоговых сигналов. Измерение среднего значения сигнала.

Предыдущий урок Ардуино - главная Следуюший урок

В уроке научимся работать с аналоговыми входами Ардуино.

Аналоговые входы платы Ардуино.

Плата Arduino UNO содержит 6 аналоговых входов предназначенных для измерения напряжения сигналов. Правильнее сказать, что 6 выводов платы могут работать в режиме, как дискретных выводов, так и аналоговых входов.

Эти выводы имеют номера от 14 до 19. Изначально они настроены как аналоговые входы, и обращение к ним можно производить через имена A0-A5. В любой момент их можно настроить на режим дискретных выходов.

pinMode(A3, OUTPUT);  // установка режима дискретного вывода для A3 
digitalWrite(A3, LOW);   //- установка низкого состояния на выходе A3

Чтобы вернуть в режим аналогового входа:

pinMode(A3, INPUT);   //- установка режима аналогового входа для A3

Аналоговые входы и подтягивающие резисторы.

К выводам аналоговых входов, так же как и к дискретным выводам, подключены подтягивающие резисторы. Включение этих резисторов производится командой

digitalWrite(A3, HIGH); //- включить подтягивающий резистор к входу A3

Команду необходимо применять к выводу настроенному в режиме входа.

Надо помнить, что резистор может оказать влияние на уровень входного аналогового сигнала. Ток от источника питания 5 В, через подтягивающий резистор, вызовет падение напряжения на внутреннем сопротивлении источника сигнала. Так что лучше резистор отключать.

Аналого-цифровой преобразователь платы Ардуино.

Собственно измерение напряжение на входах производится аналого-цифровым преобразователем (АЦП) с коммутатором на 6 каналов. АЦП имеет разрешение 10 бит, что соответствует коду на выходе преобразователя 0…1023. Погрешность измерения не более 2 единиц младшего разряда.

Для сохранения максимальной точности (10 разрядов) необходимо, чтобы внутреннее сопротивление источника сигнала не превышало 10 кОм. Это требование особенно важно при использовании резисторных делителей, подключенных к аналоговым входам платы. Сопротивление резисторов делителей не может быть слишком большим.

Программные функции аналогового ввода.

int analogRead(port)

Считывает значение напряжения на указанном аналоговом входе. Входное напряжение диапазона от 0 до уровня источника опорного напряжения (часто 5 В) преобразовывает в код от 0 до 1023.

При опорном напряжении равном 5 В разрешающая способность составляет 5 В / 1024 = 4,88 мВ.

Занимает на преобразование время примерно 100 мкс.

int inputCod;           //- код входного напряжения 
float inputVoltage; //- входное напряжение в В

inputCod= analogRead(A3); //- чтение напряжения на входе A3 
inputVoltage= ( (float)inputCod * 5. / 1024. ); //- пересчет кода в напряжение (В)

void analogReference(type)

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

Напряжение на входе = код АЦП * опорное напряжение / 1024.

Аргумент type может принимать следующие значения:

  • DEFAULT – опорное напряжение равно напряжению питания контроллера ( 5 В или 3,3 В). Для Arduino UNO R3 – 5 В.
  • INTERNAL – внутреннее опорное напряжение 1,1 В для плат с контроллерами ATmega168 и ATmega328, для ATmega8 – 2,56 В.
  • INTERNAL1V1 – внутреннее опорное напряжение 1,1 В для контроллеров Arduino Mega.
  • INTERNAL2V56 – внутреннее опорное напряжение 2,56 В для контроллеров Arduino Mega.
  • EXTERNAL – внешний источник опорного напряжения, подключается к входу AREF.

analogReference(INTERNAL); //- опорное напряжение равно 1,1 В

Рекомендуется внешний источник опорного напряжения подключать через токоограничительный резистор 5 кОм.

Двухканальный вольтметр на Ардуино.

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

Решим, что вольтметр должен измерять напряжение в пределах не меньше 0…20 В и разработаем схему подключения входов вольтметра к плате Arduino UNO.

Если мы зададим опорное напряжение равным 5 В, то аналоговые входы платы будут измерять напряжение в пределах 0…5 В. А нам надо как минимум 0…20 В. Значит надо использовать делитель напряжения.

Напряжение на входе и выходе делителя связаны соотношением:

Uвыхода = ( Uвхода / (R1 + R2 )) * R2

Коэффициент передачи:

K = Uвыхода / Uвхода = R2 / ( R1 + R2 )

Нам необходим коэффициент передачи 1/4 ( 20 В * 1/4 = 5 В).

Для сохранения максимальной точности (10 разрядов) необходимо, чтобы внутреннее сопротивление источника сигнала не превышало 10 кОм. Поэтому выбираем резистор R2 равным 4,22 кОм. Рассчитываем сопротивление резистора R1.

0,25 = 4,22 / ( R1 + 4,22)
R1 = 4,22 / 0.25 – 4,22 = 12,66 кОм

У меня с ближайшим номиналом нашлись резисторы сопротивлением 15 кОм. С резисторами R1 = 15 кОм и R2 = 4,22 :

5 / (4,22 / (15 + 4,22)) = 22,77 В.

Схема вольтметра на базе Ардуино будет выглядит так.

Два делителя напряжения подключены к аналоговым входам A0 и A1. Конденсаторы C1 и C2 вместе с резисторами делителя образуют фильтры нижних частот, которые убирают из сигналов высокочастотные шумы.

Я собрал эту схему на макетной плате.

Первый вход вольтметра я подключил к регулируемому источнику питания, а второй к питанию 3,3 В платы Ардуино. Для контроля напряжения к первому входу я подключил вольтметр. Осталось написать программу.

Программа для измерения напряжения с помощью платы Ардуино.

Алгоритм простой. Надо:

  • с частотой два раза в секунду считывать код АЦП;
  • пересчитывать его в напряжение;
  • посылать измеренные значения по последовательному порту на компьютер;
  • программой монитор порта Arduino IDE отображать полученные значения напряжений на экране компьютера.

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

// программа измерения напряжения
//- на аналоговых входах A0 и A1

#include <MsTimer2.h>

#define MEASURE_PERIOD 500  //- время периода измерения
#define R1  15.  //- сопротивление резистора R1
#define R2  4.22 //- сопротивление резистора R2

int timeCount;  //- счетчик времени
float u1, u2;   //- измеренные напряжения

void setup() {
  Serial.begin(9600);  //- инициализируем порт, скорость 9600
  MsTimer2::set(1, timerInterupt); //- прерывания по таймеру, период 1 мс 
  MsTimer2::start();              //- разрешение прерывания
}

void loop() {

//- период 500 мс 
  if ( timeCount >= MEASURE_PERIOD ) {
    timeCount= 0;

//- чтение кода канала 1 и пересчет в напряжение
    u1=  ((float)analogRead(A0)) * 5. / 1024. / R2 * (R1 + R2);
    //- чтение кода канала 2 и пересчет в напряжение
    u2=  ((float)analogRead(A1)) * 5. / 1024. / R2 * (R1 + R2);

//- передача данных через последовательный порт
    Serial.print("U1 = ");     Serial.print(u1, 2);
    Serial.print("   U2 = ");  Serial.println(u2, 2);          
  }  
}

// обработка прерывания 1 мс
void  timerInterupt() {
  timeCount++;
}

Поясню строчку, в которой пересчитывается код АЦП в напряжение:

// чтение кода канала 1 и пересчет в напряжение    
u1= ((float)analogRead(A0)) * 5. / 1024. / R2 * (R1 + R2);

  • Считывается код АЦП : analogRead(A0).
  • Явно преобразуется в формат с плавающей запятой: (float).
  • Пересчитывается в напряжение на аналоговом входе: * 5. / 1024. Точка в конце чисел показывает, что это число с плавающей запятой.
  • Учитывается коэффициент передачи делителя: / R2 * (R1 + R2).

Загрузим программу в плату, запустим монитор последовательного порта.

Два бегущих столбика показывают значения измеренных напряжений. Все работает.

Измерение среднего значения сигнала.

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

Значения напряжения первого канала на экране монитора все время дергаются, скачут. А показания контрольного вольтметра вполне стабильны. Это объясняется тем, что контрольный вольтметр измеряет среднее значение сигнала, в то время как плата Ардуино считывает отдельные выборки каждые 500 мс. Естественно, момент чтения АЦП попадает в разные точки сигнала. А при высоком уровне пульсаций амплитуда в этих точках разная.

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

Решение – сделать несколько частых выборок и усреднить измеренное значение. Для этого:

  • в обработчике прерывания считываем код АЦП и суммируем его с предыдущими выборками;
  • отсчитываем время усреднения (число выборок усреднения);
  • при достижении заданного числа выборок – сохраняем суммарное значение кодов АЦП;
  • для получения среднего значения сумму кодов АЦП делим на число выборок усреднения.

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

// программа измерения среднего напряжения
//- на аналоговых входах A0 и A1

#include <MsTimer2.h>

#define MEASURE_PERIOD 500  //- время периода измерения
#define R1  15.  //- сопротивление резистора R1
#define R2  4.22 //- сопротивление резистора R2

int timeCount;  //- счетчик времени
long  sumU1, sumU2; //- переменные для суммирования кодов АЦП
long  avarageU1, avarageU2; //- сумма кодов АЦП (среднее значение * 500)
boolean flagReady;  //- признак готовности данных измерения

void setup() {
  Serial.begin(9600); //- инициализируем порт, скорость 9600
  MsTimer2::set(1, timerInterupt); //- прерывания по таймеру, период 1 мс 
  MsTimer2::start();              //- разрешение прерывания
}

void loop() {

if ( flagReady == true ) {
    flagReady= false;
    //- пересчет в напряжение и передача на компьютер
    Serial.print("U1 = ");
    Serial.print( (float)avarageU1 / 500. * 5. / 1024. / R2 * (R1 + R2), 2);
    Serial.print("   U2 = ");
    Serial.println( (float)avarageU2 / 500. * 5. / 1024. / R2 * (R1 + R2), 2);
    }
}

// обработка прерывания 1 мс
void  timerInterupt() {

timeCount++;  //- +1 счетчик выборок усреднения
  sumU1+= analogRead(A0);  //- суммирование кодов АЦП
  sumU2+= analogRead(A1);  //- суммирование кодов АЦП

//- проверка числа выборок усреднения
  if ( timeCount >= MEASURE_PERIOD ) {
    timeCount= 0;
    avarageU1= sumU1; //- перегрузка среднего значения 
    avarageU2= sumU2; //- перегрузка среднего значения 
    sumU1= 0;
    sumU2= 0;
    flagReady= true;  //- признак результат измерений готов
    }
}

В формулу пересчета кода АЦП в напряжение добавилось /500 – число выборок. Загружаем, запускаем монитор порта (Cntr+Shift+M).

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

Число выборок надо выбирать, учитывая:

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

Основным источником помех в аналоговых сигналах является сеть 50 Гц. Поэтому желательно выбирать время усреднения кратное 10 мс – времени полупериода сети частотой 50 Гц.

Оптимизация вычислений.

Вычисления с плавающей запятой просто пожирают ресурсы 8ми разрядного микроконтроллера. Любая операция с плавающей запятой требует денормализацию мантиссы, операцию с фиксированной запятой, нормализацию мантиссы, коррекцию порядка… И все операции с 32 разрядными числами. Поэтому необходимо свести к минимуму употребление вычислений с плавающей запятой. Как это сделать я расскажу в следующих уроках, но давайте хотя бы оптимизируем наши вычисления. Эффект будет значительный.

В нашей программе пересчет кода АЦП в напряжение записан так:

(float)avarageU1 / 500. * 5. / 1024. / R2 * (R1 + R2)

Сколько здесь вычислений, и все с плавающей запятой. А ведь большая часть вычислений – операции с константами. Часть строки:

/ 500. * 5. / 1024. / R2 * (R1 + R2)

мы можем расчитать на калькуляторе и заменить на одну константу. Тогда наши вычисления можно записать так:

(float)avarageU1 * 0.00004447756

Умные компиляторы сами распознают вычисления с константами и рассчитывать их на этапе компиляции. У меня возник вопрос, насколько умный компилятор Андруино. Решил проверить.

Я написал короткую программу. Она выполняет цикл из 10 000 проходов, а затем передает на компьютер время выполнения этих 10 000 циклов. Т.е. она позволяет увидеть время выполнения операций, размещенных в теле цикла.

// проверка оптимизации вычислений

int x= 876;
float y;
unsigned int count;
unsigned long timeCurrent, timePrev;

void setup() {
  Serial.begin(9600);
}

void loop() {
  count++;
  //- y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);
  //- y= (float)x * 0.00004447756;

  if (count >= 10000) {
    count= 0;
    timeCurrent= millis();
    Serial.println( timeCurrent - timePrev   );
    timePrev= timeCurrent;      
  }
}

В первом варианте, когда в цикле операции с плавающей запятой закомментированы и не выполняются, программа выдала результат 34 мс.

Т.е. 10 000 пустых циклов выполняются за 34 мс.

Затем я открыл строку:

y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);

повторяет наши вычисления. Результат 10 000 проходов за 922 мс или

( 922 – 34 ) / 10 000 = 88,8 мкс.

Т.е. эта строка вычислений с плавающей запятой требует на выполнение 89 мкс. Я думал будет больше.

Теперь я закрыл эту строку комментарием и открыл следующую, с умножением на заранее рассчитанную константу:

y= (float)x * 0.00004447756;

Результат 10 000 проходов за 166 мс или

( 166 – 34 ) / 10 000 = 13,2 мкс.

Потрясающий результат. Мы сэкономили 75,6 мкс на одной строке. Выполнили ее почти в 7 раз быстрее. У нас таких строк 2. Но ведь их в программе может быть и гораздо больше.

Вывод – вычисления с константами надо производить самим на калькуляторе и применять в программах как готовые коэффициенты. Компилятор Ардуино их на этапе компиляции не рассчитает. В нашем случае следует сделать так:

#define ADC_U_COEFF 0.00004447756 //- коэффициент перевода кода АЦП в напряжение

Serial.print( (float)avarageU1 * ADC_U_COEFF, 2);

Оптимальный по быстродействию вариант – это передать на компьютер код АЦП, а вместе с ним и все вычисления с плавающей запятой. При этом на компьютере принимать данные должна специализированная программа. Монитор порта из Arduino IDE не подойдет.

О других способах оптимизации программ Ардуино я буду рассказывать в будущих уроках по мере необходимости. Но без решения этого вопроса невозможно разрабатывать сложные программы на 8ми разрядном микроконтроллере.

В следующем уроке научимся работать с внутренним EEPROM, поговорим о контроле целостности данных.


Хостинг нашего сайта осуществляется узлом www.cherepovets-city.ru
© 2000-2018 23/6/17 7:19->