STM32C542开发.5--定时器配置输入捕获测量频率概述视频教学样品申请源码下载硬件准备参考程序串口配置TIM1基础参数设置工程编码初始配置添加头文件printf 重定向TIM15 计数频率配置捕获变量说明处理计数器溢出启动输入捕获中断输入捕获回调函数增加 TIM15 初始化和启动主循环演示结果概述在前面的实验中已经使用 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_SENSORhttps://gitee.com/CoreMaker/STM32C542_SENSOR串口配置查看原理图PA9和PA10设置为开发板的串口。在左侧 Peripherals 中选择 Connectivity → USART1Mode 选择 Async表示配置为异步串口模式Function used by the component 显示为 UART说明 USART1 在异步模式下使用 UART HAL 驱动串口参数配置为115200 波特率、8 位数据位、无校验、1 位停止位、收发模式GPIO TxUSART1_TX 选择 PA9GPIO RxUSART1_RX 选择 PA10PA9 / PA10 均配置为 Alternate 复用功能模式Pull 选择 No pull-up and no pull-downOutput type 选择 Push pullSpeed 选择 LowTIM1基础参数使用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信号相对干净因此不需要额外滤波。如果后续测量外部噪声较大的信号可以根据实际情况开启输入滤波。本实验最关键的配置是PolarityBoth 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-IDRGPIO_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进入工程属性设置在工程属性中选择 ResourceText file encoding 选择 Other编码格式输入 GBK点击 Apply and Close 保存设置初始配置初始配置修改如下状态。/** * brief: The application entry point. * retval: none but we specify int to comply with C99 standard */intmain(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, CH125%%, CH375%%\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 中添加头文件#includemx_usart1.h#includestring.hprintf 重定向为了让 printf() 输出到 USART1需要重写 _write() 函数。GCC 工程中printf() 底层会调用 _write() 输出字符因此只需要在 _write() 中调用 HAL_UART_Transmit()就可以把 printf() 的内容通过串口发送出去。int_write(intfile,char*ptr,intlen){hal_uart_handle_t*huart1mx_usart1_uart_gethandle();if(huart1!NULL){HAL_UART_Transmit(huart1,ptr,len,1000);}returnlen;}TIM15 计数频率配置TIM15_CLK_HZTIM15 输入时钟频率这里为 144 MHzTIM15_COUNTER_HZ希望 TIM15 的计数频率为 1 MHzTIM15_PRINT_INTERVAL每累计 50 次测量后打印一次结果#defineTIM15_CLK_HZ144000000UL#defineTIM15_COUNTER_HZ1000000UL#defineTIM15_PRINT_INTERVAL50U为了让 TIM15 计数频率变成 1 MHz需要设置 Prescaler#defineTIM15_PSC((TIM15_CLK_HZ/TIM15_COUNTER_HZ)-1U)#defineTIM15_ARR0xFFFFU计算过程如下TIM15 计数频率TIM15_CLK_HZ/(PSC1)TIM15_CLK_HZ144MHz TIM15_COUNTER_HZ1MHz PSC144,000,000/1,000,000-1143因此当 TIM15_PSC 143 时TIM15 的计数频率为 1 MHz也就是1个计数1us这样在测量 PWM 信号时非常方便。例如输入 PWM 为 1000 Hz则周期为 1 ms也就是 1000 us理论上 TIM15 两次上升沿之间的捕获差值约为 1000。捕获变量说明代码中定义了一组变量用于保存上升沿、下降沿和测量结果这些变量的作用如下tim15_rise_last上一次上升沿捕获值tim15_rise_now当前上升沿捕获值tim15_fall_now当前下降沿捕获值tim15_period_countPWM 周期计数值tim15_high_countPWM 高电平计数值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测量次数计数staticvolatileuint32_ttim15_rise_last0;staticvolatileuint32_ttim15_rise_now0;staticvolatileuint32_ttim15_fall_now0;staticvolatileuint32_ttim15_period_count0;staticvolatileuint32_ttim15_high_count0;staticvolatileuint32_ttim15_print_freq0;staticvolatileuint32_ttim15_print_duty_x100;staticvolatileuint32_ttim15_print_period0;staticvolatileuint32_ttim15_print_high0;staticvolatileuint8_ttim15_print_flag0;staticvolatileuint8_ttim15_rise_valid0;staticvolatileuint8_ttim15_fall_valid0;staticvolatileuint32_ttim15_measure_count0;处理计数器溢出由于 TIM15 是 16 位计数器Counter period 设置为 0xFFFF计数器从 0 计数到 65535 后会重新回到 0。因此两次捕获值相减时需要考虑溢出情况。代码中封装了 TIM15_GetDiff() 函数staticuint32_tTIM15_GetDiff(uint32_tstart,uint32_tend){if(endstart){returnend-start;}else{return(TIM15_ARR1U-start)end;}}本文没有在 STM32CubeMX2 中固定 TIM15 的 Prescaler 和 Counter period而是在代码中通过 HAL2 函数动态配置 。这个函数主要完成三件事使用 HAL_TIM_SetPrescaler() 设置 TIM15_PSC使用 HAL_TIM_SetPeriod() 设置 TIM15_ARR使用 HAL_TIM_SetCounter() 将 TIM15 计数器清零statichal_status_tTIM15_IC_SetPSC_ARR(uint32_tpsc,uint32_tarr){hal_tim_handle_t*htim15mx_tim15_gethandle();if(htim15NULL){returnHAL_ERROR;}if(HAL_TIM_SetPrescaler(htim15,psc)!HAL_OK){returnHAL_ERROR;}if(HAL_TIM_SetPeriod(htim15,arr)!HAL_OK){returnHAL_ERROR;}if(HAL_TIM_SetCounter(htim15,0)!HAL_OK){returnHAL_ERROR;}returnHAL_OK;}启动输入捕获中断完成 TIM15 基础参数配置后需要启动 TIM15_CH1 输入捕获中断 。HAL_TIM_IC_StartChannel_IT()启动 TIM15_CH1 输入捕获中断HAL_TIM_Start()启动 TIM15 计数器statichal_status_tTIM15_IC_Start(void){hal_tim_handle_t*htim15mx_tim15_gethandle();if(htim15NULL){returnHAL_ERROR;}/* * 鍚姩 TIM15_CH1 杈撳叆鎹曡幏涓柇 */if(HAL_TIM_IC_StartChannel_IT(htim15,HAL_TIM_CHANNEL_1)!HAL_OK){returnHAL_ERROR;}/* * 鍚姩 TIM15 璁℃暟鍣 */if(HAL_TIM_Start(htim15)!HAL_OK){returnHAL_ERROR;}returnHAL_OK;}输入捕获回调函数TIM15_CH1 在 STM32CubeMX2 中配置为 Both edges也就是上升沿和下降沿都会触发输入捕获中断。由于 TIM15_CH1 配置为 Both edges所以同一个回调函数既会响应上升沿也会响应下降沿。为了判断当前捕获的是上升沿还是下降沿代码中读取 PA7 当前电平pin_level (GPIOA-IDR GPIO_IDR_ID7);判断逻辑如下如果 PA7 当前为高电平说明刚刚发生的是上升沿如果 PA7 当前为低电平说明刚刚发生的是下降沿voidHAL_TIM_InputCaptureCallback(hal_tim_handle_t*htim,hal_tim_channel_tchannel){uint32_tcapture_value;uint32_tperiod;uint32_thigh;uint32_tfreq;uint32_tduty_x10;uint32_tpin_level;if((htim!mx_tim15_gethandle())||(channel!HAL_TIM_CHANNEL_1)){return;}capture_valueHAL_TIM_IC_ReadChannelCapturedValue(htim,HAL_TIM_CHANNEL_1);/* * 读取 PA7 当前电平 * 如果当前为高电平说明刚刚发生的是上升沿 * 如果当前为低电平说明刚刚发生的是下降沿。 */pin_level(GPIOA-IDRGPIO_IDR_ID7);if(pin_level!0U){/* * 上升沿 */if(tim15_rise_valid0U){tim15_rise_lastcapture_value;tim15_rise_valid1U;tim15_fall_valid0U;}else{tim15_rise_nowcapture_value;periodTIM15_GetDiff(tim15_rise_last,tim15_rise_now);if((period!0U)(tim15_fall_valid!0U)){highTIM15_GetDiff(tim15_rise_last,tim15_fall_now);if(highperiod){freqTIM15_COUNTER_HZ/period;/* * duty_x10 放大 10 倍方便打印一位小数 * 例如 253 表示 25.3% */duty_x10(high*1000U)/period;tim15_period_countperiod;tim15_high_counthigh;tim15_measure_count;if(tim15_measure_countTIM15_PRINT_INTERVAL){tim15_measure_count0;tim15_print_freqfreq;tim15_print_duty_x10duty_x10;tim15_print_periodperiod;tim15_print_highhigh;tim15_print_flag1;}}}tim15_rise_lasttim15_rise_now;tim15_fall_valid0U;}}else{/* * 下降沿 */if(tim15_rise_valid!0U){tim15_fall_nowcapture_value;tim15_fall_valid1U;}}}增加 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_tfreq;uint32_tduty_x10;uint32_tperiod;uint32_thigh;__disable_irq();tim15_print_flag0;freqtim15_print_freq;duty_x10tim15_print_duty_x10;periodtim15_print_period;hightim15_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:freq1000Hz,duty25.0%,period1000,high250如果输入信号为 1000 Hz、75% 占空比理论打印结果接近TIM15 measured:freq1000Hz,duty75.0%,period1000,high750演示结果检测到的频率逻辑分析仪和串口打印基本上都是1025Hz正占空比为75%。