太阳能追光路灯实现左右追光
315.10、太阳能追光路灯实现左右追光
#include <Servo.h> // 引入 Arduino Servo 库,使草图可以创建并控制舵机对象
#define right 15 // 右侧光敏模块的 AO 接 Uno 的 A1;在本板核心中 A1 的数字别名是 15
#define left 16 // 左侧光敏模块的 AO 接 Uno 的 A2;在本板核心中 A2 的数字别名是 16
#define up 17 // 上侧光敏模块的 AO 接 Uno 的 A3;本节只采样,尚未参与俯仰判断
#define down 14 // 下侧光敏模块的 AO 接 Uno 的 A0;本节只采样,尚未参与俯仰判断
#define right_left_servopin 9 // 左右旋转舵机的信号线接数字引脚 D9
#define up_down_servopin 10 // 上下俯仰舵机的信号线接数字引脚 D10
Servo right_left_servo; // 创建控制左右旋转轴的 Servo 对象
Servo up_down_servo; // 创建控制上下俯仰轴的 Servo 对象
int right_size=0; // 保存右侧光敏模块本轮得到的 ADC 数值
int left_size=0; // 保存左侧光敏模块本轮得到的 ADC 数值
int up_size=0; // 保存上侧光敏模块本轮得到的 ADC 数值
int down_size=0; // 保存下侧光敏模块本轮得到的 ADC 数值
int angle1=90; // 左右轴目标角度初值为 90°,即本项目校准后的中间位置
int range1=180; // 保存左右轴名义范围 180°;视频最终代码中该变量尚未被实际引用
int angle2=0; // 上下轴目标角度初值为 0°,即本项目约定的朝上位置
int range2=160; // 保存上下轴计划上限 160°;本节尚未编写上下追光判断
void setup() { // Arduino Uno 上电或复位后执行一次初始化
pinMode(right,INPUT); // 把右侧模块对应的 A1 明确设置为输入
pinMode(left,INPUT); // 把左侧模块对应的 A2 明确设置为输入
pinMode(up,INPUT); // 把上侧模块对应的 A3 明确设置为输入
pinMode(down,INPUT); // 把下侧模块对应的 A0 明确设置为输入
right_left_servo.attach(right_left_servopin); // 将左右轴舵机对象关联到 D9 信号引脚
up_down_servo.attach(up_down_servopin); // 将上下轴舵机对象关联到 D10 信号引脚
right_left_servo.write(angle1); // 初始化时命令左右轴转到 angle1 所保存的 90°
up_down_servo.write(angle2); // 初始化时命令上下轴转到 angle2 所保存的 0°
} // setup() 初始化结束
void loop() { // 主循环会不断重复采样、判断和控制
right_size=analogRead(right); // 读取右侧 A1 的 10 位 ADC 数值
left_size=analogRead(left); // 读取左侧 A2 的 10 位 ADC 数值
up_size=analogRead(up); // 读取上侧 A3 的 ADC 数值,为下一节上下追光预留
down_size=analogRead(down); // 读取下侧 A0 的 ADC 数值,为下一节上下追光预留
if(right_size-left_size > 150) // 若右侧读数比左侧大 150 以上,说明左侧明显更亮
{ // 进入向左调整的分支
angle1=angle1 - 10; // 把左右轴目标角度减小 10°
angle1= constrain(angle1,0,180); // 将计算结果限制在 0°~180°,并写回 angle1
right_left_servo.write(angle1); // 向左右轴舵机发送更新后的目标角度
}else // 若左右差值没有满足本次向左调整条件
{ // 进入保持分支
right_left_servo.write(angle1); // 再次发送当前目标角度,使左右轴保持原位置
} // 第一个方向判断结束
if(left_size-right_size > 150) // 若左侧读数比右侧大 150 以上,说明右侧明显更亮
{ // 进入向右调整的分支
angle1=angle1 + 10; // 把左右轴目标角度增加 10°
angle1= constrain(angle1,0,180); // 将计算结果限制在 0°~180°,并写回 angle1
right_left_servo.write(angle1); // 向左右轴舵机发送更新后的目标角度
}else // 若左右差值没有满足本次向右调整条件
{ // 进入保持分支
right_left_servo.write(angle1); // 再次发送当前目标角度,使左右轴保持原位置
} // 第二个方向判断结束
delay(500); // 阻塞等待 500 ms,再开始下一轮采样和判断
} // 本轮 loop() 结束,随后自动从 loop() 开头继续
一、太阳能追光路灯编程控制
上一节完成机械组装后,本节把第 313 节的四路 ADC 采样框架与前面课程的双舵机控制代码合并,先实现水平方向的左右追光。视频中最终完成编译、上传并用手电筒验证的就是页首草图;它已经读取上、下两路传感器并初始化俯仰舵机,但还没有根据 up_size、down_size 控制上下转动。
课程配套最终 6.ino 在本节代码后面还有上下追光判断,那是后续课程才添加的内容,因此没有提前放进页首源码。页首只去除了教师原注释并增加逐行解释,变量名、数值、判断顺序和两个独立的 if...else 块均按视频最终上传状态保留,没有擅自优化。
本节程序形成的闭环可以先概括为:
左、右光敏模块的 AO
↓ analogRead()
比较 right_size 与 left_size 的差值
↓ 差值超过 150 才调整
angle1 每次增加或减少 10°
↓ constrain() 限制数值范围
D9 上的左右轴舵机改变太阳能板朝向
↓
传感器在新朝向下再次采样
1.1 AD 采样
1.1.1 从已有示例集成本节草图
视频开始时,教师分别打开了前面写过的 ADC 采样草图和舵机示例,并依次移植:
#include <Servo.h>;- 两个
Servo对象及其attach()初始化; - 四只光敏模块的引脚、采样变量和
analogRead(); - 两只舵机的目标角度变量及初始
write(); - 左右差值判断、角度增减、
constrain()和 500 ms 延时。
这不是把两个程序机械拼在一起:复制后还需要把单舵机示例中的 myservo、pos 等占位名称改成当前项目的 right_left_servo、up_down_servo、angle1 和 angle2。视频编辑过程中曾短暂出现名称未改、分号未写、四路都读取 right 等中间状态;这些都没有被当作最终源码收录。
教师还删除了原单路采样代码中的 Serial.begin() 和 Serial.println(),所以本节运行时不会在串口监视器打印四路数值。程序会读取 ADC,但没有把结果发送给电脑;调试时若要知道具体数值,需要另写串口输出代码,不能误以为 analogRead() 会自动显示结果。
1.1.2 四路采样的含义
每轮 loop() 都执行四次 analogRead():
| 变量 | 物理方向 | Uno 插孔 | 视频本节是否参与动作判断 |
|---|---|---|---|
right_size | 右 | A1 | 是,与左侧比较 |
left_size | 左 | A2 | 是,与右侧比较 |
up_size | 上 | A3 | 否,只采样 |
down_size | 下 | A0 | 否,只采样 |
Uno R3 的 ADC 默认返回 0~1023 的 10 位结果。变量名中的 _size 只是教师采用的名称,保存的不是光敏电阻尺寸,也不是欧姆值或 lux,而是模块 AO 电压经 ADC 转换后的数字码。
第 313 节已用本项目模块实测:遮挡后读数约升到 500~600,强光近照时可降到 20~30。因此,本节特定模块和接法表现为越亮读数越小,越暗读数越大。这不是所有光敏模块都必然具有的极性;换模块或改变分压接法后,应先重新做遮挡/照射测试。
1.2 定义光敏电阻引脚
1.2.1 实际定义与 Uno 模拟口名称
原始笔记所说的“四个引脚都定义为 16 号”不正确。视频最终定义为:
| 宏名 | 源码数值 | Uno R3 等价名称 | 接线图方向 |
|---|---|---|---|
down | 14 | A0 | 下 |
right | 15 | A1 | 右 |
left | 16 | A2 | 左 |
up | 17 | A3 | 上 |
在课程所选的 Arduino Uno AVR 核心中,A0~A3 同时具有数字别名 14~17,所以 analogRead(15) 等价于在这块板上读取 A1。这个数字别名属于具体开发板核心的引脚映射,不是所有 Arduino 板都保证相同;若代码改到其他板型,直接写 A0、A1、A2、A3 通常更直观。不过,README 要求保留课程源码,本节仍原样使用 14~17。
配套接线图和最终 6.ino 在这里是一致的,都是“下 A0、右 A1、左 A2、上 A3”。后续查线时仍建议逐只遮挡确认物理方向,因为标签贴错或杜邦线交叉同样会让变量名与实物不一致。
1.2.2 宏名不能与对象名重复
#define right 15 是预处理宏:正式编译前,源码中的独立标识符 right 会被替换为 15。因此不能再写 Servo right;,否则预处理后会变成无效声明。视频中教师发现名称可能重合后,最终使用了更具体的对象名:
right_left_servo:左右轴舵机对象;up_down_servo:上下轴舵机对象;right_left_servopin:左右轴信号引脚常量;up_down_servopin:上下轴信号引脚常量。
原始笔记把这段概括为“删除 pin”,容易造成误解。真正需要避免的是同一作用域内的标识符冲突和宏替换冲突,并不是单词 pin 本身不能使用。
1.3 定义舵机引脚
1.3.1 D9、D10 与两个 Servo 对象
本项目的舵机信号分工为:
| Servo 对象 | 信号引脚 | 机械轴 | 本节动作 |
|---|---|---|---|
right_left_servo | D9 | 下层水平旋转轴 | 根据左右光差自动转动 |
up_down_servo | D10 | 上层俯仰轴 | 只在初始化时转到 0°,没有自动上下追光 |
attach() 的作用是把软件对象关联到指定信号引脚;write() 写入的是目标位置命令。D9、D10 在这里传送舵机控制脉冲,不是给舵机供电的电源脚,也不能因为 Uno 板上把它们标成 PWM 引脚,就把 Servo 库控制简单理解为普通 analogWrite() 占空比输出。
setup() 末尾先发送 angle1=90 和 angle2=0。因此上电或按复位键后,程序都会重新运行 setup(),命令左右轴回到 90°、上下轴回到 0°。这只是重新发送软件设定值,不是读取了舵机真实位置,也不是机械限位开关执行的“寻零”;若舵盘安装角度不对或机构被卡住,复位不会自动修正机械问题。
1.3.2 角度和范围变量
视频保留了四个变量:
angle1=90:左右轴当前命令值;本项目约定 0°为左端、90°为中间、180°为右端。range1=180:记录左右轴名义上限,但本节constrain(angle1,0,180)直接写了常量 180,所以range1没有实际参与运行。angle2=0:上下轴初始命令值;本项目约定 0°为朝上。range2=160:为后续俯仰控制准备的上限,本节尚未引用。
代码里的 0°和 180°是 Servo API 的命令范围,不等于装好太阳能板后的机构一定能无碰撞走满全程。实际安全角度还受舵盘装配、支架、灯杆和线束限制;若舵机持续嗡鸣、发热或机构顶住,应立即断电并缩小软件范围,不能依靠 constrain() 解决机械干涉。
1.3.3 pinMode() 与初始化顺序
视频把四个模拟口显式设置为 INPUT,用于表达它们是采样输入。对 Uno 而言,复位后的引脚本来就是输入,analogRead() 也可直接读取模拟口;这四句并非 ADC 能工作的必要前提,但属于合法且清晰的课程写法。不要改成 INPUT_PULLUP,内部上拉会参与模块输出网络并可能改变 ADC 读数。
较安全的调试顺序是先断电核对机械空间和线序,再上电让 setup() 给出初始角度,最后测试光照变化。已通电时不要手扳云台,也不要在机构顶住时反复按复位。
1.4 约束函数介绍
1.4.1 constrain() 的准确作用
Arduino 的 constrain(x, a, b) 返回规则是:
| 输入关系 | 返回值 |
|---|---|
x < a | a |
a <= x <= b | x |
x > b | b |
它返回受限后的值,不会凭空修改变量,所以代码必须写成 angle1 = constrain(angle1,0,180); 才会把结果保存回 angle1。本节在每次加减 10°后进行限制,使软件变量不会继续下降到负数或上升到 180 以上。
这里应纠正视频中的一个因果说法:超出软件角度范围不会直接让“程序死机”,delay(500) 也不是防死机机制。真正的风险是机构撞限位后舵机仍试图运动,可能持续大电流、发热并拖低供电电压,进而造成 Uno 复位或 USB 断连。软件限幅、机械范围校准和可靠供电需要同时具备。
1.4.2 左右追光判断
在本项目模块“亮时读数小”的前提下,代码逻辑如下:
| 条件 | 光照解释 | angle1 变化 | 机械动作 |
|---|---|---|---|
right_size-left_size > 150 | 右侧更暗、左侧更亮 | -10° | 向左转 |
left_size-right_size > 150 | 左侧更暗、右侧更亮 | +10° | 向右转 |
| 两个差值都不大于 150 | 左右差异落在死区内 | 不变 | 保持当前位置 |
阈值使用严格的大于号,所以差值恰好为 150 时也保持不动。若令
$ d=right_size-left_size $
则这段控制可以读成:$d>150$ 时左转,$d<-150$ 时右转,$-150\le d\le150$ 时保持。这个 ±150 的区间就是死区,可减少传感器小幅波动导致的左右反复抖动;它是课程样机采用的经验值,不是所有环境都通用的物理常数。
源码使用两个独立的 if...else,没有写成 if...else if...else。由于同一次采样中不可能同时满足“右减左大于 150”和“左减右大于 150”,两个调整分支不会同时执行;每个 else 中再次写入当前 angle1 是冗余但无害的保持命令。按照源码原样保留,不在精校稿中替教师重构。
1.4.3 10°步进和 500 ms 延时
每次触发只改变 10°,随后 delay(500) 让主循环暂停 500 ms,因此新的追光判断最多约每秒两次;连续偏向一侧时,命令角度大约以每秒 20°的步幅变化。实际舵机轴的运动速度还取决于舵机型号、负载和供电,不能用这个数字断言机械速度恰好是 20°/s。
延时降低了更新频率,便于观察并减少过快连续改命令,但它会阻塞整个 loop():等待期间既不重新采样,也不执行其他循环业务。固定阈值、固定步长和阻塞延时足够完成本节演示,却不代表是高精度太阳跟踪控制器。
1.5 接线图展示
1.5.1 四只光敏模块
配套总接线图对光敏模块线色的说明为:黄色是 AO 信号、黑色是 GND、红色是 5V。本节四路完整关系如下:
| 模块位置 | AO 接 Uno | 源码定义 | VCC | GND | DO |
|---|---|---|---|---|---|
| 下 | A0 | down = 14 | 5V 扩展排针 | GND 扩展排针 | 不接 |
| 右 | A1 | right = 15 | 5V 扩展排针 | GND 扩展排针 | 不接 |
| 左 | A2 | left = 16 | 5V 扩展排针 | GND 扩展排针 | 不接 |
| 上 | A3 | up = 17 | 5V 扩展排针 | GND 扩展排针 | 不接 |
程序读取的是每块模块的模拟输出 AO,不是数字比较输出 DO。四块模块必须与 Uno 共地,否则 A0~A3 没有共同电压参考。线色只是课程接线图的约定,实际接线仍应以模块 PCB 丝印为准,并在断电状态下插拔。
1.5.2 两只舵机和供电
配套图把舵机黄色线标为信号、红色线标为 5V、黑色线标为 GND,并给出:
左右旋转舵机:信号 → D9
上下俯仰舵机:信号 → D10
两只舵机电源正极 → 5V
两只舵机地线 → GND
课程兼容板加焊了多组并联的 5V、GND 扩展排针,作用只是提供更多插接位置,不会增加 USB 口或板载电源能够提供的总电流。两只舵机同时启动、负载过重或堵转时可能造成电压下降、开发板复位或导线发热。Arduino Servo 官方资料建议,驱动较多舵机时考虑规格合适的独立电源;采用外部舵机电源时,外部电源负极必须与 Uno GND 共地,且不能把舵机电源接到普通 GPIO。
视频的短时桌面演示按照课程图接在板上扩展供电口并成功运行,但这不能证明任意 USB 口、任意兼容板或长期运行都具有足够余量。首次通电应扶稳支架,观察是否出现舵机持续嗡鸣、开发板反复复位、USB 断连、线材或稳压器异常发热。
1.5.3 上传和左右追光实测
视频中的验证顺序为:
- 依据总接线图接好两只舵机和四只光敏模块;本节动作判断实际使用左右两只模块。
- 插入 USB 数据线,在 Arduino IDE 中选择当前 Uno 和实际串口。
- 编译并上传页首草图。
- 用手电筒从右侧照射,太阳能板向右转;从左侧照射,太阳能板向左转。
- 按下板上的红色复位按钮,
setup()重新执行,两个舵机收到 90°和 0°的初始命令。
演示证明当前样机的左右传感器方位、D9 舵机方向和两条差值判断能够配合工作。它没有演示自动上下追光,也没有测量太阳能板输出功率;因此本节结论应限定为“左右轴能够朝手电筒方向逐步跟随”,不能扩大成完整二维追光已经编程完成。
核对依据
- 本地
videos/315.10、太阳能追光路灯实现左右追光.mp4:逐帧核对代码从 ADC 与舵机示例合并、变量更名、最终编译上传、总接线图、实物接线、手电筒左右测试和复位效果;确认本节最终代码只含左右判断。 - 本地
transcripts/315.10、太阳能追光路灯实现左右追光.txt与.srt:核对讲解顺序、150 阈值、10°步进、500 ms 延时和现场接线口述,并结合画面纠正“阻值”“14 号等于 A2”“防止程序死机”等不准确表述。 - 本地配套
太阳能追光路灯.pptx、接线图.jpg与最终6.ino:核对四只光敏模块和两只舵机的最终引脚关系;最终源码只用于确认本节已出现部分的最终拼写,不提前收录后续上下追光判断。 - Arduino Uno R3 官方页面:核对 Uno R3 具有 6 路模拟输入以及板卡基本资源。
- Arduino AVR Core 的 Uno 标准引脚映射:核对 A0~A3 在本课程 Uno 核心中分别对应数字别名 14~17。
- Arduino 官方 API 概览:核对 Uno 上
analogRead()返回 0~1023 的 10 位结果,以及pinMode()、delay()等接口的用途。 - Arduino 官方
constrain()参考:核对输入小于下限、位于区间内和大于上限时的三个返回规则。 - Arduino Servo 库 API:核对
attach()与write()的含义和目标角度范围。 - Arduino Servo 库说明:核对舵机三线连接、舵机供电电流注意事项以及外部供电时必须共地。