编码器与里程计 — 从脉冲计数到 PI 控制¶
这一章讲什么: 全书算法密度的第一个高峰。链条是这样: 编码器数轮子转了多少(脉冲)→ 换算成走了多少毫米(里程)→ 用「误差 + PID 控制器」让每个轮子精确跟上目标速度(闭环)→ 于是「走 1 米、走直线、转 90°」从第 6 章的许愿变成可复现的动作。 附带两件工具:config store(参数存 SD 卡、网页上直接调)和运动学 (把「转多少度」翻译成「两个轮子各走多少毫米」)——第 12 章定位的地基全在这里。
1. 先看现象:一米就偏 20 厘米,问题到底出在哪¶
书里先做了一个残酷的实验:让机器人开环直行 1 米—— 「我的左边比右边领先了 20 厘米,误差还在增长。两个电机完全相同,很罕见。」7
第 6 章说「电机有差异,所以会跑偏」,本章给出机制层面的完整解释并治它:
开环(第 6 章): 发 0.8 → 左轮实际 0.75、右轮实际 0.68 → 没人知道,没人管 → 越偏越远
闭环(本章): 发「目标 180 mm/s」→ 编码器量出实际速度 → 差 = 误差 →
PID 把误差变成新的电机功率 → 两侧都被拉到 180 mm/s → 直线
图说:开环发的是「希望」,闭环发的是「目标 + 纠偏」。这一章就是把整套闭环搭出来。
2. 顶层全景:六级台阶¶
① 硬件 磁编码盘(N/S 交替)+ 一对霍尔传感器 → 正交脉冲,可判方向
② 计数 服务发布 sensors/encoders/data(左右计数 / 圈数 / 距离 / 速度)
③ 换算 计数 ÷ (32 × 120 = 3840) = 圈数;圈数 × 2π = 弧度;弧度 × 半径 33.5 mm = 毫米
④ 闭环 wheel_control 服务:PID 把「目标速度 − 实测速度」变成电机功率
⑤ 行为 走定距(快 + 刹车 PID 两段式)、走直线、转 90°(双轮各走各的弧长)
⑥ 调参 config store:参数上 MQTT、落 SD 卡,网页输入框直接改,重启不丢
图说:①–③ 让机器人「知道自己走了多少」,④–⑥ 让它「按你要求的走」。
3. 核心原理之一:编码器——把转动变成数字¶
编码器(encoder)是检测运动的传感器,把运动编码成电信号;它量的是位置或位移, 速度可以由它算出来8。机器人上这套的具体构造:电机轴尾端装一片磁编码盘, 盘面上 N 极、S 极交替;盘转过一对霍尔效应传感器(Hall effect sensor), 磁场变化就变成脉冲9。注意一个细节:编码盘在电机轴上,而电机经过 120:1 的 齿轮箱才带动轮子——轮子转一圈,编码盘已经转了一百多圈,这就是精度来源 (可以亲手验证:断电后慢慢转轮子,看编码盘比轮子转得快多少)9。
方向怎么判? 一个传感器只能数脉冲(配合「我知道电机指令方向」才能定号);
两个传感器错开半个相位,得到四相序列 10, 11, 01, 00——这叫正交编码
(quadrature encoding):序列往哪边循环,就知道正反转,分辨率还翻倍10。
我们的 FIT0450 电机就是正交编码——电机接 HAT 的 6 根针脚,4 根属于编码器10。
绝对与相对是一对必须分清的概念,书里给了一句判据:
「能告诉你东西『在哪』的,是绝对式;能告诉你『动了多少』的,是相对式。」11
- 绝对式编码器直接报位置(可能要做标定),更笨重、更贵、引脚更多; 舵机里的电位器算是个粗糙的绝对编码器11。
- 相对式(增量式)编码器只报「从上次起走了多少步」,便宜简单; 代价是误差会累加,代码必须自己记着上次在哪——漂移是它的天性11。
我们的是相对式——书里还设计了一个实验证实:重启后读数从任意内存值继续, 而不是车轮的真实位置11。
4. 核心原理之二:三级换算——从计数到毫米¶
这是本章的数学主干,照原书的三步接住。
第一级:计数 → 圈数。 FIT0450 的编码盘每圈 32 脉冲,齿轮比 120:1, 所以轮子每转一圈 = 32 × 120 = 3840 个计数。把这个乘积喂给库:
书里的 Tip 值得当工程师信条:「让计算机算。把变量(齿轮比、盘计数)和关系 (相乘)摆出来,以后变了好改;直接贴 3840,后来的人看着就是无厘头的魔法数。」12
第二级:圈数 → 弧度。 一圈 = 2π 弧度。第三级:弧度 → 毫米。 轮子在地面上滚,转过的角度乘以轮半径,就是贴地滚过的距离(弧长 = 半径 × 角度)。 用卡尺量出轮子直径(作者的 67 mm),半径 = 33.5 mm13:
wheel_radius = 67 / 2
data = encoder.capture() # 一次性快照,避免两次读取之间轮子又动了
distance_mm = data.radians * wheel_radius # 距离
speed_mm_s = data.radians_per_second * wheel_radius # 速度
capture() 这个小动作有讲究:一次快照同时取所有数据,
分开读的话两次读取之间轮子还在动,数会对不上13。
服务照例上 MQTT:sensors/encoders/data 持续发布
左右距离(毫米)和左右速度(毫米/秒),另有 sensors/encoders/control/reset
清零计数14。书还顺手埋了两个系统味道:按一下 HAT 的 USER 键就发布
launcher/poweroff(硬件关机键);Note 提醒高频大消息会灌满总线拖慢 Pi,
频率和体积要掂量14。
5. 核心原理之三:PID 控制器——P、I、D 三项分工¶
闭环电机控制的图是这样转圈的15:
目标速度(set point, mm/s) 电机功率(-1..1)
│ ▲
▼ │
┌─ 减法:误差 = 目标 − 实测 ◀── 编码器速度(mm/s) ┘
│
▼
PID 控制器:输出 = P项 + I项 (+D项)
三个项的分工,照原书接住16:
| 项 | 干什么 | 什么时候有用 |
|---|---|---|
| P(比例) | 误差 × 比例常数 | 纠当前的错——第 9 章的比例避障就是它 |
| I(积分) | 误差的时间累积 × 积分常数 | 纠「一直差一点」的错——某个电机天生慢半拍,光靠 P 永远追不上 |
| D(微分) | 误差变化率 × 微分常数 | 给系统泼冷水、防超调(电机和编码器的物理本身也自带阻尼) |
两个工程警告书里用 Note 加重17:
- 留余量:电机速度别用到 100%——闭环要靠「还能再快一点」来纠偏, 顶到头就饱和(clipping),带积分的控制器饱和后会行为怪异。速度上限按 80% 用。
- 积分会记仇——误差持续多久,它的影响就累积多大;所以启用/换向时要
reset()清零,否则停着的这段时间攒下的旧账会让车猛蹿18。
代码实现瘦得惊人(这是故意的——好调):
class PIDController:
def __init__(self, proportional_constant, integral_constant=0):
self.proportional_constant = proportional_constant
self.integral_constant = integral_constant
self.integral = 0
def control(self, error, time_difference=1):
self.integral += error * time_difference # 误差 × 时长 逐次累加
return (self.proportional_constant * error
+ self.integral_constant * self.integral)
time_difference 的存在很关键:积分要乘时间——0.2 秒的误差比 0.1 秒的同款误差
权重翻倍;每次调用间隔不同,积分就不能只数次数19。
6. 核心原理之四:轮控服务——把行为语言从「功率」换成「速度」¶
wheel_control_service 坐在电机服务之上,永远在线、按需启用20:
- 订阅
wheel_control/wheel_speed_mm(JSON:[左目标, 右目标],单位 mm/s)—— 这是行为的新语言:以后行为只说「左 180、右 180」,不再说「0.8」; - 订阅
wheel_control/enabled(true/false)——启停这个闭环; - 听
all/stop自废 + 发停车; - 每次编码器数据到:误差 → 两侧 PID →
np.clip限 ±1 → 发motors/wheels。
调参从 0.001/0.001 起步(书里的忠告:从小往大调,防意外动作)21。 第一组症状和处方书里记录得很诚实:
| 症状 | 处方 |
|---|---|
| 纹丝不动 | P 加到 0.1,先让它「有反应」 |
| 剧烈摇摆 | 预期之中,继续调22 |
OSError: [Errno 5] Input/output error |
电池该充电了(或供不出 5 V 3 A)——I²C 在低电压下会出错 |
| 只 P 不调 I | 可能勉强可用,但「一个轮子天生慢」这种 Persistent 误差消不掉 |
整定的结论(作者地板上的值,你的会不同):轮控 P: 0.001、I: 0.01 管用23。 方法只有一句:桌上有桌子的阻尼,地板有地板的阻尼,桌面调完还要上地板复调24。 先加 P 到能动且不慢,再加 I 消残差;I 太大就开始「对已经消失的误差过度反应」、 来回超调——书里明说「积分太高会让系统对已经不在的误差持续反应而超调」, 「没有比例项,积分必然超调,所以先调 P」25。
7. 核心原理之五:走定距——两段式:先跑,再 PID 刹车¶
drive_known_distance 演示里程的用法,也是「误差驱动行为」的模板:
Sense 每 0.1 秒:left_error = 目标 − 左里程;right_error 同理
Think 取两侧误差里最大的那个(max(left,right)——后文改为绝对值大者)
误差 < 1 mm → 到点,速度 0
否则 → 全速
Act 发 wheel_control/wheel_speed_mm + 画误差曲线
默认走 1000 mm、速度 100 mm/s(刻意低于 80% 上限)26。 取「误差最大的一侧」收尾的妙处:两侧谁没到都继续走,保证不提前停。
升级版把「快到点」拆成两段——这是控制里的常见手法27:
误差 > stopping_distance(100 mm) → 全速(speed 配置,默认 180 mm/s)
1 mm ≤ 误差 ≤ stopping_distance → distance PID 接管(P 0.2、I 0.01),误差越小越慢
误差 < 1 mm → 停
图说:远处用 bang-bang 跑,近处用比例刹车——「粗糙校准」变成「精确到位」。
效果:开环版会冲过头 3–5 cm;刹车 PID 版「很快停在目标上,不超调也不欠调」。 书里给的调法:刹不住就加大 P,I 大约取 P 的十分之一;作者验证过 P:1.0 / I:0.1 的高增益仍稳定28。
8. 核心原理之六:config store——参数不该住在代码里¶
调一次参就改代码、重新部署,太磨人。书里造了一个小但完整的配置存储29:
- 存储用 Python 内置的 dbm(键值对,落 SD 卡,断电不丢);
config/set存(["wheel_control/proportional", 0.001]);config/get点名要(["..."]→ 服务回发);config/updated广播最新值——行为和网页都订阅它。
行为的接入四件套(书里明列为可复用模式)30:成员变量存状态 +
订阅 config/updated 的处理器 + 启动时发 config/get 拉初始值 + 网页上加输入框。
网页侧 config_store.js 提供 add_input(key, label, container, 初值):
失焦即发布 config/set,收到 updated 就回填——参数在手机上即时生效、重启不丢。
从此「调参」不再是开发动作,是运维动作。这是把机器人当产品养的标志。
9. 核心原理之七:转弯的运动学——两个轮子各走各的弧¶
「转 90°」听起来是一个动作,对两个轮子却是两段不同的路程31。 运动学(kinematics)就是干这个翻译的:
机器人原点 = 两轮中点;turn_radius = 转弯半径(相对原点)
左轮半径 = turn_radius − 轮距/2 (内圈,走得少)
右轮半径 = turn_radius + 轮距/2 (外圈,走得多)
弧度角 × 各自半径 = 各轮要走的路程
left_turn_distance = angle × left_turn_radius
right_turn_distance = angle × right_turn_radius
图说:原地转是 turn_radius = 0 的特例——左负右正、轮距就是各自的「半径」。
轮距量法:两轮内侧距离 + 一个轮子厚度(作者:133 + 26 ≈ 159 mm),
存进 config store(robot/wheel_distance)31。
代码侧把「走直线」和「走弯」统一成一个函数 drive_wheels_to_distance(左目标, 右目标):
- 目标远的那侧叫 primary——误差只盯它量;
- speed_factor = 1 / max(两侧目标);左系数 = 左目标 × 系数—— 把两个目标缩放到同一「满速时间」,两轮自然按各自弧长分配速度;
- 循环:误差 → (远:全速 / 近:PID 刹车 / 到:停)→ 两侧分别乘系数下发;
- 直线 = 两侧目标相等的特例,
drive_line缩成一行调用32。
复位后还有个细节:清零编码器后自旋等待两侧的里程报回 0 再起步——
异步(发送方不等接收方、各按各节奏)数据还没到就起跑,旧读数会让它「以为已经到点」而罢工32。
验证:先单转一个 90°、半径 30(drive_curve(90, 30)),再链成四段正方形——
「机器人应该转到很接近 90° 了;可能会略冲再拉回。」33
10. 作者的判断与证据¶
- 判断: 闭环优于开环,但闭环不是免费的——要留 20% 速度余量给纠偏, 要防积分饱和,要按地面复调1724。这条余量原则,书里用 Note 框单独强调。
- 判断: 直线只是转弯的特例——统一成
drive_wheels_to_distance后, 行为层代码反而更少32。 - 事实层: 32 脉冲/盘圈 × 120 齿轮比 = 3840 计数/轮圈;直径 67 mm; 轮距 159 mm;推荐增益 P 0.001/I 0.01(轮控)、P 0.2/I 0.01(刹车)、 上限 P 1.0/I 0.1——全是作者机器上的标定值,书里反复说「你的会不一样」122328。
- 证据形式: 每步都配
*/plot曲线——误差图、控制量图, 「先调 P 再加 I、I 大了会超调」这些结论全是看曲线得出的,不是背口诀25。
11. 边界与局限¶
- 里程计会漂。 相对编码器的误差随距离累加(第 5 节);轮子打滑、被抱起来, 里程全错——第 12 章开篇就以此为靶子。本章的「精确」是短程的、局部的。
- D 项只用了名字。 控制器类支持三项,实际全程 P + I;「防超调」靠物理阻尼 和手工调 I,微分是第 15 章的事16。
- 整定没有方法论。 从小往大试、看曲线、按地面复调——是手艺流程, Ziegler-Nichols 之类的系统方法没提(Further reading 指了 Brian Douglas 的视频)34。
- 电机饱和没做抗积分饱和(anti-windup)。 只警告「会行为怪异」并靠 80% 上限回避, 没有正式的 anti-windup 代码。
12. 可带走的¶
- 编码器把转动变数字:磁盘 + 霍尔传感器;双传感器错相 = 正交编码,方向 + 分辨率双赢10。
- 绝对报「在哪」,相对报「动了多少」;相对式便宜但误差累积、要记零点11。
- 三级换算:计数 ÷ 3840 = 圈;圈 × 2π = 弧度;弧度 × 半径 = 毫米;
capture()要快照1213。 - PID 三项:P 纠现在、I 纠 Persistent(乘时间累积、换向清零)、D 防超调(本章没用)1619。
- 闭环要留余量:电机别超 80%,否则饱和 + 积分 = 行为怪异17。
- 先 P 后 I;I 取 P 的十分之一起步;I 太高会对「已消失的误差」过度反应而超调2528。
- 行为语言升级:行为说目标速度(mm/s),闭环服务管功率——分层又白捡一次20。
- 两段式到位:远处 bang-bang、近处 PID 刹车,冲过头从 3–5 cm 缩到毫米级27。
- 参数住 config store(成员变量 + updated 订阅 + get 拉取 + 网页输入框),调参不再是部署2930。
- 转弯 = 两轮各走各的弧:轮距量出来、半径分内外、直线是特例3132。
补遗:直行整定的完整剧情(从「能动」到「不晃」)¶
「Driving in a straight line」这一节占了原章近一半的篇幅,因为它记录的是一段真实的 调参叙事,值得补全——每一步的症状和下一步的动作都有因果。
第一幕:能动。 P、I 各 0.001 起步,结果是「缓缓起步(gradual take-off)」—— 电机像睡醒了才开始转,两轮还不齐1。书里的忠告是先小后大: 「这些 PID 值大概率是错的;机器人可能在晃或太慢。从低值起步,防意外动作。」
第二幕:P 加大到 0.01。 车有反应了,但「剧烈摇摆属预期之中」2—— P 太大,每一点误差都被过度放大,纠偏过头再反纠。这时不要急着降 P, 先把 I 请进来:P 管「现在差多少」,I 管「一直差多少」—— 某个电机天生慢半拍的 Persistent 误差,只有 I 能消。
第三幕:加 I 之后仍然晃。 书里点破:你的系统其实一直被两层东西同时阻尼着—— 一层是电机与编码器的物理特性,另一层是地板的拖曳阻力。 桌面调出来的值,上地板要重调:「桌面上能粗调,地板上必须细调」3。 作者地板上的终点:P 0.001、I 0.01——P 比起点没变,I 大了十倍4。 这个组合的含义:P 只给一点点即时反应,主要靠 I 慢慢把 Persistent 误差磨平—— 慢,但是稳,对「走直线」这种不赶时间的任务正合适。
第四幕:图表的坑。 最早画的误差曲线有个假点:数据集初始化时塞了一个全 0 的
占位数据,图表一开场就显示一个伪零点。修法是给图表加 first_item 标志,
第一条真实数据到达时替换占位点而不是追加5。
这类「初始化残影」在实时图表里很常见,值得记住。
第五幕:参数全部上网页。 有了 config store,轮控的 P/I、刹车 PID 的 P/I、 行驶速度、刹车距离,一共六项全部出现在网页输入框里, 调参从此「浏览器里改数字 → 机器人立刻变性格」, 不用碰代码、不用重新部署6。
13. 原文地图¶
| 主题 | 原书位置 |
|---|---|
| 编码器与里程计定义 | text/97-fm-measuring-the-distance-traveled-with-encoders.txt:14(搜「odometry」) |
| 磁编码盘与霍尔传感器 | text/97-fm-measuring-the-distance-traveled-with-encoders.txt:34(搜「Hall effect sensors」) |
| 正交编码 | text/97-fm-measuring-the-distance-traveled-with-encoders.txt:197(搜「quadrature encoding」) |
| 绝对 vs 相对 | text/97-fm-measuring-the-distance-traveled-with-encoders.txt:153(搜「If a sensor can tell you where something is」) · text/97-fm-measuring-the-distance-traveled-with-encoders.txt:149(搜「accumulate errors」) |
| 计数上 MQTT 与带宽警告 | text/98-fm-detecting-the-distance-traveled-in-python-and-mq.txt:27(搜「sensors/encoders/data」) |
| counts_per_rev 与魔法数 Tip | text/98-fm-detecting-the-distance-traveled-in-python-and-mq.txt:157(搜「counts_per_rev」) · text/98-fm-detecting-the-distance-traveled-in-python-and-mq.txt:166(搜「let the computer do calculations」) |
| 弧度与轮径 | text/99-fm-driving-a-specific-distance.txt:33(搜「wheel radius」) · text/99-fm-driving-a-specific-distance.txt:93(搜「radians per second」) |
| PID 三项分工 | text/100-fm-driving-in-a-straight-line.txt:78(搜「Proportional (P)」) · text/100-fm-driving-in-a-straight-line.txt:97(搜「Leave headroom」) |
| PID 控制器代码 | text/100-fm-driving-in-a-straight-line.txt:110(搜「class PIDController」) |
| 轮控服务与症状 | text/100-fm-driving-in-a-straight-line.txt:155(搜「wheel_control/enabled」) · text/100-fm-driving-in-a-straight-line.txt:314(搜「Violent wobbling is expected」) |
| 整定结论与积分记仇 | text/100-fm-driving-in-a-straight-line.txt:739(搜「settings of P: 0.001」) · text/100-fm-driving-in-a-straight-line.txt:727(搜「keep over-reacting to errors」) |
| config store | text/100-fm-driving-in-a-straight-line.txt:407(搜「dbm」) |
| 刹车 PID | text/100-fm-driving-in-a-straight-line.txt:755(搜「stopping_distance」) · text/100-fm-driving-in-a-straight-line.txt:897(搜「doesn't overshoot or undershoot」) |
| 运动学与转弯 | text/101-fm-making-a-specific-turn.txt:24(搜「kinematics」) · text/101-fm-making-a-specific-turn.txt:43(搜「left_turn_radius」) · text/101-fm-making-a-specific-turn.txt:76(搜「drive_curve」) |
| 章节总结 | text/102-fm-summary.txt:6(搜「PID controllers react」) |
-
出处:「Driving in a straight line」第 315 段(
text/100-fm-driving-in-a-straight-line.txt:315,搜「wobbling」)。原文:PID 值 0.001/0.001 的结果是 gradual take-off(缓缓起步),两轮不齐;「These PID values are likely to be wrong, though. The robot may be wobbling or moving too slowly.」 ↩ -
出处:「Driving in a straight line」排错(
text/100-fm-driving-in-a-straight-line.txt:314,搜「Violent wobbling」)。原文:P 加到 0.1 让它有反应;此刻剧烈摇摆属正常。 ↩ -
出处:「Driving in a straight line」第 736 段(
text/100-fm-driving-in-a-straight-line.txt:736,搜「damping effect」)。原文:地板拖曳引入阻尼;桌面可粗调、地板要细调。 ↩ -
出处:「Driving in a straight line」第 739 段(
text/100-fm-driving-in-a-straight-line.txt:739,搜「settings of P: 0.001」)。原文:「settings of P: 0.001 and I: 0.01 worked(在我的地板上)」。 ↩ -
出处:「Driving in a straight line」(
text/100-fm-driving-in-a-straight-line.txt:1,搜「first_item」)。原文:first_item 变量让图表在首条真实数据到达时替换占位数据。 ↩ -
出处:「Driving in a straight line」第 881 段(
text/100-fm-driving-in-a-straight-line.txt:881,搜「A member variable」)。原文:每个可配参数四件套——成员变量、config/updated 处理器、config/get 拉取、网页输入控件;六参数同页可调。 ↩ -
出处:「Driving in a straight line」(
text/100-fm-driving-in-a-straight-line.txt:20,搜「20 cm ahead」)。原文:仅 1 米,左侧已领先右侧 20 cm 且误差在增长;两个电机完全相同很罕见;本节用实时编码器数据做电机同步。 ↩ -
出处:「Measuring the distance traveled with encoders」第 14 段(
text/97-fm-measuring-the-distance-traveled-with-encoders.txt:14,搜「odometry」)。原文:编码器检测运动、编码成电信号;转速表测轴速,编码器测位置或位移,速度可由它计算;估算行程即 odometry(里程计)。 ↩ -
出处:「Measuring the distance traveled with encoders」第 34 段(
text/97-fm-measuring-the-distance-traveled-with-encoders.txt:34,搜「Hall effect sensors」)。原文:编码盘在电机轴上、N/S 交替;霍尔传感器检测磁场变化;经 120:1 齿轮箱,轮子转一圈传感器转一百多圈;断电转轮子可见编码盘转得更快。 ↩↩ -
出处:「Measuring the distance traveled with encoders」第 197 段(
text/97-fm-measuring-the-distance-traveled-with-encoders.txt:197,搜「quadrature encoding」)。原文:单传感器数边沿、配合指令方向;双传感器错相得 10,11,01,00 四相序列,即正交编码——方向可判、分辨率翻倍;可用于录放机器人;鼠标里也有;FIT0450 的编码器占 6 针脚中的 4 针。 ↩↩↩ -
出处:「Measuring the distance traveled with encoders」第 153 段(
text/97-fm-measuring-the-distance-traveled-with-encoders.txt:153,搜「If a sensor can tell you where something is」)与第 110 段(搜「accumulate errors」)。原文 Note 判据:能报「在哪」是绝对,能报「动了多少」是相对;绝对式可能需标定、更笨重贵、引脚多;相对(增量)式便宜直接,代价是误差累积、代码要记上次位置,长距漂移。 ↩↩↩↩↩ -
出处:「Detecting the distance traveled in Python and MQTT」第 157 段(
text/98-fm-detecting-the-distance-traveled-in-python-and-mq.txt:157,搜「counts_per_rev」)与第 150 段(搜「let the computer do calculations」)。原文:齿轮比 120:1、盘 32 脉冲/rev;counts_per_rev(32*120) 让库自己换算圈数;Tip:让计算机算——摆出变量与关系,别贴 3840 这种事后看不懂的数。 ↩↩↩ -
出处:「Driving a specific distance」第 33 段(
text/99-fm-driving-a-specific-distance.txt:33,搜「wheel radius」)与第 90 段(搜「radians per second」)。原文:卡尺量轮径最宽处(作者 67 mm),半径减半;capture() 快照同取所有数据防不一致;库已把计数转弧度,乘半径即毫米;弧度/秒 × 半径 = 毫米/秒。 ↩↩↩ -
出处:「Detecting the distance traveled in Python and MQTT」第 27 段(
text/98-fm-detecting-the-distance-traveled-in-python-and-mq.txt:27,搜「sensors/encoders/data」)。原文:update_encoders 持续发布左右计数;reset topic 清零;USER 键触发 launcher/poweroff;Note:高频大 payload 会饱和总线、拖慢 Pi,注意频率与体积。 ↩↩ -
出处:「Driving in a straight line」第 65 段(
text/100-fm-driving-in-a-straight-line.txt:65,搜「Intended speed」)。原文:闭环电机控制 = 取电机轴输出速度反馈控制输入功率;框图标了单位——目标 mm/s 与实测 mm/s 相减得误差,PID 输出控制量;低了加速、高了减速,可得平滑运动。 ↩ -
出处:「Driving in a straight line」第 78 段(
text/100-fm-driving-in-a-straight-line.txt:78,搜「Proportional (P)」)。原文:P = 误差 × 比例常数,纠即时错误;I = 持续误差的时间累积 × 积分常数(如电机一直差一点);D = 阻尼防超调(电机物理本身也有阻尼);本章不用 D。 ↩↩↩ -
出处:「Driving in a straight line」第 97 段(
text/100-fm-driving-in-a-straight-line.txt:97,搜「Leave headroom」)。原文 Note:需要未用的速度容量才能再加速;接近 100% 会 clipping,带积分的 clipping 行为怪异;给 PID 留余量——速度不超 80%。 ↩↩↩ -
出处:「Driving in a straight line」第 142 段(
text/100-fm-driving-in-a-straight-line.txt:142,搜「reset method」)。原文:积分按 error × time_difference 累加——0.2 秒的误差权重是 0.1 秒的两倍;reset() 在停车或换向时清累积,否则会 buildup 出问题。 ↩ -
出处:「Driving in a straight line」第 110 段(
text/100-fm-driving-in-a-straight-line.txt:110,搜「class PIDController」)。原文:control(error, time_difference) 输出 P×误差 + I×积分;time_difference 使积分不依赖循环速度。 ↩↩ -
出处:「Driving in a straight line」第 155 段(
text/100-fm-driving-in-a-straight-line.txt:155,搜「wheel_control/enabled」)。原文:服务常驻监听不常激活;topic:encoders/data、enabled(true/false)、wheel_speed_mm(JSON 左右速度)、all/stop 自废;on_enabled 里 reset PID 防停用期间积分累积。 ↩↩ -
出处:「Driving in a straight line」第 317 段(
text/100-fm-driving-in-a-straight-line.txt:317,搜「PID tuning first attempt」)。原文:P、I 从 0.001 起步防意外动作;P 加到 0.01 看反应。 ↩ -
出处:「Driving in a straight line」第 314 段(
text/100-fm-driving-in-a-straight-line.txt:314,搜「Violent wobbling is expected」)。原文排错:不动 → P 加到 0.1;无图查浏览器控制台;journalctl -u wheel_control_service;OSError Errno 5 = 电池要充(或供不出 5V 3A);「Violent wobbling is expected at this point(此刻剧烈摇摆属正常)」。 ↩ -
出处:「Driving in a straight line」第 739 段(
text/100-fm-driving-in-a-straight-line.txt:739,搜「settings of P: 0.001」)。原文:「I found on my floor, settings of P: 0.001 and I: 0.01 worked.」要试出自己的平衡值。 ↩↩ -
出处:「Driving in a straight line」第 736 段(
text/100-fm-driving-in-a-straight-line.txt:736,搜「damping effect」)。原文:地板拖曳阻力给系统引入阻尼;桌面可粗调、地板要细调。 ↩↩ -
出处:「Driving in a straight line」第 727 段(
text/100-fm-driving-in-a-straight-line.txt:727,搜「keep over-reacting to errors」)与第 731 段(搜「definitely overshoot」)。原文:积分常数太高会让系统对不再存在的误差持续反应而超调;没有比例项积分必然超调——因此从 P 起步。 ↩↩↩ -
出处:「Driving a specific distance」第 142 段(
text/99-fm-driving-a-specific-distance.txt:142,搜「drive_line」)。原文:误差 = 目标 − 各侧里程;取最大误差(后改绝对值大者);< 1 mm 到达;否则全速;画 left_error/right_error 曲线;默认 1000 mm、100 mm/s。 ↩ -
出处:「Driving in a straight line」第 755 段(
text/100-fm-driving-in-a-straight-line.txt:755,搜「stopping_distance」)。原文:三档——离得远全速(误差 > stopping_distance 100 mm)、进了刹车区交 distance PID(P 0.2 I 0.01,输出即 mm/s 目标速度)、<1 mm 停;开环会冲过头 3–5 cm。 ↩↩ -
出处:「Driving in a straight line」第 897 段(
text/100-fm-driving-in-a-straight-line.txt:897,搜「doesn't overshoot or undershoot」)。原文:目标——很快停在目标、不超调不欠调;爬停先加 P;I 约为 P 的十分之一;作者 P:1.0/I:0.1 仍稳定。 ↩↩↩ -
出处:「Driving in a straight line」第 407 段(
text/100-fm-driving-in-a-straight-line.txt:407,搜「dbm」)。原文:Python 内置 dbm 做持久键值存储;config/set 存、config/get 取(仅存在的键)、config/updated 广播;sync() 确保写 SD 卡;断电后重开仍取到最后值。 ↩↩ -
出处:「Driving in a straight line」第 881 段(
text/100-fm-driving-in-a-straight-line.txt:881,搜「A member variable」)。原文:每个可配参数四件套——成员变量存状态、订阅 config/updated 的处理器、启动发 config/get 拉初值、网页加输入控件。 ↩↩ -
出处:「Making a specific turn」第 24 段(
text/101-fm-making-a-specific-turn.txt:24,搜「kinematics」)与第 40 段(搜「left_turn_radius」)。原文:这种运动建模叫 kinematics;轮距 = 轮内侧距 + 轮厚(作者 133+26≈159 mm)存 config;left_turn_radius = turn_radius − 轮距/2,right 加;弧度角 × 半径 = 各轮路程;负半径 = 左弯;直行是两轮目标相等的特例。 ↩↩↩ -
出处:「Making a specific turn」第 76 段(
text/101-fm-making-a-specific-turn.txt:76,搜「drive_curve」)与同文件第 90 段(搜「primary」)。原文:目标远侧为 primary,误差只盯它;speed_factor = 1/max(两侧目标),各轮系数 = 目标 × 系数;清零编码器后自旋等两侧里程归零再跑(旧异步数据会跳步);先 drive_curve(90, 30) 单弯测试,再拼正方形。 ↩↩↩↩ -
出处:「Making a specific turn」第 281 段(
text/101-fm-making-a-specific-turn.txt:281,搜「pretty close to 90 degrees」)。原文:应该转到很接近 90 度;可能超调再拉回。 ↩ -
出处:「Further reading」第 8 段(
text/103-fm-further-reading.txt:8,搜「Brian Douglas」)。原文:PID 是平衡车、无人机等的关键;推荐 Brian Douglas 的 PID 视频系列与 GW Lucas 的竞赛里程计文章。 ↩