Чердак, ноябрь, узел климата на Uno с кроной в батарейном отсеке. Скетч писал честный: раз в минуту читал датчик, остальное время тикал delay(60000) — вроде бы спит. Крона села за четыре дня. Токометр в разрыве показал сорок миллиамперов и во время пауз: delay не выключает ни одного транзистора, чип честно жжёт полную мощность, просто ничего не делает. Тогда я впервые полез в режимы сна всерьёз, а не «включил и работает».
Вводную я уже получал в уроке про таймеры, watchdog и сон: там был обзор power-down и расчёт батарейного узла. Но между «обзором» и рабочим узлом лежат три вопроса, на которых я обжигался: что именно продолжает жить в каждом режиме, кто и как будит чип и — самое обидное — почему сон чипа не спасает плату в сборе.
Здесь раскладываю оба рабочих режима на детали: idle — спит только ядро, таймеры тикают и millis() живёт; power-down — остановлено всё, кроме пары источников пробуждения. Сравниваем токи с цифрами, а не с ощущениями, собираем скетч сна с будильником по INT0 и отдельно считаем, сколько на самом деле съест Uno, Pro Mini и голый чип.
Два режима, которые нужны на деле
У ATmega328P шесть режимов сна, но в проектах выжили два. Idle — лёгкий: тактовый генератор продолжает тикать, периферия работает, засыпает только ядро. Просыпается по любому прерыванию — хоть по вашему кнопочному INT0, хоть по тику таймера Timer0, который обслуживает millis(). Для нас это значит: в idle счёт миллисекунд идёт, всё запланированное на millis-планировщик срабатывает по расписанию, а чип между делами отдыхает. Классическая цена: по замерам Ник Гэммона голый чип при 16 МГц и 5 В берёт около пятнадцати миллиампер в работе и около четырёх с половиной — в idle. Втрое меньше, но отнюдь не микроамперы.
Power-down — глухой: остановлены генератор, ядро и почти вся периферия. Будить могут только внешние прерывания INT0/INT1 на D2/D3, прерывание по смене пина PCINT, совпадение адреса на I2C, Reset и watchdog. Даташит обещает десятые доли микроампера на 3 В без сторожа и стабилизатора защиты от провалов питания; со включённым watchdog выходит порядка пяти-семи микроампер — сторож ест большую часть остатка. Разница с idle в тысячи раз, и расплатой выступает функциональность: millis() замер, Serial молчит, любой скетчевый таймер не тикает.
Ещё одна цена, о которой редко пишут: пробуждение не мгновенно. Из idle чип выходит за несколько тактов — по сути мгновенно. Из power-down кварцу нужно раскрутиться заново: даташит отводит на стабилизацию такта миллисекунды, и только потом исполняется ISR. Для пробуждений раз в минуту это неважно, для реакции на кнопку — ощутимо; если счёт времени нужен сразу, это аргумент в пользу idle или гибрида, где редкий будильник даёт сторож, а лёгкая логика дремлет в idle между тиками.
Отсюда главное инженерное решение, которое стоит принять до кода: что должно происходить во сне? Если узлу надо продолжать считать время и мигать по расписанию — idle. Если узел обязан жить на батарейке месяцами и просыпается только по событию — power-down, а расписание переезжает на watchdog или внешний RTC. Мой метеоузел в итоге пошёл вторым путём, и об этом дальше.
#include <avr/sleep.h> const int PIN_BTN = 2; // INT0: D2 на землю через кнопкуconst int PIN_LED = 12;volatile byte probudilis = 0; // флаг из ISR: volatile обязателен void izINT0() { // короткий ISR: только флаг probudilis = 1;} void setup() { pinMode(PIN_LED, OUTPUT); pinMode(PIN_BTN, INPUT_PULLUP); Serial.begin(9600); Serial.println("uzel zasypaet; knopka na D2 budit"); Serial.flush(); // дожать буфер UART до сна attachInterrupt(digitalPinToInterrupt(2), izINT0, FALLING);} void son() { set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_mode(); // <- чип стоит здесь, пока не прилетит INT0 sleep_disable(); // сюда попадаем после пробуждения} void loop() { if (probudilis) { probudilis = 0; for (byte i = 0; i < 3; i++) { // деловая часть: мигнули и отчитались digitalWrite(PIN_LED, HIGH); delay(80); digitalWrite(PIN_LED, LOW); delay(80); } Serial.println("rabota sdelana, synu dalshe"); Serial.flush(); // без flush хвост строки уедет во сне } son();}Разбор по строкам. set_sleep_mode() просто выбирает режим в регистре, sleep_enable() разрешает засыпание, а реальная остановка происходит в sleep_mode() — команда SLEEP исполняется, ядро замирает на следующей строке. Проснувшись по прерыванию, чип сначала исполняет ISR, потом продолжает со строки после sleep_mode() — поэтому sleep_disable() стоит сразу за ним. Весь шаблон «выбрать режим — разрешить — уснуть — отработать — запретить» ужимается в пять строк, и менять режимы экспериментами можно одной константой: SLEEP_MODE_IDLE вместо SLEEP_MODE_PWR_DOWN.
ISR обязателен, даже если внутри ничего полезного: именно прерывание — механика пробуждения. И флаг в нём помечен volatile, иначе главный цикл не увидит изменения из обработчика — механику этого правила я разбирал в уроке про volatile и краткость обработчика. В ISR не должно быть ни Serial, ни delay: короткий флаг, вся деловая часть — в loop.
Кнопка на D2 — не единственный будильник. Если событие может прийти на любой пин, а не только на D2/D3, спасает прерывание по смене пина: в уроке про PCINT любой ножки чипа я подключал целую гребёнку входов — и она же будит чип из power-down. А если лень возиться с регистрами сна руками, библиотека LowPower делает те же пять строк за вас: цикл «сон восемь секунд по кругу» в ней разобран в отдельном уроке.
| Состояние | Что продолжает жить | Кто будит | Ток при 5 В, 16 МГц |
|---|---|---|---|
| Активный код | всё | — | около 15 мА голый чип, около 45 мА плата Uno |
| SLEEP_MODE_IDLE | генератор, таймеры, UART, ADC; millis() тикает | любое прерывание, включая таймерные тики | около 4,6 мА голый чип; Uno почти не экономит |
| Power-down + watchdog | только сторож и входы пробуждения | WDT каждые 8 с, INT0/INT1, PCINT, Reset | порядка 5-7 мкА голый чип |
| Power-down без лишнего | только входы пробуждения | INT0/INT1, PCINT, Reset | десятые доли мкА на 3 В по даташиту |
Чип уснул, а плата — нет
Самое дорогое заблуждение новичка в энергосбережении: «заложу сон в скетч — и Uno станет батарейной». На форуме Arduino человек честно замерял: его Uno в работе брала около шестидесяти пяти миллиампер, после перевода в power-down — пятьдесят пять. Минус десять. Весь остальной ток едят не чип: светодиод питания, USB-приёмопередатчик ATmega16U2, линейный стабилизатор с его собственным потреблением. Для платы в сборе это не мелочь, а основная статья расходов.
Итог по железу простой и честный. Uno в батарейном проекте годится, только если рядом большая батарея и сон не нужен. Настоящий батарейный узел — голый чип на макетке, Pro Mini со снятым светодиодом питания и стабилизатором либо отдельная понижающая сборка. На Pro Mini по замерам той же школы экономии уходит от полутора-двух миллиампер в активном режиме на 16 МГц до микроампер во сне после удаления LED и LDO. Как такой узел выглядит целиком, я показывал в проекте метеодатчика на батарейке — там же считается, на сколько месяцев хватает пары АА.
Замерять надо самому, и это несложно: мультиметр в режим миллиампер, плюс питания в разрыв, красный щуп к плюсу батареи, чёрный к пину VIN. Для микроампер переключайтесь в соответствующий диапазон и берите в расчёт, что подключённый по USB USBasp-программатор или монитор порта добавит свой ток — мерьте окончательную сборку.
Что докрутить
Три задания. Первое: переведите скетч из урока в SLEEP_MODE_IDLE и убедитесь, что led и Serial продолжают жить, а ток по мультиметру упал втрое-вчетверо — так вы прочувствуете разницу режимов руками, а не по таблице. Второе: добавьте ко сну отключение ADC строкой ADCSRA &= ~_BV(ADEN) перед засыпанием и включение после пробуждения — на power-down эффект почти незаметен, а вот на idle и активном коде экономит около четверти миллиампера.
Третье, расчётное: посчитайте средний ток узла по формуле из урока про таймеры и сон для своего сценария пробуждений — допустим, десять деловых секунд в час — и сравните с показаниями симулятора-модели из этого урока. Когда расчёт и замер сойдутся с точностью до порядка, батарейные проекты перестанут быть лотереей.
- 1. Что продолжает работать в SLEEP_MODE_IDLE?
- 2. Чем можно разбудить чип из power-down?
- 3. Почему после перевода Uno в power-down ток платы падает лишь до десятков миллиампер?
- 4. Зачем перед сном вызывать Serial.flush()?
- 5. Сколько примерно берёт голый ATmega328P в power-down со включённым watchdog?
- Idle — сон ядра: таймеры тикают, millis() живёт, будит любое прерывание; ток голого чипа падает с ~15 мА до ~4,6 мА при 16 МГц и 5 В
- Power-down — глухой сон: генератор стоит, будят только INT0/INT1, PCINT, watchdog и Reset; ток 5-7 мкА со сторожем и доли мкА без него
- Шаблон сна — пять строк из avr/sleep.h: set_sleep_mode, sleep_enable, sleep_mode, sleep_disable; режим меняется одной константой
- ISR нужен даже пустой — это механика пробуждения; флаг в нём volatile, деловая часть живёт в loop
- Сон чипа не экономит плату: Uno во сне ест десятки мА из-за LED, USB-чипа и стабилизатора; батарейный узел — голый чип или Pro Mini без LED и LDO
Частые вопросы по теме урока
- Что продолжает работать в SLEEP_MODE_IDLE?
- Idle останавливает только ядро. Генератор, таймеры, UART, ADC живут, поэтому millis() продолжает считать, а чип просыпается от любого прерывания, включая тики таймера. тактовый генератор и периферия: таймеры тикают, millis() идёт
- Чем можно разбудить чип из power-down?
- Power-down глушит почти всё; источники пробуждения — внешние прерывания на D2/D3, прерывание по смене пина, совпадение адреса I2C, watchdog и Reset. Обычные таймерные тики не в счёт. INT0/INT1, сменой пина PCINT, watchdog или Reset
- Почему после перевода Uno в power-down ток платы падает лишь до десятков миллиампер?
- На форумах мерили: 65 мА в работе против 55 мА во сне. Сам чип в power-down берёт микроамперы — остальное съедает обвязка платы, которая скетчем не выключается. основной ток едят светодиод питания, USB-чип и стабилизатор платы
- Зачем перед сном вызывать Serial.flush()?
- Serial.print кладёт байты в буфер, передача идёт асинхронно. Power-down глушит UART, и недопечатанные байты теряются. flush() возвращает управление после отправки последнего байта. чтобы дождаться физической отправки буфера UART, иначе хвост строки умрёт во сне
- Сколько примерно берёт голый ATmega328P в power-down со включённым watchdog?
- Даташитные десятые доли мкА относятся к чистому power-down без сторожа и детектора провалов. Watchdog со своим генератором добавляет единицы микроампер — выходит пять-семь мкА. порядка 5-7 мкА