STM32F103超声波测距+OLED显示完整工程包 简介本资源是一套基于STM32F103的超声波测距与CRT显示完整嵌入式项目工程面向嵌入式初学者及STM32进阶开发者聚焦距离感知这一典型物联网感知层功能实现。项目涵盖超声波传感器如HC-SR04驱动、高精度定时器TIM回波时间捕获、距离换算算法以及通过SPI/I2C接口驱动CRT模拟显示的核心实践覆盖GPIO配置、外设时钟使能、中断处理与波形时序控制等关键知识点。压缩包共141个文件以35个.h头文件和34个.c源码为主辅以.o目标文件、.d依赖文件及Keil工程相关配置uvprojx/uvoptx/sct等完整保留了从底层寄存器操作到应用逻辑的全链路代码结构包体大小为3.5MB轻量易导入学习。目前已有210人下载学习读者可直接复现测距—计算—CRT电压映射—扫描显示全流程获得可运行的Keil工程、清晰的模块化代码组织及典型外设协同调试经验。1. STM32F103超声波测距并显示一个能直接烧录、带OLED实时刷新的完整工程包新手照着接线改引脚就能跑通你手头有一块最常见的蓝 pillSTM32F103C8T6开发板还有一块HC-SR04超声波模块和一块0.96寸SPI OLEDSSD1306但翻遍论坛只看到零散的HAL库片段、寄存器配置截图、或者缺主循环的“伪代码”——这种“能编译但不显示”“有回波但距离跳变”“OLED闪一下就黑屏”的玄学状态我踩过整整三周。这个STM32F103超声波测距并显示.zip不是教学Demo而是一个从CubeMX生成、Keil MDK v5.38实测通过、包含完整时序控制、温度补偿预留位、OLED动态刷新防撕裂机制的可交付工程。它解决的不是“能不能测”而是“测得稳、看得清、改得快”——适合刚焊完第一块板子的电子系学生、需要快速验证传感器接口的嵌入式初学者以及被客户临时加需求、要求“明天就要看到实物效果”的FAE工程师。压缩包里没有PDF讲义只有5个真实文件夹Core含main.c、DriversHALOLED驱动、Inc头文件、Src超声波时序核心逻辑、User用户可修改的参数宏。所有延时用SysTick硬定时不依赖HAL_Delay避免与超声波Echo高电平计时冲突——这是血泪经验换来的设计锚点。2. 工程结构与关键模块解析为什么必须用TIM2做输入捕获而不是GPIO中断这个工程不是把HC-SR04当玩具玩而是按工业级测距逻辑构建的触发脉冲宽度严格控制在10μs±0.5μsEcho高电平持续时间测量精度要求±1μs距离计算需避开温度漂移导致的声速误差。这就决定了硬件资源分配不能随意。2.1 超声波时序控制TIM2_CH1作为输入捕获通道的不可替代性HC-SR04的Echo信号是单脉冲宽度与距离成正比1cm ≈ 58μs但其上升沿抖动可达±2μs下降沿更严重。若用GPIO外部中断EXTI捕获边沿中断响应延迟Cortex-M3典型值为12周期即12×(1/72MHz)≈167ns叠加中断服务函数进栈开销会导致10cm以内距离测量误差超过±3cm。本工程强制使用TIM2的CH1通道配置为输入捕获模式IC1利用硬件自动锁存计数值将时间测量误差压到±0.5μs内。关键配置如下// Src/ultrasonic.c 中 TIM2 初始化片段 TIM_IC_InitTypeDef sConfigIC {0}; htim2.Instance TIM2; htim2.Init.Prescaler 71; // 72MHz / (711) 1MHz即1μs计数单位 htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 0xFFFF; // 自动重装载值足够捕获最长Echo约23ms HAL_TIM_IC_Init(htim2); sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler TIM_ICPSC_DIV1; // 不分频保证上升沿立即捕获 sConfigIC.ICFilter 0x0; // 滤波器关闭因Echo信号干净开滤波反而引入延迟 HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_1);提示Prescaler71是硬性要求。若设为072MHz直接计数计数器溢出太快若设为719100kHz则1cm对应5.8个计数量化误差达±0.17cm无法满足基础精度。1MHz基准下1cm对应58个计数量化误差仅±0.017cm这才是工程可用的起点。2.2 OLED显示驱动SSD1306的SPI四线制与DMA双缓冲防撕裂OLED刷新若在主循环中逐字节发送一次全屏刷新128×64点阵1024字节耗时约12msSPI 2MHz速率期间超声波触发会丢失。本工程采用DMA双缓冲机制前一帧数据由DMA静默传输当前帧数据在内存中预渲染HAL_SPI_Transmit_DMA()返回后立即切换缓冲区指针。关键在于SSD1306的SPI时序必须严格满足CS低电平期间SCLK必须稳定且每字节传输后需插入最小保持时间tSPW≥50ns。CubeMX生成的SPI初始化已禁用CRC校验、关闭NSS管理并将FirstBit设为MSB避免字节顺序错乱。2.3 温度补偿预留为何在main.c中定义float sound_speed 331.4 0.6 * temperature却未接入DS18B20摘要虽未提温度传感器但工程在Inc/ultrasonic.h中已预留#define USE_TEMPERATURE_COMPENSATION 1宏开关并在距离计算函数中嵌入声速公式。实际项目中若需接入DS18B20只需在Src/main.c的while(1)循环前添加read_ds18b20(temperature)调用并取消该宏注释——无需改动任何底层驱动。这种“功能就绪、接口留空”的设计让后续升级成本趋近于零。3. 接线与引脚映射从原理图到实物接线的三步确认法别急着烧录90%的“编译成功但无显示”问题源于引脚物理连接错误。本工程默认接线基于标准STM32F103C8T6核心板非ST-Link版所有引脚在Core/main.c顶部有明确宏定义必须与你的硬件一一核对。3.1 超声波模块接线TRIG与ECHO必须分属不同IO口HC-SR04的TRIG引脚需输出精确10μs高电平ECHO引脚需被TIM2_CH1捕获。工程默认配置为TRIG → PA0通用IO推挽输出ECHO → PA1复用为TIM2_CH1注意PA0和PA1在STM32F103C8T6上确为TIM2_CH1/CH2通道但部分山寨板将PA1焊接为ADC输入需用万用表蜂鸣档实测PA1与芯片PA1引脚是否导通。若不通必须在Core/main.c中修改#define TRIG_GPIO_PORT GPIOA #define TRIG_GPIO_PIN GPIO_PIN_0 #define ECHO_GPIO_PORT GPIOA // 此处改为 GPIOB #define ECHO_GPIO_PIN GPIO_PIN_3 // PB3对应TIM2_CH2需同步修改TIM2初始化通道3.2 OLED模块接线SPI四线制与供电容错设计本工程适配常见0.96寸SPI OLEDSSD1306接线要求严格OLED引脚STM32引脚说明VCC3.3V严禁接5VSSD1306逻辑电平为3.3V5V直连必烧屏GNDGND共地必须可靠建议用粗导线短接D0(SCLK)PA5SPI1_SCKCubeMX已配置为复用推挽D1(MOSI)PA7SPI1_MOSI同上RESPA8复位引脚低电平有效软件可控DCPA9数据/命令选择高电平为数据低电平为命令CSPA10片选低电平有效提示RES和DC引脚若接错现象是屏幕常亮白或全黑但无字符。用示波器测PA8在OLED_Init()执行时是否有10ms低电平脉冲可快速定位复位失败。3.3 电源与调试接口SWD下载口与串口打印的共存策略工程默认关闭USART1重定向#define DEBUG_USART1 0但保留PB6/PB7作为SWD下载口。若你的板子SWD接口被占用如接了其他调试器需在Core/stm32f1xx_hal_conf.h中启用HAL_UART_MODULE_ENABLED并在main.c中初始化USART1PA9/PA10通过串口打印距离值用于验证。此时务必断开OLED的PA9DC和PA10CS避免IO冲突——这是新手最易忽略的硬件互斥点。4. 编译与烧录全流程Keil MDK v5.38下的六个关键检查点这个工程在Keil MDK v5.38uVision5下完成全部测试低于v5.26可能因HAL库版本不兼容报错。烧录前必须完成以下六项检查缺一不可。4.1 启动文件与设备型号匹配打开Startup/startup_stm32f103xb.s确认第12行__main入口地址与你的芯片Flash大小一致。STM32F103C8T6为64KB Flash对应startup_stm32f103xb.s若误用startup_stm32f103xe.s128KB版链接时会报region FLASH overflowed。右键工程→Options for Target→Device选项卡确认选择的是STM32F103C8而非STM32F103CB后者为128KB。4.2 HAL库路径与头文件包含工程使用HAL 1.8.4版Drivers/STM32F1xx_HAL_Driver其Inc/目录下必须存在stm32f1xx_hal.h。在Options for Target→C/C→Include Paths中确保已添加.\Drivers\STM32F1xx_HAL_Driver\Inc .\Drivers\STM32F1xx_HAL_Driver\Inc\Legacy .\Core\Inc .\Inc若漏掉Legacy路径编译会报HAL_GPIO_WritePin未定义——因为新旧HAL库函数声明位置不同。4.3 时钟树配置SYSCLK必须为72MHz且HSE已使能CubeMX生成的Core/Src/system_stm32f1xx.c中SystemCoreClock必须为72000000。打开该文件搜索RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE确认HSE外部晶振已使能。若你的板子无8MHz晶振如某些USB转串口小板需手动修改为HSE_BYPASS模式并外接8MHz方波信号源否则SysTick无法启动HAL_Delay失效整个系统卡死在HAL_Init()。4.4 程序入口与分散加载分散加载文件必须指向正确ROM区域Core/STM32F103C8Tx_FLASH.ldGNU ARM或Core/STM32F103C8Tx_FLASH.sctARMCC定义了代码段位置。对于C8T6Flash起始地址为0x08000000长度为0x0001000064KB。若误用C6T6的.sct文件长度0x00008000链接时会提示region FLASH overflowed by 8192 bytes。4.5 SWD下载设置Reset and Run必须勾选在Options for Target→Debug→Settings→Flash Download中点击Add添加STM32F1xx_Flash算法然后进入Utilities选项卡勾选Reset and Run。若未勾选程序烧录后不会自动运行需手动按板载复位键——此时OLED无反应易被误判为程序故障。4.6 实时调试验证SWO Trace输出距离值工程已启用SWOSerial Wire Output通过SWD接口输出距离值无需额外串口线。在Options for Target→Debug→Settings→Trace中勾选Enable Trace设置Core Clock为72MHzSWO Clock为72MHz。烧录后点击Debug→SWO Viewer即可实时看到Distance: 23.5 cm格式输出。这是验证超声波模块是否真正工作的最快方式比盯着OLED更可靠。5. 避坑指南五个高频翻车现场与秒级修复方案这五个问题我在三个不同批次的开发板上反复验证过每个都附带现象、根因和一行代码级修复方案拒绝模糊描述。5.1 现象OLED全屏白/黑但SWO输出距离值正常原因OLED的RES复位引脚未被拉低再拉高SSD1306芯片未退出硬件复位态。常见于RES线虚焊或PA8引脚配置错误。解决在Drivers/OLED/oled.c的OLED_Init()函数开头强制插入硬件复位序列HAL_GPIO_WritePin(OLED_RES_GPIO_PORT, OLED_RES_GPIO_PIN, GPIO_PIN_RESET); // 拉低 HAL_Delay(10); // 保持10ms HAL_GPIO_WritePin(OLED_RES_GPIO_PORT, OLED_RES_GPIO_PIN, GPIO_PIN_SET); // 拉高 HAL_Delay(10);注意HAL_Delay(10)依赖SysTick若SysTick未初始化此处会死循环。请先确认HAL_InitTick(TICK_INT_PRIORITY)已执行。5.2 现象距离值在0~5cm间疯狂跳变无规律原因TRIG引脚输出的10μs脉冲宽度不足。CubeMX生成的GPIO初始化中PA0被设为GPIO_MODE_OUTPUT_PP但未指定GPIO_SPEED_FREQ_HIGH导致上升沿缓慢在72MHz下实际脉宽仅约3μsHC-SR04无法识别。解决在Core/Src/gpio.c的MX_GPIO_Init()函数中找到PA0初始化段将GPIO_SPEED_FREQ_LOW改为GPIO_SPEED_FREQ_HIGHGPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; // 原为 GPIO_SPEED_FREQ_LOW5.3 现象SWO输出Distance: 0.0 cm且始终不变原因ECHO信号未接入PA1或PA1被其他外设如JTAG的SWDIO复用。用万用表测PA1对地电压正常应为浮空约1.8V若为0V或3.3V说明被强拉。解决在Core/Src/sys.c的SystemInit()后添加JTAG释放代码释放PA14/PA15__HAL_AFIO_REMAP_SWJ_NOJTAG(); // 关闭JTAG仅保留SWD此操作将PA14/PA15恢复为普通GPIO避免与TIM2_CH1冲突。5.4 现象OLED显示字符但距离值固定为999.9原因TIM2输入捕获未触发HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1)执行后无中断。根本原因是NVIC中断优先级配置错误HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0)中抢占优先级设为0高于SysTick默认也为0导致SysTick被阻塞。解决在Core/Src/stm32f1xx_it.c的HAL_TIM_MspPostInit()中将TIM2中断优先级降为1HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0); // 抢占优先级1子优先级05.5 现象距离值随环境光变化白天偏大夜晚偏小原因HC-SR04的接收头对红外敏感强光照射时内部比较器阈值漂移导致Echo下降沿误判。非电路问题但工程必须应对。解决在Src/ultrasonic.c的Ultrasonic_Get_Distance()函数中增加光敏电阻AD采样判断若硬件已预留PC0当光照强度阈值时自动将距离值乘以0.97校准系数if (HAL_ADC_GetValue(hadc1) 2000) { // PC0接光敏电阻分压 distance * 0.97f; }提示此功能需在CubeMX中启用ADC1并配置PC0为模拟输入否则跳过该校准。6. 进阶技巧用OLED的滚动显示实现多目标距离追踪与历史曲线当基础测距跑通后真正的工程价值在于扩展性。本工程预留了OLED的滚动显示接口可将单次测距升级为连续追踪无需更换硬件。6.1 多目标距离缓存环形缓冲区设计在Inc/ultrasonic.h中定义16点环形缓冲区#define DISTANCE_BUFFER_SIZE 16 extern float distance_buffer[DISTANCE_BUFFER_SIZE]; extern uint8_t buffer_head, buffer_tail;在Src/ultrasonic.c的Ultrasonic_Get_Distance()末尾添加入队逻辑distance_buffer[buffer_head] distance; buffer_head (buffer_head 1) % DISTANCE_BUFFER_SIZE; if (buffer_head buffer_tail) { buffer_tail (buffer_tail 1) % DISTANCE_BUFFER_SIZE; // 满则覆盖最老数据 }此设计内存占用仅64字节支持16次历史距离存储为后续趋势分析打下基础。6.2 OLED滚动显示动态刷新避免闪烁OLED全屏刷新会闪烁本工程采用局部刷新策略。定义OLED_ShowDistanceHistory()函数每次只更新最后一行Y56的16个数字void OLED_ShowDistanceHistory(void) { char buf[8]; for (uint8_t i 0; i DISTANCE_BUFFER_SIZE; i) { uint8_t idx (buffer_tail i) % DISTANCE_BUFFER_SIZE; sprintf(buf, %.1f, distance_buffer[idx]); OLED_ShowString(0 i*8, 56, buf, 12); // X坐标递增每字符宽8像素 } }在main.c的while(1)中每200ms调用一次该函数配合OLED_Refresh_Gram()实现平滑滚动。6.3 历史曲线绘制用ASCII字符模拟折线图更进一步将16点距离值映射为OLED的垂直柱状图。定义最大距离为300cm则每1cm对应0.2像素16点×0.23.2像素足够分辨距离范围显示字符说明0~50cm█满格高度8像素50~100cm▓3/4格高度6像素100~200cm▒1/2格高度4像素200cm░1/4格高度2像素在OLED_ShowDistanceHistory()中追加for (uint8_t i 0; i DISTANCE_BUFFER_SIZE; i) { uint8_t idx (buffer_tail i) % DISTANCE_BUFFER_SIZE; uint8_t height (uint8_t)(distance_buffer[idx] / 50.0f); // 每50cm一级 if (height 3) height 3; OLED_ShowChar(0 i*8, 48, ░▒▓█[height], 12); }最终效果OLED上半部显示实时距离如23.5 cm下半部滚动显示16个历史值ASCII柱状图一眼看出距离变化趋势。从那以后我每次交付传感器Demo都强制走一遍“SWO验证→OLED静态显示→滚动历史→曲线图”四步验证流程哪怕客户只要求看一个数字。因为真正的稳定性藏在那些你没看见的16个历史点里。希望帮到你。本文还有配套的精品资源点击获取