Так как предыдущая моя публикация вызвала некоторый интерес, и параллельно возникли вопросы по реализации контроллера, я решил описать более подробно некоторые детали.
Принцип работы кода
Со времени публикации код претерпел некоторые изменения в лучшую сторону. По совету ino53 я добавил контроль расхода батарейки CR2032 с помощью измерения микроконтроллером напряжения питания. Также VICTOR_ZH совершенно справедливо указал на то, что опорное напряжение нужно установить внутреннее 1,1V вместо напряжения питания. Также здесь я более подробно опишу принцип измерения напряжения с датчика с помощью АЦП и принцип мигания светодиодом.
В начале кода добавились дополнительные константы:
#define SLEEP_PERIOD WDTO_8S
#define SKIP_WDT_WAKEUPS 4
#define SKIP_READVCC_WAKEUPS 4
#define THRESHOLD_VCC 2300
Первая (SLEEP_PERIOD WDTO_8S) - это, так называемая, built in (встроенная) константа для того, чтобы задать 8-секундный период срабатывания сторожевого таймера (watchdog). К сожалению у нас нет иной альтернативы для реализации функция засыпания/просыпания на сравнительно длительный период, потому что существуют или еще более мелкие интервалы сторожевого таймера (WDTO_4S, WDTO_2S, WDTO_1S, WDTO_500MS и т.д.) или просыпание по внешнему прерыванию. Внешнее прерывание может быть реализовано по нажатию кнопки или по срабатыванию какого-то датчика. Ни тем, ни другим мы не располагаем, а наш датчик измеряет непрерывно, поэтому он тоже не подходит. Поэтому используем 8-секундный таймер.
SKIP_WDT_WAKEUPS задает количество 8-секудных периодов засыпания, через которое контроллер опрашивает датчик на предмет его текущей емкости. Сейчас мы имеем период 4x8 = 32 секунды. Точно такой же период SKIP_READVCC_WAKEUPS задает количество 8-секудных периодов засыпания, через которое контроллер измеряет напряжение питания (VCC).
THRESHOLD_VCC задает пороговое значение напряжения питания VCC (мВ), при котором микроконтроллер attiny85 начинает мигать светодиодом сигнал SOS на морзянке, сигнализирующий о необходимости замены батарейки. Величина 2300 мВ была выбрана опытным путем - если еще сильнее снижать напряжение, то светодиод начинает светить слишком слабо, хотя микроконтроллер attiny85 продолжал работать и при 2100 мВ.
Проверка точности работы АЦП осуществлялась с помощью прецизионного источника питания ZK-J3X, подключенного к разъемам батарейки с помощью "крокодилов". ZK-J3X представляет собой понижающий преобразователь, на вход которого можно подключить источник питания от 5 до 27 В. Я использовал блок питания 12 В 1,5 А. В домашних условиях очень удобно использовать подобные преобразователи вместо громоздких блоков питания для проверки низковольтной электроники.
Далее мы задаем два счетчика для SKIP_WDT_WAKEUPS и SKIP_READVCC_WAKEUPS, которые мы будем изменять внутри вектора прерывания, срабатывающего по ватчдогу.
volatile uint8_t wakeup_counter_adc = 0;
volatile uint8_t wakeup_counter_readvcc = 0;
// Вектор прерывания таймера WDT
ISR(WDT_vect)
{
wakeup_counter_adc++;
wakeup_counter_readvcc++;
}
Порядок работы кода внутри цикла while(1) такой:
- Засыпаем с помощью функции goToSleep() на 8 секунд и просыпаемся
- В векторе ISR(WDT_vect) прибавляем единицу к счетчикам
- Проверяем условия wakeup_counter_adc > SKIP_WDT_WAKEUPS и wakeup_counter_readvcc > SKIP_READVCC_WAKEUPS
- Если прошло 32 секунды, то микроконтроллер совершает опрос датчика, мигает светодиодом 10 раз подряд раз в полсекунды/секунду или нет в зависимости от полученной емкости (значения АЦП)
- Если прошло 32 секунды, то микроконтроллер выполняет функцию readVcc(), измеряя напряжение питания и, в зависимости от полученного значения питания в милливольтах, подает светодиодом сигнал SOS или не подает
Функция goToSleep(), в принципе, подробно прокомментирована, остановлюсь только на одном моменте.
void goToSleep()
{
ADCSRA &= ~_BV(ADEN); // отключить ADC; уменьшает энергопотребление
wdt_enable(SLEEP_PERIOD); // установить таймер
WDTCR |= _BV(WDIE); // включить прерывания от таймера; фикс для ATtiny85
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // установить режим сна Power-down
sleep_enable(); // разрешить режим сна
sei(); // включить прерывания; иначе таймер не будет работать
sleep_cpu(); // заснуть
sleep_disable(); // запретить режим сна
ADCSRA |= _BV(ADEN); // включить ADC
wdt_disable(); // Выключить ватчдог
}
В прошлой реализации я забыл в конце выключить ватчдог (wdt_disable();), поэтому светодиод начинал моргать, но прекращал это делать ровно через 8 секунд.
Разберем функцию readVCC:
Для измерения напряжения питания приходится менять режим работы АЦП. В регистре ADMUX мы временно делаем опорным напряжение питания и переводим АЦП на измерение внутреннего напряжения 1,1V (микроконтроллер как бы производит сравнение). Для корректного измерения необходимо подождать более 1 секунды (так написано в мануале), произвести измерение и полученное значение АЦП конвертировать в милливольты с помощью формулы. В конце мы возвращаем опорное напряжение на внутреннее 1,1V и переводим измерение АЦП на ADC3.
Далее разберем процесс измерения с помощью АЦП:

В функции ADC_Read() мы проводим единственное измерение. Включаем его, ждем пока оно закончится и забираем значения из двух регистров ADCL и ADCH, складывая их побайтно. А функция ADC_Read_Avg(uint8_t nsamples) просто предоставляет интерфейс для произвольного количества последовательных измерений, значения которых суммируются и делятся на количество измерений. Делается это для увеличения точности.
Светодиодом мы мигаем, используя шаблон для установки высокого и низкого состояний, описанный в предыдущей публикации.
for(int i = 0; i < 10; i++)
{
SET_HIGH(PORTB, PB4);
_delay_ms(1000);
SET_LOW(PORTB, PB4);
_delay_ms(1000);
}
Здесь мы просто в цикле последовательно зажигаем и гасим светодиод, подавая напряжение на вывод PB4.
Настройки avrdudej-0.1
Как я уже упоминал ранее, для заливки кода в attiny85 я использую свободную программу avrdudej-0.1. Как это ни странно, она имеет минимальный необходимый набор функций и является кроссплатформенной - я использую ее и под Windows, и под Linux. Для обеспечения ее работоспособности достаточно:
- установить JRE или OpenJDK
- (под Linux в свойствах файла отметить галочку "является выполняемым")
- прописать пути к arduino IDE, avrdude и avrdude.conf в "Edit -> Preferences". На скриншоте видно, что avrdude в arduino IDE находится в скрытой папке ".arduino15".
Размер кода
Лично для меня оказалось очень странным, когда я выяснил, что размера кода, загружаемый в микроконтроллер, сильно отличается от размера формируемого файла .hex. Существует мнемоническое правило: фактический размер загружаемого кода в три раза меньше, чем размер файла. Фактический размер кода можно посмотреть в avrdudej-0.1 непосредственно при заливке или с помощью утилиты avr-size, которая поставляется вместе с компилятором. На скриншоте линуксового терминала видно, что размер soil-avr.hexz, загружаемый во flash-память микроконтроллера, составляет 784 байта. Размер файла на накопителе компьютера около 2,2 кБ.

Мигрируем на attiny13a
В данном случае мы выяснили, что объем кода для attiny85 оказался меньше 1кБ, а значит можно использовать микроконтроллер с гораздо меньшей памятью и гораздо более дешевый. Например, attiny25, attiny202 или даже attiny13a. На последнем остановимся подробнее. Дело в том, что attiny13a является очень дешевым микроконтроллером, производство клонов которого давно наладили китайцы. В принципе, нам ничего особо не мешает внести минимальные изменения в печатную плату и код, и использовать attiny13a.
В плату мы вносим два минимальных изменения: немного отодвигаем разъем для крепления приставки, учитывая опыт предыдущей конструкции, а также меняем широкое посадочное место для attiny85 (SOIC8W) на обычный SOIC8 (150mil) - в этом корпусе поставляются attiny202, attiny204 и attiny13a.
Какие изменения нужно внести в код? Прежде всего это настройки АЦП:

Регистры attiny13a отличаются от attiny85, поэтому настройки будут немного отличаться. Более подробно можно посмотреть в разделе 14.12 Register Description.
Основной проблемой является отсутствие в attiny13a прерываний по ватчдогу ISR(WDT_vect), поэтому придется пойти обходным путем. Например, как это реализовано здесь.

Отличия в Code::Blocks для Windows и для Linux
В предыдущей статье я писал, что для программирования микроконтроллеров AVR я использую свободную IDE Code::Blocks. В ходе работы я столкнулся с небольшой проблемой при переносе кода с Windows на Linux и обратно. Выяснилось, что код проекта идентичен для двух платформ за исключением шагов, которые выполняются на этапе компиляции - создаются разные файлы. Например, под Windows есть команда, создающая файл .lss. Но под Linux нет команды cmd, поэтому компиляция останавливается.
cmd /c "avr-objdump -h -S $(TARGET_OUTPUT_FILE) > $(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).lss"
Поэтому, чтобы проект заработал в иной экосистеме, нужно просто заменить команды в Build options... pre/post build steps на правильные.
Файл с командами для Windows и Linux лежит в расшаренной папке. Там же лежит проект платы для attiny13a и исправленные проекты Code:Blocks для attiny85 для Windows и Linux. Проекту под Windows требуется пересборка. Актуальный hex лежит в Linux/soil_avr/bin/Release. Проекта Code:Blocks для attiny13a пока нет, но он обязательно будет.
Ссылка на проекты
В одной из следующих статей я опишу как работать с новыми микроконтроллерами attiny202, attiny402, attiny424 и как сделать простой UPDI-программатор для них. В условиях новых цен на микроконтроллеры это очень актуально.