基于STM32的Proteus超声波测距仿真:从原理到LCD显示完整流程
2026/9/15 11:51:40 网站建设 项目流程

简介:基于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.5cm
Frequency1 Hz触发频率,配合STM32的测距周期
Amplitude5 V信号幅值,仿真中可直接接GPIO
Rise/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或STM32F103R6:MCU模型,两者都能跑这段代码。
  • LM016L:这是Proteus自带的LCD1602模型,在Libraries里直接用。
  • Signal Generator:在Generators分类下,用来产生Echo模拟脉冲。
  • 电阻和电位器:用于LCD1602的对比度调节电路。

先画STM32的最小系统:为上电复位添加一个复位电路,OSC引脚接时钟源。Proteus的STM32模型支持外部晶振和内部RC时钟,仿真时直接让HSE运行即可,不需要像实物那样严格匹配晶振负载电容。

接线方面重点关注LCD1602接口:DB0到DB7接STM32的PD0到PD7,RS接PB10,RW接PB11,EN接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。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询