Sử dụng cơ quan giám sát AVR như ISR bình thường


17

Tôi đang cố gắng quấn đầu xung quanh đồng hồ bấm giờ trên dòng ATTinyX5. Vì vậy, những điều tôi đã đọc làm cho có vẻ như bạn có thể sử dụng nó để làm cho chương trình thực hiện một cái gì đó cụ thể bao giờ N giây nhưng không bao giờ thực sự cho thấy làm thế nào. Những người khác làm cho nó có vẻ như nó sẽ chỉ đặt lại chip trừ khi có gì đó trong mã đặt lại, nó được tính trong thời gian đó (dường như là cách sử dụng "bình thường").

Có cách nào sử dụng WDT như bạn sẽ TIMER1_COMPA_vect hoặc tương tự. Tôi nhận thấy rằng nó có chế độ hết thời gian 1 giây và tôi thực sự rất thích có thể sử dụng điều đó để khiến điều gì đó xảy ra cứ sau 1 giây trong mã của tôi (và tốt nhất là ngủ ở giữa).

Suy nghĩ?

* Cập nhật: * Kể từ khi được hỏi, những gì tôi đang đề cập đến là phần 8.4 của Bảng dữ liệu ATTinyX5 . Không phải tôi hoàn toàn hiểu nó, đó là vấn đề của tôi ...


1
+1 để suy nghĩ bên ngoài hộp. Thêm ngón tay cái lên nếu bạn thêm một liên kết đến biểu dữ liệu cho AVR.
jippie

Câu trả lời:


23

Bạn chắc chắn có thể. Theo biểu dữ liệu, bộ đếm thời gian theo dõi có thể được thiết lập để đặt lại MCU hoặc gây gián đoạn khi kích hoạt. Có vẻ như bạn quan tâm nhiều hơn đến khả năng ngắt.

WDT thực sự dễ cài đặt hơn Timer bình thường vì cùng lý do nó ít hữu ích hơn: ít tùy chọn hơn. Nó chạy trên đồng hồ 128kHz được hiệu chỉnh bên trong, có nghĩa là thời gian của nó không bị ảnh hưởng bởi tốc độ xung nhịp chính của MCU. Nó cũng có thể tiếp tục chạy trong các chế độ ngủ sâu nhất để cung cấp nguồn thức dậy.

Tôi sẽ xem qua một vài ví dụ về biểu dữ liệu cũng như một số mã tôi đã sử dụng (trong C).

Bao gồm các tệp và định nghĩa

Để bắt đầu, có lẽ bạn sẽ muốn bao gồm hai tệp tiêu đề sau để mọi thứ hoạt động:

#include <avr/wdt.h>        // Supplied Watch Dog Timer Macros 
#include <avr/sleep.h>      // Supplied AVR Sleep Macros

Ngoài ra, tôi sử dụng Macro <_BV (BIT)> được xác định trong một trong các tiêu đề AVR tiêu chuẩn như sau (có thể quen thuộc với bạn hơn):

#define _BV(BIT)   (1<<BIT)

Bắt đầu mã

Khi MCU được khởi động lần đầu tiên, bạn thường khởi tạo I / O, thiết lập bộ hẹn giờ, v.v ... Ở đây đây là thời điểm tốt để đảm bảo WDT không gây ra thiết lập lại vì nó có thể thực hiện lại, giữ chương trình của bạn một vòng lặp không ổn định.

if(MCUSR & _BV(WDRF)){            // If a reset was caused by the Watchdog Timer...
    MCUSR &= ~_BV(WDRF);                 // Clear the WDT reset flag
    WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
    WDTCSR = 0x00;                      // Disable the WDT
}

Thiết lập WDT

Sau đó, sau khi bạn đã thiết lập phần còn lại của chip, hãy làm lại WDT. Thiết lập WDT yêu cầu "trình tự thời gian", nhưng thực sự rất dễ thực hiện ...

// Set up Watch Dog Timer for Inactivity
WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
WDTCSR =   _BV(WDIE) |              // Enable WDT Interrupt
           _BV(WDP2) | _BV(WDP1);   // Set Timeout to ~1 seconds

Tất nhiên, các ngắt của bạn sẽ bị vô hiệu hóa trong mã này. Hãy chắc chắn để kích hoạt lại chúng sau đó!

cli();    // Disable the Interrupts
sei();    // Enable the Interrupts

Thường trình dịch vụ ngắt WDT Điều tiếp theo cần lo lắng là xử lý IST WDT. Điều này được thực hiện như vậy:

ISR(WDT_vect)
{
  sleep_disable();          // Disable Sleep on Wakeup
  // Your code goes here...
  // Whatever needs to happen every 1 second
  sleep_enable();           // Enable Sleep Mode
}

MCU ngủ

Thay vì đặt MCU để ngủ bên trong IST WDT, tôi khuyên bạn chỉ nên bật chế độ ngủ ở cuối ISR, sau đó yêu cầu chương trình MAIN đặt MCU vào chế độ ngủ. Theo cách đó, chương trình thực sự rời khỏi ISR ​​trước khi đi ngủ, và nó sẽ thức dậy và quay trở lại vào WDT ISR.

// Enable Sleep Mode for Power Down
set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Set Sleep Mode: Power Down
sleep_enable();                     // Enable Sleep Mode  
sei();                              // Enable Interrupts 

/****************************
 *  Enter Main Program Loop  *
 ****************************/
 for(;;)
 {
   if (MCUCR & _BV(SE)){    // If Sleep is Enabled...
     cli();                 // Disable Interrupts
     sleep_bod_disable();   // Disable BOD
     sei();                 // Enable Interrupts
     sleep_cpu();           // Go to Sleep

 /****************************
  *   Sleep Until WDT Times Out  
  *   -> Go to WDT ISR   
  ****************************/

   }
 }

WOW ... rất chi tiết. Cảm ơn! Tôi hơi bối rối bởi phần ngủ nơi bạn hiển thị vòng lặp chính (if (MCUCR & _BV (SE)) {// Nếu chế độ Ngủ được bật ... vv) Tôi bối rối tại sao trong giao diện chính, bạn sẽ liên tục vô hiệu hóa và kích hoạt ngắt. Và phần ở đầu phần đó (set_s ngủ_mode (SLEEP_MODE_PWR_DOWN);) Điều đó được cho là chạy ở đâu?
Adam Haile

OK, phần "set_s ngủ_mode (MODE)" phải nằm trong chính TRƯỚC vòng lặp chính, lý tưởng nhất là trong mã khởi tạo khác nơi bạn thiết lập các cổng I / O, bộ hẹn giờ, v.v. Bạn không thực sự cần phải bật_s_s (); tại thời điểm đó, vì nó sẽ được thực hiện sau khi kích hoạt WDT đầu tiên của bạn. Bên trong vòng lặp chính, mã ngủ đó sẽ chỉ thực thi NẾU giấc ngủ được bật và hoàn toàn không cần thiết phải vô hiệu hóa / kích hoạt các ngắt ở đó, chỉ khi bạn đang thực hiện chế độ ngủ_bod_disable (); Toàn bộ câu lệnh IF đó có thể ở dưới cùng (nhưng vẫn ở bên trong) của vòng lặp MAIN sau bất kỳ mã nào khác mà bạn đang thực thi ở đó.
Kurt E. Clothier

Ok ... điều đó có ý nghĩa hơn bây giờ. Điều cuối cùng tôi mờ nhạt là "trình tự thời gian" này là gì ...
Adam Haile

Lưu ý bên lề: Tôi biết lý do tại sao bạn muốn đi ngủ, nhưng tôi cho rằng bạn không cần phải sử dụng WDT?
Adam Haile

Hãy xem phần nhỏ này của biểu dữ liệu: 8.4.1. Về cơ bản, để thay đổi thanh ghi WDT, bạn phải đặt bit thay đổi và sau đó đặt các bit WDTCR thích hợp trong rất nhiều chu kỳ xung nhịp. Mã tôi đã cung cấp trong phần Cài đặt WDT thực hiện điều này bằng cách trước tiên cho phép bit thay đổi WD. Và không, bạn hoàn toàn không phải sử dụng chức năng ngủ với WDT. Nó có thể là một nguồn ngắt theo thời gian tiêu chuẩn cho bất kỳ lý do nào bạn muốn.
Kurt E. Clothier

1

Theo bảng dữ liệu là có thể. Bạn thậm chí có thể kích hoạt cả hai, ngắt và thiết lập lại. Nếu cả hai được bật, thời gian chờ của bộ giám sát đầu tiên sẽ kích hoạt ngắt làm cho bit Kích hoạt ngắt không được đặt (ngắt bị tắt). Thời gian chờ tiếp theo sau đó sẽ thiết lập lại CPU của bạn. Nếu bạn kích hoạt Ngắt trực tiếp sau khi được thực thi, thời gian chờ tiếp theo sẽ (lại) chỉ kích hoạt một ngắt.

Bạn cũng có thể chỉ kích hoạt ngắt và hoàn toàn không cho phép thiết lập lại. Bạn sẽ phải thiết lập bit WDIE mỗi khi ngắt được kích hoạt.


Hmmm .... tôi nghĩ điều đó có ý nghĩa Tôi sẽ cho nó một shot. Tôi luôn thích sử dụng những thứ cho những gì họ không có ý định :)
Adam Haile

Thật ra tôi nghĩ đó là một thiết kế thông minh. Tiết kiệm cho bạn một bộ đếm thời gian trong khi giữ chức năng watchdog.
Tom L.

2
Thời gian chờ ban đầu này của WDT và ngắt tiếp theo cũng có thể được sử dụng để tạo lợi thế cho một số ứng dụng cho phép WDT phục hồi hangout thực tế. Người ta có thể nhìn vào địa chỉ trả lại được xếp chồng lên nhau trong WDT ISR để suy ra mã đã cố gắng làm gì khi "thời gian chờ bất ngờ" xảy ra.
Michael Karas

1

Điều này là dễ dàng hơn nhiều so với đề xuất ở trên và ở nơi khác.

Miễn là WDTONcầu chì không được lập trình (nó không được lập trình theo mặc định), thì bạn chỉ cần ...

  1. Đặt bit cho phép ngắt watchdog và thời gian chờ trong thanh ghi điều khiển watchdog.
  2. Kích hoạt ngắt.

Dưới đây là một ví dụ mã sẽ thực thi ISR ​​một lần trong 16ms ...

ISR(WDT_vect) {
   // Any code here will get called each time the watchdog expires
}

void main(void) {
   WDTCR =  _BV(WDIE);    // Enable WDT interrupt, leave existing timeout (default 16ms) 
   sei();                                           // Turn on global interrupts
   // Put any code you want after here.
   // You can also go into deep sleep here and as long as 
   // global interrupts are eneabled, you will get woken 
   // up when the watchdog timer expires
   while (1);
}

Đó thực sự là nó. Vì chúng tôi không bao giờ kích hoạt thiết lập lại watchdog, chúng tôi không bao giờ phải loay hoay với các chuỗi thời gian để vô hiệu hóa nó. Cờ ngắt của watchdog sẽ tự động bị xóa khi ISR ​​được gọi.

Nếu bạn muốn một khoảng thời gian khác nhau cứ sau 1 giây, bạn có thể sử dụng các giá trị này tại đây để đặt các bit phù hợp trong WDTCR...

nhập mô tả hình ảnh ở đây

Lưu ý rằng bạn cần phải thực hiện chuỗi thời gian để thay đổi thời gian chờ. Đây là mã đặt thời gian chờ thành 1 giây ...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second

Không thực hiện chuỗi thời gian trong khi thiết lập sẽ tiết kiệm một dòng mã - thao tác đọc-sửa-ghi. Trình tự ban đầu được khuyến nghị trong biểu dữ liệu "Nếu Watchdog vô tình được bật, ví dụ như bằng con trỏ chạy hoặc điều kiện tắt màu" và phần còn lại của mã của tôi là dành riêng cho việc sử dụng WDT kết hợp với chế độ ngủ, theo yêu cầu của OP. Giải pháp của bạn không đơn giản hơn, bạn chỉ cần bỏ qua mã tấm nồi hơi được đề nghị / yêu cầu.
Kurt E. Clothier

@ KurtE.Clothier Xin lỗi, chỉ cố gắng đưa ra ví dụ làm việc đơn giản nhất!
bigjosh
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.