资讯动态

基于STM32的Proteus超声波测距仿真:从原理到LCD显示完整流程

发布时间:2026/9/15 11:51:54 来源:尧图企业网站定制
简介基于STM32的超声波测距显示系统Proteus仿真资源包面向嵌入式开发者和电子竞赛备赛人群提供完整的Keil5工程源码与Proteus 8.15仿真电路。系统以STM32F103C8为主控搭配HC-SR04超声波模块实现金属厚度测量针对不同介质声速差异程序特别设计声速可调功能通过8个按键可完成测量、设置、左移右移、加减声速数值、数据存储与调用等操作LCD12864支持中文菜单显示并利用STM32内部Flash或外挂EEPROM实现掉电保存满足现场测试数据的持久化需求。资源包共276个文件包含C源码、头文件、启动汇编、Proteus工程、hex固件及重要驱动模块定时器、Flash、I2C等压缩包大小9.92MB目录层级完整便于定位修改。目前已有204人学习下载可直接用于课程设计、毕业设计或工程预研源码结构清晰适合二次开发与实验调试。1. 基于STM32的Proteus超声波测距仿真为什么要先在虚拟环境里跑通整个链路很多人拿到“基于STM32的超声波测距显示系统”这个题目第一反应是买一块开发板、一个HC-SR04、一块LCD1602然后开始接线。但实际动手时你会发现联调过程中最耗时间的不是写代码而是反复修改接线、排查CMOS电平不匹配、观察回波信号是否被正确捕获。Proteus仿真能把这些环节前置到写代码阶段你不需要真实硬件也能验证STM32启动配置、GPIO触发时序、定时器输入捕获、距离计算和LCD显示这几条链路是否真的闭环。这套方案适合两类人一类是急需在毕业设计或课程设计中交付可演示原型的同学另一类是希望在投板前先验证驱动逻辑是否正确的工程师。仿真遇到第一个坑往往不在代码而在Proteus元件库里根本没有HC-SR04这个型号。于是很多人的项目就卡在“找不到传感器模型”这一步。实际上超声波传感器的核心行为就是输出一个宽度随障碍物距离变化的高电平脉冲用一个可调脉宽信号源就能在仿真环境里完整模拟回波。本文从测距原理讲起到CubeMX配置、Keil编译出HEX、Proteus加载和信号源参数调整把整条路径完整走一遍。2. 超声波测距原理与替代HC-SR04的信号源模拟方案2.1 HC-SR04的工作时序Trig、Echo和时间差HC-SR04测距的原理并不复杂STM32向Trig引脚输出一个持续10微秒的高电平脉冲传感器内部电路随即发射一组40kHz的超声波脉冲同时让Echo引脚拉高。超声波遇到障碍物后反射回来传感器收到回波Echo引脚拉低。Echo保持高电平的时间就是超声波从发射到返回的时间差。声音在常温空气中的传播速度约为340米每秒也就是每微秒移动0.034厘米。考虑到超声波的路径是往返单程距离需要除以2距离(cm) Echo高电平时间(us) × 340(m/s) / 1000000 × 100(cm/m) / 2化简后大约为距离(cm) 时间(us) × 0.017举两个典型值Echo脉宽2000微秒距离就是34厘米脉宽1000微秒对应17厘米。这个换算关系待会儿写代码时会用到。HC-SR04的典型测量范围是2到400厘米在程序里需要对超过量程或没有回波的情况做超时处理否则Echo引脚一直保持高电平会把程序卡死在等待循环里。2.2 Proteus没有HC-SR04模型时用PULSE信号源模拟Echo回波Proteus元件库里有完整的STM32F103系列MCU模型、LCD1602显示模型、示波器虚拟仪器但确实没有HC-SR04的现成模型。常见做法是用Signal Generator信号发生器的PULSE模式产生一个宽度可调的脉冲接到STM32的定时器输入捕获引脚模拟传感器Echo脚的行为。你把脉冲宽度从1000微秒调到3500微秒相当于在仿真环境里把障碍物从17厘米挪到59.5厘米。信号源参数按下面这张表配置参数建议值说明WaveformPULSE脉冲波形模拟Echo单次高电平pulse width1500 us设定Echo高电平持续时间1500us对应25.5cmFrequency1 Hz触发频率配合STM32的测距周期Amplitude5 V信号幅值仿真中可直接接GPIORise/fall1 us让脉冲沿足够陡避免触发抖动这里有一个容易忽略的细节真实HC-SR04的Echo引脚输出的是5V电平而STM32的GPIO耐压是3.3V。实物连接时必须用电阻分压或电平转换芯片把Echo信号降到3.3V以下否则长时间使用存在烧毁GPIO的风险。在Proteus仿真环境里由于不涉及物理击穿问题可以用5V脉冲直连。但从仿真转移到实物时这行接线必须改否则板子会出问题。2.3 为什么不用定时器输入捕获模式也能测脉宽STM32测量Echo脉冲宽度最常见方案有两种一是使用定时器输入捕获通道捕获上升沿和下降沿两次时间戳作差另一种是把定时器当作自由运行的微秒计数器配合GPIO电平轮询。第二种方案更直观而且在Proteus仿真里更容易排查问题。我在实际写这套测距代码时更倾向于后者原因是输入捕获模式涉及定时器通道重映射、捕获中断优先级和边沿触发配置在仿真环境里一旦时序异常很难判断是信号源问题还是寄存器配置问题。GPIO轮询加定时器计数的方式逻辑更简单每个测距周期把计数器清零等待Echo脚变高后启动计时变低后停止计时计数器数值就是Echo宽度微秒。这种做法在Proteus和真实STM32上表现一致代码可维护性也好。3. 用STM32CubeMX生成最小工程再手写测距与显示代码3.1 CubeMX里的关键初始化GPIO、定时器、调试接口仿真项目和实物项目有一点不同Proteus通过HEX文件加载程序SWD调试接口并不参与仿真过程但CubeMX里默认会把PA13、PA14、PA15和PB3、PB4配置为调试功能。如果GPIO资源紧张可以在System Core - SYS - Debug里先选择Serial Wire而不是JTAG这样能多释放几个引脚。测距系统需要三类引脚Trig引脚推挽输出输出10微秒高电平触发信号。这里选PA1。Echo引脚浮空输入或上拉输入接收信号源送来的回波脉冲。这里选PA6。LCD1602数据线和控制线接一个完整8位数据端口加上RS、RW、EN三根控制线。CubeMX里用STM32F103C6作MCU模型时RCC设置为外部晶振HSE时钟树保持默认72MHz。注意TIM3用于产生微秒计时基准预分频器设置为71计数器时钟就变成1MHz计数寄存器每个数值代表1微秒。这样在代码里就不用再做单位换算读到的CNT值直接就是微秒数。3.2 测距核心代码Trig触发和Echo高电平时间测量建立工程后在main.c里补充下面的测距函数。函数先拉高Trig引脚10微秒然后启动定时器计数再循环等待Echo引脚的电平变化。uint32_t HCSR04_GetEchoUs(void) { uint32_t count 0; uint16_t timeout 0; // 1. 产生10us的Trig触发脉冲 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 2. 等待Echo引脚拉高 timeout 0; while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_RESET) { if (timeout 60000) return 0; // 超时无回波 } // 3. Echo为高电平期间TIM3以1MHz频率计数 __HAL_TIM_SET_COUNTER(htim3, 0); while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_SET) { if (timeout 60000) return 0; // 防止卡死在等待高电平结束 } count __HAL_TIM_GET_COUNTER(htim3); return count; // 返回的是微秒值 }代码里的timeout变量是安全护栏。真实场景中如果超声波没有反射回来Echo引脚会一直保持高电平如果不做超时保护程序就会卡死在第二个while循环里整个系统后续的LCD刷新、按键扫描都会停摆。timeout每次加1配合循环内的指令周期大约能扛住几十毫秒这个值在实际项目中足够用。delay_us函数需要自己实现比如在定时器里做一个微秒延时的封装或者用SystemClock运行周期数校准一个空循环延时。用TIM3计数来等待Trig脉冲宽度其实也可以但注意此时TIM3还没启动所以我在CubeMX的初始化里就把TIM3设置为自动启动这样随时可以用计数器做延时基准。3.3 距离计算、平均滤波和LCD1602输出得到Echo脉宽之后按前面提过的公式算距离。uint16_t distance_cm 0; uint32_t echo_us; echo_us HCSR04_GetEchoUs(); if (echo_us 4000) { // 超过68cm的脉宽不做处理视为超量程 distance_cm 0; } else { distance_cm (echo_us * 17) / 1000; // 保留毫米级计算余量 }公式(echo_us * 17) / 1000来源于前面说的0.017倍换算用整数运算避免浮点库带来的代码体积增加。STM32F103C6的Flash只有32KB虽然跑浮点运算不至于崩溃但整数运算在LCD显示和串口打印时更方便格式化。LCD1602的显示代码这里不再重复完整驱动关键是在主循环里控制刷新节奏while (1) { echo_us HCSR04_GetEchoUs(); // 做5次测量取平均值抑制单次测量跳变 sum 0; for (i 0; i 5; i) { echo_us HCSR04_GetEchoUs(); if (echo_us 4000) echo_us 0; sum echo_us; } avg_echo_us sum / 5; distance_cm (avg_echo_us * 17) / 1000; sprintf(lcd_buf, Dist: %3d cm , distance_cm); LCD1602_SetCursor(0, 0); LCD1602_WriteString(lcd_buf); HAL_Delay(200); }LCD1602刷新不能太快一般100毫秒到200毫秒刷新一次足够刷新太快会导致明显闪烁。HAL_Delay(200)除了控制刷新频率还顺便给超声波信号一个完整的往返周期让每次测量之间不互相干扰。4. Proteus电路搭建、HEX加载和信号源参数联调4.1 在Proteus原理图里放置MCU、LCD1602和信号发生器打开Proteus 8 Professional新建设计后从库中搜索放置这些元件STM32F103C6或STM32F103R6MCU模型两者都能跑这段代码。LM016L这是Proteus自带的LCD1602模型在Libraries里直接用。Signal Generator在Generators分类下用来产生Echo模拟脉冲。电阻和电位器用于LCD1602的对比度调节电路。先画STM32的最小系统为上电复位添加一个复位电路OSC引脚接时钟源。Proteus的STM32模型支持外部晶振和内部RC时钟仿真时直接让HSE运行即可不需要像实物那样严格匹配晶振负载电容。接线方面重点关注LCD1602接口DB0到DB7接STM32的PD0到PD7RS接PB10RW接PB11EN接PB12。LCD1602的第三脚VO接一个10k电位器的中间抽头电位器两端分别接5V和GND。VO电压决定LCD显示对比度不接这个电位器往往会导致屏幕亮但不出字符。Echo信号源输出接到PA6引脚Trig由STM32的PA1自行输出不需要外部信号。4.2 Signal Generator产生Echo脉冲手调脉宽等于移动障碍物双击Signal Generator图标把Waveform设为PULSE。Pulse Width这个参数直接决定测距结果。一开始设置1500us对应25.5cm。Frequency设为0.5Hz到1Hz比较合适让STM32每次触发完成后有足够间隙可以测量下一次。把信号源AIN接到PA6之前可以先用探针或示波器看一下波形。Proteus左侧工具栏里有虚拟仪器Dual Trace Oscilloscope把AIN接上去运行仿真后能观察到1500us宽度的脉冲。确认波形正确后再接入PA6避免把MCU和信号源接线问题混在一起排查。用示波器确认是第一优先级很多仿真不出结果其实是信号源参数没设置对程序连Echo拉高都没看到。4.3 编译生成HEX并加载到STM32模型在Keil MDK中编译工程前勾选Output选项卡里的Create HEX File编译成功后会在工程目录生成.hex文件。回到Proteus双击STM32F103C6元件在Program File一栏选择这个hex文件。注意Keil和Proteus的工程路径如果包含中文偶尔会出现加载失败建议把所有文件路径统一为英文。点击Proteus右下角的运行按钮程序开始执行。如果一切正常LCD1602上会显示一个距离值比如25cm。然后双击Signal Generator把pulse width改成3000us重新运行LCD显示的距离会变成约51cm。这个改动就相当于在真实环境中把障碍物从25厘米挪到51厘米。整套验证不需要任何硬件但GPIO配置、定时器计数、整数运算和LCD显示逻辑全部走了一遍。如果Display上不出数字最可能的原因是LCD对比度电压不对调节电位器应该能解决。如果LCD亮屏但显示乱码检查DB0到DB7接线顺序是否和CubeMX里的引脚配置一致。4.4 在仿真里增加一个串口打印通道辅助观察有时候LCD1602显示刷新太慢不好看中间变量。可以在CubeMX里开启USART1重定向printf到串口然后在串口监视器里看Echo脉宽原始值。printf(echo_us%lu, dist%ld cm\r\n, echo_us, distance_cm);Proteus提供Virtual Terminal虚拟终端从左侧仪器列表里选VIRTUAL TERMINAL把它的RXD接到STM32的TX引脚PA9在Virtual Terminal的Properties里设置波特率115200、8N1格式。程序里通过HAL_UART_Transmit发送字符串即可uint8_t msg[64]; int len sprintf((char*)msg, echo_us%lu cm\r\n, echo_us); HAL_UART_Transmit(huart1, msg, len, 100);串口输出比LCD调试速度快很多尤其是在Proteus里修改信号源脉宽后可以立刻在虚拟终端看到原始计数值变化避免LCD刷新延迟导致的误判。5. 仿真与实物差异3个排错要点和距离显示平滑技巧5.1 Proteus仿真卡顿和计数偏大的调参方法Proteus的STM32模型是解释执行的CPU占用率比QEMU等硬件仿真平台高很多。如果跑起来后界面响应缓慢可以在Debug菜单里降低仿真速度或者把信号源频率降到0.2Hz减少每秒触发次数。实测下来整个系统每秒做3到5次测距刷新已经足够演示远低于HC-SR04的最高响应频率。另一个常见问题是测量值明显大于理论值。在仿真环境里这通常不是程序问题而是信号源的脉冲上升沿或下降沿设置得太缓。把Rise Time和Fall Time都改成1us或更小可以消除沿时间带来的额外计数误差。实物环境则要注意测距结果随温度漂移室温每升高10℃声速大约增加1.8m/s如果要做精密测量可以用一个温度传感器做修正。5.2 LCD1602对比度接错导致花屏的判断方法LCD1602显示一个方块字符或者干脆白屏八成是VO引脚电压不对。仿真中电位器调整到使VO电压在2V到4V之间字符清晰可见。另一个要注意的是LCD1602的RW引脚如果直接接地程序里就不要去切换读写模式否则电平冲突。我一般把RW直接接GND写入时才不会出现忙标志判断问题。Proteus的LM016L模型自带了一个忙状态检查逻辑。如果程序在等待忙标志时出错显示就会卡在半初始化状态。可靠做法是把LCD初始化代码中每一位之间的延时拉长初始化完成后屏幕输出Testing字符验证驱动正常。5.3 让距离数值更平滑中值滤波和限幅滤波的组合单次测距会有随机跳变仿真中表现为显示值在稳定距离上下跳1到2厘米。实弹场景中更严重声波干扰、环境噪声都会让Echo脉宽异常。常见做法是连续采样5次排序后取中间值再对相邻两次中间值做限幅。uint16_t getFilteredDistance(void) { uint16_t samples[5]; uint16_t tmp; for (int i 0; i 5; i) { samples[i] HCSR04_GetEchoUs(); } // 简单冒泡排序取中值 for (int i 0; i 4; i) { for (int j 0; j 4 - i; j) { if (samples[j] samples[j 1]) { tmp samples[j]; samples[j] samples[j 1]; samples[j 1] tmp; } } } return samples[2]; // 中位数值 }当检测到前后两次中值变化超过设定阈值时可以认为有障碍物快速移动直接采用最新值而不是继续滤波。这样既保持数值平滑又不会导致通行人员或移动障碍物的目标丢失。最后再根据回波时间长短做量程限制超过4毫秒的脉宽一律视为无效测量距离显示为0。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价