View Categories

ADC, аналоговый сигнал

АЦП #

Некоторые микроконтроллеры имеют на борту АЦП - аналогово-цифровой преобразователь (ADC, Analog-to-Digital Converter). Это устройство, позволяющее измерять напряжение и передавать его в программу, по сути - вольтметр. Аналоговое напряжение оцифровывается и превращается в численную величину. АЦП имеет два важных параметра:

  • Опорное напряжение - максимальное напряжение, которое можно подавать на АЦП. Минимальное - ноль, GND
  • Разрешение (разрядность) - точность, количество значений, с которой АЦП измеряет напряжение от 0 до опорного, измеряется в битах. Например 10 бит = 2^10 = 1024 значения, при опорном 5V получится точность (цена деления) 5 / 1024 = 0.0049 V

Пины #

АЦП, как и остальная периферия МК, выведен на пины. На каких именно пинах есть выводы АЦП можно посмотреть на распиновке, например - Arduino Nano (прямоугольники с зелёным фоном):

Arduino Nano имеет один 8-канальный 10-битный АЦП, соответственно 8 выводов на плате. На выбор есть несколько опорных напряжений, по умолчанию опорное напряжение равно напряжению питания МК.

analogRead #

Для измерения напряжения используется функция analogRead(пин), где пин - номер пина. В каком виде передаётся номер пина лучше уточнить в описании к конкретной плате, например на Arduino Nano вызов

  • analogRead(0) - номер канала АЦП
  • analogRead(A0) - подпись на плате
  • analogRead(14) - номер GPIO Arduino

Измерит напряжение на одном и том же пине - подписанном как A0 на плате.

Функция возвращает результат в виде числа, соответствующего напряжению на пине: значениям от 0 до опорного Вольт соответствуют числа от 0 до разрядность - 1, т.е. при разрядности 10 бит максимальное значение будет 1024 - 1 = 1023.

Нельзя подавать на АЦП напряжение ниже нуля или выше опорного. Даже если это произойдёт - АЦП выдаст значение по границе своего диапазона, т.е. результат не опустится ниже 0 и не поднимется выше max-1

На Arduino Nano АЦП работает независимо от GPIO, то есть прочитать сигнал можно даже если пин настроен как выход. Не подключайте ничего к плате и загрузите следующий скетч:

void setup() {
    Serial.begin(115200);
    pinMode(A0, OUTPUT);
}

void loop() {
    digitalWrite(A0, LOW);
    delay(1);
    Serial.println(analogRead(A0));
    delay(500);

    digitalWrite(A0, HIGH);
    delay(1);
    Serial.println(analogRead(A0));
    delay(500);
}

Откройте монитор порта - в нём побегут цифры 0 и 1023. Мы подаём напряжение на пин, на всякий случай ждём 1 мс и выводим значение analogRead в порт. Затем выключаем пин и выводим новое значение, и так по кругу. При низком сигнале получаем 0, а при высоком - 5V - получаем 1023, т.к. оно равно опорному напряжению, это максимальное значение. Таким образом, МК сам выдаёт и сам измеряет это напряжение на пине.

Вспомните предыдущий урок - никуда не подключенный пин в режиме входа ловит всякие случайные наводки из воздуха. Если его цифровое значение меняется случайным образом между 0 и 1, значит напряжение на нём скачет довольно сильно! Загрузите следующий скетч, к пину A0 ничего не подключено:

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

void loop() {
    Serial.println(analogRead(A0));
}

Откройте плоттер графиков в Arduino IDE:

Вот они, наводки из сетевых проводов! Если подключить к пину провод, второй конец которого оставить висеть в воздухе - амплитуда сигнала увеличится:

Была 100, а стала около 900 - провод выполняет роль антенны и в нём сильнее наводятся помехи

Скорость работы #

Функция analogRead выполняется не мгновенно - она переключает канал АЦП на указанный, запускает преобразование и ждёт окончания процесса оцифровки, после чего возвращает результат. По умолчанию на Arduino Nano/Uno и подобных АЦП настроен так, что analogRead выполняется около 120 мкс (микросекунд). Работая с АЦП напрямую, можно увеличить скорость оцифровки, а также получать данные по прерыванию, не ожидая окончания преобразования. Подробнее об этом будет рассказано в отдельном цикле уроков по МК AVR.

Также например у ESP8266 analogRead не только выполняется медленно, но и мешает работе WiFi: если вызывать чтение слишком часто - WiFi просто не будет работать.

Потенциометр #

Потенциометр является очень простым и удобным способом ввода данных в программу. Близкий друг потенциометра - джойстик, состоит из двух потенциометров, по одному на каждую ось. Вспомним урок про потенциометр и подключим его на аналоговый пин как делитель напряжения:

Загрузите следующий скетч - он измеряет сигнал на A0 и просто выводит его в порт с задержкой:

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

void loop() {
    Serial.println(analogRead(A0));
    delay(50);
}

Повращайте рукоятку от минимума до максимума - напряжение на пине будет меняться от 0 до 5V, соответственно сигнал получится от 0 до 1023:

Следующий интересный эксперимент: выведем график одновременно аналогового и цифрового сигналов через digitalRead с того же пина. Его домножим на 500, чтобы графики были одного размера:

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

void loop() {
    Serial.print(digitalRead(A0) * 500);
    Serial.print(',');
    Serial.println(analogRead(A0));
    delay(20);
}

На этом графике видно, при каком значении digitalRead начинает считать сигнал за высокий и низкий: высокий - чуть больше 500, низкий - чуть меньше 500.

Опорное напряжение #

Информация в данной главе относится к Arduino Nano/UNO/Mega, варианты опорного для других плат и МК см. в документации на конкретную модель

Опорное напряжение играет главную роль в измерении аналогового сигнала, потому что именно от него зависит максимальное измеряемое напряжение и вообще возможность и точность перевода полученного значения 0.. 1023 в Вольты. Настроить его можно при помощи функции analogReference(режим), где режим:

  • DEFAULT - опорное напряжение равно напряжению питания МК. Активно по умолчанию
  • INTERNAL - встроенный источник опорного на 1.1V (ATmega168 или ATmega328P) и 2.56V (ATmega8)
  • EXTERNAL - опорным будет считаться напряжение, поданное на пин AREF
  • INTERNAL1V1 - встроенный источник опорного на 1.1V (Arduino Mega)
  • INTERNAL2V56 - встроенный источник опорного на 2.56V (Arduino Mega)

После изменения источника опорного напряжения (вызова analogReference) первые несколько измерений могут быть нестабильными

Что касается точности: при питании от 5V и режиме DEFAULT получится точность измерения напряжения 5 / 1024 = 4.9 мВ. Поставив INTERNAL, мы можем измерять напряжение от 0 до 1.1V с точностью 1.1 / 1024 = 0.98 мВ. Весьма неплохо!

Что касается внешнего источника опорного напряжения: нельзя подавать отрицательное напряжение или выше 5.5V в качестве внешнего опорного в пин AREF. Также при подключении внешнего опорного напряжения нужно вызвать analogReference(EXTERNAL) до первого вызова analogRead, считая с запуска программы, иначе можно повредить микроконтроллер!

Чтобы "на лету" переключаться между внутренними и внешним опорными, можно подключить его на AREF через резистор на ~5 кОм. Вход AREF имеет собственное сопротивление в ~32 кОм, поэтому реальное опорное будет вычисляться по формуле REF = V * 32 / (R + 32), где R - сопротивление резистора (кОм), через которое подключено опорное напряжение V. Например для 2.5V получим 2.5 * 32 / (32 + 5) = ~2.2V реальное опорное.

Измерение напряжения #

Ниже опорного #

Для измерения напряжения ниже опорного достаточно просто подключить его к выводу АЦП и вызвать analogRead. Конвертировать полученное значение в Вольты можно по следующей формуле: V = ADC * Vref / N, где:

  • ADC - значение с АЦП
  • Vref - опорное напряжение, Вольт
  • N - разрешение АЦП в количестве значений, 2^разрядность или 2^разрядность - 1

С последним пунктом есть тонкий момент - у разных МК АЦП устроены и работают по-разному. Например у AVR (Arduino Nano/UNO/Mega) АЦП считает максимальным значением Vref минус 1 бит по разрешению. Это означает, что в формуле делить нужно на 2^разрядность, то есть на 1024 при 10 битах.


Из даташита на ATMega328p

У других МК может быть более логично - если Vref соответствует максимальному значению с АЦП, то делить нужно на 2^разрядность - 1. Эту информацию можно найти в документации на конкретный МК.

АЦП может "шуметь" в пределах пары бит, поэтому каким значением выбирать максимальное - на самом деле не так уж важно

Давайте измерим напряжение пальчиковой и литиевой батарейки:

Не перепутайте + и -!!!

#define VREF 4.6    // опорное (напряжение питания)

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

void loop() {
    float v = analogRead(0) * VREF / 1024;
    Serial.println(v);
    delay(100);
}

У меня получилось 1.51 на пальчиковую и 3.12 на литиевую батарейку.

При питании платы от USB напряжение питания составляет около 4.6V, Vref по умолчанию равно напряжению питания - его мы и указали в скетче

Точность преобразования значения с АЦП в Вольты зависит от точности Vref, поэтому в программе выше получится приблизительный результат. Чтобы измерить напряжение точнее - можно измерить вольтметром напряжение питания платы между 5V и GND и внести это число в программу - это и будет точное Vref. Некоторые АЦП имеют калиброванный источник Vref, при помощи которого можно сразу измерять внешнее напряжение с хорошей точностью. Также высокоточные источники опорного напряжения существуют в виде отдельных микросхем - можно подключить такую микросхему как EXTERNAL в пин AREF и получить ещё более точные измерения.

Выше опорного #

Для измерения напряжения выше Vref, например 12V аккумулятора от шуруповёрта или батарейки Крона, нужно использовать делитель напряжения на резисторах:

Как выбрать/рассчитать делитель?

  • Напряжение с делителя на АЦП будет равно Vin * R2 / (R1 + R2) - нужно подобрать такие R1 и R2, чтобы это напряжение было близко к Vref при максимальном входном напряжении
  • Согласно даташиту на ATmega, сумма R1 + R2 не рекомендуется больше 10 кОм для достижения наибольшей точности измерения. В то же время, через делитель на 10 кОм будет течь ощутимый ток, что критично при питании от батареи. Если схема работает от сети или от аккумулятора, но МК не используется в режиме сна - спокойно ставим делитель 10 кОм
Калькулятор делителя
VinV
R1Ohm
R2Ohm
Напряжение на ADC
V

Давайте измерим напряжение на 9V батарейке Крона. В качестве делителя можно использовать 3 резистора по 10 кОм (два в параллель дадут 5 кОм), такого делителя хватит на напряжение до 15V:

Для вычисления напряжения выведем формулу из двух уже известных формул:

Vadc = Vin * R2 / (R1 + R2)
Vadc = ADC * Vref / N

Vin = ADC * Vref * (R1 + R2) / R2 / N

И получим финальный скетч для измерения высокого напряжения (до 15V):

#define VREF 4.6
#define DIV_R1 10000
#define DIV_R2 5000

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

void loop() {
    float v = analogRead(0) * VREF * ((DIV_R1 + DIV_R2) / DIV_R2) / 1024;
    Serial.println(v);
    delay(100);
}

Делитель и энергосбережение #

Если схема работает от аккумулятора и МК "спит": пусть аккумулятор 12V, тогда через 10 кОм делитель пойдёт ток 1.2 мА. Сам микроконтроллер в режиме сна потребляет ~1 мкА, что в тысячу раз меньше! На самом деле можно взять делитель с гораздо бОльшим суммарным сопротивлением (но не больше 20 МОм, внутреннего сопротивления самого АЦП), но обязательно поставить конденсатор на ~0.1 мкФ между АЦП и GND (вот здесь проводили эксперимент). Таким образом например при при R1 + R2 = 10 МОм ток через делитель будет 1.2 мкА, что уже гораздо лучше!

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх