--- title: 'RTOS 中斷管理' disqus: kyleAlien --- RTOS 中斷管理 === ## OverView of Content 中斷除了在裸機下很重要,在 RTOS 中也相當重要 中斷與硬體之間的耦合度很高,而且 OS 的任務調度基本上也是基於中斷事件,在 RTOS 移植的時候大部分工作也是在處理中斷 [TOC] ## MCU 核心暫存器 這邊以 ARM M4F CPU 來說,核心棧存器有 16 個,其中一個是 **SP 指針**,SP 指針又可分為 **MSP (主棧指針)**、**PSP (進程棧指針)** **`R13` 在 ==不同模式下使用不同暫存器==** ![](https://i.imgur.com/jeUaffS.png) ### 運行模式 & Stack * 說到模式,CPU 可以透過硬體設計切換三種模式 (或稱為運行狀態),分別是 **^1.^ 特權模式**、**^2.^ 非特權模式**、**^3.^ 處理模式** 1. 使用 `MSP` 指針:特權模式 (OS 內核)、處理模式 (中斷處理函數) > 這兩種模式下可以 **獨佔 `MSP`** 2. 使用 `PSP` 指針:非特權模式 (User Thread) > 所有 Thread **共用 `PSP` 指針** > ![](https://i.imgur.com/qq7Gmhn.png) :::info * 當中斷產生後,CPU 會自動將內核中的 `SP`、`PC`、`LR`、`R12`、`R13`、`R3 ~ R0` 這些暫存器押入棧中,**每個 Thread 都有自己的 Stack,具體要押入哪個棧要看當時的模式、使用的指針** ::: ## RTT 處理中斷 前面有說到 **每個 Thread 都有自己的 Stack 空間**,中斷發生後把很多東西保存到 Stack 上,而 Thread 可能在許多不同狀態之下 (有些 Thread 可能被掛起、等待中斷 ...); **==OS 之所以能調度 Thread 也是使用中斷機制==** > ![](https://i.imgur.com/GyFSbEx.png) 1. **前導**:**中斷前導部分是由 ==硬體== 自動實現**,其中負責把常用暫存器資訊打包 (`SP`、`PC`、`LR`、`R12`、`R13`、`R3 ~ R0`) 押入棧中、也包括 **中斷嵌套次數** 2. **中斷處理函數**:使用者自訂處理函數,**該處理 ==不該是耗時操作==** 3. **後續**:根據使用者在中斷函數中的操作來決定要返回的 Thread ### 中斷處理方案 - 上半 / 底半 * **`上半/底半` 是一種中斷處理機制**,而 RTT 就是使用該機制處理中斷事件,其主要步驟如下 1. **上半**,時間敏感: **觸發處理中斷事件的 Thread,讓後續事件異步處理** 2. **底半**,非時間敏感: 真正處理中斷事件的 Function,也就是耗時任務 > ![](https://i.imgur.com/2dx0wew.png) ### RTT 中斷 - API * 以下是 RTT 提供有關於中斷的 函數 | 功能 | API | 說明 | | -------- | -------- | -------- | | 裝載中斷服務 | `rt_hw_interrupt_install` | 每個中斷都需要開啟 (每個中斷向量都需要開啟) | | 中斷屏蔽 | `rt_hw_interrupt_mask` | 要忽略的中斷號 | | 中斷使能 | `rt_hw_interrupt_unmask` | 要開啟的中斷號 | | 中斷開 | `rt_hw_interrupt_disable` | 關閉中斷 | | 中斷關 | `rt_hw_interrupt_enable` | 返回前一次 `rt_hw_interrup_disable` 的狀態 (須保存該值) | | 進入中斷處理 | `rt_hw_interrupt_enter` | | | 離開中斷處理 | `rt_hw_interrupt_leave` | | | 獲取當前中斷嵌套深度 | `rt_hw_interrupt_get_nest` | 獲取當前中斷嵌套深度 (可能在進入該中斷之前已經有 3 個中斷已被觸發) | ## RTT 中斷 ### 實作 Button 觸發中斷 - 配合 MessageQueue :::info 實現一個 Button 點擊下去後 LED 就會亮的功能 (放開 Button 後 LED 就滅) ::: 1. 創建 `interrupt.h`:創建一些共用結構、Enum、include File... ```c= // interrupt.h #ifndef __INTERRUPT #define __INTERRUPT #include <rtthread.h> #include <rtdevice.h> #include "drv_common.h" #include "drv_gpio.h" #define BUTTON_PIN GET_PIN(A, 0) #define LED2_PIN GET_PIN(D, 13) #define LED3_PIN GET_PIN(D, 14) #define LED4_PIN GET_PIN(D, 15) extern void interrupt_init(void); extern void create_thread_mail(void); enum CMD { LED_ON, LED_OFF }; struct mail_info_t { enum CMD cmd; }; #endif ``` 2. 創建 `interrupt.c`:實作中斷處理函數 `button_isr`、創建處理中斷的 Thread、創建 MessageQueue 通知 Thread (中斷發生時用來發送訊息) :::info 以下在中斷函數中,對 MessageQueue 發送訊息,來通知另一個 Thread 運作 ::: ```c= #include "interrupt.h" #define MAX_MSG_NUM 32 static rt_mq_t mail_queue = RT_NULL; // 中斷處理函數 (不做耗時工作) void button_isr(void) { int val = rt_pin_read(BUTTON_PIN); struct mail_info_t info = {0}; if(val == 1) { info.cmd = LED_ON; } else { info.cmd = LED_OFF; } // 使用 Message Queue 傳遞訊息 rt_mq_send(mail_queue, &info, sizeof(struct mail_info_t)); } // Thread 處理函數 static void handle_thread() { while(1) { struct mail_info_t info; // 等待 Message Queue 訊息 (Block) rt_mq_recv(mail_queue, &info, sizeof(struct mail_info_t), RT_WAITING_FOREVER); if(info.cmd == LED_ON) { rt_pin_write(LED2_PIN, PIN_LOW); } else if(info.cmd == LED_OFF){ rt_pin_write(LED2_PIN, PIN_HIGH); } } } static void create_thread() { rt_thread_t tid; // 創建 Thread tid = rt_thread_create("Thread_interrupt", handle_thread, RT_NULL, 256, 8, 2); if(tid == RT_NULL) { rt_kprintf("Create thread fail\n"); return; } rt_thread_startup(tid); } static void create_mq_box() { // 創建 Mail Queue mail_queue = rt_mq_create("MB_interrupt_queue", sizeof(struct mail_info_t), MAX_MSG_NUM, RT_IPC_FLAG_FIFO); if(mail_queue == RT_NULL) { rt_kprintf("Create mail queue fail\n"); return; } } void create_thread_mq(void) { create_mq_box(); create_thread(); } void interrupt_init(void) { // 註冊 GPIO 中斷處理函數為 `button_isr` rt_pin_attach_irq(BUTTON_PIN, PIN_IRQ_MODE_RISING_FALLING, (void *)button_isr, NULL); // 開啟中斷 rt_pin_irq_enable(BUTTON_PIN, PIN_IRQ_ENABLE); } ``` 3. **Main** 函數 ```c= #include "interrupt.h" int main(void) { rt_uint32_t speed = 200; /* set led2 pin mode to output */ rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT); /* set led3 pin mode to output */ rt_pin_mode(LED3_PIN, PIN_MODE_OUTPUT); /* set led4 pin mode to output */ rt_pin_mode(LED4_PIN, PIN_MODE_OUTPUT); // 關閉所有 LED rt_pin_write(LED2_PIN, PIN_HIGH); rt_pin_write(LED3_PIN, PIN_HIGH); rt_pin_write(LED4_PIN, PIN_HIGH); interrupt_init(); create_thread_mq(); while (1) { // nothing } } ``` ### GPIO 中斷處理函數 * 在上面範例我們有用到 `rt_pin_attach_irq`、`rt_pin_irq_enable` 來註冊並開啟 GPIO 的中斷,現在我們要稍微分析一下這兩個函數 ```c= void interrupt_init(void) { rt_pin_attach_irq(BUTTON_PIN, PIN_IRQ_MODE_RISING_FALLING, (void *)button_isr, NULL); rt_pin_irq_enable(BUTTON_PIN, PIN_IRQ_ENABLE); } ``` 1. **`rt_pin_attach_irq` 函數**:可以看到該函數會把我們設定的中斷處理函數傳給 `_hw_pin.ops` 中的 `pin_attach_irq` 函數 ```c= // pin.c // @ 查看 rt_device_pin static struct rt_device_pin _hw_pin; rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args) { RT_ASSERT(_hw_pin.ops != RT_NULL); if (_hw_pin.ops->pin_attach_irq) { // @ 查看 _hw_pin 變量 return _hw_pin.ops->pin_attach_irq(&_hw_pin.parent, pin, mode, hdr, args); } return -RT_ENOSYS; } // ----------------------------------------------------------------- // pin.h struct rt_device_pin { struct rt_device parent; // @ 查看 rt_pin_ops const struct rt_pin_ops *ops; }; struct rt_pin_ops { ... 省略其他函數指標 rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args); }; ``` 2. **`rt_pin_ops` 結構被初始化的時機**:在 `rt_hw_pin_init` 函數時初始化,而該函數又是由組語 `INIT_BOARD_EXPORT` 宏調用 (使用偽指令) ```c= // drv_gpio.c const static struct rt_pin_ops _at32_pin_ops = { at32_pin_mode, at32_pin_write, at32_pin_read, // @ 查看 at32_pin_attach_irq 函數 at32_pin_attach_irq, at32_pin_dettach_irq, at32_pin_irq_enable, at32_pin_get, }; int rt_hw_pin_init(void) { ... // @ rt_device_pin_register return rt_device_pin_register("pin", &_at32_pin_ops, RT_NULL); } INIT_BOARD_EXPORT(rt_hw_pin_init); ``` 3. **最後我們知道我們設定的中斷函數被傳入 `at32_pin_attach_irq` 函數**:設定到一個 Array 中儲存,這個 Array 會由 OS 來調用,最終呼叫使用者的中斷函數 ```c= // drv_gpio.c static struct rt_pin_irq_hdr pin_irq_handler_tab[] = { // 16 根外部中斷線 {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, {-1, 0, RT_NULL, RT_NULL}, }; static rt_err_t at32_pin_attach_irq(struct rt_device *device, rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args) { ... pin_irq_handler_tab[irqindex].pin = pin; // 設定給一個 Array pin_irq_handler_tab[irqindex].hdr = hdr; pin_irq_handler_tab[irqindex].mode = mode; pin_irq_handler_tab[irqindex].args = args; rt_hw_interrupt_enable(level); return RT_EOK; } ``` ## Appendix & FAQ :::info ::: ###### tags: `RTOS`