开发环境:
MDK:Keil 5.30
开发板:GD32F207I-EVAL
MCU:GD32F207IK
Cortex-M的内核中包含Systick定时器了,只要是Cortex-M系列的MCU就会有Systick,因此这是通用的,下面详细分析。
1 Systick工作原理分析
SysTick 定时器被捆绑在 NVIC 中,用于产生 SysTick 异常(异常号 :15)。在以前,操作系统和所有使用了时基的系统都必须有一个硬件定时器来产生需要的“滴答”中断,作为整个系统的时基。滴答中断对操作系统尤其重要。例如,操作系统可以为多个任务分配不同数目的时间片,确保没有一个任务能霸占系统 ;或者将每个定时器周期的某个时间范围赐予特定的任务等,操作系统提供的各种定时功能都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。
Cortex-M3 在内核部分包含了一个简单的定时器——SysTick。因为所有的 CM3 芯片都带有这个定时器,软件在不同芯片生产厂商的 CM3 器件间的移植工作就得以简化。该定时器的时钟源可以是内部时钟(FCLK,CM3 上的自由运行时钟),或者是外部时钟。不过,外部时钟的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能大不相同。因此,需要阅读芯片的使用手册来确定选择什么作为时钟源。在 GD32 中SysTick 以 HCLK(AHB 时钟)或 HCLK/8 作为运行时钟,见上图。
SysTick 定时器能产生中断,CM3 为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其他系统软件在 CM3 器件间的移植变得简单多了,因为在所有 CM3 产品间,SysTick 的处理方式都是相同的。SysTick 定时器除了能服务于操作系统之外,还能用于其他目的,如作为一个闹铃、用于测量时间等。 Systick 定时器属于Cortex 内核部件 ,可以参考《ARM Cortex-M3 权威指南》((英)JosephYiu 著,宋岩译,北京航空航天大学出版社出版)来了解。
2 Systick寄存器分析
在传统的嵌入式系统软件按中通常实现 Delay(N) 函数的方法为:
for(i = 0; i <= x; i ++);
x --- ;
对于GD32系列微处理器来说,执行一条指令只有几十个 ns,进行 for 循环时,要实现 N 毫秒的 x 值非常大,而且由于系统频率的宽广,很难计算出延时 N 毫秒的精确值。针对GD32 微处理器,需要重新设计一个新的方法去实现该功能,以实现在程序中使用 Delay(N)。
Cortex-M3 的内核中包含一个 SysTick 时钟。SysTick 为一个 24 位递减计数器,SysTick 设定初值并使能后,每经过 1 个系统时钟周期,计数值就减 1。计数到 0 时,SysTick 计数器自动重装初值并继续计数,同时内部的 COUNTFLAG 标志会置位,触发中断 (如果中断使能情况下)。
在 GD32 的应用中,使用 Cortex-M3 内核的 SysTick 作为定时时钟,设定每一毫秒产生一次中断,在中断处理函数里对 N 减一,在Delay(N) 函数中循环检测 N 是否为 0,不为 0 则进行循环等待;若为 0 则关闭 SysTick 时钟,退出函数。
注: 全局变量 TimingDelay , 必须定义为 volatile 类型 , 延迟时间将不随系统时钟频率改变。
Cortex-M3中的Systick部分内容属于NVIC控制部分,一共有4个寄存器,名称和地址分别是:
STK_CTRL,0xE000E010--控制寄存器
第0位:ENABLE,Systick 使能位
(0:关闭Systick功能;1:开启Systick功能)
第1位:TICKINT,Systick 中断使能位
(0:关闭Systick中断;1:开启Systick中断)
第2位:CLKSOURCE,Systick时钟源选择
(0:使用HCLK/8 作为Systick时钟;1:使用HCLK作为Systick时钟)
第16位:COUNTFLAG,Systick计数比较标志,如果在上次读取本寄存器后,SysTick 已经数到了0,则该位为1。如果读取该位,该位将自动清零
STK_LOAD, 0xE000E014--重载寄存器
Systick是一个递减的定时器,当定时器递减至0时,重载寄存器中的值就会被重装载,继续开始递减。STK_LOAD 重载寄存器是个24位的寄存器最大计数0xFFFFFF。
STK_VAL, 0xE000E018--当前值寄存器
也是个24位的寄存器,读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick控制及状态寄存器中的COUNTFLAG标志。
STK_CALRB, 0xE000E01C--校准值寄存器
校准值寄存器提供了这样一个解决方案:它使系统即使在不同的CM3产品上运行,也能产生恒定的SysTick中断频率。最简单的作法就是:直接把TENMS的值写入重装载寄存器,这样一来,只要没突破系统极限,就能做到每10ms来一次 SysTick异常。如果需要其它的SysTick异常周期,则可以根据TENMS的值加以比例计算。只不过,在少数情况下, CM3芯片可能无法准确地提供TENMS的值(如, CM3的校准输入信号被拉低),所以为保险起见,最好在使用TENMS前检查器件的参考手册。
SysTick定时器除了能服务于操作系统之外,还能用于其它目的:如作为一个闹铃,用于测量时间等。要注意的是,当处理器在调试期间被喊停( halt)时,则SysTick定时器亦将暂停运作。
3 Systick定时器实现
SysTick属于Cortex-M内核的部分,因此其相关的定义在core_cm3.h文件中。
3.1 main文件分析
主函数如下:
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
//systick init
sysTick_init();
/* configure LED1 GPIO port */
led_init(LED1);
/* configure LED2 GPIO port */
led_init(LED2);
/* configure LED3 GPIO port */
led_init(LED3);
/* configure LED4 GPIO port */
led_init(LED4);
while(1)
{
/* turn on LED1, turn off LED4 */
led_on(LED1);
led_off(LED4);
/*delay 500ms*/
delay_ms(500);
/* turn on LED2, turn off LED1 */
led_on(LED2);
led_off(LED1);
/*delay 500ms*/
delay_ms(500);
/* turn on LED3, turn off LED2 */
led_on(LED3);
led_off(LED2);
/*delay 500ms*/
delay_ms(500);
/* turn on LED4, turn off LED3 */
led_on(LED4);
led_off(LED3);
/*delay 500ms*/
delay_ms(500);
}
}
在 main 函数中,sysTick_init和 delay_us() 这两个函数比较陌生,它们的功能分别是配置好 SysTick 定时器和进行精确延时。整个 main 函数的流程就是初始化 LED 及SysTick 定时器之后,就进入死循环,点亮LED的时间为精确的 500 ms。
3.2 gd32f207i_systick_eval.c文件分析
配置并启动 SysTick
我们看一下systick_init()这个函数,其功能是启动系统滴答定时器 SysTick。
/*
brief SysTick init
param[in] none
param[out] none
retval none
*/
void sysTick_init(void)
{
/* SystemFrequency / 1000 1ms中断一次
* SystemFrequency / 100000 10us中断一次
* SystemFrequency / 1000000 1us中断一次
*/
/* setup systick timer for 1000Hz interrupts */
if(SysTick_Config(SystemCoreClock / 100000U)){
/* capture error */
while(1){
}
}
// 关闭滴答定时器
SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
本函数实际上只是调用了 SysTick_Config() 函数,它是属于内核层的 Cortex-M3 通用函数,位于 core_cm3.h 文件中。若调用 SysTick_Config() 配置 SysTick 不成功,则进入死循环,初始化 SysTick 成功后,先关闭定时器,在需要的时候再开启。SysTick_Config() 函数无法在GD32 外设固件库文件中找到其使用方法。所以我们在 Keil 环境下直接跟踪这个函数到 core_cm3.h 文件,查看函数的定义。
/** \brief System Tick Configuration
The function initializes the System Timer and its interrupt, and starts the System Tick Timer.
Counter is in free running mode to generate periodic interrupts.
\param [in] ticks Number of ticks between two interrupts.
\return 0 Function succeeded.
\return 1 Function failed.
\note When the variable __Vendor_SysTickConfig is set to 1, then the
function SysTick_Config is not included. In this case, the file device.h
must contain a vendor-specific implementation of this function.
*/
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = ticks - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Systick Interrupt */
SysTick->VAL = 0; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
在这个函数定义的前面有关于它的注释,如果我们不想去研究它的具体实现,可以根据这段注释了解函数的功能 :这个函数启动了 SysTick ;并把它配置为计数至 0 时引起中断 ;输入的参数 ticks 为两个中断之间的脉冲数,即相隔 ticks 个时钟周期会引起一次中断 ;配置 SysTick 成功时返回 0,出错时返回 1。但是,这段注释并没有告诉我们它把 SysTick 的时钟设置为 AHB 时钟还是 AHB/8,这是一个十分关键的问题,于是,我们将对这个函数的具体实现进行分析,与大家再分享一下如何分析底层库函数。分析底层库函数,要有 SysTick 定时器工作分析的知识准备。
检查输入参数
SysTick_Config() 第 3 行代码是检查输入参数 ticks,因为 ticks 是脉冲计数值,要被保存到重载寄存器 STK_LOAD 寄存器中,再由硬件把 STK_LOAD 值加载到当前计数值寄存器 STK_VAL 中使用,STK_LOAD 和 STK_VAL 都是 24 位的,所以当输入参数 ticks 大于其可存储的最大值时,将由这行代码检查出错误并返回。
位指示宏及位屏蔽宏
检查 ticks 参数没有错误后,就稍稍处理一下把 ticks-1 赋值给 STK_LOAD 寄存器,要注意的是减 1,若 STK_VAL 从 ticks−1 向下计数至 0,实际上就经过了 ticks 个脉冲。这句赋值代码使用了宏 SysTick_LOAD_RELOAD_Msk,与其他库函数类似,这个宏是用来指示寄存器的特定位置或进行位屏蔽的。
/* SysTick Control / Status Register Definitions */
#define SysTick_CTRL_COUNTFLAG_Pos 16 /*!< SysTick CTRL: COUNTFLAG Position */
#define SysTick_CTRL_COUNTFLAG_Msk (1ul << SysTick_CTRL_COUNTFLAG_Pos) /*!< SysTick CTRL: COUNTFLAG Mask */
#define SysTick_CTRL_CLKSOURCE_Pos 2 /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */
#define SysTick_CTRL_TICKINT_Pos 1 /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) /*!< SysTick CTRL: TICKINT Mask */
#define SysTick_CTRL_ENABLE_Pos 0 /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) /*!< SysTick CTRL: ENABLE Mask */
/* SysTick Reload Register Definitions */
#define SysTick_LOAD_RELOAD_Pos 0 /*!< SysTick LOAD: RELOAD Position */
上一篇:【技术分享】星空派GD32开发板LVGL移植经验分享
下一篇:keil中GD32 MCU IAP中APP的存储地址如何设置?
推荐阅读最新更新时间:2024-11-12 16:41
设计资源 培训 开发板 精华推荐
- LTC3526L,具有 250mA 负载的两节 AA 锂电池至 3.3V 升压转换器
- MC34074DR2G 有源带通滤波器运算放大器的典型应用
- 配有L6208Q的步进电机驱动器
- AFBR-0549Z,HFBR-1414PTZ 光纤发射器和 AFBR-2418TZ 光纤接收器的评估套件
- LT6654AMPS6-3.3 八路 DAC 电压基准的典型应用
- LTC3119EUFD 5V、2MHz 宽输入稳压器的典型应用电路
- EVAL-AD5678DBZ,用于 AD5678 4 × 12 位和 4 × 16 位、八通道、电压输出 DAC 的评估板
- 用于精密电流源的 ADR366A、3.3V 低功耗、低噪声电压基准的典型应用
- H桥电机驱动板
- 立创训练营蓝牙功放