¿Poner ATmega328 en un sueño muy profundo y escuchar el serial?


13

He investigado las opciones de sueño del ATmega328, y leí algunos artículos al respecto, y me gustaría entender si hay más opciones.

Por lo tanto, me gustaría obtener la corriente más baja posible, de modo que cualquier cosa menor a 100uA sería buena, siempre que pueda escuchar uart e interrumpir por despertar.

Estoy usando una PCB personalizada (no la UNO), con ATmega328p.

Configurar el chip para dormir profundamente:

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

De acuerdo con esto , no lo despertaría con la comunicación en serie .

Necesitará ponerlo en IDLEmodo, para escuchar el serial, pero esto consumiría unos pocos mA -bad.

He encontrado este enlace donde puede conectar en hardware la serie a la interrupción, lo cual es peligroso para que pueda perder datos y, además, necesito estos 2 pines de interrupción.

También leí este artículo de Gammon , donde puedes desactivar algunas cosas, para que puedas dormir inactivo con una potencia mucho menor, pero no mencionó cómo exactamente obtienes esto:

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

Entonces, en resumen, ¿hay alguna opción por ahí, para obtener menos de 0.25mA al menos, y también escuchar el puerto serie, sin ninguna manipulación de hardware? Por ejemplo, ¿despertarse con una entrada de datos en serie larga ?


1
@NickAlexeev, esta es una pregunta ATmega328, no una Arduino, ya que se trata directamente con el chip muy por debajo del nivel de Arduino. ¡Deténgase con las migraciones incorrectas ya!
Chris Stratton

1
Apenas. Querer despertar a un Arduino del sueño no se puede descartar porque tiene un chip ATmega328. A ese ritmo, podrá devolver todas las preguntas sobre Arduinos al sitio de EE.
Nick Gammon

Respuestas:


11

Un tablero que hacemos hace esto.

  • El pin RX está conectado a INT0
  • Pin INT0 configurado para entrada o pullup de entrada dependiendo de cómo se maneje la línea RX
  • En suspensión, la interrupción INT0 de bajo nivel está habilitada

    //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);
    
  • La rutina de servicio de interrupción INT0 establece un indicador y desactiva la interrupción

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Al despertar, verificamos la marca (hay otras fuentes de interrupción)

En el lado de las comunicaciones, usamos un protocolo de mensaje que tiene un carácter inicial >y un carácter final \r. por ej >setrtc,2015,07,05,20,58,09\r. Esto brinda cierta protección básica contra la pérdida de mensajes, ya que los caracteres entrantes no se procesan hasta que >se recibe un. Para activar el dispositivo, enviamos un mensaje ficticio antes de la transmisión. Un solo personaje lo haría, pero enviamos >wakeup\rjeje.

El dispositivo permanece despierto durante 30 segundos después de recibir el último mensaje en caso de nuevos mensajes. Si se recibe un nuevo mensaje, el temporizador de 30 segundos se reinicia. El software de interfaz de la PC envía un mensaje ficticio cada segundo para mantener el dispositivo despierto mientras el usuario lo tiene conectado para la configuración, etc.

Este método no ofrece absolutamente ningún problema. La placa con algunos periféricos usa aproximadamente 40 uA cuando duerme. La corriente real consumida por el ATMega328P es probablemente de alrededor de 4uA.

Actualizar

Al mirar la hoja de datos, se muestra que el pin RX también es el pin 16 de interrupción de cambio de pin (PCINT16)

Por lo tanto, otro método sin cables puede ser

  • Antes de dormir: configure el bit de máscara de interrupción de cambio de puerto en PCMSK2 para PCINT16, borre la bandera del puerto 2 de cambio de pin en PCIFR, active la interrupción del puerto 2 de cambio de pin (PCINT16-PCINT23) configurando PCIE2 en PCICR.

  • Configure un ISR para la interrupción del puerto de cambio de pin 2 y continúe como antes.

La única advertencia con la interrupción de cambio de puerto es que la interrupción se comparte entre todos los 8 pines que están habilitados para ese puerto. Entonces, si tiene más de un cambio de pin habilitado para el puerto, debe determinar qué activó la interrupción en el ISR. Esto no es un problema si no está utilizando ninguna otra interrupción de cambio de pin en ese puerto (PCINT16-PCINT23 en este caso)

Idealmente, así es como habría diseñado nuestra placa, pero lo que tenemos funciona.


Muchas gracias . ¿No hay otra manera que los trucos de hardware? ¿Entonces solo conectas rx a int0 / int1 con 1 línea?
Curnelious

1
En realidad, solo eché un vistazo a la hoja de datos y es posible que pueda usar una interrupción de cambio de pin
geometrikal

Gracias, ¿cuál sería la diferencia? De todos modos, ¿tendría que despertarme con rx en int1?
Curnelious

Solo necesita 1 pin de interrupción. Publiqué un poco más arriba: podría usar el pin RX como una interrupción de cambio de pin. Sin embargo, no he hecho esto, por lo que puede haber algunas capturas, como tal vez tendrá que deshabilitar RX / habilitar el cambio de pin antes de dormir y deshabilitar el cambio de pin / habilitar RX después del despertar
geometrikal

gracias, no estoy seguro de por qué debería haber un problema con solo conectar rx a INT1, configurar la interrupción en alto, que desactivar las interrupciones cuando ocurre int1, y habilitarlas nuevamente cuando se va a dormir.
Curnelious

8

El siguiente código logra lo que está pidiendo:

#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

Usé una interrupción de cambio de pin en el pin Rx para notar cuándo llegan los datos en serie. En esta prueba, la placa se duerme si no hay actividad después de 5 segundos (el LED "despierto" se apaga). Los datos seriales entrantes provocan que la interrupción de cambio de clavija active la placa. Busca un número y parpadea el LED "verde" esa cantidad de veces.

Corriente medida

Corriendo a 5 V, medí aproximadamente 120 nA de corriente cuando estaba dormido (0.120 µA).

Mensaje de despertar

Sin embargo, un problema es que el primer byte de llegada se pierde debido al hecho de que el hardware en serie espera un nivel descendente en Rx (el bit de inicio) que ya ha llegado cuando está completamente despierto.

Sugiero (como en la respuesta de geometrikal) que primero envíe un mensaje "despierto", y luego haga una pausa por un corto tiempo. La pausa es asegurarse de que el hardware no interprete el siguiente byte como parte del mensaje despierto. Después de eso debería funcionar bien.


Dado que esto utiliza una interrupción de cambio de pin, no se requiere ningún otro hardware.


Versión modificada usando SoftwareSerial

La siguiente versión procesa con éxito el primer byte recibido en serie. Hace esto por:

  • Usando SoftwareSerial que usa interrupciones de cambio de pin. La interrupción causada por el bit de inicio del primer byte en serie también activa el procesador.

  • Configurando los fusibles para que usemos:

    • Oscilador interno RC
    • DBO deshabilitado
    • Los fusibles fueron: Bajo: 0xD2, Alto: 0xDF, Extendido: 0xFF

Inspirado por FarO en un comentario, esto permite que el procesador se active en 6 ciclos de reloj (750 ns). A 9600 baudios, cada tiempo de bit es 1/9600 (104.2 µs), por lo que el retraso adicional es insignificante.

#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

El consumo de energía cuando estaba dormido se midió como 260 nA (0.260 µA), por lo que es un consumo muy bajo cuando no se necesita.

Tenga en cuenta que con los fusibles configurados así, el procesador funciona a 8 MHz. Por lo tanto, debe informarle al IDE sobre eso (por ejemplo, seleccione "Lilypad" como tipo de placa). De esa forma, los retrasos y SoftwareSerial funcionarán a la velocidad correcta.


@NickGammon muchas gracias! Ya lo hice y funcionó. ¿Es esa forma común en otros productos que usamos todos los días, o tienen otras formas de escuchar comunicaciones y dormir? (¿Todos los MCU no pueden escuchar a uart durmiendo profundamente?)
Curnelious el

Estaba leyendo la hoja de datos y dice que cuando se usa un oscilador interno, solo se necesitan 14 ciclos de reloj para iniciar el chip, siempre que se use BOD. Si la fuente de alimentación siempre está activa (baterías), ¿eso podría usarse también sin BOD? violando las especificaciones, por supuesto. Eso elevaría el chip poco después del borde UART entrante, pero aún no estoy seguro de que sea suficiente para atrapar el primer byte.
FarO

Sí, 14 ciclos de reloj no son largos, sin embargo, posiblemente el UART aún perdería el borde (después de todo, el borde es cuando el procesador nota el cambio). Por lo tanto, incluso si comienza muy pronto después del borde, aún podría perderse.
Nick Gammon

Un poco de prueba indica que (incluso con BOD habilitado) no funciona. El procesador debe estar despierto para notar el borde de ataque (bit de inicio) y, por lo tanto, encenderlo después de recibirlo (aunque sea poco después) no funciona.
Nick Gammon

Los 14 ciclos de reloj son después del reinicio. Solo necesita 6 ciclos después del apagado, si utiliza el oscilador RC interno. Ver código de ejemplo adicional.
Nick Gammon
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.