Началось все с того, что при проектировании своего устройства на микроконтроллере ATtiny 85, которое должно было работать от встроенного li‑ion аккумулятора, я изначально не задавался целью измерения заряда АКБ, поскольку в этом не было необходимости. Однако, собрав все устройство на печатной плате, я подумал над тем, почему бы не добавить такую возможность.

Прочитав в Интернете, как это можно было реализовать, стало ясно, что сделать это вряд ли удастся, поскольку все порты PB[0:5] уже были заняты и, следовательно, не было возможности применения АЦП с аналогового пина (при чем порт PB0 я не мог настроить на вход опорного напряжения AREF - он должен был использоваться как управляющий выход).

Распиновка микроконтроллера

Долгие поиски на форумах Интернета и попытки решить эту проблему программно не прошли зря: решение проблемы определения заряда АКБ, когда нет свободных портов для АЦП (то есть когда нет свободных аналоговых пинов для измерения), оказалось довольно простым и хитрым.

Решение проблемы

Долгое изучение состояния регистров АЦП в datasheet на ATTiny 85 привело меня к следующей идее: в качестве опорного напряжения может быть выбрано само напряжение питания VCC (биты REFS [0:2] регистра ADMUX установлены в 0), а в качестве измеряемого ‑ напряжение VBG с внутреннего стабилизатора в 1.1В (биты MUX [3:0] регистра ADMUX установлены соответственно в 1100). То есть, для измерения напряжения питания не нужно ничего, кроме, собственно, самого питания VCC!

Регистр ADMUX для настройки АЦП
биты REFS [0:2] регистра ADMUX
биты MUX [3:0] регистра ADMUX

В итоге, измерив известное нам напряжение внутреннего опорного источника в 1.1В (Vin) относительно неизвестного нам опорного напряжения питания VCC (Vref), можно пересчитать формулу для измерения напряжения.

Исходная формула для значения АЦП, взятая из datasheet
Пересчитанная формула

Реализация в программе

#define INTERNAL_REF 1112.0  // Опорное напряжение VBG [мВ], известное заранее
int main() {
  uint32_t proton;           // Измеренное напряжение [мВ]
  ADMUX = (1 << MUX3) | (1 << MUX2);                                  // Выбран канал внутреннего входного напряжения на 1.1В (VBG)
  ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);  // Разрешаем АЦП и устанавливаем коэффициент деления предделителя = 128 (высокая точность)
  while (true) {
    ADCSRA |= (1 << ADSC);            // Запуск АЦП
    while (ADCSRA & (1 << ADSC)) {};  // Ждем окончания
    proton = (INTERNAL_REF * 1024.0) / ADC;  // Напряжение [мВ]
  }
}

Для калибровки встроенного опорного напряжения в 1.1В (задается в первой строке кода) необходимо подать стабильное и заранее известное напряжение на микроконтроллер (например, 3В с блока питания) и вывести (например, на дисплей) переменную "proton". Варьируя "INTERNAL_REF", необходимо добиться соответствия измеренного напряжения с истинным.