Погрузить ATmega328 в очень глубокий сон и послушать последовательный порт?
Я изучил параметры сна ATmega328 и прочитал несколько статей о нем, и мне хотелось бы понять, есть ли еще параметры.
Поэтому я хотел бы получить как можно более низкий ток, так что все, что меньше 100 мкА, будет хорошо — при условии, что я смогу прослушивать UART и прерывания для пробуждения.
Я использую специальную печатную плату (не UNO) с ATmega328p.
Перевод чипа в режим глубокого сна:
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_cpu ();
не разбудит его с помощью последовательной связи, согласно этому.
Вам нужно будет перевести его в режим IDLE
, чтобы слушать последовательный порт, но это будет потреблять несколько мА - плохо.
Я нашел эту ссылку, по которой можно аппаратно подключить последовательный порт к прерыванию — это опасно, так как можно потерять данные, и, кроме того, мне нужны эти 2 контакта прерываний.
Я также прочитал эту статью Гэммона, в которой говорится, что можно отключить некоторые вещи, чтобы перейти в режим ожидания с гораздо меньшим энергопотреблением, но он не упомянул, как именно это достигается:
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer1_disable();
power_timer2_disable();
power_twi_disable();
Итак, в заключение, есть ли какой-либо вариант, позволяющий получить ток менее 0,25 мА, а также прослушивать последовательный порт без каких-либо манипуляций с оборудованием? Например, просыпаетесь с длинным последовательным вводом данных?
@Curnelious, 👍13
Обсуждение2 ответа
Лучший ответ:
Доска, которую мы производим, делает это.
- Вывод RX подключен к INT0
- Вывод INT0 настроен на вход или подтяжку входа в зависимости от того, как управляется линия RX
В спящем режиме включается прерывание низкого уровня INT0
//Очистить программный флаг для прерывания приема rx_interrupt_flag = 0; //Очистить аппаратный флаг для прерывания приема EIFR = _BV(INTF0); //Повторно присоединить прерывание 0 attachInterrupt(INT_RX, rx_interrupt, HIGH);
Процедура обслуживания прерывания INT0 устанавливает флаг и отключает прерывание
void rx_interrupt() { detachInterrupt(INT_RX); rx_interrupt_flag = 1; }
При пробуждении мы проверяем наличие флага (есть и другие источники прерывания)
Что касается связи, мы используем протокол сообщений, который имеет начальный символ >
и конечный символ \r
. Например, >setrtc,2015,07,05,20,58,09\r
. Это обеспечивает некоторую базовую защиту от потери сообщений, поскольку входящие символы не обрабатываются, пока не будет получен >
. Чтобы разбудить устройство, мы отправляем фиктивное сообщение перед передачей. Для этого достаточно одного символа, но мы отправляем >wakeup\r
хе-хе.
Устройство остается активным в течение 30 секунд после получения последнего сообщения в случае новых сообщений. Если получено новое сообщение, 30-секундный таймер сбрасывается. Программное обеспечение интерфейса ПК отправляет фиктивное сообщение каждую секунду, чтобы устройство оставалось активным, пока пользователь подключает его для настройки и т. д.
Этот метод не дает абсолютно никаких проблем. Плата с несколькими периферийными устройствами потребляет около 40uA в спящем режиме. Фактический ток, потребляемый ATMega328P, вероятно, составляет около 4uA.
Обновление
В таблице данных показано, что вывод RX также является выводом прерывания смены вывода 16 (PCINT16)
Таким образом, еще один метод без проводов может быть
Перед сном: установите бит маски прерывания смены порта в PCMSK2 для PCINT16, очистите флаг смены выводов порта 2 в PCIFR, включите прерывание смены выводов порта 2 (PCINT16-PCINT23), установив PCIE2 в PCICR.
Настройте ISR для прерывания смены контакта порта 2 и продолжайте, как и прежде.
Единственное предостережение относительно прерывания смены порта заключается в том, что прерывание является общим для всех 8 контактов, которые включены для этого порта. Поэтому, если у вас включено более одного изменения контакта для порта, вам нужно определить, какое из них вызвало прерывание в ISR. Это не проблема, если вы не используете никаких других прерываний смены контакта на этом порту (PCINT16-PCINT23 в данном случае)
В идеале я бы спроектировал нашу доску именно так, но то, что у нас есть, работает.
Приведенный ниже код позволяет достичь того, о чем вы просите:
#include <avr/sleep.h>
#include <avr/power.h>
const byte AWAKE_LED = 8;
const byte GREEN_LED = 9;
const unsigned long WAIT_TIME = 5000;
ISR (PCINT2_vect)
{
// здесь обрабатывается прерывание по смене вывода для D0-D7
} // конец PCINT2_vect
void setup()
{
pinMode (GREEN_LED, OUTPUT);
pinMode (AWAKE_LED, OUTPUT);
digitalWrite (AWAKE_LED, HIGH);
Serial.begin (9600);
} // конец настройки
unsigned long lastSleep;
void loop()
{
if (millis () - lastSleep >= WAIT_TIME)
{
lastSleep = millis ();
noInterrupts ();
byte old_ADCSRA = ADCSRA;
// отключить АЦП
ADCSRA = 0;
// прерывание смены контакта (пример для D0)
PCMSK2 |= bit (PCINT16); // нужен пин 0
PCIFR |= bit (PCIF2); // очистить все невыполненные прерывания
PCICR |= bit (PCIE2); // включить прерывания по смене контактов для D0-D7
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer1_disable();
power_timer2_disable();
power_twi_disable();
UCSR0B &= ~bit (RXEN0); // отключить приемник
UCSR0B &= ~bit (TXEN0); // отключить передатчик
sleep_enable();
digitalWrite (AWAKE_LED, LOW);
interrupts ();
sleep_cpu ();
digitalWrite (AWAKE_LED, HIGH);
sleep_disable();
power_all_enable();
ADCSRA = old_ADCSRA;
PCICR &= ~bit (PCIE2); // отключить прерывания смены контактов для D0-D7
UCSR0B |= bit (RXEN0); // включить приемник
UCSR0B |= bit (TXEN0); // включить передатчик
} // конец времени сна
if (Serial.available () > 0)
{
byte flashes = Serial.read () - '0';
if (flashes > 0 && flashes < 10)
{
// мигает светодиодом x раз
for (byte i = 0; i < flashes; i++)
{
digitalWrite (GREEN_LED, HIGH);
delay (200);
digitalWrite (GREEN_LED, LOW);
delay (200);
}
}
} // конец if
} // конец цикла
Я использовал прерывание смены контакта на выводе Rx, чтобы заметить, когда приходят последовательные данные. В этом тесте плата переходит в спящий режим, если в течение 5 секунд не происходит никакой активности (светодиод «wake» гаснет). Входящие последовательные данные вызывают прерывание смены контакта, чтобы разбудить плату. Она ищет число и мигает «зеленым» светодиодом указанное количество раз.
Измеренный ток
Работая при 5 В, я измерил около 120 нА тока во время сна (0,120 мкА).
Пробуждающее сообщение
Однако проблема заключается в том, что первый поступивший байт теряется из-за того, что последовательное оборудование ожидает падающего уровня на Rx (стартовый бит), который уже поступил к моменту его полного пробуждения.
Я предлагаю (как в ответе geometrikal) сначала отправить сообщение "wake", а затем сделать паузу на короткое время. Пауза нужна для того, чтобы убедиться, что оборудование не интерпретирует следующий байт как часть сообщения wake. После этого все должно работать нормально.
Поскольку здесь используется прерывание по смене контакта, никакого другого оборудования не требуется.
Измененная версия с использованием SoftwareSerial
Версия ниже успешно обрабатывает первый байт, полученный по последовательному порту. Она делает это следующим образом:
Использование SoftwareSerial, который использует прерывания по смене выводов. Прерывание, вызванное стартовым битом первого последовательного байта, также пробуждает процессор.
Установка предохранителей таким образом, чтобы мы использовали:
- Внутренний RC-генератор
- БПК отключен
- Предохранители были: низкий: 0xD2, высокий: 0xDF, расширенный: 0xFF
Вдохновленный FarO в комментарии, это позволяет процессору просыпаться за 6 тактов (750 нс). При 9600 бод время каждого бита составляет 1/9600 (104,2 мкс), поэтому дополнительная задержка незначительна.
#include <avr/sleep.h>
#include <avr/power.h>
#include <SoftwareSerial.h>
const byte AWAKE_LED = 8;
const byte GREEN_LED = 9;
const unsigned long WAIT_TIME = 5000;
const byte RX_PIN = 4;
const byte TX_PIN = 5;
SoftwareSerial mySerial(RX_PIN, TX_PIN); // RX, TX
void setup()
{
pinMode (GREEN_LED, OUTPUT);
pinMode (AWAKE_LED, OUTPUT);
digitalWrite (AWAKE_LED, HIGH);
mySerial.begin(9600);
} // конец настройки
unsigned long lastSleep;
void loop()
{
if (millis () - lastSleep >= WAIT_TIME)
{
lastSleep = millis ();
noInterrupts ();
byte old_ADCSRA = ADCSRA;
// отключить АЦП
ADCSRA = 0;
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer1_disable();
power_timer2_disable();
power_twi_disable();
sleep_enable();
digitalWrite (AWAKE_LED, LOW);
interrupts ();
sleep_cpu ();
digitalWrite (AWAKE_LED, HIGH);
sleep_disable();
power_all_enable();
ADCSRA = old_ADCSRA;
} // конец времени сна
if (mySerial.available () > 0)
{
byte flashes = mySerial.read () - '0';
if (flashes > 0 && flashes < 10)
{
// мигает светодиодом x раз
for (byte i = 0; i < flashes; i++)
{
digitalWrite (GREEN_LED, HIGH);
delay (200);
digitalWrite (GREEN_LED, LOW);
delay (200);
}
}
} // конец if
} // конец цикла
Потребление энергии в спящем режиме составило 260 нА (0,260 мкА), так что это очень низкое потребление, когда оно не нужно.
Обратите внимание, что при такой установке фьюзов процессор работает на частоте 8 МГц. Поэтому вам необходимо сообщить об этом IDE (например, выбрать "Lilypad" в качестве типа платы). Таким образом, задержки и SoftwareSerial будут работать на правильной скорости.
- Отправка последовательных данных в прерывании
- Как перевести ATtiny/ATmega в режим глубокого сна (чтобы годами работать от батарей), но при этом обнаруживать нажатие кнопки?
- Последовательная связь ESP8266 с ATMega328P
- Ардуино как ISP с serial monitor для ATmega328
- ATmega328P-PU: программатор не отвечает
- Почему SoftwareSerial не работает как надо на Arduino Pro Mini 3v3?
- SIM800L и Arduino Sleep — получение странного последовательного вывода после 5 вызовов
- Почему последовательная связь не работает на atmega168/328p?
@NickAlexeev это вопрос по ATmega328 **а не по Arduino**, так как он напрямую касается чипа, который намного ниже уровня Arduino. Прекратите уже эти неправильные миграции!, @Chris Stratton
Вряд ли. Желание вывести Arduino из спящего режима не может быть отклонено, поскольку в нем установлен чип ATmega328. В таком случае вы сможете перенаправить **все** вопросы об Arduino обратно на сайт EE., @Nick Gammon