你手头那辆阈值判断的小车是不是也有这毛病:碰上墙壁就猛打方向,走着走着突然摇头晃脑,遇到墙角直接卡住原地转圈。我前阵子搭了一辆基于 Arduino 和三路传感器的 BLDC 模糊避障小车,把决策从“硬开关”换成了“软油门”之后,整台车的动作一下子顺滑了很多。这版控制器做起来并不复杂,核心思路就是用三向感知代替单点探测,用模糊推理代替多层 if-else,再加上无刷电机的高响应和顺滑输出,让避障过程接近真人驾驶的直觉。如果你已经玩过普通直流电机小车,但对 BLDC 驱动和模糊控制还有点陌生,这篇内容适合你,从选件到接线,从规则表到代码框架,再到实际跑车时踩过的坑,我都会摊开来讲。
1. 先把方案定下来:为什么是三向感知加模糊逻辑
做避障机器人,方案选型这一步几乎决定了后面的手感。我用过红外单点避障,也用过好几路超声波的阈值逻辑,最后换成“三向感知 + 模糊逻辑”的组合,其实是在踩了一堆坑之后才确定的。
1.1 阈值判断的痛点在哪里
传统做法是每个方向设一个阈值,比如超声波测到前方距离小于 30 厘米就往右转。听起来很清晰,实际上问题不少:传感器读数本身有噪声,同样的墙有时测到 31 厘米,有时测到 28 厘米,控制器就会在“转”和“不转”之间反复横跳,小车看着就像癫痫发作一样。传感器分布少一点,小车只能对正前方作出反应,侧面的墙扫到的时候基本已经撞上了。传感器分布多一点也不是不行,但多路 if-else 的组合会迅速爆炸,左近右远、左远右近、三路都近、三路都远,每个分支都要单独处理,写着写着连自己都分不清优先级了。
阈值判断还有一个隐藏毛病:它输出的转向指令是离散的,要么全速转,要么不转。实际地面摩擦、电机响应、重心偏移都会让小车实际轨迹和预想的不一样,离散指令很容易造成转向过度或者转向不足。
1.2 模糊逻辑给控制器带来的本质变化
模糊逻辑处理这类问题的方式更接近人的判断。想象你站在走廊里,你并不会精确数出“右侧墙是 37.2 厘米”,而是有一个大概的印象:“右边有点近,不过还能走”“前方挺宽敞”。模糊控制器就是把这种模糊语言量化成数学表达:输入距离被划分为“近 / 中 / 远”等模糊集合,再通过一组规则推导出连续输出。
这样做的好处非常明显:输出是平滑变化的,距离越近转向力度越大,而不是直接打满。传感器读数的小幅抖动只会让输出在小范围内波动,不会引起剧烈动作。多路输入也可以通过规则表统一管理,规则之间还能相互叠加。我之前那辆小车用阈值逻辑时,在墙边经常出现“撞一下才反应”的情况,换成模糊逻辑后,还没碰到墙,转向修正值就已经在慢慢增大了,这条曲线一样的变化过程减免了很多硬碰硬。
1.3 三向感知布局到底怎么选
三向感知指的是左、中、右三个方向同时探测。这个数量级不是拍脑袋定的:两个方向只能判断“偏向哪边”,无法识别正前方的障碍;四个方向会显著增加成本、接线和规则数量,但对基础避障场景帮助有限。三向刚好覆盖了差速底盘最常遇到的三种局面——前方有障碍、侧前方有障碍、某一侧贴着墙走。
在实际布局中,左、中、右三路传感器通常安装在底盘前缘,中间那路朝正前,左右两路可以略微朝外偏 15 到 30 度,这样能比单纯并排安装多出一点侧向视野。传感器之间的间距要大于障碍物最小尺寸的一半,不然障碍物夹在两路探测盲区之间,会出现“明明撞上了但三路都说安全”的尴尬情况。我的第二轮样机把左右传感器距离中线拉开到了 15 厘米以上,墙角漏检的问题改善特别明显。
在继续往下讲接线和驱动之前,我想先说明一下这套方案的定位:它适合做功能原型验证,也适合作为学习模糊控制算法的载体,但不适合在完全未知、随机出现障碍物的复杂环境中长期满载运行。三向感知天然存在侧后盲区,这是物理布局决定的天花板,模糊逻辑解决的是控制平滑度问题,不是传感器覆盖问题。
2. 硬件这一关:BLDC电机怎么接线、怎么供电
标题里既然提到了 BLDC,这部分绕不过去。无刷直流电机和普通直流电机最大的区别是:你不能直接把正负极接上去让它转,必须通过电子调速器换相驱动。很多第一次接触无刷电机的人在这里就被劝退了,其实搞明白原理之后,接线比想象中简单。
2.1 为什么选无刷电机而不是有刷电机
我在比较早期版本的小车上用过有刷减速电机,便宜、控制简单。但用到第二版之后发现两个问题:第一,有刷电机的碳刷和换向器磨损很快,长时间让小车来回转圈,几个月后手感就明显变肉了;第二,有刷电机的响应存在一定迟钝,从刹车到反向转动需要时间,对频繁启停的避障场景来说不太舒服。无刷电机没有碳刷,靠电子换相器驱动,转速上限高、响应快、寿命长,扭矩曲线也更线性,对于避障这类需要频繁调节速度的场景很合适。
代价就是必须引入电子调速器。Arduino 输出 PWM 信号给电调,电调内部根据 PWM 脉宽换算成油门开度,再去控制三相桥的开关时序。Arduino 本身不直接接触电机动力线,这是硬件安全上的一个关键分层。
2.2 电调与 PWM 信号的基本关系
电调一般遵守航模领域常用的油门信号协议:PWM 周期固定为 50Hz(也就是 20ms 周期),脉冲宽度范围大约在 1000 到 2000 微秒之间。一个很典型的对应关系是:1000 微秒对应油门最小(通常是停转),2000 微秒对应油门最大,1500 微秒左右对应中速。不同品牌电调的取值范围会有差别,所以首次使用必须做油门行程校准,这一步很关键,我会在后面的实操章节详细讲。
Arduino 端直接操作这个信号的想法通常是用 Servo 库,虽然名字里带 Servo,但它实际上发出的是标准 50Hz 周期、1 到 2ms 脉宽的可变脉冲,正好能驱动大部分电调。写起来就是esc.writeMicroseconds(1000)这种形式,看起来很像控制舵机,实际上是在直接给电调发油门指令。
接线方面,无刷电机三相线 A、B、C 接到电调的三个输出端,顺序接反只会导致电机反转,不会烧坏,但要注意的是电调的电源侧分为主电源输入和信号线中的控制电源。常见的电调会自带 BEC,能从主电源降压出一路 5V 左右的控制电源给接收机或主控使用。这很方便,但也带来了供电风险,我下面细说。
2.3 传感器选型:超声波和红外的取舍
三向感知的传感器,我建议优先考虑超声波。红外测距模块胜在便宜、响应快,但容易受环境光干扰,在向阳的窗边或反光的白墙上读数会飘。超声波不受光线影响,测量距离范围也更大,适合室内家居环境。
要注意的是不同型号的超声波传感器测距角度不一样。有些传感器波束角较大,左右两路会互相串扰,两个模块同时发送超声波时可能收到彼此的反射波。解决办法是让三路传感器分时触发,比如依次间隔 25 到 30 毫秒,或者在安装时让左右两路探头之间保持一定夹角。我实际测试过,测距周期调到 90 毫秒左右时,三路轮流触发依然能保证小车在正常速度下及时响应,不会因为测距刷新太慢而撞上障碍物。
2.4 供电系统怎么规划才不容易翻车
供电是整个项目中最容易被低估的一环。无刷电机启动瞬间电流很大,如果主控和传感器共用一路电源,电调大油门时会把电压拉低,Arduino 直接就复位了。我见过不止一次出现“小车开起来才两秒就重启”的情况,排查到最后都是供电问题。
比较稳妥的方案是分成两路:
| 部件 | 电源来源 | 说明 |
|---|---|---|
| BLDC 电机 + 电调 | 动力电池直接接入电调主电源端 | 满足大功率需求 |
| Arduino + 传感器 | 电调 BEC 输出或独立稳压模块 | 单独供电,避免电机冲击 |
| 信号线 | Arduino 输出 PWM 给电调信号口 | 必须和电调控制端共地 |
共地原则很重要,信号线上的 PWM 参考的是 Arduino 的地,如果 Arduino 的地和电调的地之间没有连接,信号会变得乱七八糟。市面上有些电调自带 BEC 输出,很多人直接把 Arduino 的 5V 接上去用,这在电流需求小时没问题,一旦接了三路超声波模块再加上显示屏,BEC 的输出电流就可能不够。为避免这种模棱两可的状况,我给传感器和主控单独加了一个稳压降压模块,直接从电池取电降压到 5V 或者 3.3V,动力和控制彻底分开,实测稳定性提升了好几个档次。
3. 模糊避障的算法实现:三段式推理怎么写进 Arduino
这一部分是全篇的核心。模糊控制器的框架不难理解,真正难的是怎样把它写进 Arduino 的有限资源里,以及怎样调出合理的行为。
3.1 模糊控制器的基本结构
模糊控制器通常分四个步骤:模糊化、规则推理、去模糊化、输出映射。用一个生活化的类比来解释:你在厨房炒菜,看到火力有点大(输入),你心里想“那我把火调小一点”(规则),然后你手上的动作是“微微关小旋钮”(输出)。模糊控制器做的就是同一件事,只不过把“有点大”“微微调小”这些模糊词变成了数学语言。
在本项目里,输入是三路传感器的距离值,输出是两个量:转向修正量和目标速度值。转向修正量决定小车向左偏还是向右偏,目标速度决定油门开度。方向修正量和目标速度经过合成计算,再映射到左右两个无刷电机的 PWM 信号上,就完成了最终控制。
3.2 输入量的模糊化与隶属度函数
距离值是一个精确的数值,比如 47 厘米。模糊化就是要把它转换成对多个模糊集合的隶属程度。我先把每路距离划分为三个集合:近、中、远。对 47 厘米这个值,它可能对“中”集合有 0.6 的隶属度,对“远”集合有 0.4 的隶属度,对“近”集合则是 0。隶属度函数的形态我选了梯形和三角形。
对应的数学表达,可以用一个三角形隶属度函数函数来实现:
float tri(float x, float a, float b, float c) { if (x <= a || x >= c) return 0.0; if (x <= b) return (x - a) / (b - a); return (c - x) / (c - b); }其中a、b、c是拐点参数。比如“近”的三角形取 (5, 15, 35),表示 5 厘米以下完全属于“近”,35 厘米以上完全不属于“近”。这些参数是调车的关键,我在后面路测环节会结合实际案例说明怎么改。
3.3 规则库设计:27 条规则怎么排
三路输入,每路有近、中、远三档,理论上规则库有 3 的 3 次方,也就是 27 条。把 27 条全部一条不漏地写出来是可以的,但调参时很容易看花眼,所以我倾向于先用少量核心规则定框架,再根据实测补充细分规则。核心规则大概长这个样子:
| 输入状态(左 / 中 / 右) | 转向修正 | 速度 |
|---|---|---|
| 远 / 远 / 远 | 保持当前方向 | 快 |
| 远 / 近 / 远 | 保持方向,准备减速 | 慢 |
| 近 / 远 / 远 | 向右转 | 中 |
| 远 / 远 / 近 | 向左转 | 中 |
| 近 / 近 / 远 | 向右转,且转得更多 | 慢 |
| 远 / 近 / 近 | 向左转,且转得更多 | 慢 |
| 近 / 近 / 近 | 保持方向,急减速 | 极慢 |
| 近 / 中 / 远 | 向右偏转,力度中等 | 中慢 |
| 远 / 中 / 近 | 向左偏转,力度中等 | 中慢 |
注意表中的“转向修正”实际上是一个数值区间,我后面会在去模糊化环节把它转成具体数值。例如“向右转”可能对应输出 0.7,“向右偏转,力度中等”可能对应 0.55,这个精细度是阈值逻辑完全给不了的。
实际代码中,我会把规则存进一个结构体数组。每条规则包含三路输入的预期档位、对应的转向修正值和速度权重。这样比一大串if else清晰得多:
struct FuzzyRule { int leftNear; // 0 = 远, 1 = 中, 2 = 近 int midNear; int rightNear; float turn; // 0 ~ 1,中心 0.5 表示不偏转 float speed; // 0 ~ 1,油门开度 }; FuzzyRule rules[] = { {0, 0, 0, 0.50, 0.85}, {0, 2, 0, 0.50, 0.30}, {2, 0, 0, 0.35, 0.55}, {0, 0, 2, 0.65, 0.55}, {2, 2, 0, 0.20, 0.25}, {0, 2, 2, 0.80, 0.25}, {2, 2, 2, 0.50, 0.10}, {2, 1, 0, 0.40, 0.45}, {0, 1, 2, 0.60, 0.45}, };这里有 9 条核心规则,虽然不完整,但对于基础避障已经够用了。想要更完善的行为,再在数组里补充剩余规则即可。
3.4 去模糊化与差速输出映射
每条规则的输出不是一个确定值,而是一个被“激活度”调制的候选值。激活度取决于当前输入对这条规则前件的满足程度,用模糊集合理论里最常用的最小值运算:
float activation = min(degLeft, min(degMid, degRight));也就是说,如果左侧对“近”的隶属度是 0.7,前方对“远”的是 0.8,右侧对“远”的是 0.8,那么规则“左近中远右远”的激活度就是 0.7。之后我用加权平均法把多条规则给出的候选方向整合起来,得到最终的转向修正值:
float sumActivation = 0, turnSum = 0, speedSum = 0; for (int i = 0; i < ruleCount; i++) { float act = computeActivation(i); sumActivation += act; turnSum += act * rules[i].turn; speedSum += act * rules[i].speed; } float turnCmd = turnSum / sumActivation; float speedCmd = speedSum / sumActivation;这种加权平均方式的好处是输出一定位于所有规则输出的范围之内,不会出现比如“速度 0.85 + 0.25 平均后变成 0.8”这种不合理的突变,适合做成平滑的行为曲线。
最终这两个值还要映射成左右轮的速度信号。我用一个差速底盘的经典方案:
float steer = (turnCmd - 0.5) * 2.0; // 归一化到 -1 ~ 1 float leftSpeed = speedCmd + steer * turnStrength; float rightSpeed = speedCmd - steer * turnStrength; leftPWM = constrain(map(leftSpeed, 0, 1, 1000, 2000), 1050, 2000); rightPWM = constrain(map(rightSpeed, 0, 1, 1000, 2000), 1050, 2000); escL.writeMicroseconds(leftPWM); escR.writeMicroseconds(rightPWM);turnStrength用于控制转向的力度,取 0 到 0.5 之间比较合理。如果把speedCmd乘了一个比较大的值时,左右轮差速会拉得很大,小车的转向会很贼,一碰墙就甩头;取值过小则反应迟钝,需要贴着墙才开始转。我通常在路测时先用 0.25,之后细调。
3.5 完整的 Arduino 控制循环框架
综合以上逻辑,Arduino 主循环大概长这样:
void loop() { readDistances(); // 分时读取三路超声波 fuzzifyDistances(); // 计算每路距离的隶属度 evaluateRules(); // 计算所有规则激活度与加权输出 computeSpeedAndTurn(); // 得到速度指令和转向修正 mapToDifferentialDrive(); // 映射左右轮脉宽 delay(50); // 控制循环周期 }这里我用delay(50)把循环周期控制在 20Hz 左右。这个频率对基础避障完全足够,而且能避免超声波传感器分时触发时互相干扰。模糊控制本身的运算量不大,Arduino 处理这几十条规则毫无压力,实测循环耗时也在几毫秒以内,不会有性能瓶颈。
如果你想让避障决策更细腻,可以考虑把每路输入的模糊档位从三档增加到五档,规则数会从 27 变成 243 条,虽然存储和计算压力依然能承受,但调参难度会明显上升。新手阶段先用三档,把行为调顺以后再去加细节,是我更推荐的路子。
4. 实际跑车记录:校准、标定和调参全过程
代码写完只是第一步,真正的挑战在通电和路测。这个项目从第一版能转,到第三版跑起来“像是一个会开车的人”,中间反复调了很多次。下面我把整个实操流程写一遍,你照着做可以少走弯路。
4.1 电调油门行程校准流程
电调装上之后第一次通电,一定要做油门行程校准。跳过这一步直接发 PWM,有些电调不能进入正常运转状态,有些则会把 1500 微秒的中间值理解成最大油门,一通电就全速转,非常危险。
我的校准步骤是这样的:先把 Arduino 程序写成上电后立刻输出一个最大油门信号,比如 2000 微秒,持续两秒,然后再降到一个最小值,比如 1000 微秒。Arduino 上电时保持油门信号为最大,然后给电调通电,电调检测到最高油门时会发出提示音,听到提示音后把油门信号降到最低,电调再次发出确认音,这时油门行程就校准完毕了。
完成后,程序里会让油门信号保持在 1000 微秒左右,也就是刹车或怠速状态。这段校准流程里最需要注意的是不要在上电瞬间输出一个低油门脉冲,然后一步步增大油门,因为很多电调要求先看到上边界再看到下边界,顺序反了,校准结果会错乱。
如果你的电调说明书有明确校准步骤,以说明书为准。不同品牌的校准节奏略有差别,但“先高后低”这个基本逻辑大同小异。
4.2 超声波测距的滤波与标定
超声波传感器最影响模糊控制器稳定性的地方在于数据偶发跳变。比如一面平整的墙,连续测十次,可能会有一两次突然跳到很远或很近,直接喂给模糊规则会让小车突然做出一个奇怪动作。我处理的办法是用滑动窗口中值滤波,而不是简单平均,因为中值滤波对异常值有很强的抵抗能力。
int readFilteredDistance(int trigPin, int echoPin) { int samples[5]; for (int i = 0; i < 5; i++) { samples[i] = readUltrasonicOnce(trigPin, echoPin); delay(10); } for (int i = 0; i < 4; i++) { for (int j = i + 1; j < 5; j++) { if (samples[j] < samples[i]) { int temp = samples[i]; samples[i] = samples[j]; samples[j] = temp; } } } return samples[2]; }五个样本中取中间值,每次读取大约需要 50 到 60 毫秒,三路传感器轮流读,整个循环周期差不多在 200 到 250 毫秒左右。如果觉得刷新率太低,可以把采样数降到三,或者只在某个方向出现“近”值时才启用中值滤波,其他情况下直接读单次结果。
标定这一步也不能省。传感器装好后,先用尺子量出真实距离,再对比串口打印的读数,在代码里做修正。部分超声波模块在近距离有 2 到 3 厘米的死区,在远距离会偏高一点,这些误差在模糊控制器里不会造成严重问题,因为模糊逻辑本身就是容忍不确定性的,但至少要让“50 厘米真的对应 50 厘米”,否则隶属度函数的拐点设置就失去物理意义了。
4.3 路测场景与调参记录
完成基础功能后,我端着小车到几个典型场景去试跑,包括客厅走廊、墙角区域、茶几周围和椅子腿密集区域。每个场景的表现都不同,调整方向也不一样。
第一次路测暴露的问题是小车总是在距离墙面还有不到 20 厘米才开始急转,动作幅度大。我打开串口监视器,连着观察了几次测距数据,发现前方传感器在 30 厘米以内时,“近”的隶属度才升到 0.6 以上,而我的规则在这种状态下已经开始输出大幅度转向了。也就是说问题出在隶属度函数的分界点,而不是规则本身。我把“近”集合的 c 点从 35 改到 50,“中”集合的 b 点相应右移,再上电实测,小车在距离墙面 40 到 45 厘米时就实现了顺滑转向,这个动作比之前柔和了不少。
第二个场景很典型:小车沿着走廊右侧贴墙走,左侧很开阔,但它的方向修正总是左右摆动,不断试探。查看串口数据发现,左右两侧距离接近“中”界线附近,微弱的传感器抖动反复改变激活度,导致控制器在左右转向之间摇摆。解决办法是把输出加一层平滑处理,也就是对转向修正值做一阶低通滤波:
smoothedTurn += (turnCmd - smoothedTurn) * 0.3;0.3 这个系数代表每次循环只向目标值靠近三成,相当于给输出加了惯性。实测摆动幅度明显减小,虽然牺牲了一点点响应速度,但整体行为自然了很多。
第三个场景是墙角,程序判断起来容易犹豫。后来发现三路传感器同时出现“近”值时,规则会输出急减速,但小车速度已经很低,反而卡在原地。我把“三近”的规则输出改成“保持极慢速度 + 原地旋转方向修正”,同时提高了左右电机的差速比例,小车就能在墙角从前冲状态稳定地掉头离开。这个调整在规则表里就是一行的事,但观察和判断过程花掉了我几乎一个晚上。
4.4 调参的稳妥顺序
我自己摸索出来的习惯是:先调速度,再调转向,最后调敏感度。第一版把小车速度设得很低,这样即使转向逻辑乱七八糟,撞墙的冲击力也不大。转向力度用较小的turnStrength慢慢加,直到小车在直行状态下能用一次流畅的转弯躲过障碍物为止。最后才去改隶属度函数的拐点,也就是响应敏感度。
这个顺序能有效减少变量。如果你一上来就调转向和敏感度,小车跑起来飞快,一旦方向判断有问题,很容易翻车或者撞坏传感器支架。
5. 避坑清单与问题排查速查
项目做到后面,我整理了实际遇到过的问题以及对应的排查思路,做成一张速查表。这张表能帮你省下大量排错时间。
| 现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| 电调上电无提示音 | 电源未接通、接线松动、共地缺失 | 万用表量电调输入电压 | 重新焊接电源线并检查地线通路 |
| 电机上电后抖动但不转 | 油门行程校准未完成或 PWM 频率不符 | 用逻辑分析仪或示波器查看脉宽 | 重新做油门行程校准,确认周期约 50Hz |
| 一上电小车就猛冲 | 初始化代码输出高油门 | 检查 setup 阶段脉宽 | 先输出 1000 微秒并延时等待电调自检 |
| 三路传感器读数全为固定值 | Echo 和 Trig 引脚接反或共地缺失 | 串口打印原始脉冲宽度 | 核对引脚,重新接线 |
| 避障动作太晚 | 距离开关点偏高 | 串口观察隶属度变化 | 将“近”集合的拐点向右调整 |
| 方向来回震荡 | 转向修正值过度放大或传感器串扰 | 查看转向输出曲线 | 降低转向力度,加低通滤波 |
| 小车突然重启 | 动力电源与控制电源共路,电压跌落 | 用示波器捕捉复位瞬间电压 | 独立供电或升级稳压模块 |
| 转向方向反了 | 左右轮映射相反 | 单侧电机测试 | 交换左右输出变量或电机相线 |
除了这张表,还有三个容易忽视的经验值得单独拿出来讲。
第一个是传感器安装角度问题。很多人在小车底盘前缘并排安装三路传感器,探头全部朝正前方。这种布局在墙壁完全垂直于行进方向时没问题,但遇到斜面或墙角,三路可能同时测不到障碍物,或者测出来的距离严重失真。建议中间一路朝正前,左右两路像人的余光一样向外侧偏 15 到 20 度,这样覆盖范围大得多,而且规则理解起来更直观。
第二个是传感器之间互相干扰。超声波传感器的波束角不是零,左路发射的声波可能被斜墙反射后进入中路传感器接收端,造成距离突变。三路轮流触发能解决一部分问题,但如果采集逻辑里有“同时触发”或“密集连发”,串扰会很明显。排查方法是把障碍物放在一个固定位置,然后分别让每路单独工作,对比同时工作时的读数差异,以此确认是否存在串扰。
第三个是模糊规则表的“死区”问题。如果规则覆盖不完整,某些输入组合没有任何规则被激活,或者所有规则激活度为零,那么去模糊化时除以零就会出现 NaN,输出直接乱掉。我在代码里加了防御机制:
if (sumActivation < 0.001) { turnCmd = 0.5; // 保持当前方向 speedCmd = 0.25; // 低速前进 }这个兜底分支让你永远不会有失控输出。事实证明,启动规则设计阶段的几条组合,容易被漏掉,这行代码救了小车好几次。
还有一个和代码无关但非常值得说的经验是:调试时要习惯用串口打印关键数据。我调试模糊控制器时总会打印当前三路距离、各条规则的激活度、最终转向修正值和速度值。这样每次小车有奇怪动作,我都能直接看出是哪条规则或者哪个隶属度函数在起作用,而不是靠肉眼猜。别看这些信息平时不起眼,真到现场排查时,效率差别非常大。
我个人在实际操作中最深刻的体会是:模糊控制器看起来比阈值判断复杂,但调参的过程反而比阈值逻辑轻松很多。阈值逻辑要改的是分散在各处的判断条件,一改就是一大串;模糊控制器只需要调整几个隶属度拐点或者规则表里的数值,改完立刻能看到行为连续变化,这种渐进式调参的反馈体验非常有成就感。如果条件允许,建议你在试跑时用纸箱和泡沫板搭几个固定的测试障碍物,这样每次改动都能在同一环境下对比前后差异,比满屋子乱跑要高效得多。后续如果你想让这辆小车更聪明,可以考虑把传感器数量增加到五个方向,或者把模糊输出接到通信模块上做远程控制,这些都是对现有框架的自然延伸。