完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
电子发烧友论坛|
前言
systick是Cortex-M内置的一个节拍定时器功能。它具有以下几个特点:
因为 systick处于底层,且内容比较简单,所以本文可以介绍一下其寄存器。其共有4个寄存器,在HAL库中通过一个结构体表达: /** brief Structure type to access the System Timer (SysTick). */ typedef struct { __IOM uint32_t CTRL; /*!< Offset: 0x000 (R/W) SysTick Control and Status Register */ __IOM uint32_t LOAD; /*!< Offset: 0x004 (R/W) SysTick Reload Value Register */ __IOM uint32_t VAL; /*!< Offset: 0x008 (R/W) SysTick Current Value Register */ __IM uint32_t CALIB; /*!< Offset: 0x00C (R/ ) SysTick Calibration Register */ } SysTick_Type; 其寄存器在内核中的功能为: 在STM32F429中略有不同,且由于第四个校正值寄存器使用较少,在此不再详细列出,其余的寄存器在STM32上使用为: STK_CTRL
位23到位0有效。写入的数值+1为中断产生的实际周期值。例如,需要每隔100个节拍周期产生一个中断,该寄存器的值设置为99。 STK_VAL 位23到位0有效。写入任何值,该寄存器清零且将STK_CTRL中的COUNTFLAG位清零。读取该寄存器,返回当前计数器的计数值。 正点原子 正点原子编写的代码与systick关系最大就是delay函数。若没有使用实时操作系统时,可以将delay.c简化为: #include "delay.h" #include "sys.h" static u32 fac_us = 0; //us延时倍乘数 /** * @brief 延时函数初始化 * @note None * @param SYSCLK AHB时钟频率(单位MHz) * @retval None */ void delay_init ( u8 SYSCLK ) { HAL_SYSTICK_CLKSourceConfig ( SYSTICK_CLKSOURCE_HCLK ); //SysTick频率为HCLK fac_us = SYSCLK; //设置全局变量 } /** * @brief 延时函数微秒 * @note None * @param nus:延时的微秒数,0~2^32/fac_us * @retval None */ void delay_us ( u32 nus ) { u32 ticks; //存储需要节拍数 u32 told, tnow, tcnt = 0; //told:上一次存储的时间,tnew:当前时间;tcent:累计的时间差 u32 reload = SysTick->LOAD; //读取sysTick装载值 ticks = nus * fac_us; //需要的节拍数 told = SysTick->VAL; //读取当前计数值 while ( 1 ) { tnow = SysTick->VAL; if ( tnow != told ) { /*获得上一次记录的时间和当前时间之间的差值*/ if ( tnow < told ) tcnt += told - tnow; else tcnt += reload - tnow + told; /*将当前时间记录下来*/ told = tnow; if ( tcnt >= ticks ) break; //时间超过/等于要延迟的时间,则退出. } } } /** * @brief 延时函数毫秒 * @note None * @param nms:延时的毫秒数 * @retval None */ void delay_ms ( u16 nms ) { u32 i; for ( i = 0; i < nms; i++ ) delay_us(1000); } 通过简化,可以看到代码很容易理解,工分成三个函数: delay初始化函数 微秒延时函数 毫秒延时函数 由于毫秒延时函数比较简单,此处不再详细展开。 delay初始化函数 该函数就只有两行代码,其作用为: 将Systick的时钟源设置为HCLK,一般的,也就是系统时钟180MHz。 将系统时钟传递给全局变量fac_us,为计算微秒延时函数做准备。 在第一个步骤中,调用一个函数HAL_SYSTICK_CLKSourceConfig(),该函数的定义为: /** * @brief Configures the SysTick clock source. * @param 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 HAL_SYSTICK_CLKSourceConfig(uint32_t CLKSource) { /* Check the parameters */ assert_param(IS_SYSTICK_CLK_SOURCE(CLKSource)); if (CLKSource == SYSTICK_CLKSOURCE_HCLK) { SysTick->CTRL |= SYSTICK_CLKSOURCE_HCLK; } else { SysTick->CTRL &= ~SYSTICK_CLKSOURCE_HCLK; } } 根据上述源程序,很容易理解,通过设置STK_CTRL寄存器,直接设定systick的时钟源。 微秒延时函数 void delay_us ( u32 nus ) { u32 ticks; //存储需要节拍数 u32 told, tnow, tcnt = 0; //told:上一次存储的时间,tnew:当前时间;tcent:累计的时间差 u32 reload = SysTick->LOAD; //读取sysTick装载值 ticks = nus * fac_us; //需要的节拍数 told = SysTick->VAL; //读取当前计数值 while ( 1 ) { tnow = SysTick->VAL; if ( tnow != told ) { /*获得上一次记录的时间和当前时间之间的差值*/ if ( tnow < told ) tcnt += told - tnow; else tcnt += reload - tnow + told; /*将当前时间记录下来*/ told = tnow; if ( tcnt >= ticks ) break; //时间超过/等于要延迟的时间,则退出. } } } 为了照顾在使用系统的情况,正点原子使用延时函数的方法叫 时钟摘取法。即通过在循环中,不断叠加时间差,知道时间差的数值大于等于设定值,则延时时间到,函数结束。其延时时间为计算方法为: systick的时钟源为AHB,一般的也就是系统时钟,为180MHz。其周期为1/180 us。 累积的时间界定值为:ticks = nus * fac_us=180*nus。 所以,累积时间为:ticks*1/180=nus。 综上,参数的取值就是延时的微秒数。 HAL库 在正点原子的程序中,很奇怪的一点就是,没有启动计数器,也没有设置计数器的加载值。这是因为在HAL库中HAL_init()函数已经做了这些工作。关于HAL_init()使用介绍,可以参考博客 HAL_InitTick(TICK_INT_PRIORITY); 1 其源程序定义为: __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority) { /*Configure the SysTick to have interrupt in 1ms time basis*/ HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000U); /*Configure the SysTick IRQ priority */ HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U); /* Return function status */ return HAL_OK; } 此处,我们只分析第一步,第二步与中断相关的内容暂且不看,其源代码为: __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) { if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) //设置的值大于24位 { return (1UL); /* Reload value impossible */ } SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */ SysTick->VAL = 0UL; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk| //时钟源为AHB SysTick_CTRL_TICKINT_Msk| //打开时钟中断 SysTick_CTRL_ENABLE_Msk; //启动systick时钟 return (0UL); /* Function successful */ } 除去中断部分,我们可以看到,该程序共有三个功能: 设置计数器的计数周期。 清除计数器当前计数值。 启动计数器开始计数,启动中断,设置时钟源为AHB。 又因为传递的参数为HAL_RCC_GetHCLKFreq()/1000U,即AHB时钟频率的千分之一,所以,该时钟的计时周期为1ms。 delay.c 在程序中,应该减少全局变量的使用,减少函数之间的耦合,所以上述程序可以修改为: /** ****************************************************************************** * @name delay.c * @author zhy * @version 1.0 * @date 2020.11.11 * @brief 通过时钟窃取法,得到延时时钟的功能。 ****************************************************************************** */ #include "stm32f4xx.h" //包含该头文件不会出现引用错误 #include "delay.h" /** * @brief 延时函数微秒 * @note None * @param nus:延时的微秒数,0~2^32/fac_us * @retval None */ void delay_us(uint32_t nus) { uint32_t fac_us = HAL_RCC_GetSysClockFreq() / 1e6; //us延时倍乘数 uint32_t ticks; //存储需要节拍数 uint32_t told, tnow, tcnt = 0; //told:上一次存储的时间,tnew:当前时间;tcent:累计的时间差 uint32_t reload = SysTick->LOAD; //读取sysTick装载值 ticks = nus * fac_us; //需要的节拍数 told = SysTick->VAL; //读取当前计数值 while (1) { tnow = SysTick->VAL; if (tnow != told) { /*获得上一次记录的时间和当前时间之间的差值*/ if (tnow < told) tcnt += told - tnow; else tcnt += reload - tnow + told; /*将当前时间记录下来*/ told = tnow; if (tcnt >= ticks) break; //时间超过/等于要延迟的时间,则退出. } } } /** * @brief 延时函数毫秒 * @note None * @param nms:延时的毫秒数 * @retval None */ void delay_ms(uint16_t nms) { uint32_t i; for (i = 0; i < nms; i++) delay_us(1000); } 第三种写法 /** * @brief 延时函数微秒 * @note 当时间比较短时,误差很大。 * @param nus:延时的微秒数,0~2^32/fac_us * @retval None */ void delay_us(uint32_t nus) { uint32_t told = SysTick->VAL; //记录当前时间值:放在第一句 uint32_t fac_us = HAL_RCC_GetSysClockFreq() / 1000000; //us延时倍乘数 uint32_t ticks = nus * fac_us; //存储需要节拍数 uint32_t tnow, tcnt = 0; //told:上一次存储的时间,tcnt:累计的时间差 uint32_t reload = SysTick->LOAD; //读取sysTick装载值 while (1) { tnow = SysTick->VAL; /*获得上一次记录的时间和当前时间之间的差值*/ if (tnow < told) { tcnt += told - tnow; } else { tcnt += reload - tnow + told; } /*将当前时间记录下来*/ told = tnow; if (tcnt >= ticks) break; //时间超过/等于要延迟的时间,则退出. } } 此处,对于delay_us函数进行又一步的改进: 第一次记录时间放在函数最前面,提高计算的精度。 将除法运算中的double类型替换为整数型,提高计算的速度。 delay.c更新 为了提高实时性,还是正点原子的写法比较优秀。更新最新的写法如下: /** ****************************************************************************** * @name delay.c * @author zhy * @version 1.0 * @date 2020.11.11 * @brief 通过时钟窃取法,得到延时时钟的功能。 ****************************************************************************** * @version 1.1 * @date 2021.04.07 * @brief 更新delay_us函数,减小delay误差 ****************************************************************************** * @version 1.2 * @date 2021.05.21 * @brief 更新delay_us函数,减小delay误差 ****************************************************************************** */ #include "stm32f4xx.h" //包含该头文件不会出现引用错误 #include "delay.h" //{ /*-----------------------------------私有域:开始--------------------------------------*/ uint32_t fac_us = 0; //us延时倍乘数 uint32_t reload = 0; //读取sysTick装载值 /*-----------------------------------私有域:结束--------------------------------------*/ //} /** * @brief delay函数初始化 * @note 使用delay函数必须要初始化 * @param {*}无 * @retval 无 */ void delay_init(void) { fac_us = HAL_RCC_GetSysClockFreq() / 1000000; //系统主频/10^6 reload = SysTick->LOAD; //过边界点 } /** * @brief 延时函数微秒 * @note 当nus取值比较小时,该延时函数的误差比较大 * @param nus:延时的微秒数,0~2^32/fac_us * @retval None */ void delay_us(uint32_t nus) { uint32_t told = SysTick->VAL; //记录当前时间值:放在第一句 uint32_t ticks = nus * fac_us; //存储需要节拍数 uint32_t tnow, tcnt = 0; //told:上一次存储的时间,tnew:当前时间;tcent:累计的时间差 while (1) { tnow = SysTick->VAL; //时钟时递减时钟 tcnt = tnow <= told ? tcnt + told - tnow : tcnt + reload - tnow + told; //获得上一次记录的时间和当前时间之间的差值 if (tcnt >= ticks) return; //时间超过/等于要延迟的时间,则退出. told = tnow; //将当前时间记录下来 } } /** * @brief 延时函数毫秒 * @note None * @param nms:延时的毫秒数 * @retval None */ void delay_ms(uint16_t nms) { uint32_t i; for (i = 0; i < nms; i++) delay_us(1000); } |
|
|
|
|
只有小组成员才能发言,加入小组>>
1599 浏览 0 评论
imx6ull 和 lan8742 工作起来不正常, ping 老是丢包
4738 浏览 0 评论
4229 浏览 9 评论
3822 浏览 16 评论
4398 浏览 1 评论
4210浏览 3评论
2379浏览 0评论
3393浏览 0评论
1158浏览 0评论
2843浏览 0评论
/9
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-12-2 03:27 , Processed in 0.717663 second(s), Total 74, Slave 54 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191

淘帖
1390