前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【STM32H7教程】第35章 STM32H7的定时器应用之高精度单次延迟实现(支持TIM2,3,4和5)

【STM32H7教程】第35章 STM32H7的定时器应用之高精度单次延迟实现(支持TIM2,3,4和5)

作者头像
Simon223
发布2020-02-11 10:32:33
1.3K0
发布2020-02-11 10:32:33
举报

完整教程下载地址: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个通道,可以独立配置使用,互不影响。

代码语言:javascript
复制
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 定时器单次延迟初始化

单次定时器的初始化代码如下:

代码语言:javascript
复制
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个定时器,互不干扰。

代码语言:javascript
复制
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捕获比较中断,启动单次延迟后,时间到了将执行中断服务程序里面的回调函数。用户可以在这个回调函数里面实现要做的功能。

代码语言:javascript
复制
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

函数原型:

代码语言:javascript
复制
void bsp_InitHardTimer(void)

函数描述:

此函数主要用于初始化定时器的单次延迟功能。us级别硬件定时,TIMx将自由运行,永不停止。

注意事项:

  1. 当用户调用了函数bsp_InitTimer,此函数也会被调用,无需用户再单独调用。

35.3.2 函数bsp_StartHardTimer

函数原型:

代码语言:javascript
复制
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 文件实现:

代码语言:javascript
复制
/*
*********************************************************************************************************
*    函 数 名: 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区。

代码语言:javascript
复制
/*
*********************************************************************************************************
*    函 数 名: 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。
代码语言:javascript
复制
/*
*********************************************************************************************************
*    函 数 名: 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;
            }
        }
    }
}

注意回调函数的处理:

代码语言:javascript
复制
/*
*********************************************************************************************************
*    函 数 名: 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 文件实现:

代码语言:javascript
复制
/*
*********************************************************************************************************
*    函 数 名: 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区。

代码语言:javascript
复制
/*
*********************************************************************************************************
*    函 数 名: 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。
代码语言:javascript
复制
/*
*********************************************************************************************************
*    函 数 名: 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;
            }
        }
    }
}

注意回调函数的处理:

代码语言:javascript
复制
/*
*********************************************************************************************************
*    函 数 名: TIM_CallBack2
*    功能说明: 定时器中断的回调函数,此函数被bsp_StartHardTimer所调用。                        
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void TIM_CallBack2(void)
{
    HC574_TogglePin(GPIO_PIN_23);
    bsp_LedToggle(4);
}

35.8 总结

本章节就为大家讲解这么多,单次延迟在实际项目中用到的地方较多,如Modbus帧符间隔,定时采集一段时间波形等,望初学者务必掌握。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-01-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第35章       STM32H7的定时器应用之高精度单次延迟实现(支持TIM2,3,4和5)
    • 35.1 初学者重要提示
      • 35.2 定时器单次延迟驱动设计
        • 35.2.1 定时器单次延迟宏定义
        • 35.2.2 定时器单次延迟初始化
        • 35.2.3 定时器单次延迟启动
        • 35.2.4 定时器中断处理
      • 35.3 定时器板级支持包(bsp_timer.c)
        • 35.3.1 函数bsp_InitHardTimer
        • 35.3.2 函数bsp_StartHardTimer
      • 35.4 定时器驱动移植和使用
        • 35.5 实验例程设计框架
          • 35.6 实验例程说明(MDK)
            • 35.7 实验例程说明(IAR)
              • 35.8 总结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档