GD32VF103中断机制(向量中断)
本文基于GD32VF103 MCU简单介绍RISCV的向量中断方式, 并在RV-STAR开发板上实现验证。
完整程序下载地址:https://www.jiawei.site/downloads/gd32/gd32_irq_vectored.zip
中断机制与Vectored模式简介
中断与异常
见GD32VF103中断机制(非向量中断) 中的中断与异常。
RISC-V标准向量中断
RISC-V中断入口有两种方式:
- Direct(非向量模式):所有trap(中断+异常)统一跳到 mtvec.BASE 所指的入口。
- Vectored(向量模式):如果是异常,统一跳到mtvec.BASE所指的入口,如果 是中断,根据中断类型不同,硬件会跳到 mtvec.BASE + 4×cause 的位置。
这两种模式由 mtvec 的低两位决定:
- 00 = Direct
- 01 = Vectored
GD32VF103 ECLIC
GD32VF103不支持RISC-V标准的向量中断模式,往mtvec.MODE写入01会被忽略。 取而代之地,引入了ECLIC(Enhanced Core Local Interrupt Controller, 改进型内核中断控制器),用于管理所有的中断源。往mtvec.MODE写入11 启用ECLIC,支持每个中断配置单独配置为向量或非向量处理方式。
ECLIC向量中断模式与RISC-V标准的向量中断模式类似,也有中断向量表, 不同的是RISC-V标准的向量表中存放的是一系列跳转指令,而ECLIC的向量 表存放的是中断处理函数的地址。
向量模式中断相关CSR
ECLIC模式下mcause, mepc和mstatus的作用和RISC-V标准基本一致; mie和mip在ECLIC中断模式下不起作用。
ECLIC增加了一些寄存器,其中的i是中断编号:
- mtvt:中断向量表基地址
- clicintip[i]:中断等待标志
- clicintie[i]:中断使能
- clicintattr[i]:中断属性,
trig域指定电平或边沿触发;shv域指定使用向量/非向量处理方式
典型的中断处理流程
使用ECLIC的向量中断方式前,必须先配置中断向量表,向量表 基地址写入mtvt中,在中断发生时,硬件自动地从 mtvt + 4×cause地位置取对应中断处理函数的地址,并 跳转到中断处理函数中执行。
进入和退出中断处理函数的需要执行上下文保存和恢复。
实践:定时器中断
对本站文章从零开始创建GD32VF103 Makefile工程 的程序进行扩充,添加定时器中断的处理部分,每隔 1秒进入中断,在中断处理函数中切换控制LED的IO口 的电平状态实现LED亮灭循环。
entry.S
.section .text
.align 14
.globl _vector_base
_vector_base:
j _defaut_handler
.align 2
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word _timer_handler_entry
.word 0
.word 0
_defaut_handler:
j _defaut_handler
_timer_handler_entry:
SAVE_CONTEXT
call timer_handler
RESTORE_CONTEXT
mret
_vector_base标签的值将会写入mtvec和mtvt,发生异常时会
进入_defaut_handler;定时器中断的编号为7,所以在
_vector_base偏移4×7个字节处写入定时器中断入口的地址。
_timer_handler_entry为定时器中断入口,执行上下文
保存操作,并调用中断服务函数,在退出前执行上下文恢复。
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
#define ECLIC_ADDR_BASE 0xd2000000
#define ECLIC_CLICINTIE_OFFSET 0x1001
#define ECLIC_CLICINTATTR_OFFSET 0x1002
extern uint32_t _vector_base[];
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)_vector_base;
// ECLIC mode
asm volatile("csrw mtvec, %0" :: "r"(base | 3));
asm volatile("csrw mtvt, %0" :: "r"(base));
// vectored mode
*(volatile uint32_t *)(ECLIC_ADDR_BASE + ECLIC_CLICINTATTR_OFFSET +
7 * 4) |= 0x1;
// level trigger mode
*(volatile uint32_t *)(ECLIC_ADDR_BASE + ECLIC_CLICINTATTR_OFFSET +
7 * 4) &= 0xfffffff9;
// enable timer interrupt
*(volatile uint32_t *)(ECLIC_ADDR_BASE + ECLIC_CLICINTIE_OFFSET +
7 * 4) |= 0x1;
// enable global interrupt
asm volatile("csrs mstatus, %0" :: "r"(1 << 3));
while(1);
return 0;
}
void timer_handler()
{
// 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;
}
}
main函数执行了一些初始化操作,使用内联汇编设置mtvec和
mtvt,启用ECLIC并设置中断向量表;写定时器中断相关寄存器
clicintattr和clicintie将中断设置为向量处理方式、电平
触发并启用该中断;内联汇编设置mstatus.MIE启用全局中断。
timer_handler是定时器中断服务函数,在中断触发时,重新
设置mtime值以清除中断悬起状态标志,并翻转控制LED的IO电平。