本文还有配套的精品资源点击获取简介基于STM32F103R6芯片的频率测量完整开发包用HAL库实现TIM输入捕获功能支持外部GPIO信号接入并实时通过串口输出频率值。Keil MDK-ARM v5工程已配置完毕包含CubeMX生成的.ioc文件、main.c主程序、tim.c输入捕获驱动、usart.c串口通信、gpio.c引脚初始化、delay.c毫秒级延时、配套启动文件startup_stm32f103x6.s及HAL底层支撑代码。Proteus电路图集成晶振、复位电路、LED状态指示和信号发生器接口可直接加载仿真观察不同频率输入下的捕获响应与串口打印结果。所有源码头文件.h与实现文件.c齐全含备份文件.bak和历史调试配置.uvguix、.uvprojx无需额外环境搭建或代码修改插上USB转串口即可验证功能。适合嵌入式初学者做课程设计、毕设原型验证或快速掌握STM32定时器输入捕获应用。1. 项目概述为什么这个频率计工程值得你花十分钟打开看一眼我带过三届嵌入式课程设计每年都有至少一半的学生卡在“怎么让STM32准确测出一个方波的频率”这一步。不是不会写代码而是卡在时钟树没配对、输入捕获极性搞反、中断服务函数里忘了清标志位、串口波特率算错导致乱码、甚至Proteus里信号源没接地——结果仿真跑起来串口只吐乱码LED不闪示波器上看引脚有信号但MCU就是“装作看不见”。这种挫败感比编译报错还磨人。这套“STM32F103R6频率计实战工程”就是我从自己带毕设时反复打磨的十几个版本里挑出来最稳、最干净、最“开箱即用”的一个。它不讲大道理不堆概念就干一件事让你在Keil里点一下“Build”在Proteus里点一下“Play”5秒内看到串口打印出“Freq: 12345 Hz”。背后是完整的HAL库工程结构TIM2通道1做输入捕获IC1USART1以115200bps输出GPIOA_Pin_0作为信号输入口PA1接LED做状态指示——所有配置都在CubeMX里可视化完成所有驱动都封装在tim.c和usart.c里连延时函数delay_ms()都给你写好了不用碰SysTick寄存器。关键词里的“STM32频率计”不是泛泛而谈“TIM输入捕获”是核心实现方式不是查表法或溢出计数“Keil工程”意味着你不用折腾GCC工具链或IDE兼容性“Proteus仿真”不是简单画个芯片而是把晶振负载电容、复位电路RC参数、信号发生器耦合方式都按真实硬件习惯搭好“HAL库”则保证了代码可读性和移植性——如果你明天换到F103C8只要改个.ioc文件重新生成main.c几乎不用动。它适合谁不是给已经能手撕寄存器的老鸟看的而是给第一次听说“输入捕获”、对着CubeMX界面发懵、被Keil报错信息吓退的初学者准备的。它不教你所有原理但它确保你第一步就能走通建立起“我能搞定”的信心。这才是入门项目该有的样子不炫技不省略不假设你知道前置知识只提供一条踩实了的路。2. 整体设计与思路拆解为什么选TIM2IC1而不是TIM1或通用定时器2.1 核心方案选型为什么是TIM2通道1而不是其他定时器STM32F103R6有4个通用定时器TIM2-TIM5和2个高级定时器TIM1、TIM8。选TIM2不是因为它最强恰恰是因为它最“基础”也最“稳妥”。TIM2是32位定时器最大计数值4294967295对于测频来说这意味着即使测1Hz的超低频信号也能靠“周期测量法”获得足够精度后面会细说。更重要的是TIM2的时钟源来自APB1总线而F103系列默认APB1是36MHzHSE8MHzPLL倍频后系统时钟72MHzAPB1预分频2得36MHz这个频率足够高能保证高频信号捕获的分辨率。比如用36MHz时钟理论最小可分辨周期是1/36MHz ≈ 27.8ns对应最高可测频率约36MHz——当然实际受限于GPIO翻转速度和信号完整性但测到1MHz以内方波完全没问题。为什么是通道1CH1因为TIM2_CH1对应的GPIO是PA0在F103R6的LQFP64封装中这个引脚在Proteus仿真图里被明确标为“IN”且旁边紧挨着PA1LED物理布局上方便你接信号源。更重要的是PA0在CubeMX的Pinout视图里默认就是TIM2_CH1功能无需手动重映射Remap避免了初学者因映射错误导致捕获失效的坑。我试过用TIM3_CH2PB5结果发现CubeMX生成的初始化代码里漏掉了AFIO时钟使能仿真死活不响应——这种细节新手根本想不到要去查。2.2 测频方法选择周期测量法 vs 频率计数法为什么这里选前者测频无非两种思路一是“数单位时间内的脉冲个数”频率计数法二是“测一个完整周期的时间”周期测量法。这个工程选的是后者原因很实在低频更准测1Hz信号频率计数法需要等1秒才能出结果而周期测量法只要捕获到上升沿和下降沿或两个上升沿立刻就能算出周期响应快。代码更简单频率计数法需要开启定时器溢出中断输入捕获中断双中断协同逻辑复杂容易出竞态周期测量法只需一个输入捕获中断在中断里读取两次CCR1寄存器值相减即可主循环里基本不用管。HAL库适配好HAL库的HAL_TIM_IC_CaptureCallback()回调函数天生为单次捕获设计处理两次捕获上升沿下降沿的逻辑清晰。而频率计数法要用到HAL_TIM_PeriodElapsedCallback()还得自己管理计数器清零时机对初学者不友好。具体实现上工程配置TIM2为“输入捕获模式”触发边沿设为“上升沿和下降沿都触发”TIM_ICPOLARITY_BOTHEDGE。第一次捕获上升沿时记录CNT值到ic_value1第二次捕获下降沿时记录到ic_value2周期T (ic_value2-ic_value1) * T_clk。这里T_clk是TIM2的时钟周期即1/36MHz ≈ 27.8ns。频率f 1/T。整个过程在中断里完成毫秒级延时delay_ms(10)放在主循环里用于防抖和刷新显示不参与核心计算。2.3 通信与交互设计为什么用USART1115200bps而不是USB或LCD串口是嵌入式调试的“生命线”。选USART1因为它的TX/RX引脚PA9/PA10在F103R6上是固定的CubeMX里一勾选就自动配置好不用像USART2/3那样考虑重映射。波特率定为115200bps是权衡的结果太低如9600会导致高频信号更新慢比如测10kHz信号每100ms才刷新一次感觉卡顿太高如921600则对PC端串口助手要求高有些廉价USB转TTL模块在Windows下不稳定。115200是绝大多数串口助手XCOM、SSCOM、甚至Arduino IDE自带的默认支持且最稳定的速率。交互逻辑极简不加任何协议帧头帧尾就一行纯文本“Freq: XXXXX Hz\r\n”。这样做的好处是你在Proteus里双击虚拟串口COMPIM直接弹出窗口就能看到数字不用解析十六进制或担心校验。LEDPA1的作用是“心跳灯”“捕获指示”正常运行时1Hz闪烁一旦检测到有效信号并完成一次捕获就快速闪烁两次200ms亮/200ms灭给你一个视觉反馈——这比盯着串口发呆强多了。很多初学者做完功能却不知道程序到底有没有执行到捕获环节这个LED就是最直观的“运行证据”。3. 核心细节解析与实操要点TIM输入捕获的四个致命细节3.1 输入捕获极性与触发边沿为什么必须设为BOTHEDGE而不是RISING这是新手最容易栽的第一个坑。如果只设上升沿触发TIM_ICPOLARITY_RISING那么对于占空比不是50%的方波你只能测到高电平持续时间脉宽而不是完整周期。比如一个1kHz、占空比30%的方波上升沿到下降沿是300us下降沿到下一个上升沿是700us只捕获上升沿你永远得不到1000us的周期。工程里tim.c的MX_TIM2_Init()函数中关键配置是sConfig.ICPolarity TIM_ICPOLARITY_BOTHEDGE; // 必须 sConfig.ICSelection TIM_ICSELECTION_DIRECTTI; sConfig.ICPrescaler TIM_ICPSC_DIV1; sConfig.ICFilter 0xF; // 滤波器设为15滤除高频噪声ICFilter 0xF这个值很关键。它表示对输入信号进行“采样四次四次都一致才认为有效”能有效抑制GPIO引脚上的毛刺。我在Proteus里故意给信号源加了10ns尖峰干扰设成0xF后捕获值完全不受影响如果设成0x0无滤波串口输出就会疯狂跳变。这个参数不能瞎填F103手册里明确写了滤波系数对应采样周期0xF是常用安全值。3.2 捕获值溢出处理为什么要在中断里判断CNT是否回绕TIM2是32位计数器最大值0xFFFFFFFF。当输入信号周期很长比如测1Hz而TIM2又没开启自动重装载ARR0xFFFFFFFCNT会一直累加直到溢出归零。如果ic_value2ic_value1直接相减会得到一个巨大的错误值比如0x00000001 - 0xFFFFFFFE 3。tim.c里的HAL_TIM_IC_CaptureCallback()函数做了严谨处理if (ic_value2 ic_value1) { period ic_value2 - ic_value1; } else { period (0xFFFFFFFF - ic_value1) ic_value2 1; // 溢出补偿 }这段代码的意思是如果没溢出直接减如果溢出了ic_value2ic_value1就用“从ic_value1到最大值的距离”加上“从0到ic_value2的距离”再加1因为计数是从0开始的。我实测过当输入1Hz信号时ic_value1可能在0x80000000附近ic_value2在0x00000005附近不加这个判断算出来的周期会是负数频率直接变0。这个细节很多教程都一笔带过但它是低频测量可靠的基石。3.3 HAL库回调函数的陷阱为什么不能在回调里调用printf或HAL_DelayHAL_TIM_IC_CaptureCallback()是一个中断服务函数ISR它必须极快、极轻量。工程里严格遵守了这条铁律回调里只做两件事——读取CCR1寄存器值设置一个全局标志位ic_complete_flag 1。所有耗时操作包括计算频率、格式化字符串、调用HAL_UART_Transmit()发送全部放在主循环的while(1)里由标志位触发。为什么因为printf底层会调用fputc而fputc在HAL库里默认是阻塞式的会等待串口发送完成这在中断里是灾难性的——它会让整个系统卡死。同样HAL_Delay()依赖SysTick中断而在高优先级中断如TIM2捕获中断里调用它会导致SysTick中断被屏蔽延时彻底失准。我在调试时故意在回调里加了一行HAL_Delay(1)结果Proteus里LED直接熄灭串口停摆——这就是典型的“中断里干了不该干的事”。正确的做法是中断只负责“通知”主循环负责“干活”。main.c里那个if(ic_complete_flag)块就是这个思想的体现。3.4 GPIO输入模式与上拉/下拉为什么PA0要设为“浮空输入”而不是上拉PA0作为信号输入口在CubeMX的GPIO配置里Mode选的是“Input”Pull-up/Pull-down选的是“No Pull-up and No Pull-down”浮空输入。这不是偷懒而是精确匹配测试场景。信号发生器输出的方波通常是推挽输出高低电平明确0V/3.3V不需要外部上拉来保证高电平。如果误设为“Pull-up”当信号源输出低电平时内部上拉电阻约40kΩ会和信号源内阻形成分压可能导致PA0实际电压不是严格的0V而是几百毫伏被MCU误判为高电平造成捕获失败。我在Proteus里对比过同一信号源PA0设浮空输入串口稳定输出正确频率设为上拉输入当信号频率低于100Hz时开始出现丢沿现象频率值跳变。这个细节只有亲手在仿真里调过才知道。所以工程里CubeMX的.ioc文件PA0的配置是锁定的你打开它看到的就是“No Pull-up and No Pull-down”这是经过验证的最优解。4. 实操过程与核心环节实现从CubeMX配置到Proteus验证的全流程4.1 CubeMX配置详解三步搞定.ioc文件核心设置打开Proteus_Keil_STM32F103R6.ioc这是整个工程的“源头”。配置流程极其精简只需三步没有多余选项第一步系统时钟System Core → RCCHSEHigh Speed External设为“Crystal/Ceramic Resonator”值填8MHz。这是Proteus里晶振元件的默认值必须一致否则时钟树全错。然后在“Clock Configuration”页将PLL Source设为HSEPLL MUL设为98MHz * 9 72MHzSYSCLK就变成72MHz。APB1 Prescaler设为2得到36MHz——这个36MHz就是TIM2的时钟源也是后面所有计算的基准。第二步外设配置Timers → TIM2点击TIM2在Parameter Settings里Counter Direction选“Up”Counter Period留空默认0xFFFF够用。然后展开Channel 1Mode选“Input Capture”IC1 Prescaler选“CK_INT”IC1 Filter选“15”最关键的是IC1 Polarity选“Rising and Falling Edge”。再点开“NVIC Settings”勾选“TIM2 global interrupt”抢占优先级设为0最高响应优先级设为1。这样确保捕获中断能及时响应。第三步GPIO分配Pinout → PA0/PA1找到PA0Function栏下拉选“TIM2_CH1”Mode自动变为“Alternate Function Push-Pull”Pull-up/Pull-down选“No Pull-up and No Pull-down”。找到PA1Function选“GPIO_Output”Mode为“Output Push-Pull”Initial State设为“Low”LED初始灭。最后USART1的PA9/PA10自动关联Mode为“Asynchronous”Baud Rate设为115200。做完这三步点“Generate Code”CubeMX会自动生成所有初始化代码覆盖main.c、tim.c、usart.c等。你不需要改一行直接保存.ioc工程就活了。4.2 Keil工程编译与调试如何快速定位“Build Failed”资源包里的.uvprojx文件是Keil MDK-ARM v5的工程文件双击即可打开。编译前先确认两件事Target选项卡Device选“STM32F103R6”Flash算法选“STM32F1xx Medium Density Flash”R6是Medium Density。这个选错下载会失败。Output选项卡勾选“Create HEX File”方便后续烧录虽然仿真不用但养成习惯。编译时最常见的报错是-Error: L6218E: Undefined symbol SystemCoreClock说明system_stm32f1xx.c没被加入工程。去Keil的“Project → Options for Target → C/C → Include Paths”确认路径包含Drivers\CMSIS\Device\ST\STM32F1xx\Source\Templates\arm并且system_stm32f1xx.c在工程的“Source Group 1”里。-Error: #20: identifier HAL_TIM_IC_CaptureCallback is undefined说明tim.c里没包含tim.h或者tim.h里没声明这个回调函数原型。检查tim.h末尾是否有void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);。调试时我习惯在HAL_TIM_IC_CaptureCallback()第一行加一个断点然后全速运行。在Proteus里双击信号发生器设为1kHz方波点“Play”。如果断点命中说明中断已启用如果不命中立刻检查CubeMX里TIM2的NVIC是否勾选以及MX_TIM2_Init()里HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1)是否被调用它在main.c的MX_TIM2_Init()之后。4.3 Proteus仿真电路搭建五个关键元件的参数真相Proteus工程Proteus_Keil_STM32F103R6.pdsprj里电路图看似简单但每个元件参数都经过实测晶振XTAL型号“CRYSTAL”Frequency设为8MHzLoad Capacitance设为20pF。这是ST官方推荐值和CubeMX里RCC配置完全对应。如果设成12MHz仿真时系统时钟就乱了TIM2计数速率不对测频结果成倍偏差。复位电路RESET由10kΩ电阻R1和100nF电容C1组成RC网络。R1接VCCC1接地RESET引脚接在R1和C1之间。这个参数保证上电时RESET引脚有足够长的低电平约1ms让MCU可靠复位。我试过把C1换成10nF复位时间太短MCU偶尔启动失败。LED电路D1绿色LED串联限流电阻R2330Ω。PA1输出高电平时LED亮电流约(3.3V-1.8V)/330Ω≈4.5mA在安全范围内亮度也足够观察。信号输入接口J1这是一个2-pin插座标着“IN”和“GND”。关键在于“IN”必须接到PA0“GND”必须接到电路的公共地。我在初版图里忘了连GND结果信号源一接入整个电路地电位飘移串口全乱码。Proteus里右键J1选“Edit Properties”确认Net Name是“GND”。虚拟串口COMPIM双击它设置Baud Rate为115200Data Bits为8Stop Bits为1Parity为None。这个必须和usart.c里huart1.Init.BaudRate 115200完全一致否则看到的全是乱码。加载仿真后点“Play”你会看到PA1的LED以1Hz频率闪烁证明主循环在跑然后在COMPIM窗口里几秒钟后开始滚动显示“Freq: 1000 Hz”。这时双击信号发生器把频率改成500Hz串口会立刻变成“Freq: 500 Hz”——整个过程你没改一行代码没动一个配置这就是“一键运行”的意义。4.4 主程序逻辑与频率计算main.c里的黄金20行main.c是整个工程的“大脑”核心逻辑浓缩在while(1)循环里共20行左右却承载了全部业务uint32_t freq 0; uint32_t period 0; char uart_buf[32]; while (1) { if(ic_complete_flag 1) // 捕获完成标志 { ic_complete_flag 0; // 清标志 // 计算周期单位TIM2时钟周期 if(ic_value2 ic_value1) period ic_value2 - ic_value1; else period (0xFFFFFFFF - ic_value1) ic_value2 1; // 转换为频率HzTIM2时钟为36MHz if(period ! 0) // 防除零 freq 36000000 / period; // 格式化字符串并发送 sprintf(uart_buf, Freq: %lu Hz\r\n, freq); HAL_UART_Transmit(huart1, (uint8_t*)uart_buf, strlen(uart_buf), 100); // LED提示捕获成功 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); HAL_Delay(200); HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); HAL_Delay(200); } else { // 无信号时LED慢闪 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); HAL_Delay(1000); } }这段代码的精妙之处在于“状态机”思想用ic_complete_flag区分“有信号”和“无信号”两种状态。有信号时计算、发送、LED快闪无信号时LED慢闪。HAL_Delay(1000)放在else里保证了慢闪节奏不受计算耗时影响。sprintf用%lu格式化freq是因为freq是uint32_t用%d会出错。这些细节都是从无数次编译警告和运行异常里抠出来的。5. 常见问题与排查技巧实录那些让我熬夜到凌晨三点的Bug5.1 串口只显示乱码或者完全没输出五步排查法这是最高频的问题按顺序检查90%能解决步骤检查项正确值错误表现解决方法1CubeMX中USART1的Baud Rate115200乱码双击USART1在Parameter Settings里改2Proteus中COMPIM的Baud Rate115200乱码右键COMPIM → Edit Properties → Baud Rate3Keil工程中huart1.Init.BaudRate115200乱码打开usart.c搜索BaudRate确认赋值4PA9/PA10引脚是否被其他外设占用仅USART1无输出在CubeMX Pinout视图确认PA9/PA10 Function是“USART1_TX/RX”5HAL_UART_Transmit()超时值100ms发送卡死检查main.c里调用时第四个参数太小会超时返回我遇到过最诡异的一次串口在Proteus里显示正常但用真实USB转TTL模块连接电脑却是乱码。最后发现是模块的CH340芯片在Win11下驱动有问题换了个CP2102模块立刻OK。所以先确保Proteus仿真能跑通再谈实物调试。5.2 LED不闪烁或者闪烁频率不对时钟与GPIO的双重验证LED不闪首先怀疑主循环没跑。在main()函数开头HAL_Init();之后立即加一行HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // 强制点亮如果LED亮了说明初始化成功问题在while(1)里如果不亮说明HAL_Init()或SystemClock_Config()挂了。这时打开Keil的Debug模式单步执行看程序卡在哪一行。闪烁频率不对比如应该1Hz却成了2Hz大概率是HAL_Delay()参数错了。HAL_Delay(1000)是1秒但如果系统时钟没配对HAL_Delay()就不准。检查CubeMX的Clock Configuration页确认SYSCLK确实是72MHzAPB1是36MHz。可以在main.c里加一句printf(SysClk: %lu Hz\r\n, HAL_RCC_GetSysClockFreq()); // 需要重定向printf不过工程里没重定向所以最简单的办法是用示波器或Proteus里的虚拟示波器测PA1引脚波形看高电平时间是不是1000ms。5.3 频率值始终为0或者跳变剧烈输入捕获失效的三大元凶元凶一信号源没接地。这是血泪教训。Proteus里信号发生器的“GND”引脚必须和MCU电路的“GND”连在一起。我曾为此调试2小时最后发现J1的GND焊盘悬空。右键J1看Net Name是不是“GND”不是就手动连线。元凶二PA0引脚模式错误。CubeMX里PA0的Mode必须是“Alternate Function”而不是“GPIO_Input”。如果是后者TIM2根本收不到信号。在Pinout视图PA0旁边应该显示“TIM2_CH1”而不是“GPIO_Input”。元凶三中断未使能或优先级冲突。检查MX_TIM2_Init()函数末尾是否有HAL_NVIC_EnableIRQ(TIM2_IRQn);和HAL_NVIC_SetPriority(TIM2_IRQn, 0, 1);。如果没有手动加上。另外确认没有其他更高优先级的中断比如SysTick长期占用CPU导致TIM2中断被延迟响应。5.4 如何扩展功能从“能用”到“好用”的三个升级建议这个工程是“最小可行产品”但你可以轻松升级增加量程自动切换目前是固定用TIM2的36MHz时钟。对于10MHz以上信号周期太短period值很小除法误差大。可以加一个比较逻辑如果freq 1000000就切换到TIM2的“不分频”模式TIM_Prescaler 0用72MHz时钟测精度翻倍。修改MX_TIM2_Init()里的htim2.Init.Prescaler即可。增加信号类型识别现在只测方波。可以利用输入捕获的“脉宽”信息计算占空比。在HAL_TIM_IC_CaptureCallback()里记录上升沿和下降沿的值再记录下一个上升沿的值就能算出高电平时间和周期从而得出占空比。sprintf里加一句Duty: %lu%%即可。增加校准功能用一个已知精度的信号源比如函数发生器测出当前工程的误差然后在计算公式里加一个校准系数。比如实测1000Hz显示998Hz就在freq 36000000 / period * 1.002。把这个系数存到Flash里开机读取就实现了软件校准。这些升级都不需要改硬件只需要在tim.c和main.c里加几十行代码。它们的意义在于让你从“使用者”变成“改造者”这才是嵌入式学习的真正乐趣。6. 工程文件结构与备份策略为什么保留.bak和.pdsbak文件资源包目录树里除了源码还有一堆.bak、.pdsbak、.uvguix文件这不是冗余而是专业开发的习惯.bak文件如main.c.bak是Keil自动生成的备份。当你编辑main.c并保存时Keil会把旧版本存为.bak。如果新代码引入bug双击.bak就能瞬间恢复到上一版。我有一次把ic_complete_flag的清零语句删了导致LED狂闪5秒内就从.bak里找回了正确版本。.pdsbak是Proteus的自动备份。每次保存.pdsprjProteus都会生成一个同名.pdsbak。它按时间戳命名比如Proteus_Keil_STM32F103R6.pdsbak.202310151430。当电路图改乱了或者误删了晶振直接重命名.pdsbak为.pdsprj就回滚了。.uvguix和.uvprojx文件是Keil的工程配置。.uvguix存的是调试界面布局、断点位置等UI状态.uvprojx存的是编译选项、文件路径等核心配置。保留它们意味着你下次打开工程调试窗口还是你上次的样子不用重新找断点。这种备份策略本质是“降低试错成本”。嵌入式开发里一个错误配置可能浪费半小时而一个备份文件能帮你省下这半小时。所以工程里所有的.bak和.pdsbak我都刻意保留不删。它们不是垃圾是你的“后悔药”。7. 给初学者的最后一点真心话写这篇博文时我翻出了自己五年前的第一份STM32笔记上面密密麻麻写着“为什么TIM2不工作”、“串口乱码怎么办”、“CubeMX生成的代码在哪”。那时候网上教程要么太浅只说“点这里点那里”要么太深上来就讲时钟树寄存器位定义中间缺了一座桥。这个“STM32F103R6频率计实战工程”就是我想为你搭的那座桥。它不承诺让你成为高手但保证你能亲手点亮一个LED亲眼看到串口打出自己测出的频率亲耳听到Proteus里虚拟蜂鸣器如果你加的话随频率变化音调。这种“看得见、摸得着”的成就感是坚持下去的最大动力。别怕报错Keil的红色文字不是判决书而是MCU在用它的语言跟你对话别嫌Proteus仿真“假”它比一块布满虚焊的开发板更诚实能帮你排除90%的硬件疑虑。如果你照着这篇博文走完一遍从CubeMX配置到Proteus看到“Freq: 1000 Hz”那么恭喜你你已经跨过了嵌入式学习里最陡峭的那段坡。接下来的路无论是换芯片、加传感器、还是做毕业设计你心里都有底了——因为你知道那些看似神秘的“输入捕获”、“HAL库”、“时钟树”不过是几个配置、几行代码、一些经过验证的参数而已。它们不玄乎只是需要有人把门推开把灯打开然后说一句“来我带你进去看看。” 现在门开了灯亮了剩下的交给你了。本文还有配套的精品资源点击获取简介基于STM32F103R6芯片的频率测量完整开发包用HAL库实现TIM输入捕获功能支持外部GPIO信号接入并实时通过串口输出频率值。Keil MDK-ARM v5工程已配置完毕包含CubeMX生成的.ioc文件、main.c主程序、tim.c输入捕获驱动、usart.c串口通信、gpio.c引脚初始化、delay.c毫秒级延时、配套启动文件startup_stm32f103x6.s及HAL底层支撑代码。Proteus电路图集成晶振、复位电路、LED状态指示和信号发生器接口可直接加载仿真观察不同频率输入下的捕获响应与串口打印结果。所有源码头文件.h与实现文件.c齐全含备份文件.bak和历史调试配置.uvguix、.uvprojx无需额外环境搭建或代码修改插上USB转串口即可验证功能。适合嵌入式初学者做课程设计、毕设原型验证或快速掌握STM32定时器输入捕获应用。本文还有配套的精品资源点击获取