ATmega328'i çok derin uykuya koyun ve diziyi dinleyin?


13

ATmega328'in uyku seçeneklerini araştırdım ve bununla ilgili birkaç makale okudum ve daha fazla seçenek olup olmadığını anlamak istiyorum.

Bu yüzden olabildiğince düşük akım elde etmek istiyorum, böylece 100uA'dan daha az bir şey iyi olurdu - uart'ı dinleyebildiğim ve uyanmak için kesebildiğim sürece.

ATmega328p ile özel bir PCB (UNO değil) kullanıyorum.

Çipin derin uykuya ayarlanması:

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

göre, seri haberleşme ile uyanmak olmaz bu .

Sen koymak gerekecektir IDLEseri dinlemek, moda, ama bu bir kaç mA -Kötü tüketmek olacaktır.

Seriyi kesmeye seri olarak bağlayabileceğiniz bu bağlantıyı buldum - bu da verileri kaybedebilmeniz için tehlikeli ve dahası, bu 2 kesme pimine ihtiyacım var.

Ayrıca bazı şeyleri devre dışı bırakabileceğiniz Gammon'un bu makalesini de okudum , böylece çok daha düşük bir güçle IDLE uyku alabilirsiniz - ama bundan tam olarak nasıl aldığınızdan bahsetmedi:

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

Yani, en alt satırda, en az 0.25mA'dan daha az almak ve ayrıca herhangi bir donanım manipülasyonu olmadan seri portu dinlemek için herhangi bir seçenek var mı? Örneğin, uzun seri veri girişi ile uyanıyor musunuz?


1
@NickAlexeev bu bir ATmega328 sorusu Arduino değil, doğrudan Arduino seviyesinin çok altındaki çip ile ilgilidir. Zaten uygun olmayan taşıma işlemlerine son verin!
Chris Stratton

1
Zorlukla. Bir Arduino'yu uykudan uyandırmak isteyen bir şey attırılamaz çünkü içinde bir ATmega328 yongası vardır. Bu hızda Arduinos ile ilgili tüm soruları Enerji Verimliliği sitesine geri gönderebileceksiniz.
Nick Gammon

Yanıtlar:


11

Yaptığımız bir tahta bunu yapıyor.

  • RX pimi INT0'a bağlanmıştır
  • INT0 pimi, RX hattının nasıl kullanıldığına bağlı olarak giriş veya giriş çekme ayarlı
  • Uyku durumunda, INT0 düşük seviye kesme etkin

    //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 interrupt servisi rutini bir bayrak belirler ve interrupt'ı devre dışı bırakır

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Uyandığında, bayrağı kontrol ediyoruz (başka kesinti kaynakları var)

İşlerin iletişim tarafında başlangıç >ve bitiş karakterleri olan bir mesaj protokolü kullanıyoruz \r. örn >setrtc,2015,07,05,20,58,09\r. Bu, gelen karakterler a alınana kadar işlenmediği için iletileri kaybetmeye karşı bazı temel koruma sağlar >. Cihazı uyandırmak için iletimden önce sahte bir mesaj göndeririz. Tek bir karakter yapardı ama biz >wakeup\rhehe gönderiyoruz .

Yeni mesajlarda son mesaj alındıktan sonra cihaz 30 saniye uyanık kalır. Yeni bir mesaj alınırsa 30 saniyelik zamanlayıcı sıfırlanır. PC arayüz yazılımı, kullanıcı yapılandırma vb. İçin bağlanırken cihazı uyanık tutmak için her saniye sahte bir mesaj gönderir.

Bu yöntem kesinlikle hiç problem vermiyor. Birkaç çevre birimine sahip kart, uyurken yaklaşık 40uA kullanır. ATMega328P tarafından tüketilen gerçek akım muhtemelen 4uA civarındadır.

Güncelleme

Veri sayfasına bakıldığında, RX pininin de pin değiştirme kesme pimi 16 (PCINT16) olduğunu gösterir.

Böylece telsiz başka bir yöntem olabilir.

  • Uykudan önce: PCINT16 için PCMSK2'de bağlantı noktası değiştirme kesme maskesi bitini ayarlayın, PCIFR'deki pim değiştirme bağlantı noktası 2 bayrağını temizleyin, PCICR'de PCIE2'yi ayarlayarak pim değiştirme bağlantı noktası 2 kesintisini (PCINT16-PCINT23) etkinleştirin.

  • Pim değiştirme bağlantı noktası 2 kesmesi için bir ISR ayarlayın ve önceki gibi devam edin.

Bağlantı noktası değiştirme kesmesi ile ilgili tek uyarı, kesmenin o bağlantı noktası için etkinleştirilmiş olan 8 pimin tamamında paylaşılmasıdır. Bu nedenle, bağlantı noktası için birden fazla pim değişikliği etkinleştirilmişse, ISR'de kesmeyi hangi tetiklemenin tetiklediğini belirlemeniz gerekir. Bu bağlantı noktasında başka bir pin değiştirme kesintisi kullanmıyorsanız bu sorun olmaz (bu durumda PCINT16-PCINT23)

İdeal olarak tahtamızı böyle tasarlardım ama elimizde ne var.


Çok teşekkürler . Donanım hilelerinden başka bir yol yok mu ??? Yani sadece rx 1 satır ile int0 / int1 bağlanmak ??
Curnelious

1
Aslında veri sayfasına bir göz attım ve bir pin değiştirme kesintisi kullanabilirsiniz
geometrikal

Teşekkürler, ne farklı olurdu? Neyse ben int1 rx ile uyanmak zorunda kalacak?
Curnelious

Sadece 1 kesme pimine ihtiyacınız var. Yukarıda biraz daha yayınladım - RX pinini pin değiştirme kesmesi olarak kullanabilirsiniz. Gerçi bu nedenle birkaç yakalar belki / devre dışı RX zorunda uyku ve devre dışı bırakma pimi değişikliğinden önce pim değişikliğini sağlayacak gibi olabileceğini yapmadıysanız / Uyanırken sonra etkinleştir RX
geometrikal

teşekkürler, neden sadece INT1 için rx bağlanma, int1 olduğunda kesmeleri devre dışı bırakmak daha yüksek, kesme yüksek ayarlamak ve uyku gittiklerinde onları yeniden etkinleştirmek için bir sorun olmalı emin değilim?
Curnelious

8

Aşağıdaki kod sorduğunuz şeyi sağlar:

#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

Seri veri geldiğinde fark etmek için Rx pininde bir pin değiştirme kesintisi kullandım. Bu testte, 5 saniye sonra herhangi bir işlem yapılmazsa ("uyanık" LED'i söner) kart uyku moduna geçer. Gelen seri veriler, pin değiştirme kesintisinin kartı uyandırmasına neden olur. Bir sayı arar ve "yeşil" LED'i bu sayıda yanıp söner.

Ölçülen akım

5 V'da çalışırken, uyurken yaklaşık 120 nA akım ölçtüm (0,120 µA).

Uyanış mesajı

Ancak bir sorun, ilk gelen baytın, seri donanımın, tamamen uyanık olduğu zamana kadar gelmiş olan Rx (başlangıç ​​biti) üzerinde düşme seviyesi beklemesi nedeniyle kaybolmasıdır.

(Geometrikal'ın cevabında olduğu gibi) önce bir "uyanık" mesajı göndermenizi ve sonra kısa bir süre duraklamanızı öneririm . Duraklatma, donanımın bir sonraki baytı uyanık mesajın bir parçası olarak yorumlamadığından emin olmaktır. Bundan sonra iyi çalışmalıdır.


Bu bir pin değiştirme kesintisi kullandığından başka bir donanım gerekmez.


SoftwareSerial kullanılarak değiştirilmiş sürüm

Aşağıdaki sürüm seri olarak alınan ilk baytı başarıyla işler. Bunu şu şekilde yapar:

  • Pin değiştirme kesintileri kullanan SoftwareSerial kullanma. İlk seri baytın başlangıç ​​bitinin neden olduğu kesinti ayrıca işlemciyi uyandırır.

  • Sigortaları kullanacağımız şekilde ayarlama:

    • Dahili RC osilatörü
    • BOİ devre dışı
    • Sigortalar: Düşük: 0xD2, Yüksek: 0xDF, Genişletilmiş: 0xFF

FarO'dan bir yorumda esinlenerek, işlemcinin 6 saat döngüsünde (750 ns) uyanmasını sağlar. 9600 baud'da her bit süresi 1/9600'dür (104,2 µs), bu nedenle ekstra gecikme önemsizdir.

#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

Uykudayken güç tüketimi 260 nA (0.260 µA) olarak ölçüldü, bu yüzden ihtiyaç olmadığında çok düşük tüketimdir.

Sigortalar bu şekilde ayarlandığında, işlemcinin 8 MHz'de çalıştığını unutmayın. Bu nedenle IDE'ye bundan bahsetmeniz gerekir (örn. Tahta tipi olarak "Lilypad" i seçin). Bu şekilde gecikmeler ve SoftwareSerial doğru hızda çalışacaktır.


@ NickGammon çok teşekkürler! zaten yaptım ve işe yaradı. Bu, her gün kullandığımız diğer ürünlerde yaygın mı yoksa iletişim ve uyku dinlemenin başka yolları mı var? (tüm
MCU'lar

Veri sayfasını okuyordum ve dahili osilatör kullanılırken, BOİ'nin kullanılması şartıyla, çipi başlatmak için sadece 14 saat döngüsünün gerekli olduğunu belirtir. Güç kaynağı her zaman açıksa (piller), bu BOD olmadan da kullanılabilir mi? tabii ki özellikleri ihlal. Bu, çipin gelen UART kenarından çok kısa bir süre sonra ortaya çıkardı, ancak yine de ilk baytı yakalamanın yeterli olacağından emin değilim.
FarO

Evet, 14 saat döngüsü uzun değildir, ancak UART yine de kenarı özleyecektir (sonuçta, işlemci değişikliği fark ettiğinde kenardır). Bu yüzden kenardan çok kısa bir süre sonra başlasa bile hala özleyebilir.
Nick Gammon

Bir miktar test (BOD etkinken bile) çalışmadığını gösterir. İşlemcinin ön kenarı (başlangıç ​​biti) fark etmek için uyanık olması ve böylece aldıktan sonra (çok kısa bir süre sonra bile olsa) açılmaması gerekir.
Nick Gammon

14 saat çevrimi sıfırlandıktan sonra yapılır. Dahili RC osilatörünü kullanıyorsanız, gücü kapattıktan sonra sadece 6 döngüye ihtiyacınız vardır. Ek örnek koda bakınız.
Nick Gammon
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.