无线与 USB 拓展 / 无线实验发布 内容更新

两块 ESP32-C6 组一个 Thread 网络:用 CLI 核对连接和 sleepy 模式

保留同版ot_cli入口,分别构建FTD/MTD,以同一Dataset核对入网、IPv6消息、重启保存和RxOffWhenIdle;把普通节点、Border Router与CPU低功耗分开。

读完能做什么
保留同版ot_cli入口,分别构建FTD/MTD,以同一Dataset核对入网、IPv6消息、重启保存和RxOffWhenIdle;把普通节点、Border Router与CPU低功耗分开。
开始之前
先完成所列前置;能够在精确IDF环境构建、识别UART桥口、保存原始串口日志并断电追线。
所需器材
两块官方C6 DevKitC-1-N8/WROOM-1;指南v1.2/电路V1.4/PW≥2024-03-0595或0921、两条UART桥数据线、私有Dataset记录;无外接GPIO/模块/Border Router、ESP-IDF5.5.5与已锁定的OpenThread源码;电脑Python3.12仅用于HEX基础预检
版本组合
板卡:C6 DevKitC-1-N8;指南v1.2/电路V1.4/PW≥2024-03-0595或0921SDK:ESP-IDF5.5.5 / b774170ff46c393eeb5e495ea37936038d3f4f4fOpenThread gitlink:b678a4f63b6f9397d1a0fa8f31e5b8e0271a4d00构建环境版本字符串:源码archive无.git,PACKAGE_VERSION=-;准确版本另按gitlink/固件SHA锁模式:FTD / MTD;无BR、无network_auto_start;PM_ENABLE关闭
适用范围

仅C6-N8;同IDF5.5.5的ot_cli入口/配置,CC0最小UART控制台适配。FTD+MTD,PM关闭;协议RxOffWhenIdle、实物休眠、BR分别说明,实板pending。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/wireless-c5-c6-sources.json;固定gh原文/已安装字节核对及板卡/SHT PDF实际阅读验证日期:
构建 / 配置已通过
docs/esp32-evidence/wireless-c5-c6-builds.json;FTD与MTD两种真实Exit0;原样esp_ot_cli.c/h与固定SDK逐字相同对象:ESP32-C6版本:ESP-IDF5.5.5验证日期:
仿真已通过
docs/esp32-evidence/wireless-c5-c6-hosts.json;Dataset基础预检20组合成文本通过;没有运行本机Thread CLI/射频/NVS仿真;不是射频/芯片仿真验证日期:
本站实板待验证
无本站实板、入网、覆盖/时延/功耗或长期运行结果;观察CSV仅表头

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 无线实验

两块普通节点先互通,再谈跨网和Matter

Thread用802.15.4承载IPv6网络;FTD有成为router/leader的能力,MTD只能作为end-device。本任务用A的FTD组网、B的MTD加入,先通过真实child/parent关系和双向消息理解网络。A成为leader并不会自动成为Border Router。

两板都只接USB-UART给电脑看CLI,没有Wi-Fi回程、LAN路由或Matter控制器;电脑能看到串口,不表示电脑进入了Thread网络。本站只完成两个角色实际构建和文本工具检查,尚未形成物理Thread网络。

参考:[3] IDF5.5.5 C6 OpenThread:角色/BR支持边界

02 / 无线实验

同款C6两块,分别贴FTD和MTD标签

使用与W09相同的C6-DevKitC-1-N8/WROOM-1/8MB无PSRAM,但这次刷入Thread镜像,不能让其中一块仍跑Zigbee然后等待加入。固定指南v1.2/电路V1.4、PW-2024-03-0595或0921及以后;两个版本编号分别记。

每块只接自己的USB-UART桥数据线、保持J5,不接传感器和GPIO。C6桥UART16/17,原生USB12/13,BOOT9,RGB8/启动配置脚都不复用;USB供电类、5V排针类和3V3排针类互斥。本实验选USB。

做到这里,你应该看到
系统端口、板卡标签A/B、固件角色对应,退出一个monitor后再明确选另一个端口。

参考:[1] C6官方指南v1.2:N8、J1/J3、V1.4/PW修订和供电互斥[2] C6电路V1.4:p2 GPIO6/7/10、UART/USB、J5

03 / 无线实验

保留ot_cli入口,关掉自动建网和可选辅助依赖

工程保留IDF5.5.5固定提交的esp_ot_cli.c和esp_ot_config.h原样字节,OpenThread gitlink为b678a4f…;没有另装一套Thread SDK。UART辅助console为同版CC0示例的精简适配,保留256字符REPL/10条历史,去掉LED、iperf、共存、额外system命令;核心ot命令仍由esp_openthread_start注册。

本例关闭network_auto_start,不在没有Dataset时暗自形成另一个随机网络;首次由操作者在A明确init new,B复制。同一源码用FTD和MTD两个SDKCONFIG目录构建,不能拿同一残留config勾选项覆盖后忘记角色。

本站源码archive无.git元数据,构建PACKAGE_VERSION是“-”,不能只拿ot version字符串做准确版本锁;同时保存SDK/gitlink与你的实际镜像SHA。PM_ENABLE关闭,后面的sleepy是Thread接收策略,不声称启用CPU自动Light-sleep。

代码 / 配置
idf.py -B build-ftd -DSDKCONFIG="$PWD/build-ftd/sdkconfig" -DSDKCONFIG_DEFAULTS=sdkconfig.defaults build
idf.py -B build-mtd -DSDKCONFIG="$PWD/build-mtd/sdkconfig" '-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.mtd' build
# 分别加 -p BOARD_A_UART / BOARD_B_UART flash monitor;不要刷混。

参考:[4] 固定ot_cli原样入口与默认配置[5] 固定SDK初始化CLI与netif/start顺序

04 / 无线实验

完整原样入口与UART适配文件

下列四个文件与下载原文件一致;其余CMake、sdkconfig.defaults/mtd、分区、锁与许可在完整包里。默认native radio、HOST_CONNECTION_MODE_NONE,CLI注册在SDK任务;控制台本身不是第二套Thread实现。

自定义NVS/phy/factory分区沿同版ot_cli示例,存储操作仍由SDK执行。本轮没有在本机仿真NVS/CLI。遇到初始化错误先留原日志,不为了拿到一条Done就静默清除保存网络。

代码 / 配置
// ---- main/esp_ot_config.h ----
/*
 * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 *
 * OpenThread Command Line Example
 *
 * This example code is in the Public Domain (or CC0 licensed, at your option.)
 *
 * Unless required by applicable law or agreed to in writing, this
 * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.
 */

#pragma once

#include "esp_openthread_types.h"

#if SOC_IEEE802154_SUPPORTED
#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG()              \
    {                                                      \
        .radio_mode = RADIO_MODE_NATIVE,                   \
    }

#else
#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG()                 \
    {                                                         \
        .radio_mode = RADIO_MODE_UART_RCP,                    \
        .radio_uart_config = {                                \
            .port = 1,                                        \
            .uart_config =                                    \
                {                                             \
                    .baud_rate = 115200,                      \
                    .data_bits = UART_DATA_8_BITS,            \
                    .parity = UART_PARITY_DISABLE,            \
                    .stop_bits = UART_STOP_BITS_1,            \
                    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,    \
                    .rx_flow_ctrl_thresh = 0,                 \
                    .source_clk = UART_SCLK_DEFAULT,          \
                },                                            \
            .rx_pin = 4,                                      \
            .tx_pin = 5,                                      \
        },                                                    \
    }
#endif

#define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG()                        \
    {                                                               \
        .host_connection_mode = HOST_CONNECTION_MODE_NONE,          \
    }

#define ESP_OPENTHREAD_DEFAULT_PORT_CONFIG()    \
    {                                           \
        .storage_partition_name = "nvs",        \
        .netif_queue_size = 10,                 \
        .task_queue_size = 10,                  \
    }

// ---- main/ot_examples_common.h ----
// SPDX-License-Identifier: MIT
#pragma once
void ot_console_start(void);
void ot_register_external_commands(void);

// ---- main/ot_console.c ----
/* Derived from ESP-IDF v5.5.5 examples/openthread/ot_common_components/ot_examples_common/ot_console.c.
 * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
 * SPDX-License-Identifier: CC0-1.0
 * Minimal UART-only adapter: no LED, iperf, RF coexist commands or system command component.
 * Core OpenThread CLI is registered by esp_openthread_start(), as in the upstream example.
 */
#include "ot_examples_common.h"
#include "esp_console.h"
#include "esp_err.h"
#include "sdkconfig.h"
void ot_console_start(void) {
    esp_console_repl_t *repl=NULL;
    esp_console_repl_config_t cfg=ESP_CONSOLE_REPL_CONFIG_DEFAULT();
    cfg.prompt=CONFIG_IDF_TARGET ">";cfg.max_cmdline_length=256;cfg.max_history_len=10;
    esp_console_dev_uart_config_t hw=ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw,&cfg,&repl));
    ESP_ERROR_CHECK(esp_console_start_repl(repl));
}
void ot_register_external_commands(void) { /* No extra non-Thread commands. */ }

// ---- main/esp_ot_cli.c ----
/*
 * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 *
 * OpenThread Command Line Example
 *
 * This example code is in the Public Domain (or CC0 licensed, at your option.)
 *
 * Unless required by applicable law or agreed to in writing, this
 * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include <unistd.h>
#include <string.h>

#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
#include "esp_openthread.h"
#include "esp_openthread_lock.h"
#include "esp_openthread_netif_glue.h"
#include "esp_openthread_types.h"
#include "esp_ot_config.h"
#include "esp_vfs_eventfd.h"
#include "nvs_flash.h"
#include "ot_examples_common.h"

#if CONFIG_OPENTHREAD_STATE_INDICATOR_ENABLE
#include "ot_led_strip.h"
#endif

#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
#include "esp_ot_cli_extension.h"
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION

#if CONFIG_ESP_COEX_EXTERNAL_COEXIST_ENABLE
#include "ext_coex_cmd.h"
#endif

#define TAG "ot_esp_cli"

void app_main(void)
{
    // Used eventfds:
    // * netif
    // * ot task queue
    // * radio driver
    esp_vfs_eventfd_config_t eventfd_config = {
        .max_fds = 3,
    };

    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
#if CONFIG_OPENTHREAD_PLATFORM_NETIF
    ESP_ERROR_CHECK(esp_netif_init());
#endif
    ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));

#if CONFIG_OPENTHREAD_CLI
    ot_console_start();
    ot_register_external_commands();
#if CONFIG_ESP_COEX_EXTERNAL_COEXIST_ENABLE
    register_cmd_extcoex();
#endif
#endif

    static esp_openthread_config_t config = {
#if CONFIG_OPENTHREAD_PLATFORM_NETIF
        .netif_config = ESP_NETIF_DEFAULT_OPENTHREAD(),
#endif
        .platform_config = {
            .radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG(),
            .host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(),
            .port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG(),
        },
    };

    ESP_ERROR_CHECK(esp_openthread_start(&config));
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
    esp_cli_custom_command_init();
#endif
#if CONFIG_OPENTHREAD_STATE_INDICATOR_ENABLE
    ESP_ERROR_CHECK(esp_openthread_state_indicator_init(esp_openthread_get_instance()));
#endif
#if CONFIG_OPENTHREAD_NETWORK_AUTO_START
    ot_network_auto_start();
#endif
}

参考:[4] 固定ot_cli原样入口与默认配置[5] 固定SDK初始化CLI与netif/start顺序

05 / 无线实验

A只生成一次网络,把真正的Active Dataset交给B

两边先输入ot version、ot state、ot dataset active记录初始状态。已有旧网时先thread stop、ifconfig down;只有确实要删旧保存才factoryreset。A执行下面命令,每一条都等实际Done/错误后再下一条;dataset init new只在A做一次。

等A真实变leader/router,读channel、panid、extpanid、ipaddr。dataset active -x返回完整HEX,其中有network key/PSKc,必须私有保管,不上传公开记录表或截图。B不能另init new一个同名网络,networkname相同不等于网络相同。

代码 / 配置
ot dataset init new
ot dataset channel 15
ot dataset networkname Hixinwu-W10
ot dataset commit active
ot dataset active -x
ot mode rdn
ot ifconfig up
ot thread start
ot state
ot channel
ot panid
ot extpanid
ot ipaddr
做到这里,你应该看到
得到的是A实际Active HEX和真实角色,不是教程预造的成功输出。

参考:[6] 固定OpenThread Dataset CLI:active -x / commit / set

06 / 无线实验

B复制HEX后,核child与parent两端关系

先把A真实HEX交tools/dataset_check.py做基础预检,再替换下面占位,不能连尖括号复制。脚本拒绝奇数/非HEX、TLV截短或重复、缺必要字段和过长复制命令;本REPL最大255字符输入,超长Dataset要先查原因,不能让串口截断后再碰运气。预检不是SDK完整语义校验,更不执行射频。

B设mode -表示RxOffWhenIdle、minimal设备、stable network data,pollperiod1000表示轮询配置。它应成为child;A的child table里核其RLOC16/RxOnWhenIdle,B读parent/mode/pollperiod。没有这些实际对应记录时,不能仅凭几条Done填“已入网”。

代码 / 配置
ot dataset set active <A的完整HEX>
ot mode -
ot pollperiod 1000
ot ifconfig up
ot thread start
ot state
ot parent
ot mode
ot pollperiod
# A执行:
ot child table

参考:[6] 固定OpenThread Dataset CLI:active -x / commit / set[7] 固定OpenThread mode/pollperiod/地址命令

07 / 无线实验

用真实ML-EID做双向ping,再看UDP接收端

两边先运行ot ipaddr mleid直接取得mesh-local EID,再用ot ipaddr全表核对同一mesh-local前缀,避开fe80::链路本地和尾部含ff:fe00模式的RLOC。记录自己的真实地址;地址占位必须替换,不把图中示意值当目标。B执行ot ping <A的ML-EID> 16 3,A反向同样测3次,保留真实收到/丢失与RTT。

然后A执行udp open / udp bind :: 12345,B执行udp open / udp bind :: 12346;B发ot udp send <A的ML-EID> 12345 W10-001,A反向发到B的12346端口W10-002。必须在对端串口观察到实际payload,发送端Done只证明请求被接受。结束各自udp close。

这个通信发生在Thread网络内,不需要也没有提供电脑LAN到Thread的路由;电脑直接ping失败不能据此判两节点未互通。需要跨网才另建Border Router及其回程/发现/路由条件,本页不替未来Matter教程虚构这条链路。

做到这里,你应该看到
双向至少各一条对端实际UDP接收记录,和相同trial的地址/角色记录放一起。

参考:[3] IDF5.5.5 C6 OpenThread:角色/BR支持边界[7] 固定OpenThread mode/pollperiod/地址命令

08 / 无线实验

重启保存和sleepy对照,分别验证

先记B重启前Active Dataset,执行ot reset或RESET,再查dataset active -x私有比对。这个工程不自动start,所以随后重新明确mode -、pollperiod1000、ifconfig up、thread start,再验证parent/消息;不能把每次重新init new都写成“保存成功”。不承诺mode/pollperiod跨重启保留,每次明确设置。

接着B先mode r(RxOnWhenIdle=1)做一组同样消息,再mode -/pollperiod1000,待A的child table更新后重复。每组观察至少10秒,保留实际发/收次数与RTT。轮询配置不是每条业务恰好1秒延迟;MAC重传、父节点缓存、attach与调度会影响结果。

本例PM关闭、桥口/电源灯仍在,不能用模式字段或整板电流声称芯片睡眠电流;本站也没有电流波形。Thread的RxOffWhenIdle、CPU自动Light-sleep和整板低功耗是三件分别需要证据的事。

参考:[4] 固定ot_cli原样入口与默认配置[6] 固定OpenThread Dataset CLI:active -x / commit / set[7] 固定OpenThread mode/pollperiod/地址命令

09 / 无线实验

先查Dataset和地址,再选择清除保存

两边都leader通常说明B也init new或复制错Dataset;先stop/down,再从A重新active -x与B set active。B detached时先近点、同channel/同Dataset、A持续供电,再核目标和角色。child但无消息时核实际ML-EID/UDP端口,不先用公网ping或把RLOC当固定设备身份。

重启不自动联网是本例设计;若Dataset实际丢失,保留NVS/供电错误而非假造一条成功记录。只有明确选择删除此板网络时用ot factoryreset,会清保存并重启,再复制A数据;最后的erase-flash会清全部Flash。BOOT9+RESET可回下载模式。

本轮FTD/MTD实际构建通过;20组合成Dataset预检只证明工具行为,不能替代CLI、NVS、IPv6或sleepy射频执行。观察CSV保持表头,所有真实两板实验pending。需要回补基础时回读E01/N01,三篇C5/C6无线实验各自完成,不将Zigbee入网设为Thread的必修。

BACK TO THE SOURCE

资料来源与版本说明

官方网页与具体固定源码逐项核对,详见docs/esp32-evidence/wireless-c5-c6.md及sources/builds/hosts JSON。硬件和无线链路没有本站观测。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. C6官方指南v1.2:N8、J1/J3、V1.4/PW修订和供电互斥
  2. C6电路V1.4:p2 GPIO6/7/10、UART/USB、J5
  3. IDF5.5.5 C6 OpenThread:角色/BR支持边界
  4. 固定ot_cli原样入口与默认配置
  5. 固定SDK初始化CLI与netif/start顺序
  6. 固定OpenThread Dataset CLI:active -x / commit / set
  7. 固定OpenThread mode/pollperiod/地址命令