【正点原子STM32连载】第十九章 通用定时器输入捕获实验 摘自【正点原子】APM32F407最小系统板使用指南

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第十九章 通用定时器输入捕获实验

本章将介绍使用APM32F407通用定时器的输入捕获功能。通过本章的学习,读者将学习到通用定时器输入捕获的使用。
本章分为如下几个小节:
19.1 硬件设计
19.2 程序设计
19.3 下载验证

19.1 硬件设计

19.1.1 例程功能

  1. 捕获KEY_UP按键按下拉高PA0的脉冲时间,并通过串口输出
  2. LED0闪烁,指示程序正在运行
    19.1.2 硬件资源
  3. LED
    LED0 - PF9
  4. 按键
    KEY_UP - PA0
  5. 定时器5
    通道1 - PA0
  6. USART1(PA9、PA10连接至板载USB转串口芯片上)
    19.1.3 原理图
    本章实验使用的定时器5为APM32F407的片上资源,因此没有对应的连接原理图。
    19.2 程序设计
    19.2.1 Geehy标准库的TMR驱动
    本章实验将使用TMR5的通道1捕获KEY_UP按键被按下的高电平脉冲宽度,因此除了像第十七章实验配置定时器的基本参数外,还需要配置通用定时器的输入捕获通道,具体的步骤如下:
    ①:配置TMR5的自动重装载值和预分频器数值等参数
    ②:配置输入捕获通道1
    ③:使能TMR5的捕获比较通道1中断和更新中断
    ④:使能TMR5中断,并配置其相关的中断优先级
    ⑤:使能TMR5
    在Geehy标准库中对应的驱动函数如下:
    ①:配置TMR
    请见第16.2.1小节中配置TMR的相关内容。
    ②:配置输入捕获通道
    该函数用于配置TMR的任意输入捕获通道,其函数原型如下所示:
    void TMR_ConfigIC(TMR_T* tmr, TMR_ICConfig_T* ICConfig);
    该函数的形参描述,如下表所示:
    【正点原子STM32连载】第十九章 通用定时器输入捕获实验 摘自【正点原子】APM32F407最小系统板使用指南_第1张图片

该函数的返回值描述,如下表所示:
返回值 描述
无 无
表19.2.1.2 函数TMR_ConfigIC()返回值描述
该函数使用TMR_ICConfig_T类型的结构体变量传入TMR输入捕获通道的配置参数,该结构体的定义如下所示:

typedef enum
{
    TMR_CHANNEL_1					= 0x0000,	/* 定时器通道1*/
    TMR_CHANNEL_2					= 0x0004,	/* 定时器通道2*/
    TMR_CHANNEL_3					= 0x0008,	/* 定时器通道3*/
    TMR_CHANNEL_4					= 0x000C	/* 定时器通道4*/
} TMR_CHANNEL_T;

typedef enum
{
    TMR_IC_POLARITY_RISING			= 0x00,		/* 捕获上升沿 */
    TMR_IC_POLARITY_FALLING			= 0x02,		/* 捕获下降沿 */
    TMR_IC_POLARITY_BOTHEDGE		= 0x0A		/* 捕获双边沿 */
} TMR_IC_POLARITY_T;

typedef enum
{
    TMR_IC_SELECTION_DIRECT_TI		= 0x01,		/* 输入捕获映射在TI1上 */
    TMR_IC_SELECTION_INDIRECT_TI	= 0x02,		/* 输入捕获映射在TI2上 */
    TMR_IC_SELECTION_TRC			= 0x03		/* 输入捕获映射在TRC上 */
} TMR_IC_SELECTION_T;

typedef enum
{
    TMR_IC_PSC_1,								/* 不分频 */
    TMR_IC_PSC_2,								/* 每2个事件触发1次捕获 */
    TMR_IC_PSC_4,								/* 每4个事件触发1次捕获 */
    TMR_IC_PSC_8									/* 每8个事件触发1次捕获 */
} TMR_IC_PSC_T;

typedef struct
{
    TMR_CHANNEL_T		channel;	/* 定时器通道 */
    TMR_IC_POLARITY_T	polarity;	/* 输入捕获极性 */
    TMR_IC_SELECTION_T	selection;	/* 输入捕获映射 */
    TMR_IC_PSC_T			prescaler;	/* 输入捕获通道预分频因子 */
    uint16_t				filter;		/* 输入捕获通道滤波器 */
} TMR_ICConfig_T;

该函数的使用示例,如下所示:

#include "apm32f4xx.h"
#include "apm32f4xx_tmr.h"

void example_fun(void)
{
    TMR_ICConfig_T tmr_ic_init_struct;

    /* 配置TMR5输入捕获通道1 */
    tmr_ic_init_struct.channel		= TMR_CHANNEL_1;
    tmr_ic_init_struct.polarity		= TMR_IC_POLARITY_RISING;
    tmr_ic_init_struct.selection	= TMR_IC_SELECTION_DIRECT_TI;
    tmr_ic_init_struct.prescaler	= TMR_IC_PSC_1;
    tmr_ic_init_struct.filter		= 0;
    TMR_ConfigIC(GTMR_TMRX_CAP, &tmr_ic_init_struct);
}

③:使能TMR指定中断
请见第16.2.1小节中使能TMR指定中断的相关内容。
④:配置TMR中断
请见第12.2.3小节中配置中断的相关内容。
⑤:使能TMR
请见第16.2.1小节中使能TMR的相关内容。
19.2.2 通用定时器驱动
本章实验的通用定时器驱动主要负责向应用层提供通用定时器的初始化函数,并实现通用定时器的中断回调函数。本章实验中,通用定时器的驱动代码包括gtmr.c和gtmr.h两个文件。
通用定时器驱动中,对TMR、GPIO的相关宏定义,如下所示:

#define GTMR_TMRX_CAP				TMR5
#define GTMR_TMRX_CAP_IRQn			TMR5_IRQn
#define GTMR_TMRX_CAP_IRQHandler	TMR5_IRQHandler
#define GTMR_TMRX_CAP_CHY			TMR_CHANNEL_1
#define GTMR_TMRX_CAP_CLK_ENABLE()							\
    do {														\
    		RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR5);	\
    } while (0)

#define GTMR_TMRX_CAP_CHY_GPIO_PORT		GPIOA
#define GTMR_TMRX_CAP_CHY_GPIO_PIN			GPIO_PIN_0
#define GTMR_TMRX_CAP_CHY_GPIO_PIN_SOURCE	GPIO_PIN_SOURCE_0
#define GTMR_TMRX_CAP_CHY_GPIO_AF			GPIO_AF_TMR5
#define GTMR_TMRX_CAP_CHY_GPIO_CLK_ENABLE()				\
    do {														\
    		RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);	\
    } while (0)
通用定时器驱动中TMR5的初始化函数,如下所示:
/**
 * @brief	初始化通用定时器输入捕获
 * @note	当APB1PSC!=1时,定时器的时钟频率为APB1时钟的2倍
 * 			因此定时器的时钟频率为84MHz
 * 			定时器溢出时间计算方法:Tout = ((arr + 1) * (psc + 1)) / TMRxCLK
 * 			TMRxCLK=定时器时钟频率,单位MHz
 * @param	arr: 自动重装载值
 * @param	psc: 预分频器数值
 * @retval	无
 */
void gtmr_tmrx_cap_chy_init(uint16_t arr, uint16_t psc)
{
    GPIO_Config_T gpio_init_struct;
    TMR_BaseConfig_T tmr_init_struct;
    TMR_ICConfig_T tmr_ic_init_struct;
    
    /* 使能时钟 */
    GTMR_TMRX_CAP_CLK_ENABLE();							/* 使能通用定时器时钟 */
    GTMR_TMRX_CAP_CHY_GPIO_CLK_ENABLE();				/* 使能输入捕获引脚端口时钟 */
    
    /* 配置输入捕获引脚 */
    gpio_init_struct.pin	= GTMR_TMRX_CAP_CHY_GPIO_PIN;	/* 输入捕获引脚 */
    gpio_init_struct.mode	= GPIO_MODE_AF;					/* 复用功能模式 */
    gpio_init_struct.speed	= GPIO_SPEED_100MHz;			/* 高速 */
    gpio_init_struct.otype	= GPIO_OTYPE_PP;				/* 推挽输出 */
    gpio_init_struct.pupd	= GPIO_PUPD_DOWN;				/* 下拉 */
    GPIO_Config(GTMR_TMRX_CAP_CHY_GPIO_PORT, &gpio_init_struct);
    /* 配置引脚复用功能 */
    GPIO_ConfigPinAF(	GTMR_TMRX_CAP_CHY_GPIO_PORT,
    						GTMR_TMRX_CAP_CHY_GPIO_PIN_SOURCE,
    						GTMR_TMRX_CAP_CHY_GPIO_AF);
    
    /* 配置通用定时器 */
    tmr_init_struct.countMode		= TMR_COUNTER_MODE_UP;	/* 向上计数 */
    tmr_init_struct.clockDivision	= TMR_CLOCK_DIV_1;		/* 时钟分频系数 */
    tmr_init_struct.period			= arr;					/* 自动重装载值 */
    tmr_init_struct.division		= psc;					/* 预分频器数值 */
    TMR_ConfigTimeBase(GTMR_TMRX_CAP, &tmr_init_struct);	/* 配置通用定时器 */
    
    /* 配置输入捕获通道 */
    tmr_ic_init_struct.channel		= GTMR_TMRX_CAP_CHY;	/* 输入捕获通道 */
    tmr_ic_init_struct.polarity		= TMR_IC_POLARITY_RISING;/* 捕获上升沿 */
    tmr_ic_init_struct.selection	= TMR_IC_SELECTION_DIRECT_TI;/* 输入捕获映射 */
    tmr_ic_init_struct.prescaler	= TMR_IC_PSC_1;			/* 输入信号预分频系数 */
    tmr_ic_init_struct.filter		= 0;					/* 滤波器 */
    TMR_ConfigIC(GTMR_TMRX_CAP, &tmr_ic_init_struct);		/* 配置输入捕获通道 */
    
    /* 使能通用定时器及其相关中断 */
    NVIC_EnableIRQRequest(GTMR_TMRX_CAP_IRQn, 1, 0);	/* 使能中断 */
    TMR_EnableInterrupt(GTMR_TMRX_CAP, TMR_INT_CC1);	/* 使能输入捕获通道1中断 */
    TMR_EnableInterrupt(GTMR_TMRX_CAP, TMR_INT_UPDATE);	/* 使能更新中断 */
    TMR_Enable(GTMR_TMRX_CAP);							/* 使能通用定时器 */
}

从TMR5的初始化代码中可以看到,不仅配置了TMR5的自动重装载值和预分频器系数等基本参数,还配置了TMR5的输入捕获通道1,并开启了TMR5的输入捕获通道1中断和TMR5更新中断,由于需要使用GPIO引脚来获取外部信号,因此对应的GPIO引脚也配置了复用功能。
通用定时器驱动中TMR5的中断回调函数,如下所示:

/**
 * @brief	通用定时器中断服务函数
 * @param	无
 * @retval	无
 */
void GTMR_TMRX_CAP_IRQHandler(void)
{
    /* 捕获比较通道1中断 */
    if (TMR_ReadIntFlag(GTMR_TMRX_CAP, TMR_INT_CC1) == SET)
    {
    		if ((g_tmrxchy_cap_sta & 0x80) == 0)			/* 高电平捕获未完成 */
    		{
    			if ((g_tmrxchy_cap_sta & 0x40) == 0)		/* 第一次捕获到上升沿 */
    			{
    				g_tmrxchy_cap_sta = 0;
    				g_tmrxchy_cap_val = 0;
    				g_tmrxchy_cap_sta |= 0x40;				/* 标记已经捕获到上升沿 */
    				TMR_Disable(GTMR_TMRX_CAP);
    				/* 清空计数值,准备计数高电平时间 */
    				TMR_ConfigCounter(GTMR_TMRX_CAP, 0);
    				/* 配置为下降沿捕获 */
    				TMR_ConfigOC1Polarity(GTMR_TMRX_CAP, TMR_OC_POLARITY_LOW);
    				TMR_Enable(GTMR_TMRX_CAP);
    			}
    			else										/* 捕获到下降沿 */
    			{
    				g_tmrxchy_cap_sta |= 0x80;				/* 标记高电平捕获完成 */
    				/* 获取当前的捕获值 */
    				g_tmrxchy_cap_val = TMR_ReadCaputer1(GTMR_TMRX_CAP);
    				/* 重新配置为上升沿捕获 */
    				TMR_ConfigOC1Polarity(GTMR_TMRX_CAP, TMR_OC_POLARITY_HIGH);
    			}
    		}
    		
    		TMR_ClearIntFlag(GTMR_TMRX_CAP, TMR_INT_CC1);	/* 清除捕获比较通道1中断 */
    }
    
    /* 更新中断 */
    if (TMR_ReadIntFlag(GTMR_TMRX_CAP, TMR_INT_UPDATE) == SET)
    {
    		if ((g_tmrxchy_cap_sta & 0x80) == 0)			/* 高电平捕获未完成 */
    		{
    			if ((g_tmrxchy_cap_sta & 0x40) == 0x40)	/* 已经捕获到上升沿 */
    			{
    				if ((g_tmrxchy_cap_sta & 0x3F) == 0x3F)/* 溢出次数超出最大值 */
    				{
    					g_tmrxchy_cap_sta |= 0x80;			/* 强行标记已完成捕获 */
    					g_tmrxchy_cap_val = 0xFFFF;			/* 设为最大值 */
    					/* 重新配置为上升沿捕获 */
    					TMR_ConfigOC1Polarity(GTMR_TMRX_CAP, TMR_OC_POLARITY_HIGH);
    				}
    				else
    				{
    					g_tmrxchy_cap_sta++;	/* 记录定时器自捕获到上升沿后的溢出次数 */
    				}
    			}
    		}
    		
    		TMR_ClearIntFlag(GTMR_TMRX_CAP, TMR_INT_UPDATE);/* 清除定时器更新中断 */
    }
}

从上面的代码中可以看出,在TMR5的中断回调函数中会依次捕获输入信号的上升沿和下降沿,并在第一次捕获到输入信号上升沿的时候清空TMR5的计数值,随后在捕获到信号下降沿的时候读取TMR5的计数值,该值就是该输入信号高电平脉宽对应的计数值,只要根据TMR5的计数频率,就能够计算出输入信号高电平脉宽的时间。TMR5的更新中断是用于处理计数溢出的。
19.2.3 实验应用代码
本章实验的应用代码,如下所示:

int main(void)
{
    uint32_t temp = 0;
    uint8_t t = 0;
    
    NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_3);	/* 设置中断优先级分组为组3 */
    sys_apm32_clock_init(336, 8, 2, 7);					/* 配置系统时钟 */
    delay_init(168);										/* 初始化延时功能 */
    usart_init(115200);									/* 初始化串口 */
    led_init();											/* 初始化LED */
    gtmr_tmrx_cap_chy_init(0xFFFF, 84 - 1);				/* 初始化通用定时器输入捕获 */
    
    while (1)
    {
    		if (g_tmrxchy_cap_sta & 0x80)			/* 成功捕获到了一次高电平 */
    		{
    			temp = g_tmrxchy_cap_sta & 0x3F;	/* 获取定时器溢出次数 */
    			temp *= 0xFFFF;						/* 计算溢出时间总和 */
    			temp += g_tmrxchy_cap_val;			/* 计算总的高电平时间 */
    			printf("HIGH:%d us\r\n", temp);		/* 打印总的高点平时间 */
    			g_tmrxchy_cap_sta = 0;				/* 开启下一次捕获 */
    		}
    		
    		t++;
    		if (t > 20)
    		{
    			t = 0;
    			LED0_TOGGLE();
    		}
    		
    		delay_ms(10);
    }
}

从上面的代码中可以看到,TMR5的预分频系数被配置为(84-1),同时因为TMR5的时钟频率为84MHz,因此TMR5的计数频率为1MHz,因此在TMR5成功捕获到外部输入信号的高电平后,可以直接计算出捕获到高电平的脉宽时间。
19.3 下载验证
在完成编译和烧录操作后,短暂按下并抬起KEY_UP按键,可以通过串口调试助手观察到捕获到的KEY_UP按键被按下的时间。

你可能感兴趣的:(stm32,嵌入式硬件,单片机)