资讯动态

STM32与HC-SR04倒车雷达:从超声波测距到OLED显示

发布时间:2026/9/11 16:54:05 来源:尧图企业网站定制
简介基于STM32的倒车雷达课程设计工程包使用HC-SR04超声波传感器完成测距并将距离信息通过OLED屏幕实时显示适合嵌入式初学者、电子类课程设计及竞赛备赛参考。工程基于STM32F1系列HAL库开发完整涵盖超声波触发与回波捕获、定时器计时、SSD1306 OLED显示驱动等关键环节能够帮助学习者深入理解传感器数据采集、外设初始化与中断处理的实际流程。压缩包共176个文件包括核心C源码、Keil工程配置、STM32CubeMX初始化文件、编译生成的目标文件及简要说明文档整体约6.38MB目录结构清晰可阅读源码学习也可直接烧录验证效果。目前已有514人学习对于想完整走通一个STM32物联网小项目的读者是一份不错的实践样例。此外读者可借助该工程熟悉定时器输入捕获测量脉冲宽度的方法也可在此基础上扩展距离阈值报警或数码管显示等功能。1. 基于STM32的倒车雷达项目HC-SR04与OLED构成的最短实现路径倒车雷达听起来像玩具但把它拆开就是单片机工程师最常用的一套外设组合GPIO输出脉冲、输入捕获测脉宽、传感器换算、显示驱动和状态分档。STM32在这里并不是非它不可关键是HC-SR04回波信号是5V电平STM32的IO耐压、定时器输入捕获精度和HAL库封装恰好是这套做法最典型的选择。OLED屏解决“距离可见”问题0.96寸128x64和0.91寸128x32都能用I2C两根线就能把数据送出去。这套方案适合学完GPIO和中断、想做毕业设计或临时测距工具的人二十块钱的成本就能把串口里的一串距离值变成一块能装在模型车上的屏幕。2. HC-SR04测距原理与OLED显示协议先弄懂量程、精度和驱动开销2.1 HC-SR04靠10us Trig触发Echo高电平宽度就是回波时间HC-SR04模块上电后不会自己工作必须先给Trig引脚一个大于10us的高电平脉冲模块内部自动发出8个40kHz方波同时把Echo引脚拉高当超声波遇到障碍物反射回来接收电路检测到回波后把Echo拉低。也就是说Echo引脚高电平持续的时间就是超声波从发射到返回经过的总时长你拿到的不是一个距离数值而是一个需要自己换算的脉宽。换算关系决定了代码里的系数距离不是声速乘时间因为脉冲走了往返路径。常温下声速约340m/s也就是0.034cm/us。如果脉宽单位是us距离cm 脉宽 * 0.034 / 2整理后就是0.017。这个0.017会成为主循环里最核心的换算常数。模块量程通常在2cm到400cm。2cm以下是盲区因为发射和接收共用同一块压电陶瓷回波还没等接收电路恢复就已经回来了。精度标称3mm但这个数值只对平整硬质反射面有意义。墙面、地面、木板这类表面反射稳定窗帘、衣物、草地这类吸声面经常丢回波实测时不要拿毛绒玩具测距离后怀疑代码写错了。2.2 0.96寸OLED是I2C还是SPI从引脚数量和刷新率决定市面上常见的0.96寸OLED模块驱动芯片大多是SSD1306少数是SH1106。两者初始化命令和显存组织有细微差别I2C地址通常都是0x3C也有0x3D具体由模块上地址电阻决定。0.91寸规格是128x320.96寸是128x64页寻址方式一样只是显存行数和起始页不同驱动代码基本可以复用。接口控制引脚特点适用场景I2CSCL、SDA只需两个IO刷新较慢数字、短字符串SPISCL、SDA、DC、RES、CS刷新快接线多动画、波形绘制I2C模式在400kHz下刷新一帧128x64大约要30ms如果每秒只更新几个数字这点时间完全不影响。SPI模式要占用5个IO换来的刷新速度对倒车雷达这种场景没有感知所以我一般会选I2C版本模块。买模块时看排针标注如果是4针GND/VCC/SCL/SDA那可以直接用如果排针是7针说明卖家默认SPI模式需要改板上的电阻才能切到I2C。2.3 测Echo脉宽GPIO轮询和定时器输入捕获两种做法的边界读取Echo脉宽最直观的做法是GPIO轮询发完Trig后反复读Echo电平用一个微秒级计数器测高电平宽度。HAL_Delay的最小粒度是1ms显然不能用所以要自己实现delay_us或者用DWT外设的CYCCNT寄存器。轮询的缺点是在Echo高电平期间CPU被占住按4m量程算最长要占20多毫秒这段时间里OLED没法刷新按键响应也会卡顿。定时器输入捕获是更干净的做法Echo接到定时器通道引脚上上升沿把计数器值锁存到CCR1下降沿再锁存到CCR2两个寄存器的差值乘以定时器计数周期就是脉宽。捕获过程不占CPU精度由定时器分频决定。但配置代码比轮询复杂还要事先算好计数范围4m距离对应的脉宽约23.5msSTM32F103的16位定时器在72MHz下直接计数只能数到0.9ms就溢出必须分频到1us计数一次让最大值能覆盖65ms以上或者直接换用32位定时器TIM2/TIM5。// 定时器分频到1us计数一次后两次捕获相减就是脉宽 uint32_t capture_diff TIM2-CCR2 - TIM2-CCR1; // 单位us float distance_cm capture_diff * 0.017f;这段代码只做演示实际工程里通常把差值换算放在中断下半部分避免在中断里做浮点运算。把项目跑通和把项目做到能看之间的差别就在这里倒车雷达对实时性没那么苛刻教材里几乎都会选输入捕获展示外设能力但工程上轮询方案代码量小、问题定位简单。先用轮询拿到稳定距离再换捕获是常见演进路线。下一章先按轮询给出完整代码捕获方案留在最后一章讲。3. 在STM32 HAL库上输出倒车雷达代码从CubeMX引脚配置到OLED显示3.1 引脚接线表与CubeMX必改项HC-SR04需要5V供电Echo在高电平期间也是5V。不要直接把它接到STM32的3.3V IO上F103有标注FT的5V容忍引脚但G0、L4这些系列不全是FT最稳的做法是加一级电阻分压把5V降到3.3V以下再进IO。如果你更习惯用STM32标准库新建工程CubeMX生成的代码可以只参考引脚配置测距和OLED驱动平移过去时把HAL_GPIO_WritePin替换成GPIO_SetBits/GPIO_ResetBits就能保持逻辑一致。信号模块端STM32端说明TrigTrig引脚PA0GPIO输出推挽输出默认低电平EchoEcho引脚PA1GPIO输入串10k电阻后接PA1OLED-SCLSCLPB6I2C1_SCLOLED-SDASDAPB7I2C1_SDA电源VCC/GND5V/GNDHC-SR04必须5V供电CubeMX里除了时钟树最关键的是三处SYS里把Debug打开为Serial Wire否则下载过一次程序后第二次下载就可能提示找不到目标RCC里打开HSE外部晶振不要停留在HSI上I2C1的频率设为400kHzPA0和PA1保持默认的GPIO模式即可。如果打算用软件模拟I2CI2C1可以不初始化直接把PB6/PB7配置成开漏输出外部接4.7k上拉电阻到3.3V。下面代码以硬件I2C为例软件I2C的差异只在发送字节那一层。打开Debug Serial Wire这个动作常被忽略。Keil5安装好STM32芯片包、新建工程后不配置这一项第一次烧录正常第二次烧录很容易报错原因就是程序把SWD引脚复用掉了。3.2 用GPIO翻转和微秒延时读Echo脉宽轮询方式的完整测距函数最关键的是超时保护。没有超时保护一旦Echo一直低电平或一直高电平整个单片机就卡死在那两行while里OLED和按键全部失去响应。#include main.h #include stdio.h #define TRIG_PORT GPIOA #define TRIG_PIN GPIO_PIN_0 #define ECHO_PORT GPIOA #define ECHO_PIN GPIO_PIN_1 void delay_us(uint16_t us) { // 近似空循环72MHz主频下约18次循环对应1us for (uint32_t i 0; i us * 18; i) { __NOP(); } } // 返回值距离单位cm-1.0f表示无效测量 float HCSR04_ReadDistanceCM(void) { uint32_t pulse_us 0; uint16_t guard; // 触发先低后高保证10us以上高电平 HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); delay_us(4); HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET); delay_us(12); HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); // 等待Echo变高超时约10ms guard 0; while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) GPIO_PIN_RESET) { if (guard 10000) return -1.0f; delay_us(1); } // 测量Echo高电平时长超时约25ms对应4米量程 pulse_us 0; while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) GPIO_PIN_SET) { if (pulse_us 25000) return -1.0f; delay_us(1); } return pulse_us * 0.017f; }代码里Trig先从低变高等12us再拉低满足HC-SR04高电平至少10us的要求。第一个等待循环guard上限10000对应10msEcho在模块正常响应后通常在1ms内拉高超过10ms说明模块没触发或Echo线没接。第二个循环pulse_us上限25000对应25ms常温下4m距离产生的脉宽约23.5ms超过这个数就该认定无效。返回-1.0f让业务层能区分“距离超限”和“测距失败”这是倒车雷达必须有的分支。delay_us用的是空循环近似72MHz下每个循环大约4个时钟周期us * 18约等于1us。开了-O2优化后循环会被压缩距离值会系统性偏移。想省心就用DWT外设把CYCCNT读出来精确延时但那在CubeMX里要多配一个全局变量初期先用空循环把流程跑通即可。3.3 用HAL库点亮OLED屏幕全流程从I2C地址到字符输出给OLED写数据底层是I2C。用HAL硬件I2C时地址要左移一位因为HAL接口参数需要7位地址左移后的值0x3C写成0x3C 1。很多人在第一步就卡在这里代码里用0x3C还是0x78取决于驱动库封装是直接调HAL函数还是自行拼地址字节。#define OLED_ADDR (0x3C 1) void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 10); } void OLED_WriteData(uint8_t data) { uint8_t buf[2] {0x40, data}; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 10); } void OLED_Init(void) { const uint8_t cmds[] { 0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40, 0x8D, 0x14, 0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12, 0x81, 0xCF, 0xD9, 0xF1, 0xDB, 0x40, 0xA4, 0xA6, 0xAF }; for (uint16_t i 0; i sizeof(cmds); i) { OLED_WriteCmd(cmds[i]); } } void OLED_Clear(void) { for (uint8_t page 0; page 8; page) { OLED_WriteCmd(0xB0 page); OLED_WriteCmd(0x00); OLED_WriteCmd(0x10); for (uint8_t col 0; col 128; col) { OLED_WriteData(0x00); } } }这组初始化命令是SSD1306的标准序列0xAE关显示0x8D 0x14打开内部电荷泵0xA1和0xC8设置扫描方向0xAF开显示。OLED显存按页组织128x64被分成8页每页8个像素点。OLED_Clear遍历8页、每页128列全部写0最慢的I2C下约30ms所以主循环里不要频繁调用。显示距离值的最小改动是只在变化时刷新数值区域先写静态标题再覆盖动态内容sprintf(buf, %3dcm, (int)distance); OLED_ShowString(0, 0, Distance:); OLED_ShowString(24, 3, buf);sprintf里%3d保证三位数占位距离从99cm跳变到100cm时不会留下旧字符残留。OLED_ShowString需要你自己实现字模表常见的SSD1306驱动库都带这句接口显示汉字则要专用取模工具这部分放在第4章档位图标里展开。4. 给倒车雷达加显示逻辑与距离档位判断阈值、滤波和刷新节奏4.1 主循环结构与OLED刷新率不要每次测量都全屏刷新测完距离立刻刷新屏幕这是倒车雷达程序最常见的问题。HC-SR04一个测量周期最低也要十几毫秒如果主循环跑一次测距就清一次屏屏幕会明显闪烁。原因是OLED_Clear本身要占30ms左右加上取模、数字转字符串刷新时间超过人眼闪烁融合频率。常见做法是把显示拆成两层第一层是静态部分比如页眉的“Distance”文字只在初始化时写一次第二层是动态部分比如距离数值和档位图标只在数值变化时更新。OLED显存按页组织写单个字符前先拉到对应页地址写入字符的5x8点阵数据成本远低于全屏重写。float dist; int last_disp -1; char buf[16]; while (1) { dist HCSR04_ReadDistanceCM(); if (dist 0 abs((int)dist - last_disp) 1) { sprintf(buf, %3dcm, (int)dist); OLED_ShowString(24, 3, buf); // 只覆盖数值区域 last_disp (int)dist; } HAL_Delay(80); }刷新阈值设为1cm距离变化不到1cm不重画。last_disp初始化为-1保证第一次循环必定刷新一次。延时80ms相当于每秒约12次测距HC-SR04模块建议测量间隔不小于60ms50ms偏快模块内部电容和压电晶体来不及完全复位距离值会偶发抖动80到100ms是稳妥区间。4.2 距离分档显示安全、警告、危险三档的判断逻辑倒车雷达不能只显示数字装在车模或小车上时要能根据距离触发蜂鸣器或改变屏幕提示。分档判断是独立于测距逻辑的一层这样后续把OLED换成LCD1602或加蜂鸣器只改显示层。typedef enum { ZONE_SAFE 0, ZONE_WARN, ZONE_DANGER } ZoneType; ZoneType check_zone(float dist) { if (dist 0) return ZONE_DANGER; // 无效回波按最危险处理 if (dist 30.0f) return ZONE_DANGER; if (dist 80.0f) return ZONE_WARN; return ZONE_SAFE; }档位距离范围屏幕提示建议动作安全大于80cmSAFE不提示警告30到80cmWARN蜂鸣器慢速间隔危险小于30cm或无效STOP!蜂鸣器连续响准备制动阈值30cm和80cm是倒车雷达最常见的保守值。30cm接近停车极限80cm是预警线。HC-SR04盲区是2cm2cm以内的值本来就测不到check_zone收到-1这类无效值时要按危险处理不能丢弃后保持上一帧的安全状态否则车已经撞上屏幕还显示绿色。OLED端的分档显示用字符串比画图标好控制const char *zone_text[3] {SAFE , WARN , STOP! }; ZoneType zone check_zone(dist); OLED_ShowString(24, 6, (char *)zone_text[zone]);危险档还可以把蜂鸣器接到另一个GPIO用定时器控制鸣叫节奏。OLED上显示反色的STOP! 比单纯数字更能提醒人。4.3 温度补偿与中值滤波让回波脉宽更可信HC-SR04模块内部没有温度传感器默认按常温声速计算距离。室外冬天零下10度声速约328m/s误差接近4%。倒车雷达在近距离场景误差不大但要把这套代码当测距仪器用温度补偿就是必需项。声速与温度的关系式是 c 331.4 0.607 x Tm/s。25度代入得346.6m/s工程上这个公式精度足够。换算系数0.017对应常温声速340m/s温度变化时改为float tempC 25.0f; // 从DS18B20或BMP280读入 float sound_speed 331.4f 0.607f * tempC; float coeff sound_speed / 1000000.0f / 2.0f; return pulse_us * coeff;coeff的单位是cm/us。25度时约0.0173比固定0.017多1.8%对应4m距离约7cm偏差。倒车雷达可以不做但加入后代码结构不变只替换return那一行。滤波环节中值滤波比均值滤波更适合HC-SR04。超声波测距的错误值经常是突发抖动比如某次回波丢失导致距离跳变到满量程均值滤波会把错误值带偏好几帧中值滤波直接把它排在序列外#define FILTER_N 5 uint16_t median5(uint16_t *arr) { // 只排序5个元素不需要优化 for (uint8_t i 0; i FILTER_N - 1; i) { for (uint8_t j 0; j FILTER_N - 1 - i; j) { if (arr[j] arr[j 1]) { uint16_t t arr[j]; arr[j] arr[j 1]; arr[j 1] t; } } } return arr[FILTER_N / 2]; }采样流程是连续测5次每次间隔100ms排序后取中位数。这样响应时间增加400ms对倒车雷达的慢速接近完全够用但对运动中的物体不合适。想要响应快就改3点中值或者用滑动窗口新样本插入有序数组而不是重新采5次。5. 倒车雷达实测与排错OLED不亮、Echo悬空、ST-LINK连不上的定位方法5.1 OLED只亮背光不显示内容先查地址再查初始化序列OLED模块上电后屏幕全亮或半亮但字不显示问题几乎出在两个地方。一是I2C地址不对0x3C和0x3D的差别在模块背面有标注HAL里确认用0x3C 1。二是初始化序列顺序错了SSD1306命令里0xAF开显示必须放在最后提前开显示会导致后续命令刷成花屏。排查时先用逻辑分析仪看SDA线上有没有ACK没有ACK别改驱动先查供电和地址。烧录阶段如果遇到“No STM32 target found! If your product embeds Debug Authentication”多半是SWD引脚被代码复用成普通GPIO按住复位键再点下载或者直接用STM32 ST-LINK Utility整片擦除再回来。5.2 Echo引脚悬空和Trig宽度导致的距离异常HC-SR04如果只接VCC和GNDTrig悬空时模块不工作但Echo脚在某些模块上有弱上拉表现出来是距离值永远不变。不要默认引脚悬空时是低电平万用表量一下不上电时Echo对地接近0V上电后静止状态也是0V。如果Echo空闲时是高电平说明模块内部有缺陷或者输出端被上拉串接1k电阻再测。另一个常见问题是Trig高电平不够宽HC-SR04对10us的要求比较宽松但delay_us近似循环在编译优化后可能缩水到5us模块间歇性不触发。这时把delay_us里us * 18改成us * 24或者换成DWT精确延时。5.3 把距离值打印到上位机一处printf改动OLED显示是最终输出调试时先信串口。STM32串口接USB转TTL波特率115200重定向fputc之后每个测距周期输出一行“dist: 123.4 cm”int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 10); return ch; } printf(dist: %.1f cm\r\n, dist);OLED显示的数值和串口数值对不上时以串口为准OLED的整形截断只影响小数部分。串口打印还能看出OLED刷新是否拖慢测距循环如果打印间隔在刷新时明显拉长就说明OLED_ShowString占了太多时间回到4.1的局部刷新方案处理。5.4 更进一步的技巧Echo改接到定时器输入捕获通道GPIO轮询在功能上能跑但Echo高电平期间CPU被完全占住。把Echo从PA1挪到TIM2_CH1对应的引脚配置上升沿和下降沿都捕获两次捕获差值就是脉宽。CubeMX里给TIM2开Global interrupt回调里做记数切换void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { static uint32_t start_us; if (capture_count 0) { start_us HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); capture_count 1; } else { pulse_us HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) - start_us; capture_count 0; } } }注意TIM2在STM32F103里是32位定时器分频到1us后32位计数器溢出要4294秒完全不用担心脉宽量程。切换成输入捕获后delay_us近似的误差也消失了距离值跳动会明显减少。改动量集中在CubeMX的定时器配置和这个回调函数里OLED显示代码一行都不用动。本文还有配套的精品资源点击获取

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

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

免费获取报价