1)实验平台:正点原子APM32E103最小系统板
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban
本章将介绍使用APM32E103通用定时器的输入捕获功能。通过本章的学习,读者将学习到通用定时器输入捕获的使用。
本章分为如下几个小节:
19.1 硬件设计
19.2 程序设计
19.3 下载验证
19.1 硬件设计
19.1.1 例程功能
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 "apm32e10x.h"
#include "apm32e10x_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_CHY_GPIO_PORT GPIOA
#define GTMR_TMRX_CAP_CHY_GPIO_PIN GPIO_PIN_0
#define GTMR_TMRX_CAP_CHY_GPIO_CLK_ENABLE() do{ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA); }while(0)
/* 通用定时器定义 */
#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_CHY_CLK_ENABLE() do{ RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR5); }while(0)
通用定时器驱动中TMR5的初始化函数,如下所示:
/**
* @brief 初始化通用定时器通道捕获
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为60M,所以定时器时钟 = 120Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位: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_CHY_GPIO_CLK_ENABLE(); /* 使能GPIOB时钟 */
GTMR_TMRX_CAP_CHY_CLK_ENABLE(); /* 使能通用定时器时钟 */
TMR_ClearIntFlag(GTMR_TMRX_CAP,
TMR_INT_UPDATE | TMR_INT_CC1); /* 清除中断标志位 */
/* 配置PWM输入引脚 */
gpio_init_struct.pin = GTMR_TMRX_CAP_CHY_GPIO_PIN; /* 初始化IO口 */
gpio_init_struct.mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
GPIO_Config(GTMR_TMRX_CAP_CHY_GPIO_PORT,
&gpio_init_struct); /* 初始化KEY_UP引脚 */
/* 配置通用定时器 */
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);/* 初始化通用定时器 */
/* 配置PWM输入捕获 */
tmr_ic_init_struct.channel = TMR_CHANNEL_1; /* 通道选择CHANNEL_1 */
/* 上升沿捕获 */
tmr_ic_init_struct.polarity = TMR_IC_POLARITY_RISING;
/* 映射到TI1上 */
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); /* 使能中断 */
if (GTMR_TMRX_CAP_CHY == TMR_CHANNEL_1)
{
TMR_EnableInterrupt(GTMR_TMRX_CAP, TMR_INT_CC1);/* 使能输入捕获通道1中断 */
}
else if (GTMR_TMRX_CAP_CHY == TMR_CHANNEL_2)
{
TMR_EnableInterrupt(GTMR_TMRX_CAP, TMR_INT_CC2);/* 使能输入捕获通道2中断 */
}
else if (GTMR_TMRX_CAP_CHY == TMR_CHANNEL_3)
{
TMR_EnableInterrupt(GTMR_TMRX_CAP, TMR_INT_CC3);/* 使能输入捕获通道3中断 */
}
else if (GTMR_TMRX_CAP_CHY == TMR_CHANNEL_4)
{
TMR_EnableInterrupt(GTMR_TMRX_CAP, TMR_INT_CC4);/* 使能输入捕获通道4中断 */
}
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)
{
/* 捕获比较通道n中断 */
if (((GTMR_TMRX_CAP_CHY == TMR_CHANNEL_1) &&
(TMR_ReadIntFlag(GTMR_TMRX_CAP, TMR_INT_CC1) == SET)) ||
((GTMR_TMRX_CAP_CHY == TMR_CHANNEL_2) &&
(TMR_ReadIntFlag(GTMR_TMRX_CAP, TMR_INT_CC2) == SET)) ||
((GTMR_TMRX_CAP_CHY == TMR_CHANNEL_3) &&
(TMR_ReadIntFlag(GTMR_TMRX_CAP, TMR_INT_CC3) == SET)) ||
((GTMR_TMRX_CAP_CHY == TMR_CHANNEL_4) &&
(TMR_ReadIntFlag(GTMR_TMRX_CAP, TMR_INT_CC4) == 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);
}
}
if (GTMR_TMRX_CAP_CHY == TMR_CHANNEL_1) /* 清除捕获比较通道n中断 */
{
TMR_ClearIntFlag(GTMR_TMRX_CAP, TMR_INT_CC1);
}
else if (GTMR_TMRX_CAP_CHY == TMR_CHANNEL_2)
{
TMR_ClearIntFlag(GTMR_TMRX_CAP, TMR_INT_CC2);
}
else if (GTMR_TMRX_CAP_CHY == TMR_CHANNEL_3)
{
TMR_ClearIntFlag(GTMR_TMRX_CAP, TMR_INT_CC3);
}
else if (GTMR_TMRX_CAP_CHY == TMR_CHANNEL_4)
{
TMR_ClearIntFlag(GTMR_TMRX_CAP, TMR_INT_CC4);
}
}
/* 更新中断 */
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)
{
uint8_t t = 0;
uint32_t temp = 0;
NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_4); /* 设置中断优先级分组为组4 */
sys_apm32_clock_init(15); /* 配置系统时钟 */
delay_init(120); /* 初始化延时功能 */
usart_init(115200); /* 初始化串口 */
led_init(); /* 初始化LED */
gtmr_tmrx_cap_chy_init(0xFFFF, 60 - 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) /* 200ms进入一次 */
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁 ,提示程序运行 */
}
delay_ms(10);
}
}
从上面的代码中可以看到,TMR5的预分频系数被配置为(60 - 1),同时因为TMR5的时钟频率为60MHz,因此TMR5的计数频率为1MHz,因此在TMR5成功捕获到外部输入信号的高电平后,可以直接计算出捕获到高电平的脉宽时间。
19.3 下载验证
在完成编译和烧录后,短暂按下并抬起KEY_UP按键,可以通过串口调试助手观察到捕获到的KEY_UP按键被按下的时间。