stm32的systick原理与应用
/* SysTick滴答定时器 一、功能 SysTick定时器是一个简单的定时器,CM3\CM4内核芯片都具备此定时器。SysTick定时器常用来做延时,采用实时系统时则用来做系统时钟。无论用作延时还是用作系统心跳时钟,不需要太复杂的功能,SysTick即可胜任。 二、实现原理 SysTick定时器是一个24位的倒计数,当倒计数为0时,将从RELOAD寄存器中取值作为定时器的初始值,同时可以选择在这个时候产生中断(异常号:15)。 例如从RELOAD的值为999,那么当倒计数为0时,就会从复位为999继续倒计数。 只要不把它在SysTick控制及状态寄存器中的使能位清楚,就永不停息,即使在睡眠模式下也能继续工作。 三、SysTick寄存器(在 core_cm3.h 有定义,凡是 M3 内核的单片机都是一样的) */ #define SysTick ((SysTick_Type *) SysTick_BASE) #define SysTick_BASE (SCS_BASE + 0x0010) #define SCS_BASE (0xE000E000) typedef struct { __IO uint32_t CTRL; // 控制及状态寄存器 __IO uint32_t LOAD; // 重装载数值寄存器 __IO uint32_t VAL; // 当前计数数值寄存器 __I uint32_t CALIB; // 校准寄存器 } SysTick_Type; /* SysTick->CTRL: (可通过 SysTick_CLKSourceConfig() 函数设置) COUNTFLAG(16)R: 计数标志位 当SysTick数到0,则该位被硬件置 1,当读取该位时,将被硬件清零 CLKSOURCE(2)R/W: 时钟源设置 1 = 外部时钟源(STCLK) (AHB总线时钟的1/8(HCLK/8)) 0 = 内核时钟(FCLK) (AHB总线时钟的频率(HCLK)) TICKINT(1)R/W: 中断使能位 1 = SysTick 倒数到0时产生 SysTick 异常请求 0 = 数到 0 时无动作 ENABLE(0)R/W: SysTick 定时器使能位 (当中断被使能后,需要关注 void SysTick_Handler(void) 函数) SysTick_Type->LOAD: (SysTick_Config() 函数会设置该寄存器) RELOAD(23:0)R/W: 重装载数值寄存器 当SysTick数到0,将被重装载的值 SysTick_Type->VAL: (SysTick_Config() 函数会设置该寄存器) CURRENT(23:0)R/Wc: 当前计数数值寄存器 读取时返回当前倒计数的值,写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志。 四、库函数分析 misc.c ---------------------------------------------------------------------------------- */ #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)) 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; // 设置 CLKSOURCE 为 1 } else { SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; // 设置 CLKSOURCE 为 0 } } core_cm3.c ---------------------------------------------------------------------------------- #define SysTick_LOAD_RELOAD_Pos 0 #define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) typedef enum IRQn { //... SysTick_IRQn = -1, //... } IRQn_Type; #define __NVIC_PRIO_BITS 4 #define SysTick_CTRL_CLKSOURCE_Pos 2 #define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) #define SysTick_CTRL_TICKINT_Pos 1 #define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) #define SysTick_CTRL_ENABLE_Pos 0 #define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) static __INLINE uint32_t SysTick_Config(uint32_t ticks) { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ // 设置计数值为 ticks - 1 // 原因1:视频说是执行这些代码需要时间,所以减少一个节拍 // 原因2:我认为是因为 SysTick 的倒计数到 0,例如设置 1000 ,那么范围就应该是 999 ~ 0。 SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; // 设置中断优先级 NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); SysTick->VAL = 0; // 设置时钟源为外部时钟源,同时开启中断、并使能 SysTick 定时器 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; return (0); } /* 五、延时应用 1、中断方式 */ 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);//2ms // ... } } /* SysTick_Config(SystemCoreClock / 1000): (原代码这里假设是采用时钟源为 HCLK) 这里设置的是 72000000Hz / 1000 = 72000 ticks,也就是说 SysTick 从 (72000-1) 开始倒数。 每倒数完 72000 个节拍就触发一次中断。 一个节拍的时间为:72000000 / 72000 = 1000us == 1ms SysTick_Config((SystemCoreClock / 8000000) * 1000 * 1): SysTick_Config() 会设置时钟源为 HCLK/8 所以实际应用中不能按照上述代码的参数。 SystemCoreClock / 8000000: 1us 的节拍数 1us的节拍数 * 1000: 则为 1ms 的节拍数 1ms 的节拍数 * 1: 设置 1ms 一个SysTick中断,即从 ((SystemCoreClock / 8000000) * 1000 * 1) - 1 开始倒数。 2、轮询方式 */ static u8 fac_us=0; //us延时倍乘数 static u16 fac_ms=0; //ms延时倍乘数 void delay_init() { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8 fac_us = SystemCoreClock/8000000; // 为系统时钟的1/8 1us = 72000000 / 8000000 = 9 个节拍 fac_ms = (u16)fac_us*1000; // 1ms 需要 9 * 1000 = 9000 个节拍 } //延时 nus 微秒 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; //清空计数器 } //延时nms //注意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; //等待时间到达,这里使用了一个小技巧,通过(temp&0x01)检查 SysTick 的使能位,避免 Systick 定时器被关闭而导致无限循环 } while((temp&0x01)&&!(temp&(1<<16))); SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器 SysTick->VAL =0X00; //清空计数器 }
SYSTick 定时器
CM3 内核的处理器,内部包含了一个 SysTick 定时器,(SysTick 的时钟源自HCLK的8分频,8个系统时钟周期systick跳一个,即8*1/72M=1/9 us)SysTick是一个24位的倒计数定时器,当计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。
利用 STM32 的内部 SysTick 来实现延时的,这样既不占用中断,也不占用系统定时器。因为在 ucos 下 systic 不能再被随意更改,如果我们还想利用 systick 来做 delay_us 或者delay_ms 的延时,就必须想点办法了,这里我们利用的是时钟摘取法。以 delay_us 为例,比如delay_us (50),在刚进入 delay_us 的时候先计算好这段延时需要等待的 systick 计数次数,这里为 50*9 (假设系统时钟为 72Mhz,那么 systick 每增加 1,就是 1/9us) ,然后我们就一直统计systick的计数变化,直到这个值变化了50*9,一旦检测到变化达到或者超过这个值,就说明延时50us时间到了。———实质上就是不改变systick基本单位时长,以基本单位时长为基本元做多次到达摘取。