Урок 1.5. Аналоговый сигнал. Считывание аналогового сигнала с датчиков. Процедура analogRead()
Базовый курс "Программирование микроконтроллеров"
Модуль 1. Введение в Arduino. Работа с цифровым и аналоговым сигналом
Аналого-цифровые преобразования — АЦП
Для измерения физических величин, люди придумали множество различных приборов. Так, например, термометр позволяет узнать температуру вещества, барометр — давление газа, гигрометр — влажность воздуха. А с помощью весов можно измерить вес тела.
Все эти устройства имеют шкалу, которую мы используем для фиксации их показаний. Рассмотрим простой пример — определение температуры с помощью обычного градусника.
Человек решает эту задачу очень просто: мы смотрим, к какому из делений ближе всего приблизился уровень жидкости в градуснике. Полученное таким образом значение и будет измеренной температурой.
Но насколько точными будут эти показания? Ведь если мы возьмём более точный прибор, то мы сможем измерить температуру с большей определенностью. Например из рисунка мы видим, что температура около 36 градусов. Возьмём прибор поточнее – температура уже будет составлять 36,125 градусов. До каких пор мы можем “уточнять” температуру? Конечно, до бесконечности.
Так мы подходим к тому, что в мире все величины можно разделить на аналоговые и дискретные.
Обратите внимание: микроконтроллер должен "знать и помнить" все числа, которыми он может оперировать, но когда этих чисел бесконечно, то микроконтроллеру нужна и бесконечная память для хранения всех этих чисел.
Поскольку при измерении температуры, мы перевели бесконечную величину в конкретную, то можно сказать, что мы осуществляем преобразование аналоговой непрерывной величины в дискретную, которую можно записать на бумаге с помощью цифр.
Чтобы автоматизировать процесс измерения аналоговых величин, и возложить эту задачу на электронные приборы, инженеры создали особое устройство, называемое аналого-цифровым преобразователем (АЦП). Это устройство позволяет превращать аналоговый сигнал в цифровой код, пригодный для использования в ЭВМ, подобно тому, как мы определили значение температуры.
В робототехнике АЦП являются важной составляющей системы датчиков машины. Акселерометр, гироскоп (гиротахометр), барометр, магнитометр, и даже видеокамера — все эти приборы соединяются с центральным процессором с помощью АЦП.
В одном из предыдущих уроков, для ограничения тока через светодиод, мы использовали резисторы. Как было тогда отмечено, существует множество резисторов разного номинала и рассчитанных на разную мощность. Но оказывается, кроме обычных резисторов есть и элементы с изменяемым сопротивлением, называемые переменными резисторами.
Обычно переменные резисторы делают в виде делителя напряжения, и такие элементы называются потенциометрами. Иногда потенциометры именуют реостатами, но это не совсем верно. Вот так выглядит типичный регулировочный потенциометр.
Потенциометр
Обычно, потенциометр применяют для настройки каких-либо параметров аналоговых и цифровых устройств. Например, потенциометром можно регулировать напряжение на базе биполярного транзистора, который управляет мощной нагрузкой: мотором постоянного тока, нагревателем, мощным источником света, и т.п.
Если разобрать потенциометр, то можно увидеть следующую картину:
Скользящий контакт движется по пластине определенного сопротивления, и в зависимости от его положения с контакта A0 можно снять различное сопротивление.
Для большей наглядности работы потенциометра соберем простую схему: при вращении ручки потенциометра будет расти сопротивление его плеча, что в свою очередь приведет к угасанию светодиода, и наоборот.
Подключение потенциометра к Ардуино
Теперь подключим потенциометр к Ардуино и соберем следующую схему:
Напишем следующий код и загрузим его в Ардуино:
// макроопределение используемых пинов
#define pot A0
// переменная для записи показаний потенциометра
int val = 0;
void setup() {
pinMode(pot, INPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(pot); // считываем показания потенциометра
Serial.println(val);
}
В этой программе мы использовали новую функцию:
analogRead(НОМЕР ПИНА);
Функция считывает значение с указанного аналогового входа и возвращает значение от 0 до 1023.
Откройте монитор порта и понаблюдайте за изменением показаний. Вы можете заметить, что показания изменяются в пределах от 0 до 1023. Давайте разберемся, почему так получается.
Дело в том, что у каждого устройства АЦП есть такой важный параметр как разрядность. Чем больше значение этого параметра, тем точнее работает прибор. Разрядность определяет количество градаций, на которые будет разбита шкала замера напряжения от 0 до 5 Вольт.
Предположим, что у нас есть АЦП с разрядностью 1, что бы получить количество градаций мы должгы возвести 2 в степень разрядности, в нашем случае это 1, 2 в степени 1 даст две градации:
от 0 до 2,5 Вольт, на выходе мы получим 0,
от 2,5 до 5 вольт даст нам 1.
То есть 1-битный АЦП сможет распознать только два уровня напряжения.
Графически это можно изобразить следующим образом →
АЦП с разрядностью 2 распознает уже четыре уровня напряжения:
от 0 до 1,25 — это 0;
от 1,25 до 2,5 — это 1;
от 2,5 до 3,75 — это 2;
наконец, от 3,75 до 5 — это 3.
На графиках выше изображена работа АЦП с разрядностью 2 и 3 бит ↑
В Arduino Uno установлен 10-битный АЦП, и это значит, что любое напряжение на аналоговом входе в диапазоне от 0 до 5 вольт будет преобразовано в число с точностью 1/1024 вольта.
На графике будет сложно изобразить столько ступенек. Имея такую точность, 10-битный АЦП может «почувствовать» изменение напряжение на входе величиной всего 5 милливольт.
Изменим нашу программу так, что бы с помощью потенциометра мы изменяли яркость свечения светодиода. Загрузим программу и обратим внимание на то, что будет происходить со светодиодом при вращении ручки потенциометра.
// макроопределение используемых пинов
#define pot A0
#define led 3
// переменная для записи показаний потенциометра
int val = 0;
void setup() {
pinMode(pot, INPUT);
pinMode(led, OUTPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(pot); // считываем показания потенциометра
analogWrite(led,val);
Serial.println(val);
}
Вы можете заметить, что светодиод плавно разгорается целых 4 раза! Почему так происходит? Дело в том, что функция analogRead возвращает значение от 0 до 1023, а функция analogWrite принимает значение от 0 до 255, из-за такого несоответствия величин и возникает такая картина.
Наша задача заключается в том, что бы масштабировать один диапазон в другой.
Для этого воспользуемся новой функцией:
map(МАСШТАБИРУЕМАЯ ПЕРЕМЕННАЯ, НИЖНЯЯ ГРАНИЦА ТЕКУЩЕГО ДИАПОЗОНА, ВЕРХНЯЯ ГРАНИЦА ТЕКУЩЕГО ДИАПОЗОНА, НИЖНЯЯЯ ГРАНИЦА НОВОГО
ДИАПОЗОНА, ВЕРХНЯЯ ГРАНИЦА НОВОГО ДИАПОЗОНА)
Перепишем нашу программу использую функцию map:
// макроопределение используемых пинов
#define pot A0
#define led 3
// переменная для записи показаний потенциометра
int val = 0;
void setup() {
pinMode(pot, INPUT);
pinMode(led, OUTPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(pot); // считываем показания потенциометра
val = map(val, 0, 1023, 0, 255); // масштабируем переменную val
analogWrite(led,val);
Serial.println(val);
}
Теперь, вращая ручку потенциометра мы будем видеть правильную картину свечения светодиода!
Проект "Регулятор звука"
Соберем проект для управления звуком с помощью потенциометра по схеме:
Напишем очень простой код на включение нашего динамика при достижении значений потенциометра определенного значения:
// макроопределение используемых пинов
#define pot A0
#define buz 3
// переменная для записи показаний потенциометра
int val = 0;
// переменная для записи значений частоты звука
void setup() {
pinMode(pot, INPUT);
pinMode(buz, OUTPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(pot); // считываем показания потенциометра
// если значение потенциометра больше 100 то включаем динамик
if(val > 100)
{
tone(buz,1000);
}
else
noTone(buz);
Serial.println(val);
}
Отлично, теперь масштабируем частоту звучания в зависимости от положения ручки потенциометра:
// макроопределение используемых пинов
#define pot A0
#define buz 3
// переменная для записи показаний потенциометра
int val = 0;
// переменная для записи значений частоты звука
int freq = 0;
void setup() {
pinMode(pot, INPUT);
pinMode(buz, OUTPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(pot); // считываем показания потенциометра
freq = map(val,100,1023,1000,5000);
// если значение потенциометра больше 100 то включаем динамик
if(val > 100)
{
tone(buz,freq);
delay(100);
}
else
noTone(buz);
Serial.println(val);
}
Ну и напоследок, добавим зависимость времени звучания от положения ручки потенциометра:
// макроопределение используемых пинов
#define pot A0
#define buz 3
// переменная для записи показаний потенциометра
int val = 0;
// переменная для записи значений частоты звука
int freq = 0;
// переменная для записи длительности звучания ноты
int t =0;
void setup() {
pinMode(pot, INPUT);
pinMode(buz, OUTPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(pot); // считываем показания потенциометра
freq = map(val,100,1023,1000,5000);
t = map(val,0,1023,1000,50);
// если значение потенциометра больше 100 то включаем динамик
if(val > 100)
{
tone(buz,freq);
delay(t);
}
else
noTone(buz);
Serial.println(val);
}
Отлично, можно перейти к следующему проекту!
Вольтметр на Ардуино. Проект "Тестер батареек"
Для проекта “Тестер батареек” соберем следующую схему:
// макроопределение используемых пинов
#define LED_RED 2
#define LED_GREEN 3
#define bat A0
// переменные для рассчёта напряжения на входе А0
int val = 0;
float V = 0;
void setup() {
pinMode(bat, INPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(bat);
Serial.println(val);
}
Напишем простой код для считывания аналогового сигнала с батарейки:
Подключим батарейку к нужным выводам и проверим наличие сигнала в порту А0 открыв монитор порта. Если всё работает, то мы можем двигаться дальше!)
Как же мы можем перевести показания АЦП в Вольты? Всё очень просто, воспользуемся соотношением:
\frac{V}{V_{ref}} = \frac{VAL}{1023}
Из этого соотношения мы видим, что отношения измеренного напряжения к опорному напряжению 5 Вольт равно отношению измеренного показания АЦП к максимальному значению АЦП, из этого соотношения мы можем получить формулу для преобразования значения АЦП в напряжение:
V = \frac{VAL}{1023}*5
Добавим эту формулу в нашу программу:
// макроопределение используемых пинов
#define LED_RED 2
#define LED_GREEN 3
#define bat A0
// переменные для рассчёта напряжения на входе А0
int val = 0;
float V = 0;
void setup() {
pinMode(bat, INPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(bat);
V = val/1023.0*5.0;
Serial.println(V);
}
Подсоединим батарейку и убедимся, что измеренное напряжение похоже на правду:
Осталось добавить индикацию к нашему прибору.
// макроопределение используемых пинов
#define LED_RED 2
#define LED_GREEN 3
#define bat A0
#define buz 5
// переменные для расчёта напряжения на входе А0
int val = 0;
float V = 0;
void setup() {
pinMode(bat, INPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(buz, OUTPUT);
Serial.begin(9600);
}
void loop() {
val = analogRead(bat);
V = val / 1023.0 * 5.0;
Serial.println(V);
// если напряжение на батарейке низкое
if (V > 0.8 && V < 1) {
digitalWrite(LED_RED, HIGH);
digitalWrite(LED_GREEN, LOW);
} else if (V > 1) // если напряжение на батарейке приемлемо
{
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, HIGH);
tone(buz, 2000);
} else { // если батарейка не подключена
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, LOW);
noTone(buz);
}
}
Молодцы, теперь у нас есть очень полезный прибор!