STM32
直播中

哥儿

8年用户 865经验值
擅长:嵌入式技术
私信 关注
[问答]

如何实现操作系统和delay函数共用的SysTick定时器呢

系统定时器systick延时的delay文件包括哪些?
如何实现操作系统和delay函数共用的SysTick定时器呢?

回帖(2)

冬妮

2021-11-24 09:15:28
  CM4 内核的处理和 CM3 一样,内部都包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计到 0 时,将从 RELOAD 寄存器中自动重装载定时初值。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。
  delay 文件夹内包含了 delay.c 和 delay.h 两个文件,这两个文件用来实现系统的延时功能,其中包含 7 个函数:
  -下面4 个函数,仅在支持操作系统(OS)的时候,需要用到
  void delay_osschedlock(void);
  void delay_osschedunlock(void);
  void delay_ostimedly(u32 ticks);
  void SysTick_Handler(void);
  -下面 3 个函数,则不论是否支持 OS 都需要用到。
  void delay_init(u8 SYSCLK);
  void delay_ms(u16 nms);
  void delay_us(u32 nus);
  以 UCOSII 为例,介绍如何实现操作系统和我们的 delay 函数共用 SysTick 定时器。
  首先,我们简单介绍下 UCOSII 的时钟:ucos 运行需要一个系统时钟节拍(“心跳”),而这个节拍是固定的(由 OS_TICKS_PER_SEC 宏定义设置),比如要求 5ms 一次(即可设置:OS_TICKS_PER_SEC=200),在 STM32 上面,一般是由 SysTick 来为 ucos 提供时钟节拍,也就是 systick要设置为 5ms 中断一次,而且这个时钟一般是不能被打断的(否则就不准了)。
  在 ucos 下 systick 不能再被随意更改,如果我们还想利用 systick 来做 delay_us 或者delay_ms 的延时,就必须利用时钟摘取法。以 delay_us 为例,比如delay_us(50),在刚进入 delay_us 的时候先计算好这段延时需要等待的 systick 计数次数,这里为 50 * 21次(假设系统时钟为 168Mhz,因为 systick 的频率为系统时钟频率的 1/8,那么 systick每增加 1,就是 1/21us),然后我们就一直统计 systick 的计数变化,直到这个值变化了 50*21,一旦检测到变化达到或者超过这个值,就说明延时 50us 时间到了。这样,我们只是抓取 SysTick计数器的变化,并不需要修改 SysTick 的任何状态,完全不影响 systick 作为 UCOS 时钟节拍的功能,这就是实现 delay 和操作系统共用 SysTick 定时器的原理。
  操作系统支持的3宏定义及4相关函数
  当需要 delay_ms 和 delay_us 支持操作系统(OS)的时候,我们需要用到 3 个宏定义和 4个函数
  //本例程仅作 UCOSII 和 UCOSIII 的支持,其他 OS,请自行参考着移植
  //支持 UCOSII //
  #ifdef OS_CRITICAL_METHOD
  //OS_CRITICAL_METHOD 定义了,说明要支持 UCOSII
  #define delay_osrunning OSRunning //OS 是否运行标记,0,不运行;1,在运行
  #define delay_ostickspersec OS_TICKS_PER_SEC //OS 时钟节拍,即每秒调度次数
  #define delay_osintnesting OSIntNesting //中断嵌套级别,即中断嵌套次数
  #endif
  //支持 UCOSIII//
  #ifdef CPU_CFG_CRITICAL_METHOD
  //CPU_CFG_CRITICAL_METHOD 定义了,说明要支持 UCOSIII
  #define delay_osrunning OSRunning //OS 是否运行标记,0,不运行;1,在运行
  #define delay_ostickspersec OSCfg_TickRate_Hz //OS 时钟节拍,即每秒调度次数
  #define delay_osintnesting OSIntNestingCtr //中断嵌套级别,即中断嵌套次数
  #endif
  //us 级延时时,关闭任务调度(防止打断us级延迟)
  void delay_osschedlock(void)
  {
  #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII
  OS_ERR err;
  OSSchedLock(&err); //UCOSIII 的方式,禁止调度,防止打断 us 延时
  #else //否则 UCOSII
  OSSchedLock(); //UCOSII 的方式,禁止调度,防止打断 us 延时
  #endif
  }
  //us 级延时时,恢复任务调度
  void delay_osschedunlock(void)
  {
  #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII
  OS_ERR err;
  OSSchedUnlock(&err); //UCOSIII 的方式,恢复调度
  #else //否则 UCOSII
  OSSchedUnlock(); //UCOSII 的方式,恢复调度
  #endif
  }
  //调用 OS 自带的延时函数延时//
  void delay_ostimedly(u32 ticks) //ticks:延时的节拍数
  {
  #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII 时
  OS_ERR err;
  OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);//UCOSIII 延时采用周期模式
  #else
  OSTimeDly(ticks); //UCOSII 延时
  #endif
  }
  //systick 中断服务函数,使用 ucos 时用到
  void SysTick_Handler(void)
  {
  if(delay_osrunning==1) //OS 开始跑了,才执行正常的调度处理
  {
  OSIntEnter(); //进入中断
  OSTimeTick(); //调用 ucos 的时钟服务程序
  OSIntExit(); //触发任务切换软中断
  }
  }
  delay_init 函数
  delay_init() 函数用来初始化 2 个重要参数:fac_us 以及 fac_ms;同时把 SysTick 的时钟源选择为外部时钟,如果需要支持操作系统(OS),只需要在 sys.h 里面,设置 SYSTEM_SUPPORT_OS 宏的值为 1 即可,然后,该函数会根据 delay_ostickspersec 宏的设置,来配置 SysTick 的中断时间,并开启 SysTick 中断。
  //初始化延迟函数
  //当使用 OS 的时候,此函数会初始化 OS 的时钟节拍
  //SYSTICK 的时钟固定为 HCLK 时钟的 1/8
  void delay_init()
  {
  #if SYSTEM_SUPPORT_OS //如果需要支持 OS.
  u32 reload;
  #endif
  SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 , HCLK/8 为系统时钟的 1/8
  fac_us=SystemCoreClock/8000000; //1us所需要的时钟周期 ,单位可理解为 “个/us”
  //SysTick时钟是sysclk 8分频,即SysTick时钟频率=sysclk/8, systick 计1次数所需时间为8/sysclk(s)=8*10^6/sysclk(us),因此,SysTick 1微秒计数个数为fac_us=sysclk/8*10^6.
  //sysick时钟频率=sysclk/8 =》 1s计数 sysclk/8 个
  #if SYSTEM_SUPPORT_OS //如果需要支持 OS.
  reload=SystemCoreClock/8000000; //每秒钟的计数次数 单位为 M
  reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间,溢出时间=(个/us) * (每次任务调度所需时间)
  //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
  #else
  fac_ms=(u16)fac_us*1000; //非 OS 下,代表每个 ms 需要的 systick 时钟数
  #endif
  }
  SysTick结构体:
  SysTick 是 MDK 定义了的一个结构体(在 core_m4.h 里面),里面包含 CTRL、LOAD、VAL、CALIB 等 4 个寄存器
  
  
  
  delay_us()函数
  不使用 OS 的时候,delay_us()函数如下
  // /延时 us ,nus 为要延时的 us 数。
  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; //清空计数器
  } //注意 nus 的值,不能太大,必须保证 nus《=(2^24)/fac_us,否则将导致延时时间不准确
  使用 OS 的时候,delay_us 的实现函数如下:
  延时 us ; nus 为要延时的 us 数。
  void delay_us(u32 nus)
  {
  u32 ticks;
  u32 told,tnow,tcnt=0;
  u32 reload=SysTick-》LOAD; //LOAD 的值
  ticks=nus*fac_us; //需要的节拍数
  delay_osschedlock(); //阻止 OS 调度,防止打断 us 延时
  told=SysTick-》VAL; //刚进入时的计数器值
  while(1)
  {
  tnow=SysTick-》VAL;
  if(tnow!=told)
  {
  if(tnow《told)tcnt+=told-tnow; //注意 SYSTICK 是一个递减的计数器。
  else tcnt+=reload-tnow+told;
  told=tnow;
  if(tcnt》=ticks)break; //时间超过/等于要延迟的时间,则退出。
  }
  };
  delay_osschedunlock(); //恢复 OS 调度
  }
举报

黎歆俭

2021-11-24 09:15:29
  CM4 内核的处理和 CM3 一样,内部都包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计到 0 时,将从 RELOAD 寄存器中自动重装载定时初值。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。
  delay 文件夹内包含了 delay.c 和 delay.h 两个文件,这两个文件用来实现系统的延时功能,其中包含 7 个函数:
  -下面4 个函数,仅在支持操作系统(OS)的时候,需要用到
  void delay_osschedlock(void);
  void delay_osschedunlock(void);
  void delay_ostimedly(u32 ticks);
  void SysTick_Handler(void);
  -下面 3 个函数,则不论是否支持 OS 都需要用到。
  void delay_init(u8 SYSCLK);
  void delay_ms(u16 nms);
  void delay_us(u32 nus);
  以 UCOSII 为例,介绍如何实现操作系统和我们的 delay 函数共用 SysTick 定时器。
  首先,我们简单介绍下 UCOSII 的时钟:ucos 运行需要一个系统时钟节拍(“心跳”),而这个节拍是固定的(由 OS_TICKS_PER_SEC 宏定义设置),比如要求 5ms 一次(即可设置:OS_TICKS_PER_SEC=200),在 STM32 上面,一般是由 SysTick 来为 ucos 提供时钟节拍,也就是 systick要设置为 5ms 中断一次,而且这个时钟一般是不能被打断的(否则就不准了)。
  在 ucos 下 systick 不能再被随意更改,如果我们还想利用 systick 来做 delay_us 或者delay_ms 的延时,就必须利用时钟摘取法。以 delay_us 为例,比如delay_us(50),在刚进入 delay_us 的时候先计算好这段延时需要等待的 systick 计数次数,这里为 50 * 21次(假设系统时钟为 168Mhz,因为 systick 的频率为系统时钟频率的 1/8,那么 systick每增加 1,就是 1/21us),然后我们就一直统计 systick 的计数变化,直到这个值变化了 50*21,一旦检测到变化达到或者超过这个值,就说明延时 50us 时间到了。这样,我们只是抓取 SysTick计数器的变化,并不需要修改 SysTick 的任何状态,完全不影响 systick 作为 UCOS 时钟节拍的功能,这就是实现 delay 和操作系统共用 SysTick 定时器的原理。
  操作系统支持的3宏定义及4相关函数
  当需要 delay_ms 和 delay_us 支持操作系统(OS)的时候,我们需要用到 3 个宏定义和 4个函数
  //本例程仅作 UCOSII 和 UCOSIII 的支持,其他 OS,请自行参考着移植
  //支持 UCOSII //
  #ifdef OS_CRITICAL_METHOD
  //OS_CRITICAL_METHOD 定义了,说明要支持 UCOSII
  #define delay_osrunning OSRunning //OS 是否运行标记,0,不运行;1,在运行
  #define delay_ostickspersec OS_TICKS_PER_SEC //OS 时钟节拍,即每秒调度次数
  #define delay_osintnesting OSIntNesting //中断嵌套级别,即中断嵌套次数
  #endif
  //支持 UCOSIII//
  #ifdef CPU_CFG_CRITICAL_METHOD
  //CPU_CFG_CRITICAL_METHOD 定义了,说明要支持 UCOSIII
  #define delay_osrunning OSRunning //OS 是否运行标记,0,不运行;1,在运行
  #define delay_ostickspersec OSCfg_TickRate_Hz //OS 时钟节拍,即每秒调度次数
  #define delay_osintnesting OSIntNestingCtr //中断嵌套级别,即中断嵌套次数
  #endif
  //us 级延时时,关闭任务调度(防止打断us级延迟)
  void delay_osschedlock(void)
  {
  #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII
  OS_ERR err;
  OSSchedLock(&err); //UCOSIII 的方式,禁止调度,防止打断 us 延时
  #else //否则 UCOSII
  OSSchedLock(); //UCOSII 的方式,禁止调度,防止打断 us 延时
  #endif
  }
  //us 级延时时,恢复任务调度
  void delay_osschedunlock(void)
  {
  #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII
  OS_ERR err;
  OSSchedUnlock(&err); //UCOSIII 的方式,恢复调度
  #else //否则 UCOSII
  OSSchedUnlock(); //UCOSII 的方式,恢复调度
  #endif
  }
  //调用 OS 自带的延时函数延时//
  void delay_ostimedly(u32 ticks) //ticks:延时的节拍数
  {
  #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII 时
  OS_ERR err;
  OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);//UCOSIII 延时采用周期模式
  #else
  OSTimeDly(ticks); //UCOSII 延时
  #endif
  }
  //systick 中断服务函数,使用 ucos 时用到
  void SysTick_Handler(void)
  {
  if(delay_osrunning==1) //OS 开始跑了,才执行正常的调度处理
  {
  OSIntEnter(); //进入中断
  OSTimeTick(); //调用 ucos 的时钟服务程序
  OSIntExit(); //触发任务切换软中断
  }
  }
  delay_init 函数
  delay_init() 函数用来初始化 2 个重要参数:fac_us 以及 fac_ms;同时把 SysTick 的时钟源选择为外部时钟,如果需要支持操作系统(OS),只需要在 sys.h 里面,设置 SYSTEM_SUPPORT_OS 宏的值为 1 即可,然后,该函数会根据 delay_ostickspersec 宏的设置,来配置 SysTick 的中断时间,并开启 SysTick 中断。
  //初始化延迟函数
  //当使用 OS 的时候,此函数会初始化 OS 的时钟节拍
  //SYSTICK 的时钟固定为 HCLK 时钟的 1/8
  void delay_init()
  {
  #if SYSTEM_SUPPORT_OS //如果需要支持 OS.
  u32 reload;
  #endif
  SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 , HCLK/8 为系统时钟的 1/8
  fac_us=SystemCoreClock/8000000; //1us所需要的时钟周期 ,单位可理解为 “个/us”
  //SysTick时钟是sysclk 8分频,即SysTick时钟频率=sysclk/8, systick 计1次数所需时间为8/sysclk(s)=8*10^6/sysclk(us),因此,SysTick 1微秒计数个数为fac_us=sysclk/8*10^6.
  //sysick时钟频率=sysclk/8 =》 1s计数 sysclk/8 个
  #if SYSTEM_SUPPORT_OS //如果需要支持 OS.
  reload=SystemCoreClock/8000000; //每秒钟的计数次数 单位为 M
  reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间,溢出时间=(个/us) * (每次任务调度所需时间)
  //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
  #else
  fac_ms=(u16)fac_us*1000; //非 OS 下,代表每个 ms 需要的 systick 时钟数
  #endif
  }
  SysTick结构体:
  SysTick 是 MDK 定义了的一个结构体(在 core_m4.h 里面),里面包含 CTRL、LOAD、VAL、CALIB 等 4 个寄存器
  
  
  
  delay_us()函数
  不使用 OS 的时候,delay_us()函数如下
  // /延时 us ,nus 为要延时的 us 数。
  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; //清空计数器
  } //注意 nus 的值,不能太大,必须保证 nus《=(2^24)/fac_us,否则将导致延时时间不准确
  使用 OS 的时候,delay_us 的实现函数如下:
  延时 us ; nus 为要延时的 us 数。
  void delay_us(u32 nus)
  {
  u32 ticks;
  u32 told,tnow,tcnt=0;
  u32 reload=SysTick-》LOAD; //LOAD 的值
  ticks=nus*fac_us; //需要的节拍数
  delay_osschedlock(); //阻止 OS 调度,防止打断 us 延时
  told=SysTick-》VAL; //刚进入时的计数器值
  while(1)
  {
  tnow=SysTick-》VAL;
  if(tnow!=told)
  {
  if(tnow《told)tcnt+=told-tnow; //注意 SYSTICK 是一个递减的计数器。
  else tcnt+=reload-tnow+told;
  told=tnow;
  if(tcnt》=ticks)break; //时间超过/等于要延迟的时间,则退出。
  }
  };
  delay_osschedunlock(); //恢复 OS 调度
  }
举报

更多回帖

发帖
×
20
完善资料,
赚取积分