nRF52840开发板移植CircuitPython实战:从编译到蓝牙应用
1. 项目概述与核心价值
如果你手头有一块基于 Nordic nRF52840 芯片的开发板,比如官方的 nRF52840-DK 或者 Particle 的 Argon/Xenon,并且厌倦了在 C 语言和复杂的 SDK 中挣扎,想用 Python 的简洁语法快速实现一个蓝牙传感器节点或者物联网设备原型,那么这篇文章就是为你准备的。我们将深入探讨如何在 nRF52840 这颗强大的蓝牙低功耗芯片上,从头开始构建并烧录 CircuitPython 运行环境。这不仅仅是跟着教程点几下鼠标,我会带你理解每一步背后的“为什么”,分享我在实际操作中踩过的坑和总结的技巧,让你不仅能成功运行,更能掌握这套工作流的精髓。
nRF52840 是一颗集成了 ARM Cortex-M4 内核和蓝牙 5.0/低功耗蓝牙的明星芯片,功耗和性能平衡得非常好。而 CircuitPython 是 Adafruit 主导维护的 MicroPython 分支,它最大的特点是“即插即用”——代码文件直接放在一个名为CIRCUITPY的 U 盘里,保存即运行,极大地降低了嵌入式开发的门槛。将这两者结合,意味着你可以用 Python 轻松操控 GPIO、I2C、SPI,甚至直接使用蓝牙栈,这对于快速验证想法、开发智能硬件原型来说,效率是革命性的。
不过,官方资料虽然详尽,但更多是步骤罗列。作为一个在一线折腾过无数开发板的博主,我深知其中有许多隐含的细节和容易出错的地方。例如,bootloader 的选择与烧录、不同编程方式(UF2 vs DFU)的适用场景、针对特定开发板的引脚定义修改等,这些都需要结合实战经验才能顺畅完成。本文的目标就是充当你的“实战手册”,不仅告诉你步骤,更解释原理,并提供避坑指南。无论你是刚接触嵌入式 Python 的开发者,还是想为自定义硬件移植 CircuitPython 的硬件工程师,都能从中找到所需。
2. 环境准备与工具链搭建
在开始编译和烧录之前,一个稳定、完整的开发环境是成功的基石。这一步看似繁琐,但搭建好后可以一劳永逸。我们将主要基于 Linux/macOS 的命令行环境进行,Windows 用户使用 WSL 或 Git Bash 也能获得几乎一致的体验。
2.1 核心工具安装与配置
首先,我们需要三个核心工具:git、make和arm-none-eabi-gcc交叉编译器。
- Git 与代码仓库:CircuitPython 的源代码托管在 GitHub 上。我们需要用它来获取最新代码。通常系统已自带,如果没有,请通过包管理器安装(如
apt install git或brew install git)。 - GNU Make:这是管理编译流程的引擎。在 Ubuntu/Debian 上可通过
apt install build-essential安装,macOS 上安装 Xcode Command Line Tools (xcode-select --install) 即可获得。 - ARM GCC 交叉编译器:这是最关键的一环,用于将我们的 Python 代码和 CircuitPython 运行时编译成 nRF52840 能执行的机器码。我强烈建议使用 Adafruit 维护的特定版本,以确保与项目完全兼容。
- 下载地址:访问 Adafruit 的 Arm GNU Toolchain 页面 获取下载链接。选择适用于你操作系统(Linux x86_64, macOS ARM/Intel, Windows)的版本。
- 安装与配置:下载后解压到你喜欢的目录,例如
~/gcc-arm-none-eabi。然后,需要将编译器的bin目录添加到系统的PATH环境变量中。这是很多新手会出错的地方。 - 具体操作:打开你的 shell 配置文件(如
~/.bashrc,~/.zshrc),添加一行:export PATH=$PATH:~/gcc-arm-none-eabi/bin。然后执行source ~/.bashrc使配置生效。验证是否成功:在终端输入arm-none-eabi-gcc --version,应该能看到版本信息。
注意:不要使用系统包管理器(如
apt install gcc-arm-none-eabi)安装的编译器,其版本可能过旧或存在路径差异,极易导致后续编译出现各种诡异的链接错误。我踩过这个坑,浪费了好几个小时排查。
2.2 获取 CircuitPython 源代码
环境准备好后,我们获取 CircuitPython 的源代码。建议直接在ports/nrf目录下操作,因为这是 nRF52 系列芯片的专用端口代码。
# 克隆主仓库 git clone https://github.com/adafruit/circuitpython.git cd circuitpython # 进入 nRF52 端口目录 cd ports/nrf2.3 初始化子模块与编译 mpy-cross
CircuitPython 依赖许多子模块(如库文件、特定芯片支持包)。我们需要同步并初始化它们。同时,mpy-cross是一个将 Python 脚本预编译为.mpy字节码的工具,能节省运行时内存并提高加载速度,必须先编译好。
# 同步并更新所有子模块 git submodule sync git submodule update --init --recursive # 编译 mpy-cross 工具 make -C ../../mpy-cross这个过程会下载不少内容,耗时取决于网络。如果遇到git submodule update失败,通常是网络问题,可以尝试重试,或者检查是否有子模块指向了私有仓库(一般不会)。
2.4 下载 Nordic SoftDevice
nRF52840 的蓝牙功能依赖于 Nordic 提供的专有二进制固件,称为 SoftDevice。它相当于蓝牙协议栈的底层驱动,CircuitPython 的_bleio库需要与之交互。我们必须下载它。
在ports/nrf目录下,运行提供的脚本:
./bluetooth/download_ble_stack.sh这个脚本会自动从 Nordic 官网下载对应版本的 SoftDevice 十六进制文件(通常是s140_nrf52_*.hex)。如果脚本执行失败,最常见的原因是wget命令未安装(macOS 可能没有),请先安装wget(macOS:brew install wget)。
实操心得:有时 Nordic 的服务器连接可能不稳定。如果脚本卡住或失败,你可以手动从 Nordic 开发者网站 查找并下载对应版本的 SoftDevice hex 文件,然后将其放置在ports/nrf/bluetooth/目录下。确保文件名与脚本期望的名称一致。
2.5 安装烧录辅助工具
我们将用到两个关键的烧录工具:
- nrfjprog:Nordic 官方的命令行工具,用于通过 J-Link 调试器直接与芯片的 SWD 接口通信,进行擦除、编程等底层操作。它主要用于首次烧录 bootloader。
- 从 Nordic 命令行工具页面 下载并安装。
- 安装后,同样需要将它的安装路径(如
/opt/nrfjprog或nRF-Command-Line-Tools_*/nrfjprog)添加到PATH。 - 验证:
nrfjprog --version。
- adafruit-nrfutil:Adafruit 制作的 Python 工具,用于通过芯片已有的 USB 串行 bootloader 来上传固件(DFU 模式)。这是我们后续更新 CircuitPython 固件的主要方式。
- 通过 pip 安装:
pip3 install --upgrade adafruit-nrfutil。 - 确保你使用的是 Python 3。验证:
adafruit-nrfutil --version。
- 通过 pip 安装:
至此,你的开发环境已经就绪。我们可以把它想象成一个车间:arm-none-eabi-gcc是车床,make是流水线调度员,nrfjprog是用于首次安装生产线(bootloader)的精密仪器,而adafruit-nrfutil是日后给产品(固件)升级的便捷通道。
3. Bootloader 的烧录与原理剖析
Bootloader 是一段驻留在微控制器闪存起始区域的小程序。它的核心作用是在芯片上电后,先于主应用程序运行,负责检查是否需要进入固件更新模式,并将控制权移交给有效的应用程序。对于 nRF52840 上的 CircuitPython,我们使用 Adafruit 修改版的 nRF52 Bootloader,它支持两种用户友好的更新方式:UF2 和 DFU。
3.1 为什么需要专门的 Bootloader?
nRF52840 芯片出厂时,其内部闪存是空白的,或者可能预装了芯片厂商的测试程序。为了让我们能够通过 USB 线以“拖放文件”或“串口命令”这种简单方式更新固件,而不是每次都动用昂贵的 J-Link 调试器,就必须先烧录一个支持这些功能的 bootloader。这就像给你的电脑主板刷入了一个支持 U 盘启动的 BIOS。
3.2 获取与编译 Bootloader
首先,获取 Adafruit 的 nRF52 Bootloader 源代码:
git clone https://github.com/adafruit/Adafruit_nRF52_Bootloader.git cd Adafruit_nRF52_Bootloader git submodule update --init --recursive编译 bootloader 需要指定目标板(BOARD)。不同的开发板,其引脚定义(特别是用于进入 bootloader 模式的按钮)和内存布局可能不同。以最常见的nRF52840-DK (PCA10056)为例:
make BOARD=pca10056 clean make BOARD=pca10056 sd make BOARD=pca10056 flashmake clean:清除之前的编译产物,确保全新编译。make sd:这个目标会先将 Nordic 的 SoftDevice 编程到芯片上。SoftDevice 需要占用闪存开头的特定区域,bootloader 和应用程序都必须知道它的位置并避开。这一步至关重要,没有 SoftDevice,蓝牙功能将无法工作。make flash:编译 bootloader 并将其烧录到芯片上。这个命令通常依赖于nrfjprog和已连接的 J-Link。
注意事项:对于 nRF52840-DK,其板载的 Segger J-Link 调试器在默认状态下会模拟成一个 U 盘(Mass Storage Device)。这可能会与 bootloader 尝试使用的 USB 通信串口(CDC)冲突。因此,在烧录 bootloader 前,必须禁用 J-Link 的 MSD 功能。
# 连接到 J-Link 命令行工具 JLinkExe # 在 J-Link> 提示符后输入 MSDDisable # 然后输入 exit 退出执行后,重新插拔开发板,那个名为JLINK的磁盘应该会消失。以后如果需要恢复,可以使用MSDEnable命令。
3.3 使用 nrfjprog 进行手动烧录
有时自动化的make flash可能因为环境问题失败。这时我们可以手动分步操作,这也能帮助你理解整个过程:
连接与擦除:确保开发板通过 USB 连接(连接到 J-Link 口,而不是 nRF 芯片本身的 USB 口)。执行全片擦除:
nrfjprog -f nrf52 --eraseall-f nrf52指定芯片系列,--eraseall擦除所有用户闪存。此操作不可逆,会清除芯片上所有现有程序。烧录 SoftDevice:首先烧录蓝牙协议栈基础。
nrfjprog -f nrf52 --program path/to/your/s140_nrf52_*.hex --sectorerase将
path/to/your/s140_nrf52_*.hex替换为实际下载的 SoftDevice 文件路径。--sectorerase只擦除要编程的扇区,更安全快捷。生成并烧录 Bootloader:在 Bootloader 源码目录,生成 hex 文件并烧录。
make BOARD=pca10056 genhex nrfjprog -f nrf52 --program _build/build-pca10056/s140/armgcc/xxx_bootloader.hex --sectorerase nrfjprog -f nrf52 --reset最后一条命令复位芯片,让 bootloader 开始运行。
烧录成功后,将 USB 线改插到开发板上 nRF52840 芯片本身的 USB 口(在 nRF52840-DK 上,是那个标有“nRF USB”的 Micro-USB 口)。你应该会在电脑上看到一个名为NRF52BOOT或FTHR840BOOT(针对 Adafruit Feather 板)的 U 盘。点开它,里面会有一个INFO_UF2.TXT文件,描述了 bootloader 的版本信息。恭喜,你的芯片现在拥有了“一键升级”的能力。
3.4 Bootloader 交互模式详解
与 SAMD21/SAMD51 芯片上通过快速双击复位键进入 bootloader 的模式不同,nRF52 Bootloader 的触发方式更依赖于硬件按钮的组合。以 nRF52840-DK 为例:
- 进入 USB Bootloader 模式(UF2模式):按住Button 1,再短按一下RESET按钮,然后松开。此时
NRF52BOOT磁盘会出现。 - 进入 OTA DFU 模式(无线升级模式):按住Button 1 和 Button 2,再短按一下RESET按钮,然后松开。此时芯片会进入一个等待通过蓝牙接收新固件的状态,磁盘不会出现。
理解这两种模式很重要:UF2 模式用于通过 USB 线拖放.uf2文件进行更新,是最简单直接的方式;OTA DFU 模式则为未来实现无线(蓝牙)固件升级打下了基础。
4. 编译 CircuitPython 固件
有了 bootloader 这个“基础设施”,我们就可以在上面安装“操作系统”——CircuitPython 运行时固件了。我们可以使用官方预编译的固件(最简单),但为了深度定制或学习,从源码编译是更好的选择。
4.1 编译配置与过程
回到之前克隆的 CircuitPython 仓库的ports/nrf目录。编译命令非常简单:
make BOARD=pca10056这里的BOARD=pca10056同样指定了目标板。编译系统会根据ports/nrf/boards/pca10056/目录下的配置文件(如mpconfigboard.h,pins.c)来生成针对该开发板硬件的固件。
编译过程会持续几分钟,期间你会看到大量的 GCC 编译命令滚动。如果一切顺利,最终会在ports/nrf/build-pca10056-s140/这样的目录下生成几个关键文件:
firmware.hex:标准的 Intel HEX 格式文件,包含完整的程序代码和数据。firmware.uf2:UF2 格式文件,专为 USB 拖放更新设计。dfu-package.zip:用于 DFU 串口升级的压缩包。
常见问题排查:
- 编译错误
arm-none-eabi-gcc: command not found:说明交叉编译器路径没设置对。请回头仔细检查PATH环境变量。 - 链接错误,提示某些函数未定义(undefined reference):最常见的原因是子模块没有正确初始化。确保你执行了
git submodule update --init --recursive。另一个可能是 SoftDevice 文件缺失或版本不对,确保download_ble_stack.sh脚本成功运行。 make命令找不到:确保已安装构建工具链(build-essential)。
4.2 理解固件组成
一个完整的 CircuitPython 固件包含以下几层:
- SoftDevice:位于闪存最底部,提供蓝牙射频控制等底层服务。它由 Nordic 提供闭源二进制文件。
- Bootloader:位于 SoftDevice 之上。负责固件更新。
- CircuitPython 运行时:位于 bootloader 之上。包含 Python 解释器、核心库、硬件抽象层(HAL)以及针对特定开发板的引脚定义等。
- 文件系统:通常位于闪存末尾。在 CircuitPython 中表现为
CIRCUITPY磁盘,用于存放用户的code.py和其他库文件。
编译生成的firmware.hex或firmware.uf2已经包含了第3部分的运行时,它被链接到正确的起始地址,以便在 bootloader 之后正确运行。
5. 固件烧录:UF2 与 DFU 双模式详解
现在到了最激动人心的环节——将我们编译好的 CircuitPython 固件“刷入”开发板。我们有两种主要方式:推荐且简单的 UF2,以及更底层、更通用的 DFU。
5.1 UF2 拖放烧录(推荐给所有用户)
UF2 是微软为 Microbit 发起的一种文件格式,现在被 CircuitPython、Arduino 等广泛采用。它的优点是无须任何驱动和额外软件,在主流操作系统上即插即用。
操作步骤:
- 让开发板进入 USB Bootloader 模式。对于 nRF52840-DK,就是按住 Button 1,点按 RESET,然后松开。
- 电脑上会出现一个名为
NRF52BOOT的 U 盘。 - 将编译好的
firmware.uf2文件(位于build-pca10056-s140/目录)直接拖拽或复制到这个 U 盘里。 - U 盘图标会短暂消失(此时 bootloader 正在将 UF2 文件内容写入闪存),几秒后,一个新的名为
CIRCUITPY的 U 盘会出现。
完成!现在你的开发板已经在运行 CircuitPython 了。你可以像操作普通 U 盘一样,向CIRCUITPY磁盘里添加code.py文件,代码会自动运行。
实操心得:
- UF2 文件具有特定的魔法数字和结构,bootloader 能识别并安全地将其写入正确的闪存位置,避免了用户误操作的风险。
- 如果拖放后
CIRCUITPY磁盘没有出现,或者出现后无法访问,可能是固件编译有问题,或者 bootloader 版本与固件不兼容。可以尝试重新进入 bootloader 模式,检查INFO_UF2.TXT中的版本号。 - 在某些 Linux 发行版上,可能需要你有权限写入 USB 设备。如果拖放失败,可以尝试使用命令行
cp命令,或者将你的用户加入dialout或plugdev组。
5.2 DFU 串口烧录(适用于无 UF2 或自动化场景)
DFU 是“设备固件升级”的缩写,是一种通过串口进行固件更新的标准协议。虽然步骤稍多,但它不依赖于磁盘拖放界面,更适合脚本化、自动化部署,或者在 bootloader 不支持 UF2 的早期硬件上使用。
操作步骤:
生成 DFU 包:在编译固件后,我们需要将其打包成 DFU 专用的 ZIP 格式。
make BOARD=pca10056 dfu-gen这会在
build-pca10056-s140/目录下生成一个dfu-package.zip文件。进入 DFU 模式:让开发板进入等待 DFU 的状态。对于 nRF52840-DK,就是按住 Button 1 和 Button 2,点按 RESET,然后松开。此时,
NRF52BOOT磁盘不会出现,但芯片会通过 USB 串口等待 DFU 命令。查找串口设备:你需要知道开发板在 DFU 模式下对应的串口号。
- Linux/macOS:通常在
/dev/ttyACM0或/dev/ttyUSB0。可以通过ls /dev/tty*在插拔设备前后对比查看。 - Windows:在设备管理器的“端口 (COM 和 LPT)”下查看,通常是
COM3,COM4等。
- Linux/macOS:通常在
使用 adafruit-nrfutil 上传:
adafruit-nrfutil --verbose dfu serial --package build-pca10056-s140/dfu-package.zip -p /dev/ttyACM0 -b 115200 --singlebank--verbose:显示详细日志,便于调试。-p /dev/ttyACM0:指定你的串口设备路径。-b 115200:波特率。--singlebank:这是一个关键参数。nRF52840 支持“双区交换”更新,但 Adafruit bootloader 默认使用单区模式。加上此参数可避免报错。
命令执行后,工具会与 bootloader 通信,发送固件包,并显示进度条。上传完成后,芯片会自动复位并运行新的 CircuitPython 固件,
CIRCUITPY磁盘随之出现。
模式选择建议:
- 日常开发、快速迭代:毫不犹豫地选择 UF2。它简单、直观、不易出错。
- 生产烧录、批量更新:可以考虑 DFU,可以编写脚本实现自动化。
- 调试 bootloader 或底层通信:DFU 模式能提供更底层的日志信息。
- 当 UF2 磁盘功能出现问题时:DFU 是可靠的备用方案。
6. 测试与验证:让开发板“活”起来
烧录成功后,看到CIRCUITPY磁盘只是第一步。我们需要验证 CircuitPython 是否真的在正常运行,并且硬件可以正确控制。
6.1 连接 REPL 交互环境
REPL 是“读取-求值-打印”循环,是 CircuitPython 的交互式命令行。通过它,我们可以实时执行 Python 命令,是调试和探索的强大工具。
使用串口终端工具:你需要一个串口终端软件。
- 推荐 Mu Editor:一款专为 CircuitPython/MicroPython 设计的集成编辑器,内置了串口终端和代码上传功能,对新手极其友好。 官网下载 。
- 其他选择:
screen(macOS/Linux),PuTTY(Windows),picocom,minicom。
查找 CircuitPython 串口:当
CIRCUITPY磁盘出现时,系统也会为它创建一个串行通信端口(CDC)。- Linux/macOS:
/dev/ttyACM0(常见) - Windows:
COMx(在设备管理器中查看)
- Linux/macOS:
连接并测试:
- 打开 Mu Editor,它会自动检测并连接到 CircuitPython 板。
- 或者,使用
screen:screen /dev/ttyACM0 115200。 - 连接后,按几次键盘的Ctrl+C。这会中断任何可能正在运行的程序,并显示 CircuitPython 的 REPL 提示符
>>>。 - 你应该会先看到类似下面的启动横幅,其中包含了固件版本、板卡型号等信息:
Adafruit CircuitPython 8.x.x on 2023-xx-xx; nRF52840 DK with nRF52840 >>> - 输入简单的 Python 命令测试,如
print(“Hello, nRF52840!”)或import board; print(dir(board))来查看可用的引脚。
6.2 基础硬件测试:闪烁 LED
理论再好,不如让灯闪起来。我们写一个简单的code.py来测试最基本的 GPIO 输出功能。
对于nRF52840-DK,板载 LED1 连接在引脚 P0.13。将以下代码保存为CIRCUITPY磁盘根目录下的code.py文件:
import time import board from digitalio import DigitalInOut, Direction # 初始化 LED 引脚 led = DigitalInOut(board.P0_13) # 根据你的板子,这个引脚名可能不同 led.direction = Direction.OUTPUT print("Blinking LED on", board.P0_13) while True: led.value = True # 点亮 LED (对于 DK,可能是低电平点亮) time.sleep(0.5) led.value = False # 熄灭 LED time.sleep(0.5)保存文件后,CircuitPython 会自动重新加载并运行新代码。你应该能看到 LED1 开始以 1 秒的间隔闪烁。同时,在 REPL 中也会看到打印的信息。
引脚定义详解:board.P0_13中的P0指的是 nRF52840 的 GPIO 端口 0,13是引脚号。所有可用的引脚都在board模块中定义,它们位于ports/nrf/boards/pca10056/pins.c文件中。如果你想使用其他 LED 或引脚,可以查看该文件或开发板原理图。
6.3 综合测试:按钮控制 LED
为了进行更全面的测试,我们可以利用 nRF52840-DK 上的四个按钮和四个 LED,实现一个按钮按下对应 LED 亮起的小程序。这测试了 GPIO 输入(带上拉电阻)和输出。
import time import board from digitalio import DigitalInOut, Direction, Pull # 定义 LED 和按钮的引脚 led_pins = [board.P0_13, board.P0_14, board.P0_15, board.P0_16] # LED1, LED2, LED3, LED4 button_pins = [board.P0_11, board.P0_12, board.P0_24, board.P0_25] # Button1, Button2, Button3, Button4 # 初始化所有 LED 为输出 leds = [] for pin in led_pins: led = DigitalInOut(pin) led.direction = Direction.OUTPUT led.value = False # 初始熄灭 leds.append(led) # 初始化所有按钮为输入,并启用内部上拉电阻 buttons = [] for pin in button_pins: button = DigitalInOut(pin) button.direction = Direction.INPUT button.pull = Pull.UP # 使用内部上拉,按钮未按下时为高电平 buttons.append(button) print("Testing buttons and LEDs. Press any button.") while True: for i, button in enumerate(buttons): # 按钮按下时,value 为 False (因为上拉到高电平,按下接地) if not button.value: leds[i].value = True # 对应 LED 亮起 # 可以添加去抖动逻辑,这里简单处理 else: leds[i].value = False # 按钮松开,LED 熄灭 time.sleep(0.01) # 短延时,降低 CPU 占用这段代码演示了 CircuitPython 处理硬件交互的典型模式:初始化硬件对象,然后在主循环中不断检查状态并作出响应。保存为code.py后,尝试按下开发板上的按钮,观察对应的 LED 是否亮起。
6.4 蓝牙功能快速测试
nRF52840 的核心优势是蓝牙。CircuitPython 通过_bleio库提供了蓝牙支持。由于蓝牙测试相对复杂,这里先做一个简单的广播测试,验证蓝牙栈是否正常工作。
import time import board import _bleio print("Initializing BLE...") ble = _bleio.adapter print("BLE adapter:", ble) # 检查蓝牙是否已启用 if not ble.enabled: ble.enabled = True print("BLE enabled.") # 获取并打印本机蓝牙地址 address = _bleio.adapter.address print("BLE Address:", [hex(i) for i in address.address_bytes]) print("BLE test complete. You should be able to see this device in a BLE scanner app.")运行这段代码,它不会做任何可见的硬件操作,但会在 REPL 打印蓝牙适配器的状态和地址。你可以在手机上打开一个蓝牙扫描 App(如 nRF Connect、LightBlue),应该能搜索到一个名为 “CIRCUITPY” 或类似的无服务设备。这证明 SoftDevice 和 CircuitPython 的蓝牙底层驱动工作正常。
重要提示:CircuitPython 的蓝牙 API (
_bleio) 仍在积极开发中,高级功能(如创建复杂的 GATT 服务)可能不稳定或文档不全。对于生产级别的蓝牙应用,你可能需要等待其更加成熟,或者考虑使用 Arduino 框架搭配 Nordic 原生的 nRF5 SDK。
7. 为其他 nRF52840 开发板适配
本文以 nRF52840-DK 为例,但流程可以迁移到任何基于 nRF52840 的开发板,如 Particle Argon/Xenon/Boron,甚至是你自己设计的定制板。关键在于板级支持包。
7.1 理解 Board Definition
CircuitPython 通过ports/nrf/boards/目录下的子目录来支持不同的开发板。每个板子一个目录,里面包含几个关键文件:
mpconfigboard.h:定义板级宏,如板卡名称、主时钟频率、闪存大小、是否使能特定功能(如蓝牙、特定外设)等。mpconfigboard.mk:Makefile 片段,指定编译时的源文件、链接脚本等。pins.c:最重要的文件,将物理引脚映射到 CircuitPython 中的board.Dx名称。它定义了哪个 GPIO 对应board.LED、board.SDA、board.BUTTON等。
7.2 以 Particle Argon 为例
Particle Argon 是一款集成了 nRF52840 和 ESP32 的物联网模块。在 CircuitPython 源码中,它已经有对应的板级定义particle_argon。
- 编译固件:命令类似,只是
BOARD参数不同。make BOARD=particle_argon clean make BOARD=particle_argon - 烧录 Bootloader:对于非 Adafruit 官方板,通常需要先用 J-Link 和
nrfjprog烧录 bootloader,步骤与第 3 章类似,但BOARD需改为particle_argon。注意,这会覆盖 Particle 原厂固件。 - 烧录 CircuitPython 固件:烧录好 bootloader 后,就可以通过 UF2 或 DFU 方式,使用为
particle_argon编译的固件进行更新了。
为自定义板创建支持: 如果你想为自己设计的板子添加支持,最快捷的方法是“复制-修改”一个现有相似板子的定义。
- 在
ports/nrf/boards/下复制一个现有板子目录,例如feather_nrf52840_express,重命名为你的板子名,如my_custom_board。 - 修改
mpconfigboard.h中的板卡名称、LED/按钮引脚定义等。 - 重写
pins.c文件,根据你的原理图,将物理 GPIO 号映射到有意义的board.名称上。例如,将你板上的用户 LED 连接的 GPIO P0.17 定义为{ MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_P0_17) },。 - 在
ports/nrf/boards/目录下的boards.c文件中,添加你的新板子名,使其被编译系统识别。 - 使用
make BOARD=my_custom_board进行编译测试。
这个过程需要对 nRF52840 的引脚功能和 CircuitPython 的端口结构有一定了解,是进阶玩家的必经之路。
8. 常见问题、故障排查与进阶技巧
即使按照指南操作,你也可能会遇到一些问题。这里汇总了一些常见坑点及其解决方案。
8.1 烧录与连接问题
问题:
nrfjprog找不到 J-Link 或连接失败。- 排查:确保 USB 线连接的是开发板的J-Link 调试口(通常是单独的 Micro-USB),而不是主芯片的 USB 口。
- 排查:检查 J-Link 驱动是否安装。可以尝试运行
JLinkExe看是否能连接。 - 解决:在 Linux 上,可能需要将你的用户加入
plugdev组以获得 USB 设备访问权限:sudo usermod -a -G plugdev $USER,然后注销重新登录。
问题:UF2 文件拖放后,
CIRCUITPY磁盘不出现,或者出现后立刻断开。- 排查:固件可能没有针对你的板卡正确编译。确认
BOARD=参数是否正确。 - 排查:bootloader 版本可能与新编译的固件不兼容。尝试从 circuitpython.org 下载官方预编译的固件进行测试,以排除编译环境问题。
- 排查:USB 线或 USB 口供电不足。尝试更换高质量的 USB 线,并连接到电脑后置 USB 口。
- 排查:固件可能没有针对你的板卡正确编译。确认
问题:DFU 上传时,
adafruit-nrfutil报错No data received from bootloader。- 排查:确认开发板是否已正确进入 DFU 模式(按住特定按钮组合复位)。
- 排查:确认使用的串口号 (
-p) 是否正确。在 Windows 上,COM 号可能在每次插拔后变化。 - 排查:是否有其他串口软件(如 Mu, Arduino IDE 串口监视器)占用了该端口?关闭它们。
- 解决:尝试降低波特率
-b 9600,或添加--touch 1200参数(某些 bootloader 版本需要触发 1200bps 的波特率复位)。
8.2 编译与代码运行问题
问题:
import某些模块(如_bleio,neopixel)时提示ImportError: no module named ‘xxx’。- 原因:这些模块是“内置模块”(frozen modules),它们需要被编译进固件。不是所有模块在最小化编译时都会被包含。
- 解决:在
ports/nrf/boards/你的板子/mpconfigboard.mk文件中,可以添加FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_ModuleName来将特定库冻结进固件。更简单的方法是,将库的.mpy或.py文件直接复制到CIRCUITPY磁盘的lib文件夹中。
问题:程序运行一段时间后死机或无响应。
- 排查:检查代码中是否有死循环且没有
time.sleep()或await asyncio.sleep()。这会导致看门狗定时器(WDT)超时复位。 - 排查:内存泄漏。在循环中创建大量对象而不释放可能导致内存耗尽。尽量复用对象。
- 排查:硬件中断冲突。确保没有多个部分代码同时操作同一个硬件外设。
- 工具:使用
microcontroller.cpu.reset_reason和microcontroller.cpu.heap_size来辅助调试复位原因和内存状态。
- 排查:检查代码中是否有死循环且没有
8.3 性能与优化技巧
- 使用
.mpy文件:将你自己编写的常用模块预编译为.mpy字节码(使用mpy-cross工具),然后放在CIRCUITPY的lib文件夹中。这可以加快导入速度并节省 RAM。 - 管理文件系统:
CIRCUITPY磁盘实际上是在芯片的闪存上模拟的 FAT 文件系统。频繁的小文件写入会磨损闪存并降低性能。对于需要记录的数据,考虑使用microcontroller.nvm(非易失性内存)或外接 SPI Flash/SD 卡。 - 电源管理:nRF52840 具有出色的低功耗特性,但 CircuitPython 的运行时默认并未开启所有睡眠模式。对于电池供电项目,你需要深入研究并使用
alarm和_bleio模块来进入深度睡眠,并在中断中唤醒。
8.4 从原型到产品
如果你打算将基于 CircuitPython 和 nRF52840 的原型转化为产品,需要考虑以下几点:
- 定制 bootloader:产品上可能不需要 UF2 磁盘功能,以增强安全性。你可以修改 bootloader 源码,禁用 USB 大容量存储类,只保留 DFU 功能。
- 冻结关键代码:将核心业务逻辑和库作为冻结模块编译进固件,防止用户误删,也提高启动速度和安全性。
- 安全考虑:CircuitPython 默认是开放的解释环境。对于产品,需要考虑如何防止代码被轻易读取或修改。这可能需要结合 bootloader 的读保护功能,或考虑在量产时烧录加密的固件。
- 固件升级(OTA):利用 nRF52 bootloader 支持的蓝牙 OTA DFU 功能,可以实现产品的无线固件升级。这需要你在应用程序中集成 DFU 触发逻辑和相应的蓝牙服务。
整个过程虽然步骤不少,但一旦走通,你就获得了一个极其强大的快速开发平台。nRF52840 提供了丰富的硬件资源,而 CircuitPython 则大大降低了软件开发的复杂度。从闪烁一个 LED 到构建一个复杂的蓝牙传感器网络,你现在都有了坚实的基础。记住,嵌入式开发就是不断尝试、调试和学习的过程,遇到问题多查阅官方文档、社区论坛和源码,大部分难题都有解决方案。
