Ok, sau ngày thứ 3, chúng ta đã làm quen với việc đọc nút nhấn bằng Polling. Tuy nhiên, như đã trình bày ở ngày trước việc liên tục đọc trạng thái nút nhấn trong vòng lặp là rất lãng phí vì vi điều khiển phải liên tục đứng đợi trạng thái nút nhấn trả về để thực hiện chương trình, ở ngày 4, chúng ta sẽ được làm quen với ngắt (Interrupt). Đây là một chức năng cực kì mạnh trên hầu hết các loại vi điều khiển.
Ngắt (Interrupt) là gì?
Hãy tưởng tượng bạn đang có một đơn hàng ngày Tết.
Với Polling: Cứ 5 phút bạn sẽ phải chạy ra cửa nhà để kiểm tra đơn hàng đã đến chưa
-> Hậu quả: Không làm được các việc khác, phải chạy đi chạy lại liên tục
Với Interrupt: Bạn ăn, ngủ, xem phim, chơi game và đợi shipper gọi để ra cửa lấy hàng
-> Lợi ích: Bạn làm được rất nhiều việc và phản hồi ngay lập tức khi shipper gọi.
Đây là một ví dụ kinh điển về sự khác nhau giữa Interrupt và Polling. Trên vi điều khiển, khi dùng Interrupt, CPU có thể làm nhiều việc khác hay thậm chí là đi ngủ (SLEEP MODE) và nó chỉ hoạt động khi có tín hiệu ngắt.
Cơ chế ngắt trên CC1312R7
CC1312R7 sử dụng lõi ARM-cotex M4, trong kiến trúc này, hệ thống ngắt được quản lí bởi các vector ngắt (NVIC) hay (Nested Vectored Interrupt Controller).
Trong khuôn khổ sử dụng SimpleSDK (Ti Drivers), quy trình xử lý các ngắt được đơn giản hóa thành 4 bước sau:
– Phát hiện sự kiện vật lí: Khi nhấn nút, vi điều khiển phát hiện điện áp thay đổi
– Hardware interrupt: CPU tạm dừng tác vụ, dòng code đang chạy
– Driver Dispatcher: Driver GPIO sẽ hứng lấy ngắt
– Callback Function: Thực thi hàm
Một số loại ngắt thường gặp:
Ngắt cạnh lên (Rising Edge): Kích hoạt khi tín hiệu chuyển từ thấp sang cao
Ngắt cạnh xuống (Fall Edge): Kích hoạt khi tín hiệu chuyển từ cao xuống thấp
Ngắt 2 cạnh: Kích hoạt khi tín hiệu chuyển từ thấp sang cao và từ cao xuống thấp
Coding time
Ta sẽ tiếp tục với ví dụ kinh điển của ngày 3, nhấn nút nhấn, đèn đảo trạng thái, tất nhiên ta sẽ không dùng Polling mà sẽ dùng Interrupt.
Bước 0: Tạo project mới, chọn SDK …, các bước này đã quá quen ở các ngày trước
Bước 1: Thiết lập sysconfig, ta sẽ cấu hình như hình dưới đây


Lưu ý rằng loại Pull và Interrupt trigger cần “khớp” với nhau, ví dụ bạn chọn pull up thì để detect nút có được nhấn hay không thì trigger phải là Falling Edge. Nếu chọn ngược lại thì hành động sẽ bị đảo lộn, thay vì nhấn nút đổi trạng thái sẽ trở thành nhả nút đổi trạng thái.
Bước 3: Viết code như đoạn mã dưới đây
#include <unistd.h>
#include <stdint.h>
#include <stddef.h>
/* Driver Header files */
#include <ti/drivers/GPIO.h>
// #include <ti/drivers/I2C.h>
// #include <ti/drivers/SPI.h>
// #include <ti/drivers/Watchdog.h>
/* Driver configuration */
#include "ti_drivers_config.h"
/*
* ======== mainThread ========
*/
void ButtonCallback(uint_least8_t index)
{
GPIO_toggle(LED_RED);
}
void *mainThread(void *arg0)
{
GPIO_setCallback(BUTTON, ButtonCallback);
GPIO_enableInt(BUTTON);
}
Giải thích code: Trong đoạn code này, có thể thấy là ta không còn sử dụng vòng lặp vô tận, tuy nhiên code vẫn chạy đúng yêu cầu ban đầu.
Hàm void ButtonCallback(uint_least8_t index): Đây là hàm xử lí ngắt, hay nói cách khác là tác vụ mà chúng ta muốn thực hiện khi nhận ngắt, ở đây là toggle trạng thái LED, hàm này sẽ được gọi mỗi khi vi điều khiển đọc được tín hiệu ngắt. Vì ngắt cần chạy rất nhanh, do đó trong ngắt sẽ không được để các tác vụ tốn quá nhiều thời gian cũng như các hàm ngưng chương trình như sleep, usleep. Thông thường, các hàm xử lí ngắt thường được dùng để ghi nhận và chuyển đổi trạng thái, những tác vụ khác sẽ được xử lí trong Task, hoặc Polling dựa trên trạng thái được cập nhật từ hàm xử lí ngắt
GPIO_setCallback(BUTTON, ButtonCallback); Đây là hàm thiết lập ngắt gắn với nút nhấn và đăng kí hàm xử lí ngắt (Callback)
GPIO_enableInt(BUTTON); Đây là hàm cho phép ngắt hoạt động, nếu không có dòng này, ngắt sẽ không hoạt động, bạn có thể thử xóa dòng này và Flash code lại để kiểm tra.
Mở rộng: Nút nhấn cơ học khi nhấn sẽ có rung động, hay nói cách khác đường tính hiệu cạnh sẽ không phải là một đường thẳng mà là các sọc răng cưa tín hiệu. Đôi khi nhấn nút mà đèn bị nháy 2 lần chính là do phím bị rung. Do ngắt chạy rất nhanh nên nó sẽ bắt toàn bộ các rung này và đôi khi làm đèn bị chớp tắt ngay cả trong 1 lần nhấn. Để khắc phục tình trạng này có thể có nhiều phương pháp như dùng thêm phần cứng (thêm tụ ở 2 đầu nút nhấn), hoặc xử lí mềm như lock nút nhấn bằng timer. Ngoài ra TI có lớp thư viện cao hơn của GPIO thuộc TI Driver để xử lí nút nhấn. Ta sẽ dùng lock Timer khi đến bài Timer. Ở bài này để đơn giản lướt qua các rung, chỉ cần thêm 1 lệnh nào đó cho ngắt tốn thêm 1 khoảng thời gian để xử lí, trước khi nó kịp đọc các tín hiệu rung ban đầu.
Homework: Viết thêm vào code vòng lặp While(1), thêm hàm usleep, sleep vào bên trong vòng lặp while và flash lại code. Hãy nêu hiện tượng và giải thích tại sao