GD32VF103中断机制(非向量中断)
本文基于GD32VF103 MCU简单介绍RISCV的非向量中断方式, 并在RV-STAR开发板上实现验证。
完整程序下载地址:https://www.jiawei.site/downloads/gd32/gd32_irq_direct.zip
中断机制与Direct模式简介
中断与异常
在RISC-V架构中,trap是对所有“打断当前执行流”的统一称呼,包含:
- 异常 (Exception):由指令执行引发,例如非法指令、地址对齐错误、环境调用 (ecall)。
- 中断 (Interrupt):由外部事件引发,例如定时器溢出、外设信号。
这两类事件都会导致 CPU 跳转到一个预先设定的入口地址,进入 trap 处理程序。
中断处理入口:mtvec
Machine模式下,mtvec CSR (Machine Trap Vector Base Address) 定义了trap 的入口地址,并支持两种模式:
- Direct 模式(非向量模式):所有trap都跳到同一个入口地址。
- Vectored 模式(向量模式):中断根据ID跳转到不同的偏移地址,异常仍然跳到统一入口。
这两种模式由 mtvec 的低两位决定:
- 00 = Direct
- 01 = Vectored
在Direct模式下:
- 任何中断或异常都会跳到 mtvec 所设定的同一个入口。
- 软件需要在入口里读取mcause,解析 trap 的类型和编号, 然后再分派到相应的处理函数。
这样做的好处是实现简单,入口固定,适合资源有限的 MCU。 缺点是所有 trap 共用一个入口,调度逻辑完全由软件完成。
非向量模式中断相关CSR
- 中断入口与原因
-
mtvec(Machine Trap-Vector Base Address Register)
- 保存陷入入口地址,决定中断/异常处理程序的起始点。
- 可以设置为 direct 模式(所有 trap 到同一个入口)或 vectored 模式(中断向量表)。
-
mcause(Machine Cause Register)
指示 trap 的来源:
- 最高位(bit XLEN-1):1 = 中断,0 = 异常。
- 低12位:表示具体的中断 ID 或异常原因码。
- 程序计数器保存与恢复
- mepc(Machine Exception Program Counter)
- 保存 trap 发生时的 PC。
- 中断/异常处理结束后,执行 mret 会跳回 mepc 指向的地址 (通常是触发点的下一条指令)。
- 状态与中断使能
-
mstatus(Machine Status Register)
- 关键字段:
- MIE:全局中断使能位。
- MPIE:保存进入 trap 前的中断使能状态(mret 时恢复)。
- MPP:保存 trap 前的特权级(00=U, 11=M)。
- 进入 trap 时硬件会自动修改这些位,返回时由 mret 恢复。
- 关键字段:
-
mie(Machine Interrupt Enable Register)
- 控制各个中断源的使能。常见位:
- MSIE:软件中断使能
- MTIE:定时器中断使能
- MEIE:外部中断使能
- 控制各个中断源的使能。常见位:
-
mip(Machine Interrupt Pending Register)
- 显示哪些中断正在挂起(由硬件置位,软件可读)。
典型的中断处理流程
Direct 模式下,中断的典型处理流程如下:
- CPU 硬件保存
- 将 pc 保存到 mepc。
- 将 trap 原因写入 mcause。
- 将出错地址写入 mtval(若适用)。
- 软件入口 (汇编)
- 保存通用寄存器到栈。
- 读取 mcause 判断 trap 来源。
- 跳转到相应的 C 语言处理函数。
- 中断服务函数 (C)
- 完成具体处理逻辑,例如定时器中断里更新mtime/mtimecmp,清除 pending。
- 恢复上下文并返回
- 恢复寄存器。
- mret 返回用户代码。
实践:定时器中断
对本站文章从零开始创建GD32VF103 Makefile工程 的程序进行扩充,添加定时器中断的处理部分,每隔 1秒进入中断,在中断处理函数中切换控制LED的IO口 的电平状态实现LED亮灭循环。
entry.S
.macro SAVE_CONTEXT
addi sp, sp, -33 * 4
sw x1, 0 * 4(sp)
sw x2, 1 * 4(sp)
sw x3, 2 * 4(sp)
; ...
sw x31, 30 * 4(sp)
csrr t0, mepc
sw t0, 31 * 4(sp)
csrr t0, mstatus
sw t0, 32 * 4(sp)
.endm
.macro RESTORE_CONTEXT
lw t0, 31 * 4(sp)
csrw mepc, t0
lw t0, 32 * 4(sp)
csrw mstatus, t0
lw x1, 0 * 4(sp)
lw x2, 1 * 4(sp)
lw x3, 2 * 4(sp)
; ...
lw x31, 30 * 4(sp)
addi sp, sp, 33 * 4
.endm
.section .text
.align 6 // the base must be 64 bytes alignment
.globl _trap_entry
_trap_entry:
SAVE_CONTEXT
csrr a0, mcause
call handle_trap
RESTORE_CONTEXT
mret
_trap_entry是中断处理的入口,执行
保存现场 → 调用 C 层处理函数 → 恢复现场 → 返回。
调用C编写的handle_trap函数之前,读取mcause CSR
作为handle_trap的函数参数以对中断的原因进行判断。
main.c
#include <stdint.h>
#define RCU_APB2EN 0x40021018
#define GPIOA_CTL0 0x40010800
#define GPIOA_OCTL 0x4001080C
#define TIMER_FREQ ((uint32_t)8000000/4)
#define TIMER_CTRL_ADDR 0xd1000000
#define TIMER_MTIME 0x0
#define TIMER_MTIMECMP 0x8
extern void _trap_entry();
void gpio_init()
{
// enable GPIOA clock
*(uint32_t *)RCU_APB2EN |= (uint32_t)1 << 2;
// set PA1 as output push pull
*(uint32_t *)GPIOA_CTL0 = *(uint32_t *)GPIOA_CTL0 & ((uint32_t)0xffffff0f) |
(uint32_t)1 << 4;
// PA1 output low
*(uint32_t *)GPIOA_OCTL &= (uint32_t)0xfffffffd;
}
void timer_init()
{
*(volatile uint32_t *)(TIMER_CTRL_ADDR + TIMER_MTIME + 4) = 0;
*(volatile uint32_t *)(TIMER_CTRL_ADDR + TIMER_MTIME) = 0;
*(volatile uint32_t *)(TIMER_CTRL_ADDR + TIMER_MTIMECMP + 4) = 0;
*(volatile uint32_t *)(TIMER_CTRL_ADDR + TIMER_MTIMECMP) = TIMER_FREQ;
}
int main()
{
gpio_init();
timer_init();
uint32_t base = (uint32_t)_trap_entry;
asm volatile("csrw mtvec, %0" :: "r"(base));
asm volatile("csrs mie, %0" :: "r"(1 << 7));
asm volatile("csrs mstatus, %0" :: "r"(1 << 3));
while(1);
return 0;
}
void handle_trap(uint32_t mcause)
{
if((mcause & 0x80000000) && (mcause & 0xfff) == 7){
// reset mtime
*(volatile uint32_t *)(TIMER_CTRL_ADDR + TIMER_MTIME + 4) = 0;
*(volatile uint32_t *)(TIMER_CTRL_ADDR + TIMER_MTIME) = 0;
// toggle PA1 output
if(*(uint32_t *)GPIOA_OCTL & (uint32_t)0b10){
*(uint32_t *)GPIOA_OCTL &= (uint32_t)0xfffffffd;
} else{
*(uint32_t *)GPIOA_OCTL |= (uint32_t)0b10;
}
} else{
asm volatile("nop");
}
}
main函数执行了一些初始化操作,使用内联汇编设置mtvec,
启用全局中断和定时器中断。
中断处理函数handle_trap判断中断的原因,如果发生了定时器
中断,则重新设置mtime的值以清除中断悬起状态标志,并翻转
控制LED的IO电平。