基于CircuitPython与CPX的智能书本灯:光感自动照明与舵机调角DIY
1. 项目概述:一个会思考的阅读伴侣
深夜阅读时,突然变暗的光线总是让人措手不及,要么得起身开灯打断思绪,要么就得在昏暗的光线下眯着眼睛,体验实在不佳。作为一个喜欢折腾硬件和嵌入式项目的爱好者,我一直在想,能不能做一个足够“聪明”的小工具,让它能感知环境,自动为我点亮书页,甚至还能根据我的姿势调整光线方向?这个想法最终催生了这个基于Adafruit Circuit Playground Express(后文简称CPX)的智能书本照明装置。
这个装置的核心,是一个能“看见”光、“听懂”指令的微控制器大脑。它利用CPX开发板上集成的光线传感器,实时监测你周围的环境亮度。一旦检测到光线低于一个舒适的阅读阈值,它就会自动点亮一圈高亮度的NeoPixel LED,将柔和的光线投射到书页上,整个过程无需你任何手动操作。这解决了“被动响应”的问题。更进一步,我还为它增加了一个微型舵机(伺服电机),通过一个简单的按钮,你就能指挥这个小巧的机械臂转动,从而改变光线的照射角度,无论是平放书本还是略有倾斜,都能找到最合适的照明位置。最后,通过拨动CPX上的一个物理开关,你还能在专注阅读的暖白色光和护眼的淡蓝色光之间切换,适应不同的阅读场景和心情。
整个项目融合了 嵌入式系统 的感知与控制、 物联网 设备的交互逻辑,以及通过 3D打印 实现的个性化结构设计。它非常适合对硬件DIY、 微控制器编程 感兴趣的初学者或有一定经验的创客。你不需要复杂的电路焊接,CPX的“All-in-One”设计让传感器、LED和按钮都触手可及;代码部分我们使用易于上手的CircuitPython语言;结构件通过3D打印完成,你可以轻松定制尺寸和外观。接下来,我将从设计思路、材料准备、代码编写、结构组装到调试优化,完整地拆解这个项目的每一个环节,并分享我在制作过程中踩过的坑和总结出的实用技巧。
2. 核心硬件选型与设计思路解析
2.1 为什么选择Adafruit Circuit Playground Express?
在启动一个嵌入式项目时,开发板的选择至关重要,它决定了项目的复杂度、开发门槛和扩展性。对于这个智能书本灯,我选择了Adafruit Circuit Playground Express(CPX),这几乎是为此类互动项目量身定做的开发板。
首先,CPX是一个高度集成的开发平台。它板载了本项目所需的所有关键传感器和执行器:一个光线传感器(用于检测环境亮度)、10个可编程的全彩RGB NeoPixel LED(用于照明)、两个物理按钮(A和B,用于控制舵机)、一个滑动开关(用于切换灯光模式)。这意味着我们无需额外购买和焊接任何传感器模块,大大降低了硬件连接的复杂度和出错概率,特别适合新手。其次,CPX支持CircuitPython编程。与传统的Arduino C++相比,CircuitPython语法更接近Python,更加简洁易懂,交互式串行终端(REPL)能让你实时测试代码片段,调试效率极高。最后,CPX设计坚固,带有鳄鱼夹引脚孔,可以直接用鳄鱼夹连接外部设备(如我们的舵机),无需焊接,进一步提升了项目的可访问性和安全性。
注意 :市面上有标准版(Circuit Playground Express)和简化版(Circuit Playground Bluefruit)等型号。务必确认你拿到的是“Express”版本,因为它内置了光线传感器,这是本项目自动光控功能的核心。Bluefruit版本侧重于蓝牙通信,缺少光线传感器。
2.2 执行机构:微型舵机的选型与控制逻辑
为了让灯光能够转动,我们需要一个执行机构。这里我选择了常见的9克微型舵机。这类舵机体积小、重量轻、扭矩适中,非常适合驱动一个轻量级的灯头结构。舵机的工作原理是通过接收脉冲宽度调制(PWM)信号来控制其输出轴旋转到指定角度。CPX的任何一个支持模拟输出的引脚(如A1、A2、A3等)都可以生成PWM信号。
在控制逻辑上,我设计了一个简单的“两点位”控制:按下A键,舵机旋转到0度位置(例如,灯光垂直向下照射);按下B键,舵机旋转到45度位置(灯光以一定角度斜射)。这种设计避免了复杂的连续调节,操作直观。关键在于,我们需要在代码中“记住”舵机的当前角度,并在每次按键时进行累加或设定,确保动作准确。舵机的供电需要特别注意,虽然可以直接从CPX的VOUT引脚取电(提供3.3V),但对于某些扭矩要求稍高或启动电流较大的舵机,3.3V可能驱动力不足,导致抖动或不转。更稳妥的方案是使用一个外部的3-5V电源(如电池盒)单独为舵机供电,但需要共地(GND)。
2.3 结构设计:3D打印件的功能与考量
电子部分决定了装置的“智商”,而机械结构则决定了它的“体格”和实用性。我采用3D打印来制作支撑结构,主要基于其快速原型、可定制化的优势。整个结构由两个主要部件组成,我称之为“底座”(Print A)和“灯臂”(Print B)。
底座(Print A) 是整个装置的基座和“心脏舱”。它的核心设计是一个内凹的电池仓,用于容纳为CPX供电的3节AAA电池盒。这个仓位的尺寸需要精确计算,既要能稳稳卡住电池盒,又要为电线留出足够的空间。底座背面还设计了一个平台,用于粘贴固定舵机。 灯臂(Print B) 是一个较薄的片状结构,一端与底座铰接(通过胶合),另一端则用于固定CPX开发板。它的作用是作为“脖子”,将CPX及其LED灯光“伸”到书本上方。在灯臂与底座连接处,我设计了一个小的凸起和卡槽,确保两者在粘合后能保持固定的相对角度,使灯臂能稳定地跨在书本的书脊上。
在设计思路上,我遵循了“功能分离”和“便于组装”的原则。电子部分(CPX、舵机、电池)全部集成在底座,维护方便;发光部分(CPX板)位于灯臂末端,照明范围最佳。整个结构通过卡扣和胶水固定,无需螺丝,简化了组装流程。打印材料建议使用PLA,它强度足够、易于打印且环保。
3. 软件开发:CircuitPython代码深度剖析
代码是项目的灵魂,它定义了设备如何感知、思考和行动。我们将使用CircuitPython在Mu Editor环境中进行开发。下面我将逐模块解析代码,并解释其背后的逻辑。
3.1 开发环境搭建与库导入
首先,确保你的CPX已经刷入了最新的CircuitPython固件(可从Adafruit官网下载)。然后安装Mu Editor,这是一个对CircuitPython非常友好的集成编辑器。
代码开头是必要的库导入。CircuitPython的强大之处在于其丰富的库支持。
import time
import board
import analogio
import digitalio
import pwmio
from adafruit_motor import servo
from adafruit_circuitplayground import cp
time: 用于控制延时,例如防按键抖动。board/analogio/digitalio/pwmio: 这些都是CircuitPython的核心硬件访问库,用于管理引脚。adafruit_motor.servo: 这是Adafruit提供的专用舵机库,它封装了PWM控制细节,让我们能用简单的.angle属性来控制舵机,非常方便。adafruit_circuitplayground.cp: 这是CPX的专用库,它提供了访问板上所有传感器、LED和按钮的高级接口。例如,cp.light就能直接读取光线传感器数值,cp.pixels用于控制LED,cp.button_a和cp.switch用于读取按钮和开关状态。
3.2 光敏传感与自动点亮逻辑
自动点亮功能的核心是持续监测环境光强度。
# 光线传感器阈值设定
LIGHT_THRESHOLD = 10
# 灯光颜色定义 (R, G, B)
WHITE = (50, 50, 50) # 白色光,亮度适中
BLUE = (0, 0, 30) # 蓝色光,亮度稍低以护眼
current_light_color = WHITE
leds_on = False
while True:
# 1. 读取环境光线
light_level = cp.light
# 2. 自动光控逻辑
if light_level < LIGHT_THRESHOLD and not leds_on:
# 环境变暗,且灯当前是关的,则打开灯
cp.pixels.fill(current_light_color)
leds_on = True
print("光线暗,灯已打开,亮度级:", light_level)
elif light_level >= LIGHT_THRESHOLD and leds_on:
# 环境变亮,且灯当前是开的,则关闭灯
cp.pixels.fill((0, 0, 0))
leds_on = False
print("光线足,灯已关闭")
# ... 其他逻辑(按钮、开关检测)
time.sleep(0.1) # 短暂延时,降低CPU占用
关键点解析 :
- 阈值(LIGHT_THRESHOLD)的选择 :数值
10是一个经验值。cp.light的读数范围大致在0(极暗)到几百(明亮室内)之间。你需要在实际使用环境中测试:在你觉得需要开灯的昏暗环境下,打印出light_level的读数,将这个读数稍微调高一点作为阈值。阈值设置过低,灯反应迟钝;过高,则可能在还比较亮的时候就点亮。 - 状态变量(
leds_on)的重要性 :我们使用一个布尔变量leds_on来记录LED的当前状态。这是为了防止代码在阈值附近反复触发,导致灯光频繁闪烁。只有状态发生改变时(从开到关,或从关到开),我们才执行点亮或熄灭操作。 - 颜色与亮度 :
WHITE = (50, 50, 50)并非纯白(255,255,255),因为CPX的NeoPixel在最高亮度下非常刺眼,且耗电快。50左右的亮度对于阅读来说足够柔和。蓝色光也降低了亮度,以减少视觉疲劳。
3.3 舵机角度控制与按键去抖动
控制舵机旋转到特定角度。
# 初始化舵机(假设连接在A2引脚)
pwm = pwmio.PWMOut(board.A2, frequency=50)
my_servo = servo.Servo(pwm, min_pulse=500, max_pulse=2500)
# 舵机角度定义
ANGLE_CLOSE = 0
ANGLE_OPEN = 45
current_angle = ANGLE_CLOSE # 初始角度
my_servo.angle = current_angle
# 按键状态记录,用于去抖动
button_a_pressed = False
button_b_pressed = False
while True:
# ... 光线检测逻辑 ...
# 3. 按键控制舵机逻辑(带去抖动)
if cp.button_a and not button_a_pressed:
# A键被按下(下降沿触发)
button_a_pressed = True
current_angle = ANGLE_CLOSE
my_servo.angle = current_angle
print("按下A键,舵机转到0度")
time.sleep(0.3) # 简单延时防抖,并防止连续触发
elif not cp.button_a and button_a_pressed:
# A键被释放
button_a_pressed = False
if cp.button_b and not button_b_pressed:
# B键被按下
button_b_pressed = True
current_angle = ANGLE_OPEN
my_servo.angle = current_angle
print("按下B键,舵机转到45度")
time.sleep(0.3)
elif not cp.button_b and button_b_pressed:
button_b_pressed = False
# ... 开关检测逻辑 ...
关键点解析 :
- PWM参数 :
frequency=50是标准舵机控制频率。min_pulse和max_pulse定义了脉宽范围(单位微秒),对应舵机的最小和最大角度。大多数9克舵机适用500-2500这个范围。如果舵机转动角度不准确(例如只能转90度而不是180度),可以尝试调整这两个值。 - 按键去抖动 :物理按键在按下和释放的瞬间,会产生机械抖动,导致微控制器误判为多次按下。这里采用了一种简单的“边沿检测+状态锁存”方法。我们用
button_a_pressed变量记录“按键是否已被处理过”。只有当检测到按钮被按下(cp.button_a为真)且之前未被记录为按下(button_a_pressed为假)时,才执行舵机转动,并将状态标记为已按下。直到检测到按钮释放,才重置状态。配合time.sleep(0.3),能有效消除抖动影响。 - 角度管理 :使用
current_angle变量记录当前角度,方便后续如果需要增加多角度档位或平滑移动功能时进行扩展。
3.4 灯光模式切换与滑动开关应用
利用CPX上的滑动开关来切换灯光颜色模式。
# 模式状态
MODE_WHITE = 0
MODE_BLUE = 1
current_mode = MODE_WHITE
while True:
# ... 光线和按键逻辑 ...
# 4. 滑动开关切换模式
if cp.switch:
# 开关拨到一侧(例如“开”或“左”)
if current_mode != MODE_BLUE:
current_mode = MODE_BLUE
current_light_color = BLUE
print("切换到蓝色模式")
# 如果灯当前是亮的,立即更新颜色
if leds_on:
cp.pixels.fill(current_light_color)
else:
# 开关拨到另一侧
if current_mode != MODE_WHITE:
current_mode = MODE_WHITE
current_light_color = WHITE
print("切换到白色模式")
if leds_on:
cp.pixels.fill(current_light_color)
time.sleep(0.1)
关键点解析 :
cp.switch返回一个布尔值,代表开关的位置。你需要通过实验确定哪一边对应True,哪一边对应False。通常,拨动开关远离板载复位按钮的一侧为True。- 与按键处理类似,我们通过
current_mode变量来避免在开关位置未变时重复执行切换操作。只有当检测到模式实际发生变化时,才更新current_light_color,并立即应用到正在点亮的LED上(如果灯是开的状态),实现无缝切换。
将以上所有代码段整合到一个完整的 code.py 文件中,保存到CPX的CIRCUITPY驱动器,设备就会自动运行。
4. 硬件连接与结构组装实操指南
4.1 舵机与CPX的电路连接
这是整个项目中唯一的电路连接部分,非常简单但必须准确。
- 识别舵机线缆 :微型舵机通常有三根线:红色(电源VCC)、棕色或黑色(地线GND)、橙色或黄色(信号线SIG)。
- 连接鳄鱼夹 :将三个鳄鱼夹分别夹在舵机线的金属端子上。确保夹紧,避免虚接。
- 连接到CPX :
- 红色线(VCC) -> 连接到CPX板上标记为
VOUT的引脚。VOUT提供与USB或电池相同的电压(约3.3V-4.2V),能为舵机供电。 - 棕色/黑色线(GND) -> 连接到CPX板上任意一个标记为
GND的引脚(接地)。 - 橙色/黄色线(SIG) -> 连接到CPX板上标记为
A2的引脚(或你在代码中board.A2指定的其他引脚,如A1、A3)。这个引脚负责发送PWM控制信号。
- 红色线(VCC) -> 连接到CPX板上标记为
重要提示:供电不足问题 。如果你发现舵机在接到
VOUT后动作无力、不转或导致CPX重启,这说明舵机启动电流过大。 解决方案 :使用一个独立的3-5V电源(如另一个电池盒)为舵机供电。将舵机的红色线接独立电源的正极,舵机的黑色线和独立电源的负极都接到CPX的GND(共地),舵机信号线(黄色)仍接CPX的A2。切勿将外部电源正极接到CPX的VOUT。
4.2 3D打印件的准备与后处理
- 获取与调整模型 :你可以使用提供的STL文件直接打印。我更推荐使用Tinkercad或Fusion 360等工具打开模型,根据你实际书本的厚度,微调“灯臂”(Print B)的宽度和底座卡槽的尺寸。一个通用的尺寸是底座长约9cm,宽约7cm,灯臂长度约8cm。
- 打印参数建议 :
- 材料 :PLA。它打印成功率高,无异味,强度足够。
- 层高 :0.2mm。在保证打印速度的同时,能获得较好的表面质量。
- 填充密度 :15%-20%。对于这个小型结构,这个填充率提供了足够的强度而不浪费材料和时间。
- 支撑 : 底座(Print A)的电池仓部分必须开启支撑 !因为仓口是悬空结构。灯臂(Print B)通常不需要支撑。确保在切片软件(如Cura)中正确设置。
- 后处理 :打印完成后,小心地移除所有支撑材料。用砂纸轻轻打磨结合面(底座粘接面和灯臂粘接面),使其平整,这样粘合更牢固。用刷子或气吹清理碎屑。
4.3 分步组装流程
组装顺序很重要,错误的顺序可能导致设备无法安装或维修困难。
步骤一:粘合主体结构 将清理好的底座(Print A)和灯臂(Print B)的粘合面对齐。 关键点 :确保底座上带有电池仓的一面朝上(即仓口朝天),灯臂的延伸部分朝向底座外侧。使用热熔胶枪,在结合面均匀涂抹一条胶水,迅速将两者压紧,保持约30秒直到胶水固化。检查是否粘正,灯臂是否与底座平面垂直。
步骤二:固定舵机 将微型舵机用热熔胶固定在底座背面的指定平台(通常是左上角)。确保舵机的输出轴朝向底座外侧,并且舵机机身粘贴牢固,无晃动。可以将舵机自带的白色舵盘(舵臂)暂时安装到输出轴上。
步骤三:安装CPX与连接
- 将CPX开发板 屏幕朝下 (即有LED灯的一面朝向书本)放置在灯臂的末端。用热熔胶在CPX板子边缘(避开元器件和接口)点几个小点,将其固定在灯臂上。 注意 :不要涂太多胶,以免影响散热或日后拆卸。
- 将舵机的三根线(已连接鳄鱼夹)按照“4.1”部分的说明,连接到CPX的对应引脚。整理线缆,可以用扎带或胶带将多余的线缆固定在底座背面,使其整洁。
- 将3节AAA电池盒放入底座的电池仓。如果感觉过紧,可以按照原始提示,剥掉电池盒的外层塑料皮(如果安全允许),或者稍微用砂纸打磨一下电池仓内壁。将电池盒的插头连接到CPX的电源接口。
步骤四:最终调试与固定
- 打开电池开关,CPX应启动(电源LED亮起)。此时,用手遮挡CPX上的光线传感器,LED灯带应自动亮起。
- 按下A键和B键,观察舵机是否在0度和45度之间转动。如果不转,检查接线和代码中引脚定义。
- 拨动滑动开关,检查灯光颜色是否在白色和蓝色之间切换。
- 一切正常后,你可以设计一个更长的舵臂(将两个白色小舵臂粘在一起),并将其连接到舵机输出轴,作为“灯头”的调节杆。最后,可以将整个装置轻轻卡在合上的书本书脊处进行测试。
5. 调试优化与进阶玩法
5.1 常见问题排查速查表
制作过程中,你可能会遇到以下问题。这里提供一个快速排查指南。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| CPX完全不上电 | 1. 电池没电或装反。 2. 电池盒开关未打开。 3. 电池盒插头接触不良。 |
1. 用万用表测电池电压,或更换新电池。 2. 确认开关已拨到“ON”。 3. 重新插拔CPX电源接口,检查插头内针是否弯曲。 |
| LED灯不自动亮 | 1. 光线传感器阈值( LIGHT_THRESHOLD )设置过高。 2. 代码中光线传感器读取有误。 3. 传感器被遮挡或损坏。 |
1. 在Mu Editor的串行终端查看 light_level 实时值,在昏暗环境下调整阈值。 2. 检查代码是否正确定义了 cp.light 。 3. 确保CPX正面(有LED的一面)朝向书本,传感器未被胶水覆盖。 |
| 舵机不转动或抖动 | 1. 供电不足(最常见)。 2. 信号线连接错误。 3. PWM引脚或参数错误。 4. 舵机卡死。 |
1. 优先尝试外接电源单独给舵机供电 (见4.1提示)。 2. 检查黄线是否接在代码指定的信号引脚(如A2)。 3. 检查代码中 board.A2 是否正确,尝试调整 min_pulse / max_pulse 。 4. 手动轻轻转动舵机齿轮,检查是否有机械阻力。 |
| 按键控制不灵敏或连发 | 1. 按键去抖动代码未生效。 2. 按键接触不良。 |
1. 检查代码中按键状态变量( button_a_pressed )的逻辑,确保是“边沿触发”。 2. 在Mu串行终端中打印 cp.button_a 和 cp.button_b 的值,观察按下时是否稳定为 True 。 |
| 灯光颜色无法切换 | 1. 滑动开关位置判断错误。 2. 模式切换逻辑有误。 |
1. 在代码中打印 cp.switch 的值,拨动开关,确认 True / False 对应的物理位置。 2. 检查 if cp.switch: 和 else: 分支内的颜色更新逻辑,确保 current_light_color 被正确赋值并应用于 cp.pixels.fill() 。 |
| 结构不稳定,容易倾倒 | 1. 底座太轻或重心太高。 2. 书本太薄或太厚。 |
1. 在底座底部粘贴一些配重(如几枚硬币),或增大底座与桌面的接触面积。 2. 重新设计或调整3D模型,使卡槽更贴合你的常用书本厚度。 |
5.2 性能优化与功能扩展
基础功能实现后,你可以从以下几个方面进行优化和扩展,让这个小装置变得更聪明、更好用。
- 光控平滑与 hysteresis(迟滞) :当前代码在阈值附近灯光可能会频繁开关。可以引入“迟滞”逻辑,例如:用
LIGHT_THRESHOLD_ON = 8作为开灯阈值,LIGHT_THRESHOLD_OFF = 12作为关灯阈值。这样环境光需要在更暗时才开灯,更亮时才关灯,避免了抖动。 - PWM调光与亮度渐变 :不要让LED突然全亮或全灭。可以使用PWM方式控制LED亮度,在开灯和关灯时加入一个短暂的淡入淡出效果,对眼睛更友好。CircuitPython的
cp.pixels本身支持亮度属性(cp.pixels.brightness),可以配合循环实现渐变。 - 舵机平滑移动与多角度预设 :当前的舵机是“跳变”到目标角度。可以使用
for angle in range(current_angle, target_angle, step):循环,让舵机缓慢平滑地转动。你还可以利用CPX的加速度计,设计成“敲击”切换角度,或者增加更多按钮预设角度(如30度、60度)。 - 低功耗优化 :如果使用电池供电,续航很重要。可以在代码中增加:当灯光关闭且长时间无操作时,让CPX进入“睡眠”模式(CircuitPython有
alarm模块可以模拟深度睡眠),只有按下按钮或被光线变化触发时才唤醒,极大延长电池寿命。 - 外观个性化 :这是3D打印项目的最大乐趣。你可以为底座和灯臂设计可爱的外壳造型(如小机器人、小动物),或者使用半透明的PLA材料打印灯罩,让LED光更柔和。甚至可以用彩笔或喷漆进行涂装。
这个项目从想法到实现,最深的体会是“软硬结合”的魅力。一点点代码逻辑的改变,就能让硬件产生完全不同的行为。调试时,串行终端是你的最佳伙伴,它能让你“看见”传感器的数据、程序的状态。遇到舵机不动、灯光不亮这些问题时,不要慌,按照电源、信号、代码的逻辑一步步隔离排查,总能找到原因。最后,别忘了,这个书本灯只是一个起点,CPX板载的麦克风、温度传感器、加速度计都还空着呢,想象一下,加上拍手开关、根据环境温度变换灯光颜色、或者随着音乐节奏闪烁的阅读灯?创客的世界,边界只在于你的想象力。
更多推荐


所有评论(0)