笔者有空学习了GD32的RSICV芯片,故来总结一下。
GD32 RISCV芯片系列
GD:GidaDeivce,兆易创新,生产的MCU的内核架构系列如下图所述,主要是ARM架构的,Cortex-M23、M3、M4、M33以及M7,然后也涉及到了RISC-V架构的,笔者今天就来聊一下RISCV架构的MCU产品。
GD32的RISC-V的芯片类型主要有以下几种:
特点如下:
GD32VF103系列MCU采用了全新的基于开源指令集架构RISC-V的Bumblebee处理器内核,是兆易创新(Gigadevice)携手中国领先的RISC-V处理器内核IP和解决方案厂商芯来科技(Nuclei System Technology),面向物联网及其它超低功耗场景应用自主联合开发的一款商用RISC-V处理器内核。
- GD32VF103系列RISC-V MCU提供了108MHz的运算主频,
- 16KB到128KB的片上闪存和6KB到32KB的SRAM缓存,
- gFlash®专利技术支持内核访问闪存高速零等待。
- Bumblebee内核还内置了单周期硬件乘法器、硬件除法器和加速单元应对高级运算和数据处理的挑战。
GD32 RISCV VF103芯片
来具体看一下VF103 芯片的一些特点:RISC-V处理器适用于低能耗、小面积的嵌入式应用,具有简单的动态分支预测、指令预取缓冲区 和 本 地 内 存 等 多 种 高 效 微 架 构 特 点 。它支持32个通用寄存器(GPRs)和用于性能/面积权衡的快速乘法器:
- RISC-V兼容小端RV32 IMAC(32 GPRS) ;
- 可配置的2级管道,针对低门数和高频率进行了优化;
- 机器(M)和用户(U)权限级别支持;
- 支持单周期硬件乘法器和多周期硬件除法器;
- 硬件支持加载/存储未对齐;
- 硬件支持原子指令;
- 支持不可屏蔽中断(NMI) ;
- 动态分支预测和指令预取缓冲器用于加速控制代码;
- 目前最先进的微架构设计以权衡面积和性能要求;
- 支持WFI(等待中断) ;
- WFE(等待事件)支持;
- 中断优先级可配置/可编程;
- 适用于实时性能的增强矢量中断处理;
- 支持中断优先抢占;
- 支持咬尾中断;
- 标准4线JTAG调试端口;
- 支持交互式调试功能;
- 支持4个硬件断点触发器
具体架构如下图:
ICode和DCode用来访问Flash的数据,系统总线通过AHB矩阵访问,SRAM、AHB、APB1和APB2总线。 - DMA可以通过AHB矩阵访问memory,可以做内存拷贝。
- AHB挂载一些高速外设:FMC、USB_FS、CRC以及RCU
- APB2挂载一些次高速外设:GPIO、USART0,SPI0、Timer0,EXIT以及ADC0和1
- APB1挂载一些低俗外设:USART1和2,UART2和UART3,CAN0/1、RTC、TIMER1-6等等
- ECLIC:中断控制器,外设的中断可以到这里。
memory map位置:与GD32其他系列类似:
- Flash位置:0x08000000
- SRAM位置:0x20000000
- 外设地址:0x40000000开始
启动boot设置也一样:
接着有两个地址可以读一些Flash以及Device id的信息。 - 0x1FFF F7E0:Flash以及SRAM大小
- 0x1FFF F7E8:device ID信息(96位)
Flash通过FMC去操作,从上图的架构图也可以看出,主要写和擦,读可以直接通过memory map的方式,以1KB为一个page。
接着介绍一下时钟:与GD32其他芯片类似,与STM32的时钟图也类似,这列不多介绍。
GD32 RISCV VF103中断机制
这个中断机制属于芯片架构内部的部分,需要介绍一下。采用ECLIC中断模式,改进型内核中断控制器(Enhanced Core Local Interrupt
Controller, ECLIC)。
中断类型:
其支持外部中断和内部中断。
- 外部中断主要是外设比如UART、Timer的一些中断。
- 内部中断主要是软件中断以及定时器中断。
中断屏蔽、中断级别、 优先级与仲裁
根据中断级别(Level)和中断优先级(Priority)进行确定。,两种级别的位数可以通过先关的寄存器确定。
- 中断级别(level)决定了中断是否可以嵌套,
- 中断优先级(priority)以及中断ID 会进行中断裁决,不参与中断嵌套的抉择。
- 中断阈值也可以设置。裁决出的中断高于阈值才会被CPU响应。
- 前提条件是,中断被使能,且中断已经产生了,才可以参与裁决。
中断ID编码如下:
中断处理模式
支持向量模式以及非向量模式。
- 向量模式:中断向量表,CPU根据中断ID和偏移直接 去中断向量基地址去拿地址,然后跳到中断地址(即中断函数)去执行。主要需要加关键字__attribute__((interrupt))),编译器可以加一些上下文保护。
- 非向量模式:即不是直接跳到中断函数地址,而是跳到统一的函数入口,然后再跳到对应的中断函数。
向量模式下的处理流程:
如果需要处理中断嵌套,则需要在中断开始加入处理,因为进入中断前,全局中断已经关闭。
中断向量模式的设置,通过clicintattr[i]的比特域
//sets vector-mode or non-vector mode
void eclic_set_vmode(unsigned int source) {
//read the current attr
unsigned char old_intattr = eclic_get_intattr(source);
// Keep other bits unchanged and only set the LSB bit
unsigned char new_intattr = (old_intattr | 0x1);
eclic_set_intattr(source,new_intattr);
}
void eclic_set_nonvmode(unsigned int source) {
//read the current attr
unsigned char old_intattr = eclic_get_intattr(source);
// Keep other bits unchanged and only clear the LSB bit
unsigned char new_intattr = (old_intattr & (~0x1));
eclic_set_intattr(source,new_intattr);
}
- mepc:保存进入异常之前,处理器正在执行的PC值。
- mcause:进入中断异常之前原因。
- mtval:进入异常之前的出错编码值 或者存储器的地址
- msubm:内核自定义寄存器,进入异常之前的异常类型。
- mtvec:异常处理入口,低6位,作为中断模式的选择
- mtvt:中断向量基地址
非向量模式:
- mtvt2 如果最低位为0,则和异常共用一个函数入口
- 最低位如果为1,则通过mtvt2 指定单独的中断函数入口
- 公共函数入口,首先保存 CSR 寄存器 mepc、 mcause、 msubm 入堆栈。保存这几个 CSR 寄存器是为了保证后续的中断嵌套能够功能正确,因为新的中断响应会重新覆盖 mepc、 mcause、 msubm的值,因此需要将它们先保存入堆栈。
- 保存若干通用寄存器(处理器的上下文)入堆栈。
- 然后执行一条特殊的指令“csrrw ra, CSR_JALMNXTI, ra”。如果没有中断在等待(Pending),则该指令相当于是个 Nop 指令不做任何操作;如果有中断在等待(Pending),执行该指令后处理器会:
- 直接跳入该中断的向量入口(Vector Table Entry)存储的目标地址,即该中断源的中断服务程序(Interrupt Service Routine, ISR)中去。
- 在跳入中断服务程序的同时,硬件也会同时打开中断的全局使能,即,设置 mstatus寄存器的 MIE 域为 1。打开中断全局使能后,新的中断便可以被响应,从而达到中断嵌套的效果。
- 在跳入中断服务程序的同时,“csrrw ra, CSR_JALMNXTI, ra”指令还会达到 JAL(Jump and Link)的效果,硬件同时更新 Link 寄存器的值为该指令的 PC 自身作为函数调用的返回地址。因此,从中断服务程序函数返回后会回到该“csrrw ra,CSR_JALMNXTI, ra”指令重新执行,重新判断是否还有中断在等待(Pending),从而达到中断咬尾的效果。
- 在中断服务程序的结尾处同样需要添加对应的恢复上下文出栈操作。并且在 CSR 寄存器 mepc、mcause、msubm 出堆栈之前,需要将中断全局使能再次关闭,以保证 mepc、mcause、 msubm 恢复操作的原子性(不被新的中断所打断)
中断咬尾:背靠背的恢复现场和保存现场。
GD32 RISCV VF103芯片例程学习
流水灯
#define LED_RED_ON() gpio_bit_reset(GPIOC, GPIO_PIN_13)
#define LED_BLUE_ON() gpio_bit_reset(GPIOA, GPIO_PIN_2)
#define LED_GREEN_ON() gpio_bit_reset(GPIOA, GPIO_PIN_1)
#define LED_RED_OFF() gpio_bit_set(GPIOC, GPIO_PIN_13)
#define LED_GREEN_OFF() gpio_bit_set(GPIOA, GPIO_PIN_1)
#define LED_BLUE_OFF() gpio_bit_set(GPIOA, GPIO_PIN_2)
void GPIO_Init(void)
{
rcu_periph_clock_enable(RCU_GPIOA); //打开GPIOA时钟
rcu_periph_clock_enable(RCU_GPIOC); //打开GPIOA时钟
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_1);
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_2);
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_13);
gpio_bit_set(GPIOA, GPIO_PIN_1);
gpio_bit_set(GPIOA, GPIO_PIN_2);
gpio_bit_set(GPIOC, GPIO_PIN_13);
}
void led_blink(unsigned int counter)
{
unsigned int blink_state_num = counter%8;
switch(blink_state_num)
{
case 0:
{
LED_RED_ON();
LED_GREEN_OFF();
LED_BLUE_OFF();
}break;
case 1:
{
LED_RED_ON();
LED_BLUE_ON();
LED_GREEN_ON();
}break;
case 2:
{
LED_RED_ON();
LED_GREEN_ON();
LED_BLUE_OFF();
}break;
case 3:
{
LED_RED_OFF();
LED_BLUE_OFF();
LED_GREEN_ON();
}break;
case 4:
{
LED_RED_OFF();
LED_GREEN_ON();
LED_BLUE_ON();
}break;
case 5:
{
LED_RED_OFF();
LED_GREEN_OFF();
LED_BLUE_ON();
}break;
case 6:
{
LED_RED_ON();
LED_BLUE_ON();
LED_GREEN_OFF();
}break;
case 7:
{
LED_RED_OFF();
LED_GREEN_OFF();
LED_BLUE_OFF();
}break;
default:
break;
}
}
串口发送
这里注意串口发送函数,官方驱动有点问题,发送完了需要等待发送完成,不然就会发送不全的情况产生
void usart_data_transmit(uint32_t usart_periph, uint32_t data)
{
while((USART_STAT(usart_periph) & USART_STAT_TC) == 0); //注意等待完成完成
USART_DATA(usart_periph) = USART_DATA_DATA & data;
}
void UART0_Init(void)
{
rcu_periph_clock_enable(RCU_USART0); //打开USART0时钟
rcu_periph_clock_enable(RCU_GPIOA); //打开GPIOA时钟
//TX PA9
//RX PA10
gpio_init(GPIOA,GPIO_MODE_AF_PP,GPIO_OSPEED_10MHZ,GPIO_PIN_9); //设置GPIOA9为服用输出模式
gpio_init(GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_10MHZ,GPIO_PIN_10); //设置GPIOA10为浮空输入模式
usart_deinit(USART0); //复位USART0
rcu_periph_clock_enable(RCU_AF); //使能复用时钟
usart_baudrate_set(USART0,115200); //设置波特率为115200
usart_parity_config(USART0,USART_PM_NONE); //设置校验位为无
usart_word_length_set(USART0,USART_WL_8BIT); //设置传输长度8Bit
usart_stop_bit_set(USART0,USART_STB_1BIT); //设置停止位1位
usart_transmit_config(USART0,USART_TRANSMIT_ENABLE); //设置传输使能
usart_enable(USART0); //开启UART0
}
void test_printf(const char* fmt, ...)
{
char buf[256]={0};
va_list ap;
va_start(ap,fmt);
int len = vsnprintf(buf,(unsigned int)256,fmt,ap);
if(len)
{
int i;
for(i=0;i<len;i++)
{
usart_data_transmit(USART0,buf[i]);
}
}
va_end(ap);
}
向量中断与非向量模式
向量模式下需要:eclic_set_vmode(TIMER3_IRQn); 设置相应的函数为向量模式
/* enable the global interrupt */
eclic_global_interrupt_enable();
eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
/* enable and set key EXTI interrupt priority */
#if (INTERRUPT_MODE == INTERRUPT_VECTOR_MODE)
eclic_set_vmode(TIMER3_IRQn);
#else
eclic_set_nonvmode(TIMER3_IRQn);
#endif
eclic_set_posedge_trig(TIMER3_IRQn);
eclic_irq_enable(TIMER3_IRQn, 1, 0);
test_printf("eclic_get_mth=%d\r\n",eclic_get_mth());
int test_counter_g = 0;
#if (INTERRUPT_MODE==INTERRUPT_VECTOR_MODE)
__attribute__((interrupt)) void TIMER3_IRQHandler()
#else
void TIMER3_IRQHandler()
#endif
{
if(timer_interrupt_flag_get(TIMER3,TIMER_INT_FLAG_UP))
{
test_counter_g++;
}
timer_interrupt_flag_clear(TIMER3,TIMER_INT_FLAG_UP);
}
参考
1、基于RISC-V内核的32位通用微控制器(MCU)
2、Bumblebee内核指令架构手册
3、向量中断以及非向量中断代码例程–Github
4、向量中断以及非向量中断代码例程–CSDN