本文主要来填坑,更正之前文章的错误。也进一步加深了我对SysTick定时器的理解,希望对你有帮助。
01坑的由来
在之前的推文中《STM32延时的四种方法》介绍了使用查询定时器精确延时,使用的就是systick定时器,具体代码如下
- void delay_us(uint32_t nus)
- {
- uint32_t temp;
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000000/8*nus;
- SysTick->VAL=0X00;//清空计数器
- SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
- do
- {
- temp=SysTick->CTRL;//读取当前倒计数值
- }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
- SysTick->CTRL=0x00; //关闭计数器
- SysTick->VAL =0X00; //清空计数器
- }
- void delay_ms(uint16_t nms)
- {
- uint32_t temp;
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;
- SysTick->VAL=0X00;//清空计数器
- SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
- do
- {
- temp=SysTick->CTRL;//读取当前倒计数值
- }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
- SysTick->CTRL=0x00; //关闭计数器
- SysTick->VAL =0X00; //清空计数器
- }
对于《STM32延时的四种方法》文中所说的内容如下
也就是下面代码中/8的原因。
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;
我对此深信不疑,并在STM32F207参考手册(RM0033)上找到“证据”。
上图①处直接是8分频,而不像②出的1/2/4/8分频。所以我确信是SYSTICK的时钟固定为HCLK时钟的1/8。
我在学习RTThread的时候,看到配置SysTick定制器代码如下
我心里一堆问号,STM32官方手册,明明写了SYSTICK的时钟固定为HCLK时钟的1/8。我使用示波器测量,RTThread的配置是没有问题,可以正常延时的。
02填坑
这个坑其实很简单,在《STM32延时的四种方法》也提到了,只是自己没有注意这个细节。
位2置1,表示时钟频率为AHB,也就是默认的120000000Hz。
位2清0,表示时钟频率为AHB/8,也就是120000000/8Hz。
RTThread配置为内部时钟
之前的文章配置为外部时钟源
这个细节我没有留意,导致我看RTThread代码时有点懵逼。在这里我更正《STM32延时的四种方法》中的错误描述
准确的描述是:
SYSTICK的时钟可以为HCLK时钟的1分频或8分频,在这里我们选用外部时钟源120M,所以SYSTICK的时钟为(120/8)M。
特此更正。
关于这点,STM32的标准外设库提供的SysTick_Config函数,也是使用内部时钟的
- /** \brief System Tick Configuration
- This function initialises the system tick timer and its interrupt and start the system tick timer.
- Counter is in free running mode to generate periodical interrupts.
- \param [in] ticks Number of ticks between two interrupts
- \return 0 Function succeeded
- \return 1 Function failed
- */
- 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 */
- }
调用方法,产生1ms中断调用方法
- SysTick_Config(SystemCoreClock / 1000);
关于时钟源的选择,除了操作寄存器外,还有库函数可以选择。
- /**
- * @brief Configures the SysTick clock source.
- * @param SysTick_CLKSource: specifies the SysTick clock source.
- * This parameter can be one of the following values:
- * @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.
- * @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.
- * @retval None
- */
- 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的时钟可以为HCLK时钟的1分频或8分频。
在STM32CubeMx配置软件中,可以选择1分频或8分频。
03修改代码验证
把《STM32延时的四种方法》文中涉及的代码修改成1分频的。
- void delay_ms(uint16_t nms)
- {
- uint32_t temp;
- SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000*nms-1;
- SysTick->VAL=0X00;//清空计数器
- SysTick->CTRL=0X01;
- SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
- do
- {
- temp=SysTick->CTRL;//读取当前倒计数值
- }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
- SysTick->CTRL=0x00; //关闭计数器
- SysTick->VAL =0X00; //清空计数器
- }
然后调用
- GPIO_SetBits(GPIOE,GPIO_Pin_4); //熄灭LED灯
- delay_ms(500);//延时500ms
- GPIO_ResetBits(GPIOE,GPIO_Pin_4);//点亮LED灯
- delay_ms(500);//延时500ms
就踩到另一个坑,延时不准。
原因是:此时SYSTICK时钟频率是120MHz的24位的倒计数定时器,也就是说一个周期,最多定时139.810125ms。不能延时500ms。
这里再更正之前的一个错误,如下图
这个计数器的值,我们减去了1,这样才更准确。需要减1的具体原因在定时器讲解的文章中讲解过了,不明白的同学请看《STM32基础定时器讲解》。
04总结
总结:STM32官方手册并不一定是准确的,要亲自做实验,自己动手验证。这是个老生常谈的问题,大家都知道,关键还在于实践。