开发环境:
MDK:MounRiver Studio
MCU:CH32V208
CH32V208是基于32位RISC-V指令集及架构设计的内核(青稞V4C处理器),内核中设计了一个 64 位加减计数器(Systick定时器),其时钟源可以系统时钟,也可以是系统时钟的 8 分频。可为实时操作系统提供时基,提供定时、测量时间等。定时器涉及 6 个寄存器并映射到外设地址空间,下面详细分析。
1 Systick工作原理分析
SysTick 定时器能产生中断,内核中为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其他系统软件在内核间的移植变得简单多了,SysTick 定时器除了能服务于操作系统之外,还能用于其他目的,如作为一个闹铃、用于测量时间等。
2 Systick寄存器分析
在传统的嵌入式系统软件按中通常实现 Delay(N) 函数的方法为:
for(i = 0; i <= x; i ++);
x --- ;
对于CH32系列微处理器来说,执行一条指令只有几十个 ns,进行 for 循环时,要实现 N 毫秒的 x 值非常大,而且由于系统频率的宽广,很难计算出延时 N 毫秒的精确值。针对CH32微处理器,需要重新设计一个新的方法去实现该功能,以实现在程序中使用 Delay(N)。
青稞V4C处理器中包含一个 SysTick 时钟。SysTick 为一个64位递减计数器,SysTick 设定初值并使能后,每经过 1 个系统时钟周期,计数值就减 1。计数到 0 时,SysTick 计数器自动重装初值并继续计数,同时内部会触发中断 (如果中断使能情况下)。
在CH32的应用中,使用青稞V4C处理器的 SysTick 作为定时时钟,设定每一毫秒产生一次中断,在中断处理函数里对 N 减一,在Delay(N) 函数中循环检测 N 是否为 0,不为 0 则进行循环等待;若为 0 则关闭 SysTick 时钟,退出函数。
青稞V4C处理器中的Systick一共有6个寄存器。
名称 |
访问地址 |
描述 |
复位值 |
---|
STK_CTLR |
0xE000F000 |
系统计数控制寄存器 |
0x00000000 |
STK_SR |
0xE000F004 |
系统计数状态寄存器 |
0x00000000 |
STK_CNTL |
0xE000F008 |
系统计数器低位寄存器 |
0x00000000 |
STK_CNTH |
0xE000F00C |
系统计数器高位寄存器 |
0x00000000 |
STK_CMPLR |
0xE000F010 |
计数重加载低位寄存器 |
0x00000000 |
STK_CMPHR |
0xE000F014 |
计数重加载高位寄存器 |
0x00000000 |
Ø
STK_CTLR,0xE000F000--系统计数控制寄存器
位 |
名称 |
访问 |
描述 |
复位值 |
---|
31 |
SWIE |
RW |
软件中断触发使能(SWI): 1:触发软件中断; 0:关闭触发。 进入软件中断后,需软件清0,否则持续触发。 |
0 |
[30:6] |
Reserved |
RO |
保留。 |
0 |
5 |
INIT |
W1 |
计数器初始值更新: 1:向上计数时更新为0,向下计数时更新为比较值; 0:无效。 |
0 |
4 |
MODE |
RW |
计数模式: 1:向下计数; 0:向上计数。 |
0 |
3 |
STRE |
RW |
自动重装载计数使能位: 1:向上计数到比较值后重新从0开始计数,向下计数到0后,重新从比较值开始计数; 0:继续向上/向下计数。 |
0 |
2 |
STCLK |
RW |
计数器时钟源选择位: 1:HCLK做时基; 0:HCLK/8做时基; |
0 |
1 |
STIE |
RW |
计数器中断使能控制位: 1:使能计数器中断; 0:关闭计数器中断。 |
0 |
0 |
STE |
RW |
系统计数器使能控制位: 1:启动系统计数器STK; 0:关闭系统计数器STK,计数器停止计数。 |
0 |
STK_SR, 0xE000F004 --系统计数控制寄存器
位 |
名称 |
访问 |
描述 |
复位值 |
---|
[31:1] |
Reserved |
RO |
保留 |
0 |
0 |
CNTIF |
RW |
计数值比较标志,写0清楚,写1无效: 1:向上计数达到比较值,向下计数到0; 0:未达到比较值。 |
0 |
Ø
STK_CNTL, 0xE000F008--系统计数器低位寄存器
位 |
名称 |
访问 |
描述 |
复位值 |
---|
[31:0] |
CNTL |
RW |
当前计数器计数值低32位。 |
0 |
注:寄存器 STK_CNTL 和寄存器 STK_CNTH 共同构成了 64 位系统计数器。
Ø
STK_CNTH, 0xE000F00C--系统计数器高位寄存器
位 |
名称 |
访问 |
描述 |
复位值 |
---|
[31:0] |
CNTH |
RW |
当前计数器计数值高32位。 |
0 |
Ø
STK_CMPLR, 0xE000F010 --计数比较低位寄存器
位 |
名称 |
访问 |
描述 |
复位值 |
---|
[31:0] |
CMPL |
RW |
设置重加载计数器值低32位。 |
0 |
注:寄存器 STK_CMPLR 和寄存器 STK_CMPHR 共同构成了 64 位计数器比较值。
Ø
STK_CMPHR, 0xE000F014 --计数比较高位寄存器
位 |
名称 |
访问 |
描述 |
复位值 |
---|
[31:0] |
CNTH |
RW |
设置重加载计数器值高32位。 |
0 |
SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。要注意的是,当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作。
3 Systick定时器实现
SysTick属于RISC-V内核的部分,因此其相关的定义在core_riscv.h文件中。
3.1 main文件分析
主函数如下:
int main(void)
{
ST_BSP_LED_Dev BSP_LED_Dev0 = LED_DEV0_CONFIG;
ST_BSP_LED_Dev BSP_LED_Dev1 = LED_DEV1_CONFIG;
SysTick_Init();
BSP_LED_Init(&BSP_LED_Dev0);
BSP_LED_Init(&BSP_LED_Dev1);
while( 1 )
{
Delay_ms(500);
BSP_LED_Toggle( &BSP_LED_Dev0);
Delay_ms(500);
BSP_LED_Toggle( &BSP_LED_Dev1);
}
}
在 main 函数中,SysTick_init和 Delay_us() 这两个函数比较陌生,它们的功能分别是配置好 SysTick 定时器和进行精确延时。整个 main 函数的流程就是初始化 LED 及SysTick 定时器之后,就进入死循环,点亮LED的时间为精确的 500 ms。
3.2 ch32v20x_bsp_systick.c文件分析
void SysTick_Init(void)
{
p_us = SystemCoreClock / 8000000;
p_ms = (uint16_t)p_us * 1000;
}
SysTick默认是开启的,这里主需要得到延时的系数即可。
然后就是延时函数。
void Delay_us(uint32_t count)
{
uint32_t i;
SysTick->SR &= ~(1 << 0);
i = (uint32_t)count * p_us;
SysTick->CMP = i;
SysTick->CTLR |= (1 << 4);
SysTick->CTLR |= (1 << 5) | (1 << 0);
while((SysTick->SR & (1 << 0)) != (1 << 0));
SysTick->CTLR &= ~(1 << 0);
}
void Delay_ms(uint32_t count)
{
uint32_t i;
SysTick->SR &= ~(1 << 0);
i = (uint32_t)count * p_ms;
SysTick->CMP = i;
SysTick->CTLR |= (1 << 4);
SysTick->CTLR |= (1 << 5) | (1 << 0);
while((SysTick->SR & (1 << 0)) != (1 << 0));
SysTick->CTLR &= ~(1 << 0);
}
这两个函数分别是微秒和毫秒延时,具体使用看Systick相关寄存器描述。
3.3 实验现象
将编译好的程序下载到板子中,可以看到LED灯不同地闪烁。