当前位置: 首页 > news >正文

PWM输入捕获(测量按键按下时间、测量PWM波)

使用定时器 2 通道 2 来捕获按键 2 按下时间,并通过串口打印。 计一个数的时间:1us,PSC=71,ARR=65535 下降沿捕获、输入通道 2 映射在 TI2 上、不分频、不滤波。

ic.c:

#include "ic.h"           // 本模块头文件:声明 ic_init/pressed_time_get 等接口
#include "stdio.h"        // printf 调试输出
#include "string.h"       // memset 等/* 捕获状态机:* succeed_flag=1:已完成一次“按下时长”的测量(等待应用层读取并清零)* rising_flag/ falling_flag:最近一次捕获到的边沿类型标记(本例只用 falling_flag)* timeout_cnt:计数器溢出次数(用更新中断统计每次 CNT 从 0→ARR 的次数)*/
struct
{uint8_t  succeed_flag;uint8_t  rising_flag;uint8_t  falling_flag;uint16_t timout_cnt;   // 注意拼写:timeout_cnt 更贴切;uint16_t 足够覆盖 65.536ms/次
} capture_status = {0};uint16_t last_cnt = 0;     // 保存“释放时”捕获到的计数值(单位:计数拍=1µs)TIM_HandleTypeDef ic_handle = {0};  // 定时器句柄,全局唯一,供 HAL 中断/回调使用/* 输入捕获初始化* @param arr 自动重装载值(ARR),本例传 65536-1,使溢出周期 = (ARR+1)/1MHz = 65.536ms* @param psc 预分频(PSC),本例传 72-1,使计数时钟 CK_CNT = 72MHz/(71+1) = 1MHz(1us/计数)*/
void ic_init(uint16_t arr, uint16_t psc)
{TIM_IC_InitTypeDef ic_config = {0};             // 输入捕获通道配置结构体ic_handle.Instance               = TIM2;        // 选择定时器实例:TIM2(APB1)ic_handle.Init.Prescaler         = psc;         // 预分频:72-1 → 1MHz 计数ic_handle.Init.Period            = arr;         // 自动重装载:65535 → 16bit 全范围ic_handle.Init.CounterMode       = TIM_COUNTERMODE_UP;   // 向上计数HAL_TIM_IC_Init(&ic_handle);                    // 初始化输入捕获(会回调 MSP 做时钟/GPIO/NVIC)ic_config.ICPolarity  = TIM_ICPOLARITY_FALLING; // 初始捕获“下降沿”(按下瞬间,视硬件接法而定)ic_config.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直连 TI2(把 CH2 直接映射到 TI2)ic_config.ICPrescaler = TIM_ICPSC_DIV1;         // 不分频(每个有效边沿都捕获)ic_config.ICFilter    = 0;                      // 不滤波(去抖依赖外部或软件)HAL_TIM_IC_ConfigChannel(&ic_handle, &ic_config, TIM_CHANNEL_2); // 配置 CH2__HAL_TIM_ENABLE_IT(&ic_handle, TIM_IT_UPDATE); // 使能“更新中断”:用于统计溢出次数HAL_TIM_IC_Start_IT(&ic_handle, TIM_CHANNEL_2); // 启动 CH2 输入捕获(使能 CC2 中断)
}/* MSP(底层)初始化:由 HAL_TIM_IC_Init 调用* 这里完成与板级相关的时钟/GPIO/NVIC 配置*/
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM2){GPIO_InitTypeDef gpio_initstruct;// 1) 打开外设时钟__HAL_RCC_GPIOA_CLK_ENABLE();   // 使能 GPIOA 时钟(TIM2_CH2 默认映射在 PA1)__HAL_RCC_TIM2_CLK_ENABLE();    // 使能 TIM2 时钟// 2) 配置 PA1 为输入模式(F1 输入捕获可用普通输入;推荐上拉/下拉按硬件而定)gpio_initstruct.Pin   = GPIO_PIN_1;           // TIM2_CH2 → PA1(默认映射)gpio_initstruct.Mode  = GPIO_MODE_INPUT;      // 输入模式(F1 没有专门 AF-Input)gpio_initstruct.Pull  = GPIO_PULLUP;          // 上拉:配合“按下接地”的按钮gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 对输入意义不大,保持默认HAL_GPIO_Init(GPIOA, &gpio_initstruct);// 3) 配置 NVIC(更新中断与 CC2 中断共用 TIM2_IRQn)HAL_NVIC_SetPriority(TIM2_IRQn, 2, 2);HAL_NVIC_EnableIRQ(TIM2_IRQn);}
}/* TIM2 中断向量:统一交给 HAL 分发(判断是更新还是 CC2 捕获,并调用回调) */
void TIM2_IRQHandler(void)
{HAL_TIM_IRQHandler(&ic_handle);
}/* CCx 捕获回调:每次捕获到所设定极性的边沿都会进来 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{// printf("捕获到下降沿\r\n");  // 不建议在中断里用 printf(阻塞/重入风险)if(htim->Instance == TIM2){if(capture_status.succeed_flag == 0)  // 只在一次测量窗口内处理{if(capture_status.falling_flag == 1){// 第二次边沿:捕获到“上升沿”(释放瞬间)printf("捕获到上升沿\r\n");capture_status.succeed_flag = 1;  // 标记:一次测量完成// 读取 CH2 捕获值(释放时的 CNT 数;单位:1µs)last_cnt = HAL_TIM_ReadCapturedValue(&ic_handle, TIM_CHANNEL_2);// 恢复为“下降沿等待下一次测量”TIM_RESET_CAPTUREPOLARITY(&ic_handle, TIM_CHANNEL_2);TIM_SET_CAPTUREPOLARITY(&ic_handle, TIM_CHANNEL_2, TIM_ICPOLARITY_FALLING);// 注:不在这里清状态,等 pressed_time_get() 打印后统一 memset 清零}else{// 第一次边沿:捕获到“下降沿”(按下瞬间,测量起点)printf("捕获到下降沿\r\n");memset(&capture_status, 0, sizeof(capture_status)); // 确保清空旧状态capture_status.falling_flag = 1;                    // 进入“测量中”状态__HAL_TIM_DISABLE(&ic_handle);        // 停止计数,避免切换极性时毛刺__HAL_TIM_SET_COUNTER(&ic_handle, 0); // 以“按下瞬间”为 T=0TIM_RESET_CAPTUREPOLARITY(&ic_handle, TIM_CHANNEL_2);TIM_SET_CAPTUREPOLARITY(&ic_handle, TIM_CHANNEL_2, TIM_ICPOLARITY_RISING); // 改为捕“上升沿”__HAL_TIM_ENABLE(&ic_handle);         // 重新启动计数与捕获}}}
}/* 更新事件(溢出)回调:CNT 从 ARR 回到 0 时触发(约每 65.536ms 一次) */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM2){if(capture_status.succeed_flag == 0)   // 仅在“测量中”累加溢出{if(capture_status.falling_flag == 1)capture_status.timout_cnt++;   // 记录溢出次数}}
}/* 应用层读取函数:若一次测量完成,计算并打印“按下时长(us)”,然后复位状态 */
void pressed_time_get(void)
{if(capture_status.succeed_flag == 1){// 总时长 = 溢出次数 * (ARR+1) + 释放时捕获值(单位:计数拍=1µs)printf("按下时间:%d us\r\n", capture_status.timout_cnt * 65536 + last_cnt);// 复位状态,准备下一次测量memset(&capture_status, 0, sizeof(capture_status));}
}

main.c:

#include "sys.h"     // 时钟配置接口 stm32_clock_init()
#include "delay.h"   // 简易延时(若只用串口与捕获,非必须)
#include "led.h"     // LED 初始化(与本实验无强相关)
#include "uart1.h"   // 串口初始化/printf 重定向
#include "ic.h"      // 本模块:ic_init / pressed_time_getint main(void)
{HAL_Init();                         /* 初始化 HAL 库(Systick/优先级分组等) */stm32_clock_init(RCC_PLL_MUL9);     /* 系统时钟 72MHz(HSE*9) */led_init();                         /* LED 初始化:可用于调试指示 */uart1_init(115200);                 /* 串口1:115200 8N1 */printf("hello world!\r\n");// 定时器 2 输入捕获初始化:ARR=65535,PSC=71 → 1MHz 计数(1µs/计数)ic_init(65536 - 1, 72 - 1);while(1){// 轮询读取一次测量结果(打印后会复位状态)pressed_time_get();// 其它任务...}
}

1)定时器基类(ic_handle.Init 字段)

  • Prescaler(PSC):0..65535

    • 计数时钟 CK_CNT = TIMxCLK / (PSC + 1);F1 若 APBx 分频≠1,TIMxCLK=2×PCLKx。

  • Period(ARR):0..65535(F103 通用定时器 16bit)

    • 溢出周期 T_update = (ARR + 1)/CK_CNT

  • CounterMode

    • TIM_COUNTERMODE_UP / TIM_COUNTERMODE_DOWN / TIM_COUNTERMODE_CENTERALIGNED1/2/3(输入捕获常用 UP)。

  • ClockDivision(若需要,默认 DIV1):

    • TIM_CLOCKDIVISION_DIV1/DIV2/DIV4(对数字滤波采样分频有影响)。

2)输入捕获通道(TIM_IC_InitTypeDef

  • ICPolarity(捕获哪种边沿):

    • TIM_ICPOLARITY_RISING(上升沿)

    • TIM_ICPOLARITY_FALLING(下降沿)

    • TIM_ICPOLARITY_BOTHEDGE(双沿,并非所有 F1 通用定时器/通道都支持;以参考手册为准)

  • ICSelection(通道映射与测相位差用):

    • TIM_ICSELECTION_DIRECTTI:直接把 CHx 接 TIx(最常用)

    • TIM_ICSELECTION_INDIRECTTI:把 CHx 接另一输入(如 CH2 接 TI1),配合第二路实现测高/低电平宽度

    • TIM_ICSELECTION_TRC:选择触发控制器 TRC,配合从模式做特殊测量

  • ICPrescaler(边沿预分频):

    • TIM_ICPSC_DIV1/DIV2/DIV4/DIV8(每 1/2/4/8 个有效边沿才触发一次捕获)

  • ICFilter(数字滤波,0..15):

    • 0:无滤波;1~15:不同采样频率与样本数的组合(编码见 RM0008:TIMx_CCMR寄存器的 ICxF 位)

    • 用途:去抖/抗毛刺。按键输入建议设大一些,如 8~15;代价是对窄脉冲不敏感。

3)HAL 相关 API/宏

  • HAL_TIM_IC_Init/ConfigChannel/Start_IT/Stop_IT:初始化/配置/启动/停止输入捕获(带中断)。

  • HAL_TIM_ReadCapturedValue(&htim, TIM_CHANNEL_1/2/3/4):读 CCRx 捕获值。

  • __HAL_TIM_ENABLE_IT(&htim, TIM_IT_UPDATE/CC1/CC2/...):使能指定中断源。

    • 常见:TIM_IT_UPDATE(溢出),TIM_IT_CC1~CC4(捕获),TIM_IT_TRIGGER 等。

  • __HAL_TIM_SET_COUNTER(&htim, val) / __HAL_TIM_GET_COUNTER(&htim):设/读 CNT。

  • __HAL_TIM_ENABLE/ __HAL_TIM_DISABLE(&htim):开/关计数(置/清 CEN)。

  • TIM_RESET_CAPTUREPOLARITY(&htim, TIM_CHANNEL_x):先复位极性配置(安全改边沿)

  • TIM_SET_CAPTUREPOLARITY(&htim, TIM_CHANNEL_x, TIM_ICPOLARITY_*):设置新的捕获边沿。

    最佳实践:改极性前可以先 HAL_TIM_IC_Stop_IT(...)__HAL_TIM_DISABLE(&htim),改完再启,避免半周期毛刺。

4)GPIO(GPIO_InitTypeDef

  • Pin:如 GPIO_PIN_1(PA1);

  • ModeGPIO_MODE_INPUT / GPIO_MODE_AF_PP(输出复用)/ GPIO_MODE_IT_FALLING(外部中断)等;

    • F1 输入捕获做输入即可;更高系列常见 GPIO_MODE_AF_INPUT

  • PullGPIO_NOPULL / GPIO_PULLUP / GPIO_PULLDOWN(按硬件接法选择)。

  • SpeedGPIO_SPEED_FREQ_LOW/MEDIUM/HIGH(输入无所谓,输出相关)。

实现计时的原理

  1. 把定时器当“高精度计时器”

    • PSC=71ARR=65535,在 72 MHz 下得到 CK_CNT=1 MHz,即 CNT 每 1 µs +1

    • 溢出周期为 (65535+1)/1MHz = 65.536 ms,超过此时间 CNT 回到 0 触发 更新中断

  2. 两次边沿确定时间窗

    • 初始捕获“下降沿”(按钮按下瞬间,假定按下把 PA1 拉低)。

    • 第一次捕获到下降沿:把 CNT 置 0,同时把捕获极性改为“上升沿”(期待按钮释放)。

    • 第二次捕获到上升沿:读取 CCR2(释放瞬间锁存的 CNT 值)。

    • 这段时间就是“按下持续时间”,但可能跨越多个溢出。

  3. 跨溢出计数

    • PeriodElapsedCallback(更新中断)里,每溢出一次 timeout_cnt++

    • 总时间(µs)= timeout_cnt * (ARR+1) + CCR2

    • 本例 ARR+1=65536,单位是 1µs,因此最后直接打印 us

  4. 抗抖与健壮性

    • 抖动可能导致在按下/释放附近多次进中断。做法:

      • 设置 ICFilter>0 进行硬件数字滤波;

      • 软件上在第一次边沿后立即改极性且短暂禁止计数/捕获(你已 __HAL_TIM_DISABLE/ENABLE)。

    • 避免在 ISR 里 printf(容易阻塞/丢中断),推荐置标志,主循环打印。


额外建议

  • timeout_cnt/last_cnt 扩成 32 位(防极端长按),并在 printf%lu

  • 输入滤波ic_config.ICFilter = 8~12,明显改善按键抖动;或外部 RC + 施密特触发/软件去抖。

  • 另一种写法:用 从模式(Slave Mode)+ 触发复位 自动在下降沿清 CNT,上升沿捕获 CCR,结构更优雅(RM0008:SMCR.SMS=Reset Mode,触发源 TS=TI2FP2),就不必手工改极性/清 CNT。

ic_init(65536 - 1, 72 - 1);为什么是65536 - 1,可以是其他数据吗?

简短说:ARR = 65536 - 1 是把 TIM2 的 16 位计数范围用满。在你把 PSC=72-1 配成 1 MHz 计数(1 µs/计数) 时,ARR=65535 代表溢出周期

这样既能保留 1 µs 分辨率(由 PSC 决定),又让“更新中断(溢出)”不那么频繁,CPU 负担更小,同时单次捕获的计数范围最大

能不能不是 65536-1?当然可以

ARR 可以设成其它值,但要理解它带来的权衡PSC 不变、仍是 1 µs/计数):

  • 分辨率(最小时间单位):由 PSC 决定,与你怎么设 ARR 无关。你现在就是 1 µs。

  • 更新中断频率:由 ARR 决定。ARR 越小,更频繁溢出→中断更密→CPU 开销更大;ARR 越大,中断更少。

  • 单次“块”的时间:我们用 溢出次数 * (ARR+1) + CCR 算总时间。ARR 越小,每块时间越短,乘法的块大小变小,但总精度仍是 1 µs(由 PSC 保证)。

  • 可测最大时长(在你当前 uint16_t timeout_cnt 下)

  • 如果 ARR=65535 → 约 71.6 分钟

  • 如果 ARR=9999(10 ms 溢出)→ 约 10.9 分钟

  • 如果 ARR=999(1 ms 溢出)→ 约 65.5 秒

选择建议:

  • 减轻中断负担、又不担心超长按 → 用 大 ARR(如 65535)

  • 更快知道溢出、或对极端长按不敏感 → 可用较小 ARR(比如 9999/999)。

  • 分辨率仍是 1 µs,不会因改变 ARR 而变粗。

改了 ARR,要同步改代码里的计算

现在打印里写死了 65536

printf("按下时间:%d us\r\n", capture_status.timout_cnt * 65536 + last_cnt);

换 ARR 后必须改为通用写法(推荐这样写,永远正确):

void pressed_time_get(void)
{if (capture_status.succeed_flag == 1){uint32_t arrp1 = (uint32_t)__HAL_TIM_GET_AUTORELOAD(&ic_handle) + 1U;uint32_t total_us = (uint32_t)capture_status.timout_cnt * arrp1 + (uint32_t)last_cnt;printf("按下时间:%lu us\r\n", total_us);memset(&capture_status, 0, sizeof(capture_status));}
}

同时把 timeout_cnt/last_cnt 至少按上面这样在计算处提升到 32 位,避免乘加溢出。

什么时候需要把 ARR 改小?

  • 想让更新中断更频繁,例如配合某些“超时保护”逻辑(更细粒度地知晓过长按)。

  • 确实不需要超过若干秒/分钟的最大测量窗口。

  • 想用“溢出的节拍”去做额外的心跳逻辑。

反之,如果你只是想稳定测时、CPU 中断压力要小,ARR=65535 是很好的选择

小结

  • ARR=65536-1 是为了最大计数范围 + 最少更新中断;不是强制。

  • 可以换其它 ARR,分辨率仍然是 1 µs(由 PSC 决定),但要权衡更新中断频率与最大可测时长,并把打印计算改成 overflows * (ARR+1) + CCR通用公式

基于 HAL 库、用 TIM2_CH1(PA0)测量输入 PWM 的频率与占空比

思路采用**“PWM 输入模式”:把 CH1 配成上升沿直连**、CH2 配成下降沿间接,再把定时器设成从模式 Reset,使每个上升沿复位计数器;这样:

  • CCR1 自动锁存周期计数(两次上升沿间的计数);

  • CCR2 自动锁存高电平计数(上升→下降间的计数)。

计数时钟设为 1 MHz(1us/计数)PSC=71(72 MHz/(71+1)=1 MHz),ARR=0xFFFF(防溢出)。

pwm.c(输入 PWM 测量模块)

// pwm.c — TIM2_CH1 输入捕获测 PWM 的频率/占空比(HAL)
// 引脚:PA0 = TIM2_CH1(默认映射)
// 分辨率:1 us/计数(PSC=71),周期/高电平都以“计数值”采集#include "stm32f1xx_hal.h"
#include <stdint.h>// ====== 模块内部状态 ======
static TIM_HandleTypeDef htim2;                 // TIM2 句柄
static volatile uint32_t g_tick_hz = 1000000U;  // 计数时钟(Hz),由 PSC 计算得出
static volatile uint32_t g_period_cnt = 0;      // 周期计数(CCR1)
static volatile uint32_t g_high_cnt   = 0;      // 高电平计数(CCR2)
static volatile uint8_t  g_ready      = 0;      // 新数据就绪标志// ====== 对外 API 原型(给 main.c 用)======
void pwm_input_init_TIM2_CH1(uint16_t psc, uint16_t arr);
int  pwm_input_read(float *freq_hz, float *duty);// ====== MSP:底层时钟/GPIO/NVIC 初始化(由 HAL_TIM_IC_Init 调用)======
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM2){__HAL_RCC_TIM2_CLK_ENABLE();      // TIM2 时钟__HAL_RCC_GPIOA_CLK_ENABLE();     // GPIOA 时钟// PA0 -> TIM2_CH1 输入(F1 无 AF-Input,直接普通输入即可)GPIO_InitTypeDef io = {0};io.Pin  = GPIO_PIN_0;io.Mode = GPIO_MODE_INPUT;        // 输入模式io.Pull = GPIO_PULLDOWN;          // 下拉(若信号源开路时默认为低)HAL_GPIO_Init(GPIOA, &io);// 中断优先级与使能(TIM2:更新/捕获共用同一 IRQ 入口)HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);HAL_NVIC_EnableIRQ(TIM2_IRQn);}
}// ====== 初始化:配置 PWM 输入模式(CH1=上升沿直连,CH2=下降沿间接;从模式 Reset)======
void pwm_input_init_TIM2_CH1(uint16_t psc, uint16_t arr)
{// 1) 配置定时器时基htim2.Instance = TIM2;htim2.Init.Prescaler         = psc;                 // 72MHz/(psc+1) = 1MHz when psc=71htim2.Init.CounterMode       = TIM_COUNTERMODE_UP;  // 向上计数htim2.Init.Period            = arr;                 // ARR(溢出保护)htim2.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;HAL_TIM_IC_Init(&htim2);// 计算计数时钟(tick 频率),考虑 F1 APB1 的“×2 定时器时钟”规则// 这里直接用 PSC 推导出的 1MHz;若你用其它 PSC,可改为公式计算:// g_tick_hz = TIM2CLK / (PSC+1)// 更稳妥:读取 PCLK1 并判断 APB1 分频是否为 1,然后乘以 2{uint32_t pclk1 = HAL_RCC_GetPCLK1Freq();uint32_t timclk = pclk1;// APB1 分频不为 1 时,定时器时钟 = 2 * PCLK1(F1 特性)if ((RCC->CFGR & RCC_CFGR_PPRE1) != RCC_CFGR_PPRE1_DIV1) {timclk = pclk1 * 2U;}g_tick_hz = timclk / (uint32_t)(psc + 1U);}// 2) 配置 CH1 为“上升沿 + 直连 TI1”(周期捕获)TIM_IC_InitTypeDef sConfigIC = {0};sConfigIC.ICPolarity  = TIM_ICPOLARITY_RISING;      // 捕获上升沿sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;   // 直连 TI1sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;             // 不对边沿再分频sConfigIC.ICFilter    = 4;                          // 简单数字滤波(0..15),抗抖/毛刺HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);// 3) 配置 CH2 为“下降沿 + 间接 TI1”(高电平捕获)// 间接选择会把 CH2 与 TI1 绑定,用来在相反极性边沿锁存高电平宽度sConfigIC.ICPolarity  = TIM_ICPOLARITY_FALLING;     // 捕获下降沿sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; // 间接选择 TI1sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;sConfigIC.ICFilter    = 4;HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2);// 4) 从模式:Reset(每个上升沿复位计数器),触发源:TI1FP1TIM_SlaveConfigTypeDef sSlave = {0};sSlave.SlaveMode     = TIM_SLAVEMODE_RESET;   // 触发就复位 CNTsSlave.InputTrigger  = TIM_TS_TI1FP1;         // 触发源 = TI1 上升沿滤波后的信号sSlave.TriggerPolarity     = TIM_TRIGGERPOLARITY_NONINVERTED;sSlave.TriggerPrescaler    = TIM_TRIGGERPRESCALER_DIV1;sSlave.TriggerFilter       = 0;HAL_TIM_SlaveConfigSynchro(&htim2, &sSlave);// 5) 启动捕获(带中断),两路都要开HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);   // 周期捕获HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);   // 高电平捕获
}// ====== 中断入口:统一交给 HAL 分发 ======
void TIM2_IRQHandler(void)
{HAL_TIM_IRQHandler(&htim2);
}// ====== 捕获回调:一到“上升沿捕获”(CH1)就读 CCR1/CCR2,更新测量值 ======
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance != TIM2) return;// HAL 会在回调前设置活动通道,可据此判断当前是哪一路触发if (HAL_TIM_GetActiveChannel(htim) == HAL_TIM_ACTIVE_CHANNEL_1){uint32_t period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 周期计数uint32_t high   = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); // 高电平计数if (period != 0U) {                 // 防止除零g_period_cnt = period;g_high_cnt   = (high <= period) ? high : period; // 夹紧g_ready      = 1;               // 标记:有新数据}}
}// ====== 读取一次测量结果:freq(Hz)、duty(0..1)。返回1=有新值,0=无新值 ======
int pwm_input_read(float *freq_hz, float *duty)
{if (!g_ready) return 0;g_ready = 0;// 频率 = 计数时钟 / 周期计数;占空比 = 高电平计数 / 周期计数*freq_hz = (float)g_tick_hz / (float)g_period_cnt;*duty    = (g_period_cnt == 0U) ? 0.0f : ((float)g_high_cnt / (float)g_period_cnt);return 1;
}

main.c(演示:初始化 + 打印频率/占空比)

// main.c — 初始化系统时钟/串口,启动 PWM 输入测量并打印结果#include "stm32f1xx_hal.h"
#include <stdio.h>// 你的工程里若已有 sys.h/uart1.h,可替换为现有接口
static void SystemClock_Config(void);
static void USART1_Init_115200(void);// 来自 pwm.c 的 API
void pwm_input_init_TIM2_CH1(uint16_t psc, uint16_t arr);
int  pwm_input_read(float *freq_hz, float *duty);// printf 重定向到 USART1(简易版)
int fputc(int ch, FILE *f) {while ((USART1->SR & (1<<7)) == 0) {}   // TXEUSART1->DR = (uint8_t)ch;return ch;
}int main(void)
{HAL_Init();SystemClock_Config();USART1_Init_115200();printf("PWM input measure demo (TIM2_CH1@PA0)\r\n");// 1us/计数:PSC=71;ARR=0xFFFF(最大量程、减少溢出)pwm_input_init_TIM2_CH1(71, 0xFFFF);while (1){float f, d;if (pwm_input_read(&f, &d)) {printf("Freq = %.2f Hz, Duty = %.1f %%\r\n", f, d * 100.0f);}HAL_Delay(50); // 打印节流}
}

关键点速记

  • 接法:把被测 PWM 信号接到 PA0(TIM2_CH1)。若电平空闲可能漂移,给合适的上/下拉。

  • 原理

    • 从模式 Reset + 触发源 TI1 上升沿 → 每次上升沿复位 CNT,CCR1 得到完整周期计数。

    • CH2 选 INDIRECTTI + FALLING → 在下降沿把“上升至下降的计数”锁到 CCR2(高电平宽度)。

    • 频率=tick_hz / CCR1占空比=CCR2 / CCR1

  • 分辨率:由 PSC 决定。PSC=71 → 1 MHz,每计数 1 µs。

  • 量程:最大可测周期约 (ARR+1)/tick_hz。本例 ≈ 65.536 ms(≈15.26 Hz 最低频率)。更低频可把 PSC 降低或 ARR 提大(F1 通用定时器 16 位)。

  • 抗抖/抗毛刺ICFilter 适当调大(如 4~8)。

  • 无信号/常电平:不会触发回调,不会更新数据;可额外用“更新中断”计超时来判“信号丢失”。

http://www.dtcms.com/a/336344.html

相关文章:

  • 25. 能否创建一个包含可变对象的不可变对象
  • YOLOV5训练自己的数据集并用自己的数据集检测
  • 2025-08-17 李沐深度学习16——目标检测
  • PAT 1068 Find More Coins
  • ACPI TABLE 方式加载device driver--以spi controller为例
  • 认识信号量机制、以及用信号量来实现进程互斥于进程同步
  • 计算机网络 TCP time_wait 状态 详解
  • VirtualBox-4.3.10-93012-Win.exe 安装教程附详细步骤(附安装包下载)
  • 为何她总在关键时“失联”?—— 解密 TCP 连接异常中断
  • TensorRT-LLM.V1.1.0rc1:Dockerfile.multi文件解读
  • LeetCode 刷题【44. 通配符匹配】
  • 多墨智能-AI一键生成工作文档/流程图/思维导图
  • 《WINDOWS 环境下32位汇编语言程序设计》第3章 使用MASM
  • Redis面试精讲 Day 23:Redis与数据库数据一致性保障
  • 什么是回表?
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘scikit-image’问题
  • Hooks useState的使用
  • leetcode热题100——day33
  • 视频内容提取与AI总结:提升学习效率的实用方法
  • 【深度学习新浪潮】近三年图像处理领域无监督学习的研究进展一览
  • 科目二的四个电路
  • 《Vuejs设计与实现》第 14 章(内建组件和模块)
  • 概率dp|math
  • Android中切换语言的方法
  • 基于Netty的高并发WebSocket连接管理与性能优化实践指南
  • ReactNode 类型
  • 第12章《学以致用》—PowerShell 自学闭环与实战笔记
  • “让机器人更智慧 让具身体更智能”北京世界机器人大会行业洞察
  • Python 调试工具的高级用法
  • OJ目录饿