Metti ATmega328 in un sonno molto profondo e ascolta il seriale?


13

Ho studiato le opzioni per dormire di ATmega328 e ho letto alcuni articoli a riguardo, e vorrei capire se ci sono più opzioni.

Quindi vorrei ottenere la corrente più bassa possibile, in modo che qualsiasi cosa in meno di 100uA sia buona, purché io possa ascoltare uart e interrompere per svegliarmi.

Sto usando un PCB personalizzato (non UNO), con ATmega328p.

Impostazione del chip in modalità deep sleep:

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

non lo risveglierebbe con la comunicazione seriale, secondo questo .

Dovrai metterlo in IDLEmodalità, per ascoltare il seriale, ma questo consumerebbe qualche mA -bad.

Ho trovato questo link in cui è possibile collegare in hardware il seriale all'interruzione - il che è pericoloso in modo da poter perdere i dati e, inoltre, ho bisogno di questi 2 pin di interrupt.

Ho anche letto questo articolo di Gammon , in cui è possibile disabilitare alcune cose, in modo da poter dormire IDLE con una potenza molto più bassa - ma non ha menzionato esattamente come si ottiene da questo:

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

Quindi, in fondo, c'è qualche opzione là fuori, per ottenere almeno meno di 0,25 mA e anche ascoltare la porta seriale, senza alcuna manipolazione hardware? Ad esempio, svegliarsi con un lungo input di dati seriali ?


1
@NickAlexeev questa è una domanda ATmega328 non una Arduino in quanto si occupa direttamente del chip molto al di sotto del livello di Arduino. Basta con le migrazioni improprie già!
Chris Stratton,

1
Quasi. Voler svegliare un Arduino dal sonno non può davvero essere respinto perché ha un chip ATmega328. A quel ritmo sarai in grado di rimandare tutte le domande su Arduinos al sito EE.
Nick Gammon

Risposte:


11

Una tavola che realizziamo fa questo.

  • Il pin RX è cablato su INT0
  • Pin INT0 impostato su input o input pullup a seconda di come viene pilotata la linea RX
  • In modalità di sospensione, è abilitato l'interrupt di basso livello 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);
    
  • La routine di servizio di interrupt INT0 imposta un flag e disabilita l'interrupt

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Al risveglio, controlliamo la bandiera (ci sono altre fonti di interruzione)

Per quanto riguarda le comunicazioni, utilizziamo un protocollo di messaggio con un carattere iniziale >e finale \r. es >setrtc,2015,07,05,20,58,09\r. Ciò fornisce una protezione di base contro la perdita di messaggi, poiché i caratteri in arrivo non vengono elaborati fino a quando non >viene ricevuto un. Per riattivare il dispositivo inviamo un messaggio fittizio prima della trasmissione. Un singolo personaggio lo farebbe, ma noi mandiamo >wakeup\rhehe.

Il dispositivo rimane sveglio per 30 secondi dopo la ricezione dell'ultimo messaggio in caso di nuovi messaggi. Se viene ricevuto un nuovo messaggio, il timer di 30 secondi viene ripristinato. Il software di interfaccia per PC invia un messaggio fittizio ogni secondo per mantenere il dispositivo sveglio mentre l'utente lo ha collegato per la configurazione, ecc.

Questo metodo non dà assolutamente alcun problema. La scheda con alcune periferiche utilizza circa 40uA durante il sonno. La corrente effettiva consumata da ATMega328P è probabilmente di circa 4uA.

Aggiornare

Dai un'occhiata al foglio dati mostra che il pin RX è anche pin di interruzione cambio pin 16 (PCINT16)

Quindi potrebbe essere un altro metodo senza fili

  • Prima di andare in pausa: impostare il bit di maschera di interruzione cambio porta in PCMSK2 per PCINT16, cancellare il flag porta 2 di cambio pin in PCIFR, abilitare l'interrupt porta 2 di cambio pin (PCINT16-PCINT23) impostando PCIE2 in PCICR.

  • Configurare un ISR per l'interruzione della porta 2 di cambio pin e continuare come prima.

L'unico avvertimento con l'interrupt di modifica della porta è che l'interrupt è condiviso su tutti gli 8 pin abilitati per quella porta. Quindi, se hai più di un cambio pin abilitato per la porta, devi determinare quale ha innescato l'interruzione nell'ISR. Questo non è un problema se non si utilizzano altri interrupt di cambio pin su quella porta (PCINT16-PCINT23 in questo caso)

Idealmente, è così che avrei progettato la nostra tavola, ma ciò che abbiamo funziona.


Grazie mille . Non c'è altro modo che i trucchi hardware ??? Quindi connetti rx solo a int0 / int1 con 1 linea ??
Curnelious,

1
In realtà ho appena dato un'occhiata alla scheda tecnica e potresti essere in grado di utilizzare un interrupt di cambio pin
geometrikal

Grazie, quale sarebbe il diverso? Comunque dovrei svegliarmi con rx su int1?
Curnelious,

È necessario solo 1 pin di interruzione. Ne ho postate altre sopra: potresti usare il pin RX come interruzione di cambio pin. Non l'ho fatto però, quindi potrebbero esserci alcune catture come forse dovresti disabilitare RX / abilitare il cambio pin prima di andare a dormire e disabilitare il cambio pin / abilitare RX dopo il risveglio
geometrikal

grazie, non sono sicuro del motivo per cui dovrebbe essere un problema collegando semplicemente rx a INT1, impostando l'interruzione su alto, quindi disabilitando gli interrupt quando si verifica int1 e riabilitandoli quando vanno in modalità sospensione?
Curnelious,

8

Il codice seguente ottiene ciò che stai chiedendo:

#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

Ho usato un'interruzione di cambio pin sul pin Rx per notare quando arrivano i dati seriali. In questo test la scheda entra in modalità di sospensione se non viene rilevata alcuna attività dopo 5 secondi (il LED "sveglia" si spegne). I dati seriali in entrata causano l'interruzione del cambio pin per riattivare la scheda. Cerca un numero e lampeggia il LED "verde" quel numero di volte.

Corrente misurata

Funzionando a 5 V, ho misurato circa 120 nA di corrente durante il sonno (0,120 µA).

Messaggio di risveglio

Un problema tuttavia è che il primo byte in arrivo viene perso a causa del fatto che l'hardware seriale si aspetta un livello in calo su Rx (il bit di avvio) che è già arrivato quando è completamente attivo.

Suggerisco (come nella risposta di geometrikal) di inviare prima un messaggio "sveglio", quindi di metterlo in pausa per un breve periodo. La pausa è assicurarsi che l'hardware non interpreti il ​​byte successivo come parte del messaggio di attivazione. Dopodiché dovrebbe funzionare bene.


Poiché utilizza un interrupt di cambio pin, non è richiesto altro hardware.


Versione modificata utilizzando SoftwareSerial

La versione seguente elabora correttamente il primo byte ricevuto in seriale. Lo fa da:

  • Utilizzo di SoftwareSerial che utilizza interrupt di cambio pin. L'interruzione causata dal bit di avvio del primo byte seriale attiva anche il processore.

  • Impostare i fusibili in modo che utilizziamo:

    • Oscillatore RC interno
    • BOD disabilitato
    • I fusibili erano: Basso: 0xD2, Alto: 0xDF, Esteso: 0xFF

Ispirato da FarO in un commento, questo consente al processore di svegliarsi in 6 cicli di clock (750 ns). A 9600 baud il tempo di ogni bit è 1/9600 (104,2 µs), quindi il ritardo extra è 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

Il consumo di energia durante il sonno è stato misurato a 260 nA (0,260 µA), quindi è un consumo molto basso quando non necessario.

Si noti che con i fusibili impostati in questo modo, il processore funziona a 8 MHz. Pertanto è necessario comunicarlo all'IDE (ad es. Selezionare "Lilypad" come tipo di scheda). In questo modo i ritardi e SoftwareSerial funzioneranno alla velocità corretta.


@NickGammon grazie mille! l'ho già fatto e ha funzionato. È così comune in altri prodotti che usiamo tutti i giorni o hanno altri modi per ascoltare le comunicazioni e dormire? (tutti gli MCU non riescono ad ascoltare uart durante il sonno profondo?)
Curnelious il

Stavo leggendo il foglio dati e afferma che quando si utilizza l'oscillatore interno, sono necessari solo 14 cicli di clock per avviare il chip, a condizione che venga utilizzato BOD. Se la fonte di alimentazione è sempre attiva (batterie), che potrebbe essere utilizzata anche senza BOD? violando le specifiche ovviamente. Ciò solleverebbe il chip molto poco dopo il bordo UART in arrivo, ma non sono ancora sicuro che basterebbe catturare il primo byte.
FarO,

Sì, 14 cicli di clock non sono lunghi, tuttavia probabilmente l'UART mancherebbe comunque il limite (dopotutto, il limite è quando il processore nota la modifica). Quindi, anche se si avvia molto presto dopo il limite, potrebbe comunque mancare.
Nick Gammon

Un po 'di test indica che (anche con BOD abilitato) non funziona. Il processore deve essere sveglio per notare il fronte (bit di avvio) e quindi accenderlo dopo averlo ricevuto (anche se molto poco dopo) non funziona.
Nick Gammon

I 14 cicli di clock vengono ripristinati. Sono necessari solo 6 cicli dopo lo spegnimento, se si utilizza l'oscillatore RC interno. Vedi codice di esempio aggiuntivo.
Nick Gammon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.