Руководство · Начальный · 30 min
Чтение аналоговых напряжений: опорные напряжения и делители
analogRead возвращает отношение, а не напряжение. Выбор стабильного опорного напряжения, расчёт делителя и создание достоверного монитора аккумулятора.
Введение
analogRead() — самая удобная функция Arduino и одна из самых обманчивых. Она возвращает число от 0 до 1023, все считают это число напряжением, и обычно это ни к чему не приводит.
На роботе всё ломается, потому что у робота есть двигатели, а двигатели изменяют именно то, относительно чего измеряется это число.
analogRead возвращает отношение
ADC не измеряет вольты. Он сравнивает вход с опорным напряжением и сообщает отношение:
reading = 1023 × V_in / V_ref
На Arduino Uno V_ref по умолчанию равен AVCC — шине питания платы 5 V. Поэтому analogRead() на самом деле отвечает на вопрос «какую долю от 5 V составляет этот сигнал?», а перевод в вольты умножением на 5/1023 корректен только пока шина равна ровно 5 V.
Вы уже знаете, что это не так, как только работают двигатели.
Опорное напряжение — источник ошибки
Подключите аккумулятор через делитель к A0 и наблюдайте за числом при запуске двигателей.
Защита по низкому напряжению, построенная на первой схеме, делает противоположное своей задаче: чем сильнее работает робот, тем исправнее выглядит аккумулятор. И сбой тихий, потому что каждый отдельный отсчёт является совершенно допустимым отношением.
Исправление — стабильное опорное напряжение. В ATmega328P есть встроенный источник опорного напряжения на полосовом переходе 1.1 V:
analogReference(INTERNAL); // 1.1 V on an Uno; INTERNAL1V1 on a Mega
analogRead(A0); // discard — the reference needs a moment to settle
После переключения опоры всегда отбрасывайте первый отсчёт. Внутренней опоре и входному конденсатору нужно время, а первый замер выполняется до того, как оно пройдёт.
Расчёт делителя
Теперь опорное напряжение равно 1.1 V, а блок — 8.4 V, поэтому делитель должен привести верх диапазона чуть ниже опорного напряжения:
V_adc = V_pack × R2 / (R1 + R2)
При R1 = 68 kΩ и R2 = 10 kΩ отношение равно 0.128, поэтому полностью заряженный блок 8.4 V даёт 1.077 V — чуть ниже опоры, почти на всю шкалу. Три числа, которые стоит проверить в любом проектируемом делителе:
| Проверка | Этот делитель | Почему |
|---|---|---|
| Верх диапазона и опора | 1.077 V и 1.1 V | При превышении опоры сигнал срезается |
| Ток покоя | 108 µA | За день расходует 2.6 mAh — пренебрежимо мало по сравнению с 2000 mAh |
| Выходное сопротивление (R1∥R2) | 8.7 kΩ | Держите ниже ~10 kΩ, см. ниже |
Сравните с наивной схемой — делителем 20 kΩ/10 kΩ, считываемым относительно шины 5 V. Полный блок в нём даёт 2.80 V, то есть 573 отсчёта из возможных 1023, а на всём диапазоне аккумулятора 6.0–8.4 V используется 164 отсчёта против 286 у лучшей схемы. Почти вдвое больше разрешения при тех же двух резисторах и одной строке кода.
Разрешение не равно точности
Десять бит относительно опоры 5 V дают 4.88 mV на шаг, а относительно опоры 1.1 V — 1.07 mV. Ни одно из этих чисел не является точностью.
Что действительно вас ограничивает:
- Допуск опорного напряжения. Источник опорного напряжения на полосовом переходе номинально равен 1.1 V и меняется на несколько процентов между экземплярами микросхем. Это фиксированная ошибка шкалы, поэтому измерьте её один раз для каждой платы: подайте известное напряжение, считайте отсчёты и вычислите настоящую опору. Сохраните эту константу. Это этап калибровки, а не необязательное уточнение.
- Шум. Питание робота шумное, а усреднение — дешёвый ответ. Усреднение N отсчётов уменьшает случайный шум в √N раза: 16 отсчётов дают в 4 раза меньший шум, то есть один дополнительный бит полезного разрешения. Ошибку шкалы это не устраняет.
- Выходное сопротивление источника. ADC заряжает небольшой конденсатор выборки и хранения через то, к чему он подключён. Источник с высоким сопротивлением не успевает заполнить его за отведённое время, и остаточный заряд от предыдущего канала всё ещё присутствует.
Последний случай даёт характерный сбой, который стоит назвать.
Каналы, которые читают как соседние
Читайте A0 и A1 по очереди с источниками высокого сопротивления, и каждый отсчёт будет смещаться в сторону предыдущего. Плавно изменяйте напряжение потенциометром, подключённым к A0, и посмотрите, как изменится A1. Ничего не подключено неправильно.
Есть два исправления, и можно использовать оба:
int readSettled(uint8_t pin) {
analogRead(pin); // charge the sample-and-hold on this channel
return analogRead(pin); // then take the one you keep
}
Двойное чтение с отбрасыванием первого результата даёт конденсатору второе окно выборки, чтобы достичь нового напряжения. Второе исправление — снизить выходное сопротивление источника; поэтому в делителе выше используются 68 kΩ/10 kΩ, а не более аккуратные на вид 100 kΩ/100 kΩ с выходным сопротивлением 50 kΩ.
Где это проявляется на роботе
- Мониторинг аккумулятора, как выше, — именно он делает отсечку по низкому напряжению достаточно достоверной для защиты Li-ion блока.
- Аналоговые датчики линии. Массив ИК-датчиков отражения (IR) даёт непрерывное значение для каждого датчика, благодаря чему робот, следующий по линии, может вычислять плавное положение, а не флаг «слева/справа». Эти датчики считываются относительно той же шины, которую просаживают двигатели, — одна из причин, почему калибровка робота, следующего по линии, смещается по мере разряда аккумулятора.
- Измерение тока шунтом или датчиком Холла, выход которого является напряжением.
- Потенциометры как ручки настройки, самый удобный способ настроить коэффициент ПИД (PID) без повторной прошивки.
- Не для датчика 5 V на плате 3.3 V. ESP32 или Raspberry Pi нужен делитель для защиты, а не только для диапазона.
Когда всё идёт не так
| Симптом | Обычно |
|---|---|
| Показание аккумулятора растёт при запуске двигателей | Опора от просадшей шины; используйте INTERNAL |
| Показание зафиксировано на 1023 | Вход выше опорного напряжения — отношение делителя слишком велико |
| Один канал повторяет предыдущий | Слишком большое выходное сопротивление источника; читайте дважды и отбрасывайте первый результат |
| Показания скачут на десятки отсчётов | Шум питания; усредняйте и проверьте силовые шины |
| Напряжение постоянно отличается на несколько процентов | Нерассчитанная внутренняя опора — измерьте её один раз |
Первое показание после analogReference() неверно |
Ожидаемо; отбросьте его |
| Калибровка датчика линии смещается при разряде аккумулятора | Показания с опорой от падающей шины |
| Используется только 15% диапазона | Делитель рассчитан на 5 V, а считывание идёт относительно 1.1 V |
Правило, предотвращающее большую часть этих проблем: сначала решите, каким будет опорное напряжение, и только потом принимайте остальные решения. Каждое число, которое даёт ADC, зависит от него, поэтому движущаяся опора превращает каждое показание в результат сразу двух факторов.
Исследуйте граф
Входит в эти сборки
Проекты и учебные пути, в которые входит это руководство.
Дополнительные материалы