人若无名 便可潜心练剑.|

hazy1k

园龄:7个月粉丝:14关注:0

2024-10-03 15:09阅读: 23评论: 0推荐: 0

第10章 输入捕获实验

第十章 输入捕获实验

1. 硬件设计

本实验用到的硬件资源有:

  • 指示灯DS0

  • KEY_UP按键

  • 串口

  • 定时器TIM14

  • 定时器TIM5

前面 4 个,在之前的章节均有介绍。 本节,我们将捕获 TIM5_CH1( PA0)上的高电平脉宽, 通过 KEY_UP 按键输入高电平,并从串口打印高电平脉宽。同时我们保留上节的 PWM 输出,大家也可以通过用杜邦线连接 PF9 和 PA0,来测量 PWM 输出的高电平脉宽。

2. 软件设计

2.1 编程大纲

  1. 定时器14(TIM14)初始化,输出PWM

  2. 设置占空比函数

  3. 定时器5(TIM5)初始化,配置上升沿捕获

  4. 设置TIM捕获值函数

  5. 定时器溢出中断捕获函数

  6. 定时器输入捕获中断函数

  7. 主函数测试,打印高电平时间

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: 存储捕获的值。
代码逻辑
  1. 检查捕获状态
if((TIM5CH1_CAPTURE_STA & 0x80) == 0) // 还未成功捕获
  • 检查状态标志位的第8位(0x80),如果为0,表示还没有成功捕获高电平。
  1. 判断是否已经捕获到高电平
if(TIM5CH1_CAPTURE_STA & 0x40) // 已经捕获到高电平了
  • 检查状态标志位的第7位(0x40),如果为1,表示之前已经捕获到了高电平。
  1. 捕获高电平时间过长
if((TIM5CH1_CAPTURE_STA & 0x3F) == 0x3F) // 高电平太长了
  • 检查状态变量的低6位(0x3F),如果等于63,表示高电平持续时间过长。
  • 此时,将第8位标记为1,表示成功捕获了一次,并将捕获值设置为最大值(0xFFFFFFFF),为了表示超时。
  1. 捕获到一次低电平
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. 小结

如果整个看完依旧很迷的话,看下面这个图就很容易理解了

屏幕截图 2024 10 03 144616

首先我们是捕获的上升沿,对应图的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++;
        }
    } 
}

屏幕截图 2024 10 03 145521

上升沿和溢出处理我们都已经解决了,下面还有一个下降沿,也就是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
  1. 状态变量
  • volatile uint32_t overflowCount:用于记录定时器溢出的次数。
  1. 输入捕获回调 (HAL_TIM_IC_CaptureCallback):
  • 在捕获到上升沿时,记录 captureStart
  • 在捕获到下降沿时,记录 captureEnd,并根据当前溢出的次数计算高电平持续时间。
  • 使用条件判断处理可能的计数器溢出情况。
  1. 定时器溢出回调 (HAL_TIM_PeriodElapsedCallback):
  • 每当定时器溢出时,该函数会被调用,增加溢出计数器。
  1. 定时器初始化 (MX_TIM2_Init):
  • 配置定时器为上升沿捕获模式,并设置相关参数。

2024.10.3 第一次修订,后期不再维护

2025.1.16 优化内容和程序

本文作者:hazy1k

本文链接:https://www.cnblogs.com/hazy1k/p/18445692

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hazy1k  阅读(23)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起