[其他ST产品] SYSTEM文件夹下的delay.c文件内延时函数详解

[复制链接]
9767|19
 楼主| 4y1b3 发表于 2023-12-26 17:17 | 显示全部楼层 |阅读模式
笔记:
首先是对应的头文件delay.h中的函数:
#ifndef __DELAY_H
#define __DELAY_H                           
#include <sys.h>       

void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);

#endif
可以看到只有三个函数,delay_init(u8 SYSCLK)是SysTick定时器初始化的函数,delay_ms(u16 nms)是计毫秒的,delay_us(u32 nus)是计微秒的。

在调用相关的函数之前,一定要先初始化!

关于fac_ms和fac_us,在文件一开始有这样的定义(一开始是0,后来根据实际情况改变):

static u8  fac_us=0;                                                        //us延时倍乘数                          
static u16 fac_ms=0;                                                        //ms延时倍乘数,在os下,代表每个节拍的ms数


 楼主| 4y1b3 发表于 2023-12-26 17:17 | 显示全部楼层
1、delay_init(u8 SYSCLK);
  1. //初始化延迟函数
  2. //当使用OS的时候,此函数会初始化OS的时钟节拍
  3. //SYSTICK的时钟固定为AHB时钟的1/8
  4. //SYSCLK:系统时钟频率
  5. void delay_init(u8 SYSCLK)
  6. {
  7. #if SYSTEM_SUPPORT_OS                                                 //如果需要支持OS.
  8.         u32 reload;
  9. #endif
  10.         SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
  11.         fac_us=SYSCLK/8;                                                //不论是否使用OS,fac_us都需要使用
  12. #if SYSTEM_SUPPORT_OS                                                 //如果需要支持OS.
  13.         reload=SYSCLK/8;                                                //每秒钟的计数次数 单位为M          
  14.         reload*=1000000/delay_ostickspersec;        //根据delay_ostickspersec设定溢出时间
  15.                                                                                         //reload为24位寄存器,最大值:16777216,在168M下,约合0.7989s左右       
  16.         fac_ms=1000/delay_ostickspersec;                //代表OS可以延时的最少单位          
  17.         SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;           //开启SYSTICK中断
  18.         SysTick->LOAD=reload;                                         //每1/delay_ostickspersec秒中断一次       
  19.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;         //开启SYSTICK   
  20. #else
  21.         fac_ms=(u16)fac_us*1000;                                //非OS下,代表每个ms需要的systick时钟数   
  22. #endif
  23. }       
 楼主| 4y1b3 发表于 2023-12-26 17:17 | 显示全部楼层
此处将把关于UCOS相关代码忽略,后面学习:
这里出现了SysTick_CLKSourceConfig(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;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}
 楼主| 4y1b3 发表于 2023-12-26 17:18 | 显示全部楼层
加上宏定义:
  1. #define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
  2. #define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)
  3. #define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
  4.                                        ((SOURCE) == SysTick_CLKSource_HCLK_Div8))


  5. #define SysTick             ((SysTick_Type   *)     SysTick_BASE  )   /*!< SysTick configuration struct       */
  6. #define SysTick_BASE        (SCS_BASE +  0x0010UL)                    /*!< SysTick Base Address               */
  7. #define SCS_BASE            (0xE000E000UL)                            /*!< System Control Space Base Address  */

  8. typedef struct
  9. {
  10.   __IO uint32_t CTRL;                    /*!< Offset: 0x000 (R/W)  SysTick Control and Status Register */
  11.   __IO uint32_t LOAD;                    /*!< Offset: 0x004 (R/W)  SysTick Reload Value Register       */
  12.   __IO uint32_t VAL;                     /*!< Offset: 0x008 (R/W)  SysTick Current Value Register      */
  13.   __I  uint32_t CALIB;                   /*!< Offset: 0x00C (R/ )  SysTick Calibration Register        */
  14. } SysTick_Type;           //注意SysTick_Type在这里!!!
 楼主| 4y1b3 发表于 2023-12-26 17:18 | 显示全部楼层
注:以下为SysTick结构体详解,与主体函数只是有一定联系,可略过。
 楼主| 4y1b3 发表于 2023-12-26 17:18 | 显示全部楼层
SysTick结构体中的CTRL就是系统控制和状态寄存器(SysTick Control and Status Register):
23414658a9a66c271b.png
 楼主| 4y1b3 发表于 2023-12-26 17:18 | 显示全部楼层
第零位的ENABLE就是开关。

第一位的TICKINT中的 “1=SysTick倒数到0时产生Sys Tick异常请求” 就是产生中断信号。

第二位的CLKSOURCE选择0,外部时钟源,意思是选择AHB始终总线提供的SYSCLK,这在上个笔记SystemInit函数的初始化中记过。

而SysTick_CLKSource_HCLK_Div8就是8分频的意思,因此fac_us=SYSCLK/8的意思是将SYSCLK的频率给fac_us时应该➗8,默认SYSCLK是最大值,168MHz,所以此时fac_us=21,之后会用到。
 楼主| 4y1b3 发表于 2023-12-26 17:19 | 显示全部楼层
SysTick结构体中的LOAD就是重转载数据寄存器(24位): 55910658a9a8adbaf8.png
 楼主| 4y1b3 发表于 2023-12-26 17:19 | 显示全部楼层
SysTick结构体中的VAL就是当前值寄存器(24位): 35976658a9a9c20073.png
 楼主| 4y1b3 发表于 2023-12-26 17:19 | 显示全部楼层
一般是先写入LOAD,然后LOAD传值给VAL。
 楼主| 4y1b3 发表于 2023-12-26 17:19 | 显示全部楼层
SysTick结构体中的CALIB就是校准数值寄存器(24位)(不常用): 79728658a9ab3a0a48.png
 楼主| 4y1b3 发表于 2023-12-26 17:20 | 显示全部楼层
这些都可服务于core_cm4.h中的一个叫SysTick_Config(uint32_t ticks);的函数,该函数作用就是初始化SysTick定时器并且定时,且可以直接用于main()函数:
__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 */
}
 楼主| 4y1b3 发表于 2023-12-26 17:20 | 显示全部楼层
ticks就是两个中断之间的时钟周期个数

SysTick_Config函数让SysTick定时器初始化过程:
先判断ticks有效性,之后把ticks-1赋给LOAD,然后设置优先级(此处后面学习),再之后令VAL=0(让VAL重新加载),最后设定TRL,使能之。
 楼主| 4y1b3 发表于 2023-12-26 17:20 | 显示全部楼层
使用示例:SysTick_Config(168000000/1000);代表:168000000/1000*(1/168MHz)=1/1000=1(ms)。即定时为1ms。并且产生中断,由SysTick_Handler函数检测到并作出反应。由于官方的delay.h文件采用的不是中断的方式计时,在此不过多赘述。
 楼主| 4y1b3 发表于 2023-12-26 17:20 | 显示全部楼层
2、void delay_ms(u16 nms);和delay_us(u32 nus);
  1. //延时nus
  2. //nus为要延时的us数.
  3. //注意:nus的值,不要大于798915us(最大值即2^24/fac_us@fac_us=21)
  4. void delay_us(u32 nus)
  5. {               
  6.         u32 temp;                     
  7.         SysTick->LOAD=nus*fac_us;                                 //时间加载                           
  8.         SysTick->VAL=0x00;                                        //清空计数器
  9.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数          
  10.         do
  11.         {
  12.                 temp=SysTick->CTRL;
  13.         }while((temp&0x01)&&!(temp&(1<<16)));        //等待时间到达   
  14.         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
  15.         SysTick->VAL =0X00;                                       //清空计数器
  16. }
 楼主| 4y1b3 发表于 2023-12-26 17:20 | 显示全部楼层
SysTick->LOAD=nus*fac_us的nus是想要计数的微妙数,而fac_us含义是1微秒所要计数的次数,根据第一节计算,值为168/8=21,单位是M。

!(temp&(1<<16))的含义就是1左移16位,再与上temp,最后取反。CRTL的第16位数到0则为1,如果读了就变成0,所以当:

1、temp&0x01=0(恒成立,因为CRTL第0位是使能位,为0打开);

2、temp&(1<<16)=0xFFFF(即CRTL的16位为1)时,整个结果取反,!temp&(1<<16)=0:
 楼主| 4y1b3 发表于 2023-12-26 17:21 | 显示全部楼层
时间到达,跳出循环。(temp=SysTick->CTRL)
  1. //延时nms
  2. //nms:0~65535
  3. void delay_ms(u16 nms)
  4. {                  
  5.         u8 repeat=nms/540;                                                //这里用540,是考虑到某些客户可能超频使用,
  6.                                                                                         //比如超频到248M的时候,delay_xms最大只能延时541ms左右了
  7.         u16 remain=nms%540;
  8.         while(repeat)
  9.         {
  10.                 delay_xms(540);
  11.                 repeat--;
  12.         }
  13.         if(remain)delay_xms(remain);
  14. }
 楼主| 4y1b3 发表于 2023-12-26 17:21 | 显示全部楼层
这里用到了切片处理,每540ms算一片,比如延迟2000ms,则是延迟三个540ms+一个380ms。

540这个数值可以调整,好处是有效范围变长,比如原本最多延迟798ms(nms<=0xffffff*8*1000/SYSCLK),但是这样操作后,最多延迟为65535,即0xFFFF,u16的最大长度。
 楼主| 4y1b3 发表于 2023-12-26 17:21 | 显示全部楼层
这里面用到了delay_xms函数:
  1. //延时nms
  2. //注意nms的范围
  3. //SysTick->LOAD为24位寄存器,所以,最大延时为:
  4. //nms<=0xffffff*8*1000/SYSCLK
  5. //SYSCLK单位为Hz,nms单位为ms
  6. //对168M条件下,nms<=798ms
  7. void delay_xms(u16 nms)
  8. {                                     
  9.         u32 temp;                  
  10.         SysTick->LOAD=(u32)nms*fac_ms;                        //时间加载(SysTick->LOAD为24bit)
  11.         SysTick->VAL =0x00;                                   //清空计数器
  12.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数
  13.         do
  14.         {
  15.                 temp=SysTick->CTRL;
  16.         }while((temp&0x01)&&!(temp&(1<<16)));        //等待时间到达   
  17.         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
  18.         SysTick->VAL =0X00;                                       //清空计数器                      
  19. }
 楼主| 4y1b3 发表于 2023-12-26 17:21 | 显示全部楼层
此函数和delay_us基本上一致,不再赘述。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

51

主题

358

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部