【STM32H7教程】第35章 STM32H7的定時器應用之高精度單次延遲實現(支持TIM2,3,4和5)


完整教程下載地址: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 初學者重要提示

  1.   學習本章節前,務必優先學習第32章,HAL庫的幾個常用API均作了講解和舉例。
  2.   STM32H7支持TIM1-TIM8,TIM12-TIM17共14個定時器,而中間的TIM9,TIM10,TIM11是不存在的,這點要注意。
  3.   在不需要任何補償的情況下,誤差可以做到正負1微秒以內。
  4.   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);

 

這里把幾個關鍵的地方闡釋下:

  1.   第4- 7行,用於選擇要使用的定時器,使用哪個定時器,使能那個宏定義即可。
  2.   第9 - 14行,用於配置定時器的四個宏定義,這里是配置的TIM2,后面TIM3,TIM4,TIM5的配置同理。
  3.   第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.    }

 

這里把幾個關鍵的地方闡釋下:

  1.   第13行,HAL庫的這個結構體變量要初始化為0,此問題在第32章的的4.1小節有專門說明。
  2.   第43 – 52行,獲取定時器的時鍾頻率,TIM2,TIM3,TIM4和TIM5都是用的APB1,因為APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz。
  3.   第54行,設置分頻參數,定時器分頻的頻率是1MHz。
  4.   第71 - 82行,設置分頻為usPrescaler后,那么定時器計數器計1次就是1us,而參數usPeriod的值是決定了最大計數:

    usPeriod = 0xFFFF 表示最大0xFFFF微秒。

    usPeriod = 0xFFFFFFFF 表示最大0xFFFFFFFF微秒。

  5.   第86 – 87行,這里要特別注意,此處是開啟定時器的NVIC是供CC捕獲比較中斷使用,而不是更新中斷。
  6.   第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.    }

 

這里把幾個關鍵的地方闡釋下:

  1.   第22行,獲取定時器的計數值,賦給32位變量。
  2.   第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。
  3.   第24行,_CC = 1表示通道1,_CC = 2表示通道2,_CC = 3表示通道3,_CC = 4表示通道4。
  4.   第26行,參數_pCallBack前的void (*)(void)是函數指針的強制類型轉換,防止警告。
  5.   第28 – 30行,設置捕獲比較寄存器CCR,清除CC中斷並開啟CC中斷。
  6.   第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為例進行說明。

  1.   第15 – 18行,獲取是否使能了CC中斷且CC中斷標志被置位。
  2.   第20 – 24行,清除CC中斷標志,關閉CC中斷,並執行回調函數。
  3.   第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將自由運行,永不停止。

注意事項:

  1. 當用戶調用了函數bsp_InitTimer,此函數也會被調用,無需用戶再單獨調用。

35.3.2 函數bsp_StartHardTimer

函數原型:

void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void * _pCallBack)

 

函數描述:

使用TIM2-5做單次定時器使用, 定時時間到后執行回調函數。可以同時啟動4個定時器通道,互不干擾。定時精度正負1us(主要耗費在調用本函數的執行時間)。

函數參數:

  1.   第1個參數表示使用的捕獲比較通道,數值范圍1,2,3,  4,分別表示通道1,通道2,通道3和通道4。
  2.   第2個參數是超時時間, 單位 1us。對於16位定時器,最大0xFFFF微秒,即65.5毫秒,對於32位定時器,最大 0xFFFFFFFF微秒,即4294秒。
  3.   第3個參數是超時時間到后,被執行的回調函數。

注意事項:

  1. 根據使用的16位定時器或32位定時器,設置的超時時間不可超出范圍。

使用舉例:

可以看本章節配套的實例。

35.4 定時器驅動移植和使用

定時器的移植比較簡單:

  1.   第1步:復制bsp_timer.c和bsp_timer.h到自己的工程目錄,並添加到工程里面。
  2.   第2步:這幾個驅動文件主要用到HAL庫的GPIO和TIM驅動文件,簡單省事些可以添加所有HAL庫.C源文件進來。
  3.   第3步,應用方法看本章節配套例子即可。

35.5 實驗例程設計框架

通過程序設計框架,讓大家先對配套例程有一個全面的認識,然后再理解細節,本次實驗例程的設計框架如下:

  1.   第1階段,上電啟動階段:這部分在第14章進行了詳細說明。
  2.   第2階段,進入main函數:
    •   第1步,硬件初始化,主要是MPU,Cache,HAL庫,系統時鍾,滴答定時器,LED和串口。
    •   第2步,借助按鍵消息,方便用戶測量不同微秒延遲實際耗時。

35.6 實驗例程說明(MDK)

配套例子:

V7-020_定時器四個比較捕獲通道實現微妙級單次延遲(驅動支持TIM2-TIM5)

實驗目的:

  1. 學習定時器實現微秒級單次延遲。

實驗內容:

  1. 系統上電后驅動了1個軟件定時器,每100ms翻轉一次LED2。
  2. STM32H7支持TIM1-TIM8,TIM12-TIM17共14個定時器,而中間的TIM9,TIM10,TIM11是不存在的。
  3. 在不需要任何補償的情況下,誤差可以做到正負1微秒以內。
  4. 通過測量FMC擴展引腳23,可以測試單次延遲的實際執行時間。

實驗操作:

  1. K1鍵按下,實現一個5微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
  2. K2鍵按下,實現一個10微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
  3. 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. 學習定時器實現微秒級單次延遲。

實驗內容:

  1. 系統上電后驅動了1個軟件定時器,每100ms翻轉一次LED2。
  2. STM32H7支持TIM1-TIM8,TIM12-TIM17共14個定時器,而中間的TIM9,TIM10,TIM11是不存在的。
  3. 在不需要任何補償的情況下,誤差可以做到正負1微秒以內。
  4. 通過測量FMC擴展引腳23,可以測試單次延遲的實際執行時間。

實驗操作:

  1. K1鍵按下,實現一個5微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
  2. K2鍵按下,實現一個10微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
  3. 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();
}

 

  主功能:

主程序實現如下操作:

  1.   K1鍵按下,實現一個5微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
  2.   K2鍵按下,實現一個10微秒的單次延遲,開啟后翻轉FMC擴展引腳23,時間到后翻轉LED4,再翻轉擴展引腳23。
  3.   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幀符間隔,定時采集一段時間波形等,望初學者務必掌握。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM