用50元预算打造迷你超声波雷达:STM32舵机扫描方案详解
去年我在一个创客马拉松上,看到几个学生团队在展示他们的环境感知项目。其中一个小组用树莓派搭配激光雷达,效果确实惊艳,但物料成本轻松突破了五百元。另一个团队则显得有些沮丧——他们的超声波测距方案因为成本限制,只能做单点检测,无法实现扫描成像。我当时就在想,有没有一种方法,能用极低的成本,让超声波也“看”到周围的世界?
这个想法一直在我脑海里盘旋。直到上个月,我在整理工作室的零件盒时,翻出了一块闲置的STM32F103C8T6、几个SG90舵机、还有几个HC-SR04超声波模块。粗略算了一下,这些零件的总价值还不到三十元。如果再加上一块最便宜的2.4寸LCD屏,总预算完全能控制在五十元以内。这让我意识到,用超低成本实现超声波雷达扫描,不仅可能,而且极具教学和原型开发价值。
今天我要分享的,就是如何用这五十元预算,搭建一个完整的迷你超声波雷达系统。这个方案特别适合学生创客、嵌入式初学者,或者任何预算有限但想探索环境感知技术的开发者。我们不会追求军用雷达的精度,也不会模仿自动驾驶的复杂算法,而是聚焦于用最少的钱,实现最核心的扫描与成像功能,让你亲手体验从单点测距到二维扫描的跨越。
1. 核心物料选型与成本极致压缩
要实现五十元预算的目标,每一分钱都要花在刀刃上。这意味着我们需要在保证基本功能的前提下,对每一个部件进行严格的性价比评估,甚至寻找替代方案。
1.1 主控芯片:为什么是STM32F103C8T6?
在众多微控制器中,STM32F103C8T6,也就是大家常说的“蓝色药丸”或“最小系统板”,几乎是低成本项目的代名词。它的核心优势非常明显:
- 价格极致:裸板价格通常在8-12元人民币,带基础外围电路的最小系统板也就在15元左右。
- 性能足够:基于ARM Cortex-M3内核,72MHz主频,20KB RAM,64KB Flash。对于处理超声波测距、PWM生成和LCD驱动来说,性能绰绰有余。
- 生态成熟:无论是标准库、HAL库,还是寄存器开发,资料都极其丰富。CubeMX工具可以图形化配置,大大降低了开发门槛。
- 外设齐全:拥有多个定时器(用于PWM和输入捕获)、SPI接口(驱动LCD)、足够的GPIO,完全满足本项目需求。
注意:市场上有些更便宜的国产替代芯片(如GD32),虽然引脚兼容,但在某些库函数和稳定性上可能存在细微差异。对于初学者,我建议从正品STM32开始,避免在调试上浪费过多时间。
1.2 感知与执行单元:舵机与超声波的黄金组合
扫描的核心是让传感器动起来。最经济的方案就是舵机+超声波模块。
SG90舵机:市场价格约3-5元。它是一种位置伺服舵机,通过PWM信号控制旋转角度。我们需要的正是它能在0-180度范围内精确摆动的能力。虽然它的扭力不大,但带动一个轻巧的超声波模块完全没问题。
HC-SR04超声波模块:单价约4-6元。这是最经典、最普及的超声波测距模块。工作原理很简单:触发引脚发送一个10us的高电平脉冲,模块会自动发射8个40kHz的超声波,并检测回波。通过测量高电平持续时间,即可计算出距离。
成本对比分析:
| 组件 | 型号/规格 | 预估单价(元) | 备注 |
|---|---|---|---|
| 主控 | STM32F103C8T6最小系统板 | 12 - 15 | 核心,必选 |
| 舵机 | SG90 9g微型舵机 | 3 - 5 | 提供扫描运动 |
| 传感器 | HC-SR04超声波模块 | 4 - 6 | 核心测距单元 |
| 显示 | 2.4寸 TFT LCD (ILI9341) | 15 - 20 | 显示雷达图 |
| 连接件 | 杜邦线、云台支架 | 3 - 5 | 固定与连接 |
| 电源 | USB Type-C供电线 | 0 (通常已有) | 5V/2A即可 |
| 总计 | 37 - 51 | 严格选型可控制在50元内 |
从表格可以看出,只要在LCD屏和连接件上稍加选择,总成本完全可以压在50元红线以下。这里有个小技巧:如果你手头有旧的手机充电宝或USB线,电源部分的成本就是零。
1.3 显示单元的平价选择:2.4寸TFT屏的奥秘
显示部分是成本控制的重点,也是体验提升的关键。2.4寸TFT屏是这个价位段的最佳选择。
市面上常见的2.4寸屏驱动芯片主要是ILI9341。购买时你会发现价格差异很大,从12元到25元不等。这其中的门道主要在于:
- 接口类型:有SPI和8080并行接口两种。SPI接口引脚少(通常5-7根线),编程简单,但刷新速度慢;8080并行接口速度快,但需要更多IO口。对于我们的雷达图,SPI接口的刷新率完全足够,还能节省STM32的引脚。
- 触摸功能:带触摸的屏会贵5-8元。我们的项目只需要显示,绝对不要为用不到的功能买单。
- 背光控制:有些屏的背光是直接接VCC,常亮;有些则可以通过一个GPIO控制。后者更好,可以省电。在代码初始化时,记得找到对应的背光控制引脚并拉高。
我推荐选择SPI接口、无触摸、带背光控制引脚的最基础版本,价格通常在15元左右。购买时一定要向卖家索取资料,确认引脚定义和初始化序列代码。
2. 系统架构与硬件连接实战
有了零件,下一步就是让它们正确地“对话”。硬件连接是项目的骨架,一个清晰的接线图能避免无数调试时的噩梦。
2.1 整体系统框图与信号流
我们先从逻辑上理解整个系统是如何工作的:
[STM32主控] --> (PWM信号) --> [SG90舵机] --> 物理旋转
[STM32主控] --> (Trig脉冲) --> [HC-SR04] --> 发射超声波
[HC-SR04] --> (Echo高电平) --> [STM32输入捕获] --> 计算距离
[STM32主控] --> (SPI数据) --> [ILI9341 LCD] --> 显示雷达图
主控是整个系统的大脑,它需要同时完成四件事:1) 生成舵机PWM波;2) 触发并读取超声波;3) 进行坐标计算;4) 驱动屏幕绘图。这对STM32F103来说,是多任务处理的绝佳练习场景。
2.2 分步接线指南与避坑要点
按照下面的接线表操作,可以最大程度避免错误。强烈建议先给最小系统板和LCD单独供电,测试屏幕能否点亮,再进行整体连接。
| STM32F103C8T6引脚 | 连接目标 | 功能说明 | 注意事项 |
|---|---|---|---|
| PA6 | SG90 信号线(橙色) | 舵机PWM控制 | 必须使用定时器的PWM输出通道 |
| PA5 | HC-SR04 Trig | 超声波触发 | 普通GPIO输出即可 |
| PA0 | HC-SR04 Echo | 回波信号输入 | 必须配置为输入捕获模式 |
| PB13 | LCD SCK | SPI时钟线 | |
| PB15 | LCD SDI(MOSI) | SPI数据线 | |
| PB10 | LCD DC | 数据/命令选择 | 普通GPIO |
| PB11 | LCD CS | 片选信号 | 普通GPIO,低电平有效 |
| PB12 | LCD RESET | 复位信号 | 普通GPIO,初始拉低再拉高 |
| PB9 | LCD LED | 背光控制 | 普通GPIO,拉高点亮 |
| 5V | SG90 VCC(红色)、HC-SR04 VCC | 电源正极 | 务必从外部5V取电 |
| GND | 所有GND引脚 | 电源地 | 共地至关重要 |
| 3.3V | LCD VCC | 屏幕逻辑电源 | 接STM32的3.3V输出 |
至关重要的供电警告:这是新手最容易栽跟头的地方。SG90舵机和HC-SR04模块的工作电压都是5V,而STM32的IO口输出电压是3.3V。虽然STM32的IO口可以耐受5V输入(查阅数据手册确认),但绝对不能试图用STM32的3.3V引脚或下载器的USB口来给舵机和超声波模块供电!它们的瞬间电流会导致电压被拉低,导致STM32复位或屏幕白屏闪烁。正确的做法是:使用一个独立的5V/2A电源(如手机充电器),通过USB Type-C口给整个系统供电。 STM32、舵机、超声波、LCD的电源都从这条总线上取。
接线完成后,硬件部分就准备好了。你可以先用万用表检查一下各电源引脚与地之间是否短路,这是保护芯片的好习惯。
3. 舵机控制:从PWM原理到平滑扫描
让舵机听话地来回扫描,是雷达动起来的第一步。这里的关键是理解PWM(脉冲宽度调制)如何控制舵机角度。
3.1 SG90舵机的PWM信号解读
SG90这类标准舵机期待的是周期为20ms(即频率50Hz)的PWM信号。它并不关心电压的绝对值,而是关注每个周期内高电平的持续时间(脉冲宽度)。这个宽度与角度呈线性关系:
- 0.5ms高电平 -> 对应舵机0度位置
- 1.5ms高电平 -> 对应舵机90度位置(中位)
- 2.5ms高电平 -> 对应舵机180度位置
也就是说,脉冲宽度在0.5ms到2.5ms之间变化,舵机就在0到180度之间运动。我们的目标就是让STM32的定时器产生这样一个周期固定、占空比可变的方波。
3.2 使用CubeMX配置定时器生成PWM
我们使用STM32的TIM3定时器来产生PWM信号。在CubeMX中的配置步骤如下:
- 打开TIM3,选择通道1(Channel 1)为 PWM Generation CH1。
- 设置预分频器(Prescaler)和计数器周期(Counter Period)。这是计算的关键。
- 我们的系统时钟是72MHz。要得到50Hz的频率,需要分频。
- 计算公式:
PWM频率 = 系统时钟 / [(Prescaler + 1) * (Counter Period + 1)] - 一个常见的配置是:
Prescaler = 719,Counter Period = 1999。 - 代入计算:
72,000,000 / (720 * 2000) = 50 Hz。完美!
- 设置脉冲宽度(Pulse)的初始值。这个值决定了舵机的初始位置。根据公式
脉冲宽度(us) = (Pulse / (Counter Period+1)) * 周期(us)。- 要让舵机停在90度(1.5ms),计算:
Pulse = (1500us / 20000us) * 2000 = 150。 - 所以,将初始Pulse设置为150。
- 要让舵机停在90度(1.5ms),计算:
配置完成后,生成代码。在程序中,我们只需要动态修改这个 Pulse 值,就能控制舵机角度了。HAL库提供了非常方便的宏:
// 设置TIM3通道1的占空比,i值对应角度
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, i);
这里的 i 值范围需要映射到舵机的有效角度范围。通过实验我发现,对于上述配置,i 值在50到250之间变化时,舵机大致能覆盖0-180度的范围。你可以通过以下代码进行扫描:
// 从0度扫描到180度
for(int i = 50; i < 250; i++) {
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, i);
HAL_Delay(20); // 控制扫描速度
}
// 从180度扫描回0度
for(int i = 250; i > 50; i--) {
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, i);
HAL_Delay(20);
}
HAL_Delay(20) 意味着在每个角度停留20ms。这个值决定了扫描的速度和测距的密度。调小它,扫描更快,但每个点的测量时间更短,可能影响超声波测距的稳定性;调大它,扫描更慢,图像更稳定。我建议从20ms开始调试。
3.3 实现匀变速扫描与运动优化
简单的匀速扫描有个问题:在扫描的两端(0度和180度),舵机需要瞬间反向,会产生明显的机械抖动和噪音,长期来看对舵机齿轮也是一种损耗。
一个更好的策略是匀变速扫描:在中间区域快速扫描,在两端逐渐减速到零再反向加速。这需要一点简单的运动控制算法。我们可以把扫描过程想象成一个小球在0-180度的“轨道”上做有弹性的往返运动。
下面是一个简化版的实现思路,它使用一个“速度”变量来控制角度的增量:
int current_angle = 90; // 当前角度(映射后的i值)
int speed = 2; // 扫描速度(步进值)
int direction = 1; // 扫描方向:1为增,-1为减
while(1) {
// 更新角度
current_angle += speed * direction;
// 边界检查与速度调整(模拟减速)
if(current_angle >= 240) { // 接近180度
speed = 1; // 减速
direction = -1;
} else if(current_angle <= 60) { // 接近0度
speed = 1;
direction = 1;
} else if(current_angle > 100 && current_angle < 200) { // 中间区域
speed = 3; // 加速
}
// 限制角度在有效范围
if(current_angle > 250) current_angle = 250;
if(current_angle < 50) current_angle = 50;
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, current_angle);
// 在这里触发超声波测距并显示...
HAL_Delay(15); // 基础延迟
}
这段代码让舵机在中间跑得快,在两头跑得慢,运动更加平滑,也更接近真实雷达的扫描方式。你可以调整 speed 的变化阈值和值,来获得不同的运动效果。
4. 超声波测距:精度提升与噪声滤除
HC-SR04模块用起来简单,但要想在移动的舵机上获得稳定可靠的距离数据,就需要一些技巧了。
4.1 输入捕获原理与代码实现
HC-SR04的Echo引脚会输出一个高电平脉冲,其宽度与距离成正比(每58微秒约代表1厘米)。STM32的输入捕获功能就是用来精确测量这个脉冲宽度的。
我们使用另一个定时器(比如TIM2)来捕获这个高电平。在CubeMX中,将连接Echo的引脚(如PA0)配置为 Input Capture direct mode,并选择上升沿和下降沿都触发。
核心逻辑在中断回调函数中实现:
// 全局变量,用于存储时间戳和距离
uint32_t capture_start = 0, capture_end = 0;
float distance_cm = 0;
// 输入捕获中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
if (__HAL_TIM_GET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1) == TIM_INPUTCHANNELPOLARITY_RISING) {
// 捕获到上升沿,记录开始时间
capture_start = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// 切换为下降沿捕获
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
} else {
// 捕获到下降沿,记录结束时间
capture_end = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// 计算高电平持续时间(需考虑定时器溢出)
uint32_t pulse_width = 0;
if (capture_end >= capture_start) {
pulse_width = capture_end - capture_start;
} else {
// 处理计数器溢出
pulse_width = (0xFFFF - capture_start) + capture_end;
}
// 计算距离:时间(us) / 58.0
distance_cm = pulse_width / 58.0f;
// 切换回上升沿捕获,准备下一次测量
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
}
}
}
}
这段代码是测距的核心。它通过捕获Echo引脚上升沿和下降沿的时刻,计算出高电平的持续时间,进而换算出距离。
4.2 多采样与中值滤波:对抗噪声的利器
在实际扫描中,超声波模块可能会受到各种干扰:舵机电机产生的电磁噪声、物体表面材质对声波的散射、甚至空气流动都会导致单次测量值跳动很大。直接使用单次测量结果绘图,雷达图上会出现很多闪烁的“噪点”。
解决方法是滤波。对于这种偶发的尖峰噪声,中值滤波效果非常好。它的原理是连续采样N次(比如5次),然后对这N个值进行排序,取中间的那个值作为最终结果。这个算法能有效滤除偶然出现的极大或极小值。
#define SAMPLE_SIZE 5
float get_filtered_distance(void) {
float samples[SAMPLE_SIZE];
float temp;
// 1. 采集多个样本
for(int i = 0; i < SAMPLE_SIZE; i++) {
Hcsr04Start(); // 触发一次测量
HAL_Delay(25); // 等待测量完成,HC-SR04周期建议>60ms,这里保守一点
samples[i] = Hcsr04Read(); // 读取距离值
}
// 2. 冒泡排序(简单实现)
for(int i = 0; i < SAMPLE_SIZE-1; i++) {
for(int j = 0; j < SAMPLE_SIZE-i-1; j++) {
if(samples[j] > samples[j+1]) {
temp = samples[j];
samples[j] = samples[j+1];
samples[j+1] = temp;
}
}
}
// 3. 返回中值
return samples[SAMPLE_SIZE / 2];
}
在舵机每个角度停顿的间隙,调用这个函数获取滤波后的距离,画面的稳定性会得到质的提升。SAMPLE_SIZE 取5或7是个不错的平衡点,采样次数太多会影响扫描速度。
4.3 有效量程与盲区处理
HC-SR04的标称量程是2cm-400cm,但实际有效量程和精度受很多因素影响。在扫描系统中,你需要特别关注两个问题:
- 最近盲区:物体距离太近(小于2-3cm)时,回波会与发射波重叠,导致无法测量或测距极不准。在代码中,对于小于某个阈值(如5cm)的距离,可以直接视为无效或显示为最小距离。
- 角度引起的误差:超声波波束有一定角度(约15度)。当声波斜射到光滑平面时,可能会发生镜面反射,导致回波无法返回传感器,从而测不到距离。这在雷达图上表现为某些角度上的“空洞”。这不是故障,而是物理特性。可以在数据处理时,对连续多个角度都测不到距离的情况进行特殊标记(比如显示一个特殊颜色),而不是简单地显示为最大距离。
// 距离有效性检查函数
float validate_distance(float raw_distance, int current_angle) {
static float last_valid_distance = 100.0; // 记录上一个有效距离
if (raw_distance < 5.0) {
// 太近,可能是盲区,返回上一个有效值或标记值
return last_valid_distance;
} else if (raw_distance > 300.0) {
// 太远,可能没检测到,也返回上一个有效值
return last_valid_distance;
} else {
// 有效距离,更新记录
last_valid_distance = raw_distance;
return raw_distance;
}
}
5. 雷达图绘制:从极坐标到屏幕像素
这是最有趣的部分——如何将“角度”和“距离”这两个数据,变成屏幕上那个熟悉的、旋转的雷达扫描图。
5.1 极坐标与直角坐标的转换
雷达扫描的本质是在极坐标系下采样:每个数据点由角度(θ)和半径(r,即距离)定义。而我们的LCD屏幕是直角坐标系(x, y)。因此需要转换:
x = 圆心x坐标 + r * cos(θ)
y = 圆心y坐标 - r * sin(θ) // 注意屏幕y轴向下为正,所以用减号
圆心通常设在屏幕中央偏下的位置,比如(160, 200)(对于320x240的屏幕)。r 需要根据屏幕大小和最大显示距离进行缩放。例如,如果你想在屏幕上最大显示150cm的距离,而雷达图的半径是100个像素,那么缩放因子就是 100/150。
// 将距离和角度转换为屏幕坐标
void polar_to_screen(float angle_deg, float distance_cm, int *x, int *y) {
float angle_rad = angle_deg * 3.1415926f / 180.0f;
float scale_factor = 100.0f / 150.0f; // 150cm映射到100像素
float r_pixels = distance_cm * scale_factor;
// 限制最大显示范围
if (r_pixels > 100.0f) r_pixels = 100.0f;
*x = 160 + (int)(r_pixels * cosf(angle_rad));
*y = 200 - (int)(r_pixels * sinf(angle_rad)); // 注意是减法
}
这里用到了数学库的 cosf 和 sinf 函数。对于STM32F103,如果启用了硬件浮点单元(通常没有),计算会很快;如果没有,使用浮点计算可能会稍慢。你也可以考虑使用查找表或者定点数运算来优化性能,但对于这个扫描速度,浮点计算完全够用。
5.2 绘制动态扫描线与历史轨迹
一个好看的雷达图应该包含几个元素:静态的背景网格、动态的扫描线、以及历史检测点的痕迹。
绘制静态背景:这可以在初始化时一次性画好。包括同心圆(表示距离环)和辐射线(表示角度)。
void draw_radar_background(void) {
// 画同心圆
Gui_Circle(160, 200, 100, GREEN); // 最外圈,假设对应150cm
Gui_Circle(160, 200, 66, GREEN); // 中间圈,对应100cm
Gui_Circle(160, 200, 33, GREEN); // 内圈,对应50cm
// 画角度线 (每30度一条)
for(int angle = 0; angle < 180; angle += 30) {
float rad = angle * 3.1415926f / 180.0f;
int x2 = 160 + (int)(110 * cosf(rad)); // 线稍长于最外圈
int y2 = 200 - (int)(110 * sinf(rad));
Gui_DrawLine(160, 200, x2, y2, GREEN);
}
}
动态扫描线:这是雷达的“指针”,随着舵机角度实时移动。实现方法是:在绘制新的扫描线之前,先用背景色(黑色)擦除上一条扫描线,然后在新的角度画一条新线。
int last_scan_line_angle = -1;
void update_scan_line(int current_angle_deg) {
// 1. 擦除上一条扫描线(如果存在)
if(last_scan_line_angle != -1) {
float rad = last_scan_line_angle * 3.1415926f / 180.0f;
int x_old = 160 + (int)(100 * cosf(rad));
int y_old = 200 - (int)(100 * sinf(rad));
Gui_DrawLine(160, 200, x_old, y_old, BLACK); // 用背景色画线,即擦除
}
// 2. 绘制新的扫描线
float rad = current_angle_deg * 3.1415926f / 180.0f;
int x_new = 160 + (int)(100 * cosf(rad));
int y_new = 200 - (int)(100 * sinf(rad));
Gui_DrawLine(160, 200, x_new, y_new, GREEN);
// 3. 记录当前角度
last_scan_line_angle = current_angle_deg;
}
历史轨迹点:当超声波检测到物体时,我们在对应的位置画一个点。为了让图像有“余晖”效果,而不是瞬间消失,我们可以采用两种策略:
- 淡出法:每个点画成一个小圆,然后在每次全屏刷新时,将所有点的颜色向背景色渐变。
- 定时清除法(更简单):只保留最近N次扫描的点。例如,用一个数组记录最近100个点的位置,每次绘制新点前,先清除最旧的那个点(用背景色画点覆盖),然后绘制新的点。这样屏幕上始终保留一小段时间内的轨迹。
#define MAX_POINTS 50
typedef struct {
int x;
int y;
int age; // “年龄”,用于淡出效果
} radar_point_t;
radar_point_t point_history[MAX_POINTS];
int point_index = 0;
void add_radar_point(int x, int y) {
// 清除最旧的点(如果已存在)
if(point_history[point_index].age > 0) {
Gui_DrawPoint(point_history[point_index].x,
point_history[point_index].y,
BLACK);
}
// 存储新点
point_history[point_index].x = x;
point_history[point_index].y = y;
point_history[point_index].age = 10; // 设置初始“寿命”
// 绘制新点(用亮色)
Gui_DrawPoint(x, y, YELLOW);
// 更新索引
point_index = (point_index + 1) % MAX_POINTS;
}
// 在主循环中定期调用,使所有点“衰老”
void age_radar_points(void) {
for(int i = 0; i < MAX_POINTS; i++) {
if(point_history[i].age > 0) {
point_history[i].age--;
// 可以根据age改变颜色,实现淡出效果
// 例如: age=10: 白色, age=5: 灰色, age=1: 深灰色
}
}
}
5.3 性能优化:局部刷新与双缓冲思想
如果你发现雷达图刷新时有闪烁,或者扫描感觉卡顿,可能是屏幕刷新太慢。320*240的屏幕全屏刷新大约需要传输153600个像素的数据(每个像素2字节),通过SPI接口这需要一定时间。
优化策略是局部刷新:只更新发生变化的部分,而不是每一帧都重绘整个屏幕。
- 扫描线移动:只擦除和重绘扫描线本身。
- 新目标点:只绘制单个或少量像素。
- 背景网格:只在初始化时绘制一次。
更进一步,可以引入双缓冲的思想:在内存中开辟一块和屏幕显示区域一样大的缓冲区(对于320*240的16位色,需要150KB,STM32F103C8T6的20KB RAM显然不够)。但我们可以针对扫描区域(一个扇形区域)建立一个小的缓冲区,先在内存中画好,再一次性传输到屏幕,这能有效减少闪烁。不过对于这个入门项目,局部刷新已经能带来很大改善。
最后,别忘了在屏幕角落显示一些文本信息,比如当前角度、最近物体的距离等,这能让你的雷达看起来更专业。使用你之前准备好的字体绘制函数,在固定位置更新这些数字即可。
把所有这些部分组合起来——舵机平稳扫描,超声波稳定测距,坐标实时转换,图形流畅绘制——你的迷你超声波雷达就真正“活”过来了。看着屏幕上随着真实物体移动而变化的轨迹点,那种亲手创造出一个感知系统的成就感,是任何现成产品都无法替代的。
590




被折叠的 条评论
为什么被折叠?



