<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[博客]]></title><description><![CDATA[由各位成员发布的博文]]></description><link>http://106.52.42.217:6080/category/3</link><generator>RSS for Node</generator><lastBuildDate>Thu, 05 Nov 2026 17:03:22 GMT</lastBuildDate><atom:link href="http://106.52.42.217:6080/category/3.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 03 Nov 2026 14:39:46 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[从空白工程点亮一块小电台：BLUE LONG 独立硬件开发手册导读]]></title><description><![CDATA[
本文是《BLUE LONG 独立硬件开发手册》的导读与提炼。原手册 23 页，面向 Windows + VS Code + ESP-IDF 5.5.4，用四个例程把一台带外壳的小电台当成开发板来用。

先说一个前提，这是整本手册反复强调的第一句话：
这台带外壳的小电台，本身也是一块可以编程的开发板。 但你要驱动它，不需要启动原产品那套消息、好友、菜单和 Mesh 系统——从一个全新的空白 ESP-IDF 工程开始就够了。手册的副标题写得很清楚："从空白工程开始，写下第一行自己的程序。"

一、为什么"新开一个工程"比自己改原系统更好
拿到一台能跑的产品设备，很多人的第一反应是"把源码改改再烧回去"。手册选择了相反的路：

原产品的源码里有一整套业务：消息、好友、训练、Mesh、菜单、分区迁移。任何一行你还没读懂的业务代码，都是你第一次点亮屏幕时的噪音。
独立工程只依赖板级硬件，编译产物小、启动路径短，出错时错误来源唯一。
原产品是"三槽 OTA 分区"，独立工程用的是普通开发分区。混着来的结果是：独立示例的 partition-table 和原版 app 拼在一起，谁也不认谁。

所以手册的目录结构就是一条最短路径：先在 Windows 上把工具链跑通 → 建空白工程 → 编译 → 再谈烧录 → 最后逐个点亮外设。
注意手册自己的说法：本册没有烧录。 文中所有"上板后预期"都是需要用户自己逐项核对的预期值，不是已完成的实机记录。这个态度值得先记住，后面还会反复出现。

二、先分清三个"版本"，这是新手最容易掉的第一坑

VS Code 是编辑器；ESP-IDF 扩展 2.3.0 是编辑器里的操作界面；ESP-IDF 5.5.4 是真正参与编译的 SDK 和工具链。

三件东西、三个版本号，互相独立：



东西
版本
作用




VS Code
随官方下载
编辑器


官方 ESP-IDF 扩展
2.3.0
提供 ESP-IDF: 开头的命令、状态栏、Build/Flash 按钮


ESP-IDF SDK
5.5.4
真正编译的 SDK 与 RISC-V 工具链



装了扩展 ≠ 装了 SDK ≠ 当前工程选中了那个 SDK。 手册里那条对仗很好记："扩展 2.3.0 不等于 SDK 2.3.0"。
正确的顺序是四步：


装 VS Code，用"打开文件夹"打开一个新建的空目录（示例 C:\BLUE_LONG_DEV）。


Ctrl+Shift+X 搜 @id:espressif.esp-idf-extension，核对发布者是 Espressif Systems，再 Install。要复现手册界面，在齿轮菜单里选 Install Another Version → 2.3.0。找不到图标时优先按 F1 搜命令名，而不是照抄别人的截图位置。


F1 → ESP-IDF: Open ESP-IDF Installation Manager，走 New Installation → Custom Installation 向导，把版本固定成 v5.5.4、芯片选 ESP32-C6、安装位置 C:\Espressif、勾选 IDE 集成。
这里有个细节：不要用 Easy Installation，它是"装最新稳定版"。手册要固定 5.5.4，因为后面所有"编译通过"的结论都只对这个版本成立。国内网络可以直接用管理器自带的 Espressif 官方下载源。


回到 VS Code：F1 → ESP-IDF: Select Current ESP-IDF Version 选中 5.5.4 → ESP-IDF: Doctor Command 看诊断 → 最后在 ESP-IDF: Open ESP-IDF Terminal 里敲一句 idf.py --version 独立确认。


第 4 步那个 idf.py --version 非常重要：Doctor 说选对了不算数，命令行能跑出 ESP-IDF v5.5.4 才算数。 如果提示找不到 idf.py，说明你开的是普通 PowerShell 而不是 ESP-IDF 专用终端——环境变量根本没加载，这时候再怎么重装 SDK 都没用。
还有一个很具体的坑：SDK 明明装了，但"选择版本"的列表里看不到。原因通常是扩展没读到 EIM（安装管理器）的登记文件。解法是让管理器重新识别该安装；还不行就在设置里搜 idf.eimIdfJsonPath，指向本机真实存在的 eim_idf.json。不要把版本号手填成路径，也不要照抄别人的用户目录。

三、三条容易被忽略的硬约束
1. 路径必须是纯英文、短
手册把开发目录固定成 C:\BLUE_LONG_DEV，并且明确写了一条实测结论：

如果资料包解压在中文目录，先把完整 examples 文件夹复制到 C:\BLUE_LONG_DEV\examples，再从那里打开编号工程。本轮 ESP-IDF 5.5.4 在中文源码路径出现过 ccache/objdump 路径错误。

这不是玄学。ccache 和工具链的路径处理在 Windows 上对非 ASCII 字符一直不太稳，.bat 能双击跑通，不代表官方工具链能处理同一条中文路径。能省掉的排查，就别留给运气。
2. 芯片要选 esp32c6，不是 esp32c61
ESP-IDF: Set Espressif Device Target 的列表里两者相邻。手册特意给了放大图，标题就是"选择第一行 esp32c6，不是 esp32c61"。
选错的后果不是"稍微有点问题"，而是整个工具链换成另一颗芯片的，后面所有错误都会是假错误。而且换 target 会重配工程，所以顺序必须是：先选 target，再写代码，最后编译。
3. 4 MB Flash + 原生 USB 控制台
在 SDK Configuration Editor 里两处要改：

Serial flasher config → Flash size → 4 MB
Component config → ESP System Settings → Channel for console output → USB Serial/JTAG

第二项决定了你的日志从哪里出来。手册补了一句很关键的边界：GPIO12/13 就是原生 USB 的 D-/D+，不要拿去点灯、接按键或当调试方波用。 烧录成功但 Monitor 一片空白时，先查控制台选项和 COM 是不是同一台设备，不要因此去改 USB 引脚。

四、第一段代码：越短越好
空白工程创建完（ESP-IDF: Create New Empty Project，项目名 blue_long_blank，父目录 C:\BLUE_LONG_DEV），手册让它先只做一件事——打日志：
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

void app_main(void) {
    unsigned count = 0;
    while (1) {
        ESP_LOGI("hello", "Hello BLUE LONG %u", count++);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

两个细节：

app_main 是应用入口，不是 main。
vTaskDelay 是把 CPU 交还给系统，不是"睡一会儿"。用死循环空转等一秒，会把空闲任务饿死，看门狗和电源管理都会出问题。

初学阶段最容易误判的一件事：红色波浪线不等于编译失败。 先把 SDK 和 target 选对，再以 Build 输出的第一条真实错误为准。

五、引脚速查表：这张表决定了你后面所有代码



功能
GPIO / 关系
开发时要记住




OLED 与 RGB 供电使能
GPIO4，高电平使能
共享电源，先拉高等 150 ms 再用


OLED I2C
SDA GPIO5、SCL GPIO6
SSD1306，128×32，7 位地址 0x3C


WS2812 RGB
GPIO7
数字单线时序，不是普通 PWM RGB


编码器 A / B
GPIO2 / GPIO3
两路交错相位，完整旋转要解码


编码器按下
GPIO18
按下为 LOW，独立事件 + 去抖


独立返回键
GPIO19
按下为 LOW，先处理按下沿


压电蜂鸣器
GPIO20 / GPIO21
差分输出，静音 LOW/LOW，只做短提示


原生 USB D- / D+
GPIO12 / GPIO13
保留给 USB，不改普通 GPIO


GPIO0
本轮禁止使用
不配置、不采样、不套旧板 BOOT 接法



这张表里有三个设计点值得单独说：
① 为什么 GPIO4 是"共享电源使能"。 OLED 和 RGB 灯珠挂在同一路开关后面。所以只往 GPIO7 灌 WS2812 数据、却没开 GPIO4，屏幕不亮、灯也不亮——这不是代码错，是没通电。手册把"先 GPIO4 拉高，等 150 ms，再初始化 I2C 和发数据"写成了固定动作，150 ms 是给电源爬升和 OLED 内部复位留的余量。
② 为什么蜂鸣器是差分驱动。 压电片两端接 GPIO20/21，同相 = 不振动 = 静音，反相 = 有电位差 = 发声。静音时两端都是 LOW，所以千万不要把任意一端当成固定接地——那样你只有一个推挽脚，还得额外加直流偏置。差分的好处是驱动电压翻倍、无直流偏置、音量更大。手册要求"约 80 ms、4 kHz 短提示，长按不延长"，是怕一直响把蜂鸣器烧了。
③ 为什么 GPIO0 被划成禁区。 老 ESP32 教程里"进下载模式就 GPIO0 接地"是个条件反射，但这块板上 GPIO0 不用。手册专门写了一句：下载模式连不上时，不要套用旧 ESP32 教程把 GPIO0 接地；先查数据线、USB 驱动和端口占用。
还有一个概念要分清，手册第 10 章专门讲了：芯片 GPIO 编号 ≠ 模组焊盘号 ≠ 现有测试点（TP）。 功能图上的彩色标签是"软件信号"，不是外壳边缘新增的排针。这套图说明的是现有硬件怎么用，不是一块新开发板的设计图。

六、四个例程：把一个系统拆成四块积木
手册最实用的部分是 examples/ 里四个互相独立的工程——不是四个源文件，是四份各自带 CMakeLists.txt、sdkconfig.defaults 和板级组件的完整工程。不要把四份 app_main 塞进同一个 main 一起编译。
01_serial_hello：先看到一行自己的日志
上板预期：每秒出现 HELLO BLUE LONG count=0、count=1……
这个例程不点亮屏幕、不亮灯、不发声。所以显示器黑屏是正常的，不是失败。 它的唯一目的是验证"改代码 → 编译 → 写入 → 看到变化"这条最短路径。建议第一步就是把欢迎语改成自己的名字，确认改动真的来自你当前的工程，而不是旧镜像。
02_oled_hello：共享电源，然后显示文字
流程：GPIO4 拉高 → 等 150 ms → 初始化 I2C 与 SSD1306 → 写 128×32 区域。
上板预期四行：
HELLO BLUE LONG
ESP-IDF 5-5-4
128 X 32 OLED
YOUR OWN PROGRAM

两个坑写进了手册：

屏幕不亮，先查 GPIO4 和电源，再查 SDA/SCL。"看到 SDA5/SCL6 就以为自己在查 I2C"——漏掉共享供电这一步，查多久都没用。
I2C 地址填 0x78 是把 8 位地址写法当成了 7 位参数。按例程接口应该写 0x3C。

另外注意字库边界：这个最小工程用的是精简字库，只支持大写英文、数字和少量符号，每行最多 21 字符。直接把一串中文丢进去，显示不出来是预期行为——中文点阵字库是另一个话题（四 MB Flash 上并不便宜）。
03_input_counter：知道是哪个操作件被触发
旋钮 A/B 走 GPIO2/3，读两路交错相位判断旋转方向；旋钮按下 GPIO18、独立键 GPIO19，都是低电平有效。
上板预期：屏幕显示 INPUT COUNTER、ROT:0、KNOB:0、KEY:0；转动改 ROT，短按旋钮和按独立键各自加计数。
典型故障与正确解法：



现象
正确做法
错误做法




方向反了
确认实际旋钮手感与代码的方向约定
把 A/B 电平、半步、完整卡点混成一个事件


按一次算很多次
查按下沿检测与去抖
无限加大延时



手册还提醒了一条硬件边界：独立键还连着硬件电源开关通路。示例只用短按验证输入，没有接管电源控制芯片；"长按关机"的行为以实板为准，软件示例不承诺改变它。
04_rgb_buzzer：颜色变化与明确的短提示
RGB 数据脚 GPIO7，电源仍与 OLED 共用 GPIO4。按旋钮在 OFF → RED → GREEN → BLUE 之间切换；按独立键发一次 约 80 ms、4 kHz 的短提示音，然后停止；上电默认静音。
两个"不要误解"：

WS2812 是单线数字时序，不是三路 PWM 的模拟 RGB 灯。颜色不对先查数据格式、时序和颜色顺序，别去改 PWM 占空比。
没有声音时先核对按键事件和差分输出。把"响一下"改成"一直响"不是修复，是把蜂鸣器往死里推。


七、全册最值得抄的一段：把硬件约束写成提示词
第 5 章装了 Codex（@id:openai.chatgpt，发布者 OpenAI），但真正有价值的是接下来这段——把硬件事实写成一段契约，整段贴给 AI：
这是一个新的 ESP-IDF 5.5.4、ESP32-C6、4MB Flash 项目。
我要直接开发 BLUE LONG 硬件，不依赖原小电台业务系统。
先读当前 CMakeLists、main 和板级代码，再说明最小实现。
OLED 是 SSD1306 128x32，7位I2C地址0x3C。
GPIO4 是 OLED 与 RGB 的共享电源使能，先拉高并等150ms。
I2C SDA=GPIO5，SCL=GPIO6；WS2812数据=GPIO7。
旋钮A/B=GPIO2/3，按下=GPIO18；独立返回键=GPIO19。
压电蜂鸣器由GPIO20/21差分驱动，默认关闭，只短响。
GPIO0 不使用；GPIO12/13 留给原生USB，禁止重配成普通GPIO。
不复制聊天、好友、Mesh、旧菜单或原产品分区。
一次只实现一个外设；循环要让出CPU，输入要去抖。
只改本项目，说明修改文件，编译并报告真实错误。
不要自动连接串口、烧录、擦除Flash或更改eFuse。
输出是初版代码；编译通过后由我决定是否上板验证。

这段提示词好在哪：

先给事实，再给任务。 引脚、地址、时序都是硬约束，不能让模型"看外形猜"。
划定禁区。 GPIO0 不用、GPIO12/13 不许动、不碰原产品分区——把不可逆操作显式排除。
限定节奏。 "一次只实现一个外设"，避免一次生成两百行跨模块代码。
要求真实错误。 "编译并报告真实错误"，而不是编一个"应该可以了"。
保留决策权。 "由我决定是否上板验证"——AI 不获得烧录权限。

配一条使用纪律：AI 改完先读 diff，确认没有多余功能、没有错引脚，再点 Build。 要让它修错，把第一条编译错误和对应代码给它，不要只说"还是不行"。
安全上也有两句底线：只在官方认证页登录；不要把密码、验证码、API 密钥粘进项目、提示词或截图。

八、命令列表里的"第几行"，是个真问题
F1 弹出的命令列表里有很多长得像的命令，手册几乎每一处都标了"选第几行"：



要做的事
选这条
不要选




完整编译
ESP-IDF: Build Your Project（首行）
只构建 bootloader / partition table 的相近命令


选串口
ESP-IDF: Select Port to Use（第二行）
Select Monitor Port to Use（只改监视器端口）


烧录
ESP-IDF: Flash (UART) Your Project（第三行）
Encrypt and Flash Your Project（加密烧录，不是本教程）


看日志
ESP-IDF: Monitor Device（第二行）
Build, Flash and Start a Monitor（会顺带编译+烧录）



搜索结果的蓝色默认项不一定是你要的那条。以完整名称为准，别按位置背。
烧录前手册还要求停下来核对三件事：芯片是 esp32c6、Flash 是 4 MB、当前示例和 build 输出是这一份，并确认你手上有能恢复原产品的配套工厂固件。烧录本身用构建生成的完整烧录命令与偏移，不要从别人帖子里手抄一个固定 app 偏移。
Monitor 想退出时，常见组合是 Ctrl+]。

九、出错以后：用证据定位，不盲目擦除
这张表是整本手册最"老工程师"的一页：



现象
先做什么
不要误判




列表里找不到 5.5.4
检查 EIM 登记与当前 SDK 选择，跑 Doctor
扩展 2.3.0 不等于 SDK 2.3.0


idf.py 找不到
重开 ESP-IDF 专用终端
普通 PowerShell 没加载环境 ≠ SDK 丢了


构建到最后一步失败
保存第一条实质错误，查依赖与路径
只把最后一行 ninja: build stopped 交给 AI，信息不足


只有 COM1
换数据线、换 USB 口，看设备管理器
COM1 可能是电脑自带端口，只是排在列表最前


COM 拒绝打开
关掉占用它的监视器、网页、烧录器
重装 SDK 不能释放别人占着的串口


串口有日志但屏幕黑
先分清 01 本来就不用 OLED；02 查 GPIO4
USB 工作不代表屏幕电源已开


按一次算很多次
查按下沿与去抖
几十毫秒轮询不等于几十次按下


Flash 容量或芯片异常
保留完整工具日志，核验身份和读取步骤
不要关掉检查，也不要"猜"容量


原小电台菜单不见了
这是独立固件替换产品程序后的预期
不是"开发程序必须含原系统才能跑"



左下角那句尤其重要：全擦 Flash 不是排查手段，是最后的手段。 每次"擦一下试试"，你都在丢掉现场证据。

十、手册对自己的边界说得很清楚
这部分我原样转述，因为它是这份文档最可信的地方：

本次没有烧录。 所有"上板后预期"都待用户核对，不是已完成的实机验收。
四个独立示例确实做过真实交叉编译：ESP-IDF 5.5.4 / esp32c6，四例退出码均为 0，宿主输入逻辑自检通过；源码、镜像校验值和 build.log 都留在各自 manifest 里。但四例编译通过不等于 OLED 方向、旋钮手感、RGB 颜色和声音被验收过。
截图只代表对应界面被采集过，不能证明设备执行了程序。 绿色框、放大①是后加的引导层，原始截图独立保留。
功能图是学习简化图，信号关系取自现有设计，但不能代替焊接定位、维修或 PCB 制造资料；图上的彩色端口不是焊点位置。
不承诺跨分区无损恢复。 想回到原产品，用与实际硬件匹配的原版完整工厂发布包和它自带的烧录入口（该入口可能全擦并恢复出厂状态）；恢复后名字、好友、消息、训练状态要重新核对。
官方示例图（比如 Codex 面板那张 macOS 布局）会明确标注来源，不代表本机账户已登录或硬件已运行。


十一、把整本手册压成一条路线
装 VS Code + 官方 ESP-IDF 扩展 2.3.0
        ↓
EIM 自定义安装 → 固定 ESP-IDF 5.5.4 / ESP32-C6 / C:\Espressif
        ↓
Select Current ESP-IDF Version → Doctor → idf.py --version 独立确认
        ↓
Create New Empty Project（blue_long_blank，纯英文短路径）
        ↓
Set Target = esp32c6 · Flash = 4MB · Console = USB Serial/JTAG
        ↓
Build Your Project（先只在电脑上闭环）
        ↓
挨个过 01 → 02 → 03 → 04（每例独立编译、独立 build 目录）
        ↓
自己决定是否 Flash (UART) + Monitor Device

扩展外设时，手册给的下一个练习也很"教材化"：在已经能显示、能读旋钮的工程里加一个六面骰子——短按开始，约 1 秒后显示 1 到 6，返回键中止；动画不阻塞按键，不用 IMU，不加网络和计分。
这个练习的精髓不是骰子，而是：先让四个例程各自工作，再组合。 组合时如果坏了，你能确定是新加的那部分坏了。

写在最后
这份手册真正在教的，其实是三件事：

把"版本"和"层"分清——编辑器、扩展、SDK、target、COM、控制台，每一层都能单独坏。
把硬件约束写成文字——引脚表、时序、禁区，写清楚给人和给 AI 都一样有用。
把"没验证"说出来——没烧录就说没烧录，编译通过就只说编译通过。

从空白工程到点亮一块 128×32 的屏幕，中间隔着几十个可以出错的地方。手册的价值，就是把那些地方一个个标出来，让你第一次就走在正确的路上。

原手册：BLUE LONG 独立硬件开发手册（Windows / ESP32-C6 / ESP-IDF 5.5.4 / 2026.10，共 23 页）

]]></description><link>http://106.52.42.217:6080/topic/12/从空白工程点亮一块小电台-blue-long-独立硬件开发手册导读</link><guid isPermaLink="true">http://106.52.42.217:6080/topic/12/从空白工程点亮一块小电台-blue-long-独立硬件开发手册导读</guid><dc:creator><![CDATA[niuguanchong]]></dc:creator><pubDate>Tue, 03 Nov 2026 14:39:46 GMT</pubDate></item><item><title><![CDATA[半部电台：用 ESP32-C6 做一台“附近通信”旋钮小设备——XidianFlipper 代码阅读笔记]]></title><description><![CDATA[
项目地址：qilaotie/XidianFlipper ｜ 在线社区：半部小岛

"半部电台起家"是西电校史里最常被提起的一句话。1931 年，红军第一部电台只能收、不能发，于是被叫作"半部电台"。这个项目借了这个名字，但它并不是一台真的无线电——它是一台基于 ESP32-C6 的旋钮小设备：靠 BLE 广播在几十米内和同伴互发消息、一起玩桌游、养一只电子宠物，顺便还能当 PPT 遥控器和 Wi-Fi 扫描器。
本文是我读这个仓库源码后的笔记，重点放在它是怎么搭起来的，以及那些只有在真机上才会踩到的坑。

一、硬件与工程基础



项目
配置




主控
ESP32-C6，4 MiB Flash


屏幕
128×32 SSD1306 OLED（I2C，地址 0x78）


交互
EC06 旋转编码器 + 独立按键 BACK


声光
MCPWM 蜂鸣器、RMT 驱动 WS2812


电源
GPIO4 集中管理负载开关 + ADC 电量检测


工具链
ESP-IDF 5.5.4，CMake 工程



工程按 firmware/（业务）与 components/（第三方与公共组件）两大块切分，业务目录再分为 app / config / drivers / protocol / radio / system / ui：
firmware/
├── app/        # 消息、好友、Wi-Fi、无线工具、摩斯训练
├── config/     # constants.h / pins.h / defaults.h / log_config.h
├── drivers/    # ec06 编码器、rgb_led、rmt_ws2812、mcpwm_buzzer、adc_battery...
├── protocol/   # mesh_packet.c、mesh_crypto.c
├── radio/      # ble_radio.c、tool_hid.c
├── system/     # nvs_config、log_manager、power_mgmt、event_dispatcher
└── ui/         # menu_pages、local_pages、party_pages、pet_page、dev_mode...
components/
├── u8g2/                  # 图形库（含字模）
├── u8g2-hal-esp-idf/      # I2C bit-bang 回调
└── oled_mirror/           # 把屏幕画面经 USB CDC 投到电脑

初始化顺序是这份代码里最值得抄的部分
firmware_init_all() 里每一步都有注释说明"为什么必须在这里"，这不是装饰，是血泪：
event_init();                 /* 1. 任何任务通信之前 */
load_switch_init();           /* 1.5. GPIO4 总电源，任何外设之前 */
log_manager_init();           /* 2.5. 事件之后、其他模块之前 */
buzzer_init();  haptic_init();
nvs_config_init();            /* 5. NVS 必须在 rgb_led 之前：
                                     否则 g_config 全零 → 亮度 0% */
rgb_init();  rgb_led_init();
ec06_init(&amp;ec06_cfg);         /* 7. 编码器 ISR + 任务 */
...
nvs_init();  ui_init();       /* OLED 提前亮，消除上电黑屏 */
u8g2_Setup_ssd1306_i2c_128x32_univision_f(...);
mesh_crypto_init();           /* 9. 加密必须在无线之前就绪 */
ble_radio_init(mesh_notify_dispatch, NULL);  /* 10. NimBLE 主机任务 */
quick_msg_init();  mesh_notify_init();

几个细节：

负载开关最先初始化。整块外设供电挂在 GPIO4 上，晚一步就是莫名其妙的 I2C 失败。
NVS 早于 RGB。配置从 Flash 读出来之前，结构体是零值，亮度按 0% 处理，用户看到的就是"灯不亮"。
OLED 抢先于 BLE。NimBLE 初始化会占用一截时间，先把屏幕点亮，用户感知到的开机时间就短了。
BLE 的致命错误有兜底：读 eFuse MAC 失败或设 BLE 地址失败时，红灯快闪 10 次、屏幕上画 FATAL ERR 0x01/0x02，然后死循环等看门狗。宁可停住，也不带着半残的协议栈跑。

冷启动还有一段"开机图 2 秒 + 黑屏 1 秒"的过渡，而从 DeepSleep 按键唤醒时会跳过这段直接进 UI——g_wake_reason 判断一下就行，成本极低，体验差别很大。

二、协议层：27 字节头 + 128 字节载荷
firmware/protocol/mesh_packet.h 定义了整套私有协议 v3，线格式非常克制：
#define MESH_MAGIC        0x4D45   /* 'M','E' */
#define MESH_VERSION      3
#define MESH_HEADER_SIZE  27
#define MESH_PAYLOAD_MAX  128
#define MESH_CRC_SIZE     2

typedef struct {
    uint16_t magic;
    uint8_t  version;
    uint8_t  src_mac[6], dst_mac[6];   /* 用 MAC 直接寻址 */
    uint8_t  type, len, flags, channel;/* channel: 1~16 */
    uint32_t session_id, msg_id;       /* 会话 + 消息序号，做回执去重 */
    uint8_t  payload[MESH_PAYLOAD_MAX];
    uint16_t crc16;
} mesh_packet_t;

打包后最大 27 + 128 + 2 = 157 字节——刚好塞进 BLE 的传统广播/扩展广播里，不需要建连接。几个设计取舍：

用 MAC 当身份。src_mac/dst_mac 六字节，广播地址全 FF。好处是零配置、无路由表；代价是换设备就换身份，好友关系必须落 NVS（namespace = "mesh_cfg"）。
flags 一个字节塞了四件事：ACK_REQUEST、ROUTED、ANNOUNCEMENT、CUSTODY，再加两位跳数（MESH_HOP_MASK 0x0c，MESH_TOTAL_HOPS_MAX 3）。协议从"能通"进化到"能转发、能代存"，头长度一字节没变。
session_id + msg_id 撑起了回执与重试。发送状态机（已发 / 已收 / 超时）全靠这两个字段，MESH_TYPE_RECEIPT 就是回执报文。
18 种消息类型共享同一条管线：快捷消息、摩斯、好友请求、信标、回执、石头剪刀布、寻宝、反应赛、数字炸弹、宠物、协作、社交……加新玩法基本只是往枚举里加一个 MESH_TYPE_*。

最值得称赞的是头文件第一行注释：

/* BLE local messaging v3. Wire integers are little endian; CRC is not authentication. */

CRC 不是认证。 这句话省掉了很多误解：这不是加密私聊，抓包就能读；游戏里也没有密码学防作弊。项目在 README、功能说明、工具说明里反复重申这一点——工程文档里肯把边界写清楚，比多堆十个功能更难得。

三、通信载体：广播式"对讲"，以及功耗的账
设备之间不建 BLE 连接，全靠扫描广播收发。constants.h 里这几个数字决定了续航：
#define SCAN_PERIOD_MS   5000   /* 扫描周期 5s */
#define SCAN_WINDOW_MS   100    /* 扫描窗口 100ms → 2% 占空比 */
#define COVER_ADV_MS     6000   /* 覆盖广播 6s ≥ 扫描周期 5s */
#define BLE_ADV_INTERVAL_MIN_MS 100
#define BLE_ADV_INTERVAL_MAX_MS 300


扫描占空比只有 2%，所以平时几乎不耗电；代价是延迟——最坏要等一个扫描周期才收到消息。
COVER_ADV_MS &gt; SCAN_PERIOD_MS 这条不等式是整个"对讲感"的关键：发送方要保证自己广播的持续时间覆盖住对方一整个扫描周期，否则就会出现"我明明发了对方没收到"的经典 bug。
DeepSleep 周期 5 秒，冷启动后留 15 秒宽限期禁止休眠——不然 USB 还没枚举完设备就睡了，串口工具上永远看不到设备。

这套机制的能力边界也因此很明确：不需要路由器、SIM 卡或手机，但它也不是运营商短信，不是标准 Bluetooth Mesh。三跳中继是实验特性，默认关闭、重启不保留，要三台同版设备才能实测 A→B→C。

四、界面：128×32 像素怎么装下一个系统
屏幕只有 128×32，四行文字。项目直接移植了 WouoUI 的菜单框架（ui.layer / ui.state / ui.select[] / ui.fade），配合 u8g2 渲染。
在这个尺寸上做 60 多个功能入口，靠的是三件事：

分层菜单：主菜单按"消息 → 摩斯电台 → 学习 → 好友 → 趣玩 → 工具 → 设置 → 关于"排列，长描述走滚动。
三套皮肤：经典像素、极简线框、终端，设置里切换、重启保留。
中英双语开关：主菜单、设置、工具、趣玩全部双语，即时生效。中文用的是精简字库——128×32 的屏幕上，全量 CJK 点阵字模对 4 MiB Flash 来说并不划算。

一个小而妙的组件：OLED Mirror
components/oled_mirror 把每一帧画面通过 USB Serial/JTAG 发给电脑：
usb_serial_jtag_driver_config_t usj_cfg = {
    .tx_buffer_size = 4096,   /* 整帧/日志/控制帧不被截断 */
    .rx_buffer_size = 256,
};
oled_mirror_begin(&amp;u8g2, usb_cdc_send_callback);

一帧 128×32/8 = 512 字节点阵，加 5 字节帧头正好 517 字节，所以 TX buffer 必须给到 4096 且写入超时只有 10 ms（USB 没插时静默丢弃，绝不阻塞渲染）。更妙的是它顺手 esp_log_set_vprintf() 接管了日志，让 ESP_LOGI 和画面走同一套帧格式——演示、答辩、录屏时不用举着放大镜看 128×32 的小屏。这个组件还单独做了 ENABLE_OLED_MIRROR 编译开关，不影响正式构建。

五、功能盘点到什么程度
不逐条罗列，只看"密度"：

MESSAGES：快捷短语 10 条（单条上限 48 字符，见 QUICK_MESSAGE_MAX_LEN）、机身 ASCII 编辑、字符表情、六槽持久草稿箱、频道（1–16）、好友定向、收件箱与发送状态。
摩斯：实时收发、自由练习、听音选字、跟练、听写。所有时序阈值都随 WPM 动态计算（morse_engine_set_wpm()），只保留固定值：20 ms 消抖、±30% 教学容差、600–900 ms 字母间隔、1500 ms 空闲自动发送、5000 ms TX 回 RX。
FUN 14 个入口：宠物、竖屏俄罗斯方块、贪吃蛇、答案之书、骰子、花旗骰、附近石头剪刀布、寻宝雷达、反应赛、数字炸弹、1–10 秒盲计时、玩法说明、灯光蜂鸣合奏、旅行护照。
多人桌游 10 款：UNO、谁是卧底、达芬奇密码、骗子酒馆、情书、快艇骰子、阿瓦隆、一夜狼、狼人杀、井字棋。统一"创建 → 图形准备房间 → 开局进牌桌 → 结算回原房间重新准备"的流程，还支持多桌大厅、只读观战、邀请。
宠物：四属性（饱食/心情/精力/清洁）、四阶段成长、三成年分支、七个内部小游戏、金币商店、背包装饰图鉴、双机拜访与对战、房间皮肤。没有死亡设定，喂食清洁睡觉全免费。
TOOLS 11 项：PPT/媒体/拍照 HID 遥控、Wi-Fi 连接、云同步、Wi-Fi 扫描、BLE/RSSI 扫描、内存状态、实验性自组网、网络诊断、队伍与社交。

注意 HID 遥控期间电台通信会暂停——BLE 主机和外设角色抢同一套射频资源，这是硬约束，写在了手册里。

六、最硬的一仗：分区迁移
这是整个项目里我最想单独讲的部分，因为它是不可逆操作。
原版固件的分区是老三槽布局，单个应用槽只有 1,245,184 字节。应用从 768,016 字节一路长到 1,527,808（当前发布包 1,739,152），三槽布局再也装不下：



指标
冻结原版
迁移后




单应用槽
1,245,184 B
2,097,152 B（2 MiB）


应用镜像
768,016 B
1,739,152 B


分区方案
旧三槽 + OTA
factory-2m-usb-v1，取消两个 OTA 槽


更新方式
OTA / USB
仅 USB



关键约束是两处 NVS 的地址和大小绝对不能动：

nvs：0x9000 / 0x6000
nvs_sec：0x3b0000 / 0x6000

因为它们存着 NVS v7 配置、storage ABI 1 的 UI 参数、宠物数据、游戏最高分和好友列表。工程里 docs/partition_migration.md 和 AGENTS.md 把流程钉死成几条：迁移前必须双读校验整片 Flash 备份 → 迁移工具必须验证设备身份、旧布局和备份 → 不能只刷分区表 → 完成后整片回读确认未授权区域没被写。
结果也记录得很实在：COM9、COM10、COM15 三台设备分别完成双读整片备份、2 MiB 迁移和完整回读，确认 bootloader 未替换（COM15 因旧 bootloader 不同另行批准更新）、两处 NVS 及其他未授权区域不变。
代价换取：没有 OTA 了。这很痛，但 factory 0x20000 / 0x200000 的单槽方案让应用可用空间从 1.2 MiB 涨到 2 MiB，功能密度才成为可能。工程在 README 里明确写了"迁移后仅支持 USB 更新，不支持 OTA"，没有含糊。

七、工程习惯：把"没验证"写进文档
这份仓库最不像"学生作业"的地方，是它对自己的验收口径极其苛刻：

构建产物带指纹：输出到 build/release/，包含 manifest.json、镜像、源码记录、validation/build.log 和 SHA-256，python tools/verify.py 独立核验。README 直接给应用哈希 21c39ff9...a916f3，不靠"我记得我编过"。
模拟与实机分开报告：AGENTS.md 里那句"宿主模拟测试和实机验收分开报告；不得把模拟帧率、断电或回滚结果表述为实机通过"，是很多项目缺的一条纪律。发布清单里 hardware_tested: false 一直保留着，哪怕已经上机测过一部分。
能力边界逐条列出：RSSI 只表示信号冷热、不给方向距离；拿到 IPv4 不等于能上网；KEY SENT 不等于真的拍了照；云分享关闭不等于撤下，必须执行 REMOVE ONLINE。
版本号不自作主张：扩展版固件字符串仍是 1.0.0-noautosleep，靠发行清单和 SHA-256 区分构建，明确"不把本扩展版误称为 V1.5"。

顺带一个工程上的小坑：components/u8g2/csrc/u8g2_fonts.c 有 40 MB 源码，几万个字模。ESP-IDF 默认 -ffunction-sections -fdata-sections + 链接期 --gc-sections 会丢掉没引用的段，所以最终镜像不会爆炸，但编译时间和仓库体积是真的贵——这也是它把完整 u8g2 直接 vendor 进来要付的账。

八、它不是什么
作者写得很清楚，我照抄一遍，因为这几句比功能列表更有价值：

没有 IR、NFC/RFID、Sub-GHz，不是完整 Flipper Zero 替代品；
消息不是加密私聊，游戏没有密码学防作弊；
Wi-Fi 凭据未启用 NVS 加密，不要输入真实敏感信息；
不是运营商短信，不是标准 Bluetooth Mesh，也不是互联网电台；
云排行榜是设备自报的，不是防作弊比赛。

一台 4 MiB Flash、128×32 屏幕、靠一颗旋钮操作的小设备，做到"附近能聊、能一起玩十款桌游、能养宠物、能当遥控器"，靠的不是堆料，而是把每一字节的协议头、每一次扫描占空比、每一段 NVS 地址都算清楚。

九、可以带走的三条经验

初始化顺序就是需求。事件系统、电源开关、NVS、屏幕、协议栈，谁在谁前面都有理由；写成注释，下一个人（或三个月后的你）会感谢自己。
协议头要预留成长空间。v3 的 27 字节头从"点对点消息"长到"三跳中继 + 代存 + 回执"，头长度一次没变，全靠 flags 里的两位跳数和四种标记。前期省一点，后期就能改。
不可逆操作要有备份和验收清单。分区迁移那套"双读备份 → 验证身份/旧布局 → 只改授权区域 → 整片回读比对"，比任何功能都更像一个真正的工程项目。

如果对"半部电台"感兴趣，仓库里的 docs/ 有完整手册：功能简介、使用手册、宠物手册、桌游适配、分区迁移、发布与构建。也可以去半部小岛看看设备自报的排行榜和宠物展示柜。

半部电台起家，靠的从来不是设备多好，而是有人愿意把能收的那一半，慢慢做成能发的那一半。

]]></description><link>http://106.52.42.217:6080/topic/11/半部电台-用-esp32-c6-做一台-附近通信-旋钮小设备-xidianflipper-代码阅读笔记</link><guid isPermaLink="true">http://106.52.42.217:6080/topic/11/半部电台-用-esp32-c6-做一台-附近通信-旋钮小设备-xidianflipper-代码阅读笔记</guid><dc:creator><![CDATA[niuguanchong]]></dc:creator><pubDate>Tue, 03 Nov 2026 14:36:09 GMT</pubDate></item></channel></rss>