完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980
第22章 STM32H7的SysTick实现多组软件定时器
本章节为大家讲解嘀嗒定时器SysTick,嘀嗒定时器比较容易掌握,其实大家只要知道它是一个24位的递减计数器,支持中断就可以了。
22.1 初学者重要提示
22.2 Systick基础知识
22.3 多组软件定时器驱动设计
22.4 多组软件定时器板级支持包(bsp_timer.c)
22.6 多组软件定时器驱动移植和使用
22.7 实验例程设计框架
22.8 实验例程说明(MDK)
22.9 实验例程说明(IAR)
22.10 总结
22.1 初学者重要提示
- 比通用定时器要容易掌握很多,因为嘀嗒定时器的功能比较的单一,根据ARM的说法,此定时器就是专门为RTOS的系统时钟节拍而设计。
- 本章节为大家讲解的多组软件定时器实现方案非常实用,建议初学者熟练掌握。
22.2 Systick基础知识
关于滴答定时器,初学者仅需了解到以下几点知识就够了。
- Systick是Cortex-M7内核自带的组件,其它几个常用的硬件异常HardFault,SVC和PendSV也都是是内核自带的,其中Systick,SVC和PendSV的中断优先级是可编程的,跟SPI中断、ADC中断、UART中断等一样,都在同一个NVIC下配置的。而HardFault是不可编程的,且优先级要比可编程的都要高。
- Systick是一个24位的递减计数器,用户仅需掌握ARM的CMSIS软件提供的一个函数SysTick_Config即可,原代码如下:
1. /** 2. \brief System Tick Configuration 3. \details Initializes the System Timer and its interrupt, and starts the System Tick Timer. 4. Counter is in free running mode to generate periodic interrupts. 5. \param [in] ticks Number of ticks between two interrupts. 6. \return 0 Function succeeded. 7. \return 1 Function failed. 8. \note When the variable __Vendor_SysTickConfig is set to 1, then the 9. function SysTick_Config is not included. In this case, the file device.h 10. must contain a vendor-specific implementation of this function. 11. */ 12. __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) 13. { 14. if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) 15. { 16. return (1UL); /* Reload value impossible */ 17. } 18. 19. SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */ 20. NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL)/*set Priority for Systick Interrupt */ 21. SysTick->VAL = 0UL; /* Load the SysTick Counter Value */ 22. SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | 23. SysTick_CTRL_TICKINT_Msk | 24. SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ 25. return (0UL); /* Function successful */ 26. }
- 第12行,函数的形参用于配置滴答定时器LOAD寄存器的数值,由于滴答定时器是一个递减计数器,启动后是将LOAD寄存器的数值赋给VAL寄存器,然后VAL寄存器做递减操作,等递减到0的时候重新加载LOAD寄存器的数值继续做递减操作。
函数的形参表示内核时钟多少个周期后触发一次Systick定时中断,比如形参配置为如下数值。
-- SystemCoreClock / 1000 表示定时频率为 1000Hz, 也就是定时周期为 1ms。
-- SystemCoreClock / 500 表示定时频率为 500Hz, 也就是定时周期为 2ms。
-- SystemCoreClock / 2000 表示定时频率为 2000Hz, 也就是定时周期为 500us。
注:SystemCoreClock是STM32H7的系统主频400MHz。
- 第20行,此函数设置滴答定时器为最低优先级。
- 第22行,配置滴答定时器的控制寄存器,使能滴答定时器中断。滴答定时器的中断服务程序实现比较简单,没有清除中断标志这样的操作,仅需填写用户要实现的功能即可。
控制及其状态寄存器的位定义:
重装寄存器定义,最大值2^24 – 1 = 16777215,配置的时候注意别超出范围了。
22.3 多组软定时器驱动设计
22.3.1 软件定时器框架
为了方便大家理解,先来看下软件定时器的实现框图:
1、 第1阶段,初始化:
通过函数bsp_InitTimer初始化滴答定时器和实现软件定时器所需的结构体。
2、 第2阶段,软件定时器初始化:
- 可以通过函数bsp_StartTimer做单次定时器初始化,单次定时器执行一次就结束。下次还想使用,需要重新创建。
- 可以通过函数bsp_StartAutoTimer做周期性定时器初始化,可以周期性的一直运行下去。
3、 第3阶段,滴答定时器中断里面更新每个软件定时器的计数:
在滴答定时器中断里面通过调用函数bsp_SoftTimerDec实现每个软件定时器的计数更新。
4、 第4阶段,检测时间到和停止运行
- 通过函数bsp_CheckTimer可以检测单次或者周期定时器的时间是否到。时间到后就可以执行用户任务。
- 如果不想某个单次或者周期性定时器执行,直接调用函数bsp_StopTimer停止即可。
22.3.2 程序分析之相关的变量定义
在bsp_timer.h 中定义了结构体类型SOFT_TMR。
#define TMR_COUNT 4 /* 软件定时器的个数 (定时器ID范围 0 - 3) */ typedef enum { TMR_ONCE_MODE = 0, /* 一次工作模式 */ TMR_AUTO_MODE = 1 /* 自动定时工作模式 */ }TMR_MODE_E; /* 定时器结构体,成员变量必须增加__IO 即 volatile,因为这个变量在中断和主程序中同时被访问, 有可能造成编译器错误优化。 */ typedef struct { volatile uint8_t Mode; /* 计数器模式,1次性 */ volatile uint8_t Flag; /* 定时到达标志 */ volatile uint32_t Count; /* 计数器 */ volatile uint32_t PreLoad; /* 计数器预装值 */ }SOFT_TMR;
在bsp_timer.c 中定义SOFT_TMR结构体数组变量。
/* 定于软件定时器结构体变量 */ static SOFT_TMR s_tTmr[TMR_COUNT];
每个软件定时器对象都分配一个结构体变量,这些结构体变量以数组的形式存在将便于我们简化程序代码行数。
22.3.3 程序分析之初始化
初始化函数如下:
1. /* 2. ***************************************************************************************************** 3. * 函 数 名: bsp_InitTimer 4. * 功能说明: 配置systick中断,并初始化软件定时器变量 5. * 形 参: 无 6. * 返 回 值: 无 7. ***************************************************************************************************** 8. */ 9. void bsp_InitTimer(void) 10. { 11. uint8_t i; 12. 13. /* 清零所有的软件定时器 */ 14. for (i = 0; i < TMR_COUNT; i++) 15. { 16. s_tTmr[i].Count = 0; 17. s_tTmr[i].PreLoad = 0; 18. s_tTmr[i].Flag = 0; 19. s_tTmr[i].Mode = TMR_ONCE_MODE; /* 缺省是1次性工作模式 */ 20. } 21. 22. /* 23. 配置systic中断周期为1ms,并启动systick中断。 24. 25. SystemCoreClock 是固件中定义的系统内核时钟,对于STM32H7,一般为 400MHz 26. 27. SysTick_Config() 函数的形参表示内核时钟多少个周期后触发一次Systick定时中断. 28. -- SystemCoreClock / 1000 表示定时频率为 1000Hz, 也就是定时周期为 1ms 29. -- SystemCoreClock / 500 表示定时频率为 500Hz, 也就是定时周期为 2ms 30. -- SystemCoreClock / 2000 表示定时频率为 2000Hz, 也就是定时周期为 500us 31. 32. 对于常规的应用,我们一般取定时周期1ms。对于低速CPU或者低功耗应用,可以设置定时周期为 10ms 33. */ 34. SysTick_Config(SystemCoreClock / 1000); 35. }
- 第14-20行是软件定时器结构体的初始化部分,设置初始值。实际创建软件定时器会重新做初始化。
- 第32行是本章22.2小节已经讲解。
22.3.4 程序分析之单次定时器创建
单次定时器创建函数bsp_StartTime。
1. /* 2. ****************************************************************************************************** 3. * 函 数 名: bsp_StartTimer 4. * 功能说明: 启动一个定时器,并设置定时周期。 5. * 形 参: _id : 定时器ID,值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID,以避免定时器ID冲突。 6. * _period : 定时周期,单位1ms 7. * 返 回 值: 无 8. ****************************************************************************************************** 9. */ 10. void bsp_StartTimer(uint8_t _id, uint32_t _period) 11. { 12. if (_id >= TMR_COUNT) 13. { 14. /* 打印出错的源代码文件名、函数名称 */ 15. BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__); 16. while(1); /* 参数异常,死机等待看门狗复位 */ 17. } 18. 19. DISABLE_INT(); /* 关中断 */ 20. 21. s_tTmr[_id].Count = _period; /* 实时计数器初值 */ 22. s_tTmr[_id].PreLoad = _period; /* 计数器自动重装值,仅自动模式起作用 */ 23. s_tTmr[_id].Flag = 0; /* 定时时间到标志 */ 24. s_tTmr[_id].Mode = TMR_ONCE_MODE; /* 1次性工作模式 */ 25. 26. ENABLE_INT(); /* 开中断 */ 27. }
- 第12-17行是为了防止用户设置的ID参数超过范围。
其中BSP_Printf是在bsp.h文件定义的,用于调试阶段排错。
#define BSP_Printf printf /* 使用这个宏定义的话,正常执行printf */
#define BSP_Printf(...) /* 如果使用这个宏定义的话,什么都不执行 */
- 第19-26行是临界段,结构体变量赋值前后做了开关中断操作。因为此结构体变量在滴答定时器中断里面也要调用,防止变量赋值出问题。
开关中断函数也是在bsp.h文件里面定义的。
#define ENABLE_INT() __set_PRIMASK(0) /* 使能全局中断 */
#define DISABLE_INT() __set_PRIMASK(1) /* 禁止全局中断 */
22.3.5 程序分析之周期性定时器创建
周期性定时器创建函数bsp_StartAutoTimer。
1. /* 2. ****************************************************************************************************** 3. * 函 数 名: bsp_StartAutoTimer 4. * 功能说明: 启动一个自动定时器,并设置定时周期。 5. * 形 参: _id : 定时器ID,值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID,以避免定时器ID冲突。 6. * _period : 定时周期,单位10ms 7. * 返 回 值: 无 8. ****************************************************************************************************** 9. */ 10. void bsp_StartAutoTimer(uint8_t _id, uint32_t _period) 11. { 12. if (_id >= TMR_COUNT) 13. { 14. /* 打印出错的源代码文件名、函数名称 */ 15. BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__); 16. while(1); /* 参数异常,死机等待看门狗复位 */ 17. } 18. 19. DISABLE_INT(); /* 关中断 */ 20. 21. s_tTmr[_id].Count = _period; /* 实时计数器初值 */ 22. s_tTmr[_id].PreLoad = _period; /* 计数器自动重装值,仅自动模式起作用 */ 23. s_tTmr[_id].Flag = 0; /* 定时时间到标志 */ 24. s_tTmr[_id].Mode = TMR_AUTO_MODE; /* 自动工作模式 */ 25. 26. ENABLE_INT(); /* 开中断 */ 27. }
- 这个函数跟前面22.3.4小节中讲的单次定时器是一样的,仅第24行的赋值不同,这个函数是周期性的,而22.3.4小节里面的是单次定时器。
22.3.6 程序分析之停止定时器运行
定时器停止运行函数是bsp_StopTimer。
1. /* 2. ****************************************************************************************************** 3. * 函 数 名: bsp_StopTimer 4. * 功能说明: 停止一个定时器 5. * 形 参: _id : 定时器ID,值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID,以避免定时器ID冲突。 6. * 返 回 值: 无 7. ****************************************************************************************************** 8. */ 9. void bsp_StopTimer(uint8_t _id) 10. { 11. if (_id >= TMR_COUNT) 12. { 13. /* 打印出错的源代码文件名、函数名称 */ 14. BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__); 15. while(1); /* 参数异常,死机等待看门狗复位 */ 16. } 17. 18. DISABLE_INT(); /* 关中断 */ 19. 20. s_tTmr[_id].Count = 0; /* 实时计数器初值 */ 21. s_tTmr[_id].Flag = 0; /* 定时时间到标志 */ 22. s_tTmr[_id].Mode = TMR_ONCE_MODE; /* 自动工作模式 */ 23. 24. ENABLE_INT(); /* 开中断 */ 25. }
- 这个函数跟前面22.3.4和22.3.5小节中的函数框架一样,仅是把结构体变量中的计数器和时间到标志都置位成0,从而让软件定时器停止运行。
22.3.7 程序分析之检测定时器时间到
检测定时器时间到的函数是bsp_CheckTimer。
1. /* 2. ****************************************************************************************************** 3. * 函 数 名: bsp_CheckTimer 4. * 功能说明: 检测定时器是否超时 5. * 形 参: _id : 定时器ID,值域【0,TMR_COUNT-1】。用户必须自行维护定时器ID,以避免定时器ID冲突。 6. * _period : 定时周期,单位1ms 7. * 返 回 值: 返回 0 表示定时未到, 1表示定时到 8. ****************************************************************************************************** 9. */ 10. uint8_t bsp_CheckTimer(uint8_t _id) 11. { 12. if (_id >= TMR_COUNT) 13. { 14. return 0; 15. } 16. 17. if (s_tTmr[_id].Flag == 1) 18. { 19. s_tTmr[_id].Flag = 0; 20. return 1; 21. } 22. else 23. { 24. return 0; 25. } 26. }
- 第12到15行是检查ID是否有效。
- 第17到25行是判断时间到标志值Flag是否置位,如果置位表示时间已经到,如果为0,表示时间还没有到。
22.3.8 程序分析之滴答定时器中断的处理
软件定时器的主要功能是通过滴答定时器中断实现的,函数的调用关系是滴答定时器中断函数SysTick_Handler调用SysTick_ISR,而SysTick_ISR调用bsp_SoftTimerDec。
1. /* 2. ****************************************************************************************************** 3. * 函 数 名: SysTick_Handler 4. * 功能说明: 系统嘀嗒定时器中断服务程序。启动文件中引用了该函数。 5. * 形 参: 无 6. * 返 回 值: 无 7. ****************************************************************************************************** 8. */ 9. void SysTick_Handler(void) 10. { 11. SysTick_ISR(); 12. } 13. 14. /* 15. ****************************************************************************************************** 16. * 函 数 名: SysTick_ISR 17. * 功能说明: SysTick中断服务程序,每隔1ms进入1次 18. * 形 参: 无 19. * 返 回 值: 无 20. ****************************************************************************************************** 21. */ 22. void SysTick_ISR(void) 23. { 24. static uint8_t s_count = 0; 25. uint8_t i; 26. 27. HAL_IncTick(); 28. 29. /* 每隔1ms进来1次 (仅用于 bsp_DelayMS) */ 30. if (s_uiDelayCount > 0) 31. { 32. if (--s_uiDelayCount == 0) 33. { 34. s_ucTimeOutFlag = 1; 35. } 36. } 37. 38. /* 每隔1ms,对软件定时器的计数器进行减一操作 */ 39. for (i = 0; i < TMR_COUNT; i++) 40. { 41. bsp_SoftTimerDec(&s_tTmr[i]); 42. } 43. 44. /* 全局运行时间每1ms增1 */ 45. g_iRunTime++; 46. if (g_iRunTime == 0x7FFFFFFF) /* 这个变量是 int32_t 类型,最大数为 0x7FFFFFFF */ 47. { 48. g_iRunTime = 0; 49. } 50. 51. bsp_RunPer1ms(); /* 每隔1ms调用一次此函数,此函数在 bsp.c */ 52. 53. if (++s_count >= 10) 54. { 55. s_count = 0; 56. 57. bsp_RunPer10ms(); /* 每隔10ms调用一次此函数,此函数在 bsp.c */ 58. } 59. } 60. 61. /* 62. ****************************************************************************************************** 63. * 函 数 名: bsp_SoftTimerDec 64. * 功能说明: 每隔1ms对所有定时器变量减1。必须被SysTick_ISR周期性调用。 65. * 形 参: _tmr : 定时器变量指针 66. * 返 回 值: 无 67. ****************************************************************************************************** 68. */ 69. static void bsp_SoftTimerDec(SOFT_TMR *_tmr) 70. { 71. if (_tmr->Count > 0) 72. { 73. /* 如果定时器变量减到1则设置定时器到达标志 */ 74. if (--_tmr->Count == 0) 75. { 76. _tmr->Flag = 1; 77. 78. /* 如果是自动模式,则自动重装计数器 */ 79. if(_tmr->Mode == TMR_AUTO_MODE) 80. { 81. _tmr->Count = _tmr->PreLoad; 82. } 83. } 84. } 85. }
- 第39到42行是实现的关键,滴答定时器中断会每毫秒执行一次,依次扫描所有需要创建的软件定时器。软件定时器个数由bsp_timer.h文件中定义的TMR_COUNT决定。
- 第69到85行是软件定时器的实际处理函数,首先判断计数器_tmr->Count的数值是否为0,如果不为0,则减1,直到计数器的数值为0。计数器的数值达到0后设置定时器到达标志_tmr->Flag。如果是周期性定时器,将计数器_tmr->Count设置为初始化时配置的预装值_tmr->PreLoad,这样就能以_tmr->PreLoad为周期进行计数,从而实现周期性定时器功能。
22.4 多组软件定时器板级支持包(bsp_timer.c)
滴答定时器驱动文件bsp_timer.c主要实现了如下几个API:
- bsp_InitTimer
- SysTick_ISR
- bsp_SoftTimerDec
- bsp_DelayMS
- bsp_DelayUS
- bsp_StartTimer
- bsp_StartAutoTimer
- bsp_StopTimer
- bsp_CheckTimer
- bsp_GetRunTime
- bsp_CheckRunTime
- SysTick_Handler
软件定时器涉及到的几个函数在本章的22.3小节都进行了详细讲解,本小节主要是把需要用户调用的五个函数做个应用说明。
22.4.1 函数bsp_InitTimer
函数原型:
void bsp_InitTimer(void)
函数描述:
此函数主要用于软件定时器的初始化,使用所有其它API之前,务必优先调用此函数。
注意事项:
此函数的解读在本章20.3.3小节。
使用举例:
软件定时器的初始化函数在bsp.c文件的bsp_Init函数里面调用。
22.4.2 函数bsp_StartTimer
函数原型:
void bsp_StartTimer(uint8_t _id, uint32_t _period)
函数描述:
此函数用于启动一个单次定时器,并设置定时时间。
函数参数:
- 第1个参数_id是定时器ID,值域【0,TMR_COUNT-1】,其中软件定时器个数TMR_COUNT在bsp_timer.h文件里面定义,用户必须自行维护定时器ID,以避免定时器ID冲突。
- 第2个参数_period用于定时周期设置,单位1ms。
注意事项:
此函数的解读在本章22.3.4小节。
使用举例:
调用此函数前,务必优先调用函数bsp_InitTimer进行初始化。
比如实现软件定时2单次定时200ms就是bsp_StartTimer(2, 200)。
22.4.3 函数bsp_StartAutoTimer
函数原型:
void bsp_StartAutoTimer(uint8_t _id, uint32_t _period)
函数描述:
此函数用于启动一个周期性定时器,并设置定时周期。
函数参数:
- 第1个参数_id是定时器ID,值域【0,TMR_COUNT-1】,其中软件定时器个数TMR_COUNT在bsp_timer.h文件里面定义,用户必须自行维护定时器ID,以避免定时器ID冲突。
- 第2个参数_period用于定时周期设置,单位1ms。
注意事项:
此函数的解读在本章22.3.5小节。
使用举例:
调用此函数前,务必优先调用函数bsp_InitTimer进行初始化。
比如使用软件定时器0创建启动1个100ms的自动重装的定时器,就是bsp_StartAutoTimer(0, 100)。
22.4.4 函数bsp_StopTimer
函数原型:
void bsp_StopTimer(uint8_t _id)
函数描述:
此函数用于停止运行中的周期性定时器。
函数参数:
- 第1个参数_id是定时器ID,值域【0,TMR_COUNT-1】,其中软件定时器个数TMR_COUNT在bsp_timer.h文件里面定义,用户必须自行维护定时器ID,以避免定时器ID冲突。
注意事项:
此函数的解读在本章22.3.6小节。
使用举例:
调用此函数前,务必优先调用函数bsp_InitTimer进行初始化。
比如停止软件定时0就是bsp_StopTimer(0)。
22.4.5 函数bsp_CheckTimer
函数原型:
uint8_t bsp_CheckTimer(uint8_t _id)
函数描述:
此函数用于检测软件定时器的定时时间是否到。
函数参数:
- 第1个参数_id是定时器ID,值域【0,TMR_COUNT-1】,其中软件定时器个数TMR_COUNT在bsp_timer.h文件里面定义,用户必须自行维护定时器ID,以避免定时器ID冲突。
- 返回值,返回 0 表示定时未到,1表示定时到。
注意事项:
此函数的解读在本章22.3.7小节。
使用举例:
调用此函数前,务必优先调用函数bsp_InitTimer进行初始化。比如检测软件定时0的时间是否到:
if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(1); }
22.5 多组软定时器驱动移植和使用
按键移植步骤如下:
- 第1步:复制bsp_timer.c和bsp_timer.h到自己的工程目录,并添加到工程里面。
- 第2步:根据需要的宏定义个数,修改下面的宏定义即可
#define TMR_COUNT 4 /* 软件定时器的个数 (定时器ID范围 0 - 3) */
- 第3步:这几个驱动文件主要用到HAL库的TIM驱动文件(源文件里面还封装了定时器),简单省事些可以添加所有HAL库.C源文件进来。
- 第4步,应用方法看本章节配套例子即可。
22.6 实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
1、 第1阶段,上电启动阶段:
这部分在第14章进行了详细说明。
2、 第2阶段,进入main函数:
- 第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,蜂鸣器等。
- 第2部分,应用程序设计部分,实现滴答定时器,LED和按键应用程序设计。
- 第3部分,按键检测每10ms在滴答定时中断执行一次。
22.7 实验例程说明(MDK)
配套例子:
V7-004_基于Systick滴答定时器的多组软件定时器实现
实验目的:
- 学习基于Systick滴答定时器的多组软件定时器实现。
实验内容:
- 启动自动重装软件定时器0,每100ms翻转一次LED1。
- 启动自动重装软件定时器1,每100ms翻转一次LED2。
实验操作:
- K1键按下,启动软件定时2,单次模式,定时0.5s时间到后,翻转LED3。
- K2键按下,启动软件定时3,单次模式,定时1s时间到后,翻转LED4。
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到400MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ bsp_InitLed(); /* 初始化LED */ }
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
/* ********************************************************************************************************* * 函 数 名: MPU_Config * 功能说明: 配置MPU * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 数 名: CPU_CACHE_Enable * 功能说明: 使能L1 Cache * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
主功能:
主功能的实现主要分为两部分:
- 启动自动重装软件定时器0,每100ms翻转一次LED1。
- 启动自动重装软件定时器1,每100ms翻转一次LED2。
- K1键按下,启动软件定时2,单次模式,定时0.5s时间到后,翻转LED3。
- K2键按下,启动软件定时3,单次模式,定时1s时间到后,翻转LED4。
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按键代码 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器,软件定时器0 */ bsp_StartAutoTimer(1, 100); /* 启动1个100ms的自动重装的定时器,软件定时器1 */ /* 进入主程序循环体 */ while (1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断软件定时器0是否超时 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(1); } /* 判断软件定时器1超时 */ if (bsp_CheckTimer(1)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(2); } /* 判断软件定时器2是否超时 */ if (bsp_CheckTimer(2)) { /* 单次模式,按下K1按键后,定时1秒进入 */ bsp_LedToggle(3); } /* 判断软件定时器3是否超时 */ if (bsp_CheckTimer(3)) { /* 单次模式,按下K2按键后,定时2秒进入 */ bsp_LedToggle(4); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下,启动软件定时2,单次模式,定时0.5时间 */ printf("K1键按下\r\n"); bsp_StartTimer(2, 500); break; case KEY_DOWN_K2: /* K2键按下,启动软件定时3,单次模式,定时1s时间 */ printf("K2键按下\r\n"); bsp_StartTimer(3, 1000); break; default: /* 其它的键值不处理 */ break; } } } }
22.8 实验例程说明(IAR)
配套例子:
V7-004_基于Systick滴答定时器的多组软件定时器实现
实验目的:
- 学习基于Systick滴答定时器的多组软件定时器实现。
实验内容:
- 启动自动重装软件定时器0,每100ms翻转一次LED1。
- 启动自动重装软件定时器1,每100ms翻转一次LED2。
实验操作:
- K1键按下,启动软件定时2,单次模式,定时0.5s时间到后,翻转LED3。
- K2键按下,启动软件定时3,单次模式,定时1s时间到后,翻转LED4。
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到400MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ bsp_InitLed(); /* 初始化LED */ }
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
/* ********************************************************************************************************* * 函 数 名: MPU_Config * 功能说明: 配置MPU * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 数 名: CPU_CACHE_Enable * 功能说明: 使能L1 Cache * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
主功能:
主功能的实现主要分为两部分:
- 启动自动重装软件定时器0,每100ms翻转一次LED1。
- 启动自动重装软件定时器1,每100ms翻转一次LED2。
- K1键按下,启动软件定时2,单次模式,定时0.5s时间到后,翻转LED3。
- K2键按下,启动软件定时3,单次模式,定时1s时间到后,翻转LED4。
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按键代码 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器,软件定时器0 */ bsp_StartAutoTimer(1, 100); /* 启动1个100ms的自动重装的定时器,软件定时器1 */ /* 进入主程序循环体 */ while (1) { bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */ /* 判断软件定时器0是否超时 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(1); } /* 判断软件定时器1超时 */ if (bsp_CheckTimer(1)) { /* 每隔100ms 进来一次 */ bsp_LedToggle(2); } /* 判断软件定时器2是否超时 */ if (bsp_CheckTimer(2)) { /* 单次模式,按下K1按键后,定时1秒进入 */ bsp_LedToggle(3); } /* 判断软件定时器3是否超时 */ if (bsp_CheckTimer(3)) { /* 单次模式,按下K2按键后,定时2秒进入 */ bsp_LedToggle(4); } /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */ ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下,启动软件定时2,单次模式,定时0.5时间 */ printf("K1键按下\r\n"); bsp_StartTimer(2, 500); break; case KEY_DOWN_K2: /* K2键按下,启动软件定时3,单次模式,定时1s时间 */ printf("K2键按下\r\n"); bsp_StartTimer(3, 1000); break; default: /* 其它的键值不处理 */ break; } } } }
22.9 总结
本章节为大家介绍的多组软件定时器实现方案用到的地方比较多,而且实用。后续章节中多个外部设备驱动都会用到。