---
title: 'RTOS 中斷管理'
disqus: kyleAlien
---
RTOS 中斷管理
===
## OverView of Content
中斷除了在裸機下很重要,在 RTOS 中也相當重要
中斷與硬體之間的耦合度很高,而且 OS 的任務調度基本上也是基於中斷事件,在 RTOS 移植的時候大部分工作也是在處理中斷
[TOC]
## MCU 核心暫存器
這邊以 ARM M4F CPU 來說,核心棧存器有 16 個,其中一個是 **SP 指針**,SP 指針又可分為 **MSP (主棧指針)**、**PSP (進程棧指針)**
**`R13` 在 ==不同模式下使用不同暫存器==**

### 運行模式 & Stack
* 說到模式,CPU 可以透過硬體設計切換三種模式 (或稱為運行狀態),分別是 **^1.^ 特權模式**、**^2.^ 非特權模式**、**^3.^ 處理模式**
1. 使用 `MSP` 指針:特權模式 (OS 內核)、處理模式 (中斷處理函數)
> 這兩種模式下可以 **獨佔 `MSP`**
2. 使用 `PSP` 指針:非特權模式 (User Thread)
> 所有 Thread **共用 `PSP` 指針**
> 
:::info
* 當中斷產生後,CPU 會自動將內核中的 `SP`、`PC`、`LR`、`R12`、`R13`、`R3 ~ R0` 這些暫存器押入棧中,**每個 Thread 都有自己的 Stack,具體要押入哪個棧要看當時的模式、使用的指針**
:::
## RTT 處理中斷
前面有說到 **每個 Thread 都有自己的 Stack 空間**,中斷發生後把很多東西保存到 Stack 上,而 Thread 可能在許多不同狀態之下 (有些 Thread 可能被掛起、等待中斷 ...); **==OS 之所以能調度 Thread 也是使用中斷機制==**
> 
1. **前導**:**中斷前導部分是由 ==硬體== 自動實現**,其中負責把常用暫存器資訊打包 (`SP`、`PC`、`LR`、`R12`、`R13`、`R3 ~ R0`) 押入棧中、也包括 **中斷嵌套次數**
2. **中斷處理函數**:使用者自訂處理函數,**該處理 ==不該是耗時操作==**
3. **後續**:根據使用者在中斷函數中的操作來決定要返回的 Thread
### 中斷處理方案 - 上半 / 底半
* **`上半/底半` 是一種中斷處理機制**,而 RTT 就是使用該機制處理中斷事件,其主要步驟如下
1. **上半**,時間敏感:
**觸發處理中斷事件的 Thread,讓後續事件異步處理**
2. **底半**,非時間敏感:
真正處理中斷事件的 Function,也就是耗時任務
> 
### 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`