第10章 输入捕获实验
第十章 输入捕获实验
1. 硬件设计
本实验用到的硬件资源有:
-
指示灯DS0
-
KEY_UP按键
-
串口
-
定时器TIM14
-
定时器TIM5
前面 4 个,在之前的章节均有介绍。 本节,我们将捕获 TIM5_CH1( PA0)上的高电平脉宽, 通过 KEY_UP 按键输入高电平,并从串口打印高电平脉宽。同时我们保留上节的 PWM 输出,大家也可以通过用杜邦线连接 PF9 和 PA0,来测量 PWM 输出的高电平脉宽。
2. 软件设计
2.1 编程大纲
-
定时器14(TIM14)初始化,输出PWM
-
设置占空比函数
-
定时器5(TIM5)初始化,配置上升沿捕获
-
设置TIM捕获值函数
-
定时器溢出中断捕获函数
-
定时器输入捕获中断函数
-
主函数测试,打印高电平时间
2.2 代码分析
2.2.1 TIM14初始化
// TIM14 PWM初始化
void TIM14_PWM_Init(uint16_t arr, uint16_t psc)
{
TIM14_Handle.Instance = TIM14;
TIM14_Handle.Init.Prescaler = psc;
TIM14_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM14_Handle.Init.Period = arr;
TIM14_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&TIM14_Handle);
TIM14_CH1Handle.OCMode = TIM_OCMODE_PWM1;
TIM14_CH1Handle.Pulse = arr/2;
TIM14_CH1Handle.OCPolarity = TIM_OCNPOLARITY_LOW;
HAL_TIM_PWM_ConfigChannel(&TIM14_Handle, &TIM14_CH1Handle, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&TIM14_Handle, TIM_CHANNEL_1);
}
// 定时器14底层驱动
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_TIM14_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_TIM14; // PF9复用为TIM14_CH1
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
}
2.2.2 设置TIM14_CH1占空比
// 设置TIM14通道的占空比
void TIM_SetTIM14Compare1(uint16_t compare)
{
TIM14->CCR1 = compare;
}
2.2.3 TIM5_CH1输入捕获初始化
// 定时器5的输入捕获初始化
void TIM5_CH1_Cap_Init(uint32_t arr, uint16_t psc)
{
TIM_IC_InitTypeDef TIM_ICInitStructure;
TIM5_Handle.Instance = TIM5;
TIM5_Handle.Init.Prescaler = psc;
TIM5_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM5_Handle.Init.Period = arr;
TIM5_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&TIM5_Handle);
// 上升沿捕获
TIM_ICInitStructure.ICPolarity = TIM_ICPOLARITY_RISING;
TIM_ICInitStructure.ICSelection = TIM_ICSELECTION_DIRECTTI; // 映射到输入捕获通道
TIM_ICInitStructure.ICPrescaler = TIM_ICPSC_DIV1; // 不分频
TIM_ICInitStructure.ICFilter = 0; // 不滤波
HAL_TIM_IC_ConfigChannel(&TIM5_Handle, &TIM_ICInitStructure, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&TIM5_Handle, TIM_CHANNEL_1); // 开启输入捕获中断
__HAL_TIM_ENABLE_IT(&TIM5_Handle, TIM_IT_UPDATE); // 开启更新中断
}
// 定时器5底层驱动
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_TIM5_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM5; // PA0复用为TIM5_CH1
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(TIM5_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM5_IRQn);
}
2.2.4 设置TIM捕获值
// 定时器5中断服务函数
void TIM5_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM5_Handle);
}
// 设置TIM捕获/比较寄存器值
uint32_t TIM_GetTIM14Capture1(void)
{
return HAL_TIM_ReadCapturedValue(&TIM14_Handle, TIM_CHANNEL_1); // 获取捕获值
}
2.2.5 TIM计时溢出中断处理函数
// 定时器更新中断(计数溢出)中断处理回调函数
// 此函会被HAL_TIM_IRQHandler调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) // 计时器溢出中断
{
if((TIM5CH1_CapTure_STA & 0x80) == 0) // 还未成功捕获
{
if(TIM5CH1_CapTure_STA & 0x40) // 已经捕获到高电平
{
if((TIM5CH1_CapTure_STA & 0x3F) == 0x3F) // 高电平太长了
{
TIM5CH1_CapTure_STA |= 0x80; // 成功捕获标志
TIM5CH1_CapTure_VAL = 0xFFFFFFFF; // 捕获值设为最大
}
}
else // 捕获到一次低电平
{
TIM5CH1_CapTure_STA++;
}
}
}
这段代码初次见面可能有点难以理解,下面我们分步骤解释一下:
函数概述
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- 这是一个由HAL库定义的回调函数,当定时器的计数周期到达设定值(溢出)时自动调用。
TIM_HandleTypeDef *htim
是指向定时器句柄的指针,用于识别哪个定时器产生了中断。
变量解释
TIM5CH1_CAPTURE_STA
: 这是一个状态变量,通常用来保存捕获的状态信息。TIM5CH1_CAPTURE_VAL
: 存储捕获的值。
代码逻辑
- 检查捕获状态:
if((TIM5CH1_CAPTURE_STA & 0x80) == 0) // 还未成功捕获
- 检查状态标志位的第8位(0x80),如果为0,表示还没有成功捕获高电平。
- 判断是否已经捕获到高电平:
if(TIM5CH1_CAPTURE_STA & 0x40) // 已经捕获到高电平了
- 检查状态标志位的第7位(0x40),如果为1,表示之前已经捕获到了高电平。
- 捕获高电平时间过长:
if((TIM5CH1_CAPTURE_STA & 0x3F) == 0x3F) // 高电平太长了
- 检查状态变量的低6位(0x3F),如果等于63,表示高电平持续时间过长。
- 此时,将第8位标记为1,表示成功捕获了一次,并将捕获值设置为最大值(0xFFFFFFFF),为了表示超时。
- 捕获到一次低电平:
else TIM5CH1_CAPTURE_STA++;
- 如果没有达到超时,表示捕获到了一次低电平,状态计数器加1。
2.2.6 输入捕获中断处理函数
// 定时器输入捕获中断处理回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) // 输入捕获中断
{
if((TIM5CH1_CapTure_STA & 0x80) == 0) // 还未成功捕获
{
if(TIM5CH1_CapTure_STA & 0x40) // 捕捉到一个下降沿
{
TIM5CH1_CapTure_STA |= 0x80; // 成功捕获标志
TIM5CH1_CapTure_VAL = HAL_TIM_ReadCapturedValue(&TIM5_Handle, TIM_CHANNEL_1); // 获取当前捕获值
TIM_RESET_CAPTUREPOLARITY(&TIM5_Handle, TIM_CHANNEL_1); // 复位捕获极性
TIM_SET_CAPTUREPOLARITY(&TIM5_Handle, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); // 切换到捕捉上升沿
}
else // 捕捉到一个上升沿
{
TIM5CH1_CapTure_STA = 0; // 复位状态
TIM5CH1_CapTure_VAL = 0; // 捕获值清零
TIM5CH1_CapTure_STA |= 0x40; // 捕获到上升沿标志
__HAL_TIM_DISABLE(&TIM5_Handle); // 关闭定时器
__HAL_TIM_SET_COUNTER(&TIM5_Handle, 0); // 复位计数器
TIM_RESET_CAPTUREPOLARITY(&TIM5_Handle, TIM_CHANNEL_1); // 复位捕获极性
TIM_SET_CAPTUREPOLARITY(&TIM5_Handle, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); // 切换到捕捉下降沿
__HAL_TIM_ENABLE(&TIM5_Handle); // 开启定时器
}
}
}
同样,这个函数也值得我们分析一下:
函数定义
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) // 捕获中断发生时执行
- 这是一个回调函数,当定时器捕获中断发生时被调用,参数
htim
是指向定时器句柄的指针,用于识别哪个定时器触发了中断。
捕获状态检查
if((TIM5CH1_CAPTURE_STA & 0X80) == 0) // 还未成功捕获
- 检查
TIM5CH1_CAPTURE_STA
的第 7 位(0x80)是否为 0,表示还没有成功捕获到高电平脉宽。
捕获到下降沿的处理
if(TIM5CH1_CAPTURE_STA & 0X40) // 捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA |= 0X80; // 标记成功捕获到一次高电平脉宽
TIM5CH1_CAPTURE_VAL = HAL_TIM_ReadCapturedValue(&TIM5_Handler, TIM_CHANNEL_1); // 获取当前的捕获值.
TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1); // 一定要先清除原来的设置!!
TIM_SET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); // 配置TIM5通道1上升沿捕获
}
- 如果已经捕获到下降沿(
TIM5CH1_CAPTURE_STA
的第 6 位为 1),则:- 将状态标志
0x80
设为 1,标记为成功捕获高电平脉宽。 - 使用
HAL_TIM_ReadCapturedValue
获取当前捕获的计数值,并存储在TIM5CH1_CAPTURE_VAL
中。 - 清除之前的捕获极性设置。
- 将捕获极性设置为上升沿,准备下次捕获。
- 将状态标志
第一次捕获上升沿的处理
else // 还未开始,第一次捕获上升沿
{
TIM5CH1_CAPTURE_STA = 0; // 清空
TIM5CH1_CAPTURE_VAL = 0;
TIM5CH1_CAPTURE_STA |= 0X40; // 标记捕获到了上升沿
__HAL_TIM_DISABLE(&TIM5_Handler); // 关闭定时器5
__HAL_TIM_SET_COUNTER(&TIM5_Handler, 0); // 清空计数器
TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1); // 一定要先清除原来的设置!!
TIM_SET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); // 定时器5通道1设置为下降沿捕获
__HAL_TIM_ENABLE(&TIM5_Handler); // 使能定时器5
}
- 如果还没有捕获到任何边沿:
- 清空捕获状态和捕获值。
- 标记为已捕获到上升沿(设置
0x40
)。 - 关闭 TIM5 定时器,重置计数器为 0。
- 清除之前的捕获极性设置。
- 将捕获极性设置为下降沿,以便在下次捕获时能够检测到下降沿。
- 重新使能 TIM5 定时器。
2.2.7 主函数
int main(void)
{
long temp = 0;
HAL_Init();
Stm32_Clock_Init(336,8,2,7);
delay_init(168);
uart_init(115200);
LED_Init();
KEY_Init();
TIM14_PWM_Init(500-1,84-1);
TIM5_CH1_Cap_Init(0XFFFFFFFF,84-1); //以1MHZ的频率计数
while(1)
{
delay_ms(10);
TIM_SetTIM14Compare1(TIM_GetTIM14Capture1()+1);
if(TIM_GetTIM14Capture1() == 300)
{
TIM_SetTIM14Compare1(0); // 如果计数值达到300,关闭PWM输出
}
if(TIM5CH1_CapTure_STA & 0x80) // 捕获到高电平
{
temp = TIM5CH1_CapTure_STA & 0x3F;
temp *= 0xFFFFFFFF; // 溢出时间总和
temp += TIM5CH1_CapTure_VAL; // 得到总高电平时间
printf("High time: %ld us\r\n", temp);
TIM5CH1_CapTure_STA = 0; // 清除标志位,准备下次捕获
}
}
}
此程序的核心功能是通过定时器 5 的输入捕获功能测量输入信号的高电平持续时间。每次捕获到高电平时,程序会计算并输出该高电平的时间。程序同时使用 PWM 产生信号,并控制占空比。
- 定时器 5 用于捕获外部输入信号的高电平时间。
- 定时器 14 用于产生 PWM 信号。
- 通过串口输出捕获的高电平时间。
3. 小结
如果整个看完依旧很迷的话,看下面这个图就很容易理解了
首先我们是捕获的上升沿,对应图的0~t1,对应的代码:
else //还未开始,第一次捕获上升沿
{
TIM5CH1_CAPTURE_STA = 0; // 清空
TIM5CH1_CAPTURE_VAL = 0;
TIM5CH1_CAPTURE_STA|=0X40;// 标记捕获到了上升沿
__HAL_TIM_DISABLE(&TIM5_Handler); // 关闭定时器 5
__HAL_TIM_SET_COUNTER(&TIM5_Handler,0);
TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1); //一定要先清除原来的设置!!
TIM_SET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);
//定时器 5 通道 1 设置为下降沿捕获
__HAL_TIM_ENABLE(&TIM5_Handler); // 使能定时器 5
}
代码中,我们给了一个标志位说明已经捕获到上升沿,可以进入下一个阶段了,并且初始化定时器,定时器之前我们已经配置好了,为向上计数,向上计数可能到导致问题,那么就是可能产生溢出,那么对应的我们也有定时器计数溢出函数进行处理:
//更新中断(溢出)发生时执行
{
if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获了一次
TIM5CH1_CAPTURE_VAL=0XFFFFFFFF;
}
else
TIM5CH1_CAPTURE_STA++;
}
}
}
上升沿和溢出处理我们都已经解决了,下面还有一个下降沿,也就是t1~t2,我们计算高电平的时间就是利用这段时间来处理的:
if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA|=0X80; // 标记成功捕获到一次高电平脉宽
TIM5CH1_CAPTURE_VAL=HAL_TIM_ReadCapturedValue(&TIM5_Handler,TIM_CHANNEL_1);//获取当前的捕获值.
TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1); //一定要先清除原来的设置!!
TIM_SET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);//配置 TIM5 通道 1 上升沿捕获
当我们捕获到一个下降沿后,获取当前的捕获值,然后再初始化定时器为向上计数并且上升沿捕获,开启下一轮。
如果还是不理解的,可以看看下面的简化版本:
#include "stm32f4xx_hal.h" // 根据你的具体MCU修改此头文件
TIM_HandleTypeDef htim2; // 定义定时器句柄
// 状态变量
volatile uint32_t captureStart = 0; // 存储捕获开始时间
volatile uint32_t captureEnd = 0; // 存储捕获结束时间
volatile uint8_t capturing = 0; // 捕获状态标志
volatile uint32_t overflowCount = 0;// 溢出计数
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
int main(void) {
HAL_Init(); // 初始化HAL库
SystemClock_Config();// 配置系统时钟
MX_GPIO_Init(); // 初始化GPIO(如果需要)
MX_TIM2_Init(); // 初始化定时器
// 启动定时器的输入捕获中断
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
// 启动定时器的溢出中断
HAL_TIM_Base_Start_IT(&htim2);
while (1) {
// 主循环可以放其他代码
}
}
// 输入捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2) { // 确保是TIM2的中断
if (!capturing) { // 如果当前不在捕获状态
// 捕获开始
captureStart = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 读取开始时间
capturing = 1; // 标记为正在捕获
} else { // 如果已经在捕获状态
// 捕获结束
captureEnd = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 读取结束时间
capturing = 0; // 恢复状态
// 计算高电平时间(单位为计数)
uint32_t highTime;
if (captureEnd >= captureStart) {
highTime = captureEnd - captureStart + (overflowCount * (htim2.Init.Period + 1));
} else {
highTime = (htim2.Init.Period + 1 - captureStart) + captureEnd + (overflowCount * (htim2.Init.Period + 1));
}
// 此处可以根据需要对计算的高电平时间进行处理
// 例如:存储、显示等
}
}
}
// 定时器溢出回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) { // 确保是TIM2的溢出中断
overflowCount++; // 溢出计数加1
}
}
// 初始化定时器
static void MX_TIM2_Init(void) {
__HAL_RCC_TIM2_CLK_ENABLE(); // 使能TIM2时钟
TIM_IC_InitTypeDef sConfigIC = {0}; // 输入捕获配置结构体
htim2.Instance = TIM2; // 选择TIM2
htim2.Init.Prescaler = 83; // 将定时器时钟设置为1us(84MHz / 84 = 1MHz)
htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式
htim2.Init.Period = 0xFFFF; // 最大计数值
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频
HAL_TIM_Base_Init(&htim2); // 初始化定时器基础功能
// 配置输入捕获信道
sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; // 捕获上升沿
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接连接输入
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频
sConfigIC.ICFilter = 0; // 无滤波
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1); // 配置TIM2通道1
}
// 其他必要的初始化函数,如SystemClock_Config和MX_GPIO_Init
- 状态变量:
volatile uint32_t overflowCount
:用于记录定时器溢出的次数。
- 输入捕获回调 (
HAL_TIM_IC_CaptureCallback
):
- 在捕获到上升沿时,记录
captureStart
。 - 在捕获到下降沿时,记录
captureEnd
,并根据当前溢出的次数计算高电平持续时间。 - 使用条件判断处理可能的计数器溢出情况。
- 定时器溢出回调 (
HAL_TIM_PeriodElapsedCallback
):
- 每当定时器溢出时,该函数会被调用,增加溢出计数器。
- 定时器初始化 (
MX_TIM2_Init
):
- 配置定时器为上升沿捕获模式,并设置相关参数。
2024.10.3 第一次修订,后期不再维护
2025.1.16 优化内容和程序
本文作者:hazy1k
本文链接:https://www.cnblogs.com/hazy1k/p/18445692
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步