ใส่ ATmega328 ในโหมดสลีปลึกมากและฟังซีเรียล


13

ฉันได้ตรวจสอบตัวเลือก sleeps ของ ATmega328 และอ่านบทความเกี่ยวกับเรื่องนี้และฉันอยากจะเข้าใจถ้ามีตัวเลือกเพิ่มเติม

ดังนั้นฉันต้องการได้รับกระแสที่ต่ำที่สุดเท่าที่จะเป็นไปได้เพื่อที่ว่าสิ่งใดก็ตามที่น้อยกว่านั้นจะดี 100uA - ตราบใดที่ฉันสามารถฟัง uart และขัดจังหวะเมื่อตื่นขึ้นมา

ฉันกำลังใช้ PCB แบบกำหนดเอง (ไม่ใช่ UNO) ด้วย ATmega328p

การตั้งค่าชิปเป็นการนอนหลับลึก:

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

จะไม่ตื่นขึ้นกับการสื่อสารแบบอนุกรมตามนี้

คุณจะต้องวางไว้ในIDLEโหมดเพื่อฟังซีเรียล แต่สิ่งนี้จะกิน mA -bad น้อย

ฉันได้พบลิงค์นี้ที่คุณสามารถเชื่อมต่อในฮาร์ดแวร์อนุกรมเพื่อขัดจังหวะ - ซึ่งเป็นอันตรายเพื่อให้คุณสามารถหลวมข้อมูลและยิ่งกว่านั้นฉันต้องการหมุดขัดจังหวะ 2 เหล่านี้

ฉันยังได้อ่านบทความของ Gammonซึ่งคุณสามารถปิดการใช้งานบางสิ่งได้ดังนั้นคุณจะได้รับ IDLE sleep ด้วยพลังงานที่ต่ำกว่ามาก - แต่เขาไม่ได้พูดถึงว่าคุณได้รับจากสิ่งนี้อย่างแท้จริง:

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

ดังนั้นบรรทัดล่างมีตัวเลือกใดออกไปรับน้อยกว่า 0.25mA อย่างน้อยและฟังพอร์ตอนุกรมโดยไม่มีการจัดการฮาร์ดแวร์ใด ๆ ตัวอย่างเช่นตื่นขึ้นมาพร้อมกับการป้อนข้อมูลแบบอนุกรมยาว ?


1
@ NickAlexeev นี่เป็นคำถาม ATmega328 ไม่ใช่ Arduino หนึ่งเนื่องจากเป็นข้อตกลงโดยตรงกับชิปที่ต่ำกว่าระดับของ Arduino หยุดด้วยการโยกย้ายที่ไม่เหมาะสมแล้ว!
Chris Stratton

1
แทบจะไม่ ต้องการปลุก Arduino จากการนอนหลับไม่สามารถไล่ออกได้เพราะมีชิป ATmega328 อยู่ในนั้น ในอัตรานี้คุณจะสามารถตีกลับคำถามทั้งหมดเกี่ยวกับ Arduinos กลับไปที่ไซต์ EE
Nick Gammon

คำตอบ:


11

คณะกรรมการที่เราทำทำสิ่งนี้

  • RX pin เชื่อมต่อกับ INT0 แล้ว
  • INT0 พินตั้งค่าเป็น pullup สำหรับอินพุตหรืออินพุทขึ้นอยู่กับว่าขับ RX line อย่างไร
  • ในโหมดสลีป 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สิ่งนี้ให้การป้องกันขั้นพื้นฐานต่อการสูญเสียข้อความเนื่องจากอักขระที่เข้ามาจะไม่ถูกประมวลผลจนกว่า>จะได้รับ ในการปลุกอุปกรณ์เราส่งข้อความจำลองก่อนส่ง ตัวละครตัวเดียวจะทำได้ แต่เราส่ง>wakeup\rฮิฮิ

อุปกรณ์จะยังคงทำงานเป็นเวลา 30 วินาทีหลังจากได้รับข้อความล่าสุดในกรณีที่มีข้อความใหม่ หากข้อความใหม่ได้รับการจับเวลา 30 วินาทีจะถูกรีเซ็ต ซอฟต์แวร์ PC interface ส่งข้อความดัมมี่ทุก ๆ วินาทีเพื่อให้อุปกรณ์ตื่นในขณะที่ผู้ใช้เชื่อมต่อเพื่อกำหนดค่า ฯลฯ

วิธีนี้ไม่มีปัญหาเลย บอร์ดที่มีอุปกรณ์ต่อพ่วงไม่กี่ใช้ประมาณ 40uA เมื่อนอนหลับ กระแสไฟฟ้าจริงที่ใช้โดย ATMega328P น่าจะอยู่ที่ประมาณ 4uA

ปรับปรุง

เมื่อดูที่แผ่นข้อมูลแสดงว่าหมุด RX เป็นพินเปลี่ยนหมุดขัดจังหวะ 16 (PCINT16)

ดังนั้นวิธีการอื่นที่ไม่มีสายอาจเป็น

  • ก่อนนอน: ตั้งค่าบิตมาสก์อินเตอร์รัปต์การเปลี่ยนพอร์ตใน PCMSK2 สำหรับ PCINT16 ให้ล้างค่าสถานะการเปลี่ยนพินพอร์ต 2 ใน PCIFR เปิดใช้งานการเปลี่ยนพินพอร์ตขัดจังหวะ 2 (PCINT16-PCINT23) โดยการตั้งค่า PCIE2 ใน PCICR

  • ติดตั้ง ISR สำหรับการเปลี่ยนพินพอร์ต 2 ขัดจังหวะและดำเนินการต่อเหมือนก่อนหน้า

ข้อแม้เท่านั้นที่มีการเปลี่ยนแปลงการขัดจังหวะพอร์ตคือการขัดจังหวะการใช้ร่วมกันใน 8 พินทั้งหมดที่เปิดใช้งานสำหรับพอร์ตนั้น ดังนั้นหากคุณเปิดใช้งานการเปลี่ยนพินมากกว่าหนึ่งครั้งสำหรับพอร์ตคุณจะต้องพิจารณาว่าจะให้การขัดจังหวะใดใน ISR นี่ไม่ใช่ปัญหาหากคุณไม่ได้ใช้การขัดจังหวะการเปลี่ยนพินอื่น ๆ บนพอร์ตนั้น (PCINT16-PCINT23 ในกรณีนี้)

โดยหลักการแล้วนี่คือวิธีที่ฉันจะออกแบบบอร์ดของเรา แต่สิ่งที่เราได้ทำงาน


ขอบคุณมาก ๆ . ไม่มีวิธีอื่นนอกจากเทคนิคฮาร์ดแวร์หรือไม่? ดังนั้นคุณเชื่อมต่อ rx กับ int0 / int1 ด้วย 1 บรรทัดเท่านั้น?
Curnelious

1
อันที่จริงฉันเพิ่งดูที่แผ่นข้อมูลและคุณอาจจะสามารถใช้การเปลี่ยนแปลงการขัดจังหวะพิน
geometrikal

ขอบคุณสิ่งที่จะแตกต่างกันอย่างไร ยังไงฉันก็ต้องตื่นขึ้นมาด้วย rx ใน int1?
Curnelious

คุณต้องการเพียงหนึ่งอินเทอรัลพิน ฉันโพสต์บางส่วนไว้ด้านบน - คุณสามารถใช้ RX pin เป็นหมุดขัดจังหวะการเปลี่ยน ฉันยังไม่ได้ทำสิ่งนี้แม้ว่าจะมีการจับไม่กี่ครั้งบางทีคุณอาจต้องปิดการใช้งาน RX / เปิดใช้งานการเปลี่ยนพินก่อนนอนและปิดการใช้งานการเปลี่ยนพิน / เปิดใช้งาน RX หลังจากตื่นขึ้นมา
geometrikal

ขอบคุณฉันไม่แน่ใจว่าทำไมควรมีปัญหากับการเชื่อมต่อ rx กับ INT1 ตั้งค่าให้ขัดจังหวะสูงกว่าปิดการใช้งานอินเตอร์รัปต์เมื่อเกิด int1 ขึ้นและเปิดใช้งานอีกครั้งเมื่อเข้าสู่โหมดสลีป
Curnelious

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 oscillator ภายใน
    • BOD ถูกปิดใช้งาน
    • ฟิวส์คือ: ต่ำ: 0xD2, สูง: 0xDF, ขยาย: 0xFF

ได้รับแรงบันดาลใจจาก FarO ในความคิดเห็นสิ่งนี้ทำให้โปรเซสเซอร์ตื่นขึ้นในรอบ 6 นาฬิกา (750 ns) ที่ 9600 baud ในแต่ละ bit เวลาเท่ากับ 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

ฉันอ่านแผ่นข้อมูลและระบุว่าเมื่อใช้ oscillator ภายในต้องใช้วงจรนาฬิกาเพียง 14 รอบในการเริ่มต้นชิปโดยที่ BOD ถูกใช้งาน หากแหล่งพลังงานอยู่เสมอ (แบตเตอรี่) สามารถใช้ BOD ได้หรือไม่? ละเมิดรายละเอียดแน่นอน นั่นจะนำชิปขึ้นมาไม่นานหลังจากที่ขอบ UART ที่เข้ามา แต่ฉันก็ยังไม่แน่ใจว่ามันจะเพียงพอที่จะรับไบต์แรก
FarO

ใช่ 14 รอบนาฬิกาไม่นานอย่างไรก็ตามอาจเป็นไปได้ว่า UART จะพลาดขอบ (หลังจากทั้งหมดขอบคือเมื่อหน่วยประมวลผลสังเกตเห็นการเปลี่ยนแปลง) ดังนั้นแม้ว่ามันจะเริ่มต้นขึ้นในไม่ช้าหลังจากขอบมันก็ยังอาจพลาดได้
Nick Gammon

การทดสอบเล็กน้อยบ่งชี้ว่า (แม้เปิดใช้ BOD) มันไม่ทำงาน โปรเซสเซอร์ต้องตื่นตัวเพื่อสังเกตขอบนำ (เริ่มต้นบิต) และการเปิดเครื่องขึ้นหลังจากได้รับ (แม้ว่าหลังจากนั้นไม่นาน) จะไม่ทำงาน
Nick Gammon

รอบนาฬิกา 14 รอบหลังจากรีเซ็ต คุณต้องการเพียง 6 รอบหลังจากปิดเครื่องหากคุณใช้ RC oscillator ภายใน ดูรหัสตัวอย่างเพิ่มเติม
Nick Gammon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.