Systick就是一个定时器而已,只是它放在了NVIC中,主要的目的是为了给操作系统提供一个硬件上的中断(号称滴答中断)。滴答中断?这里来简单地解释一下。操作系统进行运转的时候,也会有“心跳”。它会根据“心跳”的节拍来工作,把整个时间段分成很多小小的时间片,每个任务每次只能运行一个“时间片”的时间长度就得退出给别的任务运行,这样可以确保任何一个任务都不会霸占整个系统不放。或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。 只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。
知道systick在系统中的地位后,我们来了解systick的实现。这里只是举例说明systick的使用。它有四个寄存器,笔者把它列出来:
SysTick->CTRL, --控制和状态寄存器
SysTick->LOAD, --重装载寄存器
SysTick->VAL, --当前值寄存器
SysTick->CALIB, --校准值寄存器
库里SysTick相关的函数我们能找到两个
一个在msic.h中
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
/* Check the parameters */
assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= SysTick_CLKSource_HCLK;
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
}
}
一个在core_m3.h中
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
/* set Priority for Cortex-M0 System Interrupts */
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_Handler()(库版本不同可能是SysTickHandler)中添加就好了,意思每到加载到SysTick中的值减到0时就执行SysTick();
SystemInit();
这个函数可以让主频运行到72M。可以把它作为systick的时钟源。
接着开始配置systick,实际上配置systick的严格过程如下:
1、调用SysTick_CounterCmd() --失能SysTick计数器
2、调用SysTick_ITConfig() --失能SysTick中断
3、调用SysTick_CLKSourceConfig() --设置SysTick时钟源。
4、调用SysTick_SetReload() --设置SysTick重装载值。
5、调用SysTick_ITConfig() --使能SysTick中断
6、调用SysTick_CounterCmd() --开启SysTick计数器
这里大家一定要注意,必须使得当前寄存器的值VAL等于0!
SysTick->VAL = (0x00);只有当VAL值为0时,计数器自动重载RELOAD。
接下来就可以直接调用Delay();函数进行延迟了。延迟函数的实现中,要注意的是,全局变量TimingDelay必须使用volatile,否则可能会被编译器优化。(以上的过程 在库函数SysTick_Config() 里已经配置稍做了解即可);
时钟的选择 在库文件 system_stm32f10x.c 里面
static void SetSysClock(void);
#ifdef SYSCLK_FREQ_HSE
static void SetSysClockToHSE(void);
#elif defined SYSCLK_FREQ_24MHz
static void SetSysClockTo24(void);
#elif defined SYSCLK_FREQ_36MHz
static void SetSysClockTo36(void);
#elif defined SYSCLK_FREQ_48MHz
static void SetSysClockTo48(void);
#elif defined SYSCLK_FREQ_56MHz
static void SetSysClockTo56(void);
#elif defined SYSCLK_FREQ_72MHz
static void SetSysClockTo72(void);
#endif
SetSysClock() 函数在SystemInit() 里面被调用
SystemInit() 在startup_stm32f10x_hd.s 启动文件中的Reset_Handler调用(有的启动文件里面没有需要在main里面自己调用)
Systick作为操作系统的心跳:
UCOS 系统的os_cfg.h文件中 #define OS_TICKS_PER_SEC 100u /* Set the number of ticks in one second */
定义了 每秒心跳(中断的次数); 100次. 假设是72MH的频率 , 在这里ucos给systick 的reload值应该为720000.
100次中断耗时1s.
心跳()时间片)原理为 SysTick_Handler() 中断函数. 里面增加 OSIntEnter() (OSIntNesting++),然后调用OSTimeTick() ucos的时钟服务程序,
最后OSIntExit() 任务调度一次.
OSTimeTick()的功能: 为OSTime+1 以及为所有等待任务控制块的OSTCBDly-1 (如果-1为0 由后面的OSIntExit启动调度)
OSTimeDly()与OSTimeDlyHMSM();
OSTimeDly(INT16U ticks)比较简单就是 任务控制块 状态改为等待 ,OSTCBCur->OSTCBDly赋值延迟心跳次数ticks.并任务调度.
OSTimeDlyHMSM(INT8U hours, INT8U minutes, INT8U seconds, INT16U ms)复杂了一点
小时分钟秒的延时简单的计算出对应的ticks 并调用OSTimeDly();毫秒级别的:
注意:最小延时时间为心跳 (1/OS_TICKS_PER_SEC);
ticks= OS_TICKS_PER_SEC * ((INT32U)ms + 500L / OS_TICKS_PER_SEC) / 1000L;
这里有个500L的取值.我们暂定为MINTIME变量吧.
邵贝贝书里说:这里的500的取值 目的是为了(假设OS_TICKS_PER_SEC=100 最小心跳10ms) 延时4ms的时候不延时,5ms以上的时候才延时. 依次14ms延迟一个节拍 15ms两个节拍.
个人感觉怪怪的....
那么如果想单纯的引发一次任务调度.那么OSTimeDlyHMSM(0,0,0,5) 至少是5ms才行.
对于单位 10ms以内的精确延时还是要抛弃ucos的函数而自己写.
为了实现小于(1/OS_TICKS_PER_SEC)的延时函数.
传统的做法是定义delay_us()函数.函数功能,对Systicks 定时器的reload 重装值赋值 需要延时的时间对应的数值.
程序较为简单,但是会影响ucos的心跳.不能在操作系统下使用;
为了能在操作系统下实现us级别的延时.
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数 fac_us=时钟频率/1000000(这里是72)
tcnt=0;
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow=ticks)break;//时间超过/等于要延迟的时间,则退出.
}
};
}
这段函数功能,就是不断的读取SysTick->VAL ; Systick定时器的计数值;并计算差值. 如果大于需要的ticks 啧延时结束
如果需要的ticks大于重装载值, 用systick递减的原理.判断如果取得计数值大于记录计数值,啧更新记录值,并对cnt计数加reload重装载值. 实现us延时
STM32库函数的封装默认的外部晶振是8MHZ 一切的频率设置都是以此为基础的.
操作寄存器的话需要自己 计算倍频和分频.并设置时钟的初始化.