完整教程下載地址:http://www.armbbs.cn/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 <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。
#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 總結
本章節為大家介紹的多組軟件定時器實現方案用到的地方比較多,而且實用。后續章節中多個外部設備驅動都會用到。