MOS 端到端操作手册

从零开始:ESP32-S3(MPU6050)/ ESP32-C6(AM2302)→ 40 字节定长帧 → 上位机 APP
版本 2026-09-23(含内核与驱动里程碑:GPIO / I2C / ADC / PWM / SPI) 实测锚点:S3/COM3 · MPU6050 0x68 · SDA=GPIO8 SCL=GPIO9 C6/COM4 · AM2302 · GPIO4 · RMT 1µs 上位机:tools/thermo_display.py
这份手册是给「第一次接手的人」写的。从头照做即可跑通,每一步都给了 可判定的结果(成功长什么样、失败长什么样、什么情况属于「没执行」而不是「成功」)。 接线细节另见本站《MOS 硬件接线指导》,本文只给速查,不重复。
2026-09-23 里程碑:MOS 内核已脱离 FreeRTOS。内核节拍改由 ESP32 硬件 GPTIMER 定时器中断直接驱动,app_main 启动 GPTIMER 后即 return,FreeRTOS 仅剩空闲壳; 移植源码零 FreeRTOS 头/API,新增 GPIO 驱动框架并完成真机自检。真机证据与诚实边界见 第 11 节。

0 · 先读:三条最容易踩的坑

  1. 「编译返回 0」不等于「你烧进去的是新固件」。ESP-IDF 的构建目录按 target 分: esp32s3 → build/,其余 → build_<target>/。目录选错会 烧写成功、rc=0、打 [OK],但烧的是上一次的旧产物。本仓的 tools/idf_flash.py 专门为此加了两条拒绝烧写的检查(见第 5 节)。
  2. 「找不到板子」与「板子在但器件没接」现象完全一样。固件读不到器件时会 如实声明(frame-source=SYNTH 或干脆停发),而不是拿合成值顶上。 所以看到「没有数据」先分两件事:串口有没有开对?器件/接线在不在?
  3. 退出码 3 是「未执行」,不是「通过」。本仓所有工装用三态:0=成功、 1=失败、3=没跑到(缺硬件/缺依赖/口没打开)。 把 3 当成绿是这套体系里最严重的错误 —— 它会让「没验证」伪装成「验证过」。

1 · 器材与软件清单

1.1 硬件

件型号 / 规格作用接入
主控 AESP32-S3-DevKitC,N16R8(16MB Flash / 8MB Octal PSRAM) MPU6050 六轴 + 温度节点;同时是板载 RMT 逻辑分析仪的载体USB → COM3
主控 BESP32-C6-DevKitC,N8(8MB Flash) AM2302 温湿度节点(测温应用的真帧源)USB → COM4
传感器 AMPU6050(I2C,从机地址 0x68) 加速度 / 陀螺 / 片上温度SDA=GPIO8,SCL=GPIO9
传感器 BAM2302 / DHT22(单总线) 温度 + 湿度DATA=GPIO4
上拉电阻4.7kΩ–10kΩ(建议备 2 个) I2C 与单总线都需要。固件打开的是芯片内部上拉(标称约 45kΩ),比数据手册口径弱一个数量级—
屏(可选)MPI7002,7″ 1024×600,HDMI 输入 上位机 APP 的大屏输出接 PC / 树莓派,不是接 ESP32
屏为什么不接 ESP32:MPI7002 是 HDMI 输入的显示器,而 ESP32-S3/C6 都没有 HDMI 输出。判据不是「哪块板更强」,而是谁有 HDMI 口。 没有外接屏时,直接用开发机自己的屏幕即可(第 8 节)。

1.2 软件

软件本机实测位置说明
ESP-IDF v5.5D:/Espressif/v5.5/esp-idf 构建 S3 / C6 固件
IDF 工具链C:/Espressif/tools 由 eim 安装;IDF_TOOLS_PATH 指向这里
Python(带 pyserial + tkinter) C:/Espressif/tools/python/v5.5/venv/Scripts/python.exe 必须用这个:系统 PATH 上的 python 没有 pyserial,会 ModuleNotFoundError: serial
Rust(可选)cargo只在跑 Gateway 交叉验证时需要(第 7 节)

下面所有命令都假定:仓库根目录为工作目录,且 python 指向上表那个 venv 解释器。 为省事可以先设一个别名:

# Git-Bash
alias py='C:/Espressif/tools/python/v5.5/venv/Scripts/python.exe'
# 之后本文所有 python 命令都写作 py

2 · 一次性环境准备

1克隆仓库

git clone http://gitlab.ptteng.com/openclaw-bot/elang-eos.git
cd elang-eos
git checkout mos/bootstrap

2导出两个 ESP-IDF 环境变量

这两个是隐性知识,不设会以各种奇怪的方式失败:

export IDF_TOOLS_PATH=C:/Espressif
export ESP_ROM_ELF_DIR=C:/Espressif/tools/esp-rom-elfs/20241011

3跑一次离线门禁,确认你的环境是干净的

py verify.py --offline

期望结尾出现 ALL THREE LINES GREEN 且退出码 0。 若这里就红了,先别接硬件 —— 环境问题与硬件问题混在一起查,成本翻十倍。

判据:退出码 0。若看到 3(SKIP/INCOMPLETE), 说明某项没跑(通常是缺编译器或缺依赖),而不是「过了」。

3 · 接线速查

详细后果分析、面包板落位、逐线接错会怎样,见本站 《MOS 硬件接线指导》。这里只给最小连线表。

3.1 S3 ↔ MPU6050(I2C)

MPU6050ESP32-S3备注
VCC3V3只上 3.3V
GNDGND必须共地
SDAGPIO8建议 4.7k 上拉到 3V3
SCLGPIO9建议 4.7k 上拉到 3V3
AD0GND决定地址:接 GND → 0x68;接 3V3 → 0x69

3.2 C6 ↔ AM2302(单总线)

AM2302ESP32-C6备注
VCC3V3只上 3.3V;给 5V 会把数据线电平抬到 5V,可能打坏 C6
GNDGND必须共地
DATAGPIO4单总线,需上拉(强烈建议外挂 4.7k–10k)
NC悬空第三脚不接
上电前必须确认:没有把 5V 接到任何 GPIO;GND 已连;AM2302 的 DATA 确实在 GPIO4 而不是旁边的脚。「没接」与「接着但坏了」在电气上完全同形 —— 固件只能证明「线上没有活的东西」,分不开这两者,只能靠人看一眼实物。

4 · 编译固件

用本仓的 tools/idf_build.py,不要直接敲 idf.py: ESP-IDF 会拒绝 MSYS/Mingw 环境(MSYSTEM 在每次启动进程时被重新注入, 在 bash 里 unset 是没用的)。该工具在 Python 里构造干净环境再启动 cmake, 把这条注入链切断。

A · S3 帧源固件(MPU6050 六轴 + 温度)

py tools/idf_build.py --target esp32s3 --project firmware/esp_frame_tx

B · C6 测温固件(AM2302 温湿度)

py tools/idf_build.py --target esp32c6 --project firmware/esp_thermo

构建目录分别是 firmware/esp_frame_tx/build/ 与 firmware/esp_thermo/build_esp32c6/。这条规则第 5 节还要用。

编不过时先看这一条:本工程开启 -Werror。典型坑是 printf("%u", x) 而 x 是 uint32_t(即 long unsigned)→ 格式串不匹配直接编译失败。修法是强转: printf("%u", (unsigned)x)。

5 · 烧录固件

# S3(默认 target=esp32s3、project=firmware/esp_frame_tx)
py tools/idf_flash.py --port COM3

# C6 测温固件
py tools/idf_flash.py --port COM4 --target esp32c6 --project firmware/esp_thermo

先干跑一遍看它打算烧什么(不碰硬件):

py tools/idf_flash.py --port COM3 --dry-run
这个工具会做两条「拒绝烧写」的检查 —— 它们都是被实测踩过才加的:
  1. 构建目录的 target 与 --target 不符 → 拒。 否则会「烧写成功、rc=0、[OK],但烧的是 20 分钟前那个目录里的旧产物」。
  2. 产物 mtime 比源码旧 → 拒(--allow-stale 可越过,但要在结论里写明)。 「构建返回 0」不等于「bin 是新的」。
偏移是硬事实、不可覆盖:bootloader 在 0x0、分区表 0x8000、 app 0x10000。写错不会报错,只会启动空白。 工具只接受「从 build/ 里自己找」,不接受手填偏移。

退出码:0 成功;1 找不到产物/目标不符/产物过旧; 2 打开串口或烧写失败;3 未执行(缺 esptool)。

6 · 确认板子真的在跑(不许跳过)

烧完先看板子自己说什么,再谈上位机。复位后抓一段串口:

py tools/serial_capture.py COM4 22 --reset --out c6.txt

6.1 C6 测温节点 · 期望看到

#thermo frame-source=AM2302_LIVE ...
#thermo seq=0 temp=2780 hum=5260 ...
#thermo seq=1 temp=2790 hum=5270 ...
...

6.2 S3 帧源节点 · 期望看到

#frame_tx frame-source=MPU6050_LIVE who=0x68 note=hum_c100 恒为 0(MPU6050 无湿度源)

6.3 板载自检 · 逻辑分析仪(可选,验证 E 代码真的在用)

py tools/la_align_decode.py spec/evidence/la_i2c_mpu6050.txt

这会独立解码一份入库的双通道 RMT 波形,期望输出 0xD0 0x75 0xD1 0x68(MPU6050 的 WHO_AM_I),退出码 0。 它不依赖固件自报,是第三方视角的复核。

7 · 主机侧收帧 / 进 Gateway(进阶)

只想看温湿度,可以跳过本节直接做第 8 节。本节是「把真帧接进网关做交叉验证」的路径。

7.1 收帧存盘

py tools/serial_frame_bridge.py --port COM3 --seconds 30 --out frames.bin

它做三层校验,逐层加强:L1 结构(magic 'ME' + ver==1)、 L2 自洽(帧内 CRC 与第三方 zlib.crc32 一致)、L3 内容(整帧 40 字节与 按 seq 独立算出的期望帧逐字节相同)。

7.2 喂给 Gateway

cd gateway
cargo run -- file:../frames.bin --boot-grace 0
# 或实时监听:先 --serve-tcp 15040,再 cargo run -- tcp:127.0.0.1:9100
诚实边界(不要误读):Gateway 目前只有上行 40 字节帧, 没有下行链路,所以 scan_expired_leases() 恒返回空 —— 那是「没有派发过」,不是「回收正常」。别拿它当验收依据。

8 · 打开上位机 APP

测温应用 = tools/thermo_display.py。它只读 40 字节定长帧,按 firmware/frame-spec.md 的偏移表解析;CRC 不符即丢弃并重扫,绝不放行。

场景 A · 用开发机自己的屏幕(最省事)

py tools/thermo_display.py --port COM4 --node am2302 --ui tk

场景 B · 屏接成第二块屏(7″ 1024×600,不占主屏)

py tools/thermo_display.py --port COM4 --node am2302 --ui tk --geom 1024x600+1920+0 --kiosk

--kiosk 在面板屏上必须加: 600 高的屏要减掉标题栏与任务栏,不加的话最下面两条会被压在任务栏底下。退出按 Esc。

场景 C · 无显示器(只在终端看,排障用)

py tools/thermo_display.py --port COM4 --node am2302 --ui none --seconds 10

场景 D · S3/MPU6050 帧源

py tools/thermo_display.py --port COM3 --node mpu6050 --ui tk

MPU6050 没有湿度传感器,UI 上湿度显示为 —(无源),不是 0.0。「没有来源」与「测得 0」是两件事。

8.1 常用参数

参数默认说明
--portauto串口;auto 自动查找
--nodeam2302am2302(C6,温+湿) / mpu6050(S3,只有温度)
--baud115200与固件 UART0 一致
--uitktk=窗口,none=只看终端
--geom—窗口区域,如 1024x600+1920+0(缩放按该区域算)
--kioskoff与 --geom 合用:去标题栏并置顶,正好铺满
--seconds0跑 N 秒后收尾并打印统计;0=一直跑
--mqtt—可选,上行到 MQTT broker 主机
--selftest—离线自检:不接硬件,跑协议解析 + 排版判据

8.2 先跑自检(不接硬件也能验)

py tools/thermo_display.py --selftest
py tools/thermo_display.py --ui-smoke

9 · 判据表:成功 / 失败 / 未执行

每一级都有可判定的结果。含糊的「看起来出来了」不算。

级别看什么成功未执行(不是成功)
L1 环境verify.py --offline ALL THREE LINES GREEN,rc=0rc=3(有项 SKIP)
L2 构建idf_build.py rc=0 且产物 mtime 新于源码用错目录(旧产物)
L3 烧录idf_flash.py rc=0,且工具未报 target 不符/产物过旧rc=3(缺 esptool)
L4 器件串口 banner frame-source=…_LIVE 且 seq 递增 只有 SYNTH,或完全没有帧行
L5 链路thermo_display.py 屏上有数且持续更新 大数字变灰显示 NO DATA
L6 第三方la_align_decode.py rc=0,解出 0xD0 0x75 0xD1 0x68 rc≠0
L5 的 NO DATA 是设计行为,不是 bug。超过 STALE_S 秒没有有效帧, 界面会把大字变灰并显示 NO DATA,不保留旧值假装还在更新。 看到 NO DATA 说明「链路断了」,而不是「界面坏了」。

10 · 故障速查(软件侧)

现象最可能的原因怎么处置
ModuleNotFoundError: serial 用了 PATH 上的系统 python(没装 pyserial) 改用 IDF venv 那个解释器(第 1.2 节)
MSys/Mingw is not supported 直接敲了 idf.py 改用 tools/idf_build.py
烧完板子毫无反应、串口安静 偏移错 / 烧的是别的 target 的产物 跑 idf_flash.py --dry-run 看三个产物路径
界面显示 NO DATA 串口没开对 / 板子没发帧 / 器件没接 先用 serial_capture.py 单独看板子说什么
字符串格式 warning 导致编译失败 -Werror + %u 配 uint32_t 强转 (unsigned)
屏上有字但互相重叠 主机侧排版(字号单位:Tk 正数=点,会放大 4/3) 跑 --selftest,它的几何判据会指出越界/相交
相关页面:《MOS 硬件接线指导》(接线、面包板、逻辑分析仪)、 《测温应用 · 源码与使用说明》(应用本体与源码清单)。
本手册的每一条命令与判据都来自仓库内实测记录;文中标「可选/进阶」的部分 不影响最小闭环。

11 · MOS 内核真机里程碑(2026-09-23):脱离 FreeRTOS,GPTIMER 中断驱动节拍

本节能单独成立:MOS 微内核已不再寄生在 FreeRTOS 之上,而是用 ESP32 硬件 GPTIMER 定时器中断直接驱动内核节拍。下面给的是真机证据,不是描述。

11.1 做了什么(与旧版的本质区别)

11.2 真机证据(COM3 · ESP32-S3)

完整记录见仓库 spec/evidence/mos_esp32_s3_full.txt。关键行:

=== MOS kernel bring-up on ESP32-S3 (FreeRTOS-INDEPENDENT) ===
arena bytes = 4096
... 六组测试 A-F ...
=== ALL PASS (failures=0) ===
I (293) main_task: Returned from app_main()   # FreeRTOS 仅空闲壳
=== GPIO driver self-check (MOS drives real registers) ===
[PASS] GPIO2 set/readback consistent
# MAAAA/BBBB 轮转流从 tick0 起(GPTIMER ISR 驱动)

11.3 一个真 bug 被自检挡下(为什么可信)

首版自检是 [FAIL] GPIO2 readback mismatch:ESP32-S3 的 GPIO_MODE_OUTPUT 是纯输出、输入缓冲被关,gpio_get_level() 读输入寄存器恒回 0。改成 GPIO_MODE_INPUT_OUTPUT(开输入缓冲,pad 电平反映驱动值)后复烧 → PASS。这说明自检不是绿灯表演,它挡住了一处"看起来能写、实际读不回"的假成功。

11.4 诚实边界(不许当商用 OS 吹)

12 · MOS 驱动框架扩展(2026-09-23):I2C 驱动真实 MPU6050

继 GPIO 之后,MOS 驱动框架接入第二个真机可验证驱动——I2C。自检在板载 MPU6050(SDA8/SCL9)上读取 WHO_AM_I 寄存器(地址 0x75),应回 0x68。这是对真实硅片的 探测,证明 MOS 真的把数据送上了 I2C 总线并与真实器件成功通信,而不是"看起来能写"。

12.1 驱动接口(firmware/mos_esp32/main/mos_drivers.h/.c)

12.2 真机证据(COM3 UART 抓证)

=== I2C driver self-check (probe MPU6050 on SDA8/SCL9) ===
[PASS] I2C MPU6050 WHO_AM_I=0x68 detected

12.3 门禁

12.4 诚实边界

13 · MOS 驱动框架扩展(2026-09-23):ADC 驱动真实模数转换

MOS 驱动框架接入第三个真机可验证驱动——ADC。自检在 ADC1/CH4(GPIO5)上读 4 次原始值, 断言每次都落在 12 位合法区间 [0,4095]。这证明 MOS 真的驱动了 ADC 外设并完成一次转换。

13.1 驱动接口

13.2 真机证据(COM3 UART 抓证)

=== ADC driver self-check (ADC1 CH4/GPIO5) ===
[PASS] ADC raw reads in [0..4095] x4

13.3 诚实边界

14 · MOS 驱动框架扩展(2026-09-23):PWM(LEDC) 驱动真实定时器

MOS 驱动框架接入第四个真机可验证驱动——PWM(LEDC)。自检配置 LEDC 定时器+通道并设 50% 占空比; ESP-IDF v5.5 无 ledc_get_duty,故以"timer+channel 配置 + set_duty/update_duty 均 ESP_OK" 证明 MOS 确实把参数写进了 LEDC 寄存器。

14.1 驱动接口

14.2 真机证据(COM3 UART 抓证)

=== PWM(LEDC) driver self-check (GPIO18) ===
[PASS] LEDC timer+channel configured, 50% duty set

14.3 诚实边界

15 · MOS 驱动框架扩展(2026-09-23):SPI 主机驱动真实总线

MOS 驱动框架接入第五个真机可验证驱动——SPI 主机。自检初始化 SPI2 总线+设备并发一帧哑数据; 以"bus init + device add + transmit 均 ESP_OK"证明 MOS 真的把数据送进了 SPI 状态机。

15.1 驱动接口

15.2 真机证据(COM3 UART 抓证)

=== SPI master driver self-check (GPIO15/16/17) ===
[PASS] SPI bus+device init OK, dummy transmit ESP_OK

15.3 诚实边界