ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32超声波测距Proteus仿真:定时器输入捕获与LCD1602显示实现

STM32超声波测距Proteus仿真:定时器输入捕获与LCD1602显示实现 简介这是一套基于STM32与Proteus的超声波测距显示仿真工程适合嵌入式初学者、电子类课程设计及毕业设计人群便于在无硬件环境下完成测距逻辑与显示输出的联合验证。压缩包共274个文件总大小约9.83MB涵盖C源码、头文件、Keil工程、Proteus仿真工程及hex/axf编译产物还有docx说明文档目录与编译配置完整可直接打开仿真并对照代码理解实现细节。已有240人学习下载。借助该工程可掌握STM32定时器输入捕获在超声波测距中的用法理解GPIO、定时器、中断等模块配置流程并通过Proteus界面观察距离显示结果完整源码和工程结构也能作为二次开发模板便于移植到实际开发板或继续扩展功能。1. 为什么 STM32 超声波测距在 Proteus 里“跑不动”把 STM32F103C6、HC-SR04 和 LCD1602 拖进 Proteus按下运行结果距离数据一动不动——这不是电路画错而是多数人忽略了两个事实Proteus 里 STM32 的启动时钟会因 HSE 配置问题退回 HSI定时器分频按 72MHz 算出来的捕获精度完全不对HC-SR04 仿真器件又依赖外部时钟激励ECHO 没有回波脉冲程序直接卡死在等待循环里。这篇博文把 TRIG/ECHO 测距时序、STM32 定时器输入捕获参数、LCD1602 四线驱动和 Proteus 虚拟示波器验证串成一套最小可运行方案适合准备课设、毕业设计或者在真机调试前先用仿真排查算法逻辑问题的工程师。内容不会依赖某个现成工程模板按照下面步骤自己新建工程也能完整复现。2. HC-SR04 测距时序与 STM32 定时器捕获方案选型2.1 TRIG/ECHO 时序10us 触发脉冲和回波脉宽换算HC-SR04 的测量流程是主机先向 TRIG 引脚发送一个至少 10us 的高电平模块内部发出 40kHz 超声波同时 ECHO 引脚被拉高当接收端检测到回波后ECHO 引脚再被拉低。ECHO 高电平持续的时间就是超声波从发射到返回的总时长单向距离等于脉宽乘以声速再除以 2。20℃ 时声速取 343m/s换算成厘米每微秒是 0.0343cm/us所以测距公式可以写成距离(cm) echo_pulse_us * 0.0343 / 2简化后距离(cm) echo_pulse_us * 0.01715在 Proteus 里做这一套仿真重点不在超声波物理过程而在两个时序细节。第一个是 TRIG 脉冲宽度不能靠普通延时草草了事HSI 8MHz 下延时会偏大但 15us 到 20us 在接收端看来没有区别。第二个是 ECHO 回波脉宽最长可达 30ms 以上对应 5 米左右量程定时器计数频率至少要能分辨微秒级变化。常见做法是把定时器计数频率设置为 1MHz这样计数器每加 1 就是 1us计算脉宽时不需要再做缩放。2.2 脉宽测量的三个方案对比测量 ECHO 高电平时间在 STM32 上常用的有三种选型差别主要在中断开销和 Proteus 仿真稳定性上。方案原理精度CPU 占用Proteus 仿真注意点GPIO 轮询 定时器读计数值循环读 ECHO 电平跳变后读 CNT受循环指令延时影响高适合验证测距流程代码简单外部中断 定时器EXTI 上升沿/下降沿中断记录 CNT高中中断响应在仿真中容易受信号源毛刺干扰定时器输入捕获TIMx_CH 捕获上升沿和下降沿到 CCR最高低需要正确配置捕获极性和时基启动更稳我一般建议仿真阶段直接采用定时器输入捕获方案。原因是输入捕获由硬件完成时间戳记录不依赖 CPU 轮询速度仿真器步长变化不会影响测量结果。需要注意 TTL 电平反转在 Proteus 中模拟速度较慢如果使用外部中断法一个高频毛刺就会导致多次触发输入捕获配合可选信号滤波能把这类问题压到最低。2.3 Proteus 中的 HC-SR04 模型差异不同版本 Proteus 的超声波器件模型不完全一致常见有两类一类内置振荡器接上 VCC、GND、TRIG、ECHO 就能工作另一类额外暴露了 CLK 输入引脚需要外部提供一个 1MHz 左右的方波源作为模块内部时钟否则 ECHO 永远没有响应。开始仿真后如果 TRIG 有波形、ECHO 一直为低第一件事就是检查当前模型是否有未接的 CLK 引脚或双击器件确认属性里是否有时钟频率选项。配套的 STM32 引脚分配建议TRIG 放 PA0作为普通推挽输出ECHO 放 PA6对应 TIM3_CH1 输入捕获通道。这样 TRIG 和 ECHO 分属不同 GPIO逻辑清晰后续调试时虚拟示波器一根接 PA0、一根接 PA6波形观察非常直观。3. 用 STM32 标准库实现输入捕获、距离换算和 LCD 显示3.1 时钟配置为什么仿真中 SystemCoreClock 只有 8MHzSTM32F10x 标准库的 SystemInit 默认优先使能 HSE 外部高速晶振并检查 HSE 是否就绪。Proteus 如果没有给 STM32 放置晶振并正确配置时钟属性HSE 就不会就绪库函数会等待一段时间后自动切回 HSI 内部 8MHz 时钟。这意味着芯片实际运行频率不是 72MHz而是 8MHz。这个现象直接影响定时器分频。如果按照 72MHz 设定预分频 PSC71期望得到 1MHz 计数时钟实际得到的却是 8MHz/72≈111kHz脉宽计数值偏大约 9 倍测距结果会超出物理范围。因此在 Proteus 仿真工程里统一按 HSI 8MHz 计算分频参数PSC7 即可得到 1MHz 计数频率。若你在配置中手动启用了 HSE 且晶振属性正确则按实际频率重新计算 PSC。3.2 TIM3_CH1 输入捕获初始化代码先初始化 GPIO 和 TIM3设置的参数以 STM32F103 标准库为例主频按 8MHz 计算。void TIM3_IC_Init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef time_base; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // PA6 作为 TIM3_CH1 输入端口浮空输入 gpio.GPIO_Pin GPIO_Pin_6; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio); // 8MHz / 8 1MHz计数器每 1us 加 1 time_base.TIM_Prescaler 7; time_base.TIM_CounterMode TIM_CounterMode_Up; time_base.TIM_Period 0xFFFF; time_base.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, time_base); TIM_Cmd(TIM3, ENABLE); }PSC7 是把 8MHz 的 HSI 时钟做 8 分频得到 1MHz 计数时钟。ARR 设置为 0xFFFF 是因为测距脉宽最长可能超过 30ms需要 16 位计数器最大范围 65535us。若你的 Proteus 工程里确认 HSE 已配置且运行在 72MHz请把TIM_Prescaler改成 71。这个参数是仿真不准确的经典来源排查优先级最高。3.3 测量一次回波脉宽的完整函数捕获逻辑分两步第一次等待上升沿记录 CCR1 值第二次切换捕获极性为下降沿记录 CCR1 值并返回差值。加入超时判断是为了防止 ECHO 一直无响应时程序卡死在 while 循环Proteus 里一旦卡死界面状态看起来像“死机”实际是等待逻辑没有出口。uint32_t Echo_Measure_Pulse(void) { TIM_ICInitTypeDef ic; // 配置上升沿捕获 ic.TIM_Channel TIM_Channel_1; ic.TIM_ICPolarity TIM_ICPolarity_Rising; ic.TIM_ICSelection TIM_ICSelection_DirectTI; ic.TIM_ICPrescaler TIM_ICPSC_DIV1; ic.TIM_ICFilter 0x0F; // 仿真时可减小毛刺干扰 TIM_ICInit(TIM3, ic); TIM_ClearFlag(TIM3, TIM_FLAG_CC1); TIM_SetCounter(TIM3, 0); // 等待上升沿30ms 内无回波直接超时退出 while (TIM_GetFlagStatus(TIM3, TIM_FLAG_CC1) RESET) { if (TIM_GetCounter(TIM3) 30000) return 0; } uint32_t rise TIM_GetCapture1(TIM3); // 切换为下降沿捕获 ic.TIM_ICPolarity TIM_ICPolarity_Falling; TIM_ICInit(TIM3, ic); TIM_ClearFlag(TIM3, TIM_FLAG_CC1); while (TIM_GetFlagStatus(TIM3, TIM_FLAG_CC1) RESET) { if (TIM_GetCounter(TIM3) - rise 30000) return 0; } uint32_t fall TIM_GetCapture1(TIM3); return fall - rise; }上升沿到来时硬件将当前计数器值锁存到 CCR1所以rise变量实际记录的是从 0 开始的上升沿时间点同理fall是下降沿时间点二者相减就是 ECHO 高电平持续时间。TIM_ICFilter设置为 0x0F 在仿真中可以过滤信号源上的窄脉冲但也会增加约 240ns 的捕获延迟对测距结果的影响在毫米级可以忽略。等待下降沿时用TIM_GetCounter(TIM3) - rise判断超时避免计数器回绕造成的误判。3.4 TRIG 脉冲生成与主循环距离计算TRIG 引脚设置为 PA0 推挽输出发送 15us 高电平即可满足模块要求。距离计算使用 20℃ 声速主循环里每 200ms 触发一次测距并更新一次 LCD。void Trigger_Pulse(void) { GPIO_SetBits(GPIOA, GPIO_Pin_0); Delay_us(20); GPIO_ResetBits(GPIOA, GPIO_Pin_0); } int main(void) { GPIO_Config(); TIM3_IC_Init(); LCD_Init(); while (1) { Trigger_Pulse(); uint32_t pulse_us Echo_Measure_Pulse(); if (pulse_us 0 pulse_us 30000) { float dist_cm (float)pulse_us * 0.01715f; char buf[16]; sprintf(buf, Dist:%6.1f cm, (double)dist_cm); LCD_Clear(); LCD_WriteString(0, 0, buf); } else { LCD_Clear(); LCD_WriteString(0, 0, Out of range); } Delay_ms(200); } }0.01715 的推导过程是34300 cm/s 除以 1000000 得到 0.0343 cm/us再除以 2 得到单程距离系数即 0.01715 cm/us。pulse_us超时返回 0 时直接显示 Out of range防止把无效数据写入显示。这里的Delay_us可以用 SysTick也可以用简单的 for 循环递减仿真阶段软延时完全够用但如果时钟从 8MHz 调整到其他频率延时函数需要同步修改。3.5 LCD1602 四线驱动的最小代码Proteus 中 LM044L 器件兼容 HD44780 控制器常见做法是使用四线模式只在 DB4 到 DB7 上传输数据。下面给出初始化和写字符的最小实现RS 接 PB0EN 接 PB2RW 直接接地DB4 到 DB7 接 PB12 到 PB15。void LCD_WriteNibble(uint8_t nibble, uint8_t rs) { uint16_t tmp GPIO_ReadOutputData(GPIOB); tmp 0x0FFF; // 清空 PB12-PB15 tmp | (nibble 0x0F) 12; GPIO_Write(GPIOB, tmp); GPIO_WriteBit(GPIOB, GPIO_Pin_0, rs); // RS 电平 GPIO_SetBits(GPIOB, GPIO_Pin_2); // EN 拉高 Delay_us(2); GPIO_ResetBits(GPIOB, GPIO_Pin_2); // EN 拉低数据锁存 } void LCD_Cmd(uint8_t cmd) { LCD_WriteNibble(cmd 4, 0); LCD_WriteNibble(cmd 0x0F, 0); Delay_us(40); } void LCD_Data(uint8_t dat) { LCD_WriteNibble(dat 4, 1); LCD_WriteNibble(dat 0x0F, 1); Delay_us(40); } void LCD_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // PB0/PB1/PB2 推挽输出PB12-PB15 推挽输出 GPIO_Config_LCD(); Delay_us(50000); LCD_WriteNibble(0x03, 0); Delay_us(5000); LCD_WriteNibble(0x03, 0); Delay_us(5000); LCD_WriteNibble(0x02, 0); Delay_us(5000); LCD_Cmd(0x28); // 4 位模式2 行显示 LCD_Cmd(0x0C); // 开显示不显示光标 LCD_Cmd(0x01); // 清屏 Delay_us(2000); } void LCD_WriteString(uint8_t row, uint8_t col, char *str) { LCD_Cmd(row 0 ? 0x80 col : 0xC0 col); while (*str) LCD_Data(*str); }四线模式的初始化序列里前三个 0x03 脉冲是为了让控制器在 8 位模式下识别指令边界等它确认进入 4 位模式后再发送 0x28。不要省略这条初始化序列直接发送功能设置命令LCD 会显示乱码。Proteus 的 LM044L 仿真速度比真实液晶快但延时过短会在某些版本中产生写入失败代码中的 40us 和 5000us 均有裕量。4. Proteus 工程搭建、接线表与虚拟示波器波形验证4.1 新建工程的器件清单和关键属性在 Proteus 中新建工程并选择基于原理图的仿真模式再添加以下器件器件搜索关键字说明STM32 主控STM32F103C6在库中选择带 SWD 或 JTAG 的型号超声波模块HC-SR04若只有无源器件模型则额外加一个 1MHz 时钟源接 CLK液晶显示LM044L兼容 LCD1602 指令集终端VIRTUAL TERMINAL从虚拟仪器面板调出用于输出调试信息晶振CRYSTAL可选不接则按 HSI 8MHz 运行STM32F103C6 的 FLASH 和 RAM 较小但本工程代码规模不大完全足够。如果已有工程是基于 STM32F103RCT6 配置的外设在 Proteus 里换成 C6 型号只要启动文件和链接脚本不报错通常也能运行但保险的做法是直接在 Keil 里按 C6 或 R6 新建工程避免内存映射差异带来的启动异常。4.2 引脚接线表先完成 STM32 最小系统接线再连接外设。仿真中复位引脚和 BOOT 引脚按默认状态即可关键是下面的外设连线不能接错。HC-SR04 引脚STM32 引脚说明VCC5V 电源模块供电TRIGPA010us 以上高电平触发ECHOPA6TIM3_CH1 输入捕获GNDGND共地LM044L 引脚STM32 引脚RSPB0RWGND直接接地ENPB2DB4PB12DB5PB13DB6PB14DB7PB15VCC 和背光 5VGNDGND接线时注意 HC-SR04 模型如果是带 CLK 的版本还需要把 CLK 接到信号发生器上信号发生器设为 1MHz 方波、幅值 5V。ECHO 输出是 5V 电平STM32 的 PA6 引脚能容忍 5V但如果换上 3.3V 供电的传感器模型需要确认电平匹配。4.3 在 Keil5 里编译生成 hex 并加载到芯片工程代码写好后在 Keil5 中配置输出选项勾选 Create HEX File编译生成 hex 文件。回到 Proteus双击 STM32 芯片在 Program File 一栏选择生成的 hex 路径。晶振频率设置为 8MHz 或按 HSI 处理不要设成 72MHz否则与代码里 PSC7 的设定冲突。点击运行后先观察两个现象用来判断整体链路是否正常。LCD 第一行是否显示 Dist 开头的字符串虚拟示波器上是否看到 TRIG 短脉冲和 ECHO 宽脉冲如果两者都没有优先检查程序是否载入。如果程序载入但 LCD 无显示用虚拟终端看 MCU 是否复位循环Proteus 左下角状态栏的 CPU 占用率可以辅助判断。仿真运行正常后一般为 30% 到 60% 之间频繁复位会导致占用率波动。4.4 用虚拟示波器验证 TRIG 和 ECHO 波形从虚拟仪器面板拖出 Oscilloscope通道 A 接 PA0通道 B 接 PA6将时基设置为 10ms/div触发电平设为 2V。运行后应当看到这样的波形通道 A 每隔 200ms 出现一个宽度很窄的触发脉冲通道 B 在触发脉冲之后出现一个宽度明显大于触发脉冲的高电平段这就是 ECHO 回波。在波形验证时可以采用“手动改变模块属性中的障碍物距离”来观察 ECHO 脉宽变化。部分 Proteus 模型允许双击器件进入属性面板找到类似 Distance 的字段并调整数值ECHO 脉宽会相应变化。如果你的模型没有该属性则通过改变传感器模型中反射面参数或直接修改测量函数的声速补偿来验证换算逻辑一致。这个验证步骤能立刻区分“电路问题”和“代码问题”。4.5 用虚拟终端输出原始计数值辅助调参如果 LCD 上距离值偏离预期最好把原始脉宽值输出到虚拟终端而不是只看最终距离。添加一个 USART1 外设初始化PA9 接虚拟终端 RXDPA10 接虚拟终端 TXD然后 printf 输出pulse_us值。printf(pulse%d us\r\n, (int)pulse_us);在同一环境温度下如果输出脉宽数值稳定且与理论值一致说明测距链路没有问题如果不稳定需要考虑用滑动平均滤波抑制仿真噪声。虚拟终端还可以显示程序中的超时次数连续多次超时说明 TRIG 到 ECHO 的链路不通需要回查 CLK 信号和 ECHO 接线。5. 显示滤波、温度补偿与仿真里的三个隐蔽坑5.1 滑动平均滤波避免距离跳变Proteus 仿真中超声波模型的回波脉宽不像真实模块那样有连续噪声但偶尔也会出现单次跳变尤其在切换障碍物距离属性时。常见做法是取最近 5 次测距结果去掉最大值和最小值后再平均这比简单算术平均值对突变值更鲁棒。float Distance_Filter(float current) { static float buf[5]; static uint8_t idx 0; float sum 0, min_v 10000, max_v -1; buf[idx] current; idx (idx 1) % 5; for (uint8_t i 0; i 5; i) { sum buf[i]; if (buf[i] min_v) min_v buf[i]; if (buf[i] max_v) max_v buf[i]; } return (sum - min_v - max_v) / 3.0f; }每完成一次有效测量就调用一次该函数输出结果用于显示。注意滤波会引入约一个周期的滞后200ms 的测量周期下滞后约 1 秒运动目标场景中需要权衡。这个滤波函数无法消除由超时返回 0 造成的错误值在进入滤波前要先判断pulse_us是否有效。5.2 声速温度补偿和量程映射20℃ 声速常数 343m/s 足够应付仿真但如果后续移植到真实环境温度对声速的影响明显。声速与温度的关系近似为 v 331.4 0.6 * T温度单位是摄氏度声速单位是 m/s。换算成 cm/us 后距离公式变为float v_cm_per_us (331.4f 0.6f * temperature) / 10000.0f; float dist_cm pulse_us * v_cm_per_us / 2.0f;在代码里增加一个 temperature 变量用按键调整温度值LCD 第二行显示当前温度就能自制成一个带温度修正的超声波测距显示系统。要改造成超声波测距报警系统仿真图时只需要在主循环里增加阈值判断当dist_cm小于设定值就点亮一个 LED 或蜂鸣器无需改动测距核心逻辑。5.3 三个仿真坑和对应处理第一个坑是时钟回退。代码里写的 SystemCoreClock 变量如果还是 72M则软延时和超时判断都会偏快 9 倍。建议在初始化后输出一次SystemCoreClock到虚拟终端确认实际值。第二个坑是 HC-SR04 模型缺 CLK。ECHO 完全没有输出时优先检查模型引脚定义不要先怀疑定时器代码。第三个坑是 Proteus 内部改换 STM32 型号时启动文件不匹配这跟平时用 STM32Cube 改型号不同Cube 会重新生成启动文件而在 Proteus 里直接换芯片不会更新链接脚本表现是程序下载后不运行或停在 HardFault。处理办法是回到 Keil 里按目标型号重新编译不要只换 Proteus 器件。最后留一个实用技巧在 Proteus 里双击 STM32 芯片将 Program File 配置好后配合 Keil 的 Debug 选项卡选择 Proteus VSM Simulator就能在源码里打断点单步跟踪实时看TIM_GetCapture1返回的时间戳。用这个方式排查输入捕获极性配置比用示波器猜计数过程更直接。本文还有配套的精品资源点击获取
返回列表