实战课题
第 31 课:追光小雷达
只用 1 个光敏,做到了"两个光敏"才能做的事——因为舵机带着它去找
我是吴老师。我是吴老师。先说一句:这是我在整套课里最喜欢的一个课题。
不是因为它最难——它其实不算难。是因为它的设计太漂亮了。
先看这东西长什么样:一个冰棒棍搭的小台子,上面坐着个舵机,舵机的臂上粘着一个光敏。上电以后,它会左右慢慢地扫,扫着扫着,就停在一个方向不动了——那个方向,是灯最亮的方向。 你把台灯挪到另一边,它愣一下,然后又慢慢转过去,继续追着那盏灯。
这东西叫"太阳能追踪器"。 屋顶上那些追着太阳转的光伏板,用的就是这个原理。而我们只用了 1 个光敏。
一、只用 1 个光敏,怎么知道"哪边更亮"?
先想一个问题:判断"哪边更亮",最直觉的做法是什么?
肯定是两个光敏——一个朝左、一个朝右,谁读数大就往谁那边转。差分,一目了然。
问题是:你套件里只有 1 个光敏。那怎么办? 答案是这个课题最漂亮的地方:
让光敏动起来。 它在左边的时候读一次,在右边的时候再读一次——这不就有"两个读数"了吗?
我们不是用两个光敏,我们是用"同一个光敏的两次读数"。 而让光敏动起来的那个东西,就是舵机。看到没有——舵机不只是"执行器",它顺便还成了"传感器的一部分"。
二、【闭环】这个课题的三个环节
| 环节 | 这个课题里是什么 |
|---|---|
| 传感器(看) | 光敏——装在舵机臂上,跟着一起转 |
| 判断(想) | 转过去以后,是变亮了还是变暗了? |
| 执行器(做) | 舵机——往亮的方向转;OLED 把角度和读数显示出来 |
这个闭环跟前面几个不一样,你得仔细看:
第 9 课那个闭环是"一条线":传感器 → 判断 → 执行器,走完就完了。这个是"一个圈":舵机转过去 → 光敏的朝向变了 → 读数跟着变了 → 而这个新读数又决定了舵机下一步往哪转。
输出回过头来改变了输入。 这才叫真正的"闭环控制"。(这个词你以后会经常见到,今天第一次见它的真身。)
三、材料清单
套件里:ESP32-C3 主控 + 拓展板、光敏模块、舵机(MG90S)、OLED 显示屏、5V 电池盒(4 节 5 号电池,这次是必需品)、杜邦线若干(光敏那几根要够长,至少 20cm)。
身边要准备:
| 东西 | 上哪儿找 |
|---|---|
| 冰棒棍 | 攒 12 根左右,洗净晾干 |
| 热熔胶枪 + 胶棒 | 必备 |
| 剪刀 / 小锯条 + 针 | 锯冰棒棍、在冰棒棍上扎孔 |
| 厚纸皮一小块 | 底座配重 |
| 橡皮筋 | 绑舵机用,两三根 |
| 一盏台灯或一个手电筒 | 测试用。这个必须有,理由后面讲 |
四、接线
先看引脚规划——这次也刚好够用:
| 模块 | 针脚 | 接到 |
|---|---|---|
| 光敏 | VCC | 3.3V |
| 光敏 | GND | GND |
| 光敏 | AO |
GPIO0(模拟脚。注意是 AO 不是 DO) |
| OLED | VDD | 3.3V |
| OLED | GND | GND |
| OLED | SDA | GPIO8 |
| OLED | SCK |
GPIO9 |
| 舵机 | 橙(信号) | GPIO7 |
| 舵机 | 红(电源) | 独立 5V 电池盒的正极 |
| 舵机 | 棕(地) | GND,而且要和电池盒负极连起来(共地) |
重点全在最后三行。 舵机的红线,不许接板子。 不许接 3.3V,这次连拓展板上那个 5V 脚也别接——第 18 课我说过那个是"权宜之计,做做演示可以"。这个课题不是演示,它要连着转一整个下午。 棕线要接两个地方:板子的 GND,和电池盒的负极——这就是共地,第 18 课那张图里那根灰色、绕一大圈的线。
嫌麻烦? 我知道,多接一根线谁都不愿意。但你等下会亲眼看到:不接板子会重启。 而且你要是忘了共地,现象会更离谱——舵机会乱转、发抖、或者一动不动,而所有线看起来都是接对的。这两件事今天都会出现。第 18 课我让你故意踩过一次,今天它换个面孔回来找你。
五、分步实现
第一步:两个模块先分开跑通
别一上来就把它们凑一起。 先把舵机跑通(setup 里记得 myServo.attach(7)):
for (int a = 20; a <= 160; a++) { // 从 20 度扫到 160 度
myServo.write(a);
delay(20);
}
for (int a = 160; a >= 20; a--) { // 再扫回来
myServo.write(a);
delay(20);
}烧进去,看它来回扫。 扫得顺不顺?有没有顿、有没有嗡嗡响?不顺就把 delay(20) 调大一点(第 18 课讲过:write() 只是下命令,舵机得有时间转过去)。
然后单独测光敏:
void loop() {
Serial.println(analogRead(0));
delay(100);
}用手电筒照它、手遮住它,看数字变。 顺手记两个数:全遮住时多少,手电筒直射时多少。这两个数决定了你这个课题能不能做——等下讲。
第二步:把光敏装到舵机臂上,再验证一次
这一步是造物,但必须先做,因为它是整个课题的前提。 先按第六节的办法把光敏粘到舵机臂上,不用做得好看,能用就行。装好之后,写个小程序试试:
// 假设 setup 里已经 myServo.attach(7)
void loop() {
myServo.write(40);
delay(600);
Serial.print("左边: ");
Serial.println(analogRead(0));
myServo.write(140);
delay(600);
Serial.print("右边: ");
Serial.println(analogRead(0));
delay(1000);
}拿手电筒(或者台灯)从侧面照过来,看这两个数字。它俩差得越大,这个课题越好做。
如果两个数字几乎一样大——那你就撞上这个课题最大的坑了,而且它不是硬件问题。
原因:你的房间根本没有"哪边更亮"这回事。 整个屋子被顶灯照得一样亮,那对光敏来说"哪个方向都一样",它无从选择,只能原地发呆。
这不是你的代码错了,是这个世界太均匀了。 解决办法:拿一盏台灯放在一侧,制造一个"明显的亮方向",手电筒更好。
记住这件事:做传感器项目,先想想"这个物理量在你的环境里真的存在吗"。 这跟第 11 课"阈值不能抄"是一个道理。
第三步:核心逻辑——试一下,看结果好不好
先讲思路,思路比代码重要。
假设光敏现在在 90 度。我该往哪边转?不知道啊,房间那么乱,我算不出来。那就不算——试。
- 先记下现在的读数(转之前)
- 随便往一个方向转 3 度
- 再读一次(转之后)
- 比较这两个数:变亮了 → 方向对,下次继续往这边转;变暗了 → 方向错,下次掉头
就这么简单。 它一次一次地试,最后会稳定在最亮的那个方向。
这办法有个正式名字叫"扰动观察法"(perturb and observe)。屋顶上那些太阳能板就是用它来找太阳的。 你不用记这个名字,但你要知道:"先试一下,看好不好,好就继续,不好就换"——这是让机器自己找最优解最朴素、也最常用的一招。
#include <ESP32Servo.h>
#define LIGHT 0
Servo myServo;
int angle = 90; // 当前角度
int dir = 1; // 方向:1 = 往角度变大那边,-1 = 往变小那边
int step = 3; // 每次挪几度
void setup() {
Serial.begin(115200);
myServo.attach(7);
myServo.write(angle);
delay(500);
}
void loop() {
int before = analogRead(LIGHT); // ① 转之前,先记下来
angle += dir * step; // ② 往当前方向挪一小步
angle = constrain(angle, 10, 170); // ③ 别撞到两头
myServo.write(angle);
delay(120); // ④ 等舵机转过去、等读数稳住
int after = analogRead(LIGHT); // ⑤ 转之后,再读一次
if (after < before) dir = -dir; // ⑥ 变暗了 → 走反了,掉头
// 变亮了就什么都不用做——方向保持
Serial.print(angle);
Serial.print(" ");
Serial.println(after);
}烧进去,把台灯放在它的一侧。 看着它:一开始可能乱转,但很快就会朝一个方向慢慢走,然后停在台灯那边。把台灯挪到另一边——它愣一下(因为方向错了),然后掉头,慢慢转过去。你现在有一个会追光的装置了。
delay(120)是干什么的? 两个作用:一是给舵机时间转过去(第 18 课那个脾气),二是给光敏时间稳住。这个数太小会怎样?舵机还没到位、光敏读数还在晃,你就在比较"转前"和"转后"——比的是噪声,它就原地打转了。 太大呢?反应变慢,追一盏灯要等半天。60 到 200 之间自己试,找出你觉得顺的。
constrain(angle, 10, 170)是限制范围。 别让它往 0 度和 180 度硬顶——舵机顶到头会一直使劲、发热、嗡嗡响(第 18 课讲过)。两头各留 10 度余量,它一辈子都不会撞墙。
第四步:把角度和读数显示出来
加上 OLED,你就能看见它在想什么了。第 19 课那三步:擦干净 → 重新画 → 推上屏。
oled.clearDisplay();
oled.setCursor(0, 0);
oled.print("Angle ");
oled.println(angle);
oled.setCursor(0, 26);
oled.print("Light ");
oled.println(after);
oled.display();屏幕上两个数字:它在几度,以及它看到的亮度。 你会发现一件有意思的事:它稳定下来以后,亮度那个数字会小小地来回跳——因为它在最亮点附近左右试探,这是"扰动观察法"的天性,不是故障。调一下 step 玩:
step |
表现 |
|---|---|
1 |
转得特别慢,但最后停得特别准 |
3 |
平衡 |
10 |
转得快,但会在最亮点附近来回大幅摇摆,永远停不下来 |
这就是"精度"和"速度"的取舍。 你自己找一个舒服的数。
六、组装:冰棒棍云台
目标是:舵机能稳稳固定在底座上,光敏坐在舵机臂上,而且转起来线不会扯到。
底座。 3 根冰棒棍并排(间隔 1cm),上面横压 2 根,点胶粘死——11×4cm 的平板。底下粘一块厚纸皮或两枚硬币当配重:舵机一转是有反作用力的,底座太轻会自己"走"。
固定舵机(难点一)。 舵机要躺着放,舵盘朝上,这样臂才能在水平面上扫。两侧的安装耳(MG90S 左右各有一个带孔的小耳朵)先用橡皮筋把舵机绑在底座上,看位置对不对;满意了,在两只耳朵和底座接触的地方点热熔胶。
注意:胶别灌进舵机本体和舵盘之间的那道缝。
那里面是齿轮和轴。胶一进去,舵机当场就卡死——通电以后它会一直使劲、发烫、最后烧掉。点胶的时候手稳一点,只点在耳朵和底座接触的那一两个点上。
做横臂(难点二)。 拿 1 根冰棒棍当横臂,它得固定在舵盘上。舵盘很小,直接粘粘不牢——按舵盘上的两个孔位,在冰棒棍上扎两个孔,用舵机自带的小螺丝把舵盘和冰棒棍拧在一起,再把舵盘压回舵机轴上。 装好后试转一下,从 10 度扫到 170 度,看横臂会不会蹭到底座,要留 5 毫米以上间隙,蹭到了就在舵机下面垫一小块纸皮。
装光敏(难点三)。 光敏模块粘在横臂的末端。那个小圆片(光敏电阻)必须朝外——朝向你要扫的那个方向,别让它被横臂或者舵机本体挡住,挡住就等于蒙住它的眼睛。胶还是只点 PCB 边缘,别糊到那个小圆片上。
理线(最容易忽略的一步)。 光敏的杜邦线,要在横臂根部留一段松的。 因为横臂是要转的,你把线绷得直直的,它一转线就被拉紧——轻则读数乱跳,重则把线扯断,或者把舵机拽得转不动。 做法:在横臂根部把线松松地绕一个小圈(像个"Ω"),再用橡皮筋或一小段胶带固定住。这个圈叫"服务环",是真实工程里专门的一招——所有会动的线,都得留这么一段余量。
最后把线沿着立柱顺下来,用胶带轻轻固定,接回板子。装完了。上电,把台灯放在一侧,它自己会转过去。 你把台灯挪走,它跟着转。这东西放在书桌上,是能拿给别人看的。
七、【排查】舵机一动,板子就重启
这一节你一定要读,因为在这个课题里它几乎一定会发生。
先说症状。 上电,串口里正常打印,OLED 亮了显示出角度,一切正常。然后舵机开始扫——
"啪"一下。串口里又打印了一遍启动信息,OLED 也重新亮了一遍,从第一行开始,角度回到 90。 然后它又开始扫,转两下,"啪",又重启了一遍。周而复始。最气人的是它时好时坏:有时候能连着扫好几轮,你以为好了,结果"啪"又来一下。
这时候你会怎么想? 我猜是这三个之一:
- "是不是 OLED 抢电?拔了试试……咦,好像好一点?"
- "是不是我
delay太短了,舵机没转完就下一条命令?改大点……好像也好一点。" - "是不是板子坏了?"
先停手,别改代码。 电 → 信号 → 代码。
第一步:量电
万用表拨到直流电压档。黑表笔搭 GND,红表笔点在舵机红线接的那个电源脚上(也就是拓展板上的 5V 脚)。
舵机不动的时候,读一下——5.0V 左右。正常。所以你会觉得"电没问题"。 别急。
第二步:盯着表,让它转
这才是关键的一步。表笔别动,眼睛盯着万用表的数字,然后让舵机转起来(重新上电,或者在代码里让它扫)。
你会看到:舵机一动,数字就往下掉——掉到 4V、3.5V,甚至更低;转完又弹回 5V。掉到 3.x 伏的时候,板子就重启了。 你可以在串口里同步看着:数字掉下去的那一刻,正好就是"啪"的那一下。这就是答案。
第三步:为什么会掉
先说舵机那一头。 它里面是个电机,电机有个脾气:不转的时候不太耗电,一启动那一下特别费电——几百毫安打底,要是被东西挡住转不动,一安培都打不住。
再说板子这一头。 拓展板上那个 5V 是从哪来的?从 USB 口直接过来的。 USB 线有电阻、板子上的铜箔有电阻、插针接触也有电阻。平时电流小,这点电阻不算什么;电流一大,压降就出来了。
这就是"夏天一开空调,家里的灯会闪一下"的同一件事——大功率电器启动的瞬间把电压拉下去了。只不过你家的电网扛得住,你这块板子扛不住:电压一低,欠压保护就动作,直接重启。
顺便解释一下你那两个"好像好一点"的尝试:
拔掉 OLED 好一点? 对,因为总负载轻了一点点,但主犯是舵机,你只是把一个从犯关起来了。 一卡住还是会重启。
把
delay改大好一点? 也对,因为舵机启动得不那么频繁了,平均电流小了。但根因没动。这两个都是"改了一个变量,现象减轻了"的假象。 硬件排查里最容易被这种东西骗——现象变轻不等于问题解决。 你要找的是"那个不变的原因",不是"能让它少犯一会儿的办法"。
第四步:正确做法(外加一次对比验证)
把舵机的红线从板子上拔下来,接到那个 5V 电池盒的正极。(4 节 5 号电池 = 6V,MG90S 标称 4.8~6V,完全没问题。)然后把电池盒的负极,用一根线连到板子的 GND 上——这就是共地。接好,再量一次那个 5V 脚(表笔不动,还是量板子这一侧):舵机扫它的,板子这边的 5V 稳如泰山。 串口里的启动信息再也不打印了。
看到没有——问题不是"猜"没的,是被"排除"掉的。
你做了个对比:同一块板子、同一份代码、同一个舵机,只把供电换了,故障就消失了。 这就是证据。这就是第 5 课说的"先测,再猜"。
再补一句:共地那根线千万别忘。 忘了它,舵机会乱转、发抖、或者干脆不动——而且所有线看起来都是接对的。 第 18 课那张图里那根绕一大圈的灰线,就是它。
自测
答案与解析
关键在于"让传感器动起来"。 在左边读一次、在右边读一次,这不就等价于"两个光敏"了吗?而让光敏动起来的,正好就是我们需要控制的那个舵机——它既是执行器,又是传感器的一部分。 这也是这个课题最漂亮的地方:一个零件干了两件事。
答案与解析
这个办法叫扰动观察法。它不需要知道光的准确方向,也不需要记住所有位置——只要每一步都问一句"我刚才是往好的方向走还是往坏的方向走"。试错 + 反馈,就这么简单。屋顶上的太阳能追踪器用的就是它。注意它也有代价:到了最亮点附近它会左右来回试探,永远停不下来(所以亮度数字会一直小小地跳)。
答案与解析
关键是要测"动态"而不是"静态"。静止时量出来 5.0V 很正常,你会因此以为供电没问题。只有盯着表让它转,才能看到那一瞬间掉到 3.x 伏——那个跌落就是"启动电流大"的铁证,也是欠压重启的直接原因。通断档查不出这个(线是通的),量信号脚也查不出(信号根本不是这里的问题)。
答题要点
答题要点应该包含:要区分"现象减轻"和"根因排除"。 拔掉 OLED 确实让总电流小了一点,重启的概率随之下降,但主犯是舵机那几百毫安的启动电流,OLED 那几十毫安只是从犯。所以一遇到舵机卡住(电流更大)的情况,故障还会回来——而且是"时好时坏"这种最让人抓狂的形式。正确做法:找到那个不变的原因(舵机从板子取电),用对比实验验证它(换成独立 5V 后故障彻底消失)。"改一个变量让现象变轻"是最容易骗过自己的排查方式。
拓展太阳能发电板追太阳,一天能多发多少电?为什么不是所有电站都装追踪器?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"太阳能追踪器 发电量 提升 百分比"、"光伏 追踪支架 成本 维护"。想一想:多发的那些电,够不够抵消"电机一天到晚在转"的成本和故障率?
拓展如果房间里有两个灯,这个装置会怎么办?它还能找到"最亮"吗?
这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。
提示:搜"多峰优化 局部最优"、"爬山算法 局部最大值"。想一想:它会不会停在其中一个灯那儿就再也不动了?这算不算一个"错误"? 人类遇到这种情况会怎么做?
本节你要动手做的事
- [ ] 先分开跑通两个模块:舵机能顺滑地扫(
GPIO7),光敏读数能用手电筒改变(GPIO0) - [ ] 确认红线不接板子:舵机红→独立 5V 电池盒正极,棕→
GND并和电池盒负极共地 - [ ] 先把光敏装上舵机臂,跑"40 度 / 140 度"对比程序,确认两个读数差得够大
- [ ] 如果两个读数一样:房间光太均匀了,拿一盏台灯或手电筒制造"亮的方向"
- [ ] 写核心逻辑:转前记一次、转后读一次,变暗就掉头(
dir = -dir) - [ ] 调
step和delay:试试 1 / 3 / 10,看看"准"和"快"的取舍 - [ ] 接上 OLED:显示角度和亮度,观察它在最亮点附近的小幅摇摆
- [ ] 做冰棒棍云台:底座加配重 → 舵机躺放(胶别灌进舵机缝里)→ 冰棒棍拧在舵盘上
- [ ] 光敏粘横臂末端:小圆片朝外,别被挡住;横臂根部留一个"Ω"形的服务环,别绷紧
- [ ] 用万用表做一次对比:先量舵机从板子取电时转动瞬间的电压,再量独立供电时的——把两个数记下来
回头看看这个课题你做成了一件什么事。
你没有买第二个光敏。你让唯一的那一个动了起来,于是它变成了两个。 这个思路比这个装置值钱——硬件不够的时候,先别急着买,想想能不能让已有的东西换个位置、换个时间、换个角度,去多拿一份信息。
还有那个环。 前面二十几节课,你的闭环都是"一条线",走完就结束了。今天你第一次做出了一个真正的圈:输出改变了输入,输入又决定下一个输出。这个圈,是"自动"和"智能"之间的那道分界线。
下一节课,我们玩点热闹的:打地鼠游戏机。
按钮、OLED、蜂鸣器——这次你不再是"做实验的人",你是"设计游戏的人"。 你要自己想清楚:地鼠什么时候冒出来、冒多久、打中了怎么算、打不中怎么算。
而且我要提醒你一件事:游戏机有个特别的要求——它得"及时"。 你按下去,它得马上反应。可你的程序里到处是 delay,delay 的时候板子什么都听不见。 这是个新麻烦。下节课见。