Vi điều khiển ngắt. Ngắt trong atmega8. Cách thức hoạt động của ngắt trong bộ vi điều khiển AVR

  • 18.03.2024

Bộ vi điều khiển AVR bao gồm một số lượng lớn các thiết bị ngoại vi (ADC, Bộ đếm thời gian/Bộ đếm, EXTI, Bộ so sánh tương tự, EEPROM, USART, SPI, I2C, v.v.), mỗi thiết bị có thể thực hiện một số hành động nhất định đối với dữ liệu/tín hiệu và thông tin khác. Các thiết bị này được tích hợp vào bộ vi điều khiển để nâng cao hiệu quả ứng dụng và giảm chi phí khi phát triển tất cả các loại thiết bị dựa trên bộ vi điều khiển AVR.

Bộ xử lý giao tiếp/điều khiển các thiết bị ngoại vi thông qua các Thanh ghi I/O nằm trong Bộ nhớ dữ liệu, cho phép chúng được sử dụng như các biến thông thường. Mỗi thiết bị có thanh ghi I/O riêng.

Tất cả các thanh ghi I/O có thể được chia thành ba nhóm: thanh ghi dữ liệu, thanh ghi điều khiển và thanh ghi trạng thái.

Sử dụng Thanh ghi điều khiển, thiết bị được cấu hình để hoạt động ở chế độ này hay chế độ khác, với tần số, độ chính xác nhất định, v.v. và sử dụng Thanh ghi dữ liệu, kết quả hoạt động của thiết bị này được đọc (chuyển đổi tương tự sang số, nhận dữ liệu, giá trị bộ đếm thời gian/bộ đếm, v.v.). Có vẻ như không có gì phức tạp ở đây (thực tế là không có gì phức tạp ở đây :)), bật thiết bị, chỉ định chế độ vận hành mong muốn và tất cả những gì còn lại là cắt phiếu giảm giá, đọc dữ liệu làm sẵn và sử dụng chúng trong tính toán. Toàn bộ câu hỏi là “khi nào” đọc chính dữ liệu này (thiết bị đã hoàn thành công việc của mình hay vẫn đang xử lý dữ liệu), bởi vì tất cả các thiết bị ngoại vi đều hoạt động song song với lõi vi điều khiển và thậm chí ở các tần số khác nhau. và đồng bộ hóa giữa bộ xử lý và thiết bị ngoại vi.

Như bạn có thể đã đoán, để thực hiện giao tiếp và đồng bộ hóa giữa thiết bị và bộ xử lý, “Thanh ghi trạng thái” được sử dụng để lưu trữ trạng thái hoạt động hiện tại của một thiết bị cụ thể. Mỗi trạng thái mà thiết bị có thể tương ứng với một “bit in”. trạng thái đăng ký” (cờ), giá trị hiện tại “nói” về trạng thái hiện tại của thiết bị này hoặc chức năng riêng của nó (công việc đã hoàn thành/chưa hoàn thành, lỗi trong quá trình xử lý dữ liệu, đăng ký trống, v.v.).

Cơ chế giao tiếp giữa bộ xử lý và thiết bị ngoại vi được thực hiện bằng cách bỏ phiếu, chịu trách nhiệm về một chức năng cụ thể của thiết bị này. Tùy thuộc vào giá trị của một cờ cụ thể (trạng thái thiết bị), bạn có thể thay đổi luồng thực thi chương trình (phân nhánh). Ví dụ:

Kiểm tra xem một cờ nhất định có được đặt hay không (một số sự kiện đã xảy ra):

nếu (RegX & (1<< Flag) ) // nếu cờ trong thanh ghi RegX được đặt
{
// làm điều gì đó
}

Đang chờ hoàn thành một số hành động (sự kiện):

while(!(RegX & (1<

Cờ truy vấn là một nhiệm vụ khá tốn tài nguyên, cả về kích thước chương trình và tốc độ chương trình. Do tổng số cờ trong bộ vi điều khiển AVR khá lớn (một lợi thế) nên việc thực hiện giao tiếp giữa bộ xử lý và thiết bị bằng cờ thăm dò dẫn đến giảm hiệu quả (tốc độ mã/kích thước mã) của chương trình bạn viết, Ngoài ra, chương trình trở nên rất khó hiểu, góp phần xuất hiện các lỗi khó phát hiện ngay cả khi gỡ lỗi mã chi tiết.

Để tăng hiệu quả của các chương trình dành cho bộ vi điều khiển AVR, cũng như để tạo điều kiện thuận lợi cho quá trình tạo và gỡ lỗi các chương trình này, các nhà phát triển đã trang bị cho tất cả các thiết bị ngoại vi “nguồn ngắt” ( Nguồn ngắt), một số thiết bị có thể có nhiều nguồn ngắt.

Sử dụng các nguồn ngắt, nó được thực hiện cơ chế đồng bộ hóa, giữa bộ xử lý và thiết bị ngoại vi, nghĩa là bộ xử lý sẽ bắt đầu nhận dữ liệu, cờ thăm dò và các hành động khác trên thiết bị ngoại vi chỉ khi thiết bị sẵn sàng cho việc này (nó sẽ báo cáo việc hoàn tất xử lý dữ liệu, có lỗi trong quá trình xử lý dữ liệu, thanh ghi trống, v.v.), v.v.), bằng cách tạo ra một "yêu cầu ngắt" ( Yêu cầu ngắt), tùy thuộc vào giá trị của một số cờ (trạng thái thiết bị/chức năng/sự kiện).

Trong tài liệu, rất thường xuyên, toàn bộ chuỗi sự kiện, bắt đầu từ “yêu cầu ngắt” (IRQ) đến “thủ tục dịch vụ ngắt” (ISR), được viết tắt là ngắt ( Ngắt).

Ngắt là gì?


Ngắt là tín hiệu thông báo cho bộ xử lý về sự xuất hiện của một sự kiện. Trong trường hợp này, việc thực thi chuỗi lệnh hiện tại bị tạm dừng và điều khiển được chuyển sang quy trình xử lý ngắt tương ứng với sự kiện này, sau đó việc thực thi mã tiếp tục chính xác từ điểm bị gián đoạn (trả lại quyền điều khiển). (Wiki)

thói quen ngắt(Thói quen dịch vụ ngắt) không gì khác hơn là một hàm/chương trình con sẽ được thực thi khi một sự kiện nhất định xảy ra. Chúng ta sẽ sử dụng từ “thủ tục” để nhấn mạnh sự khác biệt của nó với tất cả các chức năng khác.

Sự khác biệt chính giữa thủ tục và các hàm đơn giản là thay vì “trả về từ hàm” thông thường (lệnh hợp ngữ RET), bạn nên sử dụng “trả về từ ngắt” (lệnh hợp ngữ RETI) - " TRỞ LẠI từ ngắt".

Thuộc tính ngắt AVR:

  • Mỗi thiết bị ngoại vi là một phần của bộ vi điều khiển AVR đều có ít nhất một nguồn ngắt. Trong số tất cả các gián đoạn này, người ta cũng nên bao gồm cả ngắt đặt lại, mục đích của nó khác với tất cả các gián đoạn khác.
  • Mỗi ngắt có một vectơ (liên kết) được gán chặt chẽ trỏ đến quy trình dịch vụ Ngắt. Tất cả các vectơ ngắt đều nằm ở đầu bộ nhớ chương trình và cùng nhau tạo thành “Bảng vectơ ngắt”.
  • Mỗi ngắt được liên kết với một “bit kích hoạt ngắt” cụ thể. Vì vậy, để sử dụng một ngắt cụ thể, bạn nên ghi vào “bit kích hoạt ngắt” của nó - nhật ký. đơn vị. Hơn nữa, bất kể bạn có kích hoạt một số ngắt nhất định hay không, bộ vi điều khiển sẽ không bắt đầu xử lý các ngắt này cho đến khi một ngắt logic được ghi vào “bit Kích hoạt ngắt toàn cầu” trong thanh ghi trạng thái SREG. thời gian không xác định), số 0 của nhật ký phải được ghi vào bit cho phép ngắt chung.

Ngắt Reset, không giống như tất cả các ngắt khác, không thể bị vô hiệu hóa. Những gián đoạn như vậy còn được gọi là những ngắt không thể che dấu được.

  • Mỗi ngắt có một mức độ ưu tiên được xác định nghiêm ngặt. Mức độ ưu tiên của ngắt phụ thuộc vào vị trí của nó trong “bảng vectơ ngắt”. Số vectơ trong bảng càng thấp thì mức độ ưu tiên của ngắt càng cao, tức là ngắt đặt lại có mức ưu tiên cao nhất, nằm đầu tiên trong đó. bảng và tương ứng trong các chương trình bộ nhớ. Ngắt ngoài INT0, theo sau ngắt Đặt lại trong “bảng vectơ ngắt”, có mức ưu tiên thấp hơn mức ưu tiên của Ngắt Đặt lại, nhưng cao hơn mức ưu tiên của tất cả các ngắt khác, v.v.

Bảng vectơ ngắt, ngoại trừ vectơ Đặt lại, có thể được di chuyển đến đầu phần Khởi động của bộ nhớ Flash bằng cách đặt bit IVSEL trong thanh ghi GICR. Vectơ đặt lại cũng có thể được di chuyển đến đầu phần Khởi động của bộ nhớ Flash bằng cách lập trình bit cầu chì - BOOTRST.



Hình 1 Bảng vectơ ngắt ATmega16

Nguyên mẫu thường trình ngắt


Để khai báo một hàm như một quy trình xử lý ngắt, bạn phải tuân theo các quy tắc tạo mẫu nhất định để trình biên dịch/trình liên kết có thể xác định chính xác và liên kết ngắt bạn cần với quy trình xử lý của nó.

Thứ nhất, quy trình phục vụ ngắt không thể chấp nhận bất cứ thứ gì làm đối số (void) và cũng không thể trả về bất cứ thứ gì (void). Điều này là do tất cả các ngắt trong AVR đều không đồng bộ nên không biết việc thực thi chương trình sẽ bị gián đoạn ở đâu, nhận giá trị từ ai và trả về giá trị cho ai, đồng thời cũng để giảm thiểu thời gian vào và ra. từ sự gián đoạn.

khoảng trống isr(void)

Thứ hai, trước nguyên mẫu hàm, bạn nên chỉ ra rằng đó là một quy trình xử lý ngắt. Như bạn đã biết, trong ngôn ngữ C chỉ có mã được sử dụng trong hàm chính được thực thi. Vì thủ tục xử lý ngắt trong hàm chính không được sử dụng ở bất kỳ đâu, do đó trình biên dịch không "loại bỏ" nó khi không cần thiết, nên trước nguyên mẫu thủ tục cần chỉ ra rằng hàm này là một thủ tục xử lý ngắt.

Nguyên mẫu của quy trình xử lý ngắt trong môi trường AVR Studio

#bao gồm

ISR (XXX_vect)
{

}

Trong AVR Studio (AVR GCC), mỗi quy trình ngắt bắt đầu bằng định nghĩa macro ISR, theo sau là cấu trúc sau trong ngoặc đơn:

XXX_vect

trong đó “XXX” là tên của vectơ ngắt cho một bộ vi điều khiển AVR cụ thể có thể được tìm thấy trong “bảng vectơ ngắt” của biểu dữ liệu của bộ vi điều khiển hoặc trong tệp tiêu đề của nó. Ví dụ: “bảng vectơ ngắt” cho bộ vi điều khiển ATmega16 được hiển thị trong Hình 1, trong đó trong cột Nguồn, tất cả tên của các vectơ ngắt cũng có thể được tìm thấy trong tệp tiêu đề của bộ vi điều khiển này (C). :\Program Files\Atmel\AVR Tools\AVR Toolchain\avr\include\avr\iom16.h), xem Hình 2. Tất cả những gì chúng ta cần làm là tìm tên của vectơ chúng ta cần trong bảng và thêm hậu tố “_vect” với nó.


Hình 2 Tệp tiêu đề ATmega16 cho AVR Studio

Ví dụ: hãy viết quy trình xử lý ngắt để nhận byte qua USART (USART, Rx Complete):

ISR (USART_RXC_vect)
{
// Phần thân xử lý ngắt
}

Nhân tiện: trước khi sử dụng bất kỳ ngắt nào trong AVR Studio, bạn nên bao gồm các tệp tiêu đề io.h và Interrupt.h trong dự án:

#bao gồm
#bao gồm

Bạn có thể đọc thêm về trình xử lý ngắt trong AVR Studio (AVR GCC) trong phần Giới thiệu về xử lý ngắt của avr-libc.

Nguyên mẫu của thủ tục xử lý ngắt trong môi trường ImageCraft

#pragma ngắt_handler : iv_XXX
trống rỗng< handler_name>(vô hiệu)
{
// Phần thân xử lý ngắt
}

Trong môi trường ImageCraft, quy trình ngắt nguyên mẫu trông như thế này:

trống rỗng< handler_name>(vô hiệu)

Ở đâu , đây là bất kỳ tên nào bạn muốn đặt cho trình xử lý ngắt này. Một trong những yêu cầu để khai báo các thủ tục xử lý ngắt là trước nguyên mẫu hàm phải chỉ rõ rằng đó là một trình xử lý ngắt. Việc này được thực hiện bằng cách sử dụng chỉ thị pragma ngắt_xử lý :

#pragma ngắt_handler : iv_XXX

Ở đâu đây là tên của hàm sẽ được sử dụng làm trình xử lý ngắt và cấu trúc “iv_XXX” là tên của vectơ ngắt (XXX) có tiền tố “iv_”. Như trong trường hợp của AVR Studio, tất cả các tên vectơ. đối với một bộ vi điều khiển AVR cụ thể, bạn có thể tìm thấy trong “bảng vectơ ngắt” của biểu dữ liệu của một bộ vi điều khiển nhất định hoặc trong tệp tiêu đề của nó (xem Hình 3).


Hình 3 Tệp tiêu đề ATmega16 cho ImageCraft IDE

Ví dụ: quy trình xử lý ngắt để nhận byte qua USART (USART, Rx Complete) trong môi trường ImageCraft sẽ như sau:

#pragma ngắt_handler usart_rxc_isr: iv_USART_RXC
void usart_rxc_isr(void)
{
// Phần thân xử lý ngắt
}

Thông tin thêm về quy trình xử lý ngắt trong ImageCraft IDE có thể được tìm thấy trong menu Trợ giúp-> Lập trình AVR-> Trình xử lý ngắt của môi trường phát triển.

Đôi khi, nếu một số trình xử lý ngắt cần thực hiện cùng một việc, thì để tiết kiệm bộ nhớ chương trình, bạn có thể hướng một số vectơ ngắt tới cùng một chương trình ngắt.

Trong AVR Studio nó trông như thế này:

ISR (INT0_vect)
{
// Làm gì đó
}
ISR(INT1_vect, ISR_ALIASOF(INT0_vect) ) ;

Đầu tiên là thủ tục xử lý ngắt cho một vectơ cụ thể, trong trường hợp này là INT0. Tất cả các thủ tục khác có thể tham chiếu đến bất kỳ trình xử lý ngắt nào bằng cách sử dụng cấu trúc:

ISR (YYY_vect, ISR_ALIASOF(XXX_vect) ) ;

trong đó YYY là tên của vectơ ngắt đề cập đến trình xử lý ngắt được khai báo trước đó cho vectơ XXX.

Trong ImageCraft nó trông như thế này:

#pragma ngắt_handler : iv_XXX : iv_YYY
trống rỗng< handler_name>(vô hiệu)
{
// Phần thân xử lý ngắt
}

#pragma ngắt_handler : iv_XXX
#pragma ngắt_handler : iv_YYY
trống rỗng< handler_name>(vô hiệu)
{
// Phần thân xử lý ngắt
}

trong đó vectơ XXX và YYY tham chiếu đến cùng một trình xử lý ngắt .

Ngắt hoạt động như thế nào trong bộ vi điều khiển AVR?

1. Giả sử “đã xảy ra” yêu cầu ngắt”(IRQ).

Nhân tiện: nếu một số yêu cầu xử lý ngắt xảy ra đồng thời, ngắt có mức ưu tiên cao nhất sẽ được xử lý trước, tất cả các yêu cầu khác sẽ được xử lý sau khi hoàn thành ngắt có mức ưu tiên cao.

2. Bài kiểm tra.

Nếu bit kích hoạt cho ngắt này được đặt (bit cho phép ngắt) và bit I (bit cho phép ngắt chung) của thanh ghi trạng thái bộ xử lý (SREG) được đặt thì bộ xử lý bắt đầu chuẩn bị quy trình dịch vụ ngắt, trong khi lệnh chung Bit cho phép ngắt (bit I của thanh ghi SREG) được đặt lại, do đó vô hiệu hóa tất cả các ngắt khác. Điều này xảy ra để không có sự kiện nào khác có thể làm gián đoạn quá trình xử lý ngắt hiện tại.

Nhân tiện: nếu trong quy trình xử lý ngắt, bạn đặt I-bit ở trạng thái nhật ký. thì bất kỳ ngắt nào được kích hoạt đều có thể làm gián đoạn quá trình xử lý ngắt hiện tại. Các ngắt như vậy được gọi là các ngắt lồng nhau.

3. Sự chuẩn bị.

Bộ xử lý hoàn thành việc thực hiện lệnh hợp ngữ hiện tại và sau đó đặt địa chỉ của lệnh tiếp theo vào ngăn xếp (PC->STACK). Tiếp theo, bộ xử lý kiểm tra xem nguồn ngắt nào đã gửi “yêu cầu ngắt” (IRQ), sau đó, sử dụng vectơ của nguồn này (liên kết) từ bảng vectơ (được gán chắc chắn cho từng nguồn ngắt), nó sẽ tiến tới thủ tục xử lý ngắt (lệnh JMP). Chỉ vậy thôi, bộ xử lý dành ít nhất 4 chu kỳ xung nhịp (tùy thuộc vào thời điểm yêu cầu xuất hiện và thời lượng của lệnh hiện tại). các nhà sản xuất khác.

Nhân tiện: nếu IRQ xảy ra khi bộ vi điều khiển ở chế độ ngủ, thời gian phản hồi với IRQ sẽ tăng thêm bốn chu kỳ xung nhịp nữa, cộng với thời gian được lưu trong các bit cầu chì SUT1 và SUT0 (Thời gian khởi động).

Ngắt bên ngoài được sử dụng để làm gì?

Gián đoạn là sự kiện trong đó việc thực thi mã chương trình chính (ví dụ: chức năng chính) bị gián đoạn và quyền điều khiển được chuyển đến bộ xử lý ngắt của chức năng. Theo đó, các ngắt bên ngoài là các sự kiện bên ngoài nhất định làm gián đoạn việc thực thi mã chương trình chính.

Các ngắt bên ngoài cho phép bạn nhận được phản hồi nhanh chóng, đảm bảo đối với các sự kiện bên ngoài. Do đó, công dụng phổ biến nhất của ngắt ngoài là thực hiện bộ đếm xung, đo tần số hoặc độ dài xung, triển khai phần mềm uart, one-wire, i2c, spi, cũng như xử lý tín hiệu từ các thiết bị ngoại vi bên ngoài.

Nguyên lý hoạt động của ngắt ngoài trong AVR

Để bộ vi điều khiển tìm hiểu về các sự kiện bên ngoài, các đầu vào rời rạc INT0 INT1, v.v. được sử dụng. Rời rạc có nghĩa là chúng hoạt động với các mức logic: 0 và 1.
0 là không có điện áp ở đầu vào
1 - sự hiện diện của điện áp ở đầu vào, bằng với điện áp cung cấp của vi điều khiển.

Ngắt bên ngoài có thể được chia thành hai loại:

  • ngắt bên ngoài theo cấp độ
  • ngắt cạnh bên ngoài

Ngắt bên ngoài theo cấp độ

Bộ kích hoạt ngắt bên ngoài có thể được cấu hình ở mức thấp hoặc cao. Ví dụ: nếu ngắt được đặt ở mức logic thấp thì nó sẽ xảy ra khi điện áp ở đầu vào INT bằng 0. Nếu ngắt được đặt ở mức cao thì nó sẽ xảy ra khi đầu vào ở mức logic 1.
Khi làm việc với các ngắt theo cấp độ, bạn phải nhớ rằng chỉ cần đầu vào INT có mức phù hợp thì ngắt sẽ xảy ra liên tục. Những thứ kia. nếu một ngắt xảy ra, chẳng hạn ở mức thấp và chương trình xử lý nó, nhưng nếu khi thoát khỏi trình xử lý ngắt, đầu vào vẫn ở mức thấp thì ngắt sẽ kích hoạt lại và trình xử lý ngắt sẽ được gọi lại, và điều này sẽ tiếp tục cho đến khi mức cao đầu vào xuất hiện. Để ngăn điều này xảy ra, bạn cần tắt loại gián đoạn này trong trình xử lý hoặc cấu hình lại nó ở cấp độ khác.

Ngắt cạnh ngoài

Ngắt trên cạnh tăng, hay như đôi khi người ta nói, tín hiệu tăng, xảy ra khi mức tín hiệu ở đầu vào INT thay đổi từ 0 thành 1. Ngắt trên cạnh giảm (tín hiệu giảm), xảy ra khi mức tín hiệu ở đầu vào INT thay đổi từ 1 thành 0.
Cũng có thể cấu hình ngắt để nó phản ứng với bất kỳ thay đổi nào ở đầu vào INT, tức là. nó sẽ xảy ra dọc theo cả cạnh đầu và cuối.

Cấu hình các ngắt ngoài trong AVR

Ngắt bên ngoài trong avr atmega8 được cấu hình bằng bit ISCxx đăng ký MCUCR .

Sự phụ thuộc của điều kiện kích hoạt của ngắt ngoài INT0 vào bit ISC0x của thanh ghi MCUCR trong avr atmega8

Đối với ngắt bên ngoài INT1 , việc thiết lập được thực hiện theo cách tương tự, chỉ sử dụng các bit ISC11 ISC10 .

Ví dụcài đặt ngắt bên ngoài cho avr atmega8:

// đặt lại tất cả các bit ISCxx MCUCR &= ~( (1 <<ISC11) | (1<<ISC10) | (1<< ISC01) | (1<<ISC00) ) MCUCR |= (1 << ISC01) | (1 << ISC00);

// đặt lại tất cả các bit ISCxx MCUCR &= ~((1<

Kích hoạt các ngắt bên ngoài trong avr atmega

Để các ngắt ngoài hoạt động, chúng phải được kích hoạt bằng cách đặt các bit tương ứng trong thanh ghi thành 1 GICR .

Chút INT0 chịu trách nhiệm kích hoạt/vô hiệu hóa các ngắt bên ngoài INT0 , và chút INT1 , tương ứng, đối với sự gián đoạn bên ngoài INT1 .

Cờ cho phép ngắt toàn cục cũng cần được đặt.

Mã ví dụ để kích hoạt ngắt ngoài INT0 cho avr atmega8:

//cho phép ngắt ngoài INT0 GICR |= (1<

Ví dụ về sử dụng các ngắt ngoài trong AVR atmega

Để làm ví dụ, tôi sẽ đưa ra một chương trình đếm xung. Chương trình đếm số xung ở đầu vào INT0 và mỗi giây một lần sẽ hiển thị kết quả đếm trong uart.

#bao gồm #bao gồm #bao gồm #bao gồm // biến đếm dễ bay hơi không dấu dài int0_cnt = 0; //cấu hình ngắt ngoài INT0 void int0_init(void ) ( // thiết lập để kích hoạt INT0 ở cạnh lên MCUCR |= (1 << ISC01) | (1 << ISC00); //cho phép ngắt ngoài INT0 GICR |= (1 <<INT0) ; )//hàm xử lý ngắt ngoài INT0 ISR( INT0_vect ) ( int0_cnt++; ) //Thiết lập UART void uart_init( void ) (//thiết lập tốc độ trao đổi UBRRH = 0 ; UBRRL = 3 ;//115200 ở thạch anh 7,3728 MHz // 8 bit dữ liệu, 1 bit stop, không có tính chẵn lẻ UCSRC = ( 1 << URSEL ) | ( 1 << UCSZ1 ) |( 1 << UCSZ0 ) ; //cho phép nhận và truyền dữ liệu UCSRB = ( 1 << TXEN ) | ( 1 << RXEN ) ;) // truyền byte qua UART int uart_putc( char c, FILE * file ) ( //đợi kết thúc quá trình truyền byte trước đó while ( ( UCSRA & ( 1 << UDRE ) ) == 0 ) ; UDR = c; trả về 0;

#bao gồm ) FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE ) ; ) FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE ) ; ) FILE uart_stream = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE ) ; int chính( ) (<

// biến tạm thời
Hầu như tất cả các bộ vi điều khiển AVR đều có ngăn xếp trong SRAM. Để đánh địa chỉ phần tử hiện tại (trên cùng của ngăn xếp), con trỏ ngăn xếp SP (Con trỏ ngăn xếp) được sử dụng. Đây là RVV SPL một byte dành cho các kiểu máy có dung lượng bộ nhớ dữ liệu lên tới 256 byte hoặc SPH:SPL hai byte (SPH - byte cao, SPL - byte thấp).

Khi bộ vi xử lý gặp một trong các lệnh gọi rcall/call/ecall/icall/eicall, địa chỉ của từ tiếp theo trong bộ nhớ chương trình sẽ được phần cứng sao chép vào ngăn xếp. Khi chương trình con thoát bằng lệnh ret, địa chỉ trả về sẽ được khôi phục từ ngăn xếp về bộ đếm chương trình. Trong các mô hình có dung lượng bộ nhớ chương trình là 128 và 256 kword, việc lưu PC vào ngăn xếp sẽ cần 3 byte, đối với tất cả các mô hình khác - 2 byte. Khi lưu trữ từng byte, nội dung của SP sẽ giảm đi một và khi được khôi phục, chúng sẽ tăng lên tương ứng.

Hình 9 Vị trí ngăn xếp trong bộ nhớ dữ liệu

Người lập trình phải xác định độc lập vị trí của ngăn xếp ngay từ đầu chương trình. Từ quan điểm về độ sâu tối đa của nó, đỉnh của ngăn xếp phải được đặt ở cuối SRAM, như trong Hình 9:

Bao gồm "m8def.inc" ldi temp,low(RAMEND) ;đặt SP = RAMEND out SPL,temp ;đối với ATmega8 SP = 0x045F ldi temp,high(RAMEND) out SPH,temp

Hằng số RAMEND từ tệp tiêu đề m8def.inc tiêu chuẩn có giá trị địa chỉ của ô SRAM cuối cùng.

Các biến chương trình ứng dụng được đặt trong dải địa chỉ SRAM giữa RBB và vị trí SP hiện tại. Vì vậy, điều rất quan trọng trước tiên là ước tính kích thước ngăn xếp tối đa (độ sâu ngăn xếp). Có thể xảy ra trường hợp đỉnh ngăn xếp tăng quá cao và bắt đầu “ghi đè” dữ liệu người dùng và đây là một trong những lỗi khó phát hiện nhất!

Ngăn xếp AVR, ngoài việc lưu trữ các địa chỉ trả về, còn có một mục đích rất quan trọng khác. Nó cho phép bạn lưu bất kỳ dữ liệu nào bằng cách sử dụng các lệnh push Rr (tải vào ngăn xếp) và pop Rd (dỡ khỏi ngăn xếp) được thiết kế đặc biệt cho mục đích này. Mỗi lần thực hiện push Rr, nội dung của Rr sẽ được sao chép vào ngăn xếp, sau đó SP sẽ giảm đi một. Khi pop Rr được thực thi, nội dung của ô ngăn xếp được trỏ tới bởi SP sẽ được khôi phục về Rr và giá trị của chính SP sẽ tăng lên. Loại ngăn xếp này có tổ chức Last In First Out: thanh ghi được lưu bằng lệnh push cuối cùng sẽ được khôi phục bằng lệnh pop đầu tiên:

; SP Mức ngăn xếp sau lệnh đẩy R16 ;lưu R16 0x045F R16 ? ?

đẩy R17; lưu R17 0x045E R16 R17?

đẩy R18 ;lưu R18 0x045D R16 R17 R18 ̣̣̣̣̣̣̣ bật R18 ;khôi phục R18 0x045D R16 R17 ?<->bật R17; khôi phục R17 0x045E R16? ?


bật R16; khôi phục R16 0x045F? ? ?

Sử dụng ngăn xếp, bạn có thể dễ dàng trao đổi nội dung của các thanh ghi:

; Trao đổi R16

R17 SP Mức ngăn xếp sau lệnh đẩy R16 ;lưu R16 0x045F R16 ?đẩy R17; lưu R17 0x045E R16 R17 pop R16; khôi phục R16 0x045E R16?

bật R17; khôi phục R17 0x045F? ? MCUCR Hình 10 Ví dụ về thao tác ngăn xếp

Hình 10 cho thấy một đoạn mã nhỏ thể hiện quy trình từng bước thay đổi ngăn xếp khi vào và thoát khỏi chương trình con chuyển đổi cũng như lưu và khôi phục thanh ghi R17. Đây là một ví dụ điển hình trong đó có thể cần đến hướng dẫn đẩy/bật. Chương trình con chuyển đổi sử dụng R17 RON cho nhu cầu của nó, nhưng thanh ghi tương tự cũng có thể được sử dụng trong chương trình chính. Do đó, để tránh dữ liệu bị hỏng, R17 được tải vào ngăn xếp trước khi sửa đổi và được khôi phục từ ngăn xếp đó trước lệnh ret. Một trong những ưu điểm của bộ vi điều khiển ATmega8 là có nhiều loại ngắt khác nhau. Ngắt

là một sự kiện khi xảy ra việc thực thi chương trình chính bị tạm dừng và một hàm được gọi để xử lý một loại ngắt nhất định.

Ngắt được chia thành bên trong và bên ngoài. Các nguồn ngắt bên trong bao gồm các mô-đun vi điều khiển tích hợp (bộ hẹn giờ, bộ thu phát USART, v.v.). Ngắt bên ngoài xảy ra khi tín hiệu bên ngoài đến các chân của bộ vi điều khiển (ví dụ: tín hiệu ở chân RESET và INT). Bản chất của các tín hiệu dẫn đến xảy ra ngắt được đặt trong thanh ghi điều khiển , đặc biệt là ở các bit - ISC00 (bit 0) và ISC01 (bit 1) cho đầu vào INT 0; ISC10 (bit2) và ISC11 (bit3) cho đầu vào INT1. Trong bộ vi điều khiển ATmega8, mỗi ngắt có một ngắt riêng
vectơ ngắt (địa chỉ ở đầu vùng bộ nhớ chương trình trong đó lệnh nhảy tới chương trình ngắt đã chỉ định được lưu trữ). Trong mega8, tất cả các ngắt đều có cùng mức độ ưu tiên. Nếu có nhiều ngắt xảy ra đồng thời thì ngắt có số vectơ thấp hơn sẽ được xử lý trước. Các vectơ ngắt trong Atmega8
Địa chỉ INT0 Nguồn ngắt
Sự miêu tả INT1 0x0000
CÀI LẠI Đặt lại tín hiệu Chụp hẹn giờ T/C1
0x0004 Đặt lại tín hiệu So sánh bộ định thời T/C1 So sánh thanh ghi A
0x0005 Đặt lại tín hiệu Khớp với thanh ghi so sánh B của bộ định thời T/C1
0x0006 Đặt lại tín hiệu Tràn bộ đếm T/C1
0x0007 T/C0 Tràn bộ đếm T/C0
0x0008 SPI Quá trình truyền dữ liệu SPI đã hoàn tất
0x0009 UART Bộ thu phát UART đã hoàn tất việc nhận dữ liệu.
0x000A UART Thanh ghi dữ liệu UART trống
0x000B UART Việc truyền dữ liệu bằng bộ thu phát UART đã hoàn tất
0x000C ANA_COMP Ngắt từ bộ so sánh tương tự

Quản lý ngắt

4 thanh ghi chịu trách nhiệm quản lý các ngắt trong ATmega8:

GIMSK(còn gọi là GICR) - cấm/cho phép ngắt dựa trên tín hiệu ở đầu vào INT0, INT1

GIFR- quản lý tất cả các ngắt bên ngoài

TIMSK, TIFR- quản lý sự gián đoạn từ bộ tính giờ/bộ đếm

Đăng ký GIMSK(GICR)

INTFx=1: xảy ra ngắt ở đầu vào INTx. Khi vào quy trình xử lý ngắt, INTFx sẽ tự động được đặt lại về trạng thái nhật ký. 0

Đăng ký TIMSK

7 6 5 4 3 2 1 0
TOIE1
OCIE1A
OCIE1B
-
TICIE
-
TOIE0
-

TOIE1=1: Đã bật ngắt tràn T/C1

OCIE1A=1: ngắt khi thanh ghi so sánh A khớp với nội dung của bộ đếm T/C1 được kích hoạt

OCIE1B=1: ngắt khi thanh ghi so sánh B khớp với nội dung của bộ đếm T/C1 được kích hoạt

TICIE=1: ngắt được kích hoạt khi điều kiện chụp được đáp ứng

TOIE0=1: Đã bật ngắt tràn T/C0

Đăng ký TIFR

7 6 5 4 3 2 1 0
TOV1
OCF1A
OCF1B
-
ICF1
-
TOV0
-

TOV1=1: Xảy ra tràn T/C1

OCF1A=1: thanh ghi so sánh A trùng với nội dung của bộ đếm T/C1 cho phép

OCF1B=1: thanh ghi so sánh B khớp với nội dung của bộ đếm T/C1 được phép

ICF=1: đáp ứng điều kiện chụp

TOV0=1: Đã xảy ra tràn T/C0

Khi vào chương trình con xử lý ngắt, cờ đăng ký TIFR tương ứng với ngắt sẽ tự động được đặt lại về trạng thái nhật ký. 0

Ngắt chỉ hoạt động khi ngắt chung được bật trong thanh ghi trạng thái SREG (bit 7 = 1). Khi xảy ra ngắt, bit này sẽ tự động được đặt lại về 0, vô hiệu hóa các ngắt tiếp theo.

Trong ví dụ này, chân INT0 được bật ở chế độ đầu vào kéo lên. Khi chân được nối đất bằng một nút, logic 0 được đặt trên đó (cạnh của tín hiệu giảm từ điện áp nguồn xuống 0) và bộ xử lý ngắt được kích hoạt, bật bóng đèn được nối với chân 0 của cổng B

đèn trốngON()
{
PORTB.0=1;
DDRB.0=1;
}

ngắt void ext_int0_isr(void)
{
đènON();
}

DDRD.2=0;
PORTD.2=1;

SREG|= (1 trong khi(1) (

Ví dụ trên cũng cho thấy cách đặt vectơ ngắt trong Code Vision AVR (ngắt void ext_int0_isr(void)). Các vectơ ngắt được đặt tương tự cho các trường hợp khác:

EXT_INT0 2
EXT_INT1 3
TIM2_COMP 4
TIM2_OVF 5
TIM1_CAPT 6
TIM1_COMPA 7
TIM1_COMPB 8
TIM1_OVF 9
TIM0_OVF 10
SPI_STC 11
USART_RXC 12
USART_DRE 13
USART_TXC 14
ADC_INT 15
EE_RDY 16
ANA_COMP 17
TWI 18
SPM_READY 19

Hôm nay chúng ta sẽ xem xét khái niệm gián đoạn và cách sử dụng nó. Đương nhiên, chúng tôi sẽ không làm được nếu không có chương trình đào tạo, nhưng lần này chúng tôi sẽ không nhấp nháy đèn LED. Tốt rồi. Hãy làm một cái gì đó giống như chuông cửa.

Nhiệm vụ: làm cho bộ vi điều khiển phát ra tiếng bíp khi nhấn nút.
Sơ đồ cho ví dụ của chúng tôi. Các tập tin dự án.

Chúng tôi tạo một dự án vòng trong không gian làm việc cũ.
Đặt cài đặt dự án cho cấu hình Phát hành:

Chọn loại vi điều khiển.
Tùy chọn chung > Mục tiêu > Cấu hình bộ xử lý
Tôi có ATmega8535 này.

Cho phép sử dụng tên bit được xác định trong tệp tiêu đề
Trong Tùy chọn chung > Hệ thống, chọn hộp Bật định nghĩa bit trong tệp I/O-Bao gồm
Cho đến nay chúng ta chưa sử dụng tên bit nhưng hôm nay chúng ta sẽ cần đến chúng.

Thay đổi loại tập tin đầu ra.
Trình liên kết > Đầu ra.
Trong trường Tệp đầu ra, chọn hộp Ghi đè mặc định và thay thế phần mở rộng d90 bằng hex
Trong trường Định dạng, chọn Khác và trong menu thả xuống Định dạng đầu ra, chọn loại tệp intel-standart

Lưu dự án và không gian làm việc.

______________________________ Ngắt ___________________________

Hãy tưởng tượng tình huống này. Bạn đang ngồi làm việc và nghiền ngẫm một chương trình vi điều khiển khác. Sếp đến gặp bạn và nói: “Nghe này, Pash, chúng tôi đã mua máy hiện sóng cho bộ phận của mình - Tektronix, bốn kênh. Hãy giúp Vasya kéo chúng đi.” Bạn nghĩ: “Ôi trời, chỉ là ý nghĩ đó đã cản trở bạn… và bạn thôi.” Và ông chủ nhìn bạn như thế, ánh mắt của ông ấy thật hiền lành, thật tốt bụng. Làm sao bạn có thể từ chối anh ấy? Chà, bạn bỏ tất cả mọi thứ và đi cùng một người bạn để mua máy hiện sóng. Họ đã mang nó vào. Chúng tôi đã báo cáo. Và họ lại ngồi xuống chương trình của mình. Đây gần như là cơ chế ngắt trông như thế nào.

Khá đơn giản, nhưng có một số điểm cơ bản.
Trước hết:
- bạn đã làm xong việc của mình
- cùng lúc đó, có người đang mua máy hiện sóng
- khi xảy ra sự kiện "đã mua máy hiện sóng" - bạn sẽ gián đoạn công việc của mình
- trong một thời gian bạn đang làm công việc khác - mang theo máy hiện sóng
- sau đó bạn quay lại nơi làm việc và tiếp tục làm công việc của mình từ nơi bạn đã dừng lại

Thứ hai:
- bạn có thể dễ dàng gửi sếp của mình và không đi đâu cả
- sau khi rời khỏi máy hiện sóng, bạn có thể ở đó một thời gian dài hoặc thậm chí không quay lại
- khi bạn trở lại nơi làm việc, bạn có thể đã quên mất những ý tưởng tuyệt vời của mình

Tất cả điều này rất giống với những gì xảy ra trong một bộ vi điều khiển. Bộ vi điều khiển AVR bao gồm một loạt các thiết bị ngoại vi (bộ định thời/bộ đếm, bộ chuyển đổi tương tự sang số, bộ so sánh tương tự, bộ thu phát không đồng bộ, v.v.). Sức mạnh của bộ vi điều khiển là tất cả các thiết bị này có thể hoạt động song song và độc lập với nhau cũng như song song với chương trình đang được thực thi. Mỗi thiết bị ngoại vi có thể kích hoạt ngắt khi một sự kiện cụ thể xảy ra. Sự gián đoạn sẽ chỉ xảy ra nếu nó được kích hoạt. Kích hoạt ngắt được đặt riêng cho từng thiết bị. Ngoài ra, còn có cờ bật/tắt toàn cục cho tất cả các ngắt - đây là cờ I trong thanh ghi SREG. Khi xảy ra ngắt, bộ vi điều khiển sẽ lưu trữ nội dung của bộ đếm chương trình PC trên ngăn xếp, nghĩa là nó ghi nhớ vị trí mà nó bị gián đoạn. Nạp địa chỉ của vectơ ngắt tương ứng vào bộ đếm chương trình và nhảy tới địa chỉ đó. Nó chạm vào một lệnh nhảy vô điều kiện, đi tới chương trình con xử lý ngắt. Vô hiệu hóa các ngắt bằng cách đặt lại cờ I, thực thi chương trình con. Sau khi thực hiện quy trình xử lý ngắt, bộ vi điều khiển cho phép ngắt bằng cách đặt cờ I và khôi phục nội dung của bộ đếm chương trình, nghĩa là nó quay trở lại vị trí cũ trong chương trình mà nó bị gián đoạn.

Về lý thuyết, bộ xử lý ngắt không được làm hỏng nội dung của các thanh ghi vi điều khiển vì chúng có thể chứa dữ liệu từ chương trình đang được thực thi tại thời điểm đó. Để thực hiện điều này, khi bắt đầu chương trình con xử lý ngắt, nội dung của các thanh ghi vi điều khiển được lưu trữ trên ngăn xếp và khi kết thúc chương trình con, chúng sẽ được khôi phục. Do đó, sau khi thoát khỏi ngắt, bộ vi điều khiển sẽ có thể tiếp tục thực hiện chương trình như không có chuyện gì xảy ra. Khi lập trình trong trình biên dịch mã, người lập trình tự quy định việc lưu thanh ghi trong C, việc này được thực hiện bởi trình biên dịch.

_______________________________________________________________

Bây giờ hãy nói về bộ đếm thời gian. ATmega8535 có ba bộ định thời/bộ đếm trên bo mạch - hai bộ đếm 8 bit (T0, T2) và một bộ đếm 16 bit (T1). Chúng ta sẽ sử dụng bộ định thời/bộ đếm 8 bit T0. Bộ định thời này bao gồm ba thanh ghi - thanh ghi điều khiển TCCR0, thanh ghi đếm TCNT0 và thanh ghi so sánh OCR0. Khi bộ định thời được khởi động, thanh ghi bộ đếm TCNT0 sẽ tăng giá trị của nó lên một cho mỗi cạnh đồng hồ. Tần số xung nhịp được chọn từ một số giá trị có thể có trong thanh ghi điều khiển TCCR0. Ngoài ra, bằng cách sử dụng thanh ghi này, chế độ hoạt động của bộ hẹn giờ được thiết lập. Bộ định thời T0 có thể kích hoạt ngắt khi xảy ra sự kiện “tràn” - đây là khi thanh ghi đếm TCNT0 bị tràn và khi xảy ra sự kiện “trùng hợp” - đây là khi giá trị của thanh ghi đếm TCNT0 trở thành giá trị của thanh ghi so sánh OCR0. Các cờ cho phép các ngắt này được đặt trong thanh ghi TIMSK.
Chúng tôi sẽ định cấu hình bộ định thời/bộ đếm T0 để kích hoạt ngắt sự kiện “khớp” ở tần số 5 kHz. Trong hàm xử lý, chúng ta sẽ đảo ngược trạng thái của đầu ra vi điều khiển mà loa áp điện được kết nối. Do đó, tần số âm thanh áp điện sẽ bằng 2,5 kHz. (Đó là loa áp điện được kết nối! Đừng nhầm lẫn. Điện trở của loa áp điện phụ thuộc vào tần số và ở 2,5 KHz thường là đơn vị của Com nên có thể kết nối trực tiếp với đầu ra của vi điều khiển mà không cần điện trở giới hạn) .

Bây giờ về chương trình. Viết chương trình từng dòng một không được nữa nên tôi sẽ đưa nội dung của nó ngay. Dưới đây chúng tôi sẽ phân tích từng dòng một và mọi thứ sẽ trở nên rõ ràng. Tôi đã cố tình không sử dụng macro; chương trình này nhỏ và tôi không muốn làm nó lộn xộn.

int chủ yếu( trống rỗng )
{
//thiết lập cổng I/O
DDRD = (0<CỔNG = (1<

//thiết lập bộ đếm thời gian T0
TCCR0 = (1<TCNT0 = 0;
OCR0 = 0xc8;

//đợi kết thúc quá trình truyền byte trước đó
__enable_interrupt();

// vòng lặp chương trình chính – thăm dò nút
trong khi(1){
nếu như((PIND & (1<TIMSK = (1<khác
TIMSK = 0;
}
trở lại 0;
}

//trình xử lý ngắt cho bộ định thời T0

__ngắt trống rỗng Hẹn giờ0CompVect( trống rỗng)
{
CỔNG ^= (1<}

Cài đặt cổng

Trong mạch của chúng tôi, một nút và loa Piezo được kết nối với cổng D. Chân mà nút được kết nối phải được cấu hình làm đầu vào và phải bật điện trở kéo lên. Chân mà loa Piezo được kết nối phải được đặt thành đầu ra.

DDRD = (0<CỔNG = (1<

Đặt hẹn giờ

Chế độ hoạt động của bộ định thời T0 là CTC (reset ngẫu nhiên), tín hiệu đồng hồ là clk/8. Chúng tôi phản ánh điều này trong sổ đăng ký TCCR0

TCCR0 = (1<

Để đề phòng, chúng ta reset lại thanh ghi đếm TCNT0

Viết 0xc8 vào thanh ghi so sánh OCR0. Tại sao? Bởi vì tôi đã tính nó vào máy tính. Vâng, trên giấy tờ, phép tính này trông như thế này.
Tần số xung nhịp của vi điều khiển 8 MHz
Tín hiệu đồng hồ hẹn giờ là 8000000 Hz/8 = 1000000 Hz.
Thời gian của một đồng hồ hẹn giờ 1/1000000 = 1 µs
Thời gian của một chu kỳ tần số chúng ta cần là 1/5000 Hz = 200 μs
Có bao nhiêu tích tắc hẹn giờ phù hợp với 200 µs? 200/1 = 200 tích tắc
200 ở dạng thập lục phân = 0xc8

Để biết mô tả chi tiết về bộ định thời T0, hãy xem tài liệu về ATMega8535.

Chúng tôi đã định cấu hình bộ hẹn giờ và kích hoạt ngắt chung bằng chức năng tích hợp sẵn.

__enable_interrupt();

Nút thăm dò ý kiến

Khi không nhấn nút, đầu ra của bộ vi điều khiển được kết nối với nguồn điện thông qua một điện trở kéo lên bên trong, nghĩa là có một điện trở ở đầu ra; khi nhấn nút, đầu ra được nối đất, nghĩa là ở đó; là số 0 ở đầu ra. Để xác định xem nút có được nhấn hay không, bạn cần đọc nội dung của thanh ghi PIND và kiểm tra giá trị của bit 0 (nút được kết nối với PD0). Chúng tôi sẽ thăm dò nút trong một vòng lặp vô tận.

trong khi (1)
{
nếu như((PIND & (1<// nếu nhấn nút, bộ vi điều khiển sẽ kêu lên
}
khác {
//nếu không thì im lặng như cá
}
}

Đừng quên == không phải là toán tử gán =.

Xử lý thao tác nhấn/nhả nút

Bằng cách nhấn nút, chúng tôi sẽ kích hoạt tính năng gián đoạn của bộ hẹn giờ T0 và bằng cách nhả nút này, chúng tôi sẽ vô hiệu hóa nó. Để làm điều này, chúng ta sẽ thao tác với bit OCIE0 của thanh ghi TIMSK

TIMSK = (1<// cho phép ngắt bộ định thời T0 do sự kiện trùng hợp

TIMSK = 0; // vô hiệu hóa gián đoạn

Vì chúng ta chỉ sử dụng một bộ đếm thời gian nên không cần thiết lập hoặc đặt lại các bit riêng lẻ.

Chức năng ngắt

_____________________ Cú pháp hàm ngắt _____________________

Hàm ngắt được chỉ định bằng cách sử dụng lệnh #pragma vector= và một từ hàm __ngắt. Hàm phải thuộc loại void và không được nhận bất kỳ tham số nào.

#pragma vector = Địa chỉ
__ngắt trống rỗng Tên( trống rỗng)
{
// mã của chúng tôi nằm ở đây
}

Tên– tên hàm chúng ta tự chọn
Địa chỉ– địa chỉ vectơ ngắt, có thể được chỉ định theo số hoặc theo tên được xác định trong tệp tiêu đề vi điều khiển (iom8535.h – phần Định nghĩa vectơ ngắt)

______________________________________________________________

Đối với nhiệm vụ của chúng tôi, hàm xử lý ngắt trông như thế này

#vectơ thực dụng = TIMER0_COMP_vect
__ngắt trống rỗng Hẹn giờ0CompVect( trống rỗng)
{
CỔNG ^= (1<//đảo tín hiệu ở chân PD1
}

Vâng, đó là tất cả. Tôi hy vọng mọi thứ đều rõ ràng.
Trong bài viết tiếp theo, chúng tôi sẽ làm cho bộ vi điều khiển phát một giai điệu.