Masukkan ATmega328 dalam tidur sangat nyenyak dan dengarkan serial?

13

Saya telah menyelidiki opsi tidur ATmega328, dan membaca beberapa artikel tentang itu, dan saya ingin memahami jika ada lebih banyak pilihan.

Jadi saya ingin mendapatkan arus serendah mungkin, sehingga apa pun yang kurang dari 100uA akan bagus - selama saya bisa mendengarkan uart dan menyela untuk bangun.

Saya menggunakan PCB khusus (bukan UNO), dengan ATmega328p.

Pengaturan chip untuk tidur nyenyak:

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

tidak akan bangun dengan komunikasi serial, menurut ini .

Anda harus memasukkannya ke IDLEmode, untuk mendengarkan serial, tetapi ini akan menghabiskan beberapa mA -bad.

Saya telah menemukan tautan ini di mana Anda dapat menyambungkan perangkat keras serial ke interupsi - yang berbahaya sehingga Anda dapat kehilangan data, dan terlebih lagi, saya memerlukan 2 pin interupsi ini.

Saya juga membaca artikel Gammon ini , di mana Anda dapat menonaktifkan beberapa hal, sehingga Anda dapat tidur IDLE dengan daya yang jauh lebih rendah - tetapi dia tidak menyebutkan bagaimana sebenarnya Anda dapatkan dari ini:

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

Jadi, intinya, apakah ada opsi di luar sana, untuk mendapatkan setidaknya kurang dari 0,25mA, dan juga mendengarkan port serial, tanpa manipulasi perangkat keras? Misalnya, bangun dengan input data serial yang panjang ?

Curnelious
sumber
1
@NickAlexeev ini adalah pertanyaan ATmega328 bukan Arduino karena berhubungan langsung dengan chip yang jauh di bawah level Arduino. Hentikan migrasi yang tidak tepat!
Chris Stratton
1
Hampir tidak. Ingin membangunkan Arduino dari tidur tidak dapat benar-benar diabaikan karena memiliki chip ATmega328 di dalamnya. Pada tingkat itu Anda akan dapat memantulkan semua pertanyaan tentang Arduinos kembali ke situs EE.
Nick Gammon

Jawaban:

11

Papan yang kami buat melakukan ini.

  • Pin RX disambungkan ke INT0
  • Pin INT0 diatur ke input atau input pullup tergantung pada bagaimana jalur RX digerakkan
  • Saat tidur, INT0 level rendah diaktifkan

    //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 layanan interupsi rutin menetapkan bendera dan menonaktifkan interupsi

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Saat bangun, kami memeriksa benderanya (ada sumber interupsi lainnya)

Di sisi komunikasi, kami menggunakan protokol pesan yang memiliki karakter awal >dan karakter akhir \r. mis >setrtc,2015,07,05,20,58,09\r. Ini memberikan beberapa perlindungan dasar terhadap kehilangan pesan, karena karakter yang masuk tidak diproses sampai >diterima. Untuk membangunkan perangkat, kami mengirim pesan palsu sebelum pengiriman. Satu karakter akan melakukannya, tetapi kami mengirim >wakeup\rhehe.

Perangkat tetap terjaga selama 30 detik setelah pesan terakhir diterima jika ada pesan baru. Jika pesan baru diterima, penghitung waktu 30 detik akan direset. Perangkat lunak antarmuka PC mengirim pesan tiruan setiap detik untuk menjaga perangkat tetap aktif saat pengguna telah terhubung untuk konfigurasi dll.

Metode ini sama sekali tidak menimbulkan masalah sama sekali. Papan dengan beberapa periferal menggunakan sekitar 40uA saat tidur. Arus aktual yang dikonsumsi oleh ATMega328P mungkin sekitar 4uA.

Memperbarui

Saat melihat datasheet menunjukkan bahwa pin RX juga pin perubahan interrupt pin 16 (PCINT16)

Jadi metode lain tanpa kabel mungkin

  • Sebelum tidur: Tetapkan bit mask interupsi penggantian port di PCMSK2 untuk PCINT16, kosongkan flag port ganti pin 2 di PCIFR, aktifkan interupsi pin ganti port 2 (PCINT16-PCINT23) dengan mengatur PCIE2 di PCICR.

  • Setup ISR untuk pin ganti port 2 mengganggu dan lanjutkan seperti sebelumnya.

Satu-satunya peringatan dengan interupsi perubahan port adalah bahwa interupsi dibagi di semua 8 pin yang diaktifkan untuk port itu. Jadi, jika Anda memiliki lebih dari satu perubahan pin diaktifkan untuk port, Anda harus menentukan yang memicu interupsi di ISR. Ini bukan masalah jika Anda tidak menggunakan interupsi perubahan pin lain pada port tersebut (PCINT16-PCINT23 dalam kasus ini)

Idealnya ini adalah bagaimana saya akan mendesain papan kami tetapi apa yang kami punya bekerja.

geometrikal
sumber
Terima kasih banyak . Apakah tidak ada cara lain selain trik perangkat keras ??? Jadi Anda hanya menghubungkan rx ke int0 / int1 dengan 1 baris ??
Curnelious
1
Sebenarnya saya baru saja melihat lembar data dan Anda mungkin dapat menggunakan interupsi penggantian pin
geometrikal
Terima kasih, apa bedanya? Lagi pula saya harus bangun dengan rx di int1?
Curnelious
Anda hanya perlu 1 pin interupsi. Saya memposting beberapa lagi di atas - Anda dapat menggunakan pin RX sebagai interupsi perubahan pin. Saya belum melakukan ini jadi mungkin ada beberapa tangkapan seperti mungkin Anda harus menonaktifkan RX / mengaktifkan perubahan pin sebelum tidur dan menonaktifkan perubahan pin / mengaktifkan RX setelah bangun
geometrikal
terima kasih, saya tidak yakin mengapa harus menjadi masalah dengan hanya menghubungkan rx ke INT1, mengatur interupsi pada tinggi, daripada menonaktifkan interupsi ketika int1 terjadi, dan mengaktifkannya lagi ketika pergi tidur?
Curnelious
8

Kode di bawah ini mencapai apa yang Anda tanyakan:

#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

Saya menggunakan interupsi pin-perubahan pada pin Rx untuk melihat kapan data serial tiba. Dalam tes ini, papan akan tidur jika tidak ada aktivitas setelah 5 detik (LED "bangun" padam). Data serial yang masuk menyebabkan pin-change mengganggu untuk membangunkan board. Ini mencari nomor dan mem-flash LED "hijau" beberapa kali.

Diukur saat ini

Berjalan pada 5 V, saya mengukur sekitar 120 nA arus saat tertidur (0,120 μA).

Pesan kebangkitan

Namun masalah adalah bahwa byte pertama yang tiba hilang karena fakta bahwa perangkat keras serial mengharapkan tingkat penurunan pada Rx (bit mulai) yang telah tiba pada saat itu sepenuhnya terjaga.

Saya menyarankan (seperti dalam jawaban geometrikal) bahwa Anda pertama kali mengirim pesan "terjaga", dan kemudian berhenti sebentar. Jeda adalah untuk memastikan perangkat keras tidak mengartikan byte berikutnya sebagai bagian dari pesan terjaga. Setelah itu harus bekerja dengan baik.


Karena ini menggunakan interupsi pin-perubahan tidak diperlukan perangkat keras lain.


Versi yang diubah menggunakan SoftwareSerial

Versi di bawah ini berhasil memproses byte pertama yang diterima secara serial. Ini dilakukan dengan:

  • Menggunakan SoftwareSerial yang menggunakan interupsi pin-perubahan. Interupsi yang disebabkan oleh bit mulai dari byte seri pertama juga membangunkan prosesor.

  • Mengatur sekering sehingga kami gunakan:

    • Osilator RC internal
    • BOD dinonaktifkan
    • Sekeringnya adalah: Rendah: 0xD2, Tinggi: 0xDF, Diperpanjang: 0xFF

Terinspirasi oleh FarO dalam komentar, ini membuat prosesor bangun dalam 6 siklus clock (750 ns). Pada 9600 baud setiap bit adalah 1/9600 (104,2 µs) sehingga penundaan tambahan tidak signifikan.

#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

Konsumsi daya saat tidur diukur 260 nA (0,260 μA) sehingga konsumsi sangat rendah saat tidak diperlukan.

Perhatikan bahwa dengan sekering yang diatur seperti itu, prosesor berjalan pada 8 MHz. Jadi, Anda perlu memberi tahu IDE tentang itu (mis. Pilih "Lilypad" sebagai jenis papan). Dengan begitu penundaan dan SoftwareSerial akan bekerja pada kecepatan yang benar.

Nick Gammon
sumber
@NickGammon terima kasih banyak! saya sudah melakukannya dan berhasil. Apakah cara itu biasa dalam produk lain yang kita gunakan sehari-hari, atau mereka punya cara lain untuk mendengarkan komunikasi dan tidur? (semua MCU tidak bisa mendengarkan uart saat tidur nyenyak?)
Curnelious
Saya membaca datasheet dan menyatakan bahwa ketika menggunakan osilator internal, hanya 14 siklus clock diperlukan untuk memulai chip, asalkan BOD digunakan. Jika sumber listrik selalu menyala (baterai), yang dapat digunakan juga tanpa BOD? tentu saja melanggar spesifikasi. Itu akan membawa chip naik sesaat setelah tepi UART masuk, tapi tetap saya tidak yakin itu akan cukup untuk menangkap byte pertama.
FarO
Ya, 14 siklus clock tidak lama, namun kemungkinan UART masih akan melewatkan edge (lagipula, edge adalah ketika prosesor memperhatikan perubahan). Jadi, bahkan jika itu dimulai segera setelah tepi mungkin masih merindukannya.
Nick Gammon
Sedikit pengujian menunjukkan bahwa (bahkan dengan BOD diaktifkan) tidak berfungsi. Prosesor perlu sadar untuk melihat ujung depan (mulai bit) dan dengan demikian menyalakannya setelah menerimanya (meskipun sangat lama setelah itu) tidak berfungsi.
Nick Gammon
Siklus 14 jam setelah reset. Anda hanya perlu 6 siklus setelah power-down, jika Anda menggunakan osilator RC internal. Lihat contoh kode tambahan.
Nick Gammon