Bài 49.226 phút đọc
Bảng vector và NVIC
Sau bài này bạn sẽ làm được
- Viết bảng vector ngắt và đặt nó vào đúng phân đoạn
- Hiểu cơ chế hàm yếu và vì sao viết sai tên là ISR không chạy
- Đặt độ ưu tiên và biết số nhỏ là ưu tiên cao
- Giải thích ngắt lồng nhau và nhóm ưu tiên
Bảng vector là cầu nối giữa số hiệu ngắt và hàm bạn viết. Cầu nối đó hoạt động bằng cách khớp tên hàm, và một chữ cái sai làm ISR không bao giờ chạy mà trình biên dịch không hề cảnh báo.
#Bảng vector
Bảng vector
Một mảng con trỏ hàm đặt ở đầu Flash. Phần tử thứ
n chứa địa chỉ hàm xử lý ngoại lệ số n. Lõi tra bảng này mỗi lần có ngoại lệ.startup.c
#include <stdint.h>
extern uint32_t _estack; /* do tệp linker định nghĩa */
void Reset_Handler(void);
void Default_Handler(void);
/* Ngoại lệ của lõi Cortex-M */
void NMI_Handler(void) __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
void MemManage_Handler(void) __attribute__((weak, alias("Default_Handler")));
void BusFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
void UsageFault_Handler(void) __attribute__((weak, alias("Default_Handler")));
void SVC_Handler(void) __attribute__((weak, alias("Default_Handler")));
void DebugMon_Handler(void) __attribute__((weak, alias("Default_Handler")));
void PendSV_Handler(void) __attribute__((weak, alias("Default_Handler")));
void SysTick_Handler(void) __attribute__((weak, alias("Default_Handler")));
/* Ngắt từ ngoại vi, tên do nhà sản xuất quy định */
void WWDG_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void EXTI0_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void TIM2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
void USART1_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
/* ... còn hàng chục hàm nữa ... */
__attribute__((section(".isr_vector"), used))
void (*const bang_vector[])(void) = {
(void (*)(void))&_estack, /* 0: đỉnh ngăn xếp ban đầu */
Reset_Handler, /* 1: reset */
NMI_Handler, /* 2 */
HardFault_Handler, /* 3 */
MemManage_Handler, /* 4 */
BusFault_Handler, /* 5 */
UsageFault_Handler, /* 6 */
0, 0, 0, 0, /* 7 tới 10: dành riêng */
SVC_Handler, /* 11 */
DebugMon_Handler, /* 12 */
0, /* 13: dành riêng */
PendSV_Handler, /* 14 */
SysTick_Handler, /* 15 */
/* Từ đây là ngắt ngoại vi, IRQ0 trở đi */
WWDG_IRQHandler, /* 16, IRQ0 */
/* ... */
TIM2_IRQHandler, /* 44, IRQ28 */
/* ... */
USART1_IRQHandler, /* 53, IRQ37 */
};
void Default_Handler(void)
{
for (;;) { } /* xem mục cuối bài về cách tốt hơn */
}Tệp linker đặt bảng vector đúng chỗ
linker.ld
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 96K
}
_estack = ORIGIN(RAM) + LENGTH(RAM); /* đỉnh ngăn xếp */
SECTIONS
{
.isr_vector :
{
KEEP(*(.isr_vector)) /* KEEP: đừng xóa dù không ai gọi */
} > FLASH /* đặt ở ĐẦU Flash */
.text :
{
*(.text*)
*(.rodata*)
} > FLASH
/* ... phần còn lại, xem Bài 50.4 ... */
}#Hàm yếu và cái bẫy tên sai
Ký hiệu yếu
Một định nghĩa hàm được đánh dấu
weak sẽ bị thay thế nếu ở nơi khác có định nghĩa mạnh cùng tên. Nếu không có, định nghĩa yếu được dùng.co-che.c
/* Trong startup.c: dinh nghia YEU, tro toi Default_Handler */
void TIM2_IRQHandler(void) __attribute__((weak, alias("Default_Handler")));
/* Trong main.c: dinh nghia MANH cua ban */
void TIM2_IRQHandler(void)
{
TIM2->SR &= ~(1u << 0);
++g_tick;
}
/* Trinh lien ket thay ban co dinh nghia manh nen dung no,
va bang vector tro toi ham cua ban. Khong can khai bao gi them. */Default_Handler nên nói cho bạn biết chuyện gì
default-handler.c
#include <stdint.h>
/* Doc so hieu ngoai le dang chay tu thanh ghi IPSR. */
void Default_Handler(void)
{
uint32_t ipsr;
__asm volatile ("mrs %0, ipsr" : "=r" (ipsr));
uint32_t so_ngoai_le = ipsr & 0x1FFu;
int32_t so_irq = (int32_t)so_ngoai_le - 16;
uart_gui_chuoi("NGAT KHONG CO ISR: exception ");
uart_in_uint(so_ngoai_le);
if (so_irq >= 0) {
uart_gui_chuoi(", IRQ ");
uart_in_int(so_irq);
}
uart_gui_chuoi("\r\n");
for (;;) { }
}terminal
picocom -b 115200 /dev/ttyUSB0
NGAT KHONG CO ISR: exception 44, IRQ 28
# Tra bảng vector: IRQ28 là TIM2
grep -n 'IRQ28\|TIM2_IRQ' startup.c
58: TIM2_IRQHandler, /* 44, IRQ28 */
#NVIC: bật, tắt, ưu tiên
NVIC
Nested Vectored Interrupt Controller. Khối phần cứng trong lõi Cortex-M quản lý mọi ngắt ngoại vi: bật, tắt, đặt độ ưu tiên, và quyết định ngắt nào được chạy khi nhiều ngắt cùng chờ.
nvic.c
#include "stm32f4.h"
void ngat_khoi_tao(void)
{
/* 1. Bật ngắt Ở NGOẠI VI. Không có bước này thì ngoại vi
không bao giờ gửi tín hiệu tới NVIC. */
USART1->CR1 |= (1u << 5); /* RXNEIE */
TIM2->DIER |= (1u << 0); /* UIE */
/* 2. Đặt độ ưu tiên. SỐ NHỎ là ưu tiên CAO. */
NVIC_SetPriority(USART1_IRQn, 1u);
NVIC_SetPriority(TIM2_IRQn, 3u);
/* 3. Bật ngắt ở NVIC */
NVIC_EnableIRQ(USART1_IRQn);
NVIC_EnableIRQ(TIM2_IRQn);
/* 4. Bật ngắt toàn cục, thường đã bật sẵn sau reset */
__enable_irq();
}| Hàm CMSIS | Việc làm |
|---|---|
| NVIC_EnableIRQ(irq) | Cho phép ngắt này tới lõi |
| NVIC_DisableIRQ(irq) | Chặn ngắt này, sự kiện vẫn được ghi nhớ |
| NVIC_SetPriority(irq, p) | Đặt độ ưu tiên, số nhỏ là cao |
| NVIC_GetPriority(irq) | Đọc lại độ ưu tiên |
| NVIC_SetPendingIRQ(irq) | Kích hoạt ngắt bằng phần mềm |
| NVIC_ClearPendingIRQ(irq) | Xóa yêu cầu ngắt đang chờ |
| NVIC_GetPendingIRQ(irq) | Hỏi ngắt này có đang chờ không |
| NVIC_SystemReset() | Khởi động lại chip |
#Ngắt lồng nhau
Ví dụ lồng nhau
/* TIM2 uu tien 3, USART1 uu tien 1 (cao hon).
Thoi gian ->
main |----| |--------
TIM2 ISR |------| |--|
USART1 ISR |--------|
1. main dang chay, ngat TIM2 toi -> vao TIM2_IRQHandler
2. Dang trong TIM2 ISR, ngat USART1 toi
USART1 uu tien CAO hon -> ngat luon TIM2 ISR
3. USART1 ISR chay xong -> quay ve TIM2 ISR dung cho vua dung
4. TIM2 ISR chay xong -> quay ve main
Neu USART1 uu tien THAP hon hoac BANG TIM2 thi no phai cho
TIM2 ISR chay xong. */Nhóm ưu tiên
nhom-uu-tien.c
/* STM32F4 cai dat 4 bit uu tien, tuc 16 muc.
Bon bit do chia thanh hai phan:
uu tien CHIEM QUYEN (preempt): quyet dinh ai ngat duoc ai
uu tien PHU (sub): quyet dinh ai chay truoc khi CUNG cho
Diem chia do truong PRIGROUP trong SCB->AIRCR quyet dinh,
va NVIC_SetPriorityGrouping ghi thang gia tri do vao. */
NVIC_SetPriorityGrouping(3u); /* 4 bit chiem quyen, 0 bit phu */
NVIC_SetPriorityGrouping(5u); /* 2 bit chiem quyen, 2 bit phu */
NVIC_SetPriorityGrouping(7u); /* 0 bit chiem quyen, 4 bit phu */
/* Voi PRIGROUP 7: KHONG ngat nao ngat duoc ngat nao.
Day la cach don gian nhat, va du cho hau het du an. */| Đối số CMSIS | Bit chiếm quyền | Bit phụ | Số mức lồng nhau |
|---|---|---|---|
| 0 tới 3 | 4 | 0 | 16 |
| 4 | 3 | 1 | 8 |
| 5 | 2 | 2 | 4 |
| 6 | 1 | 3 | 2 |
| 7 | 0 | 4 | 1, tức không lồng nhau |
#Chọn mức ưu tiên
| Mức | Dùng cho | Ví dụ | Yêu cầu |
|---|---|---|---|
| 0, cao nhất | Sự kiện tuyệt đối không được lỡ | Dừng khẩn cấp, phát hiện mất điện, bảo vệ quá dòng | ISR dưới 20 chu kỳ |
| 1 | Giao tiếp tốc độ cao | UART ở 921600 baud, SPI, encoder | ISR dưới 50 chu kỳ |
| 2 | Tick hệ thống và điều khiển | SysTick, vòng điều khiển PID | ISR dưới vài trăm chu kỳ |
| 3, thấp nhất thường dùng | Sự kiện chậm, không gấp | Nút bấm, cảm biến nhiệt độ, DMA báo xong | Không có yêu cầu chặt |
Kích hoạt ngắt bằng phần mềm
ngat-phan-mem.c
/* Mau rat huu ich: ISR uu tien cao lam phan gap,
roi kich hoat mot ngat uu tien THAP de lam phan cham. */
void USART1_IRQHandler(void) /* uu tien 1, phai nhanh */
{
uint8_t b = (uint8_t)USART1->DR;
ring_them(b);
if (b == '\n')
NVIC_SetPendingIRQ(EXTI0_IRQn); /* nho ISR khac xu ly */
}
void EXTI0_IRQHandler(void) /* uu tien 3, duoc phep cham */
{
EXTI->PR = (1u << 0); /* xoa co */
phan_tich_dong_lenh(); /* viec nang, hang tram chu ky */
}
/* Ky thuat nay goi la nua duoi, muon tu nhan Linux.
Uu diem so voi dat co cho vong lap chinh: no chay
NGAY khi khong con ngat nao uu tien cao hon,
thay vi cho vong lap chinh quay lai. */Tự làm thử
- Viết tệp startup với bảng vector đầy đủ cho chip của bạn, kèm hàm yếu cho mọi ngắt.
- Cố tình viết sai tên một ISR, chạy chương trình, rồi dùng
nmtìm ra chữWchỉ ra lỗi. - Cài
Default_Handlerin ra số hiệu ngoại lệ, rồi gây một ngắt không có ISR và đọc thông báo. - Đặt hai ngắt ở hai mức ưu tiên khác nhau, bật một chân GPIO trong mỗi ISR, và quan sát lồng nhau bằng bộ phân tích logic.
- Đo mức dùng ngăn xếp cao nhất với nhóm ưu tiên 4 và với nhóm 0, so hai con số.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Bảng vector là mảng con trỏ hàm ở đầu Flash, và ô đầu tiên chứa đỉnh ngăn xếp chứ không phải địa chỉ hàm.
- Cơ chế hàm yếu cho phép ghi đè ISR chỉ bằng cách định nghĩa hàm cùng tên. Sai một chữ là ISR không bao giờ chạy mà không có cảnh báo nào.
- Bốn công tắc mắc nối tiếp: clock ngoại vi, bit cho phép ngắt của ngoại vi,
NVIC_EnableIRQ, vàPRIMASK. - Số ưu tiên nhỏ là ưu tiên cao. Ngắt cùng mức không lồng nhau, và nhóm ưu tiên 4 tắt hẳn lồng nhau.
- Cho
Default_Handlerin ra số hiệu ngoại lệ là cải tiến rẻ nhất và hữu ích nhất trong toàn bộ phần khởi động.