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

STM32F103R6频率计实战工程:Keil编译+Proteus仿真一键运行

本文还有配套的精品资源,点击获取

简介:基于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做输入捕获(IC1),USART1以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. 整体设计与思路拆解:为什么选TIM2+IC1,而不是TIM1或通用定时器?

2.1 核心方案选型:为什么是TIM2通道1,而不是其他定时器?

STM32F103R6有4个通用定时器(TIM2-TIM5)和2个高级定时器(TIM1、TIM8)。选TIM2,不是因为它最强,恰恰是因为它最“基础”也最“稳妥”。TIM2是32位定时器,最大计数值4294967295,对于测频来说,这意味着即使测1Hz的超低频信号,也能靠“周期测量法”获得足够精度(后面会细说)。更重要的是,TIM2的时钟源来自APB1总线,而F103系列默认APB1是36MHz(HSE=8MHz,PLL倍频后系统时钟72MHz,APB1预分频2得36MHz),这个频率足够高,能保证高频信号捕获的分辨率。比如,用36MHz时钟,理论最小可分辨周期是1/36MHz ≈ 27.8ns,对应最高可测频率约36MHz——当然实际受限于GPIO翻转速度和信号完整性,但测到1MHz以内方波完全没问题。

为什么是通道1(CH1)?因为TIM2_CH1对应的GPIO是PA0(在F103R6的LQFP64封装中),这个引脚在Proteus仿真图里被明确标为“IN”,且旁边紧挨着PA1(LED),物理布局上方便你接信号源。更重要的是,PA0在CubeMX的Pinout视图里默认就是TIM2_CH1功能,无需手动重映射(Remap),避免了初学者因映射错误导致捕获失效的坑。我试过用TIM3_CH2(PB5),结果发现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 通信与交互设计:为什么用USART1+115200bps,而不是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),直接弹出窗口就能看到数字,不用解析十六进制或担心校验。LED(PA1)的作用是“心跳灯”+“捕获指示”:正常运行时1Hz闪烁,一旦检测到有效信号并完成一次捕获,就快速闪烁两次(200ms亮/200ms灭),给你一个视觉反馈——这比盯着串口发呆强多了。很多初学者做完功能,却不知道程序到底有没有执行到捕获环节,这个LED就是最直观的“运行证据”。

3. 核心细节解析与实操要点:TIM输入捕获的四个致命细节

3.1 输入捕获极性与触发边沿:为什么必须设为BOTHEDGE,而不是RISING?

这是新手最容易栽的第一个坑。如果只设上升沿触发(TIM_ICPOLARITY_RISING),那么对于占空比不是50%的方波,你只能测到高电平持续时间(脉宽),而不是完整周期。比如一个1kHz、占空比30%的方波,上升沿到下降沿是300us,下降沿到下一个上升沿是700us,只捕获上升沿,你永远得不到1000us的周期。工程里tim.cMX_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又没开启自动重装载(ARR=0xFFFFFFF),CNT会一直累加,直到溢出归零。如果ic_value2<ic_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_value2<ic_value1),就用“从ic_value1到最大值的距离”加上“从0到ic_value2的距离”,再加1(因为计数是从0开始的)。我实测过,当输入1Hz信号时,ic_value1可能在0x80000000附近,ic_value2在0x00000005附近,不加这个判断,算出来的周期会是负数,频率直接变0。这个细节,很多教程都一笔带过,但它是低频测量可靠的基石。

3.3 HAL库回调函数的陷阱:为什么不能在回调里调用printf或HAL_Delay?

HAL_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 → RCC)
HSE(High Speed External)设为“Crystal/Ceramic Resonator”,值填8MHz。这是Proteus里晶振元件的默认值,必须一致,否则时钟树全错。然后在“Clock Configuration”页,将PLL Source设为HSE,PLL MUL设为9(8MHz * 9 = 72MHz),SYSCLK就变成72MHz。APB1 Prescaler设为2,得到36MHz——这个36MHz,就是TIM2的时钟源,也是后面所有计算的基准。

第二步:外设配置(Timers → TIM2)
点击TIM2,在Parameter Settings里,Counter Direction选“Up”,Counter Period留空(默认0xFFFF,够用)。然后展开Channel 1,Mode选“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)
找到PA0,Function栏下拉选“TIM2_CH1”,Mode自动变为“Alternate Function Push-Pull”,Pull-up/Pull-down选“No Pull-up and No Pull-down”。找到PA1,Function选“GPIO_Output”,Mode为“Output Push-Pull”,Initial State设为“Low”(LED初始灭)。最后,USART1的PA9/PA10自动关联,Mode为“Asynchronous”,Baud Rate设为115200。

做完这三步,点“Generate Code”,CubeMX会自动生成所有初始化代码,覆盖main.ctim.cusart.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.cMX_TIM2_Init()之后)。

4.3 Proteus仿真电路搭建:五个关键元件的参数真相

Proteus工程Proteus_Keil_STM32F103R6.pdsprj里,电路图看似简单,但每个元件参数都经过实测:

  • 晶振(XTAL):型号“CRYSTAL”,Frequency设为8MHz,Load Capacitance设为20pF。这是ST官方推荐值,和CubeMX里RCC配置完全对应。如果设成12MHz,仿真时系统时钟就乱了,TIM2计数速率不对,测频结果成倍偏差。
  • 复位电路(RESET):由10kΩ电阻(R1)和100nF电容(C1)组成RC网络。R1接VCC,C1接地,RESET引脚接在R1和C1之间。这个参数保证上电时RESET引脚有足够长的低电平(约1ms),让MCU可靠复位。我试过把C1换成10nF,复位时间太短,MCU偶尔启动失败。
  • LED电路(D1):绿色LED,串联限流电阻R2=330Ω。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为115200,Data Bits为8,Stop Bits为1,Parity为None。这个必须和usart.chuart1.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; // 转换为频率(Hz),TIM2时钟为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,是因为frequint32_t,用%d会出错。这些细节,都是从无数次编译警告和运行异常里抠出来的。

5. 常见问题与排查技巧实录:那些让我熬夜到凌晨三点的Bug

5.1 串口只显示乱码,或者完全没输出:五步排查法

这是最高频的问题,按顺序检查,90%能解决:

步骤检查项正确值错误表现解决方法
1CubeMX中USART1的Baud Rate115200乱码双击USART1,在Parameter Settings里改
2Proteus中COMPIM的Baud Rate115200乱码右键COMPIM → Edit Properties → Baud Rate
3Keil工程中huart1.Init.BaudRate115200乱码打开usart.c,搜索BaudRate,确认赋值
4PA9/PA10引脚是否被其他外设占用仅USART1无输出在CubeMX Pinout视图,确认PA9/PA10 Function是“USART1_TX/RX”
5HAL_UART_Transmit()超时值100(ms)发送卡死检查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确实是72MHz,APB1是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.cmain.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的自动备份。每次保存.pdsprj,Proteus都会生成一个同名.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定时器输入捕获应用。


本文还有配套的精品资源,点击获取

http://www.jsqmd.com/news/978694/

相关文章:

  • 保姆级教程:手把手教你搞定华为USG6000V防火墙的跨版本升级(含固件下载与密码重置)
  • 手机App控制51单片机LED?一个HC-06蓝牙模块+串口中断就能搞定(附完整代码)
  • Proteus 8.6 仿真超声波测距,我踩过的坑和调试技巧(附完整工程)
  • GD32F405RGT6 SPI主从模式实战:手把手教你用逻辑分析仪调试时序(附完整工程)
  • 别再让STL模型在CoppeliaSim里‘飘’着了:手把手教你从Mesh到动力学仿真的完整流程
  • 从一次“信息泄露自查”说起:手把手教你用Have I Been Pwned和Reg007保护账号安全
  • 2026年靠谱的镀锌桥架/防火桥架用户口碑推荐厂家 - 行业平台推荐
  • 别再手动改Excel了!用Python的openpyxl批量处理单元格(合并、删除、移动)
  • 金水区郑大北校区购机实测:这3个黑曼巴定制款,竟能避开学区店80%的坑
  • Multisim仿真差动放大电路:从单端/双端输入到共模抑制比,一次搞懂所有测量(附实验数据对比)
  • 别再只跑 nvcc -V 了!CUDA 安装后必做的 5 项深度测试(含 Samples 编译、Pytorch GPU 验证)
  • 每一个你习以为常的 PHP 特性背后,都站着一个伟大的 CS 原理。
  • 从快时钟到慢时钟,脉冲信号CDC漏采怎么办?一个握手机制实例讲透
  • ZLToolKit线程模块源码拆解:从信号量到工作线程池,一个C++网络库的并发设计实战
  • ▲基于OFDM+QPSK的通信链路matlab性能仿真,包含LDPC,Schmidl-Cox频偏估计和MMSE信道估计
  • 【安卓】萌次元壁纸站[特殊字符]纯净免费版[特殊字符]高清壁纸⭕小组件
  • 为什么越来越多人选择聚合平台,而不是独个AI:GPT、Claude、Gemini?
  • Hadoop YARN Web UI保姆级解读:从8088页面看懂你的集群在忙啥
  • 2026年评价高的四川铝合金桥架/四川桥架/四川梯式桥架厂家综合对比分析 - 品牌宣传支持者
  • 2026图片去水印工具推荐,免费图片去水印工具合集
  • 从‘玩具’到‘工具’:给你的Vue后台管理系统加一个真正可用的SQL查询面板(含Node.js后端)
  • RK3588多屏显示实战:如何用一块板子同时驱动HDMI和MIPI双屏(DTS配置详解)
  • 毕业设计救星:如何用最少的外设搞定一个功能齐全的STM32篮球记分器?
  • 终极宝可梦存档编辑器:PKHeX.Mobile移动端跨世代精灵管理完全指南
  • 告别千篇一律!用这10个CSS技巧,让你的Element UI表格(el-table)颜值飙升
  • 飞桨EasyDL数据导出功能实测:从创建Bucket到下载分割标签的全流程避坑指南
  • 同程酒店 User-Dun 逆向复盘
  • 【C++】类与对象之类的默认成员函数(二)
  • 杭州外墙维修清洗技术要点与合规服务实操指南:杭州地毯清洗/杭州外墙玻璃清洗/杭州外墙维修清洗/杭州学校保洁/杭州家政保洁/选择指南 - 优质品牌商家
  • 用COMSOL复现经典:一杯水的自然对流仿真,从模型设置到结果后处理全解析