[其他ST产品] systick定时器工作原理_数字压力表参数设置

[复制链接]
7821|38
 楼主| 自动化陈稳 发表于 2023-2-28 01:21 | 显示全部楼层 |阅读模式
TI, ck, ic, sy
1)实验:【正点原子】 NANO STM32F103 开发板

2)摘自《正点原子STM32 F1 开发指南(NANO 板-HAL 库版)》关注官方号,获取更多资料:正点原子
五章 SYSTEM 文件夹介绍
三章,我们介绍了如何在 MDK5 下建立 STM32F1 工程,在这个新建的工程之中,我们用到了一个 SYSTEM 文件夹里面的代码,此文件夹里面的代码由 ALIENTEK 提供,是STM32F10x 系列的底层核心驱动函数,可以用在 STM32F10x 系列的各个型号上面,方便大家快速构建自己的工程。
SYSTEM 文件夹下包含了 delay、sys、usart 等三个文件夹。分别包含了 delay.c、sys.c、usart.c及其头文件。通过这 3 个 c 文件,可以快速的给任何一款 STM32F1 构建基本的框架。使用起来是很方便的。本章,我们将向大家介绍这些代码,通过这章的学习,大家将了解到这些代码的由来,也希望大家可以灵活使用 SYSTEM 文件夹提供的函数,来快速构建工程,并实际应用到自己的项目中去。

 楼主| 自动化陈稳 发表于 2023-2-28 01:22 | 显示全部楼层
本章包括如下 3 个小结:

5.1,delay 文件夹代码介绍;

5.2,sys 文件夹代码介绍;

5.3,usart 文件夹代码介绍;

5.1 delay 文件夹代码介绍

delay 文件夹内包含了 delay.c 和 delay.h 两个文件,这两个文件用来实现系统的延时功能,其中包含 7 个函数:

void delay_osschedlock(void);

void delay_osschedunlock(void);

void delay_ostimedly(u32 ticks);

void SysTick_Handler(void);

void delay_init(void);

void delay_ms(u16 nms);

void delay_us(u32 nus);
 楼主| 自动化陈稳 发表于 2023-2-28 01:22 | 显示全部楼层
前面 4 个函数,仅在支持操作系统(OS)的时候,需要用到,而后面三个函数,则不论是否支持 OS 都需要用到。在介绍这些函数之前,我们先了解一下 delay 延时的编程思想:CM3 内核的处理器,内部包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计数到 0 时,将从RELOAD 寄存器中自动重装载定时初值,开始新一轮计数。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。SysTick 在《STM32 中文参考手册》(这里是指 V10.0版本,下同)里面介绍的很简单,其详细介绍,请参阅《Cortex-M3 权威指南》 133 页。我们就是利用 STM32 的内部 SysTick 来实现延时的,这样既不占用中断,也不占用系统定时器。这里我们将介绍的是 ALIENTEK 提供的版本的延时函数,该版本的延时函数支持在任意操作系统(OS)下面使用,它可以和操作系统共用 SysTick 定时器。
 楼主| 自动化陈稳 发表于 2023-2-28 01:22 | 显示全部楼层
这里,我们以 UCOSII 为例,介绍如何实现操作系统和我们的 delay 函数共用 SysTick 定时

器。首先,我们简单介绍下 UCOSII 的时钟:ucos 运行需要一个系统时钟节拍(类似 “心跳”),

而这个节拍是固定的(由 OS_TICKS_PER_SEC 宏定义设置),比如要求 5ms 一次(即可设置:

OS_TICKS_PER_SEC=200),在 STM32 上面,一般是由 SysTick 来提供这个节拍,也就是 SysTick

要设置为 5ms 中断一次,为 ucos 提供时钟节拍,而且这个时钟一般是不能被打断的(否则就不

准了)。
 楼主| 自动化陈稳 发表于 2023-2-28 01:22 | 显示全部楼层
因为在 ucos 下 SysTick 不能再被随意更改,如果我们还想利用 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 计数器的变化,并不需要修改 SysTick 的任

何状态,完全不影响 SysTick 作为 UCOS 时钟节拍的功能,这就是实现 delay 和操作系统共用

SysTick 定时器的原理。

下面我们开始介绍这几个函数。
 楼主| 自动化陈稳 发表于 2023-2-28 01:22 | 显示全部楼层
  1. 5.1.1 操作系统支持宏定义及相关函数

  2. 当需要 delay_ms 和 delay_us 支持操作系统(OS)的时候,我们需要用到 3 个宏定义和 4

  3. 个函数,宏定义及函数代码如下:

  4. //本例程仅作 UCOSII 和 UCOSIII 的支持,其他 OS,请自行参考着移植

  5. //支持 UCOSII

  6. #ifdef OS_CRITICAL_METHOD

  7. //OS_CRITICAL_METHOD 定义了,说明要支持 UCOSII

  8. #define delay_osrunning

  9. OSRunning

  10. //OS 是否运行标记,0,不运行;1,在运行

  11. #define delay_ostickspersec OS_TICKS_PER_SEC //OS 时钟节拍,即每秒调度次数

  12. #define delay_osintnesting OSIntNesting

  13. //中断嵌套级别,即中断嵌套次数

  14. #endif

  15. //支持 UCOSIII

  16. #ifdef CPU_CFG_CRITICAL_METHOD

  17. //CPU_CFG_CRITICAL_METHOD 定义了,说明要支持 UCOSIII

  18. #define delay_osrunning

  19. OSRunning

  20. //OS 是否运行标记,0,不运行;1,在运行

  21. #define delay_ostickspersec OSCfg_TickRate_Hz

  22. //OS 时钟节拍,即每秒调度次数

  23. #define delay_osintnesting OSIntNestingCtr

  24. //中断嵌套级别,即中断嵌套次数

  25. #endif

  26. //us 级延时时,关闭任务调度(防止打断 us 级延迟)

  27. void delay_osschedlock(void)

  28. {

  29. #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII

  30. OS_ERR err;

  31. OSSchedLock(&err);

  32. //UCOSIII 的方式,禁止调度,防止打断 us 延时

  33. #else

  34. //否则 UCOSII

  35. OSSchedLock();

  36. //UCOSII 的方式,禁止调度,防止打断 us 延时

  37. #endif

  38. }

  39. //us 级延时时,恢复任务调度

  40. void delay_osschedunlock(void)

  41. {

  42. #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII

  43. OS_ERR err;

  44. OSSchedUnlock(&err);

  45. //UCOSIII 的方式,恢复调度

  46. #else

  47. //否则 UCOSII

  48. OSSchedUnlock();

  49. //UCOSII 的方式,恢复调度

  50. #endif

  51. }

  52. //调用 OS 自带的延时函数延时

  53. //ticks:延时的节拍数

  54. void delay_ostimedly(u32 ticks)

  55. {

  56. #ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII 时

  57. OS_ERR err;

  58. OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);//UCOSIII 延时采用周期模式

  59. #else

  60. OSTimeDly(ticks);

  61. //UCOSII 延时

  62. #endif

  63. }

  64. //systick 中断服务函数,使用 ucos 时用到

  65. void SysTick_Handler(void)

  66. {

  67. if(delay_osrunning==1)

  68. //OS 开始跑了,才执行正常的调度处理

  69. {

  70. OSIntEnter();

  71. //进入中断

  72. OSTimeTick();

  73. //调用 ucos 的时钟服务程序

  74. OSIntExit();

  75. //触发任务切换软中断

  76. }

  77. }

  78. 以上代码,仅支持 UCOSII 和 UCOSIII,不过,对于其他 OS 的支持,也只需要对以上代

  79. 码进行简单修改即可实现。
 楼主| 自动化陈稳 发表于 2023-2-28 01:22 | 显示全部楼层
支持 OS 需要用到的三个宏定义(以 UCOSII 为例)即:

#define delay_osrunning

OSRunning

//OS 是否运行标记,0,不运行;1,在运行

#define delay_ostickspersec OS_TICKS_PER_SEC //OS 时钟节拍,即每秒调度次数

#define delay_osintnesting OSIntNesting

//中断嵌套级别,即中断嵌套次数

宏定义:delay_osrunning,用于标记 OS 是否正在运行,当 OS 已经开始运行时,该宏定义

值为 1,当 OS 还未运行时,该宏定义值为 0。

宏定义:delay_ ostickspersec,用于表示 OS 的时钟节拍,即 OS 每秒钟任务调度次数。

宏定义:delay_ osintnesting,用于表示 OS 中断嵌套级别,即中断嵌套次数,每进入一个

中断,该值加 1,每退出一个中断,该值减 1。
 楼主| 自动化陈稳 发表于 2023-2-28 01:23 | 显示全部楼层
支持 OS 需要用到的 4 个函数,即:

函数:delay_osschedlock,用于 delay_us 延时,作用是禁止 OS 进行调度,以防打断 us 级

延时,导致延时时间不准。

函数:delay_osschedunlock,同样用于 delay_us 延时,作用是在延时结束后恢复 OS 的调度,继续正常的 OS 任务调度。

函数:delay_ostimedly,则是调用 OS 自带的延时函数,实现延时。该函数的参数为时钟节

拍数。

函数:SysTick_Handler,则是 systick 的中断服务函数,该函数为 OS 提供时钟节拍,同时

可以引起任务调度。

以上就是 delay_ms 和 delay_us 支持操作系统时,需要实现的 3 个宏定义和 4 个函数。
 楼主| 自动化陈稳 发表于 2023-2-28 01:23 | 显示全部楼层
5.1.2delay_init 函数

该函数用来初始化 2 个重要参数:fac_us 以及 fac_ms;同时把 SysTick 的时钟源选择为外

部时钟,如果需要支持操作系统(OS),只需要在 sys.h 里面,设置 SYSTEM_SUPPORT_OS

宏的值为 1 即可,然后,该函数会根据 delay_ostickspersec 宏的设置,来配置 SysTick 的中断时

间,并开启 SysTick 中断。具体代码如下:

//初始化延迟函数

//当使用 ucos 的时候,此函数会初始化 ucos 的时钟节拍

//SYSTICK 的时钟固定为 AHB 时钟

//SYSCLK:系统时钟频率 void delay_init(u8 SYSCLK)

{

#if SYSTEM_SUPPORT_OS

//如果需要支持 OS.

u32 reload;

#endif

HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

//SysTick 频率为 HCLK

fac_us=SYSCLK;

//不论是否使用 OS,fac_us 都需要使用

#if SYSTEM_SUPPORT_OS

//如果需要支持 OS.

reload=SYSCLK;

//每秒钟的计数次数 单位为 K

reload*=1000000/delay_ostickspersec; //根据 delay_ostickspersec 设定溢出时间

//reload 为 24 位寄存器,大值:16777216,在 72M 下,约合 0.233s 左右

fac_ms=1000/delay_ostickspersec; //代表 OS 可以延时的少单位

SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启 SYSTICK 中断

SysTick->LOAD=reload;

//每 1/OS_TICKS_PER_SEC 秒中断一次

SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启 SYSTICK

#else

#endif

}

可以看到,delay_init 函数使用了条件编译,来选择不同的初始化过程,如果不使用 OS 的

时候,只是设置一下 SysTick 的时钟源以及确定 fac_us 和 fac_ms 的值。而如果使用 OS 的时候,

则会进行一些不同的配置,这里的条件编译是根据 SYSTEM_SUPPORT_OS 这个宏来确定的,

该宏在 sys.h 里面定义。
 楼主| 自动化陈稳 发表于 2023-2-28 01:23 | 显示全部楼层
SysTick 是 MDK 定义了的一个结构体(在 core_m3.h 里面),里面包含 CTRL、LOAD、

VAL、CALIB 等 4 个寄存器,

SysTick->CTRL 的各位定义如图 5.1.2.1 所示:

SysTick-> LOAD 的定义如图 5.1.2.2 所示:

SysTick-> VAL 的定义如图 5.1.2.3 所示:

SysTick-> CALIB 不常用,在这里我们也用不到,故不介绍了。

HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);这句把 SysTick 的时

钟选择外部时钟,这里需要注意的是:SysTick 的时钟源自 HCLK,假设我们外部晶振为 8M,

然后倍频到 72M,那么 SysTick 的时钟即为 72Mhz,也就是 SysTick 的计数器 VAL 每减 1,就

代表时间过了 1/72us。所以 fac_us=SYSCLK;这句话就是计算在 SystemCoreClock 时钟频率下延

时 1us 需要多少个 SysTick 时钟周期。

在不使用 OS 的时候:fac_us,为 us 延时的基数,也就是延时 1us,Systick 定时器需要走

过的时钟周期数。当使用 OS 的时候,fac_us,还是 us 延时的基数,不过这个值不会被写到

SysTick->LOAD 寄存器来实现延时,而是通过时钟摘取的办法实现的(前面已经介绍了)。而

fac_ms 则代表 UCOS 自带的延时函数所能实现的小延时时间(如 delay_ostickspersec=200,

那么 fac_ms 就是 5ms)。
 楼主| 自动化陈稳 发表于 2023-2-28 01:24 | 显示全部楼层
5.1.3 delay_us 函数

该函数用来延时指定的 us,其参数 nus 为要延时的微秒数。该函数有使用 OS 和不使用 OS

两个版本,这里我们分别介绍,首先是不使用 OS 的时候,实现函数如下:

  1. //延时 nus

  2. //nus 为要延时的 us 数.

  3. //nus:0~190887435(大值即 2^32/fac_us@fac_us=22.5)

  4. void delay_us(u32 nus)

  5. {

  6. u32 ticks;

  7. u32 told,tnow,tcnt=0;

  8. u32 reload=SysTick->LOAD; //LOAD 的值

  9. ticks=nus*fac_us;

  10. //需要的节拍数

  11. told=SysTick->VAL; //刚进入时的计数器值

  12. while(1)

  13. {

  14. tnow=SysTick->VAL;

  15. if(tnow!=told)

  16. {

  17. if(tnow<told)tcnt+=told-tnow;

  18. //这里注意一下 SYSTICK 是一个递减的计数器就可以了.

  19. else tcnt+=reload-tnow+told;

  20. told=tnow;

  21. if(tcnt>=ticks)break;

  22. //时间超过/等于要延迟的时间,则退出.

  23. }

  24. };

  25. }


这里就正是利用了我们前面提到的时钟摘取法,tick 是延时 nus 需要等待的 SysTick 计数次

数(也就是延时时间),told 用于记录后一次的 SysTick->VAL 值,然后 tnow 则是当前的

SysTick->VAL 值,通过他们的对比累加,实现 SysTick 计数次数的统计,统计值存放在 tcnt 里

面,然后通过对比 tcnt 和 ticks,来判断延时是否到达,从而到达不修改 SysTick 实现 nus 的延

时。对于使用 OS 的时候,delay_us 的实现函数和不使用 OS 的时候方法类似,都是使用的时钟

摘取法,只不过使用 delay_osschedlock 和 delay_osschedunlock 两个函数,用于调度上锁和解锁,

这是为了防止 OS 在 delay_us 的时候打断延时,可能导致的延时不准,所以我们利用这两个函

数来实现免打断,从而保证延时精度。
 楼主| 自动化陈稳 发表于 2023-2-28 01:24 | 显示全部楼层
5.1.4 delay_ms 函数

该函数用来延时指定的 ms,其参数 nms 为要延时的毫秒数。该函数同样有使用 OS 和不使

用 OS 两个版本,这里我们分别介绍,首先是不使用 OS 的时候,实现函数如下:
  1. //延时 nms

  2. //nms:要延时的 ms 数

  3. void delay_ms(u16 nms)

  4. {

  5. u32 i;

  6. for(i=0;i<nms;i++) delay_us(1000);

  7. }

  8. 该函数其实就是多次调用前面所讲的 delay_us 函数,来实现毫秒级延时的。

  9. 再来看看使用 OS 的时候,delay_ms 的实现函数如下:

  10. //延时 nms

  11. //nms:要延时的 ms 数

  12. //nms:0~65535

  13. void delay_ms(u16 nms)

  14. {

  15. if(delay_osrunning&&delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面

  16. (中断里面不能任务调度)

  17. {

  18. if(nms>=fac_ms)

  19. //延时的时间大于 OS 的少时间周期

  20. {

  21. delay_ostimedly(nms/fac_ms);

  22. //OS 延时

  23. }

  24. nms%=fac_ms; //OS 已经无法提供这么小的延时了,采用普通方式延时

  25. }

  26. delay_us((u32)(nms*1000)); //普通方式延时

  27. }


该函数中,delay_osrunnig 是 OS 正在运行的标志,delay_osintnesting 则是 OS 中断嵌套次

数,必须是 delay_osrunning 为真,且 delay_osintnesting 为 0 的时候,才可以调用 OS 自带的延

时函数进行延时(可以进行任务调度),delay_ostimedly 函数就是利用 OS 自带的延时函数,

实现任务级延时的,其参数代表延时的时钟节拍数(假设 delay_ostickspersec=200,纳闷我们

delay_osrimedly(1),就代表延时 5ms)。

当 OS 还没运行的时候,我们的 delay_ms 函数将先判断延时时长是否大于等于 1 个 OS 时

钟节拍(fac_ms),当大于这个值的时候,我们就调用 OS 的延时函数来实现(此时任务可以

调度),不足 1 个时钟节拍的时候,直接调用 delay_us 函数实现(此时任务无法调度)。
 楼主| 自动化陈稳 发表于 2023-2-28 01:25 | 显示全部楼层
5.1.5 HAL 库延时函数 HAL_Delay 解析

前面我们讲解了 ALIENTEK 提供的使用 Systick 实现延时相关函数。实际上,HAL 库有提

供延时函数,只不过它只能实现简单的毫秒级延时,没有实现 us 级别延时。下面我们列出 HAL

库实现延时相关的函数。首先是功能配置函数:

调用 HAL_SYSTICK_Config 函数配置每隔 1ms 中断一次:文件 stm32f1xx_hal.c 中定义:

  1. __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)

  2. {

  3. HAL_SYSTICK_Config(SystemCoreClock/1000U);

  4. HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);

  5. return HAL_OK;

  6. }

  7. HAL 库的 SYSTICK 配置函数,在文件 stm32f1xx_hal_context.c 中定义

  8. uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)

  9. {

  10. return SysTick_Config(TicksNumb);

  11. }
 楼主| 自动化陈稳 发表于 2023-2-28 01:25 | 显示全部楼层
内核的 Systick 配置函数,配置每隔 ticks 个 systick 周期中断一次,在文件 core_cm4.h 中定

义:
  1. __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)

  2. {

  3. …//此处省略函数定义

  4. }
 楼主| 自动化陈稳 发表于 2023-2-28 01:25 | 显示全部楼层
上面三个函数,实际上开放给 HAL 调用的主要是 HAL_InitTick 函数,该函数在 HAL 库初始化函数 HAL_Init 中会被调用。该函数通过间接调用 SysTick_Config 函数配置 Systick 定时器

每隔 1ms 中断一次,永不停歇。
 楼主| 自动化陈稳 发表于 2023-2-28 01:28 | 显示全部楼层
接下来我们来看看延时的逻辑控制代码:

  1. Systick 中断服务函数:文件 stm32f1xx_it.c 中

  2. void SysTick_Handler(void)

  3. {

  4. HAL_IncTick();

  5. }
 楼主| 自动化陈稳 发表于 2023-2-28 01:28 | 显示全部楼层
下面代码均在文件 stm32f1xx_hal.c 中:
  1. __IO uint32_t uwTick;//定义计数全局变量

  2. __weak void HAL_IncTick(void)//全局变量 uwTick 递增

  3. {

  4. uwTick++;

  5. }

  6. __weak uint32_t HAL_GetTick(void)//获取全局变量 uwTick 的值

  7. {

  8. return uwTick;

  9. }
 楼主| 自动化陈稳 发表于 2023-2-28 01:28 | 显示全部楼层
开放的 HAL 延时函数,延时 Delay 毫秒:
__weak void HAL_Delay(__IO uint32_t Delay)

{

uint32_t tickstart = HAL_GetTick();

uint32_t wait = Delay;

if (wait < HAL_MAX_DELAY)

{

wait++;

}

while((HAL_GetTick() – tickstart) < wait)

{

}

}

HAL 库实现延时功能非常简单,首先定义了一个 32 位全局变量 uwTick,在 Systick 中断

服务函数 SysTick_Handler 中通过调用 HAL_IncTick 实现 uwTick 值不断增加,也就是每隔 1ms

增加 1。而 HAL_Delay 函数在进入函数之后先记录当前 uwTick 的值,确定定时时间的 wait 值,

然后不断在循环中读取 uwTick 当前值,进行减运算,得出的就是延时的毫秒数,整个逻辑非

常简单也非常清晰。
 楼主| 自动化陈稳 发表于 2023-2-28 01:28 | 显示全部楼层
但是,HAL 库的延时函数有一个局限性,在中断服务函数中使用 HAL_Delay 会引起混乱,

因为它是通过中断方式实现,而 Systick 的中断优先级是的,所以在中断中运行 HAL_delay

会导致延时出现严重误差。所以一般情况下,推荐大家使用 ALIENTEK 提供的延时函数库。
 楼主| 自动化陈稳 发表于 2023-2-28 01:29 | 显示全部楼层
5.2 sys 文件夹代码介绍

sys 文件夹内包含了 sys.c 和 sys.h 两个文件。在 sys.h 里面定义了 STM32 的 IO 口位操作输

入读 取宏定义和输 出宏定义 以及类型别名 。 sys.c 里 面除了定义时 钟系统 配置函数

Stm32_Clock_Init 外主要是一些汇编函数,对于函数 Stm32_Clock_Init 的讲解请参考本手册 4.3

小节 STM32F1 时钟系统章节内容。本小节我们主要想大家介绍 sys.h 头文件里面的 IO 口位操

作。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

106

主题

1380

帖子

1

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