STM32F1xx官方资料:
《Cortex-M3权威指南-中文》-第8章最后一个小节:Systick定时器
Systick定时器,是一个简单的定时器,对于CM3、CM4内核芯片,都有Systick定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。
Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。Systick中断的优先级也可以设置。
实际上,Systick就是一个定时器而已,只是它放在了NVIC中,主要的目的是为了给操作系统提供一个硬件上的中断,称之为滴答中断操作系统进行运转的时候,也会有时间节拍。它会根据节拍来工作,把整个时间段分成很多小小的时间片,而每个任务每次只能运行一个时间片的时间长度,超时就退出给别的任务运行,这样可以确保任何一个任务都不会霸占操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统的节拍。只要不把它在SysTick控制及状态寄存器中的使能位清除,就一直执行。
SysTick有四个寄存器,分别为CTRL(控制与状态寄存器)、LOAD(自动重装载值寄存器)、VAL(当前值寄存器)、CALIB(校准值寄存器)。
在MDK的core_m3.h文件中定义了一个结构体SysTick_Type,里面也包括了这四个寄存器。
typedef struct
{
__IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */
__IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */
__IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */
__I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */
} SysTick_Type;
它们的各位描述如下面的表格所述:
位段 | 名称 | 类型 | 复位值 | 描述 |
16 | COUNTFLAG | R | 0 | 如果在上次读取本寄存器后,SysTick已经数到了0,是该位为1.如果读 取该位,该位自动清零。 |
2 | CLKSOURCE | R/W | 0 | 0 外部时钟源(STCLK) 1 内核时钟(FCLK) |
1 | TICKINT | R/W | 0 | 1 SysTick倒数到0时产生SysTick异常请求 0 数到0时无动作 |
0 | ENABLE | R/W | 0 | SysTick定时器的使能位 |
对于STM32,外部时钟源(STCLK)是HCLK(AHB总线时钟)的1/8,内核时钟(FCLK)是HCLK(AHB总线时钟)。
位段 | 名称 | 类型 | 复位值 | 描述 |
23:0 | RELOAD | R/W | 0 | 当倒数至0是,将被重新装载的值 |
位段 | 名称 | 类型 | 复位值 | 描述 |
23:0 | CORRENT | R/Wc | 0 | 读取时返回当前倒计数的值,写它则使之清零,同时还会清 除在CTRL寄存器的COUNTFLAG位 |
SysTick_CLKSourceConfig(),这是Systick的时钟源选择,直接配置CTRL寄存器的值。假设HCLK为72MHz,选用外部时钟源,那么SysTick的时钟即9MHz。这就意味着,SysTick的计数器VAL每减1代表时间过去了1/9us。
具体的定义在misc.c文件中。
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;
}
}
SysTick_Config(uint32_t ticks) ,这是SysTick的初始化函数,时钟为HCLK,并开启SysTick中断。其中函数的参数表示两次中断之间时间间隔期间的SysTick周期,即两次中断之间有多少个SysTick周期。
具体的定义在core_cm3.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 */
}
void SysTick_Handler(void),这是SysTick的中断服务函数。我们举一个例子,利用中断的方式实现delay延时函数,见下面的程序:
static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
void SysTick_Handler(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
int main(void)
{ …
if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms
{
while (1);
}
while(1)
{ Delay(200);//200ms
…
}
}
之前利用中断实现延时函数,但是一直使用中断会造成资源的浪费,不建议这样实现,我们利用查询的方式实现delay延时。下面主要介绍Delay延时函数的实现:
首先是delay_init(),延时初始化函数。利用Syst_CLKSourceConfig()函数选择SysTick时钟源,选择外部时钟(HCLK的1/8);同时初始化fac_us和fac_ms两个变量。
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000; //为系统时钟的1/8,实际上也就是在计算1usSysTick的VAL减的数目
fac_ms=(u16)fac_us*1000; //代表每个ms需要的systick时钟数,即每毫秒SysTick的VAL减的数目
}
其次,delay_ms(),此函数用来延时指定的ms。
此时要注意nms的范围,SysTick->LOAD为24位寄存器,所以最大延时为:nms<=0xffffff*8*1000/SYSCLK;SYSCLK单位为Hz,nms单位为ms。对72M条件下,nms<=1864。如果超出这个值,建议多次调用此函数来实现。
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达,看CTRL的第16位(COUNTFLAG)是否为1,看STRL的第0位(ENABLE)是否为1
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
这段代码其实就是先把延时的时间换算成SysTick的时钟周期数,然后写入LOAD寄存器。然后清空当前寄存器VAL的内容,再开启倒数功能。等倒数结束即延时了nms、最后关闭SysTick,清空VAL的值,实现一次延时的操作。
这里特别说一下,temp&0x01,这一句用来判断SysTick定时器是否还处在开启的状态,可以防止SysTick被意外关闭导致的死循环。
最后,delay_us(),此函数用来延时指定的us。具体的逻辑和上面一个函数类似,就不介绍了。
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
delay延时的相关函数在SYSTEM文件夹下的delay子文件夹,在使用delay_ms()或者delay_us()函数之前一定不要忘记先初始化delay_init()。