背景
"Let's do活动"是DigiKey联合EEPW发起的为期一年的"跟着E课堂学技术,完成任务返红包"活动,活动旨在带着电子爱好者一起学习实用的电子技术知识,一起积攒DIY经验,一起变成更好的自己 !
这是 2025 年第三期活动, 活动主题是用一块带有240x135 液晶显示屏的 Adafruit开发板, 通过DFRobot 颜色传感器拾取现实物品的色彩来驱动蜂鸣器播放不同音阶的一场 DIY 活动。
笔者通 DigiKey 下单活动指定的元器件,按照 EEPW 提供的技术指导完成上述活动内容并在 EEPW 论坛发表活动博客以及成果视频。
本人参与过去年同期的 墨水屏 DIY 活动,虽然没有完成全部任务(可以往前追溯下我已发的博文, 虽是拖延,但是最终败于自己搭建环境遇到的坑),但是依旧拿到了 活动的部分奖励。这次借此机会果断继续参与。
我是 11月 8 日下单,17日完成通关后顺丰寄过来,19 日签收。收到货物后对比去年的快递,比较明显感觉到了 得捷(DigiKey) 更环保了,估计是收到了客户反馈(之前物料包装太精细,不环保),这次快递总共四个零件, 每个零件都采用小包装+原包装,相对 而言少了之前不必有的大量防静电大袋子(个人感觉挺好)。

不过可能因为清关的原因, 依旧给我打印了两份材料清单以及器件原产地证明。

物件开箱
订单项目如下

到手实物如下

Adafruit开发板 1528-5691-ND 和包装一起 特写

开发板功能确认
为了确认收到物件是否能正常工作,避免犯上一期的错误。首先需要先了解 开发板的大致功能, 通过开发板进一步验证其他元器件的功能。
首先这款开发板商品名字:Adafruit ESP32-S3 Reverse TFT Feather, 简述了 它是基于 乐鑫 ESP32 开发的带有 TFT屏幕的轻便开发板。
从得捷提供的规格书上可以看到:
硬件上它使用双核 240 MHz Tensilica 处理器,有 4MB Flash, 2 MB PSRAM, 原生支持 usb 协议,使用 SPI 连接了一块 240x135 的 液晶彩色显示屏,拥有低功耗 BT 和 WIFI 能力。有四个物理按键,支持四针的 STEMMA QT 接口(Adafruit 定制的 I2C 传感器连接框架)...
软件上它支持使用 ESP-IDF / CircuitPython / Arduion 等开发框架进行功能开发。
器件验证
验证思路,基于 ESP32 开发板的简单编程功能,实现自身以及其他器件的简单验证。
ESP32 开发板更新 bootloader 以及 固件
根据 EEPW 老师提供的 视频讲解了解到需要更新固件后才能使用最新版本的 CircuitPython 进行开发。
通过 开发板官方的在线指导文档找到对应的链接:
https://learn.adafruit.com/esp32-s3-reverse-tft-feather/update-tinyuf2-bootloader-for-circuitpython-10-4mb-boards-only
里面介绍是因为 CircuitPython 10 的时候我们这款 4M 存储的开发板的 flash 布局做了调整,把两个 OTA 分区融合为了一个, 以便可以开发更复杂的功能。
简单的说就是要更新板子的 bootloader 后才能用更新的 软件工具版本进行开发。
注意点: type C 一定得确保是可以传递 数据的 usb 线,避免只能充电的数据线造成干扰(数据线用对了的话,设备管理器插拔数据线都会刷新)
更新 bootloader
开发板需要手动进入 ROM bootloader 模式:
* 按住 BOOT 按键不放的情况下, 按一次 RESET 按键 后会进入黑屏状态, 这个时候就对了, 可以松开所有按键。(根据 pin out 说明可以看到 D0 按钮就是 BOOT).
更新 bootloader 有三种方式:
1. 通过 circuitpython.org 的 downloads 里面找到对应的板子后, 点击 open installer 按钮完成傻瓜式安装(我首先果断选择它, 但是奈何因为网络原因最终尝试失败)

2. 通过 Adafruit WebSerial ESPTool 手动安装 (最终尝试失败的方案)
2.1 需要下载 TinyUF2 bootloader 下载地址: https://adafruit-circuit-python.s3.amazonaws.com/bootloaders/esp32/adafruit_feather_esp32s3_reverse_tft/tinyuf2-adafruit_feather_esp32s3_reverse_tft-0.33.0-combined.bin
2.2 访问: https://adafruit.github.io/Adafruit_WebSerial_ESPTool/
2.3 点击连接设备
2.4 点击 erase 按钮 (点击后 所有数据会被清除, 这个时候不能断开设备,后续动作需要一气呵成,否则应该会变砖头)(耗时:16s)
2.5 点击第一个扇区( offset:0) 选择上面下载的 TinyUF2 bootloader 文件。
2.6 本来预期是可以直接刷进去的, 但是奈何,依旧是网络问题,导致部分模块无法加载,最终 image 校验失败,没能刷成功,目前变砖了。(我只想知道 这些 JS 的 CDN 都这么不受待见吗?)
![]()

3. 通过 esptool.py 命令行安装(最后的尝试)
3.1 先安装 python3 的环境(建议借助 window 应用商店安装)
3.2 打开 windows powershell 后执行: pip3 install esptool (如果提示 pip3找不到,请排查 python3 的环境问题)
3.3 确认 esptool 环境安装成功: 执行 python3 -m esptool 输出如下:

3.4 通过 设备管理器找到 当前设备的 COM 口(网页也可以看到)我是COM3

3.5 确认设备连接状态:python3 -m esptool --port COM3 chip_id

3.6 借助第二个方案,我完成了 flash 的 erase, 剩下刷入 bootloader了(没有 erase 的 需要使用这个工具清理下:python3 -m esptool --port COM3 erase_flash)

3.7 烧入 bootloader 分区:python3 -m esptool --port COM3 write_flash 0x0 bootloaderbin 文件

(开心)刷机后 reset 起来的界面

注意: 如果你直接刷入 circuitpython 的固件的话你会 进入不了 bootloader 而无法刷入其他 固件, 只能使用 python 编程, 最终不管你按多次/多久 reset 都无法重新进入 bootloader。每次都是进入下面的界面。(说实话这翻译的拼音真心不行, 一开始就脏话, 不建议用拼音版本), 这个时候你需要重新刷 tinyuf2 这个 bootloader

3.8 至此 bootloader 不顺利的更新完成了, 这个时候还没刷入固件,默认就是 弹出磁盘 让你拖入固件如下

3.9 当你 拖入 uf2 结尾的固件后, 按 reset 即可进入新固件。当你要重新刷入固件 只需要长按 reset一会后,松开等背面 neo-rgb 彩色灯亮起粉色后 里面再按下 reset 即可。
刷入老师提供的验证固件
1. 刷入 blinky.uf2 可以看到 neo-rgb 和电源指示灯闪烁。
2. 刷入 adafruit-circuitpython-adafruit_feather_esp32s3_reverse_tft-en_US-10.0.3.uf2 后能进入 circuitpython, 并且对应的代码分区, 后续的传感器都基于这个版本的固件进行代码开发验证。

验证蜂鸣器 + 干簧管模块
通过干簧管切换 蜂鸣器音调, 这里面做了防抖 0.3s 处理, 用磁铁靠近干簧管后移除来替换按钮来切换音符。

实现代码如下:
import pwmio as pwm
import time
import board
import digitalio
pin_pwm = pwm.PWMOut(board.D13, duty_cycle=0,frequency=440, variable_frequency=True)
pin_switch = digitalio.DigitalInOut(board.D12)
note_name= ['C4', 'D4', 'E4','F4', 'G4', 'A4', 'B4', 'C5']
frequencies = [261, 293, 329, 349, 392, 440, 490, 523]
index = 0
def play_note(note):
global note_name
global pin_pwm
if note in range(len(note_name)):
names = note_name[note]
freq = frequencies[note]
print(f'Playing {names} at {freq} Hz')
pin_pwm.frequency = int(freq)
pin_pwm.duty_cycle = 2 ** 10
else:
print(f'Unknow note id:{note}')
def stop_note():
global pin_pwm
pin_pwm.duty_cycle = 0
def switch_next_note():
global index
global note_name
stop_note()
play_note(index)
index=(index+1)%len(note_name)
action_time = 0
is_down = False
def main_loop():
global pin_switch
global is_down
global action_time
if pin_switch.value != is_down:
last_time = time.monotonic()
if is_down:
if last_time - action_time > 0.3:
print (f"switch to {pin_switch.value} dt{last_time - action_time}")
switch_next_note()
action_time = time.monotonic()
is_down = pin_switch.value
while True:
main_loop()
因为基于指导老师提供的音阶频率,这次验证下来基本上比较轻松,这里面因为用了干簧管做交互,需要注意抖动处理,ESP32 的 pwm 设置的确是比较方便的,实际体验下来能准确的通过蜂鸣器还原各个音阶。
验证彩色传感器

实现代码如下
import board
import digitalio
import time
class ColorSensor:
def __init__(self, s0, s1, s2, s3, led, out):
self.s0 = s0
self.s1 = s1
self.s2 = s2
self.s3 = s3
self.led = led
self.out = out
s0.direction = digitalio.Direction.OUTPUT
s1.direction = digitalio.Direction.OUTPUT
s2.direction = digitalio.Direction.OUTPUT
s3.direction = digitalio.Direction.OUTPUT
led.direction = digitalio.Direction.OUTPUT
out.direction = digitalio.Direction.INPUT
self.set_frequency(0)
def set_frequency(self, mode):
frequency = [[False,False], [False,True],[True, False],[True, True]]
if mode in range(len(frequency)):
self.s0.value = frequency[mode][0]
self.s1.value = frequency[mode][1]
else:
print(f'wrong frequency mode:{mode}, it need in range:0-{len(frequency)}')
def set_rgb_channel(self, color):
colors = {
"RED": [False,False],
"BLUE": [False, True],
"CLEAR": [True, False],
"GREEN": [True, True]
}
if color in colors:
self.s2.value = colors[color][0]
self.s3.value = colors[color][1]
else:
print(f'wrong color: {color}, you need use {colors} only')
def set_led(self, value):
self.led.value = value
print(f'set led:{value}')
def get_rgb_value(self):
ret = 0
times = []
# measure out pin freqence
now_value = self.out.value
count = 0
while len(times) < 10:
new_value = self.out.value
if now_value != new_value:
times.append(time.monotonic_ns())
now_value = new_value
periods = []
for i in range(2, len(times), 2):
ns = times[i] - times[i-1]
periods.append(ns)
avg = sum(periods) / len(periods)
ret = 1000000000 / avg
return ret
color = ColorSensor(digitalio.DigitalInOut(board.D6), digitalio.DigitalInOut(board.D9), digitalio.DigitalInOut(board.D10), digitalio.DigitalInOut(board.D11), digitalio.DigitalInOut(board.D12), digitalio.DigitalInOut(board.D5))
color.set_frequency(3)
color.set_led(True)
while True:
# without calibration
start = time.monotonic_ns()
color.set_rgb_channel("RED")
time.sleep(0.1)
red = color.get_rgb_value()
color.set_rgb_channel("BLUE")
time.sleep(0.1)
blue = color.get_rgb_value()
color.set_rgb_channel("GREEN")
time.sleep(0.2)
green = color.get_rgb_value()
color.set_rgb_channel("CLEAR")
time.sleep(0.2)
clear = color.get_rgb_value()
dt = (int)(time.monotonic_ns() - start)/1000000
print(f"Red:{red}, Blue:{blue}, Green:{green}, Clear:{clear} Time:{dt}ms")最终效果:

这里面的编程内容依据是基于 指导老师提供的代码片段拼接出来,因为没有做白光的标定,所以实验下来的数值不是具体颜色数值,而是原始数值。数值范围和周围亮度以及是否补光强相关。
在不补光的情况下:用传感器的黑色包装盒对比数值
频率模式1:
黑色数值 Red:40.9984, Blue:15.2587, Green:18.941, Clear:79.1976
白色数值 Red:147.604, Blue:56.2541, Green:70.2046, Clear:274.784
频率模式2:
黑色数值 Red:466.448, Blue:180.292, Green:222.156, Clear:910.221
白色数值 Red:1524.09, Blue:564.965, Green:712.35, Clear:2730.65
频率模式3:
黑色数值 Red:2383.15, Blue:923.044, Green:1129.93, Clear:4369.01
白色数值 Red:13107.0, Blue:3744.96, Green:5242.9, Clear:16383.8
从数值看,不同模式只是频率不同,范围有相关性, 唯一区别在于输出频率高的话可以提高采用速度,正常基于其中一种即可。测量过程中,数值也是抖动的厉害,具体原因还需要进一步对比看下。
开箱总结
从报名到开箱已经过去了整整一个月,拖延症始终困扰着自己,不知道大家是如何克服的,可以教教我吗?
欢迎大家发表评论,以及借鉴参考。
我在编辑开箱前看到有人已经很好的完成了任务,自己学习了一把后才决定动手,算是受到了激励。
suncat0504: https://forum.eepw.com.cn/thread/398012/1
这次拿到元器件后, 对比上次参与的活动,可以感受到更加容易入手,可以直接使用 micropython 编程,直插即用的元器件,借助指导老师和同学的参考资料可以比较容易的完成这次 DIY.
这次感谢 EEPW 和 DigiKey 一起举办的 DIY 活动,让我能够动手薅羊毛的同时提升动手能力和克服拖延症。
参考
Let’s do 2025年第3期——DigiKey陪你渡过春夏秋冬
Adafruit STEMMA & STEMMA QT
Adafruit ESP32-S3 Reverse TFT Feather
factory reset and bootloader repair
Adafruit ESP32-S3 Reverse TFT Feather Pinouts
circuit python api documentation