1. 项目概述与核心价值
如果你对物联网设备开发感兴趣,尤其是想亲手做一个能通过手机无线控制的炫酷LED信息牌,那么这个基于CircuitPython和蓝牙的RGB LED矩阵显示项目,绝对是一个能让你从零到一跑通全流程的绝佳实践。我折腾过不少嵌入式显示方案,从简单的单色点阵到复杂的全彩屏幕,这次用Adafruit的nRF52840 Feather搭配RGB矩阵,算是把“无线控制”和“动态显示”这两个玩点结合得相当优雅的一个方案。
简单来说,这个项目的核心就是让你能用手机上的一个App(Bluefruit Connect),通过蓝牙,随时向一块LED点阵屏发送任意文字,并且文字会以滚动的方式显示出来,你还能实时改变文字的颜色。它听起来像是一个简单的玩具,但背后串联起来的知识点非常扎实:包括了微控制器的固件刷写(CircuitPython)、特定硬件(RGB矩阵FeatherWing)的驱动、蓝牙低能耗(BLE)通信协议的应用,以及如何用Python(确切地说是CircuitPython)来粘合这一切。无论你是想做个个性化的桌面摆件、店铺的小型信息屏,还是为某个创客项目添加一个无线交互界面,这个项目都能提供一个可靠的技术底座。
我选择这个方案,而不是用更常见的ESP32+Wi-Fi的方案,主要是看中了nRF52840在蓝牙领域的原生优势和CircuitPython极低的开发门槛。nRF52840是Nordic的旗舰级BLE芯片,蓝牙连接稳定且功耗控制出色;CircuitPython则让你能用写Python脚本的方式操作硬件,免去了传统嵌入式开发中复杂的编译、烧录过程,真正实现了“所写即所得”,调试效率极高。接下来,我会带你一步步拆解,从硬件焊接、环境搭建,到代码逐行解析,最后分享几个我实测中遇到的坑和提升稳定性的技巧。
2. 硬件准备与组装要点
工欲善其事,必先利其器。这个项目的硬件清单比较明确,核心就几样,但组装和连接上有些细节需要注意,否则后面调试会很头疼。
2.1 核心硬件清单解析
主控板:Adafruit Feather nRF52840 Express这是项目的大脑。选择它,而不是更便宜的nRF52832版本,主要是因为nRF52840拥有更大的Flash(1MB)和RAM(256KB),能轻松容纳CircuitPython解释器、蓝牙协议栈以及我们的显示驱动代码。其内置的USB接口支持直接模拟成U盘(CIRCUITPY盘符),这是CircuitPython“即编即改”体验的基础。
显示驱动板:RGB Matrix FeatherWing Kit for nRF52840 Feathers这是关键桥梁。RGB LED矩阵屏(比如常见的64x32或32x32像素)需要特定的扫描逻辑和较高的电流驱动,直接接单片机引脚是不行的。这个FeatherWing扩展板集成了专用的LED驱动芯片和电平转换电路,它通过一组GPIO引脚与Feather主板通信,并将信号转换为矩阵屏能识别的协议。务必确认你购买的是针对nRF52840 Feather的版本,因为引脚定义可能与其他Feather板型不同。
显示面板:RGB LED矩阵屏(如64x32)这是项目的脸面。常见的规格有16x32, 32x32, 64x32, 64x64等,像素间距(Pitch)多为3mm, 4mm, 5mm。本项目代码默认针对64x32优化,但如原文所述,稍作修改即可适配其他尺寸。初次尝试建议从64x32开始,它在显示效果和硬件复杂度之间取得了很好的平衡。
电源:5V 2A (2000mA) 开关电源这是重中之重,也是新手最容易栽跟头的地方。RGB矩阵屏在点亮大量LED时(尤其是显示白色时)瞬时电流非常大,USB接口提供的5V/500mA功率远远不够。使用功率不足的电源会导致屏幕闪烁、颜色失真,甚至损坏板卡。一个标称5V 2A的开关电源是起步要求,如果屏幕更大(如64x64),可能需要3A甚至4A的电源。务必确保电源质量可靠,输出电压稳定。
连接器与线材
- 排针/排母:用于将Feather主板堆叠(Stack)到FeatherWing上。通常需要一套Feather专用的排针排母套装。
- IDC连接线:用于连接FeatherWing和RGB矩阵屏。矩阵屏一般使用标准的16Pin IDC接口,线序固定,防呆设计,插反了插不进去。
- 导线与端子:用于连接电源和FeatherWing上的接线端子。
2.2 焊接与组装实操指南
组装过程需要一些焊接技巧,但步骤是线性的。
第一步:焊接FeatherWingFeatherWing套件通常是散件,需要你亲手焊接上排母。找到板子上标有“Feather”字样的两排焊盘(一边12针,一边16针)。将长排母剪成对应的12针和16针两段(或者使用套件里提供的独立排母),分别焊接在这两排焊盘上。焊接时确保排母与板子垂直,所有引脚焊点饱满、无虚焊。这是所有连接的基础,务必稳固。
第二步:焊接Feather主板的IDC接口翻到Feather nRF52840主板的背面,你会看到一组2x8(16针)的焊盘,通常标有“MATRIX”或类似字样。将16Pin的IDC排针(公头)焊接在这里。这个接口是信号输出到矩阵屏的通道。焊接时注意排针方向,通常有凸起的一侧朝向板子外侧。
第三步:焊接电源接口在FeatherWing上找到5.08mm接线端子和DC电源插座(2.1mm内径)。将它们焊接在板子标注的位置。接线端子用于连接外部5V电源线,DC插座可以作为另一种电源输入方式。我个人的习惯是焊接好端子,用螺丝固定电源线,这样比插拔DC头更牢靠。
第四步:堆叠与连接
- 将焊接好排母的FeatherWing放在桌面,排母孔朝上。
- 将Adafruit Feather nRF52840主板对准方向,轻轻压入FeatherWing的排母中。注意观察板子上的USB接口和按钮位置,确保方向与官方图片一致。用力要均匀,听到轻微的“咔嗒”声表示所有引脚都已就位。
- 使用IDC排线,一端插入Feather主板背面的IDC排针,另一端插入RGB矩阵屏的IDC接口。注意方向:矩阵屏的PCB上通常会有一个白色三角或“IN”标识,指示输入方向。IDC排线的红色线一般代表第1脚,应对齐这个标记。
- 将5V电源的正极(红线)接入FeatherWing接线端子的“+”端,负极(黑线)接入“-”端。再次仔细检查正负极,接反会瞬间烧毁板卡或屏幕!
实操心得:在首次上电前,我强烈建议你先不要接矩阵屏,只给Feather主板通过USB供电。用串口工具(如Mu编辑器)查看是否有CircuitPython启动输出,或者CIRCUITPY盘符能否正常出现。这能排除主板本身的问题。确认主板正常后,再连接矩阵屏和外部电源。
3. 软件环境搭建与固件更新
硬件组装完毕,接下来是让硬件“活”起来的软件部分。这里主要分两步:给主控板刷入CircuitPython固件,以及更新Bootloader(引导程序)。
3.1 刷入CircuitPython固件
CircuitPython的安装过程被设计得非常简单,这就是所谓的“UF2”方式。
- 获取固件:访问CircuitPython官网,找到“Adafruit Feather nRF52840 Express”的页面,下载最新的
.uf2固件文件。文件通常以adafruit-circuitpython-feather_nrf52840_express-zh_LANG-*.uf2格式命名。 - 进入Bootloader模式:用一条数据线(确保不是充电线)将Feather主板连接到电脑。快速双击主板上的RESET按钮。此时,板载的NeoPixel RGB LED会闪烁绿色,电脑上会出现一个名为
FTHR840BOOT(或类似)的U盘。 - 拖放烧录:将下载好的
.uf2文件直接拖拽到FTHR840BOOT这个U盘里。盘符会自动消失,稍等片刻,会出现一个新的名为CIRCUITPY的U盘。这表明CircuitPython系统已经刷写成功并启动了。
常见问题排查:
- 双击RESET没反应?多试几次,掌握节奏(快速连按两下)。确保USB线是数据线。
- 拖入UF2后没出现CIRCUITPY?检查下载的UF2文件型号是否完全匹配你的主板。有时杀毒软件或磁盘工具会干扰,可以暂时关闭后重试。
- 出现CIRCUITPY但无法读写?可能是文件系统错误。可以尝试在电脑上格式化该盘(FAT32格式),然后重新拖入UF2文件。
3.2 更新Bootloader(关键步骤)
Bootloader是比操作系统更底层的程序,负责加载CircuitPython。这是一个极易被忽略但至关重要的步骤。旧版本的Bootloader(0.6.1之前)可能无法正确处理较大尺寸的CircuitPython固件(8.2.0版之后),导致刷写失败或运行不稳定。
如何检查当前Bootloader版本?双击RESET进入FTHR840BOOT模式,打开该U盘里的INFO_UF2.TXT文件。找到第一行类似UF2 Bootloader 0.4.0 lib/nrfx...的信息,中间的0.4.0就是版本号。如果低于0.6.1,必须更新。
推荐更新方法:UF2方式(最简单)前提是你的Bootloader版本已在0.4.0或以上。如果低于0.4.0,请参考下文命令行方法。
- 下载Bootloader更新文件:在Adafruit的GitHub发布页面,找到对应你主板的
update-开头的.uf2文件。例如,update-feather_nrf52840_express-0.8.1.uf2。 - 进入Bootloader模式:同上,双击RESET,出现
FTHR840BOOT盘。 - 拖放更新:将下载的
update-*.uf2文件拖入FTHR840BOOT盘。盘符会短暂消失后重新出现,更新即完成。你可以再次查看INFO_UF2.TXT确认版本号已更新。
备用更新方法:命令行方式(适用于任何旧版本)如果Bootloader太旧,无法使用UF2方式,则需要使用adafruit-nrfutil工具。
- 从Adafruit的发布页面下载对应主板的Bootloader压缩包(
.zip文件,不要解压)。 - 根据你的操作系统(Windows/macOS/Linux)下载对应的
adafruit-nrfutil工具。 - 将主板进入Bootloader模式(出现
FTHR840BOOT盘)。 - 打开终端或命令提示符,导航到工具和压缩包所在目录,执行类似以下命令(路径和文件名需替换):
# Windows示例,COM端口需根据实际情况修改 adafruit-nrfutil.exe --verbose dfu serial --package feather_nrf52840_express_bootloader-0.8.0_s140_6.1.1.zip -p COM3 -b 115200 - 等待命令行显示“Device programmed.”,即表示成功。
核心注意事项:我强烈建议在开始任何项目代码编写前,先完成Bootloader的更新。我遇到过因为Bootloader版本过低,导致代码文件莫名损坏、库无法加载等玄学问题,更新后全都迎刃而解。这步时间投入性价比极高。
4. 项目代码深度解析与定制
当CIRCUITPY盘符稳定出现后,我们就可以将项目代码和库文件放进去。直接从原文提供的链接下载项目打包文件(Project Bundle)是最快的方式,里面包含了code.py和必需的lib文件夹。
4.1 核心代码逻辑拆解
让我们打开code.py,逐段理解其工作原理。这不是简单的脚本堆砌,每一部分都体现了CircuitPython驱动硬件的典型模式。
第一部分:导入与配置
import time import board import displayio import framebufferio import rgbmatrix import terminalio from adafruit_display_text import label from adafruit_ble import BLERadio from adafruit_ble.advertising.standard import ProvideServicesAdvertisement from adafruit_ble.services.nordic import UARTServicedisplayio,framebufferio,rgbmatrix: 这是CircuitPython的显示核心框架。displayio管理显示对象树,framebufferio提供帧缓冲,rgbmatrix是专门驱动RGB矩阵的底层库。adafruit_ble及相关模块:提供了完整的BLE无线电、广告和服务功能。UARTService是核心,它模拟了一个串口,让手机和板子可以通过蓝牙像串口一样收发文本。terminalio: 提供了一个等宽字体,用于在矩阵屏上显示文字。
第二部分:硬件初始化与参数设置
SEND_RATE = 10 count = 0 SCROLL_DELAY = 0.05 ble = BLERadio() uart_server = UARTService() advertisement = ProvideServicesAdvertisement(uart_server) displayio.release_displays() matrix = rgbmatrix.RGBMatrix( width=64, height=32, bit_depth=1, rgb_pins=[board.D6, board.A5, board.A1, board.A0, board.A4, board.D11], addr_pins=[board.D10, board.D5, board.D13, board.D9], clock_pin=board.D12, latch_pin=board.RX, output_enable_pin=board.TX, ) display = framebufferio.FramebufferDisplay(matrix, auto_refresh=True) main_group = displayio.Group()SEND_RATE: 板子自动向手机发送数据(这里是计数)的间隔,单位秒。这是双向通信的体现。SCROLL_DELAY: 文字滚动速度。值越小滚动越快。rgbmatrix.RGBMatrix():这是硬件驱动的核心配置,也是适配不同屏幕的关键。参数width和height必须与你的物理屏幕一致。rgb_pins和addr_pins是FeatherWing与矩阵屏连接的引脚定义,对于Adafruit的这块FeatherWing是固定的,除非你自制驱动板,否则不要修改。bit_depth=1表示使用1-bit颜色深度(即每个颜色通道开或关),对于单色文字显示足够,且节省内存。如果想显示灰度或更多颜色,可以增加此值(如2或3),但会消耗更多内存和性能。displayio.Group(): 创建一个显示组,可以容纳多个显示元素(如图形、文字)。我们后续会把文字标签(Label)放进去。
第三部分:功能函数定义
def scroll(line): line.x -= 1 line_width = line.bounding_box[2] if line.x < -line_width: line.x = display.width def update_display(text, color=0xFFFFFF): if len(main_group) > 0: main_group.pop() text_area = label.Label(terminalio.FONT, text=text, color=color) text_area.x = display.width text_area.y = 13 main_group.append(text_area) display.root_group = main_groupscroll(line): 实现滚动动画。每次调用,将文本对象的x坐标减1(向左移动1像素)。当文本完全移出屏幕左侧(x < -文本宽度)时,将其重置到屏幕最右侧,实现循环滚动。update_display(text, color): 更新显示内容。它先清空当前显示组(main_group.pop()),然后创建一个新的文本标签。text_area.x = display.width将文本的初始位置设在屏幕最右侧,为向左滚动做准备。y = 13是垂直居中(对于32像素高的屏幕,13大致是中心)。color是RGB十六进制值,默认白色(0xFFFFFF)。
第四部分:主循环——蓝牙连接与消息处理这是整个项目的状态机核心,逻辑清晰:
- 等待连接状态:显示“WAITING...”,并开始广播BLE广告。
- 连接建立:一旦手机App连接,停止广播,显示“CONNECTED”。
- 连接保持状态:
- 接收(RX):持续检查蓝牙串口是否有数据。如果有,读取、解码并显示在屏幕上(颜色为
0x26B7FF,一种蓝色)。 - 发送(TX):每隔
SEND_RATE秒,向手机发送一次当前的计数(COUNT = X)。这是一个很好的心跳和调试机制。 - 动画:在循环中持续调用
scroll函数,并刷新屏幕,实现文字滚动效果。
- 接收(RX):持续检查蓝牙串口是否有数据。如果有,读取、解码并显示在屏幕上(颜色为
- 断开连接:跳出内层循环,打印“DISCONNECTED”,然后回到步骤1,重新开始广播等待连接。
4.2 代码定制与高级技巧
原版代码是一个完美的演示。但在实际项目中,你肯定想改点东西。
1. 修改屏幕尺寸:如果你的屏幕是32x32,只需修改RGBMatrix初始化时的width和height参数。同时,可能需要调整text_area.y的值(如y=16)来垂直居中。
2. 更改滚动速度和方向:
- 速度:修改
SCROLL_DELAY。增大则变慢,减小则变快。注意,过小的延迟(如0.01)可能导致刷新过快,CPU占用高。 - 方向:修改
scroll函数中的line.x -= 1。改为+= 1则向右滚动。修改重置条件,可以实现从右向左、从下至上等多种滚动效果。
3. 显示静态文字或居中文字:如果你不需要滚动,想要静态显示,可以注释掉主循环中所有scroll和display.refresh的调用(在update_display中已经设置了自动刷新auto_refresh=True)。对于居中显示,在update_display函数中,计算文本宽度并设置x坐标:
text_width = text_area.bounding_box[2] text_area.x = (display.width - text_width) // 24. 使用自定义字体:terminalio.FONT是内置的等宽字体。CircuitPython支持显示BDF格式的字体。你可以将.bdf字体文件放在CIRCUITPY盘根目录或字体文件夹,然后使用bitmap_font.load_font()加载。
from adafruit_bitmap_font import bitmap_font custom_font = bitmap_font.load_font("/fonts/myfont.bdf") text_area = label.Label(custom_font, text=text, color=color)注意,大字体文件会占用较多内存。
5. 优化性能与内存:
bit_depth: 对于纯文字显示,1足够。显示图片或渐变色时才需要提高。- 避免在循环内频繁创建和销毁对象(如Label)。原版代码在每次更新显示时都创建新Label并替换,对于简单应用没问题。如果追求极致性能,可以复用Label对象,只修改其
.text属性。
5. Bluefruit Connect App使用与通信调试
代码在板子上跑起来后,最后一步就是用手机App来控制它。Bluefruit Connect App是Adafruit官方出品的多合一调试工具,功能强大。
5.1 连接与UART终端使用
- 安装与准备:在苹果App Store或Google Play搜索“Adafruit Bluefruit LE Connect”并安装。确保手机蓝牙已开启。
- 扫描设备:打开App,它会自动扫描周围的BLE设备。给你的Feather nRF52840上电,几秒后,你应该能在列表里看到一个名为“CIRCUITPY”或“UART”的设备(设备名可以在代码中通过
ble.name = "MySign"来修改)。信号强度(RSSI)会以格数显示。 - 建立连接:点击设备右边的“Connect”按钮。在连接模式选择中,务必选择“UART”模式。这是代码中
UARTService所对应的服务。 - 进入UART终端:连接成功后,App底部会有一排功能图标,点击那个像“
>”终端的图标,进入UART文本终端界面。
在这个界面,你可以:
- 发送文本:在下方的输入框键入任何文字,点击发送。这些文字会通过蓝牙发送到板子,并立即显示在LED矩阵上。
- 接收文本:上方的大文本区域会显示从板子发来的信息。在这个项目中,你会看到每隔10秒出现的“COUNT = X”信息。
- 更改颜色(高级功能):原版代码固定了接收文字的颜色。你可以修改代码,使其能解析App发送的特殊指令。例如,约定发送“
COLOR FF0000”来将颜色改为红色。在代码的接收部分,添加对这类指令的解析即可。
5.2 常见连接问题与排查
设备列表中找不到“CIRCUITPY”:
- 检查供电:确保板子已上电(USB或外部电源)。
- 检查代码:确认
code.py已正确复制到CIRCUITPY盘,并且没有语法错误导致程序崩溃。可以打开Mu编辑器,查看串口输出是否有错误信息。 - 重启蓝牙:关闭手机蓝牙再打开,或重启App。
- 重启板子:按一下板子的RESET键。
- 检查广播:代码中
ble.start_advertising(advertisement)是否在执行。
连接后马上断开:
- 检查服务:确保手机App连接时选择了“UART”模式,而不是“Control Pad”或“Pin I/O”。
- 电源干扰:如果使用外部电源,尝试只用USB供电测试,排除电源噪声干扰蓝牙的可能(虽然概率低)。
- 代码阻塞:检查你的代码主循环中是否有长时间阻塞的操作(如
time.sleep(10)),这可能导致蓝牙协议栈无法及时响应而断开。使用time.monotonic()进行非阻塞延时是好的做法,原版代码已采用。
发送文本无显示:
- 查看串口输出:在Mu编辑器中连接板子的串口,查看当手机发送信息时,是否有“RX: xxx”打印出来。如果没有,说明蓝牙数据未成功送达板子。检查App连接状态和发送操作。
- 检查显示初始化:确认矩阵屏连接牢固,电源功率足够。尝试在代码开头添加简单的测试,如点亮屏幕上的几个像素,确认硬件驱动正常。
调试心法:嵌入式开发,尤其是无线项目,最有效的调试工具就是串口打印。充分利用CircuitPython的
print()函数,将程序关键节点的状态(如“开始广播”、“已连接”、“收到数据:xxx”)打印出来,通过Mu编辑器观察,绝大多数问题都能快速定位。把蓝牙通信想象成一个无线串口,问题就会变得简单很多。
6. 项目优化与扩展思路
走通基础功能后,我们可以让这个项目变得更实用、更强大。
6.1 稳定性与电源优化
- 独立供电是必须:再次强调,务必使用独立的5V 2A以上电源为整个系统(特别是矩阵屏)供电。USB口仅用于编程和调试。
- 添加电容:在FeatherWing的电源输入端子附近,并联一个大容量电解电容(如470uF 10V)和一个小容量陶瓷电容(0.1uF)。这可以平滑LED快速扫描时产生的电流尖峰,防止电压跌落导致单片机复位,显著提升系统稳定性。
- 代码看门狗:CircuitPython支持软看门狗。在代码开头
import supervisor,然后在主循环中定期调用supervisor.reload()的条件判断改为喂狗supervisor.watchdog.feed(),并设置超时时间。这可以在程序跑飞时自动复位。import supervisor supervisor.watchdog.timeout = 5 # 5秒超时 # 在主循环中定期喂狗 supervisor.watchdog.feed()
6.2 功能扩展
- 多行显示与图文混合:
displayio.Group可以容纳多个Label甚至TileGrid(用于显示位图)。你可以创建多个文本对象,设置不同的y坐标,实现多行显示。结合adaruit_display_shapes和adaruit_display_text库,可以画出图形和文字混合的界面。 - 网络时间同步:搭配一个ESP32作为协处理器或使用具有Wi-Fi功能的板卡(但本项目是nRF52840),通过网络获取NTP时间,实现一个精准的无线时钟。
- 传感器数据展示:连接温湿度传感器(如DHT22)、空气质量传感器等,将采集的数据实时显示在屏幕上,并通过蓝牙发送到手机App,做成一个环境监测站。
- 自定义协议与App:不满足于Bluefruit Connect的简单UART?你可以定义自己的BLE服务(Service)和特征值(Characteristic),并开发一个专用的手机App(使用Swift或Kotlin,或跨平台框架如Flutter+Bleak),实现更复杂的交互,比如选择预设动画、调节亮度、定时开关等。
- 离线信息队列:修改代码,将接收到的信息存储到一个列表(List)中,实现一个滚动播放的信息队列。手机可以一次发送多条信息,板子循环播放。
6.3 生产部署建议
如果你打算把这个项目作为一个长期运行的设备部署:
- 外壳与散热:为LED矩阵和主板设计一个通风良好的外壳。LED屏工作时会产生热量。
- 电源管理:如果需要电池供电,需考虑nRF52840的低功耗模式。在无连接时,可以停止屏幕刷新、降低CPU频率、让蓝牙进入深度睡眠,仅定时唤醒广播。这需要更深入的代码优化。
- 固件防篡改:将最终的
code.py重命名为main.py,这样板子启动后会自动运行它。同时,可以将CIRCUITPY盘设为只读,防止意外修改。
需要再次编程时,再将其设为可写(# 在CircuitPython的REPL中执行 import storage storage.remount("/", readonly=True)readonly=False)。
这个项目就像一把钥匙,为你打开了用CircuitPython和蓝牙玩转智能硬件显示的大门。从最初的硬件组装、软件烧录,到代码调试、功能扩展,每一步都充满了动手的乐趣和解决问题的成就感。我最享受的时刻,就是在代码修改后保存的瞬间,LED屏上的效果随之改变,那种即时反馈的体验是传统嵌入式开发难以比拟的。希望这份详细的拆解和补充,能帮你绕过我踩过的那些坑,更顺畅地实现你的创意。