Урок 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);
  }
}

Молодцы, теперь у нас есть очень полезный прибор!