Arduino умеет измерять аналоговые сигналы, в частности напряжение.

Разные версии плат Ардуино имеют и немного разное количество аналоговых пинов. У самых распространенных Arduino UNO  6 входов от А0 - А5 и в NANO к ним добавляются А6, А7 (стоит заметить что последние можно использовать только как аналоговый вход!)

А плата Wemos Mini обходится одним А0

В скетче к аналоговым выводам можно обратится просто номером А-пина А0 - 0 или как написано на плате А0 или по номеру GPIO: А0 - 14, A1 - 15.. А7 - 21

Пример, опрашивающий пин А0, можно использовать любой вариант:

int value1 = analogRead(0);   // считать напряжение с пина A0
int value2 = analogRead(A0);  // считать напряжение с пина A0
int value3 = analogRead(14);  // считать напряжение с пина A0

Хранить полученное значение разумно в переменной типа int, потому что значение варьируется от 0 до 1023.



Нельзя подавать на аналоговый пин напряжение выше напряжения питания МК!

По умолчанию измеряется напряжение от 0 до напряжения питания МК. 
Функция analogReference(mode), где mode:

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

Значение 1023 функции analogRead() будет соответствовать выбранному опорному напряжению или напряжению выше его.

В режиме DEFAULT мы можем оцифровать напряжение от 0 до напряжения питания VCC. Если напряжение питания 4.5 Вольта, и мы подаём 4.5 Вольт - получим оцифрованное значение 1023. Если подаём 5 Вольт - опять же получим 1023, т.к. выше опорного. Это правило работает и дальше, главное не превышать 5.5 Вольт.

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

Что касается внешнего источника опорного напряжения: нельзя подавать напряжение меньше 0V (отрицательное) или выше 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 реальное опорное.

Измерение напряжения 0-5 Вольт

Простой пример, как измерить напряжение на аналоговом пине и перевести его в Вольты. Плата питается от 5V.

float voltage = (float)(analogRead(0) * 5.0) / 1024;

Таким образом переменная voltage получает значение в Вольтах, от 0 до 5. 

Измерение напряжения больше 5 Вольт

Для измерения постоянного напряжения больше 5 Вольт нужно использовать делитель напряжения на резисторах. Схема подключения, при которой плата питается от 12V в пин Vin и может измерять напряжение источника (например, аккумулятора):

Код для перевода значения с analogRead() в Вольты с учётом делителя напряжения:

// GND -- [ R2 ] -- A0 -- [ R1 ] -- VIN
#define VREF 5.1      // точное напряжение на пине 5V (в данном случае зависит от стабилизатора на плате Arduino)
#define DIV_R1 10000  // точное значение 10 кОм резистора
#define DIV_R2 4700   // точное значение 4.7 кОм резистора
void setup() {
  float voltage = (float)analogRead(0) * VREF * ((DIV_R1 + DIV_R2) / DIV_R2) / 1024;
}
void loop() {}

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

Согласно даташиту на ATmega, сумма R1 + R2 не рекомендуется больше 10 кОм для достижения наибольшей точности измерения. В то же время через делитель на 10 кОм будет течь ощутимый ток, что критично для автономных устройств (читай ниже). Если девайс работает от сети или от аккумулятора, но МК не используется в режиме сна - ставим делитель 10 кОм и не задумываемся. Также рекомендуется поставить конденсатор между GND и аналоговым пином для уменьшения помех.

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

Коэффициент делителя равен (R1 + R2) / R2. Коэффициент должен быть таким, чтобы при делении на него измеряемого напряжения не получилось больше напряжения питания МК. У меня в примере (10 + 4.7) / 4.7 ~ 3.13. Я хочу измерять литиевый аккумулятор с максимальным напряжением 12.8 Вольт. 12.8 / 3.13 ~ 4 Вольта - отлично. Например для измерения 36 Вольт я бы взял делитель с плечами 100к и 10к. Для снижения риска превышения можно на вход поставить стабилитрон 5,1В

Можно воспользоваться онлайн-калькулятором.

Как точнее измерить напряжение сильно меньше 5 Вольт

Для более точных измерений маленького напряжения можно подключить пин AREF к источнику низкого опорного напряжения (об этом было выше), чтобы "сузить" диапазон работы АЦП. Источник может быть как внешний, так и внутренний, например изменив опорное на внутреннее 1.1V ( analogReference(INTERNAL) ) можно измерять напряжение от 0 до 1.1 Вольта с точностью 1.1/1024 ~ 1.01 мВ.

analogReadResolution()

По умолчанию разрешение аналоговых входов в Ардуино - 10 бит. Функция analogReadResolution() позволяет увеличить до 12 бит разрешение, возвращаемое функцией analogRead(), но только для плат Zero, Due, MKR Family boards