完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第35章 STM32H7的定時器應用之高精度單次延遲實現(支持TIM2,3,4和5)
本章教程為大家講解定時器應用之高精度單次延遲實現,支持TIM2,TIM3,TIM4和TIM5。實際項目中用到的地方較多,如Modbus幀符間隔,定時采集一段時間波形等。
35.1 初學者重要提示
35.2 定時器單次延遲驅動設置
35.3 定時器板級支持包(bsp_timer.c)
35.4 定時器驅動移植和使用
35.5 實驗例程框架
35.6 實驗例程說明(MDK)
35.7 實驗例程說明(IAR)
35.8 總結
35.1 初學者重要提示
- 學習本章節前,務必優先學習第32章,HAL庫的幾個常用API均作了講解和舉例。
- STM32H7支持TIM1-TIM8,TIM12-TIM17共14個定時器,而中間的TIM9,TIM10,TIM11是不存在的,這點要注意。
- 在不需要任何補償的情況下,誤差可以做到正負1微秒以內。
- TIM2和TIM5是32位定時器,而TIM3和TIM4是16位定時器。
35.2 定時器單次延遲驅動設計
單次定時器要實現1us的精度,可以直接將定時器時鍾設置為1MHz,這樣定時器每計數1次就是1us。對於16位定時器最大值就是0xFFFF微秒,而32位定時器就是0xFFFFFFFF微秒。
剩下的問題就是單次延遲時間到了可以及時執行相應功能,那么就可以開啟一個CC捕獲比較中斷。而延遲時間可以直接通過設置CCR比較捕獲寄存器實現。比如當前定時器的計數值是1000,我們要實現10us的單次延遲,我們就可以直接設置CCR的數值為1000 + 10 =1010即可,等1010的計數值到了,就會觸發CC捕獲比較中斷。
35.2.1 定時器單次延遲宏定義
單次延遲支持TIM2,TIM3,TIM4和TIM5,其中TIM2和TIM5是32位定時器,而TIM3和TIM4是16位定時器。每個定時器都有4個通道,可以獨立配置使用,互不影響。
1. /* 2. 定義用於硬件定時器的TIM, 可以使 TIM2 - TIM5 3. */ 4. #define USE_TIM2 5. //#define USE_TIM3 6. //#define USE_TIM4 7. //#define USE_TIM5 8. 9. #ifdef USE_TIM2 10. #define TIM_HARD TIM2 11. #define RCC_TIM_HARD_CLK_ENABLE() __HAL_RCC_TIM2_CLK_ENABLE() 12. #define TIM_HARD_IRQn TIM2_IRQn 13. #define TIM_HARD_IRQHandler TIM2_IRQHandler 14. #endif 15. 16. #ifdef USE_TIM3 17. #define TIM_HARD TIM3 18. #define RCC_TIM_HARD_CLK_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE() 19. #define TIM_HARD_IRQn TIM3_IRQn 20. #define TIM_HARD_IRQHandler TIM3_IRQHandler 21. #endif 22. 23. #ifdef USE_TIM4 24. #define TIM_HARD TIM4 25. #define RCC_TIM_HARD_CLK_ENABLE() __HAL_RCC_TIM4_CLK_ENABLE() 26. #define TIM_HARD_IRQn TIM4_IRQn 27. #define TIM_HARD_IRQHandler TIM4_IRQHandler 28. #endif 29. 30. #ifdef USE_TIM5 31. #define TIM_HARD TIM5 32. #define RCC_TIM_HARD_CLK_ENABLE() __HAL_RCC_TIM5_CLK_ENABLE() 33. #define TIM_HARD_IRQn TIM5_IRQn 34. #define TIM_HARD_IRQHandler TIM5_IRQHandler 35. #endif 36. 37. /* 保存 TIM定時中斷到后執行的回調函數指針 */ 38. static void (*s_TIM_CallBack1)(void); 39. static void (*s_TIM_CallBack2)(void); 40. static void (*s_TIM_CallBack3)(void); 41. static void (*s_TIM_CallBack4)(void);
這里把幾個關鍵的地方闡釋下:
- 第4- 7行,用於選擇要使用的定時器,使用哪個定時器,使能那個宏定義即可。
- 第9 - 14行,用於配置定時器的四個宏定義,這里是配置的TIM2,后面TIM3,TIM4,TIM5的配置同理。
- 第38 – 40行,定義4個函數指針,用於保存定時器CC比較捕獲中斷執行后的回調函數指針。
35.2.2 定時器單次延遲初始化
單次定時器的初始化代碼如下:
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: bsp_InitHardTimer 4. * 功能說明: 配置 TIMx,用於us級別硬件定時。TIMx將自由運行,永不停止. 5. * TIMx可以用TIM2 - TIM5 之間的TIM, 這些TIM有4個通道, 掛在 APB1 上,輸入時鍾 6. * =SystemCoreClock / 2 7. * 形 參: 無 8. * 返 回 值: 無 9. ****************************************************************************************************** 10. */ 11. void bsp_InitHardTimer(void) 12. { 13. TIM_HandleTypeDef TimHandle = {0}; 14. uint32_t usPeriod; 15. uint16_t usPrescaler; 16. uint32_t uiTIMxCLK; 17. TIM_TypeDef* TIMx = TIM_HARD; 18. 19. RCC_TIM_HARD_CLK_ENABLE(); /* 使能TIM時鍾 */ 20. 21. /*----------------------------------------------------------------------- 22. bsp.c 文件中 void SystemClock_Config(void) 函數對時鍾的配置如下: 23. 24. System Clock source = PLL (HSE) 25. SYSCLK(Hz) = 400000000 (CPU Clock) 26. HCLK(Hz) = 200000000 (AXI and AHBs Clock) 27. AHB Prescaler = 2 28. D1 APB3 Prescaler = 2 (APB3 Clock 100MHz) 29. D2 APB1 Prescaler = 2 (APB1 Clock 100MHz) 30. D2 APB2 Prescaler = 2 (APB2 Clock 100MHz) 31. D3 APB4 Prescaler = 2 (APB4 Clock 100MHz) 32. 33. 因為APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz; 34. 因為APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz; 35. APB4上面的TIMxCLK沒有分頻,所以就是100MHz; 36. 37. APB1 定時器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1 38. APB2 定時器有 TIM1, TIM8 , TIM15, TIM16,TIM17 39. 40. APB4 定時器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5 41. 42. ----------------------------------------------------------------------- */ 43. if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM15) || (TIMx == TIM16) || (TIMx == TIM17)) 44. { 45. /* APB2 定時器時鍾 = 200M */ 46. uiTIMxCLK = SystemCoreClock / 2; 47. } 48. else 49. { 50. /* APB1 定時器 = 200M */ 51. uiTIMxCLK = SystemCoreClock / 2; 52. } 53. 54. usPrescaler = uiTIMxCLK / 1000000 - 1; /* 分頻比 = 1 */ 55. 56. if (TIMx == TIM2 || TIMx == TIM5) 57. { 58. usPeriod = 0xFFFFFFFF; 59. } 60. else 61. { 62. usPeriod = 0xFFFF; 63. } 64. 65. /* 66. 設置分頻為usPrescaler后,那么定時器計數器計1次就是1us 67. 而參數usPeriod的值是決定了最大計數: 68. usPeriod = 0xFFFF 表示最大0xFFFF微妙。 69. usPeriod = 0xFFFFFFFF 表示最大0xFFFFFFFF微妙。 70. */ 71. TimHandle.Instance = TIMx; 72. TimHandle.Init.Prescaler = usPrescaler; 73. TimHandle.Init.Period = usPeriod; 74. TimHandle.Init.ClockDivision = 0; 75. TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP; 76. TimHandle.Init.RepetitionCounter = 0; 77. TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; 78. 79. if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK) 80. { 81. Error_Handler(__FILE__, __LINE__); 82. } 83. 84. /* 配置定時器中斷,給CC捕獲比較中斷使用 */ 85. { 86. HAL_NVIC_SetPriority(TIM_HARD_IRQn, 0, 2); 87. HAL_NVIC_EnableIRQ(TIM_HARD_IRQn); 88. } 89. 90. /* 啟動定時器 */ 91. HAL_TIM_Base_Start(&TimHandle); 92. }
這里把幾個關鍵的地方闡釋下:
- 第13行,HAL庫的這個結構體變量要初始化為0,此問題在第32章的的4.1小節有專門說明。
- 第43 – 52行,獲取定時器的時鍾頻率,TIM2,TIM3,TIM4和TIM5都是用的APB1,因為APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz。
- 第54行,設置分頻參數,定時器分頻的頻率是1MHz。
- 第71 - 82行,設置分頻為usPrescaler后,那么定時器計數器計1次就是1us,而參數usPeriod的值是決定了最大計數:
usPeriod = 0xFFFF 表示最大0xFFFF微秒。
usPeriod = 0xFFFFFFFF 表示最大0xFFFFFFFF微秒。
- 第86 – 87行,這里要特別注意,此處是開啟定時器的NVIC是供CC捕獲比較中斷使用,而不是更新中斷。
- 第91行,啟動定時器。
35.2.3 定時器單次延遲啟動
下面是定時器的啟動代碼,使用TIM2-5做單次定時器使用, 定時時間到后執行回調函數。可以同時啟動4個定時器,互不干擾。
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: bsp_StartHardTimer 4. * 功能說明: 使用TIM2-5做單次定時器使用, 定時時間到后執行回調函數。可以同時啟動4個定時器,互不干擾。 5. * 定時精度正負1us (主要耗費在調用本函數的執行時間,函數內部進行了補償減小誤差) 6. * TIM2和TIM5 是32位定時器。定時范圍很大 7. * TIM3和TIM4 是16位定時器。 8. * 形 參: _CC : 捕獲通道幾,1,2,3, 4 9. * _uiTimeOut : 超時時間, 單位 1us. 對於16位定時器,最大 65.5ms; 對於32位定時器,最大 4294秒 10. * _pCallBack : 定時時間到后,被執行的函數 11. * 返 回 值: 無 12. ****************************************************************************************************** 13. */ 14. void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void * _pCallBack) 15. { 16. uint32_t cnt_now; 17. uint32_t cnt_tar; 18. TIM_TypeDef* TIMx = TIM_HARD; 19. 20. /* H743速度較快,無需補償延遲,實測精度正負1us */ 21. 22. cnt_now = TIMx->CNT; 23. cnt_tar = cnt_now + _uiTimeOut; /* 計算捕獲的計數器值 */ 24. if (_CC == 1) 25. { 26. s_TIM_CallBack1 = (void (*)(void))_pCallBack; 27. 28. TIMx->CCR1 = cnt_tar; /* 設置捕獲比較計數器CC1 */ 29. TIMx->SR = (uint16_t)~TIM_IT_CC1; /* 清除CC1中斷標志 */ 30. TIMx->DIER |= TIM_IT_CC1; /* 使能CC1中斷 */ 31. } 32. else if (_CC == 2) 33. { 34. s_TIM_CallBack2 = (void (*)(void))_pCallBack; 35. 36. TIMx->CCR2 = cnt_tar; /* 設置捕獲比較計數器CC2 */ 37. TIMx->SR = (uint16_t)~TIM_IT_CC2; /* 清除CC2中斷標志 */ 38. TIMx->DIER |= TIM_IT_CC2; /* 使能CC2中斷 */ 39. } 40. else if (_CC == 3) 41. { 42. s_TIM_CallBack3 = (void (*)(void))_pCallBack; 43. 44. TIMx->CCR3 = cnt_tar; /* 設置捕獲比較計數器CC3 */ 45. TIMx->SR = (uint16_t)~TIM_IT_CC3; /* 清除CC3中斷標志 */ 46. TIMx->DIER |= TIM_IT_CC3; /* 使能CC3中斷 */ 47. } 48. else if (_CC == 4) 49. { 50. s_TIM_CallBack4 = (void (*)(void))_pCallBack; 51. 52. TIMx->CCR4 = cnt_tar; /* 設置捕獲比較計數器CC4 */ 53. TIMx->SR = (uint16_t)~TIM_IT_CC4; /* 清除CC4中斷標志 */ 54. TIMx->DIER |= TIM_IT_CC4; /* 使能CC4中斷 */ 55. } 56. else 57. { 58. return; 59. } 60. }
這里把幾個關鍵的地方闡釋下:
- 第22行,獲取定時器的計數值,賦給32位變量。
- 第23行,將當前的計數值和延遲的計數值求和,這里有個隱含的知識點,就是兩個數求和會有溢出的情況,溢出了會不會出問題,答案是不會的
- 對於32位定時器,如果兩個32位變量求和超過范圍,那么變量cnt_tar最終結果是超出的那部分。而定時器的配置也是向上計數的,計數滿32位后,也是從0開始重新計數,記到cnt_tar就是我們所設置的_uiTimeOut時間。為了方便大家理解,舉個例子,比如cnt_now = TIMx->CNT = 0xfffffff0, _uiTimeOut = 0x20。那么cnt_tar = 0x10,定時器從0xfffffff0計數到0xffffffff后,再從0開始計數到0x10,時間差就是_uiTimeOut。
- 對於16位定時器,cnt_now = TIMx->CNT獲取的數值是小於等於0xffff的,執行第23行的函數后,變量cnt_tar的數值是有可能會大於0xffff的,這也沒有關系的,因為16位定時器對應的CCR寄存器是16位的,執行效果跟32位定時器溢出的效果一樣。比如cnt_now = TIMx->CNT = 0xfff0, _uiTimeOut = 0x20。那么cnt_tar = 0x10010,將這個數值賦值給16位的CCR寄存器效果就是CCR = 0x10。定時器從0xfff0計數到0xffff后,再從0開始計數到0x10,時間差就_uiTimeOut。
- 第24行,_CC = 1表示通道1,_CC = 2表示通道2,_CC = 3表示通道3,_CC = 4表示通道4。
- 第26行,參數_pCallBack前的void (*)(void)是函數指針的強制類型轉換,防止警告。
- 第28 – 30行,設置捕獲比較寄存器CCR,清除CC中斷並開啟CC中斷。
- 第32 – 55行,其它通道的處理。跟通道1的處理方式相同。
看了源碼后,也許會有讀者會問,程序里面直接將定時器計數器CNT清零后設置新的計數是否可行。答案是不行的,因為我們要實現四個通道可以同時使用,如果CNT清零,將影響其它通道的使用。
35.2.4 定時器中斷處理
定時器中斷服務程序主要用於處理 CC捕獲比較中斷,啟動單次延遲后,時間到了將執行中斷服務程序里面的回調函數。用戶可以在這個回調函數里面實現要做的功能。
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: TIMx_IRQHandler 4. * 功能說明: TIM 中斷服務程序 5. * 形 參:無 6. * 返 回 值: 無 7. ****************************************************************************************************** 8. */ 9. void TIM_HARD_IRQHandler(void) 10. { 11. uint16_t itstatus = 0x0, itenable = 0x0; 12. TIM_TypeDef* TIMx = TIM_HARD; 13. 14. 15. itstatus = TIMx->SR & TIM_IT_CC1; 16. itenable = TIMx->DIER & TIM_IT_CC1; 17. 18. if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET)) 19. { 20. TIMx->SR = (uint16_t)~TIM_IT_CC1; 21. TIMx->DIER &= (uint16_t)~TIM_IT_CC1; /* 禁能CC1中斷 */ 22. 23. /* 先關閉中斷,再執行回調函數。因為回調函數可能需要重啟定時器 */ 24. s_TIM_CallBack1(); 25. } 26. 27. itstatus = TIMx->SR & TIM_IT_CC2; 28. itenable = TIMx->DIER & TIM_IT_CC2; 29. if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET)) 30. { 31. TIMx->SR = (uint16_t)~TIM_IT_CC2; 32. TIMx->DIER &= (uint16_t)~TIM_IT_CC2; /* 禁能CC2中斷 */ 33. 34. /* 先關閉中斷,再執行回調函數。因為回調函數可能需要重啟定時器 */ 35. s_TIM_CallBack2(); 36. } 37. 38. itstatus = TIMx->SR & TIM_IT_CC3; 39. itenable = TIMx->DIER & TIM_IT_CC3; 40. if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET)) 41. { 42. TIMx->SR = (uint16_t)~TIM_IT_CC3; 43. TIMx->DIER &= (uint16_t)~TIM_IT_CC3; /* 禁能CC2中斷 */ 44. 45. /* 先關閉中斷,再執行回調函數。因為回調函數可能需要重啟定時器 */ 46. s_TIM_CallBack3(); 47. } 48. 49. itstatus = TIMx->SR & TIM_IT_CC4; 50. itenable = TIMx->DIER & TIM_IT_CC4; 51. if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET)) 52. { 53. TIMx->SR = (uint16_t)~TIM_IT_CC4; 54. TIMx->DIER &= (uint16_t)~TIM_IT_CC4; /* 禁能CC4中斷 */ 55. 56. /* 先關閉中斷,再執行回調函數。因為回調函數可能需要重啟定時器 */ 57. s_TIM_CallBack4(); 58. } 59. }
中斷服務程序里面四個通道的處理方式是一樣的,這里以通道1為例進行說明。
- 第15 – 18行,獲取是否使能了CC中斷且CC中斷標志被置位。
- 第20 – 24行,清除CC中斷標志,關閉CC中斷,並執行回調函數。
- 第27 - 58行,其它通道的處理。跟通道1的處理方式相同。
35.3 定時器板級支持包(bsp_timer.c)
定時器單次延遲驅動文件bsp_timer.c供用戶調用的主要是如下兩個函數:
- bsp_InitHardTimer
- bsp_StartHardTimer
注意,當用戶調用了函數bsp_InitTimer,此函數里面會調用bsp_InitHardTimer,用戶無需再單獨調用進行初始化。
35.3.1 函數bsp_InitHardTimer
函數原型:
void bsp_InitHardTimer(void)
函數描述:
此函數主要用於初始化定時器的單次延遲功能。us級別硬件定時,TIMx將自由運行,永不停止。
注意事項:
- 當用戶調用了函數bsp_InitTimer,此函數也會被調用,無需用戶再單獨調用。
35.3.2 函數bsp_StartHardTimer
函數原型:
void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void * _pCallBack)
函數描述:
使用TIM2-5做單次定時器使用, 定時時間到后執行回調函數。可以同時啟動4個定時器通道,互不干擾。定時精度正負1us(主要耗費在調用本函數的執行時間)。
函數參數:
- 第1個參數表示使用的捕獲比較通道,數值范圍1,2,3, 4,分別表示通道1,通道2,通道3和通道4。
- 第2個參數是超時時間, 單位 1us。對於16位定時器,最大0xFFFF微秒,即65.5毫秒,對於32位定時器,最大 0xFFFFFFFF微秒,即4294秒。
- 第3個參數是超時時間到后,被執行的回調函數。
注意事項:
- 根據使用的16位定時器或32位定時器,設置的超時時間不可超出范圍。
使用舉例:
可以看本章節配套的實例。
35.4 定時器驅動移植和使用
定時器的移植比較簡單:
- 第1步:復制bsp_timer.c和bsp_timer.h到自己的工程目錄,並添加到工程里面。
- 第2步:這幾個驅動文件主要用到HAL庫的GPIO和TIM驅動文件,簡單省事些可以添加所有HAL庫.C源文件進來。
- 第3步,應用方法看本章節配套例子即可。
35.5 實驗例程設計框架
通過程序設計框架,讓大家先對配套例程有一個全面的認識,然后再理解細節,本次實驗例程的設計框架如下:
- 第1階段,上電啟動階段:這部分在第14章進行了詳細說明。
- 第2階段,進入main函數:
-
- 第1步,硬件初始化,主要是MPU,Cache,HAL庫,系統時鍾,滴答定時器,LED和串口。
- 第2步,借助按鍵消息,方便用戶測量不同微秒延遲實際耗時。
35.6 實驗例程說明(MDK)
配套例子:
V7-020_定時器四個比較捕獲通道實現微妙級單次延遲(驅動支持TIM2-TIM5)
實驗目的:
- 學習定時器實現微秒級單次延遲。
實驗內容:
- 系統上電后驅動了1個軟件定時器,每100ms翻轉一次LED2。
- STM32H7支持TIM1-TIM8,TIM12-TIM17共14個定時器,而中間的TIM9,TIM10,TIM11是不存在的。
- 在不需要任何補償的情況下,誤差可以做到正負1微秒以內。
- 通過測量FMC擴展引腳23,可以測試單次延遲的實際執行時間。
實驗操作:
- K1鍵按下,實現一個5微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
- K2鍵按下,實現一個10微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
- K3鍵按下,實現一個100微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
FMC擴展引腳23的位置:
上電后串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1
實際執行時間測量:
在不做任何誤差補償的情況下,誤差在正負1微妙內,下面是延遲5微妙的實際執行時間:
下面是延遲10微妙的實際執行時間:
程序設計:
系統棧大小分配:
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(); }
主功能:
主程序實現如下操作:
- K1鍵按下,實現一個5微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
- K2鍵按下,實現一個10微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
- K3鍵按下,實現一個100微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名稱和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重裝的定時器 */ /* 進入主程序循環體 */ while (1) { bsp_Idle(); /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,實現一個5微秒的單次延遲 */ bsp_StartHardTimer(1 ,5, (void *)TIM_CallBack2); HC574_TogglePin(GPIO_PIN_23); break; case KEY_DOWN_K2: /* K2鍵按下,實現一個10微秒的單次延遲 */ bsp_StartHardTimer(1 ,10, (void *)TIM_CallBack2); HC574_TogglePin(GPIO_PIN_23); break; case KEY_DOWN_K3: /* K3鍵按下,實現一個100微秒的單次延遲 */ bsp_StartHardTimer(1 ,100, (void *)TIM_CallBack2); HC574_TogglePin(GPIO_PIN_23); break; default: /* 其它的鍵值不處理 */ break; } } } }
注意回調函數的處理:
/* ********************************************************************************************************* * 函 數 名: TIM_CallBack2 * 功能說明: 定時器中斷的回調函數,此函數被bsp_StartHardTimer所調用。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void TIM_CallBack2(void) { HC574_TogglePin(GPIO_PIN_23); bsp_LedToggle(4); }
35.7 實驗例程說明(IAR)
配套例子:
V7-020_定時器四個比較捕獲通道實現微妙級單次延遲(驅動支持TIM2-TIM5)
實驗目的:
- 學習定時器實現微秒級單次延遲。
實驗內容:
- 系統上電后驅動了1個軟件定時器,每100ms翻轉一次LED2。
- STM32H7支持TIM1-TIM8,TIM12-TIM17共14個定時器,而中間的TIM9,TIM10,TIM11是不存在的。
- 在不需要任何補償的情況下,誤差可以做到正負1微秒以內。
- 通過測量FMC擴展引腳23,可以測試單次延遲的實際執行時間。
實驗操作:
- K1鍵按下,實現一個5微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
- K2鍵按下,實現一個10微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
- K3鍵按下,實現一個100微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
FMC擴展引腳23的位置:
上電后串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1
實際執行時間測量:
在不做任何誤差補償的情況下,誤差在正負1微妙內,下面是延遲5微妙的實際執行時間:
下面是延遲10微妙的實際執行時間:
程序設計:
系統棧大小分配:
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(); }
主功能:
主程序實現如下操作:
- K1鍵按下,實現一個5微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
- K2鍵按下,實現一個10微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
- K3鍵按下,實現一個100微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名稱和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 啟動1個100ms的自動重裝的定時器 */ /* 進入主程序循環體 */ while (1) { bsp_Idle(); /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,實現一個5微秒的單次延遲 */ bsp_StartHardTimer(1 ,5, (void *)TIM_CallBack2); HC574_TogglePin(GPIO_PIN_23); break; case KEY_DOWN_K2: /* K2鍵按下,實現一個10微秒的單次延遲 */ bsp_StartHardTimer(1 ,10, (void *)TIM_CallBack2); HC574_TogglePin(GPIO_PIN_23); break; case KEY_DOWN_K3: /* K3鍵按下,實現一個100微秒的單次延遲 */ bsp_StartHardTimer(1 ,100, (void *)TIM_CallBack2); HC574_TogglePin(GPIO_PIN_23); break; default: /* 其它的鍵值不處理 */ break; } } } }
注意回調函數的處理:
/* ********************************************************************************************************* * 函 數 名: TIM_CallBack2 * 功能說明: 定時器中斷的回調函數,此函數被bsp_StartHardTimer所調用。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void TIM_CallBack2(void) { HC574_TogglePin(GPIO_PIN_23); bsp_LedToggle(4); }
35.8 總結
本章節就為大家講解這么多,單次延遲在實際項目中用到的地方較多,如Modbus幀符間隔,定時采集一段時間波形等,望初學者務必掌握。