两块普通节点先互通,再谈跨网和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网络。
同款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
保留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;不要刷混。完整原样入口与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
}
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和真实角色,不是教程预造的成功输出。
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/地址命令
用真实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/地址命令
重启保存和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/地址命令
先查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的必修。
资料来源与版本说明
官方网页与具体固定源码逐项核对,详见docs/esp32-evidence/wireless-c5-c6.md及sources/builds/hosts JSON。硬件和无线链路没有本站观测。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。