OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 7215|回复: 0

《MiniPRO H750开发指南》第二十一章 通用定时器实验(下)

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4896
金钱
4896
注册时间
2019-5-8
在线时间
1248 小时
发表于 2022-12-24 17:37:26 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2022-12-26 15:15 编辑

第二十一章 通用定时器实验
1)实验平台:正点原子MiniPro STM32H750开发板


2) 章节摘自【正点原子】MiniPro STM32H750 开发指南_V1.1


3)购买链接:https://detail.tmall.com/item.htm?id=677017430560


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h750_minipro.html


5)正点原子官方B站:https://space.bilibili.com/394620890


6)MiniPro STM32H750技术交流QQ群:170313895

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

21.4 通用定时器输入捕获实验
本小节我们来学习使用通用定时器的输入捕获模式。
输入捕获模式可以用来测量脉冲宽度或者测量频率。我们以测量脉宽为例,用一个简图来说明输入捕获脉宽测量原理,如图21.4.1所示:
             image001.png                      
图21.4.1 输入捕获脉宽测量原理

图21.4.1中,t1到t2的时间段,就是我们需要测量的高电平时间。测量方法如下:假如定时器工作在递增计数模式,首先设置定时器通道x为上升沿捕获,这样在t1时刻上升沿到来时,就会发生捕获事件。这里我们还会打开捕获中断,所以捕获事件发生就意味着捕获中断也会发生。在捕获中断里将计数器值清零,并设置通道x为下降沿捕获,这样t2时刻下降沿到来时,就会发生捕获事件和捕获中断。捕获事件发生时,计数器的值会被锁存到捕获/比较寄存器中(比如通道1对应的是CCR1寄存器)。那么在捕获中断里,我们读取捕获/比较寄存器就可以获取到高电平脉冲时间内,计数器计数的个数,从而可以算出高电平脉冲的时间。这里是假设定时器没有溢出为前提的。

实际上,t1到t2时间段,定时器可能会产生N次溢出,这就需要我们对定时器溢出做相应的处理,防止高电平太长,导致测量出错。在t1到t2时间段,假设定时器溢出N次,那么高电平脉冲时间内,计数器计数的个数计算方法为:N*(ARR+1)+ CCRx2,CCRx2表示t2时间点,捕获/比较寄存器的值。经过计算得到高电平脉宽时间内计数器计数个数后,用这个个数乘以计数器的计数周期,就可得到高电平持续的时间。就是输入捕获测量高电平脉宽时间的整个过程。

STM32H750的定时器除了TIM6和TIM7,其他定时器都有输入捕获功能。输入捕获,简单的说就是通过检测TIMx_Chy输入的信号边沿,在信号边沿发生跳变(比如上升沿/下降沿)时,会发生捕获事件,将当前定时器的值(TIMx_CNT)锁存到对应通道的捕获/比较寄存器(TIMx_CCRy)里,完成一次捕获。同时还可以配置捕获事件发生时是否触发捕获中断/DMA。另外还要考虑测量的过程中是否可能发生定时器溢出,如果可能溢出,还要做溢出处理。

21.4.1 TIM2/TIM3/TIM4/TIM5寄存器
通用定时器输入捕获实验需要用到的寄存器有:TIMx_ARR、TIMx_PSC、TIMx_CCMR1、TIMx_CCER、TIMx_DIER、TIMx_CR1、TIMx_CCR1这些寄存器在前面的章节都有提到,在这里只需针对性的介绍。

l  捕获/比较模式寄存器1/2(TIMx_CCMR1/2
该寄存器我们在PWM输出实验时讲解了他作为输出功能的配置,现在重点学习输入捕获模式的配置。因为本实验我们用到定时器5通道1输入,所以我们要看TIMx_CCMR1寄存器,其描述如图21.4.1.1所示:
   image003.png    
图21.4.1.1 TIMx_CCMR1寄存器

该寄存器在输入模式和输出模式下,功能是不一样,所以需要看准不同模式的描述,请打开手册查看。TIMx_CCMR1寄存器对应于通道1和通道2的设置,CCMR2寄存器对应通道3和通道4。如:TIMx_CCMR1寄存器位[7:0]和位16用于捕获/比较通道1的控制,而位[15:8]和位24则用于捕获/比较通道2的控制。我们用到定时器5通道1输入,所以需要配置TIMx_CCMR1的位[7:0]和位16。

其中CC1S[1:0],这两个位用于CCR1的通道配置,这里我们设置IC1S[1:0]=01,也就是配置IC1映射在TI1上。

输入捕获1预分频器IC1PSC[1:0],这个比较好理解。我们是1次边沿就触发1次捕获,所以选择00就行了。

输入捕获1滤波器IC1F[3:0],这个用来设置输入采样频率和数字滤波器长度。其中,fCK_INT是定时器时钟源频率,按照例程的配置为240Mhz,而fDTS则是根据TIMx_CR1的CKD[1:0]的设置来确定的,如果CKD[1:0]设置为00,那么fDTS=fCK_INT。N值采样次数,举个简单的例子:假设IC1F[3:0]=0011,并设置IC1映射到TI1上。表示以fCK_INT为采样频率,当连续8次都是采样到TI1为高电平或者低电平,滤波器才输出一个有效输出边沿。当8次采样中有高有低,那就保持原来的输出,这样可以滤除高频干扰信号,从而达到滤波的效果。这里,我们不做滤波处理,所以设置IC1F[3:0]=0000。

l   捕获/比较使能寄存器(TIMx_ CCER
TIM2/TIM3/TIM4/TIM5的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如图21.4.1.2所示:
    image005.png
图21.4.1.2 TIMx_CCER寄存器

我们要用到这个寄存器的最低2位,CC1E和CC1P位。要使能输入捕获,必须设置CC1E=1,而CC1P则根据自己的需要来配置。我们这里是保留默认设置值0,即高电平触发捕获。

接下来我们再看看DMA/中断使能寄存器:TIMx_DIER,该寄存器的各位描述见图21.2.1.3(在21.2.1小节)。本小节,我们需要用到中断来处理捕获数据,所以必须开启通道1的捕获比较中断,即CC1IE设置为1。同时我们还需要在定时器溢出中断中累计定时器溢出的次数,所以还需要使能定时器的更新中断,即UIE置1。

控制寄存器:TIMx_CR1,我们只用到了它的最低位,也就是用来使能定时器的。

最后再来看看捕获/比较寄存器1:TIMx_CCR1,该寄存器用来存储发生捕获事件时,TIMx_CNT的值,我们从TIMx_CCR1就可以读出通道1捕获事件发生时刻的TIMx_CNT值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度(注意,对于高电平脉宽太长的情况,还要计算定时器溢出的次数)。

21.4.2 硬件设计
1. 例程功能
1、使用TIM5_CH1来做输入捕获,捕获PA0上的高电平脉宽,并将脉宽时间通过串口打印出来,通过按WK_UP按键,模拟输入高电平,例程中能测试的最长高电平脉宽时间为:4194303us。
2、LED0闪烁指示程序运行。

2. 硬件资源
1)RGB灯
    RED : LED0 - PB4
2)  独立按键:WK_UP - PA0
3)定时器5,使用TIM5通道1,将PA0复用为TIM5_CH1。

3. 原理图
定时器属于STM32H750的内部资源,只需要软件设置好即可正常工作。我们借助WK_UP做输入脉冲源并通过串口上位机来监测定时器输入捕获的情况。

21.4.3 程序设计
21.4.3.1 定时器的HAL库驱动
定时器在HAL库中的驱动代码在前面已经介绍了部分,这里我们再介绍几个本实验用到的函数。

1.HAL_TIM_IC_Init函数
定时器的输入捕获模式初始化函数,其声明如下:
  1. HAL_StatusTypeDefHAL_TIM_IC_Init(TIM_HandleTypeDef *htim);
复制代码
l  函数描述:
用于初始化定时器的输入捕获模式。

l  函数形参:
形参1是TIM_HandleTypeDef结构体类型指针变量,介绍基本定时器的时候已经介绍。

l  函数返回值:
HAL_StatusTypeDef枚举类型的值。

l  注意事项:
与PWM输出实验一样,当使用定时器做输入捕获功能时,在HAL库中并不使用定时器初始化函数HAL_TIM_Base_Init来实现,而是使用输入捕获特定的定时器初始化函数HAL_TIM_IC_Init。该函数内部还会调用输入捕获初始化回调函数HAL_TIM_IC_MspInit来初始化输入通道对应的GPIO(复用),以及输入捕获相关的配置。

2. HAL_TIM_IC_ConfigChannel函数
定时器的输入捕获通道设置初始化函数。其声明如下:
  1. HAL_StatusTypeDef HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim,
  2. TIM_IC_InitTypeDef *sConfig, uint32_t Channel);
复制代码
l  函数描述:
该函数用于设置定时器的输入捕获通道。

l  函数形参:
形参1是TIM_HandleTypeDef结构体类型指针变量,用于配置定时器基本参数。
形参2是TIM_IC_InitTypeDef结构体类型指针变量,用于配置定时器的输入捕获参数。
重点了解一下TIM_IC_InitTypeDef结构体指针类型,其定义如下:
  1. typedef struct
  2. {
  3.   uint32_t ICPolarity;      /* 输入捕获触发方式选择,比如上升、下降和双边沿捕获 */
  4.   uint32_t ICSelection;     /* 输入捕获选择,用于设置映射关系 */
  5.   uint32_t ICPrescaler;     /* 输入捕获分频系数 */
  6.   uint32_t ICFilter;        /* 输入捕获滤波器设置 */
  7. }TIM_IC_InitTypeDef;
复制代码
该结构体成员我们现在介绍一下。成员变量ICPolarity用来设置输入信号的有效捕获极性,取值范围为:TIM_ICPOLARITY_RISING(上升沿捕获),TIM_ICPOLARITY_FALLING(下降沿捕获)和TIM_ICPOLARITY_BOTHEDGE(双边沿捕获)。成员变量ICSelection用来设置映射关系,我们配置IC1直接映射在TI1上,选择TIM_ICSELECTION_DIRECTTI(另外还有两个输入通道TIM_ICSELECTION_INDIRECTTI和TIM_ICSELECTION_TRC)。成员变量ICPrescaler用来设置输入捕获分频系数,可以设置为TIM_ICPSC_DIV1(不分频),TIM_ICPSC_DIV2(2分频),TIM_ICPSC_DIV4(4分频)以及TIM_ICPSC_DIV8(8分频),本实验需要设置为不分频,所以选值为TIM_ICPSC_DIV1。成员变量ICFilter用来设置滤波器长度,这里我们不使用滤波器,所以设置为0。
形参3是定时器通道,范围:TIM_CHANNEL_1到TIM_CHANNEL_6,比如定时器5只有4个通道,那选择范围只有TIM_CHANNEL_1到TIM_CHANNEL_4,就具体情况选择。

l  函数返回值:
HAL_StatusTypeDef枚举类型的值。

3.HAL_TIM_IC_Start_IT函数
启动定时器输入捕获模式函数,其声明如下:
  1. HAL_StatusTypeDef HAL_TIM_IC_Start_IT(TIM_HandleTypeDef*htim,
  2. uint32_t Channel);
复制代码
l  函数描述:
用于启动定时器的输入捕获模式,且开启输入捕获中断。

l  函数形参:
形参1是TIM_HandleTypeDef结构体类型指针变量。
形参2是定时器通道,范围:TIM_CHANNEL_1到TIM_CHANNEL_6。

l  函数返回值:
HAL_StatusTypeDef枚举类型的值。

l  注意事项:
如果我们不需要开启输入捕获中断,只是开启输入捕获功能,HAL库函数为:
  1. HAL_StatusTypeDef HAL_TIM_IC_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
复制代码

定时器输入捕获模式配置步骤
1)开启TIMx和相应输入通道的GPIO时钟,配置该IO口的复用功能输入
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器5通道1,对应IO是PA0,它们的时钟开启方法如下:
  1. __HAL_RCC_TIM5_CLK_ENABLE();             /* 使能定时器5 */
  2. __HAL_RCC_GPIOA_CLK_ENABLE();            /* 开启GPIOA时钟 */
复制代码
IO口复用功能是通过函数HAL_GPIO_Init来配置的。

2)初始化TIMx,设置TIMx的ARR和PSC等参数
使用定时器的输入捕获功能时,我们调用的是HAL_TIM_IC_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_IC_MspInit函数,我们可以通过后者存放定时器和GPIO时钟使能、GPIO初始化、中断使能以及优先级设置等代码。

3)设置定时器为输入捕获模式,开启输入捕获
在HAL库中,定时器的输入捕获模式是通过HAL_TIM_IC_ConfigChannel函数来设置定时器某个通道为输入捕获通道,包括映射关系,输入滤波和输入分频等。

4)使能定时器更新中断,开启捕获功能以及捕获中断,配置定时器中断优先级
通过__HAL_TIM_ENABLE_IT函数使能定时器更新中断。
通过HAL_TIM_IC_Start_IT函数使能定时器并开启捕获功能以及捕获中断。
通过HAL_NVIC_EnableIRQ函数使能定时器中断。
通过HAL_NVIC_SetPriority函数设置中断优先级。
因为我们要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获是下降沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就会溢出,对溢出必须做处理,否则结果就不准了。

5)编写中断服务函数
定时器中断服务函数为:TIMx_IRQHandler等,当发生中断的时候,程序就会执行中断服务函数。HAL库提供了一个定时器中断公共处理函数HAL_TIM_IRQHandler,该函数会根据中断类型调用相关的中断回调函数。用户根据自己的需要重定义这些中断回调函数来处理中断程序。本实验我们除了用到更新(溢出)中断回调函数HAL_TIM_PeriodElapsedCallback之外,还要用到捕获中断回调函数HAL_TIM_IC_CaptureCallback。详见本实验例程源码。

21.4.3.2 程序流程图
image008.png

图21.4.3.2.1通用定时器输入捕获实验程序流程图

21.4.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。通用定时器驱动源码包括两个文件:gtim.c和gtim.h。
首先看gtim.h头文件的几个宏定义:
  1. /*****************************************************************************/
  2. /* TIMX 输入捕获定义
  3. * 这里的输入捕获使用定时器TIM2_CH1,捕获WK_UP按键的输入
  4. * 默认是针对TIM2~TIM5, TIM12~TIM17.
  5. * 注意: 通过修改这8个宏定义,可以支持TIM1~TIM17任意一个定时器,任意一个IO口做输入捕获
  6. *       注意:默认用的PA0,设置的是下拉输入!如果改其他IO,对应的上下拉方式/AF功能等也得改!
  7. */
  8. #define GTIM_TIMX_CAP_CHY_GPIO_PORT            GPIOA
  9. #define GTIM_TIMX_CAP_CHY_GPIO_PIN             GPIO_PIN_0
  10. #define GTIM_TIMX_CAP_CHY_GPIO_AF              GPIO_AF2_TIM5      /* AF功能选择 */
  11. #define GTIM_TIMX_CAP_CHY_GPIO_CLK_ENABLE()
  12. do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */
  13. #define GTIM_TIMX_CAP                      TIM5                       
  14. #define GTIM_TIMX_CAP_IRQn                TIM5_IRQn
  15. #define GTIM_TIMX_CAP_IRQHandler         TIM5_IRQHandler
  16. #define GTIM_TIMX_CAP_CHY                 TIM_CHANNEL_1  /* 通道Y,  1<= Y <=4 */
  17. #define GTIM_TIMX_CAP_CHY_CCRX           TIM5->CCR1      /* 通道Y的输出比较寄存器 */
  18. #define GTIM_TIMX_CAP_CHY_CLK_ENABLE()      
  19.   do{__HAL_RCC_TIM5_CLK_ENABLE(); }while(0)    /* TIMX 时钟使能 */
  20. /*****************************************************************************/
复制代码
可以把上面的宏定义分成两部分,第一部分是定时器5输入通道1对应的IO口的宏定义,第二部分则是定时器5输入通道1的相应宏定义。
gtim.h头文件就添加了这部分的程序,下面看gtim.c的程序,首先是通用定时器输入捕获初始化函数,其定义如下:
  1. /**
  2. *@brief       通用定时器TIMX 通道Y 输入捕获 初始化函数
  3. *@note
  4. *             通用定时器的时钟来自APB1,当D2PPRE1≥2分频的时候
  5. *             通用定时器的时钟为APB1时钟的2倍, 而APB1为120M, 所以定时器时钟 = 240Mhz
  6. *             定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
  7. *             Ft=定时器工作频率,单位:Mhz
  8. *
  9. *@param       arr: 自动重装值
  10. *@param       psc: 时钟预分频数
  11. *@retval      无
  12. */
  13. voidgtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
  14. {
  15.    TIM_IC_InitTypeDef timx_ic_cap_chy = {0};
  16.    g_timx_cap_chy_handle.Instance = GTIM_TIMX_CAP;                /* 定时器5 */
  17.    g_timx_cap_chy_handle.Init.Prescaler = psc;                   /* 定时器分频 */
  18.    g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;/* 递增计数模式 */
  19.    g_timx_cap_chy_handle.Init.Period = arr;                       /* 自动重装载值 */
  20.    HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
  21.    
  22.    timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING;         /* 上升沿捕获 */
  23.    timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;     /* 映射到TI1上 */
  24.    timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1;     /* 配置输入分频,不分频 */
  25.    timx_ic_cap_chy.ICFilter = 0;                        /* 配置输入滤波器,不滤波 */
  26. HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy,
  27. GTIM_TIMX_CAP_CHY);/* 配置TIM5通道1 */
  28. __HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE);  /* 使能更新中断 */
  29. /* 使能通道输入以及使能捕获中断*/
  30.    HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY);
  31. }
复制代码
HAL_TIM_IC_Init初始化定时器的基础工作参数,如:ARR和PSC等,第二部分则是调用HAL_TIM_IC_ConfigChannel函数配置输入捕获通道映射关系,滤波和分频等。最后是使能更新中断和使能通道输入以及定时器捕获中断。通道对应的IO、时钟开启和NVIC的初始化都在HAL_TIM_IC_MspInit函数里编写,其定义如下:
  1. /**
  2. *@brief       通用定时器输入捕获初始化接口
  3.                  HAL库调用的接口,用于配置不同的输入捕获
  4. *@param       htim:定时器句柄
  5. *@note        此函数会被HAL_TIM_IC_Init()调用
  6. *@retval      无
  7. */
  8. voidHAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
  9. {
  10.     if (htim->Instance == GTIM_TIMX_CAP)                      /* 输入通道捕获 */
  11.     {
  12.        GPIO_InitTypeDef gpio_init_struct;
  13.        GTIM_TIMX_CAP_CHY_CLK_ENABLE();                       /* 使能TIMx时钟 */
  14.        GTIM_TIMX_CAP_CHY_GPIO_CLK_ENABLE();                  /* 开启捕获IO的时钟 */
  15.        gpio_init_struct.Pin = GTIM_TIMX_CAP_CHY_GPIO_PIN; /* 输入捕获的GPIO口 */
  16.        gpio_init_struct.Mode = GPIO_MODE_AF_PP;             /* 复用推挽输出 */
  17.        gpio_init_struct.Pull = GPIO_PULLDOWN;               /* 下拉 */
  18.        gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
  19.        gpio_init_struct.Alternate = GTIM_TIMX_CAP_CHY_GPIO_AF; /* 复用 */
  20.        HAL_GPIO_Init(GTIM_TIMX_CAP_CHY_GPIO_PORT, &gpio_init_struct);
  21.        HAL_NVIC_SetPriority(GTIM_TIMX_CAP_IRQn, 1, 3);    /* 抢占1,子优先级3 */
  22.        HAL_NVIC_EnableIRQ(GTIM_TIMX_CAP_IRQn);             /* 开启ITMx中断 */
  23.     }
  24. }
复制代码
该函数调用HAL_GPIO_Init函数初始化定时器输入通道对应的IO,并且开启GPIO时钟,使能定时器。其中要注意IO口复用功能的选择一定要选对了。最后配置中断抢占优先级和响应优先级,以及打开定时器中断。
通过上面的两个函数输入捕获的初始化就完成了,下面先来介绍两个变量。
  1. /* 输入捕获状态(g_timxchy_cap_sta)
  2. *[7]  :0,没有成功的捕获;1,成功捕获到一次.
  3. *[6]  :0,还没捕获到高电平;1,已经捕获到高电平了.
  4. *[5:0]:捕获高电平后溢出的次数,最多溢出63次,所以最长捕获值= 63*65536+65535 = 4194303
  5. *       注意:为了通用,我们默认ARR和CCRy都是16位寄存器,对于32位的定时器(如:TIM5),
  6. *            也只按16位使用
  7. *       按1us的计数频率,最长溢出时间为:4194303 us, 约4.19秒
  8. *      (说明一下:正常32位定时器来说,1us计数器加1,溢出时间:4294秒)
  9. */
  10. uint8_t g_timxchy_cap_sta = 0;     /* 输入捕获状态 */
  11. uint16_t g_timxchy_cap_val =0 ;    /* 输入捕获值 */
复制代码
这两个变量用于辅助实现高电平捕获。其中g_timxchy_cap_sta,是用来记录捕获状态,(这个变量,我们把它当成一个寄存器那样来使用)。对其各位赋予状态含义,描述如下表所示:
QQ截图20221224172331.png
表21.4.3.3.1 g_timxchy_cap_sta各位描述

变量g_timxchy_cap_sta的位[5:0]是用于记录捕获高电平定时器溢出次数,总共6位,所以最多可以记录溢出的次数为2的6次方减一次,即63次。
变量g_timxchy_cap_val,则用来记录捕获到下降沿的时候,TIM5_CNT寄存器的值。
下面开始看中断服务函数的逻辑程序,HAL_TIM_IRQHandler函数会调用下面两个回调函数,我们的逻辑代码就是放在回调函数里,函数定义如下:
  1. /**
  2. * @brief       定时器输入捕获中断处理回调函数
  3. * @param       htim:定时器句柄指针
  4. * @note        该函数在HAL_TIM_IRQHandler中会被调用
  5. * @retval      无
  6. */
  7. void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
  8. {
  9.     if ((g_timxchy_cap_sta & 0X80) == 0)        /* 还没成功捕获 */
  10.     {
  11.         if (g_timxchy_cap_sta & 0X40)             /* 捕获到一个下降沿 */
  12.         {
  13.             g_timxchy_cap_sta |= 0X80;             /* 标记成功捕获到一次高电平脉宽 */
  14.             g_timxchy_cap_val =HAL_TIM_ReadCapturedValue(&g_timx_cap_chy_handler,
  15. GTIM_TIMX_CAP_CHY); /* 获取当前的捕获值 */
  16.            TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handler,
  17. GTIM_TIMX_CAP_CHY);/* 一定要先清除原来的设置 */
  18.             TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handler, GTIM_TIMX_CAP_CHY,
  19. TIM_ICPOLARITY_RISING);/* 配置TIM5通道1上升沿捕获 */
  20.         }
  21.         else                                     /* 还未开始,第一次捕获上升沿 */
  22.         {
  23.             g_timxchy_cap_sta = 0;           /* 清空 */
  24.             g_timxchy_cap_val = 0;
  25.             g_timxchy_cap_sta |= 0X40;      /* 标记捕获到了上升沿 */
  26.             __HAL_TIM_DISABLE(&g_timx_cap_chy_handler);  /* 关闭定时器5 */
  27.             __HAL_TIM_SET_COUNTER(&g_timx_cap_chy_handler,0); /* 计数器清零 */
  28.            TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handler,
  29. GTIM_TIMX_CAP_CHY); /* 一定要先清除原来的设置!! */
  30.             TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handler, GTIM_TIMX_CAP_CHY,
  31. TIM_ICPOLARITY_FALLING);     /* 定时器5通道1设置为下降沿捕获 */
  32.             __HAL_TIM_ENABLE(&g_timx_cap_chy_handler);   /* 使能定时器5 */
  33.         }
  34.     }
  35. }
  36. /**
  37. * @brief       定时器更新中断回调函数
  38. * @param        htim:定时器句柄指针
  39. * @note        此函数会被定时器中断函数共同调用的
  40. * @retval      无
  41. */
  42. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  43. {
  44.     if (htim->Instance == GTIM_TIMX_CAP)
  45.     {
  46.         if ((g_timxchy_cap_sta & 0X80) == 0)               /* 还没成功捕获 */
  47.         {
  48.             if (g_timxchy_cap_sta & 0X40)                   /* 已经捕获到高电平了 */
  49.             {
  50.                 if ((g_timxchy_cap_sta & 0X3F) == 0X3F)   /* 高电平太长了 */
  51.                 {
  52.                    TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle,
  53. GTIM_TIMX_CAP_CHY);  /* 一定要先清除原来的设置 */
  54. /* 配置TIM5通道1上升沿捕获 */
  55.                    TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle,
  56. GTIM_TIMX_CAP_CHY,TIM_ICPOLARITY_RISING);
  57.                    g_timxchy_cap_sta |= 0X80;             /* 标记成功捕获了一次 */
  58.                    g_timxchy_cap_val = 0XFFFF;
  59.                 }
  60.                 else                                           /* 累计定时器溢出次数 */
  61.                 {
  62.                    g_timxchy_cap_sta++;
  63.                 }
  64.             }
  65.         }
  66.     }
  67. }
复制代码
现在我们来介绍一下,捕获高电平脉宽的思路:首先,设置TIM5_CH1捕获上升沿,然后等待上升沿中断到来,当捕获到上升沿中断,此时如果g_timxchy_cap_sta的第6位为0,则表示还没有捕获到新的上升沿,就先把g_timxchy_cap_sta、g_timxchy_cap_val和TIM5_CNT寄存器等清零,然后再设置g_timxchy_cap_sta的第6位为1,标记捕获到高电平,最后设置为下降沿捕获,等待下降沿到来。如果等待下降沿到来期间,定时器发生了溢出,就用g_timxchy_cap_sta变量对溢出次数进行计数,当最大溢出次数来到的时候,就强制标记捕获完成,并配置定时器通道上升沿捕获。当下降沿到来的时候,先设置g_timxchy_cap_sta的第7位为1,标记成功捕获一次高电平,然后读取此时的定时器值到g_timxchy_cap_val里面,最后设置为上升沿捕获,回到初始状态。

这样,我们就完成一次高电平捕获了,只要g_timxchy_cap_sta的第7位一直为1,那么就不会进行第二次捕获,我们在main函数处理完捕获数据后,将g_timxchy_cap_sta置零,就可以开启第二次捕获。
在main.c里面编写如下代码:
  1. int main(void)
  2. {
  3.     uint32_t temp = 0;
  4.     uint8_t t = 0;
  5.    sys_cache_enable();                              /* 打开L1-Cache */
  6.    HAL_Init();                                       /* 初始化HAL库 */
  7.    sys_stm32_clock_init(240, 2, 2, 4);          /* 设置时钟, 480Mhz */
  8.    delay_init(480);                                 /* 延时初始化 */
  9.    usart_init(115200);                             /* 串口初始化为115200 */
  10.    led_init();                                       /* 初始化LED */
  11.    key_init();                                       /* 初始化按键 */
  12.    gtim_timx_cap_chy_init(0XFFFF, 240 - 1);    /* 以1Mhz的频率计数 捕获 */
  13.     while (1)
  14.     {
  15.        if (g_timxchy_cap_sta & 0X80)             /* 成功捕获到了一次高电平 */
  16.        {
  17.            temp =g_timxchy_cap_sta & 0X3F;
  18.            temp *= 65536;                          /* 溢出时间总和 */
  19.            temp +=g_timxchy_cap_val;            /* 得到总的高电平时间 */
  20.            printf("HIGH:%dus\r\n", temp);      /* 打印总的高点平时间 */
  21.            g_timxchy_cap_sta = 0;                 /* 开启下一次捕获*/
  22.        }
  23.        t++;
  24.        if (t > 20)           /* 200ms进入一次 */
  25.        {
  26.            t = 0;
  27.            LED0_TOGGLE();    /* LED0闪烁 ,提示程序运行 */
  28.        }
  29.        delay_ms(10);
  30.     }
  31. }
复制代码
先看gtim_timx_cap_chy_init(0XFFFF,240 - 1)这个语句,这两个形参分别设置自动重载寄存器的值为65535,以及预分频器寄存器的值为239。定时器5是32位的计数器,为了通用性,我们只使用16位,所以计数器设置为65535。预分频系数,我们设置为240分频,定时器5的时钟频率是2倍的APB1总线时钟频率,即240MHz,可以得到计数器的计数频率是1MHz,即1us计数一次,所以我们的捕获时间精度是1us。这里可以知道定时器的溢出时间是65536us。

While(1)无限循环通过判断g_timxchy_cap_sta的第7位,来获知有没有成功捕获到一次高电平,如果成功捕获,先计算总的高电平时间,再通过串口传输到电脑。

21.4.4 下载验证
下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,我们再打开串口调试助手,选择对应的串口端口,我这边是COM15,然后按KEY_UP按键,可以看到串口打印的高电平持续时间,如图21.4.4.1所示:
      image009.png
图21.4.4.1 打印捕获到的高电平时间

21.5 通用定时器脉冲计数实验
前面我们介绍了通用定时器的四类时钟源,本小节我们来学习使用通用定时器的外部时钟模式1这类时钟源。

前面的三个通用定时器实验的时钟源都是来自内部时钟 (CK_INT),本实验我们将使用外部时钟模式 1:外部输入引脚 (TIx)作为定时器的时钟源。关于这个外部输入引脚(TIx),我们使用WK_UP按键按下产生的高电平脉冲作为定时器的计数器时钟,每按下一次按键产生一次高电平脉冲,计数器加一。

下面通过框图给大家展示本实验用到定时器内部哪些资源,如下图所示:
image011.png
图21.5.1 脉冲计数实验原理

前面介绍过,外部时钟模式1的外部输入引脚只能是通道1或者通道2对应的IO,通道3或者通道4是不可以的。以通道1输入为例,外部时钟源信号通过通道1输入后,接下来我们用TI1表示该信号。TI1分别要经过滤波器、边沿检测器后,来到TI1FP1,被触发输入选择器选择为触发源,接着来到从模式控制器。从模式选择为外部时钟模式1,这时候外部时钟源信号就会到达时基单元的预分频器,后面就是经过分频后就作为计数器的计数时钟了。这个过程的描述,大家亦可参考前面介绍外部时钟模式1的描述。因为前面已经介绍过,这里只是简单讲一下。

如果大家想时钟源信号的上升沿和下降沿,计数器都计数,可以选择TI1F_ED作为触发输入选择器的触发源。

假设计数器工作在递增计数模式,那么每来一个选择的边沿,计数器就加一。最后,外部时钟源信号的边沿计数个数会保存计数器寄存器中,我们只需要直接读取CNT的值即可。这里是没有考虑定时器溢出的情况,如果定时器溢出还需要对溢出进行处理。比如开启更新中断,定时器溢出后,在更新中断里,对溢出次数进行记录,然后用溢出次数乘以溢出一次计数的个数,再加上CNT现在的值,就可以得到总的计数个数了。在例程中,我们也是这样处理的。

下面开始讲解本实验用到的寄存器配置情况。

21.5.1 TIM2/TIM3/TIM4/TIM5寄存器
通用定时器脉冲计数实验需要用到的寄存器有:TIMx_ARR、TIMx_PSC、TIMx_CCMR1、TIMx_CCER、TIMx_DIER、TIMx_CR1、TIMx_EGR这些寄存器在前面的章节都有提到,在这里只需针对性的介绍。

l  捕获/比较模式寄存器1/2(TIMx_CCMR1/2
该寄存器我们在PWM输出实验时讲解了他作为输出功能的配置,在输入捕获实验学习了输入捕获模式的配置,本小节我们的外部信号也同样要作为输入信号给定时器作为时钟源,所以我们要看输入捕获模式定时器对应功能。WK_UP按键(PA0)对应着定时器2的通道1,这个可以在《STM32H750VBT6.pdf》数据手册62页找到。接下来我们开始配置TIMx_CCMR1寄存器,其描述如图21.5.1.1所示:
      image013.png
图21.5.1.1 TIMx_CCMR1寄存器

用到定时器2的通道1,所以要配置TIM2_CCMR1寄存器的位[7:0],其中CC1S[1:0],这两个位用于CCR1的通道配置,这里我们设置IC1S[1:0]=01,也就是配置IC1映射在TI1上,即CCR1对应TIMx_CH1。
输入捕获1预分频器IC1PSC[1:0],我们是1次高电平脉冲就触发1次计数,所以不用分频选择00即可。

输入捕获1滤波器IC1F[3:0],这个用来设置输入采样频率和数字滤波器长度,关于滤波长度的介绍请看上一个实验。这里,我们不做滤波处理,所以设置IC1F[3:0]=0000,只要采集到上升沿,就触发捕获。

l  捕获/比较使能寄存器(TIMx_ CCER
TIM2/TIM3/TIM4/TIM5的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如图21.5.1.2所示:
    image015.png   
图21.5.1.2 TIMx_CCER寄存器

我们要用到这个寄存器的最低2位,CC1E和CC1P位。要使能输入捕获,必须设置CC1E=1,而CC1P则根据自己的需要来配置。我们这里是保留默认设置值0,即高电平触发捕获。

l  从模式控制寄存器(TIMx_ SMCR
TIM2/TIM3/TIM4/TIM5的从模式控制寄存器,该寄存器用于配置从模式,以及定时器的触发源相关的设置。TIMx_SMCR寄存器描述如图21.5.1.3所示:
    image017.png   
图21.5.1.3 TIMx_SMCR寄存器

因为我们要让外部引脚脉冲信号作为定时器的时钟源,所以位[2:0]和位16组合的SMS[0:3],我们设置的值是0111,即外部时钟模式1。位[6:4] 和位[21:20]组合的TS[0:5]是触发选择设置,TIMx_CH1对应TI1FP1,TIMx_CH2则对应TI2FP2,我们是定时器通道1,所以需要配置的值为00101。ETF[3:0]和ETPS[1:0]分别是外部触发滤波器和外部触发预分频器,我们没有用到。

接下来我们再看看DMA/中断使能寄存器:TIMx_DIER,该寄存器的各位描述见图21.2.1.3(在21.2.1小节)。本本实验,我们需要用到定时器更新中断,在中断服务函数中累加定时器溢出的次数,所以需要使能定时器的更新中断,即UIE置1。

控制寄存器1:TIMx_CR1,我们只用到了它的最低位,也就是用来使能定时器的。

21.5.2 硬件设计
1. 例程功能
使用TIM2_CH1做输入捕获,我们将捕获PA0上的高电平脉宽,并对脉宽进行计数,通过串口打印出来。大家可以通过按WK_UP按键,输入高电平脉冲,通过按KEY0重设当前计数。LED0闪烁,提示程序运行。

2. 硬件资源
1)RGB灯
    RED : LED0 - PB4
3)  独立按键:
KEY0  - PA1
WK_UP- PA0
3)定时器2,使用TIM2通道1,PA0复用为TIM2_CH1。

3. 原理图
定时器属于STM32H750的内部资源,只需要软件设置好即可正常工作。我们借助WK_UP做输入脉冲源,捕获PA0上的高电平脉宽,然后对脉宽进行计数并通过串口上位机打印出来。还可以通过按KEY0重设当前计数。

21.5.3 程序设计
21.5.3.1 定时器的HAL库驱动
定时器在HAL库中的驱动代码在前面已经介绍了部分,这里我们针对定时器从模式介绍HAL_TIM_SlaveConfigSynchronization函数。

1. HAL_TIM_SlaveConfigSynchronization函数
该函数是HAL_TIM_SlaveConfigSynchro函数的宏定义,真正的函数定义是后者,其定义如下:
  1. HAL_StatusTypeDefHAL_TIM_SlaveConfigSynchro(TIM_HandleTypeDef *htim,
  2. TIM_SlaveConfigTypeDef *sSlaveConfig);
复制代码
l  函数描述:
该函数用于配置定时器的从模式选择、输入触发源等参数。

l  函数形参:
形参1是TIM_HandleTypeDef结构体类型指针变量,用于配置定时器基本参数。
形参2是TIM_SlaveConfigTypeDef结构体类型指针变量,用于配置定时器的从模式。
重点了解一下TIM_SlaveConfigTypeDef结构体指针类型,其定义如下:
  1. typedef struct
  2. {
  3.   uint32_t SlaveMode;            /* 从模式选择 */
  4.   uint32_t InputTrigger;         /* 输入触发源选择 */
  5.   uint32_t TriggerPolarity;     /* 输入触发极性 */
  6.   uint32_t TriggerPrescaler;    /* 输入触发预分频 */
  7.   uint32_t TriggerFilter;       /* 输入滤波器设置 */
  8. } TIM_SlaveConfigTypeDef;
复制代码
l  函数返回值:
HAL_StatusTypeDef枚举类型的值。

定时器从模式脉冲计数配置步骤
1)开启TIMx和输入通道的GPIO时钟,配置该IO口的复用功能输入
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器2通道1,对应IO是PA0,它们的时钟开启方法如下:
  1. __HAL_RCC_TIM2_CLK_ENABLE();             /* 使能定时器2 */
  2. __HAL_RCC_GPIOA_CLK_ENABLE();            /* 开启GPIOA时钟 */
复制代码
       IO口复用功能是通过函数HAL_GPIO_Init来配置的。

2)初始化TIMx,设置TIMx的ARR和PSC等参数
使用定时器的输入捕获功能时,我们调用的是HAL_TIM_IC_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_IC_MspInit函数,我们可以通过后者存放定时器和GPIO时钟使能、GPIO初始化、中断使能和优先级设置等代码。

3)选择从模式:外部触发模式1
TIMx_SMCR寄存器控制着定时器的从模式,包括模式选择,触发源选择、极性和输入预分频等。HAL库是通过HAL_TIM_SlaveConfigSynchro函数来初始化定时器从模式配置参数的。

4)设置定时器为输入捕获模式,开启输入捕获
在HAL库中,定时器的输入捕获模式是通过HAL_TIM_IC_ConfigChannel函数来设置定时器某个通道为输入捕获通道,包括映射关系,输入滤波和输入分频等。

5)使能定时器更新中断,开启捕获功能,配置定时器中断优先级
通过__HAL_TIM_ENABLE_IT函数使能定时器更新中断。
通过HAL_TIM_IC_Start函数使能定时器并开启捕获功能。
通过HAL_NVIC_EnableIRQ函数使能定时器中断。
通过HAL_NVIC_SetPriority函数设置中断优先级。

6)编写中断服务函数
定时器中断服务函数为:TIMx_IRQHandler等,当发生中断的时候,程序就会执行中断服务函数。HAL库提供了一个定时器中断公共处理函数HAL_TIM_IRQHandler,该函数会根据中断类型调用相关的中断回调函数。用户根据自己的需要重定义这些中断回调函数来处理中断程序。本实验我们不使用HAL库的中断回调机制,而是把中断程序写在定时器中断服务函数里。详见本实验例程源码。

21.5.3.2 程序流程图
image020.png

图21.5.3.2.1通用定时器脉冲计数实验程序流程图

21.5.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。通用定时器驱动源码包括两个文件:gtim.c和gtim.h。
首先看gtim.h头文件的几个宏定义:
  1. /*****************************************************************************/
  2. /* TIMX 输入计数定义
  3. * 这里的输入计数使用定时器TIM2_CH1,捕获WK_UP按键的输入
  4. * 默认是针对TIM2~TIM5, TIM12~TIM17.只有CH1和CH2通道可以用做输入计数,CH3/CH4不支持!
  5. * 注意: 修改这9个宏定义,可以支持TIM1~TIM17任意一个定时器,CH1/CH2对应IO口做输入计数
  6. * 特别要注意:默认用的PA0,设置的是下拉输入!如果改其他IO,对应的上下拉方式/AF功能等也得改!
  7. */
  8. #define GTIM_TIMX_CNT_CHY_GPIO_PORT      GPIOA
  9. #define GTIM_TIMX_CNT_CHY_GPIO_PIN       GPIO_PIN_0
  10. #define GTIM_TIMX_CNT_CHY_GPIO_AF        GPIO_AF1_TIM2           /* AF功能选择 */
  11. #defineGTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE()   
  12. do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)    /* PA口时钟使能 */
  13. #define GTIM_TIMX_CNT                       TIM2
  14. #define GTIM_TIMX_CNT_IRQn                 TIM2_IRQn
  15. #define GTIM_TIMX_CNT_IRQHandler          TIM2_IRQHandler
  16. #define GTIM_TIMX_CNT_CHY                  TIM_CHANNEL_1    /* 通道Y,  1<= Y <=2 */
  17. #define GTIM_TIMX_CNT_CHY_CLK_ENABLE()     
  18.     do{__HAL_RCC_TIM2_CLK_ENABLE(); }while(0) /* TIMX 时钟使能 */
  19. /*****************************************************************************/
复制代码
可以把上面的宏定义分成两部分,第一部分是定时器2输入通道1对应的IO口的宏定义,第二部分则是定时器2输入通道1的相应宏定义。需要注意的点是:只有CH1和CH2通道可以用做输入计数,CH3/CH4不支持!
gtim.h头文件就添加了这部分的程序,下面看gtim.c的程序,首先是通用定时器脉冲计数初始化函数,其定义如下:
  1. /**
  2. *@brief       通用定时器TIMX 通道Y 脉冲计数 初始化函数
  3. *@note
  4. *             本函数选择通用定时器的时钟选择: 外部时钟源模式1(SMS[2:0] = 111)
  5. *             计数器时钟源就来自 TIMX_CH1/CH2, 可以实现外部脉冲计数(脉冲接入CH1/CH2)
  6. *
  7. *             时钟分频数 = psc, 一般设置为0, 表示每一个时钟都会计数一次, 以提高精度.
  8. *             通过读取CNT和溢出次数, 经过简单计算, 可以得到当前的计数值, 从而实现脉冲计数
  9. *
  10. *@param       arr: 自动重装值
  11. *@retval      无
  12. */
  13. voidgtim_timx_cnt_chy_init(uint16_t psc)
  14. {
  15.    GPIO_InitTypeDef gpio_init_struct;
  16.    TIM_SlaveConfigTypeDef tim_slave_config = {0};
  17.    GTIM_TIMX_CNT_CHY_CLK_ENABLE();                          /* 使能TIMx时钟 */
  18.    GTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE();                     /* 开启GPIOA时钟 */
  19.    
  20.    g_timx_cnt_chy_handle.Instance = GTIM_TIMX_CNT;       /* 定时器x */
  21.    g_timx_cnt_chy_handle.Init.Prescaler = psc;            /* 定时器分频 */
  22.    g_timx_cnt_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;/* 递增计数模式 */
  23.    g_timx_cnt_chy_handle.Init.Period = 65535;             /* 自动重装载值 */
  24.    HAL_TIM_IC_Init(&g_timx_cnt_chy_handle);
  25.    gpio_init_struct.Pin = GTIM_TIMX_CNT_CHY_GPIO_PIN;   /* 输入捕获的GPIO口 */
  26.    gpio_init_struct.Mode = GPIO_MODE_AF_PP;               /* 复用推挽输出 */
  27.    gpio_init_struct.Pull = GPIO_PULLDOWN;                 /* 下拉 */
  28.    gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
  29.    gpio_init_struct.Alternate = GTIM_TIMX_CNT_CHY_GPIO_AF; /* 复用 */
  30.    HAL_GPIO_Init(GTIM_TIMX_CNT_CHY_GPIO_PORT, &gpio_init_struct);
  31.     /* 从模式:外部触发模式1 */
  32.    tim_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;/* 外部触发模式1 */
  33.    tim_slave_config.InputTrigger = TIM_TS_TI1FP1;/* TI1FP1作为触发输入源 */
  34.    tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;/* 上升沿 */
  35.    tim_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;/* 不分频 */
  36.    tim_slave_config.TriggerFilter = 0x0;          /* 滤波:本例中不需要任何滤波 */
  37. HAL_TIM_SlaveConfigSynchronization(&g_timx_cnt_chy_handle,
  38. &tim_slave_config);
  39. /* 设置中断优先级,抢占优先级1,子优先级3 */
  40.    HAL_NVIC_SetPriority(GTIM_TIMX_CNT_IRQn, 1, 3);                       
  41.    HAL_NVIC_EnableIRQ(GTIM_TIMX_CNT_IRQn);          /* 开启ITMx中断 */
  42.    __HAL_TIM_ENABLE_IT(&g_timx_cnt_chy_handle, TIM_IT_UPDATE); /* 使能更新中断 */
  43.    HAL_TIM_IC_Start(&g_timx_cnt_chy_handle, GTIM_TIMX_CNT_CHY);/* 使能通道输入 */
  44. }
复制代码
gtim_timx_cnt_chy_init函数包含了输入通道对应IO的初始代码、NVIC、使能时钟、定时器基础工作参数和从模式配置的所有代码。下面来看看该函数的代码内容。

第一部分使能定时器和GPIO的时钟。
第二部分调用HAL_TIM_IC_Init函数初始化定时器的基础工作参数,如:ARR和PSC等。
第三部分是定时器输入通道对应的IO的初始化。
第四部分调用HAL_TIM_SlaveConfigSynchronization函数配置从模式选择、输入捕获通道映射关系、捕获边沿和滤波等。
第五部分是NVIC的初始化,配置抢占优先级、响应优先级和开启NVIC定时器中断。
最后是使能更新中断和使能通道输入。

我们在通用定时器输入捕获实验使用了HAL_TIM_IC_MspInit函数,为了方便代码的管理和移植性等,这里就不再使用这个函数了。当一个项目使用到多个定时器时,也同样建议大家不要使用HAL库的回调机制,真的不方便。至于前面为什么要用HAL_TIM_IC_MspInit函数,只是为了让大家知道HAL库回调机制的使用方法。大家可以根据自己的情况权衡利弊,决定是否使用HAL库的回调机制。
下面先看中断服务函数,在基本定时器中断实验中,我们知道中断逻辑程序的逻辑代码是放在更新中断回调函数里面的,这是HAL库回调机制标准的做法。因为我们在通用定时器输入捕获实验中使用过HAL_TIM_PeriodElapsedCallback更新中断回调函数,所以本实验我们不使用HAL库这套回调机制,而是直接将中断处理写在定时器中断服务函数中,定时器中断服务函数定义如下:
  1. /**
  2. * @brief       通用定时器TIMX 脉冲计数 更新中断服务函数
  3. * @param       无
  4. * @retval      无
  5. */
  6. void GTIM_TIMX_CNT_IRQHandler(void)
  7. {
  8.     /* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
  9.     if(__HAL_TIM_GET_FLAG(&g_timx_cnt_chy_handle, TIM_FLAG_UPDATE) != RESET)
  10.     {
  11.         g_timxchy_cnt_ofcnt++;          /* 累计溢出次数 */
  12.     }
  13.     __HAL_TIM_CLEAR_IT(&g_timx_cnt_chy_handle, TIM_IT_UPDATE);
  14. }
复制代码
在函数中,使用__HAL_TIM_GET_FLAG函数宏获取更新更新中断标志位,然后判断是否发生更新中断,如果发生了更新中断,表示脉冲计数的个数等于ARR寄存器的值,那么我们让g_timxchy_cnt_ofcnt变量++,累计定时器溢出次数。最后调用__HAL_TIM_CLEAR_IT函数宏清除更新中断标志位。这样就完成一次对更新中断的处理。
再来介绍两个自定义的功能函数,第一个是获取当前计数值函数,其定义如下:
  1. /**
  2. * @brief       通用定时器TIMX 通道Y 获取当前计数值
  3. * @param       无
  4. * @retval      当前计数值
  5. */
  6. uint32_t gtim_timx_cnt_chy_get_count(void)
  7. {
  8.     uint32_t count = 0;
  9.     count =g_timxchy_cnt_ofcnt * 65536;/* 计算溢出次数对应的计数值 */
  10.     count +=__HAL_TIM_GET_COUNTER(&g_timx_cnt_chy_handler);/* 加上当前CNT的值 */
  11.     return count;
  12. }
复制代码
该函数先是计算定时器溢出次数对应的计数个数,因为定时器每溢出一次的计数个数是65536,所以这里用g_timxchy_cnt_ofcnt乘以65536,就可以得到溢出计数的个数,前提是ARR寄存器的值得设置为65535。然后再加上定时器计数器当前的值,计数器当前的值通过调用__HAL_TIM_GET_COUNTER函数宏可以获取。函数返回值是脉冲计数的总个数。
第二个自定义功能函数是重启计数器函数,其定义如下:
  1. /**
  2. * @brief       通用定时器TIMX 通道Y 重启计数器
  3. * @param       无
  4. * @retval      当前计数值
  5. */
  6. void gtim_timx_cnt_chy_restart(void)
  7. {
  8.     __HAL_TIM_DISABLE(&g_timx_cnt_chy_handler);              /* 关闭定时器TIMX */
  9.     g_timxchy_cnt_ofcnt = 0;                                     /* 累加器清零 */
  10.     __HAL_TIM_SET_COUNTER(&g_timx_cnt_chy_handler, 0);      /* 计数器清零 */
  11.     __HAL_TIM_ENABLE(&g_timx_cnt_chy_handler);               /* 使能定时器TIMX */
  12. }
复制代码
该函数先关闭定时器,然后做清零操作,包括:记录溢出次数全局变量g_timxchy_cnt_ofcnt和定时器计数器的值,最后使能定时器重新计数。
通用定时器脉冲计数实验的整体驱动和逻辑程序还算比较容易理解,下面看看main.c里面编写的代码:
  1. int main(void)
  2. {
  3.     uint32_t curcnt = 0;
  4.     uint32_t oldcnt = 0;
  5.     uint8_t key = 0;
  6.     uint8_t t = 0;
  7.    sys_cache_enable();                        /* 打开L1-Cache */
  8.    HAL_Init();                                  /* 初始化HAL库 */
  9.    sys_stm32_clock_init(240, 2, 2, 4);     /* 设置时钟, 480Mhz */
  10.    delay_init(480);                            /* 延时初始化 */
  11.    usart_init(115200);                        /* 串口初始化为115200 */
  12.    led_init();                                 /* 初始化LED */
  13.    key_init();                                 /* 初始化按键 */
  14.    gtim_timx_cnt_chy_init(0);               /* 定时器计数初始化, 不分频 */
  15.    gtim_timx_cnt_chy_restart();             /* 重启计数 */
  16.     while (1)
  17.     {
  18.        key = key_scan(0);       /* 扫描按键 */
  19.        if (key == KEY0_PRES)   /* KEY0按键按下,重启计数 */
  20.        {
  21.            gtim_timx_cnt_chy_restart();           /* 重新启动计数 */
  22.        }
  23.         curcnt = gtim_timx_cnt_chy_get_count(); /* 获取计数值 */
  24.        if (oldcnt != curcnt)
  25.        {
  26.            oldcnt = curcnt;
  27.            printf("CNT:%d\r\n", oldcnt);         /* 打印脉冲个数 */
  28.        }
  29.        t++;
  30.        if (t > 20)           /* 200ms进入一次 */
  31.        {
  32.            t = 0;
  33.            LED0_TOGGLE();    /* LED0闪烁 ,提示程序运行 */
  34.        }
  35.        delay_ms(10);
  36.     }
  37. }
复制代码
调用定时器初始化函数gtim_timx_cnt_chy_init(0),形参是0,表示设置预分频器寄存器的值为0,即表示不分频。如果形参设置为1,就是2分频,这种情况,要按按键WK_UP两次才会计数一次,大家不妨试试。该函数内部已经设置自动重载寄存器的值为65535,所以在不分频的情况下,定时器发生一次更新中断,表示脉冲计数了65536次。

21.5.4 下载验证
下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,我们再打开串口调试助手,然后每按KEY_UP按键一次,就可以看到串口打印的高电平脉冲次数。如果按KEY0按键,就会重设当前计数,从0开始计数,如图21.5.4.1所示:
image021.png
图21.5.4.1 打印高电平脉冲次数
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-24 20:25

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表