diff --git a/.workbuddy/memory/2026-04-26.md b/.workbuddy/memory/2026-04-26.md deleted file mode 100644 index f1ebe88..0000000 --- a/.workbuddy/memory/2026-04-26.md +++ /dev/null @@ -1,18 +0,0 @@ -# 2026-04-26 工作日志 - -## 固件工程架构梳理 -- 工程路径:`code/firmware-2.7.15.567b8ea` -- 版本:Meshtastic v2.7.15,基于 PlatformIO 构建 -- 支持平台:ESP32 / nRF52 / RP2040 / RP2350 / Linux (Portduino) -- 路由层级:ReliableRouter → NextHopRouter → FloodingRouter → Router -- 定制点:TCA9535 I²C IO 扩展器用于代理 LoRa RST(P1.4)、GPS EN(P1.7)和 POWER_EN 引脚控制 -- 自定义文件:`src/input/TCA9535ButtonThread.cpp/.h`(I²C 按键驱动,约16KB+12KB) -- main.cpp 中有 TCA9535GpioHal 自定义 HAL 类,拦截虚拟引脚转发到 I²C - -## P1.6 振子震动功能实现 -- 变体:`variants/esp32c3/diy/esp32c3_moonshine_travelers_3` -- P1.6 原为 GPS RST,改为振子(VIBRATOR),高电平震动,低电平停止 -- 改动文件: - - `src/input/TCA9535ButtonThread.h`:`tca9535GpsReset()` → `tca9535Vibrate()`,添加 `_vibrateOn`/`_vibrateStartMs` 成员 - - `src/input/TCA9535ButtonThread.cpp`:init() 开机后触发 300ms 震动;runOnce() 关机时触发 300ms 震动;振子超时自动停止逻辑在 runOnce() 开头 - - `variants/.../variant.h`:注释更新 diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 15a4abb..e69de29 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -1,29 +0,0 @@ -# MEMORY.md - 项目长期记忆 - -## 当前固件工程 - -- **路径**:`code/firmware-2.7.15.567b8ea` -- **版本**:Meshtastic v2.7.15(PlatformIO 工程) -- **支持平台**:ESP32 / nRF52 / RP2040 / RP2350 / Linux(Portduino) -- **构建系统**:PlatformIO,默认目标 `tbeam`,variants/ 目录存放各板型配置 - -## 自定义硬件扩展(重要!) - -### TCA9535 I²C IO 扩展器 -用于代理几个不能直连 MCU GPIO 的关键引脚: -- **P1.4** → LoRa 模块 RST(通过 `TCA9535GpioHal` 自定义 HAL 拦截 RadioLib 调用) -- **P1.6** → 振子 VIBRATOR(高电平震动):开机/关机各震动 300ms(变体 esp32c3_moonshine_travelers_3) -- **P1.7** → GPS 使能引脚(`GpioTca9535GpsEnPin` 包装) -- **POWER_EN** → 上电保持(早期 boot 时即拉高,防止松开按键后 MOS 断电) - -### 自定义文件 -- `src/input/TCA9535ButtonThread.cpp` / `.h`:I²C 按键驱动(~16KB + ~12KB) -- `src/main.cpp`:含 TCA9535GpioHal 类定义、GPS EN 替换逻辑、TCA9535 按键线程初始化 - -## 工程架构要点 - -- **路由层级**:ReliableRouter → NextHopRouter → FloodingRouter → Router -- **Mesh 服务**:MeshService 连接射频收发、手机 API、GPS、模块系统 -- **模块系统**:setupModules() 统一初始化,各模块通过 MeshModule 订阅数据包端口 -- **线程调度**:基于 concurrency::OSThread + Periodic,loop() 中 mainController.runOrDelay() -- **消息协议**:Protobuf(protobufs/ 目录),生成代码在 src/mesh/generated/ diff --git a/PCB/Moonshine_travelers.eprj2 b/PCB/Moonshine_travelers.eprj2 index 079ac81..c007c0d 100644 Binary files a/PCB/Moonshine_travelers.eprj2 and b/PCB/Moonshine_travelers.eprj2 differ diff --git a/code/firmware-2.7.15.567b8ea/.workbuddy/memory/2026-05-02.md b/code/firmware-2.7.15.567b8ea/.workbuddy/memory/2026-05-02.md new file mode 100644 index 0000000..f38e922 --- /dev/null +++ b/code/firmware-2.7.15.567b8ea/.workbuddy/memory/2026-05-02.md @@ -0,0 +1,10 @@ +# 2026-05-02 工作日志 + +## 分析 esp32c3_moonshine_travelers_3 变体工作逻辑 + +- 对 Meshtastic 固件项目中的 esp32c3_moonshine_travelers_3 变体进行了完整的工作逻辑分析。 +- 该变体基于 ESP32-C3,使用 TCA9535PWR I²C IO 扩展器进行硬件扩展(矩阵键盘、电源管理、LoRa RST、振子、背光等)。 +- 与 travelers(上一版本)的主要差异:新增 PIN_BUZZER (GPIO12),LoRa 功率从 RA-01SC-P(最大3dBm)改为 E22_400M33S(最大22dBm),新增 SX126X_RXEN (GPIO13)。 +- 与 moonshine(基础版)差异:travelers_3 有 TCA9535 全套功能,moonshine 基础版无屏、无I2C、无GPS。 +- 关键源码:src/input/TCA9535ButtonThread.cpp/.h 实现 I²C IO 扩展器的全部驱动逻辑。 +- main.cpp 中早期 Boot 即拉高 POWER_EN(Wire.begin 后立即调用 tca9535PowerEn(true)),随后 setupModules 后才创建 TCA9535ButtonThread。 diff --git a/code/firmware-2.7.15.567b8ea/.workbuddy/memory/MEMORY.md b/code/firmware-2.7.15.567b8ea/.workbuddy/memory/MEMORY.md new file mode 100644 index 0000000..11dcf8a --- /dev/null +++ b/code/firmware-2.7.15.567b8ea/.workbuddy/memory/MEMORY.md @@ -0,0 +1,78 @@ +# 长期记忆 — Meshtastic 固件项目 + +## 项目基本信息 +- 项目路径:`C:\Users\wuwen\Documents\prj\lora_meshtastic_project\code\firmware-2.7.15.567b8ea` +- 固件版本:2.7.15.567b8ea +- 当前开发变体:`esp32c3_moonshine_travelers_3`(位于 `variants/esp32c3/diy/esp32c3_moonshine_travelers_3/`) + +## esp32c3_moonshine 系列变体谱系 + +| 变体 | 屏幕 | GPS | TCA9535 | LoRa模组 | 蜂鸣器 | 振子 | +|------|------|-----|---------|----------|--------|------| +| esp32c3_moonshine | 无 | 无 | 无 | E22_400M33S | 无 | 无 | +| esp32c3_moonshine_mv | 有(SH1106) | 有 | 有 | RA-01SC-P(3dBm) | 无 | 有 | +| esp32c3_moonshine_travelers | 有(SH1106) | 有 | 有 | RA-01SC-P(3dBm) | 无 | 有 | +| esp32c3_moonshine_travelers_3 | 有(SH1106) | 有 | 有 | E22_400M33S(22dBm) | GPIO12 | 有 | + +## esp32c3_moonshine_travelers_3 硬件引脚汇总 + +### ESP32-C3 直连引脚 +- GPIO0: I2C_SCL(屏幕+TCA9535共用) +- GPIO1: I2C_SDA +- GPIO2: BATTERY_PIN(ADC,分压比2.0x) +- GPIO3: LORA_DIO1 +- GPIO4: LORA_BUSY +- GPIO5: TCA9535_INT(下降沿,低有效) +- GPIO6: LORA_MISO +- GPIO7: LORA_MOSI +- GPIO8: LORA_CS +- GPIO9: BUTTON_PIN(短按=SELECT,长按功能禁用) +- GPIO10: LORA_SCK +- GPIO12: PIN_BUZZER / BUZZER_PIN +- GPIO13: SX126X_RXEN +- GPIO20: GPS_TX(GPS RX端) +- GPIO21: GPS_RX(GPS TX端) + +### TCA9535 I²C IO扩展(地址0x20) +- P0.0~P0.3: 矩阵键盘行输出(ROW0~ROW3) +- P0.4~P0.7: 矩阵键盘列输入(COL0~COL3) +- P1.0: 键盘背光(高电平点亮,5秒无操作自动熄灭) +- P1.1: CHARGE_DET输入(高电平=充电中) +- P1.2: POWER_EN输出(高电平=MOS维持供电) +- P1.3: POWER_BOOT输入(低电平有效,长按2秒关机) +- P1.4: LoRa RST输出(虚拟引脚号200) +- P1.5: 状态指示灯(低电平点亮,500ms周期闪烁) +- P1.6: 振子VIBRATOR(高电平震动) +- P1.7: GPS_EN输出(高电平=GPS上电) + +### LoRa模组(E22_400M33S / LLCC68 / SX126X兼容) +- LORA_RESET: 虚拟引脚200(通过TCA9535 P1.4控制) +- SX126X_DIO2_AS_RF_SWITCH: 启用 +- SX126X_DIO3_TCXO_VOLTAGE: 1.8V(TCXO可选) + +## 关键代码逻辑 + +### 开机流程 +1. 用户按住电源键 → MOS导通 → ESP32+TCA9535上电 +2. main.cpp Wire.begin() 后立即调用 `tca9535PowerEn(true)` 锁住供电 +3. setupModules() 后创建 TCA9535ButtonThread,调用 init() +4. init() 检查开机已确认,状态机进入RUNNING,触发300ms开机震动 + +### 关机流程 +- runOnce() 检测 P1.3(POWER_BOOT),持续按住2秒 → 触发关机震动300ms → 发送SHUTDOWN事件 + +### 矩阵键盘扫描 +- 有TCA9535_INT_PIN时:中断驱动(下降沿),20ms防抖 +- 无中断时:50ms轮询 +- 扫描:逐行拉低→读列状态→16位状态bitmap→边沿检测→派发InputEvent + +### 4×4键盘映射(九宫格布局) +- key0~2=1~3,key4~6=4~6,key8~10=7~9 +- key12=*,key13=0,key14=# +- key3=UP,key7=DOWN,key11=LEFT,key15=RIGHT +- GPIO9短按=SELECT,P1.3短按=CANCEL + +## 项目约定 +- 变体文件路径:variants/esp32c3/diy/{变体名}/variant.h +- 自定义驱动放在 src/input/ 目录下 +- 虚拟引脚号200用于TCA9535控制的LoRa RST diff --git a/code/firmware-2.7.15.567b8ea/docs/lora_rx_forward_analysis.md b/code/firmware-2.7.15.567b8ea/docs/lora_rx_forward_analysis.md new file mode 100644 index 0000000..370a0cc --- /dev/null +++ b/code/firmware-2.7.15.567b8ea/docs/lora_rx_forward_analysis.md @@ -0,0 +1,192 @@ +/** + * Meshtastic 固件 - LoRa 消息接收、转发与处理链路分析 + * + * 本文档追踪从 LoRa 硬件中断到应用层消息处理的完整代码链路 + * 适用版本:firmware-2.7.15.567b8ea + * 目标变体:esp32c3_moonshine_travelers_3(E22_400M33S / LLCC68) + */ + +// ============================================================================ +// 一、LoRa 消息接收(中断驱动) +// ============================================================================ + +/** + * 接收方式:DIO1 硬件中断(非轮询) + * + * 完整中断链路: + * + * LoRa 芯片(SX1262/LLCC68)收到数据包 + * → DIO1 引脚拉高(硬件中断信号) + * → ESP32-C3 GPIO3 (LORA_DIO1) 收到中断 + * → [RadioLib 库中断分发] + * → SX126xInterface::isrRxLevel0() [RadioLibInterface.cpp:69] + * → isrLevel0Common(ISR_RX) [RadioLibInterface.cpp:56-67] + * → instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, true) + * (通知 RadioLibInterface 工作线程) + * → RadioLibInterface::onNotify(ISR_RX) [RadioLibInterface.cpp:264-267] + * → handleReceiveInterrupt() [RadioLibInterface.cpp:425-512] + * - iface->readData() 读取接收到的数据 + * - 构建 meshtastic_MeshPacket 结构体 + * - 调用 addReceiveMetadata() 添加 SNR/RSSI 信息 + * - 调用 deliverToReceiver(mp) + */ + +// 关键文件: +// src/mesh/SX126xInterface.cpp 第 283-303 行:startReceive() 注册中断 +// src/mesh/RadioLibInterface.h 第 50 行:lora.setDio1Action(isrRxLevel0) +// src/mesh/RadioLibInterface.cpp 第 69-77 行:isrRxLevel0() 中断入口 +// src/mesh/RadioLibInterface.cpp 第 425-512 行:handleReceiveInterrupt() 读取数据 + +// ============================================================================ +// 二、消息转发判断(Router 层) +// ============================================================================ + +/** + * 消息从 RadioInterface 交付到 Router 队列: + * + * RadioInterface::deliverToReceiver(mp) [RadioInterface.cpp:675-681] + * → mp->transport_mechanism = TRANSPORT_LORA + * → router->enqueueReceivedMessage(p) 将消息入队到 fromRadioQueue + * + * Router 工作线程处理(独立 OSThread): + * + * Router::runOnce() [Router.cpp:137-147] + * → 从 fromRadioQueue 取出消息 + * → perhapsHandleReceived(mp) [Router.cpp:759-808] + * - 检查 ignore_incoming 列表(被屏蔽的节点) + * - 检查节点是否被忽略(node->is_ignored) + * - 检查是否来自 MQTT 需要特殊处理 + * → shouldFilterReceived(mp) 【转发判断入口】 + */ + +/** + * 转发判断流程(三层路由类继承): + * + * Router(基类) + * ↓ + * FloodingRouter(泛洪路由) + * ↓ + * NextHopRouter(下一跳路由) + * ↓ + * ReliableRouter(可靠路由,实际使用的类) + * + * 判断顺序: + * + * 1. ReliableRouter::shouldFilterReceived() [ReliableRouter.cpp:44] + * → 检查是否有人转发过我们的包(隐式 ACK) + * + * 2. FloodingRouter::shouldFilterReceived() [FloodingRouter.cpp:27-60] + * → wasSeenRecently(p) 去重检测 + * → PacketHistory::find(sender, packet_id) [PacketHistory.cpp:199-225] + * → 通过 (发送者节点号, 数据包ID) 组合判断是否重复 + * → 如果是重复包:rxDupe++,调用 perhapsRebroadcast() 重新广播 + * → 如果是新包:继续向下处理 + * + * 3. NextHopRouter::shouldFilterReceived() [NextHopRouter.cpp:37] + * → 处理 fallback 到泛洪模式 + * → sniffReceived() 更新下一跳信息 [NextHopRouter.cpp:86] + * + * 4. 转发决策:NextHopRouter::perhapsRebroadcast() [NextHopRouter.cpp:128-168] + * 条件检查: + * a. !isToUs(p) && !isFromUs(p) 不是发给我们,也不是我们发的 + * b. p->hop_limit > 0 剩余跳数 > 0 + * c. isRebroadcaster() 设备角色允许转发 + * → 角色不是 CLIENT_MUTE 且 rebroadcast_mode != NONE + * d. p->next_hop == NO_NEXT_HOP_PREFERENCE(泛洪) + * 或 p->next_hop == 本节点号(指定下一跳) + * 跳数处理: + * → shouldDecrementHopLimit(p) 判断是否需要递减跳数 + * → toSend->hop_limit-- (跳数递减) + * 发送: + * → FloodingRouter::send(toSend) 泛洪发送 + * → 或 NextHopRouter::send(toSend) 下一跳发送 + */ + +// 去重缓存结构: +// struct PacketRecord { +// NodeNum sender; // 发送者节点号 +// PacketId id; // 数据包 ID +// uint32_t rxTimeMsec; // 接收时间戳 +// uint8_t next_hop; // 下一跳偏好 +// uint8_t hop_limit; // 跳数限制 +// uint8_t relayed_by[6];// 中继节点列表 +// }; + +// ============================================================================ +// 三、消息处理(应用层) +// ============================================================================ + +/** + * 通过转发判断后,进入消息处理: + * + * Router::handleReceived(mp, src) [Router.cpp:683-757] + * → p->rx_time = getValidTime(RTCQualityFromNet) 记录接收时间 + * → perhapsDecode(p) [Router.cpp:408-536] + * - 尝试 PKI 解密(端到端加密) + * - 尝试 PSK 频道解密(频道共享密钥) + * - 返回 DecodeState::DECODE_SUCCESS / DECODE_FAIL / DECODE_NOT_NEEDED + * → 如果解密成功: + * → MeshModule::callModules(*p, src) [MeshModule.cpp:88-205] + * - 遍历所有已注册的模块(位置模块、文本模块、遥测模块等) + * - 对每个模块调用 wantPacket(&mp) 判断是否关心此包 + * - 如果关心,调用 module.handleReceived(mp) + * - 如果 handled == STOP,停止分发给后续模块 + * → 如果包是发给我们的(toUs): + * → 触发模块 sendResponse() 发送回复 + * → 如果启用了 MQTT: + * → mqtt->onSend() 转发到 MQTT 服务器 + */ + +// ============================================================================ +// 四、完整调用链总结 +// ============================================================================ + +/** + * 接收链路: + * LoRa 硬件 DIO1 中断 + * → RadioLibInterface::isrRxLevel0() + * → handleReceiveInterrupt() + * → deliverToReceiver(mp) + * → router->enqueueReceivedMessage(p) + * + * 处理链路: + * Router::runOnce() 从队列取消息 + * → perhapsHandleReceived(mp) + * → shouldFilterReceived() [去重 + 转发判断] + * → handleReceived(mp) [解密] + * → MeshModule::callModules() [模块分发] + * → module.wantPacket() + module.handleReceived() + * + * 转发链路: + * perhapsRebroadcast(mp) [跳数>0 且角色允许] + * → hop_limit-- + * → FloodingRouter::send() 或 NextHopRouter::send() + * → RadioLibInterface::send() + * → txQueue 入队 + * → startSend() → iface->startTransmit() + * → DIO3/TXEN 中断 → completeSending() + */ + +// ============================================================================ +// 五、关键文件索引 +// ============================================================================ + +// 接收层: +// src/mesh/SX126xInterface.cpp 中断注册、startReceive() +// src/mesh/RadioLibInterface.cpp 中断处理、数据读取、消息交付 + +// 路由层: +// src/mesh/Router.cpp 基类路由、消息处理入口、解密 +// src/mesh/FloodingRouter.cpp 泛洪路由、去重、转发判断 +// src/mesh/NextHopRouter.cpp 下一跳路由、跳数管理 +// src/mesh/ReliableRouter.cpp 可靠传输(ACK 机制) +// src/mesh/PacketHistory.cpp 去重缓存管理 + +// 应用层: +// src/mesh/MeshModule.cpp 模块注册与消息分发 +// src/mesh/MeshService.cpp 消息服务(发包、收包高层逻辑) +// src/modules/ 各功能模块实现(位置、文本、遥测等) + +// 配置相关: +// src/mesh/generated/meshtastic/config.pb.h Protobuf 定义(MeshPacket 结构体) +// variant.h 变体引脚定义(LORA_DIO1 等) diff --git a/code/firmware-2.7.15.567b8ea/src/main.cpp b/code/firmware-2.7.15.567b8ea/src/main.cpp index 1defe4a..186f7da 100644 --- a/code/firmware-2.7.15.567b8ea/src/main.cpp +++ b/code/firmware-2.7.15.567b8ea/src/main.cpp @@ -335,69 +335,106 @@ void printInfo() #ifndef PIO_UNIT_TESTING void setup() { +// 适用于 R1_NEO 硬件:初始化 DCDC 和 NRF 模块电源使能引脚 #if defined(R1_NEO) + // 将 DCDC 电源保持引脚设置为输出模式,用于后续控制 DCDC 电源使能 pinMode(DCDC_EN_HOLD, OUTPUT); + // 拉高 DCDC 电源保持引脚,使能 DCDC 电源,确保核心电路供电正常 digitalWrite(DCDC_EN_HOLD, HIGH); + // 将 NRF 模块电源使能引脚设置为输出模式 pinMode(NRF_ON, OUTPUT); + // 拉高 NRF 电源使能引脚,开启 NRF 模块电源 digitalWrite(NRF_ON, HIGH); #endif +// 适用于带 PIN_POWER_EN 引脚的硬件:初始化通用电源使能引脚 #if defined(PIN_POWER_EN) + // 将通用电源使能引脚设置为输出模式 pinMode(PIN_POWER_EN, OUTPUT); + // 拉高电源使能引脚,开启外设电源 digitalWrite(PIN_POWER_EN, HIGH); #endif +// 适用于 ELECROW ThinkNode M5 硬件:初始化 I2C 总线及 PCA9557 IO 扩展器电源引脚 #if defined(ELECROW_ThinkNode_M5) + // 初始化 I2C 总线,SDA=48,SCL=47 Wire.begin(48, 47); + // 设置 PCA9557 的电子墨水屏使能引脚为输出模式 io.pinMode(PCA_PIN_EINK_EN, OUTPUT); + // 设置 PCA9557 的电源使能引脚为输出模式 io.pinMode(PCA_PIN_POWER_EN, OUTPUT); + // 拉高 PCA9557 电源使能引脚,开启外设电源 io.digitalWrite(PCA_PIN_POWER_EN, HIGH); // io.pinMode(C2_PIN, OUTPUT); #endif +// 适用于带 LED_POWER 引脚的硬件:初始化电源指示灯 #ifdef LED_POWER + // 将电源指示灯引脚设置为输出模式 pinMode(LED_POWER, OUTPUT); + // 设置电源指示灯为点亮状态(LED_STATE_ON 为点亮电平) digitalWrite(LED_POWER, LED_STATE_ON); #endif +// 适用于带 USER_LED 引脚的硬件:初始化用户指示灯 #ifdef USER_LED + // 将用户 LED 引脚设置为输出模式 pinMode(USER_LED, OUTPUT); + // 设置用户 LED 状态(HIGH 与 LED_STATE_ON 异或,适配 LED 电平逻辑) digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); #endif +// 适用于带 WIFI_LED 引脚的硬件:初始化 WiFi 指示灯 #ifdef WIFI_LED + // 将 WiFi 指示灯引脚设置为输出模式 pinMode(WIFI_LED, OUTPUT); + // 初始设置 WiFi 指示灯为熄灭状态 digitalWrite(WIFI_LED, LOW); #endif +// 适用于带 BLE_LED 引脚的硬件:初始化蓝牙指示灯 #ifdef BLE_LED + // 将蓝牙指示灯引脚设置为输出模式 pinMode(BLE_LED, OUTPUT); #ifdef BLE_LED_INVERTED + // 若蓝牙 LED 为低电平点亮(反逻辑),则拉高引脚熄灭 digitalWrite(BLE_LED, HIGH); #else + // 若蓝牙 LED 为高电平点亮,则拉低引脚熄灭 digitalWrite(BLE_LED, LOW); #endif #endif +// 适用于 T-Deck、T-Deck Pro、T-LoRa Pager 等硬件:初始化外设电源、SPI 片选引脚及 IO 扩展器 #if defined(T_DECK) + // GPIO10 控制所有外设电源,启动后立即开启避免低电压导致 ESP32 复位 // GPIO10 manages all peripheral power supplies // Turn on peripheral power immediately after MUC starts. // If some boards are turned on late, ESP32 will reset due to low voltage. + // 外设包括:ESP32-C3(键盘)、MAX98357A(音频功放)、TF卡、显示屏背光、轨迹球、解码器 // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) , // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder) + // 将键盘电源使能引脚设置为输出模式 pinMode(KB_POWERON, OUTPUT); + // 拉高键盘电源引脚,开启外设电源 digitalWrite(KB_POWERON, HIGH); + // T-Deck 的 TFT、SD、LoRa 共用同一 SPI 总线,需提前初始化所有片选引脚避免通信冲突 // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus // We need to initialize all CS pins in advance otherwise there will be SPI communication issues // e.g. when detecting the SD card + // 初始化 LoRa 片选引脚为输出并拉高(默认不选中) pinMode(LORA_CS, OUTPUT); digitalWrite(LORA_CS, HIGH); + // 初始化 SD 卡片选引脚为输出并拉高(默认不选中) pinMode(SDCARD_CS, OUTPUT); digitalWrite(SDCARD_CS, HIGH); + // 初始化 TFT 屏幕片选引脚为输出并拉高(默认不选中) pinMode(TFT_CS, OUTPUT); digitalWrite(TFT_CS, HIGH); + // 等待外设电源稳定 delay(100); #elif defined(T_DECK_PRO) + // T-Deck Pro 硬件:初始化 LoRa、SD 卡、电子墨水屏的片选引脚并拉高 pinMode(LORA_EN, OUTPUT); digitalWrite(LORA_EN, HIGH); pinMode(LORA_CS, OUTPUT); @@ -407,64 +444,94 @@ void setup() pinMode(PIN_EINK_CS, OUTPUT); digitalWrite(PIN_EINK_CS, HIGH); #elif defined(T_LORA_PAGER) + // T-LoRa Pager 硬件:初始化 SPI 片选引脚、键盘中断引脚及 XL9555 IO 扩展器 pinMode(LORA_CS, OUTPUT); digitalWrite(LORA_CS, HIGH); pinMode(SDCARD_CS, OUTPUT); digitalWrite(SDCARD_CS, HIGH); pinMode(TFT_CS, OUTPUT); digitalWrite(TFT_CS, HIGH); + // 设置键盘中断引脚为上拉输入模式 pinMode(KB_INT, INPUT_PULLUP); + // 初始化 XL9555 IO 扩展器,使用默认 Wire 总线,地址为 XL9555_SLAVE_ADDRESS0 // io expander io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); + // 设置驱动使能引脚为输出并拉高,开启驱动 io.pinMode(EXPANDS_DRV_EN, OUTPUT); io.digitalWrite(EXPANDS_DRV_EN, HIGH); + // 设置音频功放使能引脚为输出并拉低,关闭功放(默认不开启) io.pinMode(EXPANDS_AMP_EN, OUTPUT); io.digitalWrite(EXPANDS_AMP_EN, LOW); + // 设置 LoRa 使能引脚为输出并拉高,开启 LoRa 模块 io.pinMode(EXPANDS_LORA_EN, OUTPUT); io.digitalWrite(EXPANDS_LORA_EN, HIGH); + // 设置 GPS 使能引脚为输出并拉高,开启 GPS 模块 io.pinMode(EXPANDS_GPS_EN, OUTPUT); io.digitalWrite(EXPANDS_GPS_EN, HIGH); + // 设置键盘使能引脚为输出并拉高,开启键盘 io.pinMode(EXPANDS_KB_EN, OUTPUT); io.digitalWrite(EXPANDS_KB_EN, HIGH); + // 设置 SD 卡使能引脚为输出并拉高,开启 SD 卡 io.pinMode(EXPANDS_SD_EN, OUTPUT); io.digitalWrite(EXPANDS_SD_EN, HIGH); + // 设置 GPIO 扩展使能引脚为输出并拉高,开启扩展 GPIO io.pinMode(EXPANDS_GPIO_EN, OUTPUT); io.digitalWrite(EXPANDS_GPIO_EN, HIGH); + // 设置 SD 卡上拉使能引脚为输入模式 io.pinMode(EXPANDS_SD_PULLEN, INPUT); #endif + // 标记并发框架已完成 setup 初始化 concurrency::hasBeenSetup = true; +// 根据平台配置 SPI 通信参数:时钟速度、位顺序(MSB 优先)、SPI 模式 #if ARCH_PORTDUINO + // Portduino 平台:使用用户配置的 SPI 速度 SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); #else + // 其他平台:默认 SPI 时钟 4MHz,MSB 优先,模式0 SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); #endif + // 设置屏幕默认型号为 OLED_AUTO(自动检测),默认分辨率为 128x64 meshtastic_Config_DisplayConfig_OledType screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64; +// 适用于启用 SEGGER RTT 调试输出的硬件:配置 RTT 上行缓冲区 #ifdef USE_SEGGER + // 设置 RTT 模式:非阻塞裁剪模式(FIFO 满时丢弃数据) auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM; #ifdef NRF52840_XXAA + // NRF52840 内存较大,设置 RTT 缓冲区大小为 4096 字节 auto buflen = 4096; // this board has a fair amount of ram #else + // 其他板卡内存较小,设置 RTT 缓冲区大小为 256 字节 auto buflen = 256; // this board has a fair amount of ram #endif + // 配置 SEGGER RTT 上行缓冲区(用于调试输出) SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, buflen, mode); #endif +// 适用于启用调试串口(DEBUG_PORT)的硬件:初始化串口控制台 #ifdef DEBUG_PORT + // 初始化调试串口(设置波特率等)并启动 mesh 控制台 consoleInit(); // Set serial baud rate and init our mesh console #endif +// 适用于 UNPHONE 硬件:打印存储信息 #ifdef UNPHONE + // 打印 UNPHONE 的存储状态信息 unphone.printStore(); #endif +// 适用于 Portduino 平台:初始化系统时间并设置 RTC #if ARCH_PORTDUINO + // 定义时间结构体 struct timeval tv; + // 获取当前系统时间(秒级)并赋值 tv.tv_sec = time(NULL); + // 微秒级时间设为 0 tv.tv_usec = 0; + // 尝试设置 RTC 时间,质量标记为设备级 perhapsSetRTC(RTCQualityDevice, &tv); #endif @@ -554,43 +621,58 @@ void setup() #endif #endif + // 初始化 SPI 总线(配置 SPI 参数并启动) initSPI(); + // 初始化 OSThread 并发框架(设置线程调度基础) OSThread::setup(); +// 适用于 ELECROW ThinkNode M1/M2 硬件:使用自定义 LED 闪烁逻辑(当前注释未启用) #if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) // The ThinkNodes have their own blink logic // ledPeriodic = new Periodic("Blink", elecrowLedBlinker); #else + // 其他硬件:创建 LED 周期闪烁线程(默认 1Hz 心跳效果) ledPeriodic = new Periodic("Blink", ledBlinker); #endif + // 初始化文件系统(挂载 SPI Flash/SD 卡等存储设备) fsInit(); +// 阶段3:I2C 总线初始化与设备扫描(排除 I2C 功能时跳过) #if !MESHTASTIC_EXCLUDE_I2C +// 初始化第二 I2C 总线(Wire1,适用于有 2 个 I2C 接口的平台) #if defined(I2C_SDA1) && defined(ARCH_RP2040) + // RP2040 平台:设置 Wire1 的 SDA/SCL 引脚并启动总线 Wire1.setSDA(I2C_SDA1); Wire1.setSCL(I2C_SCL1); Wire1.begin(); #elif defined(I2C_SDA1) && !defined(ARCH_RP2040) + // 非 RP2040 平台:启动 Wire1 总线并指定 SDA/SCL 引脚 Wire1.begin(I2C_SDA1, I2C_SCL1); #elif WIRE_INTERFACES_COUNT == 2 + // 平台有 2 个 I2C 接口:启动默认 Wire1 总线 Wire1.begin(); #endif +// 初始化主 I2C 总线(Wire) #if defined(I2C_SDA) && defined(ARCH_RP2040) + // RP2040 平台:设置主 Wire 的 SDA/SCL 引脚并启动总线 Wire.setSDA(I2C_SDA); Wire.setSCL(I2C_SCL); Wire.begin(); #elif defined(I2C_SDA) && !defined(ARCH_RP2040) + // 非 RP2040 平台:启动主 Wire 总线并指定 SDA/SCL 引脚 Wire.begin(I2C_SDA, I2C_SCL); #ifdef HAS_TCA9535_BUTTON + // TCA9535 电源使能必须在 I2C 初始化后立即拉高,防止用户松开按键后系统掉电 // TCA9535 POWER_EN 必须在 I²C 初始化完成后立即拉高,否则用户松开按键后 // MOS 断电,系统在 setup() 中途就会掉电。 tca9535PowerEn(true); LOG_INFO("TCA9535: POWER_EN latched HIGH (early boot)"); #endif #elif defined(ARCH_PORTDUINO) + // Portduino 平台:根据配置启动主 I2C 总线 if (portduino_config.i2cdev != "") { LOG_INFO("Use %s as I2C device", portduino_config.i2cdev.c_str()); Wire.begin(portduino_config.i2cdev.c_str()); @@ -598,36 +680,59 @@ void setup() LOG_INFO("No I2C device configured, Skip"); } #elif HAS_WIRE + // 其他有 Wire 总线的平台:启动默认主 I2C 总线 Wire.begin(); #endif #endif +// 适用于 M5STACK_UNITC6L 硬件:初始化 LoRa 片选引脚并启动 C6L 模块 #if defined(M5STACK_UNITC6L) + // 设置 LoRa 片选引脚为输出模式 pinMode(LORA_CS, OUTPUT); + // 拉高片选引脚,默认不选中 LoRa digitalWrite(LORA_CS, 1); + // 初始化 M5STACK_UNITC6L 模块 c6l_init(); #endif +// 适用于带 LCD 复位引脚的硬件:执行 LCD 复位时序 #ifdef PIN_LCD_RESET + // FIXME - 后续将该复位逻辑移至更合适的位置,LCD I2C 地址为 0x3F // FIXME - move this someplace better, LCD is at address 0x3F + // 设置 LCD 复位引脚为输出模式 pinMode(PIN_LCD_RESET, OUTPUT); + // 拉低复位引脚,触发 LCD 复位 digitalWrite(PIN_LCD_RESET, 0); + // 延时 1ms 确保复位生效 delay(1); + // 拉高复位引脚,结束复位状态 digitalWrite(PIN_LCD_RESET, 1); + // 延时 1ms 等待 LCD 启动 delay(1); #endif +// 适用于带空气质量传感器(AQ)设置引脚的硬件:初始化 AQ 传感器电源 #ifdef AQ_SET_PIN + // RAK-12039 空气质量传感器设置引脚,上电后约 3 秒可在 I2C 检测到,需后续重新扫描 // RAK-12039 set pin for Air quality sensor. Detectable on I2C after ~3 seconds, so we need to rescan later + // 设置 AQ 传感器电源引脚为输出模式 pinMode(AQ_SET_PIN, OUTPUT); + // 拉高引脚,开启 AQ 传感器电源 digitalWrite(AQ_SET_PIN, HIGH); #endif + // 初始化电源管理单元(PMU,目前仅 T-Beam 等硬件有) + // 当前仅 T-Beam 等硬件有 PMU // Currently only the tbeam has a PMU + // PMU 初始化必须放在 I2C 扫描之前,因为需要 PMU 为外设供电 // PMU initialization needs to be placed before i2c scanning + // 创建 Power 实例(电源管理核心对象) power = new Power(); + // 设置电源状态处理器为 powerStatus,用于接收电源状态更新 power->setStatusHandler(powerStatus); + // 让 powerStatus 观察 power 的新状态事件 powerStatus->observe(&power->newStatus); + // 执行 PMU 初始化(必须在状态处理器安装后,确保接收初始配置通知) power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration #if !MESHTASTIC_EXCLUDE_I2C @@ -671,68 +776,91 @@ void setup() } #endif + // 检测 I2C 扫描到的第一个屏幕设备,更新屏幕型号 auto screenInfo = i2cScanner->firstScreen(); + // 记录屏幕设备地址(未找到则为 ADDRESS_NONE) screen_found = screenInfo.type != ScanI2C::DeviceType::NONE ? screenInfo.address : ScanI2C::ADDRESS_NONE; + // 若找到屏幕设备,根据型号更新屏幕型号配置 if (screen_found.port != ScanI2C::I2CPort::NO_I2C) { switch (screenInfo.type) { case ScanI2C::DeviceType::SCREEN_SH1106: + // SH1106 屏幕型号(128x64 OLED) screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_SH1106; break; case ScanI2C::DeviceType::SCREEN_SSD1306: + // SSD1306 屏幕型号(128x64 OLED) screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_SSD1306; break; case ScanI2C::DeviceType::SCREEN_ST7567: case ScanI2C::DeviceType::SCREEN_UNKNOWN: default: + // 未知或 ST7567 屏幕,保持自动检测模式 screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; } } +// 宏定义:从扫描结果更新传感器映射(当前未使用,预留扩展) #define UPDATE_FROM_SCANNER(FIND_FN) +// 若定义了虚拟键盘,直接标记键盘已找到 #if defined(USE_VIRTUAL_KEYBOARD) kb_found = true; #endif + // 获取第一个 RTC 设备信息,更新 RTC 地址 auto rtc_info = i2cScanner->firstRTC(); rtc_found = rtc_info.type != ScanI2C::DeviceType::NONE ? rtc_info.address : rtc_found; + // 获取第一个键盘设备信息 auto kb_info = i2cScanner->firstKeyboard(); + // 若找到键盘设备,标记并配置键盘型号 if (kb_info.type != ScanI2C::DeviceType::NONE) { + // 标记已找到键盘 kb_found = true; + // 记录键盘设备地址 cardkb_found = kb_info.address; + // 根据键盘型号设置 kb_model 值(用于区分不同键盘型号) switch (kb_info.type) { case ScanI2C::DeviceType::RAK14004: + // RAK14004 键盘型号标识(0x02) kb_model = 0x02; break; case ScanI2C::DeviceType::CARDKB: + // CARDKB 键盘型号标识(0x00) kb_model = 0x00; break; case ScanI2C::DeviceType::TDECKKB: + // T-Deck 键盘型号标识(0x10) // assign an arbitrary value to distinguish from other models kb_model = 0x10; break; case ScanI2C::DeviceType::BBQ10KB: + // BBQ10 键盘型号标识(0x11) // assign an arbitrary value to distinguish from other models kb_model = 0x11; break; case ScanI2C::DeviceType::MPR121KB: + // MPR121 键盘型号标识(0x37) // assign an arbitrary value to distinguish from other models kb_model = 0x37; break; case ScanI2C::DeviceType::TCA8418KB: + // TCA8418 键盘型号标识(0x84) // assign an arbitrary value to distinguish from other models kb_model = 0x84; break; default: + // 未知键盘型号,默认使用 0x00 // use this as default since it's also just zero LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type); kb_model = 0x00; } } + // 检查 PMU(AXP192/AXP2101)是否存在,更新 pmu_found 标志 pmu_found = i2cScanner->exists(ScanI2C::DeviceType::PMU_AXP192_AXP2101); + // 获取第一个空气质量传感器(AQI)信息,更新 aqi_found 地址 auto aqiInfo = i2cScanner->firstAQI(); aqi_found = aqiInfo.type != ScanI2C::DeviceType::NONE ? aqiInfo.address : ScanI2C::ADDRESS_NONE; @@ -742,19 +870,24 @@ void setup() * "found". */ -// Two supported RGB LED currently +// 当前支持两种 RGB LED 检测 #ifdef HAS_RGB_LED + // 从扫描结果获取 RGB LED 设备信息 rgb_found = i2cScanner->firstRGBLED(); #endif #ifdef HAS_TPS65233 + // TPS65233 是卫星调制解调器的电源管理 IC,用于 Dreamcatcher 硬件 // TPS65233 is a power management IC for satellite modems, used in the Dreamcatcher + // 此处关闭 LNB 电源(我们使用卫星调制解调器时不使用 LNB) // We are switching it off here since we don't use an LNB. if (i2cScanner->exists(ScanI2C::DeviceType::TPS65233)) { + // 写入寄存器 0:关闭 LNB 电源,保持 I2C 控制使能 Wire.beginTransmission(TPS65233_ADDR); Wire.write(0); // Register 0 Wire.write(128); // Turn off the LNB power, keep I2C Control enabled Wire.endTransmission(); + // 写入寄存器 1:关闭 22kHz 音调发生器 Wire.beginTransmission(TPS65233_ADDR); Wire.write(1); // Register 1 Wire.write(0); // Turn off Tone Generator 22kHz @@ -762,19 +895,31 @@ void setup() } #endif +// 非 STM32WL 平台:检测加速度计并获取设备地址 #if !defined(ARCH_STM32WL) + // 获取第一个加速度计设备信息 auto acc_info = i2cScanner->firstAccelerometer(); + // 更新加速度计设备地址(未找到则保持原值) accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found; + // 输出加速度计设备类型调试信息 LOG_DEBUG("acc_info = %i", acc_info.type); #endif + // 将 INA260 传感器映射到遥测传感器类型 scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260); + // 将 INA226 传感器映射到遥测传感器类型 scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226); + // 将 INA219 传感器映射到遥测传感器类型 scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219); + // 将 INA3221 传感器映射到遥测传感器类型 scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221); + // 将 MAX17048 传感器映射到遥测传感器类型 scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048); + // 将 QMC6310 传感器映射到遥测传感器类型 scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310); + // 将 QMI8658 传感器映射到遥测传感器类型 scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658); + // 将 QMC5883L 传感器映射到遥测传感器类型 scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);