Versetzen Sie den ATmega328 in einen sehr tiefen Schlaf und hören Sie die Seriennummer?


13

Ich habe die Schlafoptionen des ATmega328 untersucht und ein paar Artikel darüber gelesen, und ich würde gerne verstehen, ob es weitere Optionen gibt.

Also möchte ich so wenig Strom wie möglich haben, damit alles, was unter 100 uA liegt, gut ist - solange ich auf uart hören kann und beim Aufwachen unterbricht.

Ich verwende eine benutzerdefinierte Platine (nicht die UNO) mit ATmega328p.

Versetzen des Chips in einen tiefen Schlaf:

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

wäre es nicht aufwachen mit der seriellen Kommunikation auf, nach diesem .

Sie müssen es in den IDLEModus versetzen , um seriell zu hören, aber dies würde ein paar mA-Bad verbrauchen.

Ich habe diesen Link gefunden, über den Sie die serielle Schnittstelle der Hardware mit dem Interrupt verbinden können - was gefährlich ist, damit Sie Daten verlieren können. Außerdem benötige ich diese 2 Interrupt-Pins.

Ich habe auch diesen Artikel von Gammon gelesen , in dem Sie einige Dinge deaktivieren können, damit Sie mit viel weniger Energie LEERLAUF schlafen können - aber er hat nicht erwähnt, wie genau Sie davon profitieren:

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

Unterm Strich gibt es also eine Möglichkeit, weniger als 0,25 mA zu erhalten und auch den seriellen Anschluss ohne Hardwaremanipulation abzuhören? Zum Beispiel aufwachen mit langen seriellen Dateneingabe ?


1
@ NickAlexeev Dies ist eine ATmega328-Frage, keine Arduino- Frage, da sie sich direkt mit dem Chip befasst, der weit unter dem Niveau von Arduino liegt. Stoppen Sie schon mit den unsachgemäßen Migrationen!
Chris Stratton

1
Kaum. Der Wunsch, einen Arduino aus dem Schlaf zu wecken, lässt sich nicht wirklich abweisen, da er einen ATmega328-Chip enthält. Mit dieser Geschwindigkeit können Sie alle Fragen zu Arduinos an die EE-Site zurücksenden.
Nick Gammon

Antworten:


11

Ein Board, das wir herstellen, macht das.

  • Der RX-Pin ist mit INT0 verbunden
  • Der INT0-Pin ist je nach Ansteuerung der RX-Leitung auf Eingang oder Eingangspullup eingestellt
  • Im Ruhezustand ist der INT0-Interrupt mit niedrigem Pegel aktiviert

    //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);
    
  • Die Interrupt-Serviceroutine INT0 setzt ein Flag und deaktiviert den Interrupt

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Beim Aufwachen wird nach dem Flag gesucht (es gibt andere Interruptquellen)

Auf der Kommunikationsseite verwenden wir ein Nachrichtenprotokoll mit einem Start- >und einem Endzeichen \r. zB >setrtc,2015,07,05,20,58,09\r. Dies bietet einen grundlegenden Schutz vor Nachrichtenverlust, da eingehende Zeichen erst verarbeitet werden, wenn ein >empfangen wird. Um das Gerät aufzuwecken, senden wir vor der Übertragung eine Dummy-Nachricht. Ein einzelner Charakter würde es tun, aber wir senden >wakeup\rhehe.

Das Gerät bleibt 30 Sekunden lang wach, nachdem die letzte Nachricht bei neuen Nachrichten eingegangen ist. Wenn eine neue Nachricht empfangen wird, wird der 30-Sekunden-Timer zurückgesetzt. Die PC-Schnittstellensoftware sendet jede Sekunde eine Dummy-Nachricht, um das Gerät wach zu halten, während der Benutzer es zur Konfiguration usw. angeschlossen hat.

Diese Methode gibt absolut keine Probleme. Das Board mit ein paar Peripheriegeräten verbraucht im Schlaf ca. 40uA. Der tatsächliche Stromverbrauch des ATMega328P liegt wahrscheinlich bei 4uA.

Aktualisieren

Ein Blick auf das Datenblatt zeigt, dass der RX-Pin auch Pin-Wechsel-Interrupt-Pin 16 (PCINT16) ist.

Somit kann eine andere Methode ohne Drähte sein

  • Vor dem Ruhezustand: Setzen Sie das Bit für die Portwechsel-Interrupt-Maske in PCMSK2 für PCINT16, löschen Sie das Flag für Pinwechsel-Port 2 in PCIFR und aktivieren Sie den Pinwechsel-Port 2-Interrupt (PCINT16-PCINT23), indem Sie PCIE2 in PCICR einstellen.

  • Richten Sie einen ISR für den Pinwechsel-Port 2-Interrupt ein und fahren Sie wie zuvor fort.

Die einzige Einschränkung beim Portwechsel-Interrupt besteht darin, dass der Interrupt von allen 8 Pins gemeinsam genutzt wird, die für diesen Port aktiviert sind. Wenn Sie also mehr als einen Pinwechsel für den Port aktiviert haben, müssen Sie feststellen, welcher den Interrupt im ISR ausgelöst hat. Dies ist kein Problem, wenn Sie keine anderen PIN-Wechsel-Interrupts an diesem Port verwenden (in diesem Fall PCINT16-PCINT23).

Im Idealfall hätte ich unser Board so entworfen, aber was wir haben, funktioniert.


Vielen Dank . Gibt es keinen anderen Weg als Hardware-Tricks ??? Sie verbinden also nur RX mit Int0 / Int1 mit 1 Zeile?
Curnelious

1
Eigentlich habe ich mir nur das Datenblatt angesehen und es könnte sein, dass Sie einen PIN-Wechsel-Interrupt verwenden können
geometrikal

Danke, was wäre das anders? Wie auch immer, ich müsste mit RX auf Int1 aufwachen?
Curnelious

Sie benötigen nur 1 Interrupt-Pin. Ich habe oben einiges mehr gepostet - Sie könnten den RX-Pin als Pin-Wechsel-Interrupt verwenden. Ich habe dies jedoch nicht getan, so dass es ein paar Probleme geben kann, z. B. müssen Sie RX deaktivieren / Pinwechsel vor dem Schlafen aktivieren und Pinwechsel / RX nach dem Aufwachen aktivieren
geometrikal

danke, ich bin nicht sicher, warum sollte ein Problem mit nur der Verbindung von RX zu INT1, setzen Sie die Interrupt auf High, als deaktivieren Sie Interrupts, wenn Int1 passiert, und aktivieren Sie sie wieder, wenn Sie schlafen gehen?
Curnelious

7

Der folgende Code erreicht das, wonach Sie fragen:

#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

Ich habe einen PIN-Wechsel-Interrupt am Rx-Pin verwendet, um festzustellen, wann serielle Daten ankommen. In diesem Test geht das Board in den Ruhezustand, wenn nach 5 Sekunden keine Aktivität stattfindet (die "Wake" -LED erlischt). Eingehende serielle Daten bewirken, dass der Pinwechsel-Interrupt die Karte aktiviert. Es sucht nach einer Nummer und blinkt die "grüne" LED so oft.

Gemessener Strom

Beim Betrieb mit 5 V habe ich im Schlaf einen Strom von ca. 120 nA gemessen (0,120 µA).

Nachricht erwecken

Ein Problem besteht jedoch darin, dass das erste ankommende Byte verloren geht, da die serielle Hardware einen fallenden Pegel auf Rx (dem Startbit) erwartet, der bereits eingetroffen ist, wenn es vollständig aktiviert ist.

Ich schlage vor (wie in der Antwort von geometrikal), dass Sie zuerst eine "Wach" -Nachricht senden und dann eine kurze Pause machen . Die Pause besteht darin, sicherzustellen, dass die Hardware das nächste Byte nicht als Teil der Aktivierungsnachricht interpretiert. Danach sollte es gut funktionieren.


Da hierbei ein PIN-Wechsel-Interrupt verwendet wird, ist keine weitere Hardware erforderlich.


Geänderte Version mit SoftwareSerial

In der folgenden Version wird das erste seriell empfangene Byte erfolgreich verarbeitet. Dies geschieht durch:

  • Verwenden von SoftwareSerial, das PIN-Wechsel-Interrupts verwendet. Der Interrupt, der durch das Startbit des ersten seriellen Bytes verursacht wird, weckt auch den Prozessor.

  • Stellen Sie die Sicherungen so ein, dass wir Folgendes verwenden:

    • Interner RC-Oszillator
    • BSB deaktiviert
    • Die Sicherungen waren: Low: 0xD2, High: 0xDF, Extended: 0xFF

Inspiriert von FarO in einem Kommentar, lässt dies den Prozessor in 6 Taktzyklen (750 ns) aufwachen. Bei 9600 Baud beträgt jede Bitzeit 1/9600 (104,2 µs), sodass die zusätzliche Verzögerung unbedeutend ist.

#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

Der Stromverbrauch im Schlaf wurde mit 260 nA (0,260 µA) gemessen, was einen sehr geringen Verbrauch bedeutet, wenn er nicht benötigt wird.

Beachten Sie, dass der Prozessor mit den so eingestellten Sicherungen mit 8 MHz arbeitet. Dazu müssen Sie der IDE Bescheid geben (z. B. "Lilypad" als Kartentyp auswählen). Auf diese Weise arbeiten die Verzögerungen und SoftwareSerial mit der richtigen Geschwindigkeit.


@ NickGammon vielen Dank! Ich habe es schon getan und es hat funktioniert. Ist dies bei anderen Produkten, die wir täglich verwenden, üblich, oder haben sie andere Möglichkeiten, um auf Kommunikation und Schlaf zu hören? (Alle MCUs können im Tiefschlaf nicht hören?)
Curnelious

Ich habe das Datenblatt gelesen und es besagt, dass bei Verwendung eines internen Oszillators nur 14 Taktzyklen erforderlich sind, um den Chip zu starten, vorausgesetzt, dass BOD verwendet wird. Wenn die Stromquelle immer in Betrieb ist (Batterien), kann dies auch ohne BSB verwendet werden? Verstöße gegen die Spezifikationen natürlich. Das würde den Chip sehr kurz nach der eingehenden UART-Flanke anzeigen, aber ich bin mir immer noch nicht sicher, ob es ausreichen würde, das erste Byte abzufangen.
FarO

Ja, 14 Taktzyklen sind nicht lang, aber möglicherweise würde der UART die Flanke immer noch verfehlen (schließlich ist die Flanke, wenn der Prozessor die Änderung bemerkt). Selbst wenn es sehr bald nach der Kante startet, könnte es dennoch fehlen.
Nick Gammon

Einige Tests haben ergeben, dass dies (auch bei aktiviertem BOD) nicht funktioniert. Der Prozessor muss wach sein, um die Vorderkante (Startbit) zu bemerken, und er muss eingeschaltet werden, nachdem er sie empfangen hat (auch wenn sie kurz danach noch nicht funktioniert).
Nick Gammon

Die 14 Taktzyklen sind nach dem Reset. Sie benötigen nur 6 Zyklen nach dem Ausschalten, wenn Sie den internen RC-Oszillator verwenden. Siehe zusätzlichen Beispielcode.
Nick Gammon
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.