STM32C542开发(5)----定时器配置输入捕获测量频率
STM32C542开发.5--定时器配置输入捕获测量频率
概述
在前面的实验中,已经使用 TIM1 生成 PWM 输出,并通过修改 PSC、ARR 和 CCR 实现了 PWM 频率和占空比控制。本篇文章继续在此基础上,使用 TIM15 输入捕获功能 对 TIM1 输出的 PWM 信号进行测量,从而实现频率和占空比检测。
需要样片的可以加群申请:925643491 / 615061293 。
视频教学
https://www.bilibili.com/video/BV1J6T56oEKy/
样品申请
https://www.wjx.top/vm/OhcKxJk.aspx#
源码下载
https://download.csdn.net/download/qq_24312945/93058804
硬件准备
首先需要准备一个开发板,这里我准备的是自己绘制的开发板,需要的可以进行申请。
主控为STM32C542CCT6
参考程序
https://github.com/CoreMaker-lab/STM32C542_SENSOR
https://gitee.com/CoreMaker/STM32C542_SENSOR
串口配置
查看原理图,PA9和PA10设置为开发板的串口。

- 在左侧 Peripherals 中选择 Connectivity → USART1
- Mode 选择 Async,表示配置为异步串口模式
- Function used by the component 显示为 UART,说明 USART1 在异步模式下使用 UART HAL 驱动
- 串口参数配置为:115200 波特率、8 位数据位、无校验、1 位停止位、收发模式

- GPIO Tx:USART1_TX 选择 PA9
- GPIO Rx:USART1_RX 选择 PA10
- PA9 / PA10 均配置为 Alternate 复用功能模式
- Pull 选择 No pull-up and no pull-down,Output type 选择 Push pull,Speed 选择 Low

TIM1基础参数
使用PA7去捕获PA5的PWM信号。

先确认 TIM15 的输入时钟频率,如果时钟频率判断错误,最终计算出来的 PWM 频率也会不准确。这里 TIM15 Clock = 144 MHz

在 STM32CubeMX2 中进入 Timers → TIM15 配置页面,并使能 TIM15 外设。
本文没有直接在 STM32CubeMX2 中固定 Prescaler 和 Counter period,而是在代码中进行配置。这样后续修改测量精度和测量范围时更加方便。

完成 TIM15 基础参数配置后,接下来需要配置 TIM15_CH1 输入捕获通道。本文使用 TIM15_CH1 捕获 TIM1 输出的 PWM 信号,并通过捕获到的上升沿和下降沿计算频率和占空比。
其中,Channel direction 选择 Input,表示 TIM15_CH1 作为输入捕获通道使用。Input capture source 选择 Input capture direct mode,表示捕获信号直接来自 TIM15_CH1 对应的输入引脚。本文中 TIM15_CH1 映射到 PA7,因此外部 PWM 信号需要输入到 PA7 引脚。
Prescaler 选择 ÷1,表示每一个有效捕获边沿都会触发捕获事件,不对输入捕获事件进行分频。这样可以保证每个 PWM 边沿都能够被 TIM15 捕获。
Filter 选择 No filter。本实验中 TIM1 输出 PWM 后直接通过杜邦线连接到 TIM15_CH1,信号相对干净,因此不需要额外滤波。如果后续测量外部噪声较大的信号,可以根据实际情况开启输入滤波。
本实验最关键的配置是:
Polarity:Both edges
这表示 TIM15_CH1 会同时捕获输入 PWM 的上升沿和下降沿。这样只需要一个输入捕获通道,就可以同时测量 PWM 的周期和高电平时间。

测量逻辑如下:
上升沿 -> 保存周期起点
下降沿 -> 保存高电平结束点
下一次上升沿 -> 计算完整周期
其中:
上升沿到下一次上升沿的时间 = PWM 周期 period
上升沿到下降沿的时间 = 高电平时间 high
因此可以得到:
频率 = TIM15_COUNTER_HZ / period
占空比 = high / period × 100%
在代码中,TIM15_CH1 配置为 Both edges 后,每次捕获中断都会进入 HAL_TIM_InputCaptureCallback()。为了区分当前捕获的是上升沿还是下降沿,程序会读取 PA7 当前电平:
pin_level = (GPIOA->IDR & GPIO_IDR_ID7);
如果 PA7 当前为高电平,说明刚刚发生的是上升沿;如果 PA7 当前为低电平,说明刚刚发生的是下降沿。通过这种方式,程序就可以在一个输入捕获通道上同时完成频率和占空比测量。

完成 TIM15_CH1 输入捕获通道配置后,还需要确认 TIM15 的高级功能配置。本文只是使用 TIM15_CH1 对外部 PWM 信号进行输入捕获测量,因此大部分高级功能保持默认即可。
其中,Update event generation 保持 Enabled。这表示 TIM15 允许产生更新事件。更新事件通常与计数器溢出、重新装载等动作有关。本文虽然主要使用输入捕获功能,但保持更新事件使能即可,不需要额外修改。
Update event source 选择 Regular,表示使用常规更新事件来源。对于本文的输入捕获测频实验来说,这里保持默认配置即可。
完成 TIM15_CH1 输入捕获通道配置后,还需要检查 TIM15 对应的 GPIO 和中断配置。本文使用 TIM15_CH1 捕获 TIM1 输出的 PWM 信号,因此需要确认 TIM15_CH1 已经正确映射到 PA7,并且 TIM15 中断已经使能。
因为本文使用的是输入捕获中断方式,当 PA7 检测到 PWM 的上升沿或下降沿时,TIM15 会触发输入捕获事件,并进入 HAL_TIM_InputCaptureCallback() 回调函数。
如果这里没有开启 TIM15 的 Global interrupt,即使 TIM15_CH1 配置正确,也无法进入输入捕获回调函数,程序就无法计算频率和占空比。
本文中 TIM15_CH1 配置为 Both edges,因此上升沿和下降沿都会触发输入捕获中断。程序在回调函数中读取捕获值,并通过 PA7 当前电平判断本次捕获的是上升沿还是下降沿,最终计算 PWM 的周期、高电平时间、频率和占空比。

设置工程编码
- 在 Project Explorer 中选中当前工程
- 点击菜单栏 Project
- 选择 Properties,进入工程属性设置

- 在工程属性中选择 Resource
- Text file encoding 选择 Other
- 编码格式输入 GBK
- 点击 Apply and Close 保存设置

初始配置
初始配置修改如下状态。
/**
* brief: The application entry point.
* retval: none but we specify int to comply with C99 standard
*/
int main(void)
{
/** System Init: this code placed in targets folder initializes your system.
* It calls the initialization (and sets the initial configuration) of the peripherals.
* You can use STM32CubeMX to generate and call this code or not in this project.
* It also contains the HAL initialization and the initial clock configuration.
*/
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
/*
* You can start your application code here
*/
/*
* TIM1_CH1 -> PB8
* TIM1_CH3 -> PA5
*
* 当前配置:
* TIM1_CLK = 144 MHz
* ARR = 9999
*/
/*
* 设置 PWM 频率为 1Hz
*
* PWM频率 = 144MHz / ((PSC + 1) * (ARR + 1))
* ARR = 9999
* 当 freq = 1Hz 时,PSC = 14399
*/
if (TIM1_SetFrequency(1) != HAL_OK)
{
printf("TIM1_SetFrequency failed\r\n");
}
/*
* 设置多通道占空比
*
* CH1 = 25%
* CH3 = 75%
*/
if (TIM1_SetChannelDuty(HAL_TIM_CHANNEL_1, TIM1_PWM_ARR, 25) != HAL_OK)
{
printf("TIM1 CH1 duty set failed\r\n");
}
if (TIM1_SetChannelDuty(HAL_TIM_CHANNEL_3, TIM1_PWM_ARR, 75) != HAL_OK)
{
printf("TIM1 CH3 duty set failed\r\n");
}
/*
* 启动 TIM1 CH1 和 CH3 PWM 输出
*/
if (TIM1_PWM_Start_CH1_CH3() != HAL_OK)
{
printf("TIM1 PWM start failed\r\n");
}
printf("TIM1 PWM: 1000Hz, CH1=25%%, CH3=75%%\r\n");
/*
* 设置 TIM1 PWM 频率为 1000Hz
*
* TIM1_CLK = 144MHz
* ARR = 999
* PSC = 143
*
* PWM频率 = 144MHz / ((143 + 1) * (999 + 1))
* = 1000Hz
*/
TIM1_SetFrequency(1000);
/*
* CH1 输出 25% 占空比
* CH3 输出 75% 占空比
*/
TIM1_SetChannelDuty(HAL_TIM_CHANNEL_1, TIM1_PWM_ARR, 25);
TIM1_SetChannelDuty(HAL_TIM_CHANNEL_3, TIM1_PWM_ARR, 75);
HAL_Delay(5000);
while (1)
{
}
}
} /* end main */
添加头文件
在 main.c 中添加头文件
#include "mx_usart1.h"
#include <string.h>

printf 重定向
为了让 printf() 输出到 USART1,需要重写 _write() 函数。GCC 工程中,printf() 底层会调用 _write() 输出字符,因此只需要在 _write() 中调用 HAL_UART_Transmit(),就可以把 printf() 的内容通过串口发送出去。
int _write(int file, char *ptr, int len)
{
hal_uart_handle_t *huart1 = mx_usart1_uart_gethandle();
if (huart1 != NULL)
{
HAL_UART_Transmit(huart1, ptr, len, 1000);
}
return len;
}

TIM15 计数频率配置
TIM15_CLK_HZ:TIM15 输入时钟频率,这里为 144 MHz
TIM15_COUNTER_HZ:希望 TIM15 的计数频率为 1 MHz
TIM15_PRINT_INTERVAL:每累计 50 次测量后打印一次结果
#define TIM15_CLK_HZ 144000000UL
#define TIM15_COUNTER_HZ 1000000UL
#define TIM15_PRINT_INTERVAL 50U

为了让 TIM15 计数频率变成 1 MHz,需要设置 Prescaler:
#define TIM15_PSC ((TIM15_CLK_HZ / TIM15_COUNTER_HZ) - 1U)
#define TIM15_ARR 0xFFFFU
计算过程如下:
TIM15 计数频率 = TIM15_CLK_HZ / (PSC + 1)
TIM15_CLK_HZ = 144 MHz
TIM15_COUNTER_HZ = 1 MHz
PSC = 144,000,000 / 1,000,000 - 1
= 143
因此,当 TIM15_PSC = 143 时,TIM15 的计数频率为 1 MHz,也就是:
1 个计数 = 1 us
这样在测量 PWM 信号时非常方便。例如输入 PWM 为 1000 Hz,则周期为 1 ms,也就是 1000 us,理论上 TIM15 两次上升沿之间的捕获差值约为 1000。

捕获变量说明
代码中定义了一组变量,用于保存上升沿、下降沿和测量结果这些变量的作用如下:
tim15_rise_last:上一次上升沿捕获值
tim15_rise_now:当前上升沿捕获值
tim15_fall_now:当前下降沿捕获值
tim15_period_count:PWM 周期计数值
tim15_high_count:PWM 高电平计数值
tim15_print_freq:准备打印的频率
tim15_print_duty_x10:准备打印的占空比,放大 10 倍
tim15_print_period:准备打印的周期计数值
tim15_print_high:准备打印的高电平计数值
tim15_print_flag:打印标志位
tim15_rise_valid:上升沿捕获有效标志
tim15_fall_valid:下降沿捕获有效标志
tim15_measure_count:测量次数计数
static volatile uint32_t tim15_rise_last = 0;
static volatile uint32_t tim15_rise_now = 0;
static volatile uint32_t tim15_fall_now = 0;
static volatile uint32_t tim15_period_count = 0;
static volatile uint32_t tim15_high_count = 0;
static volatile uint32_t tim15_print_freq = 0;
static volatile uint32_t tim15_print_duty_x10 = 0;
static volatile uint32_t tim15_print_period = 0;
static volatile uint32_t tim15_print_high = 0;
static volatile uint8_t tim15_print_flag = 0;
static volatile uint8_t tim15_rise_valid = 0;
static volatile uint8_t tim15_fall_valid = 0;
static volatile uint32_t tim15_measure_count = 0;

处理计数器溢出
由于 TIM15 是 16 位计数器,Counter period 设置为 0xFFFF,计数器从 0 计数到 65535 后会重新回到 0。
因此,两次捕获值相减时需要考虑溢出情况。代码中封装了 TIM15_GetDiff() 函数
static uint32_t TIM15_GetDiff(uint32_t start, uint32_t end)
{
if (end >= start)
{
return end - start;
}
else
{
return (TIM15_ARR + 1U - start) + end;
}
}

本文没有在 STM32CubeMX2 中固定 TIM15 的 Prescaler 和 Counter period,而是在代码中通过 HAL2 函数动态配置 。
这个函数主要完成三件事:
- 使用 HAL_TIM_SetPrescaler() 设置 TIM15_PSC
- 使用 HAL_TIM_SetPeriod() 设置 TIM15_ARR
- 使用 HAL_TIM_SetCounter() 将 TIM15 计数器清零
static hal_status_t TIM15_IC_SetPSC_ARR(uint32_t psc, uint32_t arr)
{
hal_tim_handle_t* htim15 = mx_tim15_gethandle();
if (htim15 == NULL)
{
return HAL_ERROR;
}
if (HAL_TIM_SetPrescaler(htim15, psc) != HAL_OK)
{
return HAL_ERROR;
}
if (HAL_TIM_SetPeriod(htim15, arr) != HAL_OK)
{
return HAL_ERROR;
}
if (HAL_TIM_SetCounter(htim15, 0) != HAL_OK)
{
return HAL_ERROR;
}
return HAL_OK;
}

启动输入捕获中断
完成 TIM15 基础参数配置后,需要启动 TIM15_CH1 输入捕获中断 。
- HAL_TIM_IC_StartChannel_IT():启动 TIM15_CH1 输入捕获中断
- HAL_TIM_Start():启动 TIM15 计数器
static hal_status_t TIM15_IC_Start(void)
{
hal_tim_handle_t* htim15 = mx_tim15_gethandle();
if (htim15 == NULL)
{
return HAL_ERROR;
}
/*
* 鍚姩 TIM15_CH1 杈撳叆鎹曡幏涓柇
*/
if (HAL_TIM_IC_StartChannel_IT(htim15, HAL_TIM_CHANNEL_1) != HAL_OK)
{
return HAL_ERROR;
}
/*
* 鍚姩 TIM15 璁℃暟鍣�
*/
if (HAL_TIM_Start(htim15) != HAL_OK)
{
return HAL_ERROR;
}
return HAL_OK;
}

输入捕获回调函数
TIM15_CH1 在 STM32CubeMX2 中配置为 Both edges,也就是上升沿和下降沿都会触发输入捕获中断。
由于 TIM15_CH1 配置为 Both edges,所以同一个回调函数既会响应上升沿,也会响应下降沿。为了判断当前捕获的是上升沿还是下降沿,代码中读取 PA7 当前电平:
pin_level = (GPIOA->IDR & GPIO_IDR_ID7);
判断逻辑如下:
如果 PA7 当前为高电平,说明刚刚发生的是上升沿
如果 PA7 当前为低电平,说明刚刚发生的是下降沿
void HAL_TIM_InputCaptureCallback(hal_tim_handle_t* htim, hal_tim_channel_t channel)
{
uint32_t capture_value;
uint32_t period;
uint32_t high;
uint32_t freq;
uint32_t duty_x10;
uint32_t pin_level;
if ((htim != mx_tim15_gethandle()) || (channel != HAL_TIM_CHANNEL_1))
{
return;
}
capture_value = HAL_TIM_IC_ReadChannelCapturedValue(htim, HAL_TIM_CHANNEL_1);
/*
* 读取 PA7 当前电平:
* 如果当前为高电平,说明刚刚发生的是上升沿;
* 如果当前为低电平,说明刚刚发生的是下降沿。
*/
pin_level = (GPIOA->IDR & GPIO_IDR_ID7);
if (pin_level != 0U)
{
/*
* 上升沿
*/
if (tim15_rise_valid == 0U)
{
tim15_rise_last = capture_value;
tim15_rise_valid = 1U;
tim15_fall_valid = 0U;
}
else
{
tim15_rise_now = capture_value;
period = TIM15_GetDiff(tim15_rise_last, tim15_rise_now);
if ((period != 0U) && (tim15_fall_valid != 0U))
{
high = TIM15_GetDiff(tim15_rise_last, tim15_fall_now);
if (high <= period)
{
freq = TIM15_COUNTER_HZ / period;
/*
* duty_x10 放大 10 倍,方便打印一位小数
* 例如 253 表示 25.3%
*/
duty_x10 = (high * 1000U) / period;
tim15_period_count = period;
tim15_high_count = high;
tim15_measure_count++;
if (tim15_measure_count >= TIM15_PRINT_INTERVAL)
{
tim15_measure_count = 0;
tim15_print_freq = freq;
tim15_print_duty_x10 = duty_x10;
tim15_print_period = period;
tim15_print_high = high;
tim15_print_flag = 1;
}
}
}
tim15_rise_last = tim15_rise_now;
tim15_fall_valid = 0U;
}
}
else
{
/*
* 下降沿
*/
if (tim15_rise_valid != 0U)
{
tim15_fall_now = capture_value;
tim15_fall_valid = 1U;
}
}
}

增加 TIM15 初始化和启动
你的 main() 里,在启动 TIM1 PWM 后面增加 TIM15:
/*
*
* 硬件连接:
* PA5 接 PA7
*/
if (TIM15_IC_SetPSC_ARR(TIM15_PSC, TIM15_ARR) != HAL_OK)
{
printf("TIM15 IC set PSC/ARR failed\r\n");
}
if (TIM15_IC_Start() != HAL_OK)
{
printf("TIM15 IC start failed\r\n");
}

主循环
在 while(1) 中,程序判断 tim15_print_flag 是否置位。
while (1)
{
if (tim15_print_flag)
{
uint32_t freq;
uint32_t duty_x10;
uint32_t period;
uint32_t high;
__disable_irq();
tim15_print_flag = 0;
freq = tim15_print_freq;
duty_x10 = tim15_print_duty_x10;
period = tim15_print_period;
high = tim15_print_high;
__enable_irq();
printf("TIM15 measured: freq=%lu Hz, duty=%lu.%lu%%, period=%lu, high=%lu\r\n",
freq,
duty_x10 / 10U,
duty_x10 % 10U,
period,
high);
}
}

这里先关闭中断,是为了防止主循环读取变量时,中断刚好更新这些变量,导致读取到一半新数据、一半旧数据。
读取完成后重新打开中断,再通过串口打印:
freq:测量到的 PWM 频率
duty:测量到的 PWM 占空比
period:一个周期对应的计数值
high:高电平对应的计数值
如果输入信号为 1000 Hz、25% 占空比,理论打印结果接近:
TIM15 measured: freq=1000 Hz, duty=25.0%, period=1000, high=250
如果输入信号为 1000 Hz、75% 占空比,理论打印结果接近:
TIM15 measured: freq=1000 Hz, duty=75.0%, period=1000, high=750
演示结果
检测到的频率逻辑分析仪和串口打印基本上都是1025Hz,正占空比为75%。


更多推荐


所有评论(0)