Положить ATmega328 в очень глубокий сон и послушать сериал?

13

Я исследовал варианты сна ATmega328 и прочитал несколько статей об этом, и я хотел бы понять, есть ли еще варианты.

Поэтому я хотел бы получить как можно более низкий ток, чтобы все, что меньше, чем 100uA, было бы хорошо - до тех пор, пока я могу слушать uart и прерывания для пробуждения.

Я использую пользовательскую плату (не UNO) с ATmega328p.

Установка чипа в глубокий сон:

 set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
 sleep_enable();
 sleep_cpu ();

не проснулся бы с последовательной связью, согласно этому .

Вам нужно будет перевести его в IDLEрежим, чтобы прослушивать сериал, но это потребует нескольких мА-бад.

я нашел эту ссылку, где вы можете подключить аппаратно последовательный порт к прерыванию - это опасно, так что вы можете потерять данные, и, кроме того, мне нужны эти 2 контакта прерываний.

Я также читал эту статью о Gammon , где вы можете отключить некоторые вещи, чтобы вы могли бездействовать с гораздо меньшим энергопотреблением - но он не упомянул, как именно вы получаете от этого:

 power_adc_disable();
      power_spi_disable();
      power_timer0_disable();
      power_timer1_disable();
      power_timer2_disable();
      power_twi_disable();

Итак, суть в том, есть ли какая-либо опция, чтобы получить не менее 0,25 мА и прослушивать последовательный порт без каких-либо аппаратных манипуляций? Например, просыпаться с длинным последовательным вводом данных ?

Curnelious
источник
1
@NickAlexeev это вопрос ATmega328, а не Arduino, поскольку он имеет дело непосредственно с чипом намного ниже уровня Arduino. Остановись с неправильной миграцией уже!
Крис Страттон,
1
Едва. Желание разбудить Arduino из сна не может быть отклонено, поскольку в нем есть чип ATmega328. В таком случае вы сможете перевести все вопросы об Arduinos обратно на сайт EE.
Ник Гэммон

Ответы:

11

Доска, которую мы делаем, делает это.

  • Контакт RX подключен к INT0
  • Вывод INT0 установлен на вход или подтягивание входа в зависимости от того, как управляется линия RX
  • В режиме сна прерывание по низкому уровню INT0 включено

    //Clear software flag for rx interrupt
    rx_interrupt_flag = 0;
    //Clear hardware flag for rx interrupt
    EIFR = _BV(INTF0);
    //Re-attach interrupt 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. Это дает некоторую базовую защиту от потери сообщений, так как входящие символы не обрабатываются до получения a >. Чтобы разбудить устройство, мы отправляем фиктивное сообщение перед передачей. Один персонаж сделает это, но мы отправим >wakeup\rхе-хе.

Устройство остается без сна в течение 30 секунд после получения последнего сообщения в случае появления новых сообщений. Если получено новое сообщение, 30-секундный таймер сбрасывается. Программное обеспечение интерфейса ПК отправляет фиктивное сообщение каждую секунду, чтобы держать устройство в активном состоянии, пока пользователь подключил его для настройки и т. Д.

Этот метод не дает абсолютно никаких проблем. Плата с несколькими периферийными устройствами потребляет около 40 мкА во время сна. Фактический ток, потребляемый ATMega328P, составляет около 4 мкА.

Обновить

При взгляде на таблицу видно, что вывод RX также является выводом прерывания смены контакта 16 (PCINT16)

Таким образом, другой метод без проводов может быть

  • Перед сном: установите бит маски прерывания смены порта в PCMSK2 для PCINT16, очистите флаг порта смены контактов 2 в PCIFR, активируйте прерывание порта смены контактов 2 (PCINT16-PCINT23), установив PCIE2 в PCICR.

  • Установите ISR для прерывания порта 2 смены контактов и продолжайте, как и раньше.

Единственное предупреждение, связанное с прерыванием смены порта, состоит в том, что прерывание является общим для всех 8 контактов, которые включены для этого порта. Таким образом, если у вас есть более одной смены контактов, разрешенной для порта, вы должны определить, кто вызвал прерывание в ISR. Это не проблема, если вы не используете другие прерывания смены контактов на этом порту (в данном случае PCINT16-PCINT23)

В идеале именно так я и разработал бы нашу доску, но у нас все работает.

geometrikal
источник
Огромное спасибо . Нет ли другого способа, кроме аппаратных трюков ??? Таким образом, вы только подключите rx к int0 / int1 с 1 строки ??
Curnelious
1
На самом деле, я только что взглянул на таблицу данных, и вы могли бы использовать прерывание смены штырька
геометрический
Спасибо, что будет по-другому? В любом случае, я бы проснулся с rx на int1?
Curnelious
Вам нужен только 1 контакт прерывания. Я разместил еще несколько выше - вы можете использовать контакт RX в качестве прерывания смены контакта. Я не сделал этого , хотя , так что может быть несколько уловов нравятся , возможно , вам придется отключить RX / включить изменения штырьковых перед сном и отключить смены ПИН / включить RX после пробуждения
geometrikal
спасибо, я не уверен, почему должна быть проблема с простым подключением rx к INT1, установкой прерывания на высоком уровне, чем отключение прерываний, когда происходит int1, и их повторное включение, когда вы ложитесь спать?
Curnelious
8

Код ниже достигает того, что вы просите:

#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)
{
  // handle pin change interrupt for D0 to D7 here
}  // end of PCINT2_vect

void setup() 
{
  pinMode (GREEN_LED, OUTPUT);
  pinMode (AWAKE_LED, OUTPUT);
  digitalWrite (AWAKE_LED, HIGH);
  Serial.begin (9600);
} // end of setup

unsigned long lastSleep;

void loop() 
{
  if (millis () - lastSleep >= WAIT_TIME)
  {
    lastSleep = millis ();

    noInterrupts ();

    byte old_ADCSRA = ADCSRA;
    // disable ADC
    ADCSRA = 0;  
    // pin change interrupt (example for D0)
    PCMSK2 |= bit (PCINT16); // want pin 0
    PCIFR  |= bit (PCIF2);   // clear any outstanding interrupts
    PCICR  |= bit (PCIE2);   // enable pin change interrupts for D0 to 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);  // disable receiver
    UCSR0B &= ~bit (TXEN0);  // disable transmitter

    sleep_enable();
    digitalWrite (AWAKE_LED, LOW);
    interrupts ();
    sleep_cpu ();      
    digitalWrite (AWAKE_LED, HIGH);
    sleep_disable();
    power_all_enable();

    ADCSRA = old_ADCSRA;
    PCICR  &= ~bit (PCIE2);   // disable pin change interrupts for D0 to D7
    UCSR0B |= bit (RXEN0);  // enable receiver
    UCSR0B |= bit (TXEN0);  // enable transmitter
  }  // end of time to sleep

  if (Serial.available () > 0)
  {
    byte flashes = Serial.read () - '0';
    if (flashes > 0 && flashes < 10)
      {
      // flash LED x times 
      for (byte i = 0; i < flashes; i++)
        {
        digitalWrite (GREEN_LED, HIGH);
        delay (200);  
        digitalWrite (GREEN_LED, LOW);
        delay (200);  
        }
      }        
  }  // end of if

}  // end of loop

Я использовал прерывание смены выводов на выводе Rx, чтобы заметить, когда поступают последовательные данные. В этом тесте плата переходит в спящий режим, если в течение 5 секунд не происходит никаких действий (светодиод «проснулся» гаснет). Входящие последовательные данные вызывают прерывание смены штырька, чтобы разбудить плату. Он ищет номер и мигает «зеленым» светодиодом столько раз.

Измеренный ток

Работая при 5 В, я замерял около 120 нА тока во время сна (0,120 мкА).

Пробуждение сообщение

Проблема, однако, заключается в том, что первый поступающий байт теряется из-за того, что последовательное оборудование ожидает падения уровня на Rx (начальный бит), который уже прибыл к моменту полного пробуждения.

Я предлагаю (как в ответе геометрического), чтобы вы сначала отправили «пробужденное» сообщение, а затем сделали паузу на короткое время. Пауза состоит в том, чтобы убедиться, что оборудование не интерпретирует следующий байт как часть активного сообщения. После этого все должно работать нормально.


Поскольку для этого используется прерывание смены штырька, никакое другое оборудование не требуется.


Исправленная версия с использованием 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);
} // end of setup

unsigned long lastSleep;

void loop() 
{
  if (millis () - lastSleep >= WAIT_TIME)
  {
    lastSleep = millis ();

    noInterrupts ();

    byte old_ADCSRA = ADCSRA;
    // disable ADC
    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;
  }  // end of time to sleep

  if (mySerial.available () > 0)
  {
    byte flashes = mySerial.read () - '0';
    if (flashes > 0 && flashes < 10)
      {
      // flash LED x times 
      for (byte i = 0; i < flashes; i++)
        {
        digitalWrite (GREEN_LED, HIGH);
        delay (200);  
        digitalWrite (GREEN_LED, LOW);
        delay (200);  
        }
      }        
  }  // end of if

}  // end of loop

Потребляемая мощность во время сна была измерена как 260 нА (0,260 мкА), так что это очень низкое потребление, когда не требуется.

Обратите внимание, что с такими предохранителями процессор работает на частоте 8 МГц. Таким образом, вы должны сообщить об этом IDE (например, выберите «Lilypad» в качестве типа платы). Таким образом, задержки и SoftwareSerial будут работать с правильной скоростью.

Ник Гаммон
источник
@NickGammon большое спасибо! Я уже сделал это, и это сработало. Этот способ распространен в других продуктах, которые мы используем каждый день, или у них есть другие способы слушать комм и спать? (все MCU не могут слушать уарта в глубоком сне?)
Любопытно
Я читал таблицу данных, и в ней говорится, что при использовании внутреннего генератора для запуска чипа необходимы только 14 тактов при условии, что используется БПК. Если источник питания постоянно (аккумуляторы), что можно использовать и без БПК? нарушая спецификации конечно. Это привело бы к появлению микросхемы очень скоро после входящего фронта UART, но все же я не уверен, что этого будет достаточно, чтобы поймать первый байт.
FarO
Да, 14 тактов не длинны, однако, возможно, UART все равно не достигнет своего края (в конце концов, это преимущество, когда процессор замечает изменение). Таким образом, даже если он запускается очень скоро после края, он все равно может пропустить его.
Ник Гэммон
Небольшое тестирование показывает, что (даже с включенным BOD) это не работает. Процессор должен быть активным, чтобы заметить передний фронт (стартовый бит) и, таким образом, включить его после получения (даже если очень скоро) не работает.
Ник Гэммон
14 тактов после сброса. Вам нужно только 6 циклов после отключения питания, если вы используете внутренний RC генератор. Смотрите дополнительный пример кода.
Ник Гэммон