
本文还有配套的精品资源点击获取简介这套代码专为STM32F103设计实现电阻和电容两个参数的快速测量基于RC充放电原理完成采集计算结果实时显示在OLED屏幕上。工程已集成完整的底层驱动OLED显示SSD1306、独立按键扫描、高精度定时器基准所有功能模块分离清晰——main.c负责主流程调度timer.c管理计时与触发key.c处理按键响应oled.c驱动屏幕刷新。配套Keil MDK工程.uvproj格式包含编译输出文件.obj、.lst等和启动文件上电即运行无需额外配置即可验证基础测量逻辑。实测支持常见贴片/直插电阻10Ω–1MΩ和电容1nF–10μF范围精度满足教学与调试需求若更换传感器或外围电路只需调整timer.c中的采样阈值和延时参数即可适配。适合电子初学者搭建简易LRC测试仪也方便教师用于单片机实践课演示或作为毕业设计、创客项目的基础测量模块直接复用。1. 这不是“玩具”而是一台能真正干活的便携式阻容测量仪你手上拿到的这套代码不是那种只能在实验室里亮个灯、跑个流水灯的“教学Demo”。它是一台实实在在能放进工具包、带进电子维修现场、插上电池就能测元件的便携式阻容测量仪。核心是STM32F103C8T6——那个被戏称为“蓝 pill”但实际性能扎实的入门级ARM Cortex-M3芯片。它不靠外部ADC芯片、不依赖高精度运放电路而是用最朴素的RC充放电原理配合单片机内部的精准定时器和GPIO翻转能力把电阻值和电容值“算”出来再通过一块0.96寸SSD1306 OLED屏清清楚楚地显示在你眼前。关键词里提到的“STM32F103”、“电阻测量”、“电容测量”、“OLED显示”、“RC测试”每一个都不是虚词。它意味着你不需要额外买万用表模块不需要接USB转串口调试线只要一块最小系统板比如常见的STM32F103C8T6核心板、一块OLED屏、两个按键、几根杜邦线烧录进去上电——屏幕上立刻跳出“R: 1.23kΩ C: 4.70nF”这样的实时读数。我第一次把它焊在洞洞板上用两节AA电池供电带着去修一台老式功放当场测出一个疑似失效的100nF耦合电容误差不到5%比手头那台用了十年的老万用表还快半拍。它解决的不是“能不能测”的问题而是“能不能在现场快速、可靠、独立地测”的问题。适合谁电子爱好者想搞懂RC时间常数怎么变成数字单片机初学者想明白定时器捕获和GPIO输出如何协同工作高校老师需要一个不依赖昂贵仪器、学生能亲手焊接调试的实践项目毕业设计的同学需要一个功能明确、代码清晰、可扩展性强的基础测量模块——它就是为这些真实场景打磨出来的。它不追求六位半精度但追求“测得准、看得清、改得动、带得走”。2. 整体设计思路用最简硬件实现最稳逻辑这套方案的核心哲学是“用软件的确定性弥补硬件的简易性”。它没有用专用LRC测量芯片比如AD5933也没有外挂高精度基准电压源或高速ADC而是回归到大学《电路分析》课本里那个最基础的公式τ R × C。整个系统围绕这个公式展开所有设计决策都服务于让这个公式在单片机上稳定、可重复地落地。2.1 为什么选RC充放电法而不是其他市面上有三种主流的阻容测量思路一是恒流源充电测电压斜率适合大电容但需要精密电流源二是交流阻抗法精度高但电路复杂需信号发生器和锁相放大三是RC时间常数法即本方案。我们选第三种理由非常实在硬件极简只需要一个已知的、稳定的参考电阻比如10kΩ精密贴片电阻一个待测元件R或C加上单片机的两个GPIO一个推挽输出一个浮空输入/带上拉就能构成完整回路。没有运放、没有DAC、没有额外电源轨。原理透明τ R × C 是线性关系对单片机来说测量“时间”比测量“电压”更容易、更精准。STM32F103的SysTick或TIM2定时器分辨率可达1μs远高于其内置ADC的12位精度约0.1%。抗干扰强充放电过程是数字信号主导GPIO翻转模拟部分只涉及一个RC网络的自然响应不像小信号电压测量那样容易受PCB布线、电源纹波影响。成本归零所有功能都在芯片内部完成外围器件就是几个电阻、电容、按键、OLED屏BOM成本控制在15元以内。当然它也有局限测量范围受定时器计数上限和最小分辨时间限制对极小电容100pF和极大电阻10MΩ灵敏度下降。但这恰恰是它的定位——覆盖日常维修和教学中最常遇到的元件范围10Ω–1MΩ电阻1nF–10μF电容而不是去挑战实验室级仪器。2.2 模块化架构为什么main.c只做“调度员”打开工程目录你会看到main.c、timer.c、key.c、oled.c四个核心文件。这不是为了“看起来整洁”而是源于一个硬性约束STM32F103的资源有限64KB Flash20KB RAM且测量过程对时序要求苛刻。如果把所有代码揉进一个文件调试时一个变量名冲突、一个延时写错整个测量逻辑就崩了。main.c的唯一职责是初始化、状态机轮询、以及调用其他模块的接口。它像一个交通指挥中心不参与具体测量只负责说“现在该测电阻了timer模块准备触发”“用户按了‘切换’键key模块告诉我状态”“结果出来了oled模块刷新屏幕”。这样当你想把测量逻辑改成支持电感L时只需修改timer.c里的充放电时序main.c几乎不用动。timer.c是整个系统的“心脏起搏器”。它管理两个关键定时器一个是用于精确控制充放电时间的主定时器比如TIM2另一个是用于按键消抖和屏幕刷新的通用定时器比如TIM3。它封装了Timer_Start()、Timer_GetMicros()、Timer_WaitUs()等函数屏蔽了寄存器操作细节。你调用Timer_GetMicros()得到的就是从启动到现在经过的微秒数底层是读取TIM2_CNT寄存器并换算完全不用关心APB总线分频、预分频系数这些。key.c解决的是“人机交互”的毛刺问题。两个独立按键比如K1测电阻K2测电容直接接GPIO上拉。但机械按键按下释放时会有5–20ms的抖动。key.c采用“两次采样时间窗”策略每隔10ms读一次按键状态连续两次读到相同状态才认为有效并记录按下时间戳。这样既避免误触发又保留了长按功能比如长按K1进入校准模式。oled.c是“视觉输出层”。它基于SSD1306的I2C协议SCL/SDA但做了深度优化。标准驱动往往用软件模拟I2C速度慢、占CPU。这里利用STM32F103的硬件I2C外设I2C1配置为标准模式100kHz并通过DMA传输显示缓冲区oled_buffer[128*8]CPU只需设置好地址和数据长度剩下的交给DMA。刷新一屏128×64像素仅耗时约3ms且不阻塞主循环。这种分工让每个模块都能被单独测试、单独替换。我曾把oled.c替换成SPI接口的驱动因为手头只有SPI OLED只改了OLED_Init()和OLED_SendData()两个函数其余代码一行没动屏幕照样亮。2.3 OLED显示策略不只是“显示”更是“交互界面”OLED屏在这里不是简单的数码管替代品而是一个微型人机界面HMI。它的布局经过反复推敲顶部固定栏显示当前模式“R-MODE”或“C-MODE”、电池电量图标基于ADC读取VDD、以及一个“BUSY”闪烁指示灯测量中亮起。中央主显示区大号字体16×16点阵显示数值例如“R: 4.70kΩ”单位自动切换Ω/kΩ/MΩnF/μF/mF小数点后两位确保易读性。底部状态栏显示测量状态“READY”、“CHARGING…”、“DISCHARGING…”、“CALIBRATING”和辅助信息比如当前参考电阻值可配置。这个布局背后有实操考量维修现场光线复杂大字体保证远距离可读状态栏实时反馈让用户知道“仪器没卡死只是正在充电”电池图标避免用户测到一半断电导致数据丢失。更重要的是所有显示内容都存放在一个全局结构体display_t display_data中main.c只更新这个结构体的字段oled.c的刷新任务在TIM3中断里执行完全异步。这意味着即使测量过程占用大量CPU比如计算对数拟合屏幕也不会闪烁或卡顿。3. 核心细节解析RC测量是如何一步步“算”出来的理解这套代码的精髓不在于看懂某一行GPIO_ResetBits()而在于搞清整个测量流程的物理意义和数学转换。下面以电容测量为例拆解从按键按下到屏幕显示的每一步。3.1 电容测量一个完整的“充-放-判-算”闭环假设用户按下K2选择电容测量模式。系统执行以下步骤初始化与放电首先将连接待测电容C_x的GPIO比如PA0配置为推挽输出并强制输出低电平GPIO_ResetBits(GPIOA, GPIO_Pin_0)。这相当于用一根导线把电容两端短接确保其初始电压为0V。持续约10ms确保彻底放电。这是关键前提任何残余电荷都会导致后续测量偏差。充电阶段将PA0切换为开漏输出GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD并外接一个已知精度的参考电阻R_ref比如10.0kΩ ±1%到VCC。此时PA0相当于一个“可控开关”。程序将其置高GPIO_SetBits(GPIOA, GPIO_Pin_0)电流经R_ref向C_x充电。同时启动TIM2定时器开始计时。电压判别点我们不测量全程充电曲线而是只关心一个特定时刻的电压。根据RC电路理论电容电压V_c(t) V_cc × (1 - e^(-t/(R_ref×C_x)))。当t R_ref × C_x时V_c V_cc × (1 - e^(-1)) ≈ 0.632 × V_cc。这个0.632倍V_cc的电压点就是我们的“判别阈值”。单片机无法直接测这个模拟电压所以采用“比较器思想”将PA0充电端通过一个分压电阻网络比如10kΩ 10kΩ接到一个ADC通道或者更巧妙地——利用STM32F103的“模拟看门狗”AWD功能设定ADC阈值为0.632×V_refV_ref通常是3.3V所以阈值≈2.09V。一旦ADC读数超过此值触发中断TIM2停止计时记录下此时的时间t_measured。计算与校准理论上t_measured R_ref × C_x所以C_x t_measured / R_ref。但实际中GPIO输出阻抗、PCB走线电容、ADC采样延迟都会引入系统误差。因此代码中引入了一个校准系数cap_cal_factor默认1.0。最终计算为C_x (t_measured * cap_cal_factor) / R_ref。这个系数就是在“校准模式”下用一个已知高精度电容比如100nF测出的t_measured_real与理论值t_theory R_ref × 100nF的比值cap_cal_factor t_theory / t_measured_real。提示timer.c中的TIM2_IRQHandler是关键。它不仅要处理ADC AWD中断还要处理TIM2的溢出中断防止超时。代码里有一个timeout_flag一旦TIM2计数超过预设最大值比如100ms就强制退出返回“OVER RANGE”错误避免程序死锁。3.2 电阻测量反向思维用已知电容做“标尺”电阻测量不能直接套用电容逻辑因为待测电阻R_x本身没有储能特性。方案是“角色互换”把一个已知精度的小电容C_ref比如1nF和待测电阻R_x串联构成一个新的RC网络。然后对这个网络进行同样的充放电并测量达到0.632V_cc所需的时间t_measured。充电回路VCC → C_ref → R_x → GND。注意此时C_ref是固定的R_x是变量。时间常数τ R_x × C_ref。计算R_x t_measured / C_ref × cap_cal_factor这里复用了电容测量的校准系数因为C_ref的精度直接影响R_x精度。这个设计的妙处在于它复用了同一套充放电硬件和判别逻辑只需切换外部接入的元件电容or电阻软件层面通过模式标志位measure_mode区分即可。main.c里的Measure_Capacitance()和Measure_Resistor()函数核心都是调用同一个RC_TimeMeasure()底层函数只是传入的参考值R_ref or C_ref和计算公式不同。3.3 OLED显示驱动的底层细节为什么用DMA而不用轮询oled.c里最值得深究的是OLED_Fill_Buffer()和OLED_Refresh()的配合。OLED_Fill_Buffer()它不直接操作硬件而是把要显示的字符、图形按照SSD1306的内存映射规则128列×8页逐字节写入一个全局数组oled_buffer[1024]128×81024字节。比如要在坐标(0,0)显示字母“A”函数会计算出它在buffer中的起始位置第0页第0列然后把16×16点阵字模的前8行对应第0页写入buffer[0]到buffer[7]后8行对应第1页写入buffer[128]到buffer[135]。OLED_Refresh()这才是真正的硬件操作。它配置I2C1外设设置目标地址为SSD1306的显存起始地址0x00然后启动DMA传输。DMA控制器自动从oled_buffer的首地址开始搬运1024字节数据到I2C的TXDR寄存器整个过程无需CPU干预。传输完成后DMA触发一个中断在中断服务程序里发送一个STOP条件结束本次通信。为什么不用简单的while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE))轮询因为一次完整的1024字节I2C传输如果用轮询CPU要忙等上千次期间无法响应任何中断包括TIM2的测量中断会导致测量超时或精度暴跌。而DMA方式CPU只花几十个时钟周期启动DMA之后就可以去干别的事比如处理按键、计算数值。实测表明轮询方式下屏幕刷新会拖慢整体测量周期达30%而DMA方式几乎无感。4. 实操过程从Keil工程到上电验证的完整链路拿到源码包你面对的不是一个抽象的“代码”而是一个完整的、可立即动手的硬件项目。下面是我自己从解压到测出第一个元件的全过程包含所有坑点和捷径。4.1 环境准备Keil MDK不是唯一选择但它是最快路径资源包里提供了.uvproj文件这意味着Keil MDKuVision5是官方推荐环境。但请注意这不是强制绑定。如果你习惯用STM32CubeIDE或PlatformIO完全可以迁移。不过对于首次上手者我强烈建议先用Keil原因有三工程已预配置好Target选项卡里芯片型号STM32F103C8Tx、晶振频率8MHz、Flash算法STM32F1xx 256K都已设定好无需你手动查找。启动文件齐全STARTUP.A51是Keil专用的汇编启动文件它完成了栈指针初始化、中断向量表复制、以及调用main()前的C运行环境准备。你不需要懂汇编但要知道它存在且不可删除。输出文件完备Template.hex是编译好的固件你可以直接用ST-Link Utility烧录跳过编译环节快速验证硬件连通性。安装Keil后双击Template.uvproj点击Project - Options for Target...在Debug选项卡里确认Use选择了ST-Link Debugger并勾选Load Application at Startup和Run to main()。这样点击CtrlF5Start/Stop Debug Session程序就会自动下载并运行。4.2 硬件连接一张表搞定所有引脚这是最容易出错的环节。main.c里定义了默认引脚你必须严格对照你的硬件接线。以下是标准连接表基于常见STM32F103C8T6核心板和0.96寸I2C OLED功能单片机引脚OLED引脚按键引脚备注OLED SCLPB6SCL—I2C1_SCLOLED SDAPB7SDA—I2C1_SDA按键K1测RPA8—K1外部上拉至3.3V按下接地按键K2测CPA9—K2外部上拉至3.3V按下接地测量端子PA0——接参考电阻R_ref一端测量端子-PA1——接待测元件另一端注意PA0和PA1是测量专用引脚绝对不要接任何其他外设比如LED、串口。它们需要纯净的GPIO控制能力。如果PA0被用来做串口TX测量必然失败。4.3 关键参数微调timer.c里的三个魔法数字代码开箱即用但要达到最佳精度必须根据你的实际硬件调整timer.c里的三个宏定义#define REF_RESISTOR_VALUE 10000.0f // 参考电阻R_ref单位Ω #define REF_CAPACITOR_VALUE 1.0e-9f // 参考电容C_ref单位F #define ADC_THRESHOLD_VOLTAGE 2.09f // ADC判别阈值单位V0.632 * 3.3REF_RESISTOR_VALUE你实际焊在板子上的那个精密电阻的标称值。别信色环用万用表实测。比如你焊的是标称10kΩ但实测9.98kΩ这里就填9980.0f。REF_CAPACITOR_VALUE同理你用于电阻测量的那个已知电容的实测值。1nF电容的误差可能高达10%务必用LCR表或高精度万用表校准。ADC_THRESHOLD_VOLTAGE这个值依赖于你的VDD电压。如果VDD不是严格的3.3V比如电池供电时降到3.0V0.632倍就不再是2.09V。此时你需要重新计算ADC_THRESHOLD_VOLTAGE 0.632f * VDD_measured。VDD_measured可以通过ADC通道读取VDD/3利用内部VREFINT来间接获得但这属于进阶技巧新手建议先用稳压电源供电确保VDD3.3V。这三个值就是整个测量精度的基石。我曾因为用了一个误差5%的10kΩ电阻导致所有电容测量结果系统性偏高5%折腾了一下午才定位到这个问题。4.4 首次上电验证五步诊断法烧录成功后屏幕应该亮起显示初始界面。如果黑屏或乱码按以下顺序排查电源与地用万用表测PA0和PA1对GND电压应为0V放电后。测VDD对GND应为3.3V±0.1V。OLED通信用逻辑分析仪抓PB6/PB7波形看是否有I2C START信号和设备地址0x78。没有则检查SCL/SDA是否接反、上拉电阻4.7kΩ是否焊好。按键响应在key.c的KEY_Scan()函数里加一句GPIO_ToggleBits(GPIOA, GPIO_Pin_12)假设PA12接了一个调试LED编译烧录。按K1/K2看LED是否闪烁。不闪说明按键硬件或初始化失败。定时器心跳在timer.c的TIM2_IRQHandler里同样加GPIO_ToggleBits(GPIOA, GPIO_Pin_12)。如果LED以固定频率闪烁说明TIM2正常工作如果不闪检查RCC时钟使能RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_TIM2, ENABLE)和中断使能NVIC_EnableIRQ(TIM2_IRQn)是否遗漏。测量逻辑用一个已知10kΩ电阻接在PA0和PA1之间。按K1看屏幕是否显示“R: 10.00kΩ”。如果显示“OVER RANGE”或“0.00Ω”回到第3步检查REF_RESISTOR_VALUE是否正确以及PA0/PA1是否真的接到了电阻两端。这套诊断法我在带学生做课设时总结出来90%的硬件问题都能在一分钟内定位。5. 常见问题与排查技巧实录那些文档里不会写的坑在上百次的实际调试和教学中我记录下了最常遇到的12个问题。它们不是理论缺陷而是真实世界里焊锡、走线、元件批次、甚至环境温度带来的“意外”。5.1 问题速查表现象最可能原因解决方案屏幕全白或全黑OLED的DC引脚未接或接错I2C地址配置错误0x78 vs 0x7A检查oled.c中OLED_I2C_ADDRESS宏定义用万用表确认DC引脚电平变化按键无反应key.c中GPIO初始化时GPIO_Speed设得太低如GPIO_Speed_2MHz导致读取不稳定改为GPIO_Speed_50MHz检查上拉电阻是否虚焊电容测量值总是偏小约10%PCB上PA0/PA1走线过长分布电容过大10pF相当于并联了一个小电容将测量端子尽量靠近MCU引脚用短线直接焊接在timer.c中增加cap_cal_factor补偿电阻测量值在100kΩ以上跳变待测电阻阻值过大充电时间接近TIM2最大计数值65535导致计时溢出误差放大在RC_TimeMeasure()函数中增加对t_measured的范围判断对超限值做线性外推电池供电时精度下降VDD电压随负载波动导致ADC参考电压和GPIO输出电平变化在main.c中加入VDD监测动态调整ADC_THRESHOLD_VOLTAGE或改用内部VREFINT作为ADC参考同一元件多次测量结果不一致按键消抖时间KEY_DEBOUNCE_TIME_MS设置过短10ms将key.h中KEY_DEBOUNCE_TIME_MS改为20或改用硬件RC消抖电路测量时屏幕闪烁OLED_Refresh()被频繁调用且未加临界区保护在main.c的测量主循环中调用OLED_Refresh()前加__disable_irq()刷新完再__enable_irq()“OVER RANGE”错误频繁出现REF_RESISTOR_VALUE或REF_CAPACITOR_VALUE填写错误导致理论时间超出TIM2量程用公式t_max R_ref * C_max估算最大时间如R_ref10k, C_max10uF → 100ms确保TIM2预分频后能覆盖5.2 独家避坑技巧来自产线和维修现场的经验“冷焊点”是万恶之源我遇到过最诡异的问题是PA0引脚虚焊。万用表测通断是好的但示波器看波形发现充电时PA0电压上升缓慢且不规则。用热风枪对该焊点补焊3秒问题消失。结论所有测量引脚PA0, PA1务必用放大镜检查焊点确保光亮、饱满、无冰裂。电容的“记忆效应”电解电容尤其是老化电容在放电后会缓慢“自充电”。表现为刚放完电立刻测结果偏低等1秒再测结果恢复正常。解决方案在Measure_Capacitance()函数开头增加一个Delay_ms(1000)的等待让电容彻底稳定。这个1秒是无数个报废电容教会我的。温度漂移的应对参考电阻R_ref的温度系数TCR直接影响精度。普通金属膜电阻TCR约±100ppm/°C室温25°C到夏天40°C误差可达1.5%。我的做法是在timer.c里用ADC读取一个NTC热敏电阻接在PA2根据查表法得到当前温度然后动态修正REF_RESISTOR_VALUEadjusted_R_ref R_ref_nominal * (1 TCR * (T_current - 25))。这个功能在源码里是预留的// TODO: Temperature compensation但只要你加上精度就能稳住。OLED的“鬼影”消除长时间显示同一画面OLED会出现残影。oled.c里有一个OLED_Clear()函数但它只是清空buffer。真正的清除需要向SSD1306发送0xA5全屏点亮和0xA4正常显示指令序列。我在main.c的主循环里每30秒强制调用一次OLED_Fill_Buffer(0x00)然后OLED_Refresh()有效杜绝了鬼影。最后再分享一个小技巧如果你想把这个测量仪升级成“智能仪表”只需在main.c里加一个UART_Init()然后在Measure_Resistor()函数末尾添加printf(R:%.2fkΩ\r\n, r_value_kohm);。用USB转TTL模块接到电脑就能把每次测量结果实时打印到串口助手方便做数据记录和统计分析。这个改动不超过10行代码却打开了数据分析的大门。我在实际使用中发现这套代码最大的价值不在于它能测多准而在于它把一个看似复杂的测量原理拆解成了一个个可触摸、可修改、可验证的模块。当你亲手调好REF_RESISTOR_VALUE看着屏幕上的数字和你万用表的读数越来越接近时那种“我懂了”的豁然开朗是任何仿真软件都无法替代的。它不是一个终点而是一个起点——一个让你真正走进嵌入式世界、理解硬件与软件如何咬合工作的起点。本文还有配套的精品资源点击获取简介这套代码专为STM32F103设计实现电阻和电容两个参数的快速测量基于RC充放电原理完成采集计算结果实时显示在OLED屏幕上。工程已集成完整的底层驱动OLED显示SSD1306、独立按键扫描、高精度定时器基准所有功能模块分离清晰——main.c负责主流程调度timer.c管理计时与触发key.c处理按键响应oled.c驱动屏幕刷新。配套Keil MDK工程.uvproj格式包含编译输出文件.obj、.lst等和启动文件上电即运行无需额外配置即可验证基础测量逻辑。实测支持常见贴片/直插电阻10Ω–1MΩ和电容1nF–10μF范围精度满足教学与调试需求若更换传感器或外围电路只需调整timer.c中的采样阈值和延时参数即可适配。适合电子初学者搭建简易LRC测试仪也方便教师用于单片机实践课演示或作为毕业设计、创客项目的基础测量模块直接复用。本文还有配套的精品资源点击获取