news 2026/9/2 5:36:52

基于STM32的超声波雷达扫描系统:硬件设计、驱动开发与图形显示实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于STM32的超声波雷达扫描系统:硬件设计、驱动开发与图形显示实战

简介:这是一份面向嵌入式初学者与课程设计者的STM32雷达模拟系统实战项目资源,基于STM32F103C8T6主控,融合舵机控制、超声波测距与TFT-LCD图形显示三大核心技能,完整复现简易机械扫描雷达的工作原理。资源包共1024个文件,涵盖563个C源码(含驱动与算法实现)、250个头文件(定义外设配置与LCD绘图接口)、51个汇编启动文件及Keil/IAR工程配置文件(.uvprojx、.ioc、.icf等),并包含关键的PDF原理图与雷达图像算法模块(如线性插值、DCT初始化等数学库支持)。目前已有270人学习下载,适合用于单片机综合实训、电子设计竞赛备赛或毕业设计参考。读者可直接部署运行,在2.4寸SPI接口TFT屏上实时绘制极坐标雷达图,同步显示物体角度与距离,并深入理解定时器PWM输出、输入捕获测距、LCD点阵绘图及坐标映射等关键技术实现细节。

1. 项目概述:一个嵌入式雷达扫描系统的诞生

最近在整理工作室的旧项目,翻出来一个几年前用STM32F103C8T6做的超声波雷达扫描仪,感觉挺有意思的。这个项目的核心目标很简单:让一个SG90舵机带着HC-SR04超声波模块旋转,像雷达一样扫描前方区域,然后把探测到的物体位置(角度和距离)实时显示在一块2.4寸的TFT-LCD屏幕上,画出一个动态的“雷达图”。听起来是不是有点像简易版的自动驾驶感知demo或者扫地机器人的避障预览?其实它的应用场景可以很广,比如作为小型机器人的环境感知前端、智能小车的避障演示教具,或者就是一个很酷的桌面交互装置。

为什么选STM32F103C8T6这块经典的“蓝桥杯”神片?原因无他,就是性价比高、资料多、生态成熟。对于这种需要同时处理舵机PWM、超声波测距时序、LCD刷屏以及坐标计算的中等复杂度项目,C8T6的72MHz主频、64KB Flash和20KB RAM完全够用,而且引脚数量也刚好满足我们的需求。整个开发流程基于STM32CubeMX进行图形化配置,配合HAL库,能极大降低底层驱动的开发难度,让我们把精力集中在应用逻辑上。SG90舵机负责提供0-180度的扫描角度,HC-SR04超声波模块负责测量距离,最后通过SPI或FSMC接口驱动那块320x240分辨率的TFT-LCD,将极坐标(角度、距离)转换为屏幕上的直角坐标点,一幅动态的雷达扫描图就跃然屏上了。无论你是想学习STM32多外设协同、实时系统概念,还是想做一个有趣的实物,这个项目都是一个不错的起点。

2. 硬件选型与核心电路设计解析

2.1 主控芯片:STM32F103C8T6的引脚分配策略

STM32F103C8T6属于STM32F1系列的“中等容量”产品,48个引脚,资源对于本项目来说需要精打细算。使用CubeMX配置时,清晰的引脚规划是成功的第一步。

首先,SG90舵机需要一路PWM信号。舵机的控制信号是一个周期为20ms(50Hz),脉宽在0.5ms到2.5ms之间的PWM波,对应0到180度的位置。我们可以使用定时器(TIM)的PWM输出通道来生成。这里我选择了TIM2的通道1(PA0),因为PA0是5V容忍的引脚,虽然SG90信号线一般是3.3V兼容,但这样更稳妥。在CubeMX中,需要将TIM2的通道1配置为PWM Generation CH1模式,预分频器和自动重载值(ARR)的设置需要计算,以达到20ms的周期。假设系统时钟是72MHz,经过适当分频,让计数器频率为1MHz,那么ARR设置为20000-1,即可得到20ms的周期。脉宽则由捕获比较寄存器(CCR)的值控制,500对应0.5ms(0度),2500对应2.5ms(180度)。

其次,HC-SR04超声波模块需要两个GPIO,一个用于触发(Trig),一个用于回波(Echo)。Trig引脚输出一个至少10us的高电平脉冲,我选择了PA1,配置为推挽输出。Echo引脚用于检测高电平的持续时间,这个时间与距离成正比。这里有个关键点:HC-SR04的Echo脚输出是5V电平,而STM32的IO口通常只耐受3.3V。绝对不能直接连接!常见的解决方案是使用一个简单的电阻分压电路(例如1kΩ和2kΩ串联),将5V分压到约3.3V后再接入STM32的引脚。我选择了PA2,并将其配置为输入模式,同时开启上升沿/下降沿中断,用于精确测量高电平脉宽。

最后是2.4寸TFT-LCD屏幕。这类屏幕通常使用SPI接口或8位/16位并行接口。为了获得更快的刷屏速度(这对动态雷达图很重要),我强烈推荐使用并口。STM32F103C8T6没有专用的FSMC(Flexible Static Memory Controller)控制器,但我们可以用GPIO模拟8位并口,或者使用它的“FSMC”功能(实际上C8T6没有,但一些兼容屏的驱动芯片支持8080时序,我们可以用GPIO模拟8080时序)。为了简化,我选择了SPI接口,虽然速度稍慢,但对于320*240分辨率、仅更新变化点的雷达图来说,优化后完全可以接受。我使用了SPI1(PA5-SCK, PA6-MISO, PA7-MOSI),以及一个额外的GPIO(PA4)作为片选CS,另一个(PB0)作为数据/命令选择线DC,还有一个(PB1)作为复位线RST。

注意:电源是关键!SG90舵机在转动时电流可能达到数百mA,绝对不能直接用STM32开发板上的3.3V或5V引脚供电,否则极易导致芯片复位或损坏。必须为舵机提供独立的外接电源(例如5V/2A的适配器),并将此电源地与STM32的地(GND)连接在一起,确保共地。

2.2 外设模块特性与接口要点

SG90舵机:这是一个180度模拟舵机。其内部有一个基准电路、一个比较器、一个电机驱动和一个位置反馈电位器。PWM信号的本质是提供一个基准电压,舵机内部通过比较来决定转动方向和幅度。除了PWM控制线(橙色/黄色),还有电源线(红色,5V)和地线(棕色)。控制的关键是精度和稳定性。定时器产生的PWM波需要非常稳定,周期抖动会导致舵机抖动。在软件上,我们通常以1度为最小单位改变CCR值,对应脉宽变化约11us。扫描时,可以让舵机以固定步长(如1度或2度)从0度移动到180度,再返回,形成一个周期性的扫描运动。

HC-SR04超声波模块:它通过Trig引脚触发一次测距,然后从Echo引脚输出一个与距离成正比的高电平脉冲。距离计算公式为:距离 = (高电平时间 * 声速) / 2。声速在常温下可取340m/s或343m/s。高电平时间单位是微秒(us),除以58即可得到厘米数,除以58000得到米数。这个“58”的由来就是 (34000 cm/s) / (2 * 1000000 us/s) ≈ 0.017 cm/us,取倒数约为58.8。测量时,需要注意模块的盲区(通常2-3cm),以及声波的散射角。对于雷达扫描,我们假设探测到的物体是一个点,但实际上超声波波束是有宽度的,这会影响角度分辨率。

2.4寸 TFT-LCD:我使用的是一款常见的驱动芯片为ILI9341的屏幕。它支持SPI和8位并行模式。SPI模式下,需要向屏幕写入命令和数据来初始化、设置窗口、写入像素颜色。颜色格式通常是RGB565(16位色),即一个像素点用两个字节表示(5位红,6位绿,5位蓝)。绘制雷达图时,我们需要频繁地进行画点操作。为了提高效率,可以先将整个屏幕清空为背景色(如黑色),然后在每个扫描角度,根据测得的距离,在对应的极坐标位置画一个亮色的点(如绿色)。为了有“扫描线”效果,可以在当前角度画线,并逐渐擦除之前角度的线。

3. CubeMX工程配置与HAL库驱动搭建

3.1 系统时钟与关键外设的CubeMX图形化配置

打开STM32CubeMX,选择STM32F103C8T6芯片。首先配置时钟树(Clock Configuration)。我们的目标是将系统时钟(SYSCLK)设置为72MHz,这是F103系列在外部8MHz晶振(HSE)下的标准最高速度。在图形界面中,选择HSE作为时钟源,经过PLL倍频,将PLLCLK设置为72MHz,然后作为SYSCLK。APB1总线时钟(PCLK1)最高36MHz,APB2总线时钟(PCLK2)最高72MHz。定时器挂在不同的APB总线上,时钟可能会被倍频,CubeMX会帮你计算好,我们只需确保最终定时器的计数时钟是我们期望的频率(如1MHz用于舵机PWM)。

接下来配置引脚(Pinout & Configuration):

  1. TIM2 CH1 (PWM输出):在左侧“Timers”中找到TIM2,选择“Clock Source”为“Internal Clock”,然后在“Channel1”中选择“PWM Generation CH1”。此时PA0会被自动配置。转到“Parameter Settings”选项卡,设置预分频器(Prescaler)和计数器周期(Counter Period)。为了得到1MHz的计数频率,如果APB1的TIM2时钟是72MHz,则预分频器应设为71(72MHz/(71+1)=1MHz)。计数器周期(ARR)设为19999(这样PWM周期就是20000个时钟周期,即20ms)。脉冲宽度(Pulse)先设为1500(对应1.5ms,90度中间位置)。注意将“PWM Generation CH1”的“Mode”设置为“PWM mode 1”,极性(Polarity)根据舵机要求设置,通常为高电平有效。
  2. GPIO for HC-SR04:找到PA1,设置为“GPIO_Output”,命名为“Trig”。找到PA2,设置为“GPIO_Input”,并开启外部中断。在“NVIC Settings”中,使能EXTI line2中断。
  3. SPI for LCD:在“Connectivity”中选择SPI1。模式设置为“Full-Duplex Master”,硬件NSS选择“Disable”。参数设置中,波特率可以设高一些,比如18Mbits/s(需要看屏幕芯片支持情况)。数据大小8位,时钟极性低,时钟相位第1边沿。引脚会自动分配为PA5(SCK), PA6(MISO), PA7(MOSI)。然后手动配置PA4为“GPIO_Output”作为CS,PB0作为DC,PB1作为RST。
  4. SYS:将“Debug”改为“Serial Wire”,这样可以使用ST-LINK进行调试和下载。
  5. Project Manager:设置好项目名称、路径、IDE(MDK-ARM或STM32CubeIDE),在“Code Generator”中,选择“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”,这样代码结构更清晰。

生成代码后,一个包含所有初始化代码的工程就创建好了。

3.2 HAL库超声波测距与舵机控制代码实现

在生成的工程中,我们主要编写应用层代码。

舵机角度控制函数

// 设置舵机角度,angle范围0-180 void Servo_SetAngle(TIM_HandleTypeDef *htim, uint32_t Channel, uint8_t angle) { if(angle > 180) angle = 180; // 将角度转换为PWM脉宽(单位:us) // 0度->500us, 180度->2500us,线性映射 // CCR值 = 脉宽(us) * 定时器计数频率(MHz) // 假设定时器计数频率为1MHz,即1us计数1次 uint32_t pulse_width_us = 500 + angle * (2000 / 180); // 简化计算,避免浮点 // 更精确的公式:pulse = 500 + angle * 2000.0 / 180.0; __HAL_TIM_SET_COMPARE(htim, Channel, pulse_width_us); }

这个函数封装了角度到PWM占空比的转换。在主循环或定时器中断中,我们可以周期性地调用此函数,改变角度值,让舵机匀速转动。

超声波测距模块驱动: 超声波测距需要精确计时,我们利用Echo引脚的外部中断和定时器来实现。

// 全局变量,用于存储高电平开始和结束的时间戳 volatile uint32_t ultrasonic_rising_tick = 0; volatile uint32_t ultrasonic_falling_tick = 0; volatile uint32_t distance_cm = 0; volatile uint8_t ultrasonic_measure_done = 0; // Echo引脚外部中断回调函数(在stm32f1xx_it.c中声明,或在main.c中重写弱函数) void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == GPIO_PIN_2) { // PA2 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_SET) { // 上升沿,记录开始时间 ultrasonic_rising_tick = HAL_GetTick(); // 使用毫秒定时器,精度不够! // 为了更高精度,应该使用一个定时器的计数器值,例如TIM3的CNT ultrasonic_rising_tick = __HAL_TIM_GET_COUNTER(&htim3); } else { // 下降沿,记录结束时间,计算距离 ultrasonic_falling_tick = __HAL_TIM_GET_COUNTER(&htim3); uint32_t pulse_width_ticks = ultrasonic_falling_tick - ultrasonic_rising_tick; // 假设TIM3的计数频率为1MHz,1个tick=1us // 距离 = 脉宽(us) / 58.0 (cm) distance_cm = pulse_width_ticks / 58; ultrasonic_measure_done = 1; } } } // 触发一次测距的函数 void Ultrasonic_StartMeasure(void) { HAL_GPIO_WritePin(Trig_GPIO_Port, Trig_Pin, GPIO_PIN_SET); HAL_Delay_us(12); // 需要实现一个微秒级延时函数,可以用定时器或NOP循环 HAL_GPIO_WritePin(Trig_GPIO_Port, Trig_Pin, GPIO_PIN_RESET); }

这里的关键是高精度计时HAL_GetTick()是毫秒级,对于超声波测距(厘米级精度,对应几十微秒的脉宽)来说误差太大。因此,我使用了一个额外的定时器(如TIM3),将其时钟配置为1MHz,在中断中读取它的计数器值(CNT),精度可以达到1微秒。微秒延时函数HAL_Delay_us可以用一个简单的循环实现,或者用另一个定时器。

实操心得:中断的优化。在Echo中断回调函数中,不要做复杂计算或调用耗时的HAL函数。最好只记录时间戳,设置标志位,把距离计算放到主循环中。同时,注意中断嵌套和优先级,避免超声波中断被其他高优先级中断阻塞,导致计时错误。

4. 雷达扫描逻辑与坐标变换算法

4.1 扫描策略与数据采集的时序控制

一个完整的雷达扫描周期,是舵机从0度匀速运动到180度,同时在每个角度(或每隔几个角度)触发超声波测距并记录数据。这里涉及到多任务协调:舵机控制(PWM更新)、超声波触发与读取、LCD刷新。一个简单可靠的架构是利用定时器中断来构建一个时间片轮询系统。

我们可以设置一个基础定时器(如TIM4),每10ms产生一次中断。在这个中断服务函数中,维护一个状态机:

  1. 状态0 - 移动舵机:每进入中断若干次(比如2次,即20ms后),更新一次舵机目标角度(步进1度)。这样舵机大约每20ms移动1度,从0到180度需要3.6秒,速度比较平滑。
  2. 状态1 - 触发测距:在更新舵机角度后,立即触发一次超声波测距(调用Ultrasonic_StartMeasure)。
  3. 状态2 - 等待结果:等待ultrasonic_measure_done标志被置位。由于超声波测距本身需要几十毫秒(最大测距对应的时间),这个等待过程是非阻塞的,我们只是在下一次中断时检查标志位。
  4. 状态3 - 处理数据:当标志位为1时,读取distance_cm,结合当前的舵机角度,计算物体在屏幕上的坐标,并标记该点需要刷新。然后清除标志位,准备下一个角度的测量。

这样,舵机的运动、超声波的触发和数据的处理就被一个定时器中断有序地串联起来,避免了在主循环中使用HAL_Delay导致的阻塞,使得LCD刷新和其他逻辑(如按键扫描)可以同时在主循环中运行。

4.2 从极坐标到屏幕直角坐标的映射与绘图

超声波测得的距离(r)和舵机角度(θ)构成了一个极坐标系下的数据点。我们需要将其映射到320x240的LCD直角坐标系上。

通常,我们将雷达的扫描中心(舵机旋转轴)映射到屏幕底部中间,假设为点(X0, Y0)。例如,令X0 = 160(屏幕水平中心),Y0 = 220(靠近屏幕底部,留出一些空间显示信息)。那么,屏幕上点的坐标(x, y)可以通过以下公式计算:

x = X0 + r * sin(θ) * scale_factor; y = Y0 - r * cos(θ) * scale_factor; // 注意屏幕y轴向下为正,所以用减法

其中:

  • θ需要从角度转换为弧度:rad = θ * π / 180.0
  • scale_factor是一个缩放因子,因为超声波测距的单位是厘米,而屏幕像素有限。例如,如果最大显示距离为100cm,我们希望它映射到屏幕上方约100像素的范围,那么scale_factor可以设为 1.0(即1像素代表1厘米),或者根据布局调整。
  • sincos计算:在STM32上,如果直接使用标准库的sinfcosf函数,计算浮点数开销较大。对于这种实时性要求不极端高的应用,可以接受。如果追求效率,可以预先计算好0-180度每个角度的sin和cos值,存入数组,直接查表。

绘图策略

  • 背景:每次扫描开始前,可以用一种颜色(如黑色或深蓝色)清除整个屏幕,绘制固定的雷达刻度线和角度标识。
  • 扫描线:绘制一条从中心(X0, Y0)到当前角度对应最大半径处的线段,颜色为亮色(如青色),表示当前正在扫描的方向。
  • 历史轨迹点:将每个有效测距点(距离在合理范围内,如2cm-400cm)根据上述公式计算坐标,并在该坐标画一个点。为了有“余晖”效果,可以不用立即擦除旧点,而是让旧点随着时间慢慢变暗或消失。一个简单实现是:在绘制新点前,将整个“扫描区域”(一个扇形)用半透明的背景色重绘一次,这样旧点就会变淡,新点最亮。
  • 物体突出显示:如果某个点的距离突然很近,可能是障碍物,可以用更醒目的颜色(如红色)绘制一个稍大的点或圆圈。
// 示例:将一个极坐标点绘制到LCD上 void Radar_PlotPoint(float angle_deg, float distance_cm) { if(distance_cm < 2 || distance_cm > 200) return; // 过滤无效数据 float rad = angle_deg * 3.1415926f / 180.0f; float scale = 1.0f; // 1 pixel per cm int16_t x0 = 160, y0 = 220; int16_t x = x0 + (int16_t)(distance_cm * sinf(rad) * scale); int16_t y = y0 - (int16_t)(distance_cm * cosf(rad) * scale); // 注意是减法 // 确保坐标在屏幕范围内 if(x >= 0 && x < 320 && y >= 0 && y < 240) { ILI9341_DrawPixel(x, y, RGB565_GREEN); // 假设这是你的画点函数 } }

5. TFT-LCD显示驱动与图形界面优化

5.1 ILI9341 SPI驱动实现与优化

对于SPI接口的ILI9341,我们需要实现基本的写命令、写数据函数,以及初始化序列。

// 定义控制引脚 #define LCD_CS_LOW() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET) #define LCD_CS_HIGH() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET) #define LCD_DC_CMD() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET) #define LCD_DC_DATA() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET) #define LCD_RST_LOW() HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET) #define LCD_RST_HIGH() HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET) // SPI发送一个字节(封装HAL_SPI_Transmit) static void SPI_SendByte(uint8_t data) { HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY); } // 写命令 void LCD_WriteCommand(uint8_t cmd) { LCD_DC_CMD(); LCD_CS_LOW(); SPI_SendByte(cmd); LCD_CS_HIGH(); } // 写数据 void LCD_WriteData(uint8_t data) { LCD_DC_DATA(); LCD_CS_LOW(); SPI_SendByte(data); LCD_CS_HIGH(); } // 初始化函数需要按照ILI9341数据手册的序列,发送一系列命令和参数 void ILI9341_Init(void) { // 硬件复位 LCD_RST_LOW(); HAL_Delay(100); LCD_RST_HIGH(); HAL_Delay(100); // 发送初始化命令序列,例如设置像素格式、内存访问控制、睡眠模式退出等 LCD_WriteCommand(0xCF); // 示例命令 LCD_WriteData(0x00); LCD_WriteData(0xC1); LCD_WriteData(0X30); // ... 更多初始化命令 LCD_WriteCommand(0x11); // 退出睡眠 HAL_Delay(120); LCD_WriteCommand(0x29); // 开启显示 }

关键优化:批量像素写入。单点绘制函数ILI9341_DrawPixel效率很低,因为它每次都要设置一次坐标窗口(发送4个命令),再发送2个字节的颜色数据。绘制雷达图时,我们可能需要连续绘制多个点(如扫描线)。优化方法是实现一个设置绘图窗口的函数,然后连续发送颜色数据。

// 设置绘图窗口(x1,y1)到(x2,y2)的矩形区域 void ILI9341_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_WriteCommand(0x2A); // 列地址设置 LCD_WriteData(x1 >> 8); LCD_WriteData(x1 & 0xFF); LCD_WriteData(x2 >> 8); LCD_WriteData(x2 & 0xFF); LCD_WriteCommand(0x2B); // 行地址设置 LCD_WriteData(y1 >> 8); LCD_WriteData(y1 & 0xFF); LCD_WriteData(y2 >> 8); LCD_WriteData(y2 & 0xFF); LCD_WriteCommand(0x2C); // 内存写入命令 } // 然后可以连续调用LCD_WriteData发送颜色数据,屏幕会自动递增地址。

对于雷达扫描线,我们可以用ILI9341_SetWindow设置一个1像素宽的矩形(从中心到目标点),然后连续发送一种颜色的数据,效率远高于逐点绘制。

5.2 动态雷达图界面设计与绘制技巧

一个直观的雷达界面应该包含以下元素:

  1. 静态背景:绘制同心圆(或圆弧)作为距离刻度线(如每50cm一个圈),绘制从中心发出的射线作为角度刻度线(如每30度一条)。这些在初始化时绘制一次即可。
  2. 动态扫描线:一条从中心指向当前扫描角度的线段。需要在每次角度更新时重绘。为了不闪烁,可以采用“先擦后画”的方式:记住上一次扫描线的角度,用背景色重绘那条线,然后用前景色绘制新的扫描线。
  3. 动态轨迹点:物体探测点。如前所述,可以采用“余晖”效果。一个高效的实现是:维护一个二维数组(对应屏幕的极坐标网格),存储每个“格子”的“亮度”或“存在时间”。每次扫描到新点,对应格子的值刷新为最大值。在每次屏幕刷新时,遍历所有格子,根据其值绘制不同亮度或颜色的点,并将值衰减。这样就能实现点迹慢慢消失的效果,而无需重绘整个背景。
  4. 信息显示区:在屏幕角落,用文字显示当前角度、最近障碍物距离、系统状态等。需要实现一个字符显示函数,可以基于位模库(如ASCII字库)。

避坑指南:SPI速度与屏幕刷新率。SPI的时钟速度(在CubeMX中设置)会影响刷屏速度。如果设置过高,可能导致屏幕驱动芯片无法正确识别数据。ILI9341的SPI时钟最高通常支持几十MHz,但受限于STM32的GPIO速度和布线,可能需要适当降低。如果发现屏幕显示花屏或不稳定,首先检查SPI时钟分频,尝试降低速度。另外,在连续发送大量数据时,确保片选(CS)信号在数据传输期间保持低电平,而不是每字节都翻转。

6. 系统集成、调试与性能优化实战

6.1 多任务协同与中断管理的平衡

本项目实质上是一个简单的实时系统:定时中断驱动舵机和超声波采样,主循环负责LCD刷新和界面更新。处理好它们之间的协同和资源共享是关键。

中断服务函数(ISR)要短小精悍。以10ms定时器中断为例:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM4) { static uint8_t servo_step_counter = 0; static uint8_t scan_angle = 0; static uint8_t scan_direction = 0; // 0: 0->180, 1: 180->0 // 每2次中断(20ms)更新一次舵机角度 if(++servo_step_counter >= 2) { servo_step_counter = 0; if(scan_direction == 0) { scan_angle++; if(scan_angle >= 180) { scan_angle = 180; scan_direction = 1; // 可以在这里触发一次全屏背景刷新 } } else { scan_angle--; if(scan_angle <= 0) { scan_angle = 0; scan_direction = 0; } } Servo_SetAngle(&htim2, TIM_CHANNEL_1, scan_angle); Ultrasonic_StartMeasure(); // 触发测距 } // 检查超声波测量是否完成 if(ultrasonic_measure_done) { // 将数据存入缓冲区,供主循环处理。避免在中断内进行复杂计算和LCD操作。 g_angle_buffer = scan_angle; g_distance_buffer = distance_cm; g_new_data_ready = 1; ultrasonic_measure_done = 0; } } }

主循环则不断检查g_new_data_ready标志,处理数据并更新显示。

共享变量的保护g_angle_bufferg_distance_buffer这类在中断和主循环间共享的变量,虽然在本例中一个字节写一个字节读可能不会出问题,但好的习惯是使用volatile关键字声明,或者关中断进行读写操作,以防编译器优化导致意外。

6.2 常见问题排查与系统稳定性提升

在实际焊接和调试中,你肯定会遇到各种问题。下面是一个快速排查清单:

现象可能原因排查步骤与解决方案
舵机不转或乱转1. 电源功率不足。
2. PWM信号频率或脉宽不对。
3. 信号线接触不良。
1. 用万用表测量舵机供电电压,转动时是否跌落到5V以下?务必使用独立电源。
2. 用逻辑分析仪或示波器测量PA0引脚波形,检查周期是否为20ms,脉宽是否随角度变化(0.5ms-2.5ms)。
3. 检查接线,确保信号线连接正确。
超声波一直返回0或超大值1. Echo引脚电平转换电路问题。
2. 计时不准确。
3. 模块硬件故障。
1. 用示波器测量Echo引脚波形,看是否有高电平脉冲。如果没有,检查Trig触发信号和模块供电。
2. 检查用于计时的定时器配置,确保计数频率准确(1MHz)。检查中断优先级,确保Echo中断能及时响应。
3. 更换一个HC-SR04模块测试。
LCD白屏或花屏1. 初始化序列错误。
2. SPI时序或速度问题。
3. 电源或复位信号问题。
1. 仔细核对ILI9341数据手册的初始化命令序列,特别是电源相关和像素格式命令。
2. 降低SPI波特率(如降到9Mbps)测试。用示波器看SCK、MOSI波形是否清晰。
3. 检查屏幕的VCC、GND、复位引脚电平。确保复位信号有正确的下拉-上拉过程。
雷达图刷新闪烁严重1. 刷新策略问题,全屏刷新太慢。
2. SPI速度太慢。
3. 主循环被其他任务阻塞。
1. 采用局部刷新和“余晖”算法,避免频繁清屏。只更新变化的区域(扫描线和新的点)。
2. 在屏幕芯片允许范围内,提高SPI时钟频率。优化SPI发送函数,使用DMA传输(如果SPI支持)。
3. 检查主循环中是否有耗时操作(如浮点计算、延时)。将sin/cos查表化,将浮点运算转换为定点整数运算。
物体位置显示不准确1. 舵机角度与PWM脉宽非线性。
2. 超声波模块有测量误差和盲区。
3. 坐标映射公式或参数有误。
1. 对舵机进行校准:记录0度和180度对应的准确CCR值,中间角度线性插值。
2. 对固定距离(如10cm, 50cm, 100cm)进行多次测量取平均,建立一个简单的误差补偿表。
3. 在屏幕上打印出原始的angledistance值,与实际物体位置对比,调试映射公式和缩放因子。

稳定性提升技巧

  • 电源去耦:在STM32的每个电源引脚附近放置一个0.1uF的陶瓷电容,靠近舵机电源输入端放置一个100uF以上的电解电容,可以有效抑制电机转动引起的电源噪声。
  • 软件滤波:对超声波测距值进行软件滤波,例如取连续3次测量的中值,或进行滑动平均,可以消除偶然的跳变。
  • 看门狗:启用STM32的独立看门狗(IWDG),在主循环中定期喂狗。当程序跑飞或陷入死循环时,系统会自动复位,提高抗干扰能力。

7. 项目扩展与进阶玩法思路

这个基础框架搭建好后,你可以尝试很多有趣的扩展:

  1. 增加交互:接入一个旋转编码器或按键,用于手动控制扫描启停、切换显示模式(如原始数据、图形)、调整扫描速度。
  2. 数据输出:通过串口(UART)将实时的角度-距离数据发送到电脑,用上位机软件(如Python的Matplotlib)绘制更精美的雷达图,甚至进行数据记录和分析。
  3. 多传感器融合:在舵机云台上再加一个红外测距或TOF传感器,与超声波数据互补。超声波适合远距离、大物体,红外/TOF精度更高但可能受环境影响。
  4. 引入RTOS:使用FreeRTOS,将舵机控制、超声波采样、LCD刷新、用户接口等任务模块化,分别放在不同的任务中,通过队列和信号量通信,使程序结构更清晰,更容易扩展复杂功能。
  5. 算法升级:实现简单的目标跟踪。连续几帧扫描中,如果相邻角度的点距离相近,可以将其聚类,认为是一个物体,并估算其大小和运动趋势。
  6. 结构改造:设计一个3D打印的外壳,将整个系统封装起来,做成一个真正的桌面雷达摆件,通过USB供电,更加美观实用。

这个项目麻雀虽小,五脏俱全,涵盖了嵌入式开发的硬件接口、定时器、中断、SPI通信、图形显示、坐标变换等多个核心知识点。调试过程中遇到的每一个问题,都是加深你对STM32和嵌入式系统理解的机会。希望这份详细的解析和实操指南能帮你少走弯路,顺利做出属于自己的超声波雷达。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 5:36:10

基于ResNeXt-101的植物识别系统:从模型训练到工程化部署全解析

简介&#xff1a;这是一套基于Python实现的高精度植物图像识别项目源码与模型&#xff0c;面向计算机视觉初学者、AI爱好者及植物学交叉领域研究者&#xff0c;解决细粒度植物物种&#xff08;含属、种、亚种、变种&#xff09;自动化识别问题。资源包共20个文件&#xff0c;涵…

作者头像 李华
网站建设 2026/9/2 5:36:03

开源启动器Tinycast:从原理到实践,打造你的专属效率工具

如果你是一个 macOS 用户&#xff0c;每天在 Finder、终端、浏览器和各种应用之间来回切换&#xff0c;寻找文件、启动应用、执行脚本&#xff0c;那么你大概率听说过或者正在使用Raycast。它几乎成了 macOS 效率工具的代名词&#xff1a;一个全局快捷键呼出的启动器&#xff0…

作者头像 李华
网站建设 2026/9/2 5:34:09

嵌入式-MCU调试记录—GPIO复用

1、调试现象这个问题是在调试一个产品的LED显示的功能&#xff0c;LED嘛无非就是配置好GPIO然后拉高或者拉低就行了&#xff0c;这个在嵌入式工作中也很常见想必各位大佬觉得&#xff0c;调个LED能有啥强度&#xff0c;&#xff0c;Emmmmm&#xff0c;一开始我也是这么想的&…

作者头像 李华
网站建设 2026/9/2 5:33:07

电商商品数据的清洗与格式转换:从采集结果到 Shopify CSV 的完整链路

采集到商品数据只是第一步。真正决定数据能不能用&#xff0c;在于后面的清洗和格式转换。本文从工程角度梳理电商商品数据从采集结果到可导入格式的完整处理链路&#xff0c;重点讲 Shopify 和 WooCommerce 两种主流格式的差异。## 一、采集数据的常见问题原始采集数据通常存在…

作者头像 李华
网站建设 2026/9/2 5:32:30

老板消毒柜XB710A0472深度解析:智能烘干与干燥收纳技术全揭秘

最近在给新家装修选厨电&#xff0c;发现一个很有意思的现象&#xff1a;很多朋友在选消毒柜时&#xff0c;特别容易陷入一个“参数陷阱”&#xff1a;只看容量、看品牌、看价格&#xff0c;却忽略了消毒柜最核心的“使用体验”和“长期维护成本”。结果就是&#xff0c;买回家…

作者头像 李华