Umieścić ATmega328 w bardzo głębokim śnie i posłuchać serialu?

13

Zbadałem opcje snu ATmega328 i przeczytałem kilka artykułów na ten temat i chciałbym zrozumieć, czy jest więcej opcji.

Chciałbym więc uzyskać jak najniższy prąd, aby wszystko, co jest mniejsze niż 100uA, byłoby dobre - o ile mogę słuchać uart i przerywa budzenie.

Używam niestandardowej płytki drukowanej (nie UNO) z ATmega328p.

Ustawienie chipa w tryb głębokiego spania:

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

Zgodnie z tym, nie obudziłby go przy komunikacji szeregowej .

Będziesz musiał IDLEprzełączyć go w tryb, aby słuchać seryjnego, ale zużyłoby to kilka mA-wad.

Znalazłem ten link, w którym można podłączyć sprzętowo szereg do przerwań - co jest niebezpieczne, więc możesz stracić dane, a ponadto potrzebuję tych 2 pinów przerwań.

Przeczytałem również ten artykuł Gammona , w którym możesz wyłączyć niektóre rzeczy, abyś mógł spać bezczynnie ze znacznie mniejszą mocą - ale nie wspomniał o tym, jak dokładnie z tego korzystasz:

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

Podsumowując, czy jest jakaś opcja, aby uzyskać mniej niż 0,25 mA, a także słuchać portu szeregowego, bez jakiejkolwiek manipulacji sprzętowej? Na przykład budzisz się z długim szeregowym wprowadzaniem danych ?

Curnelious
źródło
1
@NickAlexeev jest to pytanie ATmega328, a nie Arduino, ponieważ dotyczy bezpośrednio chipa znacznie poniżej poziomu Arduino. Przestań już z niewłaściwymi migracjami!
Chris Stratton,
1
Ledwie. Chcąc obudzić Arduino ze snu, tak naprawdę nie można go odrzucić, ponieważ ma w nim układ ATmega328. Przy takim tempie będziesz w stanie odesłać wszystkie pytania dotyczące Arduinos z powrotem na stronę EE.
Nick Gammon

Odpowiedzi:

11

Robi to tablica, którą tworzymy.

  • Pin RX jest podłączony do INT0
  • Pin INT0 ustawiony na wejście lub pullup wejściowy w zależności od sposobu sterowania linią RX
  • W trybie uśpienia włączone jest przerwanie niskiego poziomu 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);
    
  • Procedura obsługi przerwań INT0 ustawia flagę i wyłącza przerwanie

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Po wybudzeniu sprawdzamy flagę (istnieją inne źródła przerwań)

Po stronie komunikacyjnej używamy protokołu wiadomości, który ma znak początkowy >i końcowy \r. np >setrtc,2015,07,05,20,58,09\r. Zapewnia to podstawową ochronę przed utratą wiadomości, ponieważ znaki przychodzące nie są przetwarzane, dopóki nie >zostanie odebrane. Aby obudzić urządzenie, wysyłamy fałszywą wiadomość przed transmisją. Zrobiłaby to jedna postać, ale wysyłamy >wakeup\rhehe.

W przypadku nowych wiadomości urządzenie pozostaje w trybie czuwania przez 30 sekund po otrzymaniu ostatniej wiadomości. W przypadku otrzymania nowej wiadomości licznik 30 sekund jest resetowany. Oprogramowanie interfejsu komputera wysyła fałszywy komunikat co sekundę, aby utrzymać urządzenie w stanie uśpienia, gdy użytkownik ma je podłączone do konfiguracji itp.

Ta metoda nie daje absolutnie żadnych problemów. Płyta z kilkoma urządzeniami peryferyjnymi zużywa około 40uA podczas snu. Rzeczywisty prąd pobierany przez ATMega328P wynosi prawdopodobnie około 4uA.

Aktualizacja

Spojrzenie na arkusz danych pokazuje, że pin RX jest również pinem przerwania zmiany pinów 16 (PCINT16)

Zatem może być inna metoda bez drutów

  • Przed snem: Ustaw bit maski zmiany portu w PCMSK2 dla PCINT16, usuń flagę zmiany portu 2 w PCIFR, włącz przerwanie zmiany portu 2 (PCINT16-PCINT23), ustawiając PCIE2 w PCICR.

  • Ustaw ISR dla przerwań zmiany portu 2 i kontynuuj jak poprzednio.

Jedynym zastrzeżeniem związanym z przerwaniem zmiany portu jest to, że przerwanie jest wspólne dla wszystkich 8 pinów, które są włączone dla tego portu. Więc jeśli masz włączoną więcej niż jedną zmianę pinów dla portu, musisz ustalić, która wywołała przerwanie w ISR. Nie stanowi to problemu, jeśli nie używasz żadnych innych przerwań zmiany pinów na tym porcie (w tym przypadku PCINT16-PCINT23)

Idealnie tak zaprojektowałbym naszą tablicę, ale to, co mamy, działa.

geometrikal
źródło
Dziękuję bardzo . Czy istnieje inny sposób niż sztuczki sprzętowe? Więc podłączasz rx tylko do int0 / int1 z 1 linią ??
Curnelious,
1
Właściwie właśnie rzuciłem okiem na arkusz danych i być może możesz użyć przerwania zmiany pinów
geometrikal
Dzięki, co by było inaczej? W każdym razie musiałbym obudzić się z rx na int1?
Curnelious,
Potrzebujesz tylko 1 pinu przerwania. Powyżej zamieściłem kilka innych - możesz użyć pinu RX jako przerwania zmiany pinu. Nie zrobiłem tego jednak, więc może być kilka chwytów, na przykład może będziesz musiał wyłączyć RX / włączyć zmianę pinów przed snem i wyłączyć zmianę pinów / włączyć RX po przebudzeniu
geometrikal
dzięki, nie jestem pewien, dlaczego miałby być problem z podłączeniem rx do INT1, ustawieniem interrupt na high, niż wyłączeniem przerwań, gdy int1 się zdarzy, i włączeniem ich ponownie, gdy idę spać?
Curnelious,
8

Poniższy kod spełnia to, o co prosisz:

#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

Użyłem przerwania zmiany pinu na pinie Rx, aby zauważyć, kiedy nadchodzą dane szeregowe. W tym teście płyta przechodzi w tryb uśpienia, jeśli po 5 sekundach nie będzie żadnej aktywności (dioda LED „czuwania” gaśnie). Przychodzące dane szeregowe powodują, że przerwanie zmiany pinów budzi płytkę. Wyszukuje liczbę i tyle razy miga „zielona” dioda LED.

Mierzony prąd

Pracując przy 5 V, mierzyłem około 120 nA prądu podczas snu (0,120 µA).

Przebudzenie wiadomości

Problemem jest jednak to, że pierwszy przychodzący bajt zostaje utracony z powodu faktu, że sprzęt szeregowy oczekuje spadku poziomu na Rx (bicie początkowym), który już przybył do czasu pełnego przebudzenia.

Sugeruję (jak w odpowiedzi geometrikal), aby najpierw wysłać wiadomość „przebudzoną”, a następnie wstrzymać na chwilę. Przerwa polega na upewnieniu się, że sprzęt nie interpretuje następnego bajtu jako części przebudzonego komunikatu. Potem powinno działać dobrze.


Ponieważ wykorzystuje to przerwanie zmiany pinów, nie jest wymagany żaden inny sprzęt.


Wersja poprawiona za pomocą SoftwareSerial

Poniższa wersja z powodzeniem przetwarza pierwszy bajt odebrany na serial. Robi to poprzez:

  • Korzystanie z SoftwareSerial, które korzysta z przerwań zmiany pinów. Przerwanie spowodowane bitem początkowym pierwszego bajtu szeregowego również budzi procesor.

  • Ustawienie bezpieczników w celu użycia:

    • Wewnętrzny oscylator RC
    • BZT wyłączone
    • Bezpieczniki były: Niska: 0xD2, Wysoka: 0xDF, Rozszerzona: 0xFF

Zainspirowany przez FarO w komentarzu, procesor budzi się w 6 cyklach zegara (750 ns). Przy 9600 bodów każdy bit wynosi 1/9600 (104,2 µs), więc dodatkowe opóźnienie jest nieznaczne.

#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

Pobór mocy podczas snu mierzono jako 260 nA (0,260 µA), co oznacza bardzo niskie zużycie, gdy nie jest potrzebne.

Zauważ, że przy takim ustawieniu bezpieczników procesor pracuje z częstotliwością 8 MHz. Dlatego musisz powiedzieć IDE o tym (np. Wybierz „Lilypad” jako typ płytki). W ten sposób opóźnienia i SoftwareSerial będą działać z właściwą prędkością.

Nick Gammon
źródło
@NickGammon wielkie dzięki! już to zrobiłem i zadziałało. Czy taki sposób jest powszechny w innych produktach, z których korzystamy na co dzień, czy też mają inne sposoby słuchania komunikacji i snu? (wszyscy MCU nie mogą słuchać uart podczas głębokiego snu?)
Curnelious,
Czytałem arkusz danych i stwierdzono, że podczas korzystania z wewnętrznego oscylatora do uruchomienia układu potrzeba tylko 14 cykli zegara, pod warunkiem, że używany jest BZT. Jeśli źródło zasilania jest zawsze włączone (baterie), można by tego użyć również bez BZT? oczywiście naruszanie specyfikacji. To sprawiłoby, że układ pojawił się bardzo krótko po nadchodzącym zboczu UART, ale wciąż nie jestem pewien, czy wystarczyłoby złapać pierwszy bajt.
FarO
Tak, 14 cykli zegarowych nie jest długie, jednak możliwe, że UART nadal nie trafi na krawędź (w końcu krawędź występuje, gdy procesor zauważy zmianę). Więc nawet jeśli uruchomi się bardzo szybko po krawędzi, nadal może go przegapić.
Nick Gammon
Trochę testów wskazuje, że (nawet przy włączonym BZT) nie działa. Procesor musi być przebudzony, aby zauważyć przewagę (bit startowy), a zatem zasilanie go po otrzymaniu (nawet jeśli bardzo krótko potem) nie działa.
Nick Gammon
14 cykli zegara jest resetowanych. Potrzebujesz tylko 6 cykli po wyłączeniu zasilania, jeśli używasz wewnętrznego oscylatora RC. Zobacz dodatkowy przykładowy kod.
Nick Gammon