Đặt ATmega328 vào giấc ngủ rất sâu và nghe nối tiếp?


13

Tôi đã điều tra các tùy chọn ngủ của ATmega328, và đọc một vài bài viết về nó, và tôi muốn hiểu nếu có nhiều lựa chọn hơn.

Vì vậy, tôi muốn có được dòng điện càng thấp càng tốt, để bất cứ thứ gì ít hơn 100uA đều tốt - miễn là tôi có thể nghe uart và ngắt lời khi thức dậy.

Tôi đang sử dụng PCB tùy chỉnh (không phải UNO), với ATmega328p.

Đặt chip vào chế độ ngủ sâu:

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

sẽ không đánh thức nó với giao tiếp nối tiếp, theo điều này .

Bạn sẽ cần đặt nó ở IDLEchế độ, để nghe nối tiếp, nhưng điều này sẽ tiêu tốn một vài mA -bad.

Tôi đã tìm thấy liên kết này nơi bạn có thể kết nối phần cứng nối tiếp với ngắt - rất nguy hiểm để bạn có thể mất dữ liệu, và hơn nữa, tôi cần 2 chân ngắt này.

Tôi cũng đã đọc bài viết này của Gammon , nơi bạn có thể vô hiệu hóa một số thứ, do đó bạn có thể có được giấc ngủ IDLE với sức mạnh thấp hơn nhiều - nhưng anh ấy đã không đề cập chính xác bạn nhận được từ điều này như thế nào:

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

Vì vậy, điểm mấu chốt, có bất kỳ tùy chọn nào ngoài đó, để có ít nhất 0,25mA, và cũng nghe cổng nối tiếp, mà không cần bất kỳ thao tác phần cứng nào không? Ví dụ, thức dậy với đầu vào dữ liệu nối tiếp dài ?


1
@NickAlexeev đây là một câu hỏi ATmega328 không phải là Arduino vì nó liên quan trực tiếp đến chip thấp hơn nhiều so với mức của Arduino. Dừng lại với việc di chuyển không đúng cách rồi!
Chris Stratton

1
Khó khăn. Muốn đánh thức một Arduino khỏi giấc ngủ thực sự không thể bị loại bỏ vì nó có chip ATmega328 trong đó. Với tốc độ đó, bạn sẽ có thể đưa tất cả câu hỏi về Arduinos trở lại trang EE.
Nick Gammon

Câu trả lời:


11

Một hội đồng chúng tôi làm điều này.

  • Chân RX được nối với INT0
  • Chân INT0 được đặt thành pullup đầu vào hoặc đầu vào tùy thuộc vào cách điều khiển dòng RX
  • Khi ngủ, ngắt mức thấp INT0 được bật

    //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);
    
  • Thường trình dịch vụ ngắt INT0 đặt cờ và vô hiệu hóa ngắt

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Khi thức dậy, chúng tôi kiểm tra cờ (có các nguồn ngắt khác)

Về phía comms của những thứ chúng ta sử dụng một giao thức tin nhắn có ký tự bắt đầu >và ký tự kết thúc \r. ví dụ >setrtc,2015,07,05,20,58,09\r. Điều này cung cấp một số bảo vệ cơ bản chống mất tin nhắn, vì các ký tự đến không được xử lý cho đến khi >nhận được một. Để đánh thức thiết bị, chúng tôi gửi một tin nhắn giả trước khi truyền. Một nhân vật sẽ làm điều đó, nhưng chúng tôi gửi >wakeup\rhehe.

Thiết bị vẫn hoạt động trong 30 giây sau khi nhận được tin nhắn cuối cùng trong trường hợp có tin nhắn mới. Nếu nhận được tin nhắn mới, bộ hẹn giờ 30 giây sẽ được đặt lại. Phần mềm giao diện PC gửi tin nhắn giả mỗi giây để giữ cho thiết bị luôn hoạt động trong khi người dùng đã kết nối để cấu hình, v.v.

Phương pháp này hoàn toàn không có vấn đề gì. Bảng với một vài thiết bị ngoại vi sử dụng khoảng 40uA khi ngủ. Dòng điện thực tế mà ATMega328P tiêu thụ có lẽ khoảng 4uA.

Cập nhật

Nhìn vào biểu dữ liệu cho thấy chân RX cũng là chân ngắt thay đổi chân 16 (PCINT16)

Do đó, một phương pháp khác không có dây có thể là

  • Trước khi ngủ: Đặt bit mặt nạ ngắt thay đổi cổng trong PCMSK2 cho PCINT16, xóa cờ cổng thay đổi pin 2 trong PCIFR, bật ngắt cổng thay đổi pin 2 (PCINT16-PCINT23) bằng cách đặt PCIE2 trong PCICR.

  • Thiết lập ISR cho cổng 2 thay đổi pin và tiếp tục như trước.

Nhắc nhở duy nhất với ngắt thay đổi cổng là ngắt được chia sẻ trên tất cả 8 chân được bật cho cổng đó. Vì vậy, nếu bạn có nhiều thay đổi pin được kích hoạt cho cổng, bạn phải xác định xem đã kích hoạt ngắt trong ISR. Đây không phải là vấn đề nếu bạn không sử dụng bất kỳ ngắt thay đổi pin nào khác trên cổng đó (PCINT16-PCINT23 trong trường hợp này)

Lý tưởng nhất là cách tôi sẽ thiết kế bảng của chúng tôi nhưng những gì chúng tôi đã làm việc.


Cảm ơn rất nhiều . Không có cách nào khác ngoài thủ thuật phần cứng ??? Vì vậy, bạn chỉ kết nối rx với int0 / int1 với 1 dòng ??
Curnelious

1
Trên thực tế tôi chỉ cần nhìn vào bảng dữ liệu và bạn có thể có thể sử dụng ngắt thay đổi pin
geometrikal

Cảm ơn, điều gì sẽ khác? Dù sao tôi sẽ phải thức dậy với rx trên int1?
Curnelious

Bạn chỉ cần 1 pin ngắt. Tôi đã đăng thêm một số thông tin ở trên - bạn có thể sử dụng chân RX làm ngắt thay đổi pin. Tôi đã không làm điều này mặc dù có thể có một vài lưu ý như có thể bạn sẽ phải tắt RX / kích hoạt thay đổi pin trước khi ngủ và vô hiệu hóa thay đổi pin / bật RX sau khi thức dậy
geometrikal

cảm ơn, tôi không chắc tại sao lại gặp sự cố khi chỉ kết nối rx với INT1, đặt ngắt ở mức cao, hơn là tắt ngắt khi int1 xảy ra và bật lại chúng khi đi ngủ?
Curnelious

8

Mã dưới đây đạt được những gì bạn đang yêu cầu:

#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

Tôi đã sử dụng ngắt thay đổi pin trên chân Rx để thông báo khi dữ liệu nối tiếp đến. Trong thử nghiệm này, bo mạch sẽ chuyển sang chế độ ngủ nếu không có hoạt động nào sau 5 giây (đèn LED "tỉnh táo" tắt). Dữ liệu nối tiếp đến làm gián đoạn thay đổi pin để đánh thức bảng. Nó tìm kiếm một số và nhấp nháy đèn LED "xanh" số lần đó.

Đo hiện tại

Chạy ở 5 V, tôi đo được khoảng 120 nA dòng điện khi ngủ (0.120 MartA).

Tin nhắn thức tỉnh

Tuy nhiên, có một vấn đề là byte đến đầu tiên bị mất do phần cứng nối tiếp mong đợi mức giảm trên Rx (bit start) đã đến khi nó hoàn toàn tỉnh táo.

Tôi đề nghị (như trong câu trả lời của geometrikal) trước tiên bạn gửi tin nhắn "tỉnh táo", sau đó tạm dừng trong một thời gian ngắn. Việc tạm dừng là để đảm bảo phần cứng không diễn giải byte tiếp theo như một phần của thông báo tỉnh táo. Sau đó nó sẽ hoạt động tốt.


Vì điều này sử dụng ngắt thay đổi pin, không yêu cầu phần cứng nào khác.


Phiên bản sửa đổi bằng SoftwareSerial

Phiên bản dưới đây xử lý thành công byte đầu tiên nhận được trên serial. Nó thực hiện điều này bằng cách:

  • Sử dụng SoftwareSerial sử dụng các ngắt thay đổi pin. Ngắt gây ra bởi bit bắt đầu của byte nối tiếp đầu tiên cũng đánh thức bộ xử lý.

  • Đặt cầu chì để chúng tôi sử dụng:

    • Bộ dao động RC bên trong
    • HĐQT bị vô hiệu hóa
    • Các cầu chì là: Thấp: 0xD2, Cao: 0xDF, Mở rộng: 0xFF

Lấy cảm hứng từ FarO trong một bình luận, điều này cho phép bộ xử lý thức dậy trong 6 chu kỳ đồng hồ (750 ns). Với tốc độ 9600 baud mỗi bit thời gian là 1/9600 (104,2 Pha) nên độ trễ thêm không đáng kể.

#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

Tiêu thụ điện năng khi ngủ được đo là 260 nA (0,260, một) vì vậy mức tiêu thụ rất thấp khi không cần thiết.

Lưu ý rằng với các cầu chì được đặt như vậy, bộ xử lý chạy ở tốc độ 8 MHz. Do đó, bạn cần nói với IDE về điều đó (ví dụ: chọn "Lilypad" làm loại bảng). Bằng cách đó, độ trễ và SoftwareSerial sẽ hoạt động với tốc độ chính xác.


@NickGammon cảm ơn rất nhiều! tôi đã làm nó và nó đã làm việc Là cách đó là phổ biến trong các sản phẩm khác mà chúng ta sử dụng hàng ngày, hoặc họ có những cách khác để nghe comm và ngủ? (tất cả MCU không thể nghe uart vào giấc ngủ sâu?)
Curnelious

Tôi đã đọc biểu dữ liệu và thông báo rằng khi sử dụng bộ tạo dao động nội, chỉ cần 14 chu kỳ xung nhịp để khởi động chip, với điều kiện là BOD được sử dụng. Nếu nguồn điện luôn hết (pin), có thể sử dụng mà không cần BOD? vi phạm các thông số kỹ thuật tất nhiên. Điều đó sẽ đưa chip lên rất nhanh sau khi cạnh UART đến, nhưng tôi vẫn không chắc nó sẽ đủ để bắt được byte đầu tiên.
FarO

Có, 14 chu kỳ xung nhịp không dài, tuy nhiên có thể UART vẫn sẽ bỏ lỡ cạnh (sau tất cả, cạnh là khi bộ xử lý nhận thấy sự thay đổi). Vì vậy, ngay cả khi nó khởi động rất sớm sau khi ra rìa, nó vẫn có thể bỏ lỡ nó.
Nick Gammon

Một chút kiểm tra chỉ ra rằng (ngay cả khi bật BOD), nó không hoạt động. Bộ xử lý cần phải tỉnh táo để nhận thấy cạnh đầu (bit start) và do đó cấp nguồn cho nó sau khi nhận được nó (ngay cả khi rất lâu sau đó) không hoạt động.
Nick Gammon

14 chu kỳ đồng hồ là sau khi thiết lập lại. Bạn chỉ cần 6 chu kỳ sau khi tắt nguồn, nếu bạn sử dụng bộ dao động RC bên trong. Xem mã ví dụ bổ sung.
Nick Gammon
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.