实战课题

第 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 课那张图里那根灰色、绕一大圈的线。

舵机的电源要从独立 5V 来,并且负极必须和板子的 GND 连在一起共地

嫌麻烦? 我知道,多接一根线谁都不愿意。但你等下会亲眼看到:不接板子会重启。 而且你要是忘了共地,现象会更离谱——舵机会乱转、发抖、或者一动不动,而所有线看起来都是接对的。这两件事今天都会出现。第 18 课我让你故意踩过一次,今天它换个面孔回来找你。

五、分步实现

第一步:两个模块先分开跑通

别一上来就把它们凑一起。 先把舵机跑通(setup 里记得 myServo.attach(7)):

C++
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() 只是下命令,舵机得有时间转过去)。

然后单独测光敏:

C++
void loop() {
  Serial.println(analogRead(0));
  delay(100);
}

用手电筒照它、手遮住它,看数字变。 顺手记两个数:全遮住时多少,手电筒直射时多少。这两个数决定了你这个课题能不能做——等下讲。

第二步:把光敏装到舵机臂上,再验证一次

这一步是造物,但必须先做,因为它是整个课题的前提。 先按第六节的办法把光敏粘到舵机臂上,不用做得好看,能用就行。装好之后,写个小程序试试:

C++
// 假设 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 度。我该往哪边转?不知道啊,房间那么乱,我算不出来。那就不算——试。

  1. 先记下现在的读数(转之前)
  2. 随便往一个方向转 3 度
  3. 再读一次(转之后)
  4. 比较这两个数变亮了 → 方向对,下次继续往这边转;变暗了 → 方向错,下次掉头

就这么简单。 它一次一次地试,最后会稳定在最亮的那个方向。

这办法有个正式名字叫"扰动观察法"(perturb and observe)。屋顶上那些太阳能板就是用它来找太阳的。 你不用记这个名字,但你要知道:"先试一下,看好不好,好就继续,不好就换"——这是让机器自己找最优解最朴素、也最常用的一招。

C++
#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 课那三步:擦干净 → 重新画 → 推上屏。

C++
  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 课那张图里那根绕一大圈的灰线,就是它。

自测

单选题这个课题只用 1 个光敏,为什么还能判断出"哪边更亮"?
单选题"变亮了就保持方向,变暗了就掉头",这个办法为什么能找到最亮的方向?
单选题舵机一转板子就重启,用万用表怎么确认是供电问题?
问答题拔掉 OLED 之后,重启好像变少了。为什么说这个"好一点"是假象?
拓展太阳能发电板追太阳,一天能多发多少电?为什么不是所有电站都装追踪器?

这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。

提示:搜"太阳能追踪器 发电量 提升 百分比"、"光伏 追踪支架 成本 维护"。想一想:多发的那些电,够不够抵消"电机一天到晚在转"的成本和故障率?

拓展如果房间里有两个灯,这个装置会怎么办?它还能找到"最亮"吗?

这个问题课上不讲,留给你自己找答案——**问 AI 是最好的办法**。

提示:搜"多峰优化 局部最优"、"爬山算法 局部最大值"。想一想:它会不会停在其中一个灯那儿就再也不动了?这算不算一个"错误"? 人类遇到这种情况会怎么做?

本节你要动手做的事

  • [ ] 先分开跑通两个模块:舵机能顺滑地扫(GPIO7),光敏读数能用手电筒改变(GPIO0
  • [ ] 确认红线不接板子:舵机红→独立 5V 电池盒正极,棕→GND 并和电池盒负极共地
  • [ ] 先把光敏装上舵机臂,跑"40 度 / 140 度"对比程序,确认两个读数差得够大
  • [ ] 如果两个读数一样:房间光太均匀了,拿一盏台灯或手电筒制造"亮的方向"
  • [ ] 写核心逻辑:转前记一次、转后读一次,变暗就掉头(dir = -dir
  • [ ] stepdelay:试试 1 / 3 / 10,看看"准"和"快"的取舍
  • [ ] 接上 OLED:显示角度和亮度,观察它在最亮点附近的小幅摇摆
  • [ ] 做冰棒棍云台:底座加配重 → 舵机躺放(胶别灌进舵机缝里)→ 冰棒棍拧在舵盘上
  • [ ] 光敏粘横臂末端:小圆片朝外,别被挡住;横臂根部留一个"Ω"形的服务环,别绷紧
  • [ ] 用万用表做一次对比:先量舵机从板子取电时转动瞬间的电压,再量独立供电时的——把两个数记下来

回头看看这个课题你做成了一件什么事。

你没有买第二个光敏。你让唯一的那一个动了起来,于是它变成了两个。 这个思路比这个装置值钱——硬件不够的时候,先别急着买,想想能不能让已有的东西换个位置、换个时间、换个角度,去多拿一份信息。

还有那个环。 前面二十几节课,你的闭环都是"一条线",走完就结束了。今天你第一次做出了一个真正的圈:输出改变了输入,输入又决定下一个输出。这个圈,是"自动"和"智能"之间的那道分界线。

下一节课,我们玩点热闹的:打地鼠游戏机。

按钮、OLED、蜂鸣器——这次你不再是"做实验的人",你是"设计游戏的人"。 你要自己想清楚:地鼠什么时候冒出来、冒多久、打中了怎么算、打不中怎么算。

而且我要提醒你一件事:游戏机有个特别的要求——它得"及时"。 你按下去,它得马上反应。可你的程序里到处是 delaydelay 的时候板子什么都听不见。 这是个新麻烦。下节课见。