关于RT thread系统节拍时钟的配置
-----本文基于rt-thread-3.1.3版本编写
首先,使用RTthread OS时,要配置(或者明白)它的系统节拍rt_tick(划重点)。
系统节拍
- 系统节拍是特定的周期中断,可以看是系统心跳,中断之间的时间间隔取决于不同的应用,一般是 1ms–100ms,系统节拍率越快,系统的额外开销就越大,从系统启动开始计数的时钟节拍数称为系统时间。
- RT-Thread 中,系统节拍的长度可以根据 RT_TICK_PER_SECOND 的定义来调整,等于 1/RT_TICK_PER_SECOND 秒。
系统节拍实现方式
- 系统节拍由配置为中断触发模式的硬件定时器产生,当中断到来时,将调用一次:void rt_tick_increase(void),通知操作系统已经过去一个系统时钟
先看这段代码:
- void SysTick_Handler(void)
- {
- /* enter interrupt */
- rt_interrupt_enter();
-
- rt_tick_increase();
-
- /* leave interrupt */
- rt_interrupt_leave();
- }
其中函数rt_tick_increase()的代码是:
- void rt_tick_increase(void)
- {
- struct rt_thread *thread;
-
- /* increase the global tick */
- #ifdef RT_USING_SMP
- rt_cpu_self()->tick ++;
- #else
- ++ rt_tick;
- #endif
-
- /* check time slice */
- thread = rt_thread_self();
-
- -- thread->remaining_tick;
- if (thread->remaining_tick == 0)
- {
- /* change to initialized tick */
- thread->remaining_tick = thread->init_tick;
-
- /* yield */
- rt_thread_yield();
- }
-
- /* check timer */
- rt_timer_check();
- }
若系统的硬件是外置8MHz晶振,系统时钟每秒节拍数1000表示1s内rt_tick增加1000,即时钟节拍为1ms;若设为100,则rt_tick每隔10ms加1,时钟节拍为10ms。
所以如果想做成时钟节拍为5ms的,那就把RT_TICK_PER_SECOND设置为200;如果想把时钟节拍做成2ms的,那就把RT_TICK_PER_SECOND设置为500,以此类推。
SysTick_Handler是STM32的硬件滴答时钟的中断库函数,既然是一个时钟,那么类似定时器,它应该有一个时间间隔才产生一次中断。这个时间间隔就由以下这个函数来设置:
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);
其中RT_TICK_PER_SECOND定义在rtconfig.h中:
- /* Tick per Second */
- #define RT_TICK_PER_SECOND 1000
RT thread对于此处代码的解释是,等于 1/RT_TICK_PER_SECOND 秒,RT_TICK_PER_SECOND默认是1000。
又因HAL_RCC_GetHCLKFreq()的返回值是SystemCoreClock,所以就要查找SystemCoreClock在哪里赋值的。所以继续查看代码,按以下顺序找到:
rtthread_startup() -> rt_hw_board_init() -> SystemClock_Config() -> SystemCoreClockUpdate ()
在函数SystemCoreClockUpdate()中有如下一段代码(关键部份为橙色):
- /* Get SYSCLK source -------------------------------------------------------*/
- switch (RCC->CFGR & RCC_CFGR_SWS)
- {
- case 0x00: /* MSI used as system clock source */
- SystemCoreClock = msirange;
- break;
-
- case 0x04: /* HSI used as system clock source */
- SystemCoreClock = HSI_VALUE;
- break;
-
- case 0x08: /* HSE used as system clock source */
- SystemCoreClock = HSE_VALUE;
- break;
-
- case 0x0C: /* PLL used as system clock source */
- /* PLL_VCO = (HSE_VALUE or HSI_VALUE or MSI_VALUE/ PLLM) * PLLN
- SYSCLK = PLL_VCO / PLLR
- */
- pllsource = (RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC);
- pllm = ((RCC->PLLCFGR & RCC_PLLCFGR_PLLM) >> 4U) + 1U ;
-
- switch (pllsource)
- {
- case 0x02: /* HSI used as PLL clock source */
- pllvco = (HSI_VALUE / pllm);
- break;
-
- case 0x03: /* HSE used as PLL clock source */
- pllvco = (HSE_VALUE / pllm);
- break;
-
- default: /* MSI used as PLL clock source */
- pllvco = (msirange / pllm);
- break;
- }
- pllvco = pllvco * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 8U);
- pllr = (((RCC->PLLCFGR & RCC_PLLCFGR_PLLR) >> 25U) + 1U) * 2U;
- SystemCoreClock = pllvco/pllr;
- break;
-
- default:
- SystemCoreClock = msirange;
- break;
- }
-
- /* Compute HCLK clock frequency --------------------------------------------*/
- /* Get HCLK prescaler */
- tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4U)];
- /* HCLK clock frequency */
- SystemCoreClock >>= tmp;
在代码中,switch (RCC->CFGR & RCC_CFGR_SWS)显然就是硬件的时钟寄存器,一般STM32F103系列电路板上用的是外部晶振,所以就是0x08的值在起作用。
- case 0x08: /* HSE used as system clock source */
- SystemCoreClock = HSE_VALUE;
查找HSE_VALUE的定义,果然能找到
#define HSE_VALUE ((uint32_t)8000000U) /*!< Value of the External oscillator in Hz */
一般电路板上这个外部晶振都是8MHz,所以此处HSE_VALUE为8000000U
同时,看这段代码:
- /* Get HCLK prescaler */
- tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4U)];
- /* HCLK clock frequency */
- SystemCoreClock >>= tmp;
注释Get HCLK prescaler,意思是取得AHB的预分频;
注释HCLK clock frequency,意思是HCLK的频率。
一般我们都会将HCLK调整为72MHz,由此可知,SystemCoreClock算出来的值是72000000。
资料一:RT thread中的SysTick
在版本为3.1.3的RT thread OS的board.c源码中,有关于OS系统滴答(心跳)的初始化的代码。
此处要先说明一个知识点,STM32芯片中,分为ARM内核(如SysTick)和普通外设(如IIC,USART,TIM)。ARM内核的部件也是具有中断的,这些中断当然也具有优先级。
RT thread OS通过重定义和配置,来使能SysTick计时,并产生SysTick_Handler中断。
- /**
- * SysTick是一个简单的递减24位计数器
- * 以下是对SysTick进行重定义,用于初始化OS系统的系统滴答(又称心跳)
- **/
- #define _SCB_BASE (0xE000E010UL)
- #define _SYSTICK_CTRL (*(rt_uint32_t *)(_SCB_BASE + 0x0)) //SysTick->CTRL, 地址 0xE000E010 -- 状态和控制寄存器
- #define _SYSTICK_LOAD (*(rt_uint32_t *)(_SCB_BASE + 0x4)) //SysTick->LOAD, 地址 0xE000E014 -- 重装载值寄存器
- #define _SYSTICK_VAL (*(rt_uint32_t *)(_SCB_BASE + 0x8)) //SysTick->VAL, 地址 0xE000E018 -- 当前值寄存器
- #define _SYSTICK_CALIB (*(rt_uint32_t *)(_SCB_BASE + 0xC)) //SysTick->CALRB, 地址 0xE000E01C -- 校准值寄存器
- #define _SYSTICK_PRI (*(rt_uint8_t *)(0xE000ED23UL)) //SCB->SHP[11], 地址 0xE000ED23 -- ARM内核部件优先级设置寄存器的第11个字节:SysTick Priority level ,
- // 用于配置SysTick(系统定时器)的优先级,0x00为最高,0xFF为最低
- static uint32_t _SysTick_Config(rt_uint32_t ticks)
- {
- if ((ticks - 1) > 0xFFFFFF)
- {
- return 1;
- }
-
- _SYSTICK_LOAD = ticks - 1;
- _SYSTICK_PRI = 0xFF;
- _SYSTICK_VAL = 0;
- _SYSTICK_CTRL = 0x07; //使能了SysTick工作;产生中断;时钟来源:HCLK;
-
- return 0;
- }
资料二:关于STM32的中断优先级设置—systick
项目中需要将systick中断的优先级调低,遇到的问题总结如下:
1. STM32中断优先级是使用4-bit来表示的,即总共有16个级别。
2. 优先级分为2个部分:抢先优先级和子优先级,上述的4个bit可以灵活分配给抢先优先级和子优先级,比如,1个bit表示抢先优先级,则剩余3个bit表示子优先级。固件库中对应的函数是:NVIC_PriorityGroupConfig()。
3. 中断分为内核中断和芯片(STM32)中断,配置的寄存器位置是不同的,芯片中断的配置使用NVIC,内核中断(比如systick)的配置使用SCB。
4. 内核中断优先级设置使用函数:NVIC_SetPriority(),此函数需要一个表示优先级的参数,可以利用NVIC_EncodePriority()这个函数生成,比如:
NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 1));//抢先优先级:0(最高);子优先级:1(中)
资料三:SysTick的总结(寄存器操作)
我是新手,最近用STM32的SYSTick做了延时
编程思路:
选择时钟源
关闭计数器
设置重装载值
当前值清零
使能SysTick
等待计数器计数完毕
当前值清零
关闭计数器
一、概述:
SysTick是一个简单的递减24位计数器
如果你不需要再应用程序中嵌入操作系统,SysTick可以作为简单的延时和产生周期性的中断;
状态控制寄存器的第0位可以使能计数器,current value register(当前值寄存器)随着时钟一直递减,当他减到0的时候,重装载寄存器(reload value register)就会重新装载这只的值,计数器继续从这个值递减
二、相关寄存器:
2.1 SysTick->CTRL 状态和控制寄存器
控制和状态寄存器CTRL(复位值0x00000000)
位段 |
名称 |
类型 |
描述 |
16 |
COUNTFLAG |
只读 |
计数到0时置1;读取该位将清0 |
2 |
CLKSOURCE |
可读可写 |
时钟来源?: 0--HCLK/8;1--HCLK |
1 |
TICKINT |
可读可写 |
1:计数到0时产生SysTick异常请求(即中断) 0:不产生异常请求(即中断) |
0 |
ENABLE |
可读可写 |
使能位,即定时器的开关,1有效 |
2.2 SysTick->LOAD 重装载值寄存器
当前值寄存器为0时,自动将重装载值重装到当前值计数器,重装载值的大小需要自己设置
2.3 SysTick->VAL当前值寄存器
可读可写,当计数器使能时,这个寄存器的值开始递减,使用前后注意清零
三、SysTick逻辑图
四、时钟频率与延时
4.1时钟选择
采用参考8分频的参考时钟(168M / 8 = 21M)比较准,所以此处SysTick计数器选择21M的时钟
4.2 如何延时1us
时钟频率为21M,也就是1s的时间技术21M次。
由此可知计数一次用了 (1/21000000) s ,用了(1/21000) ms, 用了(1/21) us
所以,1us计数21次。
4.3 如何延时1ms
因为1ms = 1000us,所以综上所述,1ms计数1000*21次计数,1ms也就是21000次计数
4.3最大延时
24位计数器能保存的最大值 : 16777215。
最大延时时间 =:16777215 / 21 = 798915 us = 798.915ms
五、编程思路
5.1 Delay初始化
选择时钟源
关闭计数器
5.2 延时函数
设置重装载值
当前值清零
使能SysTick
等待计数器计数完毕
当前值清零
关闭计数器
六、示例代码
- #include "delay.h"
- #define Value_us 21
- #define Value_ms 21000
- void delay_init(void)
- {
- SysTick->CTRL &= (1 << 2); //控制寄存器位2置0,选择8分频时钟
- SysTick->CTRL &= ~(1 << 0); //关闭计数器
- }
- void delay_us(u32 num)
- {
- SysTick->LOAD = num * Value_us;
- SysTick->VAL = 0;
- SysTick->CTRL |= (1 << 0); //使能计数器
- while (!(SysTick->CTRL & 1<<16)); //判断是否计数完毕
- SysTick->VAL = 0;
- SysTick->CTRL &= ~(1 << 0); //关闭计数器
- }
- void delay_ms(u32 num)
- {
- SysTick->LOAD = num * Value_ms;
- SysTick->VAL = 0;
- SysTick->CTRL |= (1 << 0); //使能计数器
- while (!(SysTick->CTRL & 1<<16));
- SysTick->VAL = 0;
- SysTick->CTRL &= ~(1 << 0); //关闭计数器
- }
详细资料请参考《Cortex M3与M4权威指南》