Продолжаем тему минимизации энергопотребления Ардуино и теперь пора разобраться в режимах работы МК. Все AVR микроконтроллеры поддерживают различные режимы энергосбережения. Их описание можно найти в документации к МК в разделе Power Management and Sleep Modes. ATmega328P имеет 6 режимов:
- Idle Mode (режим ожидания). В нем процессор приостанавливается, но остальная периферия - SPI, USART, аналоговый компаратор, шина TWI (I2C), таймеры/счетчики, сторожевой таймер и система прерываний - продолжают работать. Поэтому выход из режима Idle возможен как по внешнему, так и по внутреннему прерыванию. Основным преимуществом данного режима является быстрая реакция на события, приводящие к пробуждению МК. Другими словами, выполнение программы начинается сразу же после перехода из режима Idle в рабочий режим.
- ADC Noise Reduction Mode (режим снижения шумов АЦП). Данный режим присутствует только в моделях МК, содержащих в своем составе АЦП и предназначен для уменьшения всевозможных наводок во время его работы. В этом режиме прекращает работу процессор и отключается синхронизация ввода-вывода, остальные устройства продолжают работу. Вывести МК из этого режима, кроме прерывания по завершению преобразования АЦП, могут:
- прерывание от сторожевого таймера;
- прерывание по совпадению адреса от интерфейса TWI;
- прерывание от асинхронного таймера/счетчика 2;
- прерывание по готовности EEPROM;
- прерывание при изменения уровня (pin change interrupt)
- внешние прерывания INT0 и INT1;
- а так же сбросы (аппаратного, от сторожевого таймера, от схемы BOD).
- Power-Down mode (режим микропотребления) - самый экономный режим, присутствует во всех AVR. В этом режиме отключаются все внутренние тактовые сигналы, соответственно, прекращается функционирование всех узлов МК, работающих в синхронном режиме. Единственными узлами, продолжающими работать в этом режиме, являются асинхронные модули МК: сторожевой таймер (если он включен), подсистема обработки внешних прерываний и блок сравнения адреса модуля TWI. Пробуждение из режима Power-Down возможно при возникновении сброса (аппаратного, от сторожевого таймера, от схемы BOD) или в результате генерации следующих прерываний:
- прерывание от сторожевого таймера;
- прерывание по совпадению адреса от интерфейса TWI;
- прерывание изменения уровня (pin change interrupt)
- внешние прерывания INT0 и INT1.
- Power Save Mode (экономичный режим). Идентичен режиму Power Down с одним отличием: в режиме Power Save таймер/счетчик 2 может продолжать работу, причем как в синхронном, так и асинхронном режиме. Вывести МК из этого режима могут те же события, что и в случае использования режима Power Down, а так же прерывания от таймера/счетчика 2.
- Standby Mode (режим ожидания). Этот режим рекомендуется задействовать только при использовании внешнего резонатора. Он идентичен режиму Power Down, за исключением того, что тактовый генератор продолжает функционировать. Благодаря этому пробуждение МК и переход в рабочий режим требует всего 6 тактов.
- Extended Standby Mode (расширенный режим ожидания). Как и режим Standby этот режим рекомендуется использовать совместно с внешним резонатором. Он идентичен режиму Power Save, за исключением того, что тактовый генератор продолжает функционировать. Благодаря этому пробуждение МК и переход в рабочий режим требует всего 6 тактов.
Для того чтобы перевести Ардуино в один из перечисленных режимов достаточно выполнить 2 функции: set_sleep_mode(<mode>) для задания конкретного режима и sleep_mode(). Они объявлены в файле sleep.h, поэтому он должен быть добавлен в секцию include вашего скетча.
#include <avr/sleep.h>
...
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_mode();
Для указания режима используются константы:
- SLEEP_MODE_IDLE
- SLEEP_MODE_ADC
- SLEEP_MODE_PWR_DOWN
- SLEEP_MODE_PWR_SAVE
- SLEEP_MODE_STANDBY
- SLEEP_MODE_EXT_STANDBY
Пробуждение по сторожевому таймеру
В основе сторожевого таймера (WatchDog Timer) лежит многоразрядный счетчик, снабженный собственным тактовым генератором. Если таймер включен, то значение счетчика будет постоянно увеличиваться и при его переполнении будет сгенерирован сигнал сброса МК. Чтобы избежать сброса по переполнению программа должна постоянно обнулять счетчик специальной командой. Если программа зависла и счетчик не был вовремя сброшен, то сигнал сброса выведет МК из зависшего состояния, таким образом повышается стабильность системы на основе МК.Наличие собственного тактового генератора и способность работать когда другие узлы МК остановлены позволяют использовать сторожевой таймер для вывода МК из спящего режима. Для этого следует настроить его на генерацию прерывания, а не сигнала сброса. Также необходимо задать значение предделителя, чтобы получить интересующую задержку. Ниже приведен пример скетча, в котором Ардуино будет просыпаться каждые 2 секунды и мигать встроенным светодиодом для индикации пробуждения.
#include <avr/wdt.h>
#include <avr/sleep.h>
volatile bool f = 0;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
wdt_enable(WDTO_2S); //Задаем интервал сторожевого таймера (2с)
WDTCSR |= (1 << WDIE); //Устанавливаем бит WDIE регистра WDTCSR для разрешения прерываний от сторожевого таймера
set_sleep_mode(SLEEP_MODE_PWR_DOWN); //Устанавливаем интересующий нас режим
sleep_mode(); // Переводим МК в спящий режим
digitalWrite(LED_BUILTIN, f);
}
ISR (WDT_vect) {
wdt_disable();
f = !f;
}
Здесь задается интервал сторожевого таймера и режим его работы для генерации прерываний через 2 секунды. После выполнения функции sleep_mode() Ардуино переходит в спящий режим. При генерации прерывания от сторожевого таймера и пробуждении МК, управление передается соответствующему обработчику (ISR (WDT_vect)). После его выполнения продолжится работа основной программы, т.е. выполнится следующая после sleep_mode() команда. Для задания значения предделителя сторожевого таймера можно использовать одну из следующих констант:
- WDTO_15MS
- WDTO_30MS
- WDTO_60MS
- WDTO_120MS
- WDTO_250MS
- WDTO_500MS
- WDTO_1S
- WDTO_2S
- WDTO_4S
- WDTO_8S
Для получения более длительных интервалов можно использовать цикл и переводить МК в спящий режим внутри цикла. Но лучше использовать для этого часы реального времени с будильником.
Пробуждение при нажатии кнопки
Пробуждение микроконтроллера при замыкании/размыкании контактов, будь то кнопка, геркон, энкодер и т.п. - это пробуждение по внешнему прерыванию (INT) или по прерыванию изменения уровня (PCINT). Для работы с первыми в IDE Arduino предусмотрены удобные функции. Для вторых ничего подобного в IDE нет, поэтому придется работать с регистрами микроконтроллера, отвечающими за настройку PCINT (хотя, наверняка, существуют библиотеки для работы с ними, я не искал). Для тех и других у меня есть подробные публикации с примерами использования: Прерывания в Ардуино. Часть 1, и Часть 2.Предположим, что микроконтроллер в нашем устройстве должен всегда спать, просыпаться при нажатии кнопки, подключенной к входу запроса прерывания INT0, что-то делать и снова засыпать. Вход запроса прерывания (для INT0 - это вывод D2 Ардуино) должен быть подтянут к питанию или земле. Публикация про подтяжку в тему. Мы воспользуемся внутренними подтягивающими резисторами микроконтроллера. Кнопку установим между выводами D2 и Gnd. Простой скетч для ухода в сон и пробуждения при нажатии может быть таким:
#include <avr/sleep.h>
#define INTERRUPT_PIN 2
void setup() {
Serial.begin(9600);
pinMode(INTERRUPT_PIN, INPUT_PULLUP); // Задействуем подтягивающий резистор
}
void loop() {
byte b = 0;
// Просыпаться будем при при изменении сигнала на входе INTERRUPT_PIN от HIGH к LOW
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), myISR, FALLING);
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // PowerDown - самый экономный режим
sleep_mode(); // Переводим МК в сон
// Проснулись, запрещаем обработку внешнего прерывания
detachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN));
// А здесь добавляем свой код, например, вывод в Serial
Serial.println("Wake up!");
// Перед тем как снова уйти в сон дождемся стабильного HIGH на входе INTERRUPT_PIN
while (b < 5) { // Стабильным будем считать сигнал, который не изменится за 5 выборок
if (digitalRead(INTERRUPT_PIN) == HIGH)
b++; // На входе HIGH, увеличиваем счетчик
else
b = 0; // На входе LOW, обнуляем счетчик
delay(1); // Следующая выборка через 1мс
}
// Дождались HIGH, можем засыпать
}
void myISR() {
}
Скетч содержит достаточно комментариев, я поясню лишь назначение цикла while. Все электромеханические коммутирующие устройства подвержены дребезгу контактов. В нашем случае дребезг на входе запроса прерывания будет воспринят микроконтроллером как многократное нажатие кнопки, в результате чего он будет выходить из спящего режима, выполнять заданный код и уходить в сон несколько раз подряд. Чтобы этого не происходило мы откладываем уход в сон до момента отпускания кнопки и возвращения сигнала на входе D2 к высокому уровню. Если сигнал остается высоким в течение 5 выборок с интервалом в 1мс, то считаем, что он стабилизировался и можно снова переводить микроконтроллер в режим энергосбережения.
Если добавить в схему аппаратное подавление дребезга, то можно обойтись без описанного цикла.
Пробуждение при нажатии кнопки, уход в сон по таймауту
Другой пример: есть устройство с дисплеем, которое должно просыпаться при нажатии кнопки, а засыпать по истечении некоторого времени, чтобы пользователь успел увидеть всё необходимое на дисплее. В простейшем случае хватило бы задержки на несколько секунд перед уходом в сон. Но если предполагается дополнительное взаимодействие с пользователем (опрос элементов управления, изменение выводимой информации и т.д.), то в этом случае поможет следующий подход:#include <avr/sleep.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define INTERRUPT_PIN 2 // Для Arduino UNO можно использовать 2 или 3 пин
#define TIME_BEFORE_SLEEP 10000 // Время до ухода в сон 10с
//LiquidCrystal_I2C lcd(0x27, 16, 2);
LiquidCrystal_I2C lcd(0x27, 20, 4);
unsigned long tm = 0;
bool buttonPrev = 0;
bool f = 0;
void setup() {
lcd.begin();
lcd.print("Time: 12:00");
pinMode(INTERRUPT_PIN, INPUT_PULLUP); // Задействуем подтягивающий резистор
}
void loop() {
// Проверим не пора ли спать
if (millis() - tm > TIME_BEFORE_SLEEP) { // Пора
lcd.noBacklight(); // Выключаем подсветку дисплея
//Разрешаем внешнее прерывание, которое разбудит МК
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), myISR, FALLING);
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // PowerDown - самый экономный режим
sleep_mode(); // Переводим МК в сон
// Проснулись, запрещаем обработку внешнего прерывания
detachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN));
tm = millis();
lcd.backlight(); // Включаем подсветку дисплея
buttonPrev = 0;
}
// А здесь основной код: опрос кнопок, вывод информации и т.п.
if (isButtonPressed(INTERRUPT_PIN)) {
f = !f;
lcd.clear();
if (f) lcd.print("Temp: 24C");
else lcd.print("Time: 12:00");
tm = millis();
}
delay(100);
}
void myISR() {
}
bool isButtonPressed(uint8_t pin) {
if (digitalRead(pin) == HIGH) {
buttonPrev = 1;
}
else if (buttonPrev) {
// Нажата кнопка
buttonPrev = 0;
return true;
}
return false;
}
Здесь уход микроконтроллера в сон возможен только при выполнении условия: разница между текущим значением millis() и значением переменной tm должно быть больше 10 секунд. Для того чтобы отложить сон, достаточно записать в tm значение millis(), что и делается при пробуждении и при нажатии на кнопку.
Также в данном примере отключается подсветка дисплея (текстовый дисплей типа 20*4 или 16*2) при переходе микроконтроллера в режим энергосбережения и включается при выходе из него. Сделано это в большей степени для демонстрации. В реальном же устройстве лучше отключать питание дисплея, поскольку он не имеет режима энергосбережения, а отключение подсветки уменьшит ток недостаточно - экономия должна быть экономной.
Для минимизации энергопотребления необходимо активнее использовать энергосберегающие режимы, причем выбирать режим следует так, чтобы в нем функционировало минимально необходимое количество узлов микроконтроллера. Также следует помнить о модулях МК, работа которых может продолжаться в спящих режимах. Если эти модули не используются в программе, то их работу следует запретить. К таким модулям относятся:
- Аналого-цифровой преобразователь;
- Аналоговый компаратор;
- Детектор пониженного напряжения питания (Brown-Out Detector);
- Внутренний источник опорного напряжения;
- Сторожевой таймер;
- Порты ввода-вывода;
- Модуль внутрисхемной отладки.
Итак, продолжим эксперименты по снижению энергопотребления Ардуино. В предыдущей статье мы уже научились управлять тактовой частотой и сократили потребляемый Arduino Pro Mini ток до 1,24мА при напряжении питания 3.3В и частоте 1МГц. Попробуем теперь перевести микроконтроллер в спящий режим Power Down и отключить ненужные узлы.
#include <avr/sleep.h>
void setup() {
ADCSRA &= ~(1 << ADEN); // Отключаем АЦП
set_sleep_mode(SLEEP_MODE_PWR_DOWN); //Устанавливаем интересующий нас режим
cli(); // Временно запрещаем обработку прерываний
sleep_enable();
// Отключаем детектор пониженного напряжения питания
MCUCR != (1 << BODS) | (1 << BODSE);
MCUCR &= ~(1 << BODSE);
sei(); // Разрешаем обработку прерываний
sleep_cpu(); // Переводим МК в спящий режим
sleep_disable();
}
void loop() {
}
В режиме Power Down продолжают работать АЦП и схема контроля питания. В приведенном скетче есть команды для их отключения. АЦП при необходимости потом можно будет включить установкой бита ADEN. Потребляемый Arduino Pro Mini ток с данным скетчем составил 136мкА.
Я считаю, это неплохой результат по сравнению с начальными 15мА.
Чтобы уменьшить потребление еще больше, придется отказаться от использования плат Ардуино и перейти на использование "голых" микроконтроллеров. Оцените, например, потребление ATmega328P при тактировании от внутреннего RC генератора в этой статье.
Советую также посмотреть следующую мою статью об управлении режимами энергосбережения Ардуино - Библиотека Low-Power.
У меня на нано почему то не работает функция снижения частоты процессора (ругается при прошивке), но сбив её диоды и с помощью фунции (WatchDog Timer) я добился энергопотребления 1.7 mA. На блоке с восьми батареек спаянного по четыре в параллели, даже с периодическим выходом из сна для опроса датчиков, она должна проработать около 2 месяцев.
ОтветитьУдалитьСтранно, что ругается при прошивке. У меня с этим проблем не было.
УдалитьМне на днях пришли суперконденсаторы (2.7В, 4Ф) с Али, хочу попробовать запитать от них Ардуино, а для подзарядки использовать солнечную батарейку. Для периодического пробуждения и опроса датчиков этого должно хватить. Если получится что интересное, то отпишусь о результатах.
ADCSRA &= ~(1 << ADEN); // Отключаем АЦП
ОтветитьУдалить// Отключаем детектор пониженного напряжения питания
MCUCR = bit (BODS) | bit (BODSE);
MCUCR = bit (BODS);
Как включить обратно?). Если можно толковую книгу посоветуйте где такие тонкости описывают. За ранее спасибо!
Добрый день!
УдалитьМогу посоветовать книгу "Микроконтроллеры AVR семейств Tiny и Mega фирмы Atmel", плюс читайте datasheet.
Для разрешения работы АЦП необходимо записать логическую единицу в разряд ADEN регистра управления модулем АЦП (ADCSR), а для выключения - логический ноль. Таким образом:
ADCSRA &= ~(1 << ADEN); // Отключаем АЦП
ADCSRA |= 1 << ADEN; // Включаем АЦП
- это обычная логика: ADEN - седьмой бит регистра (т.е. ADEN = 7), выражение 1 << ADEN выполняет сдвиг единицы влево на 7, результат операции 10000000 - маска для изменения значения бита ADEN. Дальше мы либо сбрасываем, либо устанавливаем этот бит.
С выключением схемы BOD чуть посложнее. За ее работу отвечает бит BODS регистра управления микроконтроллером MCUCR. Чтобы запретить работу схемы при переходе в спящий режим нужно записать в этот бит логическую единицу. Запись в BODS разрешается битом BODSE, поэтому для выключения схемы BOD нужно записать в оба этих бита логическую единицу, затем в течение 4 тактов записать в них 1 и 0 соответственно. Отключать BOD следует непосредственно перед переходом в спящий режим, пока бит BODS установлен, потому что по истечении 3 тактов он будет автоматически сброшен. Соответственно, включать его потом специально не нужно. Это все можно найти в даташите на ATmega328 в разделе 14.12.2 MCU Control Register.
В скетче для отключения BOD я использовал функцию bit(n) - она эквивалентна сдвигу 1 << n.
Спасибо!
УдалитьСпасибо за информацию, но для себя я сделал так - пишу фонарик:)))
ОтветитьУдалить// КОМПИЛИРОВАТЬ В 1.6.5!!!!!!!!
#include
bool f = 0;
byte keyPin = 2; // задаем вывод для кнопки кнопки (ардуиновский)
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(keyPin, INPUT_PULLUP); // подтягиваем вывод кнопки к питанию (кнопка на землю)
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Устанавливаем интересующий нас режим
attachInterrupt(digitalPinToInterrupt(keyPin), myISR, LOW); // Прерывание по низкому уровню
}
void loop() {
sleep_mode(); // Переводим МК в спящий режим, проснется когда на keyPin будет 0
}
void myISR() { //выполняется каждый раз, при нажатии кнопки
while (!digitalRead(keyPin)) delay(20); // антидребезг
f = !f;
digitalWrite(LED_BUILTIN, f); // Далее выполняем интересующие нас действия
}
Владимир, можно узнать вашу почту, есть несколько вопросов, если можно?
ОтветитьУдалитьVladimirTsibrov@yandex.ru
Удалитьспасибо. Теперь ардуино не распознаётся!!!
ОтветитьУдалитьВы, наверное, удивитесь, но код, записанный в микроконтроллер, не влияет на распознавание Ардуино. И у других все работает. Так что разбирайтесь, что вы там напортачили.
Удалитьпрекрасная статья! подскажите, почему в одном случае вы используете sleep_mode(), а в другом slep_enable() и sleep_cpu()?
ОтветитьУдалитьДобрый день!
УдалитьМакросы sleep_enable и sleep_cpu вызываются внутри sleep_mode, поэтому одного его вызова достаточно. Но если требуется выключить brownout detection (детектор пониженного напряжения питания), то это должно быть сделано непосредственно перед уходом в сон (перед sleep_cpu). Об этом я писал чуть выше в комментариях, что бит BODS (отвечающий за разрешение работы bod) сбрасывается автоматически по истечении 3 тактов. Поэтому все некритичные по времени настройки выполняем в первую очередь и только потом отключаем bod и уходим в сон.
у меня ардуино уно+лед дисплей16*2+радиомодуль в режиме ожидания потребляют 35мА, в активном режиме 55мА, а в режиме передачи данных - 100мА. Отключение ацп и детектора пониженного напряжения не уменьшает энергопотребление совершенно(
УдалитьКогда вы перейдете с использования Ардуино на отдельный микроконтроллер, вы заметите, что bod и ацп весьма прожорливые и их отключение в спящем режиме поможет снизить ток с порядка 100мкА до 1мкА и ниже.
УдалитьС Ардуино такого потребления не добиться. Пробуйте советы из предыдущей статьи (уменьшить напряжение, частоту, откажитесь от линейных регуляторов), но это все равно будут миллиамперы, не микро.
догнать хотя бы до 20мА. частоту снижаю - сразу все перестает работать, напряжение от аккумулятора 3,7В - слабовато работает дисплей, ему 5В необходимо. а что за линейные регуляторы и как от них отказаться?
УдалитьВ одной из последних статей я описывал способ изменения источника тактирования. Частоту можно будет менять в меню ide arduino, правда при этом нужно перезаписывать загрузчик, но это минутное дело с usbasp. Дальше микроконтроллер можно вынимать из ардуино (он будет работать от внутреннего RC-генератора) и использовать с небольшой обвязкой. Для перепрошивки микроконтроллер можно снова вставить в ардуино или использовать usb-ttl адаптер. Если в ардуино микроконтроллер не в dip корпусе, т.е. не вынимается, то можно купить отдельный. Да и тактирование можно оставить от кварца, а не от RC - вариантов масса. Но чтобы увидеть те самые микроамперы, заявленные в даташите, от Ардуино придется уйти.
УдалитьЛинейные регуляторы установлены на платах ардуино и снижают подаваемое напряжение до необходимых 5В. Все что выше уходит в тепло. Отказаться от них - в смысле использовать для этих целей dc-dc преобразователь и подавать питание на пин 5V (в обход стабилизатора). Если у вас 3.7В то о преобразователе и речи нет. Разве что, наоборот, повысить напряжение, если дисплею этого мало.
УдалитьКасательно периферии - по ним тоже нужно изучать документацию, есть ли в них энергосберегающие режимы. Как вариант отключать их перед уходом в сон (используя транзисторный ключ)
ну, дисплей отключаю просто с пина ардуино, а вот с радиомодулем так не выходит - радиомодуль отключается, но ардуино почему то не засыпает. вместо того, чтоб уснуть, программа зацикливается и экран просто постоянно обновляется
УдалитьВладимир! У меня есть старый пульт, он сломался и я его переделал на Ардуино, в режиме работы потребляет 14 мА, быстро жрёт аккумулятор. Можно попросить вас за деньги дописать мой скрипт, чтобы пульт работал с режимами сна и потреблял меньше энергии. Цена вопроса?
ОтветитьУдалитьДобрый день!
УдалитьПришлите мне скетч на почту, я посмотрю что можно сделать. VladimirTsibrov@yandex.ru
Здравствуйте Владимир!
ОтветитьУдалитьСпасибо за ваши труды.
Хочу спросить, есть ли у вас в планах раскрыть ваши темы на примерах работы в FLProg?
Эта очень удобная платформа для тех кто занят проектами малой и средней автоматизации.
Добрый день!
УдалитьВ планах такого не было. Могу посмотреть, прикинуть, что из этого получится. Речь именно об энергосбережении в FLProg?
Энергосбережение и прерывания можно для каждого Пина ардуино. Там не очень сложно сделать блок. Блок это по сути код
УдалитьЕсть ардуинка нано + радиомодуль, который отсылает каждые 15 мин на "базу" сигнал: подскажите - как усыпить на эти 15 мин и понизить потребление от батарейки.
ОтветитьУдалитьvoid loop()
Удалить{
radio.powerUp();
//Serial.println("nrf on");
delay(2000);
data[0] = 200; // подаем сигнал (флаг 200) на базу о присутствии в радиусе действия
//Serial.println("Send 200");
for (int m=0; m <= 14; m++) // отправляем данные и указываем сколько байт пакет:
// отправка 15 пакетов через 5 секунд
{
radio.write(&data, sizeof(data));
delay(5000);
//Serial.print ("Send signal: ");
//Serial.println (m);
}
radio.powerDown();
//Serial.println("nrf off and sleep 10 sec");
delay(900000); // сон на 15 мин (900000 мс)
}
Возьмите скетч для пробуждения от сторожевого таймера, установите интервал таймера 8 секунд. Код для ухода в сон поместите в цикл for. 112 итераций цикла дадут примерно 15 минут сна.
Удалитьгде то не так
Удалитьvoid loop() {
for (int s=0; s <= 114; s++) {
wdt_enable(WDTO_8S); //Задаем интервал сторожевого таймера (8с)
WDTCSR |= (1 << WDIE); //Устанавливаем бит WDIE регистра WDTCSR для разрешения прерываний от сторожевого таймера
set_sleep_mode(SLEEP_MODE_PWR_DOWN); //Устанавливаем интересующий нас режим
sleep_mode(); // Переводим МК в спящий режим
}
digitalWrite(LED_BUILTIN, f);
}
ISR (WDT_vect) {
wdt_disable();
f = !f;
}
Зачем вам при каждом событии wdt инвертировать переменную f?
УдалитьЕсли вы хотите увидеть как Ардуино спит, то изменяйте значение f после цикла. Для тестов уменьшите продолжительность сна (интервал wdt и количество проходов цикла): чтобы проверить работоспособность ждать 15 минут не требуется.
Также нет необходимости разрешать/запрещать работу wdt внутри цикла. Поставьте wdt_enable перед циклом, wdt_disable - после.
Для чего в прерывании нужна эта строка?
ОтветитьУдалитьwdt_disable();
Название функции говорит само за себя - она останавливает работу сторожевого таймера.
УдалитьЕсли WDT был запущен для того чтобы выйти из спящего режима, то после пробуждения он больше не нужен, поэтому отключается.
Добрый день. А какая точность будет при пробуждении от Watch Dog Timer ?
ОтветитьУдалитьУ меня 40 устройств, должны просыпаться в заданное время и отсылать по радиоканалу информацию, если их усыпить в одно время они все просыпаются как им захочется. Я устанавливаю что бы просыпались от Watch Dog Timer, Сон самый глубокий - SLEEP_MODE_PWR_DOWN, точность времени просыпания при таких параметрах и на большом времени уплывает в небытие.. Подскажите пожалуйста как можно добиться максимальной точности для пробуждений от таймеров при минимальном потреблении?
Добрый день.
УдалитьВнутренний генератор на 128кГц, который служит источником тактирования для WDT, не годится для приложений, требующих точности. Его частота меняется в зависимости от температуры и напряжения питания.
Я бы для более точной привязки к времени добавил в устройство RTC часы, а WDT использовал для выхода из сна и проверки текущего времени. Далее принимается решение опять перевести микроконтроллер в сон или выполнить какие-то действия. Да, это будет более затратно и по стоимости устройства, и в плане энергосбережения. Но зато наши события уже будут привязаны ко времени с некоторой известной нам погрешностью. Например, просыпаясь каждые 500мс, можно рассчитывать на погрешность этого же порядка.
Чтобы сократить время выхода из сна можно использовать режимы Standby, в которых генератор не останавливается.
Что касается синхронизации устройств, то с этим уже сложнее. Все зависит от требуемой точности. И RTC кстати также имеют свою погрешность, что нужно учитывать при их выборе.
УдалитьА если использовать режим SLEEP_MODE_PWR_SAVE + Timer2 для пробуждения, точность будет лучше? Забыл сказать что у меня плата на базе Atmega 328p (https://www.electrodragon.com/product/atmega328p-arduino-plus-lora-sx1278-board-loraduino/) я не пойму стоит ли там кварц, или нечто другое..
УдалитьМожно попробовать с таймером, это будет точнее WDT. Кварц там есть.
УдалитьВладимир с чего это вы взяли что при спящем режиме таймеры останавливаются, с каких это пор?
УдалитьИз даташита взял. Раздел 14. PM - Power Management and Sleep Modes
УдалитьЯсно. Большое спасибо за статьи и помощь!
ОтветитьУдалитьОчень полезная статья, Владимир!
ОтветитьУдалитьИспользуя периодическое пробуждение по сторожевому таймеру и режим SLEEP_MODE_PWR_DOWN снизил на моей Arduino RF-Nano энергопотребление примерно в 3 раза.
Но есть одна проблема, которую не могу решить. На контроллере реализована некая система команд, которые я передаю ему с клавиатуры через Serial. Так вот, когда МК спит, то эти команды до него не доходят. Надо видимо как-то пробуждать его в момент передачи команд, чтобы он их принимал и выполнял. Не подскажете как можно это реализовать? Думаю, что тоже через прерывания, но признаться совсем не силен в них...
Попробуйте перед уходом в сон разрешить PCINT16 (Pin Change Interrupt на пине D0, он же RX). При отправке данных на микроконтроллер возникнет прерывание, которое его разбудит. При пробуждении запрещаете PCINT16. На пробуждение и выход в рабочий режим нужно время, и часть передаваемых по Serial данных будет потеряна. Поэтому первая передача должна быть фиктивной.
УдалитьПро PCINT я подробно расписал здесь: https://tsibrov.blogspot.com/2019/06/arduino-interrupts-part2.html
Там есть пример скетча с функцией разрешения PCINT на конкретном пине. Если ничего не путаю, вам нужен вызов: pciSetup(0); Обработчик прерывания PCINT2_vect оставьте пустым.
Здравствуйте! Спасибо большое за Ваши статьи - очень полезны. Приведите, пожалуйста, пример с засыпанием и побудкой процессора в режиме аналогового измерения ADC Noise Reduction Mode. Толкового примера я к сожалению не нашел. Спасибо!
ОтветитьУдалить#include "avr/sleep.h"
Удалитьvoid setup() {
ADMUX = B01000000;
// REFS[1:0] = 01 - источник опор. напряжения - AVcc
// ADLAR = 0 - отключаем левое вырвнивание результата
// MUX[3:0] = 0000 - к АЦП подключен вход A0
ADCSRA = B10001111;
// ADEN = 1 - включаем АЦП
// ADIE = 1 - разрешаем прерывание от АЦП
// ADPS[2:0] = 111 - устанавливаем делитель = 128 (16000КГц/128 = 125КГц)
Serial.begin(9600);
}
void loop() {
set_sleep_mode(SLEEP_MODE_ADC);
ADCSRA |= 1 << ADSC; // устанавливаем бит ADC Start Conversion
sleep_mode();
// Проснулись. Результат оцифровки ждет нас в регистрах ADCL ADCH
int V = ADCL | (ADCH << 8);
Serial.println(V);
delay(1000);
}
EMPTY_INTERRUPT(ADC_vect);
Добрый день! Попробуйте такой код. В нем запускается АЦП перед уходом в сон. При завершении оцифровки модуль генерирует прерывание, которое пробуждает микроконтроллер. Обработчик как таковой не нужен, поэтому в скетче вместо макроса ISR используется EMPTY_INTERRUPT.
УдалитьЗа выбор канала отвечают биты MUX: 0000 - ADC0, 0111 - ADC7.
Кроме программных приемов для повышения точности оцифровки есть рекомендации к проектированию устройств. Например, использование LC фильтра при подключении AVcc к источнику питания, минимизация длины проводников и т.п.
Команду ADCSRA |= 1 << ADSC можно даже убрать, преобразование запустится автоматически при переходе в сон.
УдалитьЗдравствуйте! Спасибо большое. Собираюсь изучить вопрос снижения шума подробнее с разных сторон. Сообщу результат.
УдалитьЗдравствуйте! Прошу прощения за задержку ответа. Обработчик прерывания заменил на ISR(ADC_vect){} и всё заработало (нашёл в другом примере). До замены - не просыпался. Проверял на Arduino Nano, установленную на макетную беспаечную плату, - пульсации 1-2 дискреты при сигнале 0,8 максимального и различия с обычным измерением без засыпания не заметил. Думаю, что для снижения шума эффективнее сначала использовать "голый" процессор с правильной разводкой питания аналоговой и цифровой частей (в Nano они соединены вместе), а дальше использовать измерение во сне. Спасибо большое!
ОтветитьУдалитьДобрый день!
УдалитьДело еще в том, что analogRead не худший вариант. Эта функция запускает АЦП и гоняет пустой цикл в ожидании окончания преобразования:
while (bit_is_set(ADCSRA, ADSC));
Другое дело, если бы мы запустили АЦП и пошли выполнять программу дальше: работать с таймерами, Serial, I2C и т.п. Вероятно тогда бы можно было ощутить те шумы, о которых говорится в даташите. Я бы проверил такой вариант для сравнения.
Странно, что не работало с EMPTY_INTERRUPT. Макрос ISR отличается от него тем, что даже для пустого обработчика он добавляет пролог и эпилог - лишний код. Это, кстати, тоже интересная тема, можете почитать об этом здесь: https://tsibrov.blogspot.com/2019/06/arduino-interrupts-part2.html#Ключевое слово ISR
УдалитьЗдравствуйте! Отличная статья. Но я столкнулся с проблемой. Устанавливаю arduino pro mini в состояние сна кнопкой. Для вывода из сна использую прерывание по второму пину и делаю перезагрузку. После включения питания цикл выкл-вкл проходит, но только 1 раз. reset не помогает, только повторное выключение питания. Не пойму кто сбрасывает состояния сна?! Если ставить блок в setup, все работает. В программе использую только softwareserial, его прерывания запрещаю командой end().
ОтветитьУдалитьМожно ли запретить все прерывания кроме одного(для вывода из сна)?
Добрый день!
УдалитьПерезагрузку как делаете? Переход на нулевой адрес или через wdt?
через wdt
ОтветитьУдалитьМеня удивляет разный результат от нажатия на кнопку reset и отключения и затем включения питания. Во втором случае работает один цикл сон - пробуждение от кнопки, а потом опять, сон "сбрасывается" каким-то прерыванием. Если оставить cli(), то все нормально, но разбудить можно только кнопкой reset.
ОтветитьУдалитьВ старом загрузчике была проблема с зависанием после перезагрузки по wdt. Возможно, у вас как раз такой и прошит. В загрузчике optiboot (используется в уно) такой проблемы нет, поэтому, если еще сменили загрузчик в этой про мини, советую это сделать (при условии, что плата на 16мгц, а не на 8). Я описывал данную процедуру в статье https://tsibrov.blogspot.com/2018/07/arduino-as-isp.html
УдалитьВ качестве целевой платы укажите не про мини, а уно. После этого при её программировании так же выбирать уно.
Отпишитесь о результатах после обновления загрузчика.
Разный результат при отключении питания и ресете связан с тем, что последний сбрасывает не все флаги/регистры контроллера
У меня в системе используется wdt, загрузчик был optiboot, а сейчас вообще его убрал. Программирую через SPI. Я тоже думал про ОЗУ или флаги/регистры (но какие?). С другой стороны, пробуждение может быть от wdt или внешние, но я их отключаю, если бы нет, то режим сна не реализовывался бы вообще. Помехи на кнопку? Но режим сна не реализовывался бы вообще, кроме этого, sleep_cpu() сбрасывается практически сразу, после установки. Softwareserial по команде end() запрещает прерывание, проверял. Подозрения на UART, к нему подключен приемопередатчик E32. Я его тоже перевожу в режим глубокого сна, но может E32 ведет себя не так, как надо. А сбрасывается E32 только при выключении питания.
ОтветитьУдалитьНет, отключение E32 не влияет.
ОтветитьУдалитьwdt отключаете в сетапе? попробуйте добавить следующий код в самое начало функции setup:
Удалитьbyte _mcusr = MCUSR;
MCUSR = 0;
if (_mcusr & 1 << WDRF) wdt_disable();
Бит WDE регистра WDTCSR не сбросится пока установлен WDRF в MCUSR. Поэтому сначала нужно сбросить флаг, потом бит WDE.
УдалитьЗаработало! Спасибо большое! А то я всю голову сломал.
ОтветитьУдалитьА я правильно понимаю, что, если проверка в setup показывает, что установлен WDRF, то микропроцессор выполнил перезагрузку, а если нет, то было включено питание (мне желательно знать была перезагрузка, или включение питания).
ОтветитьУдалитьНе совсем. Загляните в даташит, раздел 15.9.1. MCU Status Register
УдалитьWDRF - это Watchdog System Reset Flag, говорит о том, что источником перезагрузки был wdt
PORF - это Power-on Reset Flag - сигнал сброса был сгенерирован схемой PoR, т.е. при подаче питания.
Другие 2 флага для оставшихся источников сброса: BOD и внешний сброс.
Понял, еще раз спасибо.
ОтветитьУдалитьМожете подсказать как сделать сон 15 минут.
ОтветитьУдалитьЯ делал так, но он все равно спит 8 секунд:
wdt_enable(WDTO_8S);
for(int s=0;s <= 112;s++){
WDTCSR |= (1 << WDIE);
}
float h = dht.readHumidity(); //Измеряем влажность
float t = dht.readTemperature(); //Измеряем температуру
Serial.print(h);
Serial.print("\t");
Serial.print(t);
Serial.println();
if(millis() - tm > TIME_BEFORE_SLEEP){
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_mode();
tm = millis();
}
}
ISR (WDT_vect) {
wdt_disable();
}
А что вы собственно хотели сделать циклом for? Установить бит wdie 113 раз? Если же усыпить микроконтроллер, то в цикле этого нет. Ну и наворотили!
Удалить#include
#include
void setup() {
Serial.begin(9600);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}
void loop() {
wdt_enable(WDTO_1S); // Запускаем wdt
WDTCSR |= (1 << WDIE);
for (int s = 0; s <10; s++) {
sleep_mode();
WDTCSR |= (1 << WDIE);
}
wdt_disable(); // Останавливаем wdt
Serial.println("123");
delay(100);
}
ISR (WDT_vect) {
}
Погуглите еще RTC модули с будильником.
Этот комментарий был удален автором.
ОтветитьУдалитьИсправь бессмысленное
ОтветитьУдалитьMCUCR != (1 << BODS) | (1 << BODSE);
Вместо lcd.begin(); надо lcd.init(); иначе ошибка скетча
ОтветитьУдалить