STM32C552定时器输入捕获实现PWM频率测量与占空比计算

📅 2026/7/10 2:19:09
STM32C552定时器输入捕获实现PWM频率测量与占空比计算
30款热门AI模型一站整合DeepSeek/GLM/Qwen 随心用限时 5 折。 点击领海量免费额度在嵌入式开发中频率测量是一个常见需求特别是在电机控制、电源管理、通信协议分析等场景。STM32C552系列微控制器提供了强大的定时器功能其中输入捕获功能可以精确测量外部信号的频率和占空比。本文将详细介绍如何使用STM32C552的TIM15定时器配置输入捕获功能来测量TIM1输出的PWM信号频率。1. 输入捕获功能概述1.1 什么是输入捕获输入捕获是STM32定时器的重要功能之一主要用于测量外部信号的脉冲宽度、频率和占空比。当外部信号发生跳变上升沿或下降沿时定时器会立即记录当前计数器的值这个值就是捕获值。输入捕获的工作原理基于定时器的计数器机制。定时器以固定的频率进行计数当检测到指定的边沿事件时当前计数器的值会被保存到捕获寄存器中。通过计算两次捕获值之间的差值就可以得到信号的时间间隔进而计算出频率和占空比。1.2 STM32C552定时器特性STM32C552系列微控制器包含多个高级定时器和通用定时器其中TIM15是一个通用定时器具有以下特性16位自动重装载计数器支持向上、向下计数模式独立的输入捕获通道支持PWM输入模式最高时钟频率可达144MHz1.3 测量原理分析频率测量的基本原理是通过测量信号周期来计算频率。对于一个PWM信号我们主要关注两个参数周期Period一个完整波形的时间长度高电平时间High Time信号为高电平的时间长度计算公式如下频率 1 / 周期 占空比 (高电平时间 / 周期) × 100%2. 硬件环境准备2.1 开发板配置本实验基于STM32C552CET6或STM32C552CEU6主控芯片需要准备以下硬件STM32C552开发板USB转串口模块用于调试输出杜邦线若干逻辑分析仪可选用于验证结果2.2 引脚连接方案实验中使用两个定时器通道TIM1_CH3PA5作为PWM信号输出TIM15_CH1PA7作为输入捕获通道硬件连接非常简单只需要用杜邦线将PA5和PA7连接起来PA5PWM输出 → PA7输入捕获2.3 时钟配置正确的时钟配置是确保测量精度的关键。STM32C552的主频配置为144MHz定时器时钟分配如下TIM1时钟144MHzTIM15时钟144MHz3. 软件环境搭建3.1 开发工具准备本实验使用以下开发工具STM32CubeMX用于生成初始化代码Keil MDK或STM32CubeIDE用于代码编写和调试串口调试助手用于查看测量结果3.2 工程创建步骤首先使用STM32CubeMX创建新工程选择STM32C552CET6或STM32C552CEU6芯片配置系统时钟为144MHz使能TIM1和TIM15外设配置USART1用于调试输出生成工程代码3.3 关键配置说明在STM32CubeMX中需要进行以下重要配置USART1配置调试串口ModeAsynchronousBaud Rate115200Word Length8 BitsParityNoneStop Bits1GPIOPA9TX、PA10RXTIM1配置PWM输出Channel3PWM Generation CH3Prescaler143Counter Period999PWM模式PWM模式1TIM15配置输入捕获Channel1Input Capture direct modePolarityBoth edgesPrescaler在代码中动态设置Counter Period0xFFFF4. 核心代码实现4.1 头文件包含和宏定义首先在main.c文件中添加必要的头文件和宏定义#include mx_usart1.h #include mx_tim15.h #include string.h #include stdio.h /* TIM15配置宏定义 */ #define TIM15_CLK_HZ 144000000UL // TIM15时钟频率 #define TIM15_COUNTER_HZ 1000000UL // 期望的计数频率1MHz #define TIM15_PRINT_INTERVAL 50U // 每50次测量打印一次结果 /* TIM15预分频器和自动重装载值计算 */ #define TIM15_PSC ((TIM15_CLK_HZ / TIM15_COUNTER_HZ) - 1U) #define TIM15_ARR 0xFFFFU /* 全局变量定义 */ 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; // PWM周期计数值 static volatile uint32_t tim15_high_count 0; // PWM高电平计数值 static volatile uint32_t tim15_print_freq 0; // 准备打印的频率 static volatile uint32_t tim15_print_duty_x10 0; // 准备打印的占空比放大10倍 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; // 测量次数计数4.2 printf重定向实现为了使用printf函数输出调试信息需要重写_write函数/* printf重定向到USART1 */ int _write(int file, char *ptr, int len) { hal_uart_handle_t *huart1 mx_usart1_uart_gethandle(); if (huart1 ! NULL) { HAL_UART_Transmit(huart1, (uint8_t*)ptr, len, 1000); } return len; }4.3 计数器溢出处理函数由于TIM15是16位计数器最大计数值为65535需要考虑计数器溢出的情况/* 处理计数器溢出的差值计算函数 */ static uint32_t TIM15_GetDiff(uint32_t start, uint32_t end) { if (end start) { return end - start; } else { /* 处理计数器溢出情况 */ return (TIM15_ARR 1U - start) end; } }4.4 TIM15动态配置函数为了灵活调整测量精度和范围我们在代码中动态配置TIM15的参数/* 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; }4.5 输入捕获启动函数配置完成后启动TIM15的输入捕获功能/* 启动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; }5. 输入捕获中断处理5.1 回调函数实现原理输入捕获的核心逻辑在中断回调函数中实现。当检测到边沿事件时系统会自动调用这个函数/* 输入捕获中断回调函数 */ 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; /* 检查是否为TIM15_CH1的中断 */ 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; /* 占空比放大10倍便于显示小数 */ 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; } } }5.2 边沿检测逻辑详解输入捕获的关键在于正确识别上升沿和下降沿。由于我们配置为Both edges模式同一个回调函数会处理两种边沿事件上升沿检测当PA7引脚电平为高时说明刚刚发生了上升沿下降沿检测当PA7引脚电平为低时说明刚刚发生了下降沿测量流程如下第一个上升沿记录起始位置下降沿记录高电平结束位置第二个上升沿计算完整周期和高电平时间5.3 频率和占空比计算在获得周期和高电平时间后计算过程如下/* 频率计算 */ freq TIM15_COUNTER_HZ / period; /* 占空比计算放大10倍显示小数 */ duty_x10 (high * 1000U) / period;其中TIM15_COUNTER_HZ为1MHz因此每个计数代表1微秒。对于1000Hz的PWM信号周期应为1000微秒计数值约为1000。6. 主函数实现6.1 系统初始化和PWM配置主函数中需要完成系统初始化、PWM输出配置和输入捕获启动int main(void) { /* 系统初始化 */ if (mx_system_init() ! SYSTEM_OK) { return (-1); } /* PWM信号生成配置 */ printf(STM32C552 TIM15输入捕获频率测量实验\r\n); /* 设置TIM1 PWM频率为1000Hz */ /* * 计算参数 * TIM1_CLK 144MHz * ARR 999 * PSC 143 * PWM频率 144MHz / ((143 1) * (999 1)) 1000Hz */ TIM1_SetFrequency(1000); /* 设置占空比 */ TIM1_SetChannelDuty(HAL_TIM_CHANNEL_1, TIM1_PWM_ARR, 25); // CH1: 25% TIM1_SetChannelDuty(HAL_TIM_CHANNEL_3, TIM1_PWM_ARR, 75); // CH3: 75% /* 启动PWM输出 */ TIM1_PWM_Start_CH1_CH3(); printf(TIM1 PWM: 1000Hz, CH125%%, CH375%%\r\n); /* 延时等待系统稳定 */ HAL_Delay(100); /* TIM15输入捕获配置 */ /* * 硬件连接PA5PWM输出接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); } printf(TIM15输入捕获已启动开始测量...\r\n);6.2 主循环和结果输出主循环中定期检查测量结果并通过串口输出/* 主循环 */ 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测量结果: 频率%lu Hz, 占空比%lu.%lu%%, 周期%lu, 高电平%lu\r\n, freq, duty_x10 / 10U, // 整数部分 duty_x10 % 10U, // 小数部分 period, high); } /* 短暂延时减少CPU占用 */ HAL_Delay(10); } }7. 测量结果分析与优化7.1 预期测量结果根据配置参数对于1000Hz、75%占空比的PWM信号理论测量结果应为频率1000Hz占空比75.0%周期计数值1000对应1000微秒高电平计数值750对应750微秒7.2 实际测量误差分析在实际测试中可能会观察到一些误差主要来源包括时钟精度误差晶振频率偏差软件处理延迟中断响应时间信号抖动硬件连接噪声计数器量化误差数字测量的固有误差典型的测量结果可能为TIM15测量结果: 频率1025 Hz, 占空比75.3%, 周期975, 高电平7347.3 精度优化策略为了提高测量精度可以采取以下措施提高计数频率减小预分频值提高计时分辨率信号滤波在硬件或软件上添加滤波电路多次平均对多次测量结果求平均温度补偿在宽温度范围内使用时考虑温度影响8. 常见问题与解决方案8.1 初始化配置问题问题现象输入捕获无法触发中断解决方案检查TIM15全局中断是否使能验证GPIO引脚配置是否正确确认输入捕获通道配置为Both edges问题现象测量结果明显错误解决方案检查时钟配置是否正确应为144MHz验证预分频器计算是否正确确认PWM信号源频率设置正确8.2 测量精度问题问题现象测量值跳动较大解决方案增加软件滤波算法检查硬件连接是否稳定考虑使用定时器的硬件滤波功能问题现象高频信号测量不准确解决方案提高TIM15的计数频率使用更高性能的定时器如高级定时器考虑使用定时器的PWM输入模式8.3 资源冲突问题问题现象与其他外设冲突解决方案检查引脚复用冲突调整定时器通道使用方案使用DMA传输减少CPU干预9. 扩展应用场景9.1 多通道频率测量本方案可以扩展为多通道频率测量同时监测多个信号源/* 多通道测量变量定义 */ #define CAPTURE_CHANNELS 4 static volatile uint32_t multi_channel_data[CAPTURE_CHANNELS]; /* 多通道回调函数处理 */ void HAL_TIM_IC_CaptureCallback(hal_tim_handle_t* htim) { /* 根据通道标识处理不同通道的数据 */ for (int i 0; i CAPTURE_CHANNELS; i) { if (htim-Channel capture_channels[i]) { /* 处理对应通道的测量数据 */ process_channel_data(i, HAL_TIM_ReadCapturedValue(htim, htim-Channel)); } } }9.2 高精度频率测量对于需要更高精度的应用可以采用以下技术使用32位定时器STM32C552的某些定时器支持32位计数器结合输入捕获和外部时钟使用外部高精度时钟源相位锁定环技术通过PLL倍频提高测量分辨率9.3 实时频率监控系统将频率测量功能集成到完整的监控系统中/* 频率监控系统框架 */ typedef struct { uint32_t frequency; uint32_t duty_cycle; uint32_t timestamp; uint8_t status; // 0:正常, 1:警告, 2:异常 } freq_monitor_t; void frequency_monitoring_task(void) { /* 定期读取测量数据 */ freq_monitor_t monitor_data; /* 数据分析与报警判断 */ if (analyze_frequency_data(monitor_data)) { /* 触发相应处理逻辑 */ handle_frequency_alert(monitor_data); } }10. 性能优化与最佳实践10.1 中断优化策略输入捕获对实时性要求较高需要优化中断处理/* 优化后的中断处理函数 */ __attribute__((optimize(O3))) void HAL_TIM_InputCaptureCallback(hal_tim_handle_t* htim, hal_tim_channel_t channel) { /* 使用寄存器级操作提高速度 */ uint32_t capture_value htim-Instance-CCR1; uint32_t pin_level GPIOA-IDR GPIO_IDR_ID7; /* 简化判断逻辑 */ // ... 优化后的处理逻辑 }10.2 内存使用优化对于资源受限的应用优化内存使用/* 使用位域优化标志变量 */ typedef union { struct { uint8_t rise_valid : 1; uint8_t fall_valid : 1; uint8_t print_flag : 1; uint8_t reserved : 5; } bits; uint8_t all; } capture_flags_t; static volatile capture_flags_t tim15_flags {0};10.3 电源管理考虑在电池供电应用中优化功耗/* 低功耗模式下的频率测量 */ void enter_low_power_mode(void) { /* 降低定时器时钟频率 */ reduce_timer_clock(); /* 配置为仅在测量时唤醒 */ configure_wakeup_on_capture(); /* 进入低功耗模式 */ HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); }本实验完整展示了STM32C552定时器输入捕获功能的配置和使用方法涵盖了从基础概念到实际应用的各个方面。通过这个方案可以准确测量PWM信号的频率和占空比为各种嵌入式应用提供可靠的频率检测能力。 30款热门AI模型一站整合DeepSeek/GLM/Qwen 随心用限时 5 折。 点击领海量免费额度