引言:无人机激光雷达的价值与挑战
激光雷达(LiDAR)作为一种能够精确获取环境三维信息的传感器,在无人机领域具有不可替代的价值。它能够为无人机提供实时的环境感知能力,使其在自主导航、避障、三维建模等任务中表现出色。然而,商用激光雷达高昂的价格(通常从数千元到数万元不等)成为了许多开发者和爱好者进入该领域的障碍。
本文将深入探讨如何 "手搓"(DIY)适用于无人机的低成本激光雷达系统,详细介绍三种不同的实现方案,从硬件选型、结构设计到软件开发进行全面解析,并通过表格形式多维度对比各方案的成本、性能与精度。最后,我们将学习如何将这些 DIY 激光雷达集成到 ROS2 系统中,实现与无人机导航栈的无缝对接。
无论你是无人机爱好者、机器人开发者,还是希望降低项目成本的企业研发人员,本文都将为你提供有价值的参考和实践指导。
一、激光雷达基础原理
1.1 激光雷达工作原理
激光雷达(Light Detection and Ranging,简称 LiDAR)的基本工作原理是通过发射激光束并接收其反射信号来计算目标物体的距离。具体过程如下:
- 激光发射器发射一束激光脉冲
- 激光脉冲遇到物体后反射
- 接收器接收反射回来的激光脉冲
- 根据激光脉冲的飞行时间(ToF,Time of Flight)计算距离:距离 = (光速 × 飞行时间) / 2
除了距离信息,激光雷达还能通过测量激光束的发射方向(角度)来确定目标点的空间坐标,当对周围环境进行扫描时,就能获得一系列空间点的集合,即 "点云"(Point Cloud)。
1.2 点云数据解析
点云是激光雷达输出的核心数据形式,它是由海量三维点组成的数据集,每个点包含以下信息:
- X、Y、Z:三维空间坐标
- 强度(Intensity):反射信号的强度,与物体表面材质相关
- 时间戳:数据采集时间
点云数据能够精确描述环境的三维结构,是无人机进行环境感知和自主决策的基础。
1.3 无人机激光雷达的特殊要求
与地面机器人相比,无人机搭载的激光雷达有其特殊要求:
- 重量轻:直接影响无人机的续航时间和载荷能力
- 体积小:减少飞行阻力,便于安装
- 功耗低:避免过度消耗无人机电池
- 抗震性好:适应飞行过程中的振动环境
- 数据率适中:既能提供足够的环境信息,又不会过度占用通信带宽
这些特点为 DIY 无人机激光雷达提出了独特的设计挑战。
二、低成本激光雷达方案设计考量
在开始设计 DIY 激光雷达之前,我们需要明确一些关键的设计考量因素:
2.1 核心性能指标
- 测距范围:能够测量的最远距离,决定了无人机的感知范围
- 测距精度:距离测量的误差范围,影响环境建模的准确性
- 角分辨率:相邻测量点之间的角度间隔,决定了点云的密度
- 扫描频率:单位时间内完成的扫描次数,影响数据的实时性
- 点云密度:单位面积内的点数量,决定了环境细节的丰富程度
2.2 成本控制策略
- 选用消费级而非工业级元器件
- 简化机械结构,采用 3D 打印等低成本制造方式
- 优化电路设计,减少不必要的功能模块
- 利用开源软件和固件,降低开发成本
2.3 无人机适配性设计
- 轻量化设计:目标重量控制在 100g 以内(针对小型无人机)
- 低功耗设计:工作电流尽量控制在 500mA 以内
- 紧凑结构:尺寸越小越好,便于集成到无人机上
- 减震设计:减少飞行振动对测量精度的影响
- 安装便利性:设计通用的安装接口,适应不同无人机平台
三、方案一:旋转单线激光雷达
3.1 方案概述
旋转单线激光雷达是最经典也最容易实现的 DIY 方案。其核心思想是将一个固定方向的激光测距模块安装在旋转机构上,通过旋转运动实现对周围环境的 360° 扫描。
这种方案的优点是结构相对简单,成本低廉,适合初学者入门;缺点是点云密度相对较低,机械结构存在磨损问题。
3.2 硬件选型
3.2.1 激光测距模块
推荐选用以下几种低成本激光测距模块:
| 型号 | 测距范围 | 精度 | 测量频率 | 接口 | 功耗 | 价格 (人民币) |
|---|---|---|---|---|---|---|
| TFmini-S | 0.1-12m | ±(3cm+0.5% 测量值) | 100Hz | UART | 5V, 80mA | 80-100 |
| VL53L1X | 0.05-4m | ±3% | 50Hz | I2C | 3.3V, 18mA | 30-50 |
| YDLIDAR X2 | 0.1-8m | ±5cm | 4000 点 / 秒 | UART | 5V, 120mA | 200-250 |
| A02YYUW | 0.2-40m | ±0.5% | 10Hz | UART | 5V, 30mA | 40-60 |
对于无人机应用,推荐优先考虑 TFmini-S,它在测距范围、精度和功耗之间取得了较好的平衡。
3.2.2 旋转机构
| 部件 | 推荐型号 | 特点 | 价格 (人民币) |
|---|---|---|---|
| 电机 | N20 减速电机 | 体积小,扭矩适中,转速可调 | 15-30 |
| 电机驱动 | TB6612FNG | 双通道,支持 PWM 调速 | 15-20 |
| 旋转编码器 | AS5600 | 12 位分辨率,I2C 接口 | 10-15 |
| 轴承 | 608ZZ | 低摩擦,高转速 | 5-10 |
3.2.3 控制单元
| 控制单元 | 特点 | 价格 (人民币) |
|---|---|---|
| ESP32 | 双核,WiFi + 蓝牙,足够的 GPIO 和 UART | 30-50 |
| STM32F103 | 性能稳定,定时器资源丰富 | 20-40 |
| Arduino Nano | 入门简单,兼容性好 | 20-30 |
推荐使用 ESP32,它不仅性能足够,还内置无线通信功能,可以减少布线,适合无人机应用。
3.2.4 电源模块
| 电源模块 | 特点 | 价格 (人民币) |
|---|---|---|
| MT3608 升压模块 | 输入 2-24V,输出可调 | 3-5 |
| AMS1117-3.3V | 5V 转 3.3V,输出电流 1A | 2-3 |
| TP4056 | 锂电池充电管理 | 5-8 |
3.3 硬件总成本估算
| 部件类别 | 成本范围 (人民币) |
|---|---|
| 激光测距模块 | 80-100 |
| 旋转机构(电机、驱动、编码器等) | 45-75 |
| 控制单元 | 30-50 |
| 电源模块 | 10-16 |
| 结构件与连接件 | 20-50 |
| 其他电子元件(电阻、电容等) | 10-20 |
| 总计 | 195-311 |
3.4 机械结构设计
3.4.1 整体结构
旋转单线激光雷达的机械结构主要由以下几部分组成:
- 底座:固定电机和电子元件
- 旋转平台:安装激光测距模块,由电机驱动旋转
- 轴承组件:减少旋转摩擦,保证旋转平稳性
- 连接导线:需要考虑旋转时的导线处理,可采用滑环或限制旋转角度
3.4.2 3D 打印设计要点
- 材料选择:PLA + 或 PETG,兼顾强度和打印难度
- 壁厚:1.5-2mm,在重量和强度间平衡
- 支撑结构:旋转部件需要添加适当支撑,保证精度
- 装配间隙:设计 0.1-0.2mm 的装配间隙,便于安装
3.4.3 减震设计
在无人机应用中,减震设计尤为重要:
- 在激光雷达与无人机之间添加硅胶减震垫
- 电机与底座之间使用弹性连接
- 尽量降低重心,提高稳定性
3.5 电路设计
3.5.1 电源电路
- 主电源:通常由无人机提供 5V 电源
- 激光模块:多数需要 5V 供电
- 控制单元:ESP32 可直接使用 5V 供电,内部稳压到 3.3V
- 编码器:通常需要 3.3V 供电
3.5.2 控制电路
- 电机驱动:TB6612FNG 与 ESP32 的 GPIO 连接,通过 PWM 控制转速
- 激光模块:UART 接口与 ESP32 的 UART 引脚连接
- 编码器:I2C 接口与 ESP32 的 I2C 引脚连接
3.5.3 电路保护
- 添加保险丝或自恢复保险丝,防止短路
- 关键元器件添加 TVS 二极管,防止电压尖峰
- 电机驱动电路添加续流二极管
3.6 固件开发
3.6.1 开发环境
推荐使用 Arduino IDE 或 PlatformIO 进行 ESP32 的固件开发,它们都提供了丰富的库支持和友好的开发界面。
3.6.2 核心功能实现
-
电机控制:
- 实现 PWM 调速,控制旋转速度(推荐 300-600RPM)
- 实现电机启动和停止的平滑过渡,减少机械冲击
-
角度测量:
- 通过 AS5600 读取当前角度
- 实现角度校准,确定 0° 位置
-
距离测量:
- 与激光模块通信,获取距离数据
- 实现数据校验和错误处理
-
点云生成:
- 将角度和距离数据转换为三维坐标
- 为每个点添加时间戳
- 实现数据缓存和批量发送
-
数据传输:
- 通过 UART 或 WiFi 发送点云数据
- 实现简单的通信协议,包含帧头、帧尾和校验
3.6.3 关键代码片段
cpp
运行
// 角度与距离转换为三维坐标
void calculatePoint(float angle, float distance, Point& point) {
// 角度转换为弧度
float rad = angle * PI / 180.0;
// 计算X, Y坐标(假设在水平面上扫描)
point.x = distance * cos(rad);
point.y = distance * sin(rad);
point.z = 0; // 单线雷达在同一高度
// 设置时间戳
point.timestamp = millis();
}
// 数据发送函数
void sendPointCloud(Point* points, int count) {
// 发送帧头
Serial.write(0xAA);
Serial.write(0x55);
// 发送点数量
Serial.write(count >> 8);
Serial.write(count & 0xFF);
// 发送每个点的数据
for(int i = 0; i < count; i++) {
sendPoint(points[i]);
}
// 发送校验和
Serial.write(calculateChecksum(points, count));
}
3.7 性能测试与优化
3.7.1 测试环境搭建
- 平整室内空间,标记已知距离的参考点
- 使用高精度卷尺测量实际距离作为基准
- 记录不同距离下的测量数据,计算误差
3.7.2 关键性能指标测试
| 测试项目 | 测试方法 | 预期结果 |
|---|---|---|
| 测距精度 | 在不同距离放置目标,比较测量值与实际值 | 误差 < 5cm(近距离) |
| 角度精度 | 测量已知角度的目标,比较测量值与实际值 | 误差 < 1° |
| 扫描频率 | 记录完成 360° 扫描的时间 | 10-20Hz |
| 点云密度 | 统计单位角度内的点数 | >1 点 /° |
| 稳定性 | 连续工作 30 分钟,观察数据是否稳定 | 无明显漂移 |
3.7.3 常见问题与优化方案
-
测距误差大:
- 优化激光模块的校准参数
- 增加温度补偿算法
- 过滤异常值
-
角度测量不准:
- 精确校准编码器零点
- 增加角度补偿,修正机械偏差
- 降低旋转速度,提高角度采样精度
-
数据传输不稳定:
- 优化通信协议,增加校验机制
- 降低数据传输速率,确保可靠性
- 改用 WiFi 或蓝牙等无线传输方式
-
机械振动大:
- 增加减震结构
- 优化电机控制,实现平滑启停
- 选用更高质量的轴承
四、方案二:多线固定布局激光雷达
4.1 方案概述
多线固定布局激光雷达采用多个激光测距模块,按一定角度差固定安装,无需旋转机构即可实现一定角度范围的扫描。这种方案通过增加激光模块数量来提高点云密度和垂直方向的感知能力,同时避免了旋转机构带来的机械磨损和振动问题。
优点是结构简单可靠,无运动部件,适合对稳定性要求高的场景;缺点是激光模块数量增加导致成本上升,且扫描角度范围受限。
4.2 硬件选型
4.2.1 激光测距模块
考虑到成本因素,多线方案通常选用更经济的激光模块:
| 型号 | 测距范围 | 精度 | 测量频率 | 接口 | 功耗 | 价格 (人民币) |
|---|---|---|---|---|---|---|
| VL53L0X | 0.05-2m | ±3% | 50Hz | I2C | 3.3V, 18mA | 20-30 |
| VL53L1X | 0.05-4m | ±3% | 50Hz | I2C | 3.3V, 18mA | 30-50 |
| TFmini-S | 0.1-12m | ±(3cm+0.5% 测量值) | 100Hz | UART | 5V, 80mA | 80-100 |
对于 4-8 线的方案,推荐使用 VL53L1X,它在成本和性能之间取得了较好的平衡。
4.2.2 控制单元
| 控制单元 | 特点 | 适合线数 | 价格 (人民币) |
|---|---|---|---|
| ESP32 | 多个 I2C 和 UART 接口,处理能力强 | 4-16 线 | 30-50 |
| STM32F405 | 丰富的外设,高性能 | 8-32 线 | 50-80 |
| Raspberry Pi Pico | 双核心,GPIO 丰富 | 4-8 线 | 20-30 |
ESP32 是性价比很高的选择,它的 I2C 接口可以通过软件模拟扩展,支持更多的激光模块。
4.2.3 电源管理
多线方案的电源管理尤为重要,因为多个激光模块同时工作会消耗较多电流:
| 电源模块 | 特点 | 价格 (人民币) |
|---|---|---|
| MP2307 降压模块 | 输入 4.5-28V,输出 1.2-20V,3A | 5-8 |
| TPS63070 | 同步降压 - 升压,输入 2.7-11V,输出 1.8-11V,2A | 10-15 |
| 多路 LDO | 提供稳定的 3.3V 和 5V 输出 | 8-12 |
4.3 硬件总成本估算
以 8 线方案为例:
| 部件类别 | 成本范围 (人民币) |
|---|---|
| 激光测距模块(8 个 VL53L1X) | 240-400 |
| 控制单元(ESP32) | 30-50 |
| 电源模块 | 20-35 |
| 结构件与连接件 | 30-60 |
| 转接板与连接线 | 20-40 |
| 其他电子元件 | 10-20 |
| 总计 | 350-605 |
4.4 机械结构设计
4.4.1 布局设计
多线激光雷达的核心是激光模块的角度布局,常见的布局方式有:
- 对称布局:激光模块在水平面对称分布,适合全方位扫描
- 前倾布局:所有激光模块向前倾斜一定角度,适合前向避障
- 分层布局:在垂直方向按不同角度排列,获取更多高度信息
对于无人机应用,推荐采用前倾 + 少量后倾的混合布局,既能有效感知前方障碍物,又能一定程度上感知下方地形。
4.4.2 角度选择
激光模块之间的角度差决定了垂直方向的分辨率,常见的角度间隔有:
- 密集布局:2.5°-5° 间隔,适合需要高精度的场景
- 稀疏布局:5°-10° 间隔,适合低成本方案
8 线方案推荐采用 5° 间隔,可覆盖 40° 左右的垂直视角。
4.4.3 结构设计要点
- 轻量化:尽量采用镂空设计,减轻重量
- 刚性:保证激光模块的角度稳定性
- 散热:激光模块工作时会发热,适当设计散热结构
- 安装接口:设计与主流无人机兼容的安装孔位
4.5 电路设计
4.5.1 电源分配
多线方案的电源设计需要特别注意:
- 采用分布式供电,每个或每两个激光模块一组电源
- 增加电源滤波电容,减少模块间的干扰
- 设计过流保护,防止单个模块故障影响整体
4.5.2 通信接口设计
VL53L1X 使用 I2C 接口,但默认地址相同,需要通过硬件方式修改地址或使用多路 I2C 开关:
- 硬件地址修改:通过焊接电阻改变 ADDR 引脚电平
- I2C 多路开关:使用 TCA9548A 等芯片扩展 I2C 总线
推荐使用 TCA9548A I2C 多路开关,它支持 8 路 I2C 通道,非常适合 8 线方案,且编程简单。
4.5.3 电路原理图(简化)
plaintext
[无人机电源5V] → [电源模块] → [3.3V] → [ESP32]
→ [3.3V] → [TCA9548A]
→ [3.3V] → [8个VL53L1X]
[ESP32 I2C] → [TCA9548A] → [VL53L1X #1]
→ [VL53L1X #2]
→ ...
→ [VL53L1X #8]
[ESP32 UART] → [数据传输接口]
4.6 固件开发
4.6.1 核心功能实现
-
I2C 多路控制:
- 初始化 TCA9548A,实现通道切换
- 为每个激光模块分配唯一通道
-
激光模块同步:
- 实现多个激光模块的同步测量
- 优化测量顺序,减少模块间干扰
-
点云生成:
- 根据每个模块的安装角度计算三维坐标
- 对不同模块的数据进行时间同步
-
数据传输:
- 打包处理多线数据
- 实现高效的数据压缩和传输
4.6.2 关键代码片段
cpp
运行
// 初始化I2C多路开关和激光模块
void initSensors() {
// 初始化TCA9548A
tca.begin();
// 逐个初始化每个激光模块
for(int i = 0; i < NUM_SENSORS; i++) {
// 切换到对应的I2C通道
tca.selectChannel(i);
// 初始化VL53L1X
if(!sensors[i].init()) {
Serial.print("Sensor ");
Serial.print(i);
Serial.println(" initialization failed!");
} else {
// 配置传感器参数
sensors[i].setDistanceMode(VL53L1X_DISTANCEMODE_MEDIUM);
sensors[i].setMeasurementTimingBudget(50000); // 50ms测量时间
sensors[i].startContinuous(50); // 50ms间隔连续测量
}
}
}
// 读取所有传感器数据并生成点云
void readAndGeneratePointCloud() {
unsigned long timestamp = millis();
for(int i = 0; i < NUM_SENSORS; i++) {
// 切换到对应的I2C通道
tca.selectChannel(i);
// 读取距离
int distance = sensors[i].read();
// 跳过无效数据
if(distance <= 0 || distance > MAX_DISTANCE) continue;
// 计算三维坐标
Point point;
calculatePoint(i, distance, point);
point.timestamp = timestamp;
// 添加到点云
pointCloud.push_back(point);
}
}
// 根据传感器编号计算三维坐标
void calculatePoint(int sensorId, float distance, Point& point) {
// 获取该传感器的安装角度(水平和垂直)
float horizontalAngle = sensorAngles[sensorId].horizontal;
float verticalAngle = sensorAngles[sensorId].vertical;
// 角度转换为弧度
float hRad = horizontalAngle * PI / 180.0;
float vRad = verticalAngle * PI / 180.0;
// 计算三维坐标
float r = distance * cos(vRad);
point.x = r * cos(hRad);
point.y = r * sin(hRad);
point.z = distance * sin(vRad);
}
4.7 性能测试与优化
4.7.1 关键性能指标测试
| 测试项目 | 测试方法 | 预期结果(8 线方案) |
|---|---|---|
| 测距精度 | 在不同距离和角度放置目标,比较测量值与实际值 | 误差 < 5%(近距离) |
| 视场角 | 测量能够探测到的最大角度范围 | 水平 ±45°,垂直 ±20° |
| 数据更新率 | 记录每秒生成的点数量 | >400 点 / 秒 |
| 模块一致性 | 比较不同模块在相同条件下的测量结果 | 差异 < 3% |
| 功耗测试 | 测量不同工作模式下的电流消耗 | <300mA@5V |
4.7.2 常见问题与优化方案
-
模块间干扰:
- 增加模块间的物理隔离
- 采用异步测量方式,避免激光同时发射
- 调整激光模块的发射功率
-
数据同步问题:
- 为所有点添加精确时间戳
- 实现软件同步算法,补偿测量延迟
- 优化测量顺序,减少同步误差
-
功耗过高:
- 实现动态功率管理,空闲模块进入低功耗模式
- 降低非关键区域的测量频率
- 选用更低功耗的激光模块
-
数据量过大:
- 实现点云数据压缩算法
- 动态调整测量频率,根据需要改变点云密度
- 过滤冗余点,保留关键信息
五、方案三:MEMS 振镜激光雷达
5.1 方案概述
MEMS(微机电系统)振镜激光雷达采用微型振镜替代传统的机械旋转结构,通过振镜的快速摆动实现激光束的扫描。这种方案结合了机械扫描和固态方案的优点,具有体积小、重量轻、寿命长、功耗低等特点。
优点是结构紧凑,可靠性高,扫描速度快;缺点是 MEMS 振镜成本相对较高,光学设计复杂,调试难度大。
5.2 硬件选型
5.2.1 核心光学部件
| 部件 | 推荐型号 | 特点 | 价格 (人民币) |
|---|---|---|---|
| MEMS 振镜 | ADNS-9800 | 二维扫描,角度 ±15° | 150-200 |
| 激光发射器 | 905nm 激光二极管 | 功率 5-20mW,适合测距 | 30-50 |
| 光电接收器 | APD/SPAD | 高灵敏度,适合微弱信号检测 | 50-100 |
| 光学透镜 | 定制或标准透镜组 | 聚焦激光束,提高效率 | 40-80 |
5.2.2 控制与信号处理单元
| 部件 | 推荐型号 | 特点 | 价格 (人民币) |
|---|---|---|---|
| 主控制器 | STM32F407 | 高性能,丰富外设 | 60-80 |
| 激光驱动 | 定制电路 | 支持脉冲调制 | 20-40 |
| 信号处理 | TIA + 比较器 | 放大和处理接收信号 | 30-50 |
| 定时器 | 高精度定时器 | 测量飞行时间 | 10-20 |
5.2.3 电源模块
| 电源模块 | 特点 | 价格 (人民币) |
|---|---|---|
| 多路 LDO | 提供多种稳定电压 | 15-25 |
| 激光驱动电源 | 提供高峰值电流 | 20-30 |
5.3 硬件总成本估算
| 部件类别 | 成本范围 (人民币) |
|---|---|
| 核心光学部件 | 270-430 |
| 控制与信号处理单元 | 120-190 |
| 电源模块 | 35-55 |
| 结构件与光学调整架 | 50-100 |
| 连接线与接插件 | 20-40 |
| 其他电子元件 | 30-50 |
| 总计 | 525-865 |
5.4 光学系统设计
5.4.1 光路设计
MEMS 振镜激光雷达的光路设计是关键,主要包括:
-
发射光路:
- 激光二极管发出的光束经过准直透镜
- 准直后的光束入射到 MEMS 振镜
- 振镜反射光束并投射到目标物体
-
接收光路:
- 目标反射的光束经振镜反射
- 通过接收透镜聚焦到光电探测器
- 探测器将光信号转换为电信号
5.4.2 关键光学参数
- 激光波长:推荐 905nm,兼顾安全性和探测距离
- 光束发散角:0.5°-1°,平衡探测距离和视场角
- 接收视场:应略大于发射光束的扫描范围
- 焦距:根据期望的探测距离和视场角设计
5.4.3 光学对准要求
MEMS 方案对光学对准精度要求较高:
- 激光束应垂直入射到振镜中心
- 发射和接收光路应精确校准,避免盲区
- 透镜中心应与光轴重合,减少像差
5.5 电路设计
5.5.1 激光驱动电路
激光驱动电路需要产生高峰值电流的窄脉冲:
- 脉冲宽度:10-50ns,减少功耗和热效应
- 峰值电流:50-200mA,根据激光功率要求调整
- 触发方式:外部触发,与 MEMS 振镜扫描同步
5.5.2 接收电路
接收电路需要处理微弱的反射信号:
- 跨阻放大器(TIA):将光电流转换为电压信号
- 信号放大:多级放大,提高信噪比
- 比较器:将模拟信号转换为数字脉冲
- 滤波电路:去除环境光和电路噪声
5.5.3 MEMS 驱动电路
MEMS 振镜需要特殊的驱动信号:
- 正弦波驱动:通常需要 X 和 Y 两个方向的正弦信号
- 驱动频率:通常在 kHz 级别,根据振镜型号确定
- 振幅控制:通过调整驱动电压控制扫描角度
5.6 固件开发
5.6.1 核心功能实现
-
MEMS 振镜控制:
- 生成高精度正弦驱动信号
- 实现扫描模式控制(如光栅扫描、螺旋扫描)
- 振镜位置反馈与校准
-
激光脉冲控制:
- 精确控制激光发射时间
- 实现与振镜位置的同步
- 动态调整激光功率
-
飞行时间测量:
- 高精度时间间隔测量(ps 级别)
- 噪声过滤与信号验证
- 距离计算与校准
-
点云生成:
- 根据振镜位置和距离数据计算三维坐标
- 扫描区域填充与插值
- 数据格式转换与打包
5.6.2 关键代码片段
c
运行
// 初始化MEMS振镜
void initMEMS() {
// 初始化DAC和定时器,用于生成驱动信号
initDAC();
initTimers();
// 设置扫描参数
memsParams.scanMode = SCAN_MODE_RASTER;
memsParams.xFrequency = 2000; // X方向2kHz
memsParams.yFrequency = 50; // Y方向50Hz
memsParams.xAmplitude = 15; // X方向±15°
memsParams.yAmplitude = 10; // Y方向±10°
// 启动MEMS驱动
startMEMSDrive();
// 等待振镜稳定
delay(100);
}
// 激光与MEMS同步控制
void startMeasurement() {
// 启动定时器,用于同步激光发射
HAL_TIM_Base_Start_IT(&htim2);
// 使能激光发射
laserEnable();
}
// 定时器中断服务函数,用于同步控制
void TIM2_IRQHandler() {
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET) {
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
// 获取当前MEMS振镜位置
float xAngle = getCurrentXAngle();
float yAngle = getCurrentYAngle();
// 发射激光脉冲
triggerLaserPulse();
// 记录发射时间
uint64_t发射Time = getTimestamp();
// 等待接收信号(实际中使用中断方式)
if (waitForLaserEcho(MAX_WAIT_TIME)) {
// 计算飞行时间
uint64_t receiveTime = getTimestamp();
uint32_t tof = receiveTime - 发射Time;
// 计算距离(单位:米)
float distance = (tof * SPEED_OF_LIGHT) / 2.0 / 1e9;
// 生成点云数据
Point point;
calculatePoint(xAngle, yAngle, distance, point);
point.timestamp = 发射Time;
// 添加到点云缓冲区
addToPointCloudBuffer(point);
}
}
}
// 根据角度和距离计算三维坐标
void calculatePoint(float xAngle, float yAngle, float distance, Point& point) {
// 角度转换为弧度
float xRad = xAngle * PI / 180.0;
float yRad = yAngle * PI / 180.0;
// 计算三维坐标
float r = distance * cos(yRad);
point.x = r * sin(xRad);
point.y = r * cos(xRad);
point.z = distance * sin(yRad);
}
5.7 性能测试与优化
5.7.1 关键性能指标测试
| 测试项目 | 测试方法 | 预期结果 |
|---|---|---|
| 测距范围 | 测量能够可靠探测的最远距离 | 1-10m(取决于激光功率) |
| 测距精度 | 比较测量距离与实际距离 | 误差 < 3cm(近距离) |
| 视场角 | 测量能够覆盖的角度范围 | 水平 ±15°,垂直 ±10° |
| 扫描频率 | 计算每秒完成的全视场扫描次数 | 10-30Hz |
| 点云密度 | 统计单位面积内的点数 | >100 点 /㎡ |
| 功耗 | 测量工作状态下的电流和电压 | <200mA@5V |
5.7.2 常见问题与优化方案
-
测距精度不足:
- 优化时间测量电路,提高计时精度
- 增加温度补偿算法
- 实现多脉冲平均,减少随机误差
-
扫描不均匀:
- 优化 MEMS 驱动信号,减少非线性失真
- 实现扫描位置校准,修正几何畸变
- 采用非均匀采样补偿振镜的速度变化
-
信噪比低:
- 优化光学系统,提高接收效率
- 增加信号处理电路的增益和带宽
- 实现背景光抑制算法
-
点云数据不完整:
- 调整扫描模式,覆盖盲区
- 优化触发时机,确保每个角度都有测量
- 增加数据插值算法,填充缺失点
六、三种方案多维度对比
6.1 成本对比
| 成本项目 | 方案一:旋转单线 | 方案二:多线固定(8 线) | 方案三:MEMS 振镜 |
|---|---|---|---|
| 核心传感器 | 80-100 元 | 240-400 元 | 270-430 元 |
| 控制单元 | 30-50 元 | 30-50 元 | 60-80 元 |
| 机械 / 结构件 | 65-125 元 | 50-100 元 | 50-100 元 |
| 电路元件 | 50-86 元 | 70-125 元 | 60-100 元 |
| 总成本 | 225-361 元 | 390-675 元 | 440-710 元 |
| 成本等级 | ★☆☆☆☆ | ★★☆☆☆ | ★★★☆☆ |
6.2 性能参数对比
| 性能参数 | 方案一:旋转单线 | 方案二:多线固定(8 线) | 方案三:MEMS 振镜 |
|---|---|---|---|
| 测距范围 | 0.1-12m | 0.05-4m | 0.5-10m |
| 测距精度 | ±(3cm+0.5% 测量值) | ±3% | ±2cm(近距离) |
| 水平视场角 | 360° | ±45°(可扩展) | ±15°(可定制) |
| 垂直视场角 | 1° | ±20° | ±10° |
| 角分辨率 | 0.5-1° | 5°(垂直) | 0.1-0.5° |
| 扫描频率 | 10-20Hz | 50Hz(全视场) | 10-30Hz |
| 点云密度 | 360-720 点 / 帧 | 400-800 点 / 帧 | 1000-5000 点 / 帧 |
| 性能等级 | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ |
6.3 物理特性对比
| 物理特性 | 方案一:旋转单线 | 方案二:多线固定(8 线) | 方案三:MEMS 振镜 |
|---|---|---|---|
| 重量 | 50-80g | 60-100g | 40-70g |
| 尺寸 | Φ40-60mm × H30-50mm | 50×50×30mm | 30×30×20mm |
| 功耗 | 400-600mW | 600-900mW | 300-600mW |
| 抗震性 | 中(有运动部件) | 高(无运动部件) | 高(微运动部件) |
| 寿命 | 中等(机械磨损) | 长(无磨损) | 长(微磨损) |
| 物理特性等级 | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ |
6.4 开发难度对比
| 开发方面 | 方案一:旋转单线 | 方案二:多线固定(8 线) | 方案三:MEMS 振镜 |
|---|---|---|---|
| 机械设计 | 中等(旋转机构) | 简单(固定结构) | 复杂(光学对准) |
| 电路设计 | 简单 | 中等(电源管理) | 复杂(高频信号) |
| 固件开发 | 中等(同步控制) | 简单(并行读取) | 复杂(精确同步) |
| 调试难度 | 中等 | 低 | 高 |
| 开发周期 | 2-4 周 | 1-3 周 | 4-8 周 |
| 开发难度等级 | ★★☆☆☆ | ★☆☆☆☆ | ★★★★☆ |
6.5 适用场景对比
| 应用场景 | 方案一:旋转单线 | 方案二:多线固定(8 线) | 方案三:MEMS 振镜 |
|---|---|---|---|
| 室内导航 | ★★★☆☆ | ★★★☆☆ | ★★★★☆ |
| 避障系统 | ★★☆☆☆ | ★★★★☆ | ★★★★☆ |
| 三维建模 | ★☆☆☆☆ | ★★☆☆☆ | ★★★★☆ |
| 低功耗需求 | ★★☆☆☆ | ★☆☆☆☆ | ★★★☆☆ |
| 轻量化需求 | ★★★☆☆ | ★★☆☆☆ | ★★★★☆ |
| 低成本需求 | ★★★★☆ | ★★☆☆☆ | ★☆☆☆☆ |
6.6 综合评价
| 评价维度 | 方案一:旋转单线 | 方案二:多线固定(8 线) | 方案三:MEMS 振镜 |
|---|---|---|---|
| 性价比 | ★★★★☆ | ★★★☆☆ | ★★☆☆☆ |
| 易用性 | ★★★☆☆ | ★★★★☆ | ★★☆☆☆ |
| 性能表现 | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ |
| 扩展性 | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ |
| 推荐指数 | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ |
七、ROS2 集成方案
7.1 ROS2 简介
ROS2(Robot Operating System 2)是新一代的机器人操作系统,它在 ROS1 的基础上进行了全面改进,具有更好的实时性、可靠性和跨平台性。对于无人机应用,ROS2 提供了丰富的功能包,包括导航、控制、感知等,可以大大简化开发流程。
7.2 激光雷达 ROS2 驱动开发
为 DIY 激光雷达开发 ROS2 驱动主要包括以下步骤:
- 创建 ROS2 功能包
- 定义消息接口
- 实现传感器数据读取
- 数据转换为 ROS2 消息格式
- 发布点云数据
- 实现参数配置和动态重配置
- 添加诊断信息
7.2.1 创建功能包
bash
# 创建工作空间
mkdir -p ~/lidar_ros2_ws/src
cd ~/lidar_ros2_ws/src
# 创建功能包
ros2 pkg create --build-type ament_cmake diy_lidar_driver --dependencies rclcpp sensor_msgs tf2 tf2_ros
7.2.2 消息接口
激光雷达通常使用sensor_msgs/msg/PointCloud2消息类型发布点云数据:
cpp
运行
#include "sensor_msgs/msg/point_cloud2.hpp"
#include "sensor_msgs/point_cloud2_iterator.hpp"
// 创建PointCloud2消息
auto point_cloud_msg = std::make_unique<sensor_msgs::msg::PointCloud2>();
point_cloud_msg->header.frame_id = "lidar_frame";
point_cloud_msg->header.stamp = this->get_clock()->now();
// 设置点云消息的字段
sensor_msgs::PointCloud2Modifier modifier(*point_cloud_msg);
modifier.setPointCloud2FieldsByString(2, "xyz", "intensity");
modifier.resize(point_cloud.size());
// 填充点云数据
sensor_msgs::PointCloud2Iterator<float> iter_x(*point_cloud_msg, "x");
sensor_msgs::PointCloud2Iterator<float> iter_y(*point_cloud_msg, "y");
sensor_msgs::PointCloud2Iterator<float> iter_z(*point_cloud_msg, "z");
sensor_msgs::PointCloud2Iterator<float> iter_intensity(*point_cloud_msg, "intensity");
for (size_t i = 0; i < point_cloud.size(); ++i, ++iter_x, ++iter_y, ++iter_z, ++iter_intensity) {
*iter_x = point_cloud[i].x;
*iter_y = point_cloud[i].y;
*iter_z = point_cloud[i].z;
*iter_intensity = point_cloud[i].intensity;
}
7.2.3 串口通信实现
大多数 DIY 激光雷达通过串口与无人机通信:
cpp
运行
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
// 打开串口
int open_serial(const std::string& port, int baud_rate) {
int fd = open(port.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
RCLCPP_ERROR(this->get_logger(), "Failed to open serial port: %s", port.c_str());
return -1;
}
// 配置串口
struct termios tty;
memset(&tty, 0, sizeof(tty));
if (tcgetattr(fd, &tty) != 0) {
RCLCPP_ERROR(this->get_logger(), "Failed to get serial attributes");
close(fd);
return -1;
}
// 设置波特率
speed_t speed = B0;
switch (baud_rate) {
case 9600: speed = B9600; break;
case 115200: speed = B115200; break;
// 其他波特率...
default:
RCLCPP_ERROR(this->get_logger(), "Unsupported baud rate: %d", baud_rate);
close(fd);
return -1;
}
cfsetospeed(&tty, speed);
cfsetispeed(&tty, speed);
// 配置数据格式: 8位数据, 无奇偶校验, 1位停止位
tty.c_cflag &= ~PARENB;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
// 禁用硬件流控制
tty.c_cflag &= ~CRTSCTS;
// 启用接收器, 忽略调制解调器控制线
tty.c_cflag |= (CLOCAL | CREAD);
// 禁用软件流控制
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
// 原始输入
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 原始输出
tty.c_oflag &= ~OPOST;
// 设置读取超时
tty.c_cc[VTIME] = 10; // 1秒超时
tty.c_cc[VMIN] = 0;
// 应用配置
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
RCLCPP_ERROR(this->get_logger(), "Failed to set serial attributes");
close(fd);
return -1;
}
return fd;
}
7.2.4 驱动节点主循环
cpp
运行
void LidarDriverNode::main_loop() {
// 打开串口
int serial_fd = open_serial(serial_port_, baud_rate_);
if (serial_fd == -1) {
rclcpp::shutdown();
return;
}
// 创建点云发布者
auto publisher = this->create_publisher<sensor_msgs::msg::PointCloud2>("points", 10);
// 主循环
while (rclcpp::ok()) {
// 读取激光雷达数据
std::vector<Point> point_cloud = read_lidar_data(serial_fd);
// 如果有数据,发布点云
if (!point_cloud.empty()) {
auto msg = convert_to_pointcloud2(point_cloud);
publisher->publish(std::move(msg));
}
rclcpp::spin_some(shared_from_this());
}
// 关闭串口
close(serial_fd);
}
7.3 坐标变换配置
在 ROS2 中,需要正确配置传感器的坐标变换(TF):
- 创建 URDF 文件描述激光雷达与无人机的连接关系
- 使用 tf2 发布坐标变换
7.3.1 URDF 配置示例
xml
<?xml version="1.0"?>
<robot name="drone_with_lidar">
<!-- 无人机基础_link -->
<link name="base_link">
<visual>
<geometry>
<box size="0.2 0.2 0.05"/>
</geometry>
</visual>
</link>
<!-- 激光雷达_link -->
<link name="lidar_link">
<visual>
<geometry>
<cylinder length="0.05" radius="0.03"/>
</geometry>
<origin xyz="0 0 0" rpy="0 0 0"/>
<material name="red">
<color rgba="1 0 0 1"/>
</material>
</visual>
</link>
<!-- 激光雷达与无人机的连接 -->
<joint name="lidar_joint" type="fixed">
<parent link="base_link"/>
<child link="lidar_link"/>
<!-- 激光雷达安装在无人机中心上方5cm处 -->
<origin xyz="0 0 0.05" rpy="0 0 0"/>
</joint>
</robot>
7.3.2 静态坐标变换发布
可以使用static_transform_publisher发布静态坐标变换:
bash
ros2 run tf2_ros static_transform_publisher 0 0 0.05 0 0 0 base_link lidar_link
或者在 launch 文件中配置:
python
运行
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='tf2_ros',
executable='static_transform_publisher',
arguments=['0', '0', '0.05', '0', '0', '0', 'base_link', 'lidar_link']
)
])
7.4 与导航栈集成
将 DIY 激光雷达与 ROS2 导航栈集成,实现无人机自主导航:
- 安装 ROS2 导航相关功能包
- 配置导航参数
- 启动导航节点
7.4.1 安装导航功能包
bash
sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup
7.4.2 配置导航参数
主要配置文件包括:
params.yaml:导航栈参数配置behavior_tree.xml:行为树配置map.yaml:地图配置(如果使用已有地图)
关键参数配置示例(params.yaml):
yaml
amcl:
ros__parameters:
use_sim_time: False
alpha1: 0.2
alpha2: 0.2
alpha3: 0.2
alpha4: 0.2
alpha5: 0.2
base_frame_id: "base_link"
beam_skip_distance: 0.5
beam_skip_error_threshold: 0.9
beam_skip_threshold: 0.3
do_beamskip: false
global_frame_id: "map"
lambda_short: 0.1
laser_likelihood_max_dist: 2.0
laser_max_range: 10.0
laser_min_range: 0.1
laser_model_type: "likelihood_field"
max_beams: 60
max_particles: 2000
min_particles: 500
odom_frame_id: "odom"
pf_err: 0.05
pf_z: 0.99
recovery_alpha_fast: 0.0
recovery_alpha_slow: 0.0
resample_interval: 1
robot_model_type: "differential"
save_pose_rate: 0.5
sigma_hit: 0.2
tf_broadcast: true
transform_tolerance: 0.1
update_min_a: 0.2
update_min_d: 0.25
z_hit: 0.5
z_max: 0.05
z_rand: 0.5
z_short: 0.05
bt_navigator:
ros__parameters:
use_sim_time: False
global_frame: map
robot_base_frame: base_link
odom_topic: /odom
bt_loop_duration: 10
default_bt_xml_filename: "navigate_w_replanning_and_recovery.xml"
plugin_lib_names:
- nav2_compute_path_to_pose_action_bt_node
- nav2_follow_path_action_bt_node
# ... 其他插件
planner_server:
ros__parameters:
expected_planner_frequency: 20.0
use_sim_time: False
planner_plugins: ["GridBased"]
GridBased:
plugin: "nav2_navfn_planner/NavfnPlanner"
tolerance: 0.5
use_astar: false
allow_unknown: true
controller_server:
ros__parameters:
use_sim_time: False
controller_frequency: 20.0
min_x_velocity_threshold: 0.001
min_y_velocity_threshold: 0.001
min_theta_velocity_threshold: 0.001
failure_tolerance: 0.3
progress_checker_plugin: "progress_checker"
goal_checker_plugin: "goal_checker"
controller_plugins: ["FollowPath"]
# ... 其他控制器参数
# ... 其他节点参数
7.4.3 启动导航系统
创建 launch 文件(drone_navigation_launch.py):
python
运行
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
# 获取导航功能包路径
nav2_dir = get_package_share_directory('nav2_bringup')
# 导航参数文件路径
params_file = os.path.join(
get_package_share_directory('diy_lidar_driver'),
'params',
'nav2_params.yaml'
)
# 激光雷达驱动节点
lidar_driver_node = Node(
package='diy_lidar_driver',
executable='diy_lidar_driver_node',
name='diy_lidar_driver',
parameters=[{
'serial_port': '/dev/ttyUSB0',
'baud_rate': 115200,
'frame_id': 'lidar_link'
}],
output='screen'
)
# 导航系统
nav2_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(os.path.join(nav2_dir, 'launch', 'bringup_launch.py')),
launch_arguments={
'map': 'false',
'use_sim_time': 'false',
'params_file': params_file
}.items()
)
return LaunchDescription([
lidar_driver_node,
nav2_launch
])
启动导航系统:
bash
ros2 launch diy_lidar_driver drone_navigation_launch.py
7.5 数据可视化与调试
ROS2 提供了多种工具用于数据可视化与调试:
- RViz2:可视化点云数据、机器人模型和导航信息
- ros2 bag:记录和回放传感器数据
- rqt:调试和监控 ROS2 节点
7.5.1 使用 RViz2 可视化
bash
ros2 run rviz2 rviz2
在 RViz2 中配置:
- 添加
PointCloud2显示,话题设置为/points - 添加
TF显示,查看坐标变换 - 添加
LaserScan显示(如果支持) - 添加
Map显示,查看构建的地图
7.5.2 记录和回放数据
bash
# 记录激光雷达数据
ros2 bag record /points /tf
# 回放数据
ros2 bag play <bag文件>
7.5.3 使用 rqt 监控
bash
ros2 run rqt_gui rqt_gui
在 rqt 中可以:
- 查看节点之间的连接关系
- 监控话题数据
- 查看节点日志
- 动态调整参数
八、实际应用案例
8.1 室内自主导航无人机
使用方案二(8 线固定激光雷达)构建的室内自主导航无人机系统:
-
硬件配置:
- 四轴无人机机架(轴距 250mm)
- 飞控:PX4 或 ArduPilot
- 激光雷达:8 线固定布局 DIY 激光雷达
- 计算单元:NVIDIA Jetson Nano
- 电池:11.1V 2200mAh 锂电池
-
软件配置:
- ROS2 Humble
- Nav2 导航栈
- 自定义激光雷达驱动
- 飞控接口节点(MAVROS)
-
功能实现:
- 室内无 GPS 环境下的自主定位
- 避障功能(前方、下方障碍物检测)
- 自主路径规划
- 定点悬停与自主降落
-
性能指标:
- 定位精度:±30cm
- 最大避障距离:4m
- 导航速度:0.5-1m/s
- 续航时间:8-10 分钟
8.2 低成本三维建模无人机
使用方案三(MEMS 振镜激光雷达)构建的低成本三维建模无人机:
-
硬件配置:
- 六轴无人机机架(轴距 350mm)
- 飞控:Pixhawk 4
- 激光雷达:MEMS 振镜 DIY 激光雷达
- 计算单元:Raspberry Pi 4
- 辅助相机:1200 万像素 USB 相机
- 电池:14.8V 5000mAh 锂电池
-
软件配置:
- ROS2 Humble
- 点云处理库(PCL)
- 三维重建算法
- 激光雷达 - 相机标定工具
-
功能实现:
- 室内场景三维点云采集
- 点云与图像融合
- 三维模型构建
- 模型导出(PLY/OBJ 格式)
-
性能指标:
- 点云分辨率:5cm
- 建模精度:±5cm
- 单架次建模范围:200-300㎡
- 续航时间:15-20 分钟
九、挑战与未来改进方向
9.1 当前方案的局限性
- 测距范围有限:低成本激光模块的有效测距通常在 10m 以内
- 环境适应性差:在强光、雨天等环境下性能下降明显
- 点云密度不足:相比商用激光雷达,DIY 方案的点云密度较低
- 功耗与重量平衡:在保证性能的同时难以进一步降低功耗和重量
- 校准复杂度高:尤其是多线方案,需要精确校准各模块的角度
9.2 技术改进方向
-
传感器融合:
- 结合视觉相机,实现稠密三维重建
- 融合 IMU 数据,补偿运动模糊
- 集成超声波传感器,补充近距离测量
-
算法优化:
- 实现基于深度学习的点云去噪和补全
- 开发动态障碍物检测算法
- 优化点云配准和 SLAM 算法
-
硬件创新:
- 探索新型低成本激光发射器和接收器
- 开发更高效的 MEMS 振镜驱动方案
- 研究低功耗设计,延长续航时间
-
系统集成:
- 开发更紧凑的结构设计
- 优化散热方案,提高稳定性
- 实现即插即用的标准化接口
十、总结
本文详细介绍了三种适用于无人机的低成本 DIY 激光雷达方案,从硬件选型、结构设计到固件开发进行了全面解析,并通过表格形式多维度对比了各方案的成本、性能与适用场景。我们还学习了如何将这些 DIY 激光雷达集成到 ROS2 系统中,实现与导航栈的无缝对接。
旋转单线方案成本最低,适合入门学习和对全向扫描有需求的场景;多线固定方案结构简单可靠,适合避障和近距离环境感知;MEMS 振镜方案性能最佳,适合对精度和点云密度有较高要求的应用。
通过 DIY 激光雷达,不仅可以大幅降低无人机环境感知系统的成本,还能深入理解激光雷达的工作原理,为进一步的技术创新打下基础。随着开源硬件和软件的不断发展,低成本激光雷达的性能将不断提升,为无人机应用开辟更广阔的空间。
希望本文能为无人机爱好者、机器人开发者提供有价值的参考,鼓励更多人参与到低成本传感器的开发和应用中来,推动无人机技术的普及和创新。
附录:常用工具与资源
A.1 硬件开发工具
- 3D 建模软件:FreeCAD, Fusion 360
- 电路设计软件:KiCad, Eagle
- 编程工具:Arduino IDE, PlatformIO, STM32CubeIDE
A.2 开源项目与库
- 激光雷达驱动:https://github.com/Slamtec/rplidar_ros
- 点云处理:Point Cloud Library | The Point Cloud Library (PCL) is a standalone, large scale, open project for 2D/3D image and point cloud processing.
- ROS2 导航:https://navigation.ros.org/
- 无人机控制:https://github.com/mavlink/mavros
A.3 学习资源
- ROS2 官方文档:ROS 2 Documentation — ROS 2 Documentation: Humble documentation
- 激光雷达原理:https://www.cs.cmu.edu/~./16385/s17/Slides/14.1_LiDAR.pdf
- 无人机 SLAM:https://github.com/ethz-asl/kalibr
A.4 零部件采购渠道
- 电子元件:Digikey, Mouser, 立创商城
- 机械零件:淘宝,阿里巴巴,McMaster-Carr
- 3D 打印服务:Shapeways, 淘宝 3D 打印服务
通过这些工具和资源,你可以更高效地开发和调试自己的 DIY 激光雷达系统,加速项目进展。

1073

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



