ATmega328を非常に深いスリープ状態にして、シリアルを聞きますか?


13

ATmega328のスリープオプションを調査し、それに関するいくつかの記事を読みましたが、さらにオプションがあるかどうかを理解したいと思います。

したがって、可能な限り低い電流を取得して、100uA未満であれば良いと思います-目覚めのためにuartと割り込みを聞くことができる限り。

ATmega328pで、カスタムPCB(UNOではない)を使用しています。

チップをディープスリープに設定する:

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

これによると、シリアル通信で起動することはありません。

IDLEシリアルを聞くには、モードにする必要がありますが、これは数mAを消費します。

ハードウェアでシリアルに割り込みを接続できるこのリンクを見つけました-これはデータを失う可能性があるため危険であり、さらにこれらの2つの割り込みピンが必要です。

また、Gammonのこの記事を読んで、いくつかの機能を無効にできるため、より低い電力でIDLEスリープを取得できます。

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

結論として、少なくとも0.25mA未満を取得し、ハードウェアを操作せずにシリアルポートをリッスンするオプションはありますか?たとえば、長いシリアルデータ入力で目覚めますか?


1
@NickAlexeev は、Arduinoのレベルよりはるかに低いチップを直接扱うため、Arduinoの質問ではなく ATmega328の質問です。不適切な移行は既にやめてください!
クリスストラットン

1
ほとんどない。Arduinoをスリープ状態から解除したいのは、ATmega328チップが搭載されているため、実際に却下することはできません。そのレートでは、Arduinosに関するすべての質問をEEサイトに戻すことができます。
ニックギャモン

回答:


11

私たちが作るボードはこれを行います。

  • RXピンはINT0に配線されています
  • RXラインの駆動方法に応じて、入力または入力プルアップに設定されたINT0ピン
  • スリープ時には、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);
    
  • INT0割り込みサービスルーチンはフラグを設定し、割り込みを無効にします

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • ウェイクアップ時に、フラグを確認します(他の割り込みソースがあります)

物事の通信側では、開始文字>と終了文字を持つメッセージプロトコルを使用します\r。例えば>setrtc,2015,07,05,20,58,09\r。これにより、着信文字はa >が受信されるまで処理されないため、メッセージの損失に対する基本的な保護が提供されます。デバイスを起動するには、送信前にダミーメッセージを送信します。1人のキャラクターがそれを行いますが、私たちは>wakeup\rhehe を送信します。

デバイスは、新しいメッセージの場合に最後のメッセージが受信された後、30秒間アウェイク状態を維持します。新しいメッセージが受信されると、30秒のタイマーがリセットされます。PCインターフェイスソフトウェアは、ユーザーが構成などのために接続している間、デバイスを起動状態に保つために、ダミーメッセージを毎秒送信します。

この方法ではまったく問題はありません。いくつかの周辺機器を備えたボードは、スリープ時に約40uAを使用します。ATMega328Pが実際に消費する電流は、おそらく約4uAです。

更新

データシートを見ると、RXピンもピン変更割り込みピン16(PCINT16)であることがわかります。

したがって、ワイヤなしの別の方法は

  • スリープ前:PCINT16のPCMSK2でポート変更割り込みマスクビットを設定し、PCIFRでピン変更ポート2フラグをクリアし、PCICRでPCIE2を設定してピン変更ポート2割り込み(PCINT16-PCINT23)を有効にします。

  • ピン変更ポート2割り込み用のISRをセットアップし、以前のように続行します。

ポート変更割り込みの唯一の注意点は、そのポートで有効になっている8つのピンすべてで割り込みが共有されることです。そのため、ポートで複数のピン変更を有効にしている場合、ISRで割り込みをトリガーしたものを特定する必要があります。そのポート(この場合はPCINT16-PCINT23)で他のピン変更割り込みを使用していない場合、これは問題ではありません。

理想的には、これがボードの設計方法ですが、機能するものです。


どうもありがとう 。ハードウェアのトリック以外の方法はありませんか???したがって、rxをint0 / int1に接続するのは1行だけですか??
15

1
実は私は、データシートを見ていた、あなたはピン変化割り込みを使用することができるかもしれない
geometrikal

ありがとう、違いは何ですか?とにかくint1でrxで目を覚ます必要がありますか?
2015

必要な割り込みピンは1つだけです。上記にもう少し投稿しました-RXピンをピン変更割り込みとして使用できます。非常に少ない漁獲量は多分あなたは無効RXに/睡眠とディスエーブル・ピンの変更前のピン変化を可能にする必要がありますように/ウェイクアップ後にRXを有効があるかもしれませんけれども、私はこれを行っていない
geometrikal

おかげで、なぜINT1にrxを接続するだけで問題になるのかわかりません
15

8

以下のコードは、あなたが求めていることを達成します。

#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

Rxピンでピン変更割り込みを使用して、シリアルデータが到着したことを確認しました。このテストでは、5秒後にアクティビティがない場合、ボードはスリープ状態になります(「アウェイク」LEDが消灯します)。シリアルデータを受信すると、ピン変更割り込みによってボードがウェイクします。番号を探し、その回数だけ「緑色」のLEDを点滅させます。

測定電流

5 Vで動作し、スリープ状態で約120 nAの電流を測定しました(0.120 µA)。

覚醒メッセージ

ただし、問題は、シリアルハードウェアがRx(開始ビット)の立ち下がりレベルを予期しているという事実により、最初に到着したバイトが失われることです。

(geometrikalの答えのように)最初に「覚醒」メッセージを送信してから、少しの間休止することをお勧めします。一時停止は、ハードウェアが次のバイトをアウェイクメッセージの一部として解釈しないようにすることです。その後、正常に動作するはずです。


これはピン変更割り込みを使用するため、他のハードウェアは必要ありません。


SoftwareSerialを使用した修正版

以下のバージョンは、シリアルで受信した最初のバイトを正常に処理します。これを行うには:

  • ピン変更割り込みを使用するSoftwareSerialを使用します。最初のシリアルバイトの開始ビットによって引き起こされる割り込みも、プロセッサを呼び起こします。

  • 使用するようにヒューズを設定する:

    • 内部RCオシレーター
    • BOD無効
    • ヒューズは、低:0xD2、高:0xDF、拡張:0xFFです。

コメントでFarOに触発され、これにより、プロセッサは6クロックサイクル(750 ns)で起動します。9600ボーでは、各ビット時間は1/9600(104.2 µs)であるため、余分な遅延はわずかです。

#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

スリープ時の消費電力は260 nA(0.260 µA)と測定されたため、不要な場合の消費電力は非常に低くなります。

ヒューズをそのように設定すると、プロセッサは8 MHzで動作することに注意してください。したがって、IDEにそのことを伝える必要があります(たとえば、ボードタイプとして「Lilypad」を選択します)。そうすれば、遅延とSoftwareSerialは正しい速度で動作します。


@NickGammonどうもありがとう!私はすでにそれをやった、それは働いた。その方法は、私たちが毎日使用する他の製品で一般的ですか、それとも通信を聞いて眠る他の方法がありますか?(すべてのMCUはディープスリープでuartを聞くことができませんか?)
Curnelious

データシートを読んでいたところ、BODが使用されていれば、内部発振器を使用する場合、チップを起動するのに必要なクロックサイクルは14クロックだけです。電源が常にアップしている場合(バッテリー)、BODなしでも使用できますか?もちろん仕様に違反しています。これにより、着信UARTエッジの直後にチップが起動しますが、それでも最初のバイトをキャッチするのに十分かどうかはわかりません。
-FarO

はい、14クロックサイクルは長くはありませんが、おそらくUARTはエッジを逃す可能性があります(結局、エッジはプロセッサが変更を認識したときです)。そのため、エッジのすぐ後に起動したとしても、それを見逃す可能性があります。
ニックギャモン

少しのテストでは、(BODが有効になっていても)動作しないことが示されています。プロセッサは、リーディングエッジ(開始ビット)に気付くために起動する必要があるため、受信後(非常に短い時間であっても)動作しません。
ニックギャモン

14クロックサイクルはリセット後です。内部RCオシレーターを使用する場合、パワーダウン後に必要なのは6サイクルのみです。追加のサンプルコードを参照してください。
ニックギャモン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.