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 <b>__Vendor_SysTickConfig</b> is set to 1, then the
- 9. function <b>SysTick_Config</b> is not included. In this case, the file <b><i>device</i>.h</b>
- 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。
在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);
- 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
函数原型:
函数描述:
此函数主要用于软件定时器的初始化,使用所有其它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 总结
本章节为大家介绍的多组软件定时器实现方案用到的地方比较多,而且实用。后续章节中多个外部设备驱动都会用到。
|