
简介基于STM32F103的数字万用表Proteus仿真工程面向嵌入式初学者与电子设计爱好者演示从电路搭建到固件调试的完整开发流程。工程覆盖模数转换器的多通道采集、液晶屏显示驱动、电压电流电阻测量算法、按键交互及低功耗管理等核心环节并通过软件仿真与编译环境联调有效降低实物调试门槛。压缩包共1020个文件大小14.29MB以C语言源码和头文件为主辅以电路仿真工程文件、编译配置文件、生成的烧录文件及说明文档便于直接打开工程或按需修改学习。目前已有3084人学习下载资源完整性较好既可作为课程设计或毕业设计的参考资料也能帮助开发者掌握微控制器外设驱动、混合信号测量与仿真验证的实际方法是理解数字万用表产品化设计的有价值案例。1. 项目背景与整体设计思路1.1 为什么选择“STM32F103 Proteus”这套组合做数字万用表这个课题很多人第一反应是先买块开发板再焊接一堆电阻电容。但我的建议是先用Proteus把整个系统跑通再动手焊电路。原因很简单Proteus里改一条连线、换一个电阻阻值比在洞洞板上飞线快得多而且不怕烧芯片。尤其对于STM32F103这种LQFP48封装的芯片手工焊接的难度不低一旦焊错引脚排查起来相当费劲。先仿真验证逻辑再移植到实物这个顺序能省掉至少一半的调试时间。STM32F103系列在国内单片机学习圈子里属于“万金油”般的存在。72MHz主频、256KB Flash、64KB SRAM内置3个12位ADC每个ADC有16个外部通道还带DMA和定时器做数字万用表这种中等复杂度的测量仪表性能完全够用而且ST官方库函数文档齐全网上参考资料也特别多。Proteus作为一款电路仿真软件自8.x版本以后原生支持STM32F103系列芯片的仿真还能加载Keil编译生成的HEX或ELF文件实现软硬件联合调试。这意味着代码逻辑和电路设计可以在同一套环境里互相验证。1.2 数字万用表的测量原理简析数字万用表本质上是一个“多量程ADC电压表”。电压测量是核心电流和电阻测量都是通过一定变换转成电压量再采集。直流电压测量通过电阻分压网络把高电压按比例降到ADC可采样的范围内0~3.3V或0~5V取决于参考电压。测200mV档时直接接入信号测20V档就需要分压比大约1:10到1:100的分压电阻。直流电流测量让电流流过一段已知阻值的采样电阻通过测量电阻两端压降换算出电流值。比如1欧姆采样电阻流过1A电流时压降是1V再配合差分放大电路把信号抬到适合ADC采样的范围。电阻测量给待测电阻串联一个已知阻值的基准电阻接一个稳定电压源通过分压关系反算出被测电阻阻值。需要注意激励电流不能太大否则电阻自身发热会引入误差。1.3 仿真方案相对实物开发的核心优势有一点必须强调很多初学者容易陷入“仿真无用论”的误区觉得仿真只是花架子。实际上对于数字万用表这类混合信号系统仿真最大的价值在于可以随时用示波器探针观测电路中每个节点的波形这在实物调试中往往要借助昂贵的示波器和万用表才能做到。Proteus里自带虚拟示波器、信号发生器和虚拟端子可以直接观察ADC采样引脚的电压波动、放大电路输出是否饱和、分压网络的负载效应等关键指标。还有一个隐性的好处仿真环境默许你“提笔就画”不用考虑元器件封装、焊盘间距、电源噪声抑制这些硬件层面的老问题。把全部精力聚焦在核心逻辑——算法、量程切换策略、显示驱动和数据稳定性上。等这些都跑通了再去设计实际的PCB时就心中有数了。2. Proteus仿真环境搭建与关键配置2.1 新建工程与STM32F103芯片添加打开Proteus 8 Professional版本点击“File”菜单选择“New Project”在设备选择界面搜索“STM32F103”下拉列表里会出现多个具体型号一般选择STM32F103C6或STM32F103R6即可。这里需要特别注意仿真模型不一定支持所有外设比如有些Proteus版本对STM32F103的USB外设支持不完整好在数字万用表用不到USB只用ADC、GPIO、TIM和USART这些基本都支持得很好。图纸创建后在左侧工具箱点击“Component Mode”按钮默认快捷键P打开元件库搜索面板。除了主控芯片之外还需要添加以下元件LCD1602液晶显示屏型号LM016L、若干个10K/100K/1M高精度电阻、几个按键开关BUTTON、16x2排座用于虚拟连接、一个5V电源端子POWER TERMINAL。如果元件库里搜不到某些元件一般是因为搜索关键词不准确比如LCD要搜“LM016L”而不是“LCD1602”要注意这个坑。2.2 Keil工程配置与HEX文件生成Proteus仿真的核心流程是在Keil中编写并编译代码生成HEX文件然后在Proteus中双击芯片在“Program File”路径处指定该HEX文件点击运行就开始仿真了。这里强烈建议使用STM32标准外设库Standard Peripheral Library而非HAL库因为标准库生成的代码量更小、更直观而且网上绝大多数Proteus STM32教程基于标准库遇到问题容易找到参考资料。在Keil中创建工程时Device选择“STM32F103C6”或与Proteus中一致的型号然后在“Options for Target”对话框的“Output”选项卡里勾选“Create HEX File”同时建议把“Debug”选项卡的仿真器设为“Use Simulator”。代码编写完成后编译生成HEX文件在Proteus中就能加载运行了。有一个细节需要注意STM32F103如果编译出来的HEX文件太大超出芯片型号对应的Flash空间仿真会报错。C6型号对应32KB Flash如果代码量确实超了换成R664KB或R8128KB的型号即可引脚兼容性也很好。2.3 仿真时钟频率的校正这是新手最容易忽略的环节。STM32F103内部有HSI内部高速8MHz时钟和HSE外部高速晶振时钟。Proteus仿真模型中如果不主动配置外部晶振芯片会默认使用内部RC振荡器而系统时钟最终取决于代码里SystemInit函数的配置。很多人在仿真时发现程序运行速度异常比如延迟函数时间不对、ADC采样速率不对多数是因为外部晶振频率和代码预期不一致。我的做法是在Proteus原理图中给STM32的OSC_IN和OSC_OUT引脚放置一个8MHz的晶振元件CRYSTAL并在晶振两端各接一个20pF左右的电容到地。这样仿真模型就能识别到外部晶振。代码中SystemInit函数会读取RCC寄存器配置把外部8MHz倍频到72MHz。如果不想接晶振也可以直接在Proteus里双击芯片在“Clock Frequency”属性中填写8MHz效果等同。实测下来时钟频率不准确会导致串口波特率、定时器延时、ADC采样周期全部跑偏排查这个问题往往要花不少时间所以务必一开始就配置正确。3. 核心电路设计——模拟前端与显示模块3.1 电压档位分压网络设计数字万用表的第一个核心问题是如何用3.3V ADC量程去测量远超3.3V的电压。最常用的方案就是电阻分压网络。经典的量程设计是200mV、2V、20V、200V四档但考虑到STM32的ADC参考电压是3.3V留出一定的安全余量可以把默认档位设计为2V、20V、200V三档加上一个直通档0~3.3V。分压网络的具体做法用四个高精度电阻串联成“十”字形分压结构各档位从不同节点引出。比如设计10MΩ、1MΩ、100KΩ、10KΩ的串联网络从1MΩ端引出就得到1/10分压比从100KΩ端引出得到1/100从10KΩ端引出得到1/1000。这四个电阻都是精密电阻误差1%以下因为分压比直接决定电压测量精度。实际设计时还会在每个档位后端并联一个RC低通滤波器通常10KΩ电阻串100nF电容主要滤掉高频噪声否则ADC采样到的数值会跳来跳去。3.2 电流测量与采样电阻选型电流测量沿用“I-V变换法”。选择一个低阻值的精密采样电阻串联在电流回路里比如测200mA量程时用1Ω电阻压降为200mV接近ADC的最佳采样区间测2A量程时则需要0.1Ω电阻。需要注意的是采样电阻的功率额定值必须足够大否则大电流流过时电阻发热导致阻值漂移测量值就会跟着漂。在Proteus仿真中这些功封效应虽然不会真实显现但设计实物时必须考虑。我习惯在电流档电路上标注出每个采样电阻的功率等级比如1Ω/0.5W、0.1Ω/1W便于日后制作BOM表。仿真阶段可以用虚拟电流源或电压源串联电阻的方式模拟被测电流来验证ADC转换公式和显示值是否正确。3.3 LCD1602显示方案显示方案选LCD160216列2行字符屏是性价比最高的选择。虽然OLED屏更小巧、显示信息更丰富但LCD1602在Proteus中的仿真模型比较成熟驱动代码也非常通用四线并行模式只需要6根控制线RS、EN、D4~D7与STM32F103的GPIO连接非常方便。驱动LCD1602的核心步骤是初始化、写命令、写数据。初始化时要注意时序延时因为LCD控制器启动需要时间。代码上建议把每个操作之间的延时控制在微秒级可以使用STM32的SysTick定时器精确延时。真正用在万用表项目里显示内容可以设计成两行第一行显示测量模式DCV、DCA、OHM和量程信息第二行显示测量数值和单位。这里有一个经验丰富的设计细节刷新频率不要太高一般10Hz左右即可刷新太快LCD会闪太慢又感觉迟钝。3.4 按键量程切换与交互逻辑量程切换除了自动挡还需要保留手动挡模式实现方式就是按键扫描加状态机转换。在Proteus中画三个按键分别接到STM32的三个GPIO引脚并配置为输入模式同时需要开启内部上拉电阻。按键检测要做软件消抖常规做法是延时20ms后再读一次端口状态。我通常用定时器中断做10ms周期扫描检测到电平变化连续两次一致才认定为有效按键可以有效避免机械抖动导致的一次按动多次响应。按键逻辑的交互设计MODE键切换DCV/MODE/OHM模式RANGE键切换当前模式下的量程HOLD键保持当前测量值。在LCD上一行固定显示模式另一行显示数值量程切换时同步更新。状态机用switch-case结构实现每个模式对应一个处理方法逻辑清晰后续扩展频率测量等功能也方便。4. STM32软件设计——ADC采样与数据处理算法4.1 ADC初始化与多通道采样STM32F103内置3个ADC模块每个ADC拥有多个通道但同一个ADC的多个通道在规则模式下是“轮询采样”而非“并行采样”。对于数字万用表这种应用每次只采一路信号所以完全够用。初始化代码如下void ADC_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟 72MHz / 6 12MHz GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; // 模拟输入模式 GPIO_Init(GPIOA, GPIO_InitStructure); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; // 独立 ADC 模式 ADC_InitStructure.ADC_ScanConvMode DISABLE; // 单通道扫描模式关闭 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 连续转换模式开启 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; // 右对齐 ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); }需要特别提醒的是ADC的采样通道不能悬空。在Proteus里如果ADC引脚没有连接任何元件模型读取到的数值是一个随机浮动值这会直接导致万用表显示乱码。建议在实际电路里用10KΩ电阻将未使用的ADC引脚下拉到地或者配置为GPIO输出模式推低。在仿真环境里至少要接一个模拟源哪怕是一个0V电源端子保证引脚有确定的电平。4.2 均值滤波与防脉冲干扰ADC原始采样值直接用来显示的话末位数字会不停地跳动。数字万用表里常用的处理方式是“滑动平均滤波”每采样一次把最近的N个采样值求平均N通常取32或64。每次新采样进来淘汰最旧的一个点再算新平均值这样既平滑又不会显著滞后。但光有均值滤波还不够。有时候测量环境中会有尖峰脉冲干扰比如附近继电器动作、电机启停一次异常的采样值可能会让均值明显偏离真实值。我的做法是加一个“排序去极值”步骤每次取32个点先用简单排序32个点排序开销可忽略不计去掉最大的3个点和最小的3个点剩余26个点求平均。这个名为“剔除极值平均滤波”的算法在实际项目中表现非常稳定实测下来电压测量值波动能控制在±1mV以内。代码实现也不复杂uint16_t ADC_FilteredValue(ADC_TypeDef* ADCx, uint8_t channel, uint8_t rank) { uint16_t buf[32]; for (int i 0; i 32; i) { ADC_RegularChannelConfig(ADCx, channel, rank, ADC_SampleTime_55Cycles5); ADC_SoftwareStartConvCmd(ADCx, ENABLE); while (!ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC)); buf[i] ADC_GetConversionValue(ADCx); } // 简单排序取中间值平均 for (int i 0; i 32; i) { for (int j i 1; j 32; j) { if (buf[j] buf[i]) { uint16_t temp buf[i]; buf[i] buf[j]; buf[j] temp; } } } uint32_t sum 0; for (int i 3; i 29; i) sum buf[i]; return (uint16_t)(sum / 26); }这个函数里每次采样重新配置通道是为了便于后续扩展多路测量。实际调用时把采样值乘以参考电压系数3.3V / 4096就得到对应的电压值。这里有个精度优化的细节使用32位中间变量累加求和避免16位溢出。采样时间选择55.5个周期可以得到更稳定的结果虽然速度慢一点但万用表这种应用根本不在乎单次采样速度。4.3 线性校准与零点修正ADC的理想传递函数是输出码值 4095 ×Vin / Vref但实际电路中分压电阻的误差、参考电压的偏离、放大器偏置等都会引入系统误差。软件校准通常采用“两点校准法”已知一个标准电压Vcal1比如用精密可调电源给1.000V时记录ADC输出码值Code1再给一个Vcal2比如5.000V时记录Code2。然后计算增益gain和偏置offsetGain (Vcal2 - Vcal1) / (Code2 - Code1)Offset Vcal1 - Gain × Code1实际测量时测量值 Gain × 当前码值 Offset。把这两个校准参数固化在Flash里STM32F103内部Flash的自编程功能可以实现每次上电时读取。Proteus仿真中虽然没有真实电阻误差但这个校准框架是必须搭好的移植到实物时直接套用。我在项目中还额外做了一个“零点校准”快捷键测量模式下长按HOLD键3秒系统把当前显示值自动扣成0相当于便携万用表的相对测量功能在校准传感器桥路和导线电阻时很实用。5. 常见问题与排查技巧实录5.1 Proteus仿真常见故障速查表仿真的过程中我确实踩过不少坑总结下来以下几个问题出现频率最高可以对照排查故障现象可能原因解决方法仿真运行后LCD无显示LCD对比度引脚V0悬空或接地错误V0接一个10K电位器到地调节对比度数字乱跳且无规律ADC引脚悬空或采样时间过短在ADC引脚加下拉电阻采样时间设为55.5周期量程切换无效GPIO配置错误按键未开启上拉检查GPIO模式是否设为IPU输入上拉实测值比预期偏大/偏小分压电阻阻值错误或参考电压不对核对分压比确认VREF是否接3.3V程序运行慢一倍外部晶振未连接默认使用内部8MHz在Proteus原理图中加8MHz晶振或设置Clock Frequency编译报Flash空间不足芯片型号Flash过小换成STM32F103R6或R8这里面最让我头疼的是LCD无显示的问题。最初我把V0脚直接接地结果屏幕一直是暗的后来查资料才知道LM016L的V0脚是需要一个负压或者通过电位器分压来调节对比度的。在Proteus里用10K电位器一端接5V一端接地中间抽头接V0就能很好地解决。5.2 仿真与实物的差异你要做好这些心理准备Proteus仿真跑通了不等于实物做出来就一定能用。至少有三点差异必须提前意识到第一仿真中所有导线都是理想导体没有线阻和接触电阻。但实物上杜邦线或PCB走线是有电阻的尤其在电流档位1米左右的细导线可能增加几十毫欧的回路电阻直接影响电流测量精度。因此实物设计时尽量采用开尔文接法四线制来消除导线影响。第二ADC参考电压在仿真中是理想的3.3V但实物中如果用STM32芯片自带的VREF引脚直连3.3V其精度受稳压芯片DC-DC纹波影响很大会导致采样值抖动。我的经验是实物设计时使用一个精密基准电压源芯片如TL431或REF3033给VREF单独供电或者用外部基准电压芯片生成精确的2.5V基准接到ADC。第三Proteus里按键不会产生物理抖动但实物按键的机械抖动如果不处理会表现为一次按动多次触发。所以软件消抖算法在仿真时往往体现不出差别但实物移植时一定不能省略。5.3 测量精度提升的几个实战细节做了好几版万用表之后我总结出几个提升精度的偏方虽然不一定在教科书上出现但效果立竿见影尽可能把数据运算的中间变量用32位浮点型不要让误差在多次转换中累积放大。比如显示2.000V和显示2.00V看着差不多但仪表的精度等级就差了10倍。我用浮点数运算最终显示时可以保留3位小数视觉上非常专业。分压网络的电阻选择要慎重。普通5%精度的电阻分压比偏差足够让测量结果面目全非。我一般选择0.1%精度的金属膜电阻淘宝上价格也不贵一块钱以内一个。如果要达到便携万用表的精度最好用1%的精密电阻网络模块四只电阻在同一块基板上温度系数一致性更好。采样前加一个由运放构成的电压跟随器。原理上ADC的输入阻抗虽然很高但毕竟不是无穷大直接并联在分压网络上会形成负载改变分压比例。加一级电压跟随器比如LM358实质上是把ADC从分压网络中隔离出来输入阻抗提高到百兆欧级别基本不加载被测电路。在Proteus仿真中也要模拟这个电路否则实物移植后精度对不上。6. 总结与经验延伸把整个项目做完回头看最值钱的不是那套代码而是整个从“分压网络→ADC采样→软件滤波→量程算法→显示驱动”的闭环思路。只要你愿意花时间把电路图画熟把每一档位的量程配合和算法逻辑理清从这版模拟万用表扩展成自动量程、数据记录、串口上报的完整系统其实只是工作量问题不是技术门槛问题。这里再分享一个具体的扩展方向在现有电路上加一个运算放大器构成的自动量程检测电路通过比较器判断输入电压范围用GPIO控制模拟开关切换分压网络就能实现真正的“傻瓜式”自动量程万用表。如果你手边有这类项目的实物需求建议先用Proteus把自动量程的逻辑验证好再动手画PCB成功率会高非常多。根据我的实际测试用这套方案做出来的万用表直流电压测量精度在2V档可以达到±0.5%左右200mA电流档精度大约±1%对于课程设计和日常电子制作完全够用。如果后续想进一步做交流电压测量只需要在模拟前端增加一个整流电路再用ADC采样有效值整体框架不需要推翻。以上就是我这次项目最核心的收获希望能给正在琢磨类似设计的朋友提供一些参考。本文还有配套的精品资源点击获取