文章目录
- 系统定时器SysTick
- 滴答定时器寄存器
- STK_CTRL 控制寄存器
- STK_LOAD 重载寄存器
- STK_VAL 当前值寄存器
- STK_CALRB 校准值寄存器
- 非系统初始化 Systick 定时器
- SysTick_Init
- SysTick_CLKSourceConfig
- delay_us寄存器
- delay_us库函数
- delay_xms短时
- delay_ms长时
- SysTick_Config
- 系统RTOS延时
系统定时器SysTick
CM4 内核的处理和 CM3 一样,内部都包含了一个SysTick定时器。两者没有区别。其详细介绍,请参阅
《STM32F3与F4系列Cortex M4内核编程手册》第230页。
-
系统定时器是属于 CM3 内核中的一个外设,内嵌在 NVIC 中。因为所有的 CM3 芯片都带有这个定时器,软件在不同 CM3 器件间的移植工作得以化简。
-
系统定时器是一个 24bit 的向下递减的计数器,计数器每计数一次的时间为 1/SYSCLK,一般我们设置系统时钟 SYSCLK 等于 72M。
- 该定时器的时钟源可以是内部时钟,或者是外部时钟。不过,STCLK 的具体来源则由芯片设计者决定
-
当重装载数值寄存器的值递减到 0 的时候,系统定时器就产生一次中断,以此循环往复。
- SysTick 设定初值并使能后,每经过 1 个系统时钟周期,计数值就减 1。计数到 0 时,SysTick 计数器自动重装初值并继续计数,同时内部的 COUNT FLAG 标志会置位,触发中断 (如果中断使能情况下)。
-
只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。
在M4内核编程手册中这样说道:SysTick是一个24位系统定时器,它将重加载值递减到零。再在下一个时钟沿将该值重新装载到STK_LOAD寄存器中,然后根据随后的时钟进行递减。
滴答定时器寄存器
Systick 部分内容属于 NVIC 控制部分,一共有 4 个寄存器,名称和地址分别是:
STK_CTRL 0xE000E010 -- 控制寄存器
//貌似这个才是控制寄存器
STK_LOAD 0xE000E014 -- 重载寄存器
STK_VAL 0xE000E018 -- 当前值寄存器
STK_CALIB 0xE000E01C -- 校准值寄存器
STK_CTRL 控制寄存器
寄存器内有 4 个位具有意义
- 第 0 位:ENABLE,Systick 使能位(0:关闭 Systick 功能;1:开启 Systick 功能)
- 第 1 位:TICKINT,Systick 中断使能位(0:关闭 Systick 中断;1:开启 Systick 中断)
- 第 2 位:CLK SOURCE,Systick 时钟源选择(0:使用 HCLK/8 作为 Systick 时钟;1:使用 HCLK 作为 Systick 时钟)
- 第 16 位:COUNT FLAG,Systick 计数比较标志,当计数到0时,置1;定时器开始重新计数时(CLK_LOAD重新写入数值)自动清零。
STK_LOAD 重载寄存器
Systick 是一个递减的定时器,当定时器递减至0 时,重载寄存器中的值就会被重装载,继续开始递减。STK_LOAD 重载寄存器是个24 位的寄存器最大计数0xFFFFFF。
STK_VAL 当前值寄存器
24 位的寄存器,读取时返回当前倒计数的值,写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志。
STK_CALRB 校准值寄存器
SysTick calibration value register
- 位31 NOREF:= 1 没有外部参考时钟(STCLK 不可用),= 0 外部参考时钟可用。
- 位30 SKEW:= 1 校准值不是准确的1ms,= 0校准值是准确的1ms
- 不常用
非系统初始化 Systick 定时器
SysTick_Init
void SysTick_Init(u8 SYSCLK);
SysTick_Init(72);
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void SysTick_Init(u8 SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8;
fac_ms=(u16)fac_us*1000;
}
系统时钟是72/8M,计数一次时间1/9000000秒,换算成us就是1/9us,则计数72/8次也就是9次就是1us。
SysTick_CLKSourceConfig
设置SysTick的时钟源。可选项如下:
#define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
((SOURCE) == SysTick_CLKSource_HCLK_Div8))
HCLK的介绍
delay_us寄存器
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD = 9*nus;
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
do
{
temp=SysTick->CTRL;//读取当前倒计数值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
//使能且没有计数到0就一直循环
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
9*nus :假设外设频率为 9M,也就是经过 8 分频,那么计数 9 次是 1us,乘以 9 的意义就是参数的时间对应的次数,也就是重装载值
- 这里看一下判断条件,第一位是使能,16位是计数到0。
第 16 位:COUNT FLAG,Systick 计数比较标志,当计数到0时,置1;定时器开始重新计数时(CLK_LOAD重新写入数值)自动清零。
总结一下过程
- 先把要延时的 us 数换算成 SysTick 的时钟数
- 写入 LOAD 寄存器
- 清空当前寄存器 VAL 的内容
- 开启倒数功能。
- 等到倒数结束,即延时了 nus。
- 最后关闭 SysTick,清空 VAL 的值
注意:
- nus 的值,不能太大,必须保证 nus<=(2^24)/fac_us,否则将导致延时时间不准确
- temp&0x01,这一句是用来判断 systick 定时器是否还处于开启状态,可以防止 systick 被意外关闭导致的死循环。
delay_us库函数
//延时nus
//注意:nus的值,不要大于798915us(最大值即2^24/fac_us@fac_us=21)
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_xms短时
//延时nms
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对168M条件下,nms<=798ms
void delay_xms(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))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
delay_ms长时
//延时nms
//nms:0~65535
void delay_ms(u16 nms)
{
u8 repeat=nms/540; //这里用540,是考虑到某些客户可能超频使用,
//比如超频到248M的时候,delay_xms最大只能延时541ms左右了
u16 remain=nms%540;
while(repeat)
{
delay_xms(540);
repeat--;
}
if(remain)delay_xms(remain);
}
SysTick_Config
设置重装载值。该函数的内部就是个:SysTick->LOAD = 9*nus;
SysTick_Config 的参数是一个时钟次数,意思就是我要多少个1/fosc 时间后中断一下。
static void BSP_CoreClockInit(void)
{
SysTick_Config(SystemCoreClock / 100); //10ms
//SysTick_Config(720000); //10ms
}
SystemCoreClock / 100 代表的是时钟次数,即放入重装载寄存器中的值,每计时一个数需要 1/72000000s 的时间,那么计SystemCoreClock / 100 (SystemCoreClock 为 72M)个数的时间就是 10ms,通过在中断中设置标志位达到 systick 定时中断的功能。
函数实现
__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 */
}
在外设的中断优先级设置里,外设的硬件编号都是大于等于零的,内核外设的编号都小于零,相当于一个IP。
- __NVIC_PRIO_BITS=4,因为F4使用了四位。
- 中断处理函数IP:
typedef enum IRQn
{
/****** Cortex-M4 Processor Exceptions Numbers ****************************************************************/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */
MemoryManagement_IRQn = -12, /*!< 4 Cortex-M4 Memory Management Interrupt */
BusFault_IRQn = -11, /*!< 5 Cortex-M4 Bus Fault Interrupt */
UsageFault_IRQn = -10, /*!< 6 Cortex-M4 Usage Fault Interrupt */
SVCall_IRQn = -5, /*!< 11 Cortex-M4 SV Call Interrupt */
DebugMonitor_IRQn = -4, /*!< 12 Cortex-M4 Debug Monitor Interrupt */
PendSV_IRQn = -2, /*!< 14 Cortex-M4 Pend SV Interrupt */
SysTick_IRQn = -1, /*!< 15 Cortex-M4 System Tick Interrupt */
/****** STM32 specific Interrupt Numbers **********************************************************************/
WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */
PVD_IRQn = 1, /*!< PVD through EXTI Line detection Interrupt */
TAMP_STAMP_IRQn = 2, /*!< Tamper and TimeStamp interrupts through the EXTI line */
RTC_WKUP_IRQn = 3, /*!< RTC Wakeup interrupt through the EXTI line */
FLASH_IRQn = 4, /*!< FLASH global Interrupt */
RCC_IRQn = 5, /*!< RCC global Interrupt */
EXTI0_IRQn = 6, /*!< EXTI Line0 Interrupt */
EXTI1_IRQn = 7, /*!< EXTI Line1 Interrupt */
EXTI2_IRQn = 8, /*!< EXTI Line2 Interrupt */
EXTI3_IRQn = 9, /*!< EXTI Line3 Interrupt */
EXTI4_IRQn = 10, /*!< EXTI Line4 Interrupt */
DMA1_Stream0_IRQn = 11, /*!< DMA1 Stream 0 global Interrupt */
DMA1_Stream1_IRQn = 12, /*!< DMA1 Stream 1 global Interrupt */
DMA1_Stream2_IRQn = 13, /*!< DMA1 Stream 2 global Interrupt */
DMA1_Stream3_IRQn = 14, /*!< DMA1 Stream 3 global Interrupt */
DMA1_Stream4_IRQn = 15, /*!< DMA1 Stream 4 global Interrupt */
DMA1_Stream5_IRQn = 16, /*!< DMA1 Stream 5 global Interrupt */
DMA1_Stream6_IRQn = 17, /*!< DMA1 Stream 6 global Interrupt */
ADC_IRQn = 18, /*!< ADC1, ADC2 and ADC3 global Interrupts
内核外设和普通外设的中断优先级比较,虽然二者不是在同一个寄存器中,但是在比较时,也是把内核外设的优先级寄存器配置按照普通的解读。
系统RTOS延时
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
u32 reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //168M,8分频 21M
fac_us=SYSCLK/8; //不论是否使用OS,fac_us都需要使用21M
//168M也就是168次/us,对于滴答就是21次/us,所以计时21次就是1us,单位就是fac_us
reload=SYSCLK/8; //每秒钟的计数次数 单位为M
reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间
//reload为24位寄存器,最大值:16777216,在168M下,约合0.7989s左右
fac_ms=1000/delay_ostickspersec; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/delay_ostickspersec秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}
这里需要注意的是:SysTick 的时钟源自 HCLK 的 8 分频,假设我们外部晶振为 8M,
然后倍频到 168M,那么 SysTick 的时钟即为 21Mhz,也就是 SysTick 的计数器 VAL 每减 1,就
代 表 时 间 过 了 1/21us 。
fac_us=SYSCLK/8; ; 这 句 话 就 是 计 算 在SystemCoreClock 时 钟 频 率 下 延 时 1us 需 要 多 少 个 SysTick 时 钟 周 期。21M的时钟,21次是1us,1次就是1/21us。sysClk就是168。fac_us就是168/8次。
Systick 的时钟来自系统时钟 8 分频,正因为如此,系统时钟如果不是 8 的倍数(不能被 8 整除),则会导致延时函数不准确,这也是我们推荐外部时钟选择 8M 的原因