在CH32V003内部有一个特殊的定时器——系统定时器(SysTick),它位于青稞V2微处理内核里面,是RISC-V内核的一个组成部分,主要用来给操作系统提供时间片轮转的定时,一般固定为10ms的定时,所以中文也称它为“嘀嗒”定时器(也称“心跳”定时器)。在不跑操作系统时,可以把它当作普通定时器来使用,一般用来进行程序延时。下面就来讨论一下如何使用SysTick系统定时器。
系统定时器位于“内核私有外设”(Core Private peripheral)内,其地址为0xE0000000~0xE0100000。SysTick定时器的位长度是32位,即最长的计数次数为4,294,967,296次。计数为正向计数形式,递增到与比较值一致时产生中断请求。计数的脉冲可直接取系统时钟,也可取8分频的系统时钟。下表给出了和SysTick相关的寄存器。
从上表中可以看到,SysTick定时器并不复杂,只用了4个寄存器。下面就分别来进行讨论。
下面给出的是上表中控制寄存器STK_CTLR的全部位结构。
(1)第31位(SWIE)为软件中断触发使能位。置1时触发软件中断被激活,置0时关闭触发。在进入软件中断后,需软件清0,否则会连续触发。该位复位值为0。
(2)第30~4位均为保留位。
(3)第3位(STRE)为自动重装载计数使能位。置1时正向计数到比较值后重新从0开始计数,置0时正向计数到比较值后继续正向计数。该位复位值为0。
(4)第2位(STCLK)为计数器时钟源选择位。置1时选取系统时钟作为计数时钟,置0时选取系统时钟的8分频作为计数时钟。该位复位值为0。
(5)第1位(STIE)为计数器中断使能控制位。置1时使能计数器中断,置0时关闭计数器中断。该位复位值为0。
(6)第0位(STE)为系统计数器使能控制位。置1时启动系统计数器STK,置0时关闭系统计数器STK,计数器停止计数。该位复位值为0。
STK_SR是系统定时器的计数状态寄存器,负责SysTick的状态标志。下表给出了它的全部位结构。
(1)第31~1位均为保留位。
(2)第0位(CNTIF)为计数值比较标志位,写0 清除,写1 无效。值为1时表示向上计数已达到比较值,值为0时表示未达到比较值。该位复位值为0。
STK_CNTR是系统定时器的计数器值寄存器,负责SysTick的计数。下表给出了它的全部位结构。
(1)第31~0位(CNTR)全部用于计数,其值为当前计数器的计数值。这些位的复位值为0。
STK_CMPR是系统定时器的计数比较值寄存器,负责SysTick的计数比较。下表给出了它的全部位结构。
(1)第31~0位(CMPR)为当前设置的计数器比较值。这些位的复位值为0。
以上介绍完了SysTick 所用到的寄存器,现在来讨论一下如何使用它。下面给出系统时钟SysTick的结构体定义(该定义位于开发环境MRS自带的core_riscv.h文件中)。
typedef struct { __IO uint32_t CTLR; __IO uint32_t SR; __IO uint32_t CNT; uint32_t RESERVED0; __IO uint32_t CMP; uint32_t RESERVED1; }SysTick_Type;
SysTick定时器的基址为0xE000F000,所以要将基址指针强制转换为上述结构体,还要加上下面的定义。
#define SysTick ((SysTick_Type *) 0xE000F000)
对于系统定时器SysTick产生的的具体使用,可分为两种形式,即中断形式和软件查询形式。
先来看中断形式,它有特定的入口函数,形式如下所示。
void SysTick_Handler(void) { //系统定时中断服务程序部分 }
在使用中断时要特别注意一点,根据官方手册描述,由于CH32V003具有硬件压栈模式,所以在使用中断入口函数前,必须先声明一下是启用硬件压栈模式还是软件压栈模式,具体如下。
void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); //硬件压栈模式 void SysTick_Handler(void) __attribute__((interrupt())); //软件压栈模式
声明可放在main函数前面,但只能选择其中一种模式。至于要选择哪种模式,可参看“PFIC中断控制”部分的内容。
在使用中断前可通过下面的初始化函数配置一下SysTick。
void SysTick_init(void) { NVIC_EnableIRQ(SysTicK_IRQn); //使能系统的SysTick快速中断 SysTick->SR &= ~(1 << 0); //计数器状态清零 SysTick->CMP = 3000000; //设置比较值 SysTick->CNT = 0; //计数器清零 SysTick->CTLR = 0xb; //设置为系统时钟8分频、自动重载、启用中断、开启定时器 }
在中断服务函数中,需要先执行“SysTick->SR &= ~(1 << 0);”语句,向系统计数状态寄存器SR的最低位写0,以清除中断标志,然后再执行其他任务。
再来看软件查询的形式。不使用中断的话,也可以通过软件查询STK_SR寄存器的第0位CNTIF是否被置1,如果置1了表示定时时间到了。下面就来看一个使用软件查询方式实现的毫秒级延时函数(设系统时钟为48MHz),代码如下。
void delay_ms(uint32_t time) { SysTick->SR &= ~(1 << 0); SysTick->CMP = time * 6000; SysTick->CNT = 0; SysTick->CTLR |=(1 << 0); while((SysTick->SR & (1 << 0)) != (1 << 0)); SysTick->CTLR &= ~(1 << 0); }
在程序中,通过“while((SysTick->SR & (1 << 0)) != (1 << 0));”这句来等待定时器比较成功,其实就是循环查询STK_SR寄存器的第0位CNTIF是否被置1,若为0则循环等待直到1为止。
由上述可见,其实SysTick本身是一个简易的定时器,操作较为简单方便。在实际应用时可按以下顺序进行操作。
(1)写STK_SR寄存器清零标志位。
(2)给STK_CMPR寄存器写入定时器的比较值。
(3)给STK_CNTR寄存器值清零。
(4)设置STK_CTLR寄存器配置时钟源分频,中断使能等,然后启动定时器。
接下来看一个LED灯闪烁的例子,要求通过CH32V003的PD6引脚实现一个LED的闪烁功能,周期为1秒。使用程序查询方式的代码如下(实际运行时还需要加上时钟配置)。
#include <ch32v00x.h> void delay_ms(uint32_t time) { SysTick->SR &= ~(1 << 0); //清除标志位 SysTick->CMP = time * 6000; //48MHz的8分频是6MHz,故1ms需要扩大6000倍 SysTick->CNT = 0; //计数值清零 SysTick->CTLR |=(1 << 0); //仅启动计数器 while((SysTick->SR & (1 << 0)) != (1 << 0)); //等待计数值到 SysTick->CTLR &= ~(1 << 0); //停止计数器 } int main(void) { RCC->APB2PCENR |= 1 << 5; //使能PD端口的时钟 GPIOD->CFGLR &= ~0x4000000; GPIOD->CFGLR |= 0x1000000; //通过以上两句配置PD6为通用推挽输出模式,速度为10MHz while(1) { GPIOD->OUTDR ^= (uint16_t)0x40; //PD6取反(通过异或方式) delay_ms(500); //延时0.5秒 } }
使用中断方式的代码如下:
#include <ch32v00x.h> void SysTick_Handler(void) __attribute__(())); //中断使用软件压栈的方式 void SysTick_init(void) { NVIC_EnableIRQ(SysTicK_IRQn); //使能系统的SysTick快速中断 SysTick->SR &= ~(1 << 0); //计数器状态清零 SysTick->CMP = 3000000; //48MHz的8分频是6MHz,定时0.5秒的值是一半 SysTick->CNT = 0; //计数器清零 SysTick->CTLR = 0xb; //设置为系统时钟8分频、自动重载、启用中断、开启定时器 } int main(void) { RCC->APB2PCENR |= 1 << 5; //使能PD端口的时钟 GPIOD->CFGLR &= ~0x4000000; GPIOD->CFGLR |= 0x1000000; //通过以上两句配置PD6为通用推挽输出模式,速度为10MHz SysTick_init(); //调用定时器初始化函数 while(1) { ; } } void SysTick_Handler(void) //定时器中断服务函数 { SysTick->SR &= ~(1 << 0); //清除标志位 GPIOD->OUTDR ^= (uint16_t)0x40; //PD6取反(通过异或方式) }
把程序编译后下载到CH32V003中,给系统上电,可看到接到PD6引脚上的LED在闪烁,闪烁周期为1秒。
标签:定时器,中断,SR,void,CH32V003,计数,SysTick From: https://www.cnblogs.com/fxzq/p/17382108.html