Началось все с того, что при проектировании своего устройства на микроконтроллере 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!

![биты REFS [0:2] регистра ADMUX биты REFS [0:2] регистра ADMUX](https://habrastorage.org/r/w1560/getpro/habr/upload_files/4b8/cca/92c/4b8cca92c8c80bc611ed7e388e3a4870.png)
![биты MUX [3:0] регистра ADMUX биты MUX [3:0] регистра ADMUX](https://habrastorage.org/r/w1560/getpro/habr/upload_files/824/df0/14e/824df014ec29c2cfd289b7dc7ed29c16.png)
В итоге, измерив известное нам напряжение внутреннего опорного источника в 1.1В (Vin) относительно неизвестного нам опорного напряжения питания VCC (Vref), можно пересчитать формулу для измерения напряжения.


Реализация в программе
#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", необходимо добиться соответствия измеренного напряжения с истинным.