10BASE-T1S Arduino 代码接口深度解析(〇):系列索引与全局速查

系列导读

本系列四篇从代码接口视角对 Arduino_10BASE_T1S 库 v0.1.1 进行完整拆解,逐函数、逐字段、逐回调地列出语义、约束、错误模式和移植要点。配套已发布的架构/协议/移植/构建文档共同构成完整的代码理解资料库。

1. 系列文章索引

本系列(代码接口级)

# 标题 主要内容
0 系列索引与全局速查(本篇) 全局架构、文件清单、API 速查、移植决策树
1 应用层 UDP Socket API Arduino_10BASE_T1S_UDP、内部 UdpRxPacket、发送/接收状态机、错误码
2 PHY Interface 与 HAL 层 Arduino_10BASE_T1S_PHY_InterfaceTC6_Arduino_10BASE_T1STC6_Io SPI HAL
3 libtc6 协议核心与 lwIP 集成 libtc6 C API、回调契约、lwIP netif、7 阶段寄存器队列
4 辅助类与 lwIP 配置 MacAddress / T1SPlcaSettings / T1SMacSettingslwipopts.h 详解

配套系列(架构级)

标题 内容
10BASE-T1S Arduino 开源库架构分析(一):架构概览与开源评估 四层架构、CI 矩阵、许可证分析
10BASE-T1S Arduino 开源库架构分析(二):OA TC6 协议栈深度分析 OA TC6 帧结构、Credit 流控、PLCA 寄存器、初始化序列
10BASE-T1S Arduino 开源库架构分析(三):移植实战指南 抽象边界、可复用模块、移植 checklist
Arduino IDE 编译系统深度解析 从源码到 Flash 的完整流程、内存布局

2. 全局架构与文件映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
┌─────────────────────────────────────────────────────────────────────┐
│ Sketch (.ino) │
│ Arduino_10BASE_T1S_PHY_TC6(SPI, 9, 6, 2); │
│ Arduino_10BASE_T1S_UDP udp; │
│ t1s_io.begin(); t1s_phy.begin(...); udp.begin(port); │
└─────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌────────────────────────┐ ┌─────────────────┐
│ Arduino_10BASE_ │ │ TC6_Arduino_10BASE_T1S │ │ Arduino_10BASE_ │
│ T1S_UDP.h/cpp │ │ .h/cpp │ │ T1S.h │
│ │ │ (继承 PHY_Interface) │ │ (主入口 + 宏) │
│ UDP socket API │ │ lwIP ↔ libtc6 桥接 │ │ │
└──────────────────┘ └────────────────────────┘ └─────────────────┘
│ │
│ ├─→ MacAddress.h/cpp
│ ├─→ T1SPlcaSettings.h/cpp
│ ├─→ T1SMacSettings.h/cpp
│ │
│ └─→ microchip/TC6_Io.h/cpp (HAL)
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ lib/liblwip/ (lwIP TCP/IP 协议栈 + cfg/lwipopts.h 重命名) │
│ lib/lwip_sys_now.cpp (sys_now → millis()) │
└─────────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ microchip/lib/libtc6/ │
│ inc/tc6.h (公开 C API + 回调声明) │
│ src/tc6.cpp (协议核心:chunk/credit/状态机) │
│ src/tc6-queue.h (3 个无锁环形队列) │
│ inc/tc6-regs.h (LAN865x 寄存器层 API) │
│ src/tc6-regs.cpp (32 条寄存器初始化表 + PLCA + Chip ID) │
│ cfg-example/tc6-conf.h (编译配置) │
└─────────────────────────────────────────────────────────────────────┘

▼ SPI (24 MHz, Mode 0, MSB First)
┌─────────────────────────────────────────────────────────────────────┐
│ LAN8651 MAC-PHY → 10BASE-T1S 双绞线 │
└─────────────────────────────────────────────────────────────────────┘

3. 文件清单与作用

3.1 应用层与公开 API

文件 行数 作用 文档
src/Arduino_10BASE_T1S.h 64 主入口、Arduino_10BASE_T1S_PHY_TC6 宏、板卡引脚定义 [1]
src/Arduino_10BASE_T1S_PHY_Interface.h 54 抽象基类(预留) [2]
src/Arduino_10BASE_T1S_UDP.h 304 UDP socket 类声明、内部 UdpRxPacket [1]
src/Arduino_10BASE_T1S_UDP.cpp 257 UDP 实现 [1]
src/MacAddress.h / .cpp 48 / 109 6 字节 MAC 地址 + UID 生成 [4]
src/T1SPlcaSettings.h / .cpp 57 / 56 PLCA 节点配置 [4]
src/T1SMacSettings.h / .cpp 50 / 51 MAC 行为配置 [4]

3.2 PHY 与 HAL 层

文件 行数 作用 文档
src/microchip/TC6_Arduino_10BASE_T1S.h 156 PHY 实现类 + DIO 枚举 [2]
src/microchip/TC6_Arduino_10BASE_T1S.cpp 668 核心集成(lwIP ↔ libtc6) [2][3]
src/microchip/TC6_Io.h 69 SPI HAL 类声明 [2]
src/microchip/TC6_Io.cpp 107 SPI HAL 实现(24MHz, Mode 0) [2]

3.3 libtc6 协议核心

文件 行数 作用 文档
src/microchip/lib/libtc6/inc/tc6.h 344 公开 C API + 回调声明 + 类型定义 [3]
src/microchip/lib/libtc6/src/tc6.cpp 1318 OA TC6 协议核心实现 [3]
src/microchip/lib/libtc6/src/tc6-queue.h 300 三种无锁环形队列(自动生成) [3]
src/microchip/lib/libtc6/inc/tc6-regs.h 222 LAN865x 寄存器层 API [3]
src/microchip/lib/libtc6/src/tc6-regs.cpp ~700 LAN865x 32 条初始化表 + Chip ID + PLCA [3]
src/microchip/lib/libtc6/cfg-example/tc6-conf.h 153 编译时配置(队列大小、instance 数) [3]

3.4 lwIP 集成

文件 作用 文档
src/lib/liblwip/ (整个子目录) lwIP 协议栈(已重命名符号) [3]
src/lib/liblwip/cfg/lwipopts.h lwIP 编译配置(592 行重命名 + 选项) [4]
src/lib/lwip_sys_now.cpp sys_now() 桥接到 millis() [4]

3.5 Examples

文件 演示内容
examples/UDP_Client/UDP_Client.ino 客户端循环发包 + 接收 echo + PLCA 状态监控
examples/UDP_Server/UDP_Server.ino 服务端 echo 回环 + PLCA Coordinator 配置
examples/pingpong/pingpong.ino 多节点互发 ping/pong(broadcast)
examples/tools/Generate-MAC/Generate-MAC.ino 打印 MAC 地址
examples/tools/Control-DIOx/Control-DIOx.ino 通过 LAN8651 GPIO 控制 PoDL 电源
examples/tools/PoDL-Source/PoDL-Source.ino PoDL 电源端
examples/tools/PoDL-Sink-Auto-TurnOff/PoDL-Sink-Auto-TurnOff.ino PoDL 接收端自动断电

4. 全局 API 速查表

4.1 应用层(C++ / Arduino)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 1. 实例化 PHY + HAL(宏展开)
Arduino_10BASE_T1S_PHY_TC6(SPI, CS_PIN, RESET_PIN, IRQ_PIN);
Arduino_10BASE_T1S_UDP udp_client;

// 2. 中断配置
attachInterrupt(digitalPinToInterrupt(IRQ_PIN),
[]() { t1s_io.onInterrupt(); }, FALLING);

// 3. HAL + PHY 初始化
t1s_io.begin();
MacAddress const mac = MacAddress::create_from_uid();
T1SPlcaSettings const plca(1); // Node 1, default others
T1SMacSettings const mac_settings; // 全默认

bool ok = t1s_phy.begin(ip_addr, network_mask, gateway,
mac, plca, mac_settings);

// 4. UDP socket
udp_client.begin(8889);

// 5. 发送
udp_client.beginPacket(server_ip, 8888);
udp_client.write("hello", 5);
int rc = udp_client.endPacket(); // 1 = OK, 0 或 -1 = 失败

// 6. 接收
int size = udp_client.parsePacket();
if (size > 0) {
uint8_t buf[256];
int n = udp_client.read(buf, sizeof(buf));
IPAddress remote = udp_client.remoteIP();
uint16_t port = udp_client.remotePort();
udp_client.flush();
}

// 7. 主循环
void loop() {
t1s_phy.service(); // 必调,越快越好
}

// 8. PLCA 状态
t1s_phy.getPlcaStatus([](bool success, bool active) {
if (success && !active) t1s_phy.enablePlca();
});

4.2 libtc6 核心(C API)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 初始化
TC6_t *tc6 = TC6_Init(global_tag);

// 周期调用
TC6_Service(tc6, /*IRQ level*/ false); // IRQ 触发时
TC6_Service(tc6, true); // TC6_CB_OnNeedService 触发时
TC6Regs_CheckTimers(); // 寄存器超时检查

// 发送
uint8_t frame[64];
TC6_SendRawEthernetPacket(tc6, frame, sizeof(frame), 0, NULL, NULL);

// 寄存器
TC6_ReadRegister(tc6, 0x00010000, true, OnRegRead, NULL);
TC6_WriteRegister(tc6, 0x00010000, 0x0C, true, OnRegWrite, NULL);

4.3 libtc6 必实现回调(C API)

1
2
3
4
5
6
7
8
extern bool TC6_CB_OnSpiTransaction(TC6_t*, uint8_t*, uint8_t*, uint16_t, void*);
extern void TC6_CB_OnRxEthernetSlice(TC6_t*, const uint8_t*, uint16_t, uint16_t, void*);
extern void TC6_CB_OnRxEthernetPacket(TC6_t*, bool, uint16_t, uint64_t*, void*);
extern void TC6_CB_OnNeedService(TC6_t*, void*);
extern void TC6_CB_OnError(TC6_t*, TC6_Error_t, void*);
extern void TC6_CB_OnExtendedStatus(TC6_t*, void*); // 通常由 tc6-regs 实现
extern uint32_t TC6Regs_CB_GetTicksMs(void);
extern void TC6Regs_CB_OnEvent(TC6_t*, TC6Regs_Event_t, void*);

5. 内存预算速查(UNO R4 Minima)

模块 Flash RAM
Arduino 核心 + SPI 库 ~25KB ~5KB
liblwip(裁剪版) ~30KB ~6KB
libtc6 + tc6-regs ~20KB ~5KB
TC6_Io + TC6_Arduino_10BASE_T1S ~8KB ~2KB
Arduino_10BASE_T1S_UDP ~3KB ~1.5KB(10 包排队)
UDP_Client.ino sketch ~5KB ~1KB
合计 ~91KB ~20KB
可用 256KB (35%) 32KB (62%)

详细 lwipopts.h 调优见文档 [4]。

6. 移植决策树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
要移植到非 Arduino 平台?
├─ 是
│ ├─ 是否仍是 SPI + LAN865x?
│ │ ├─ 是 → 仅替换 TC6_Io 内部 5 个方法 + sys_now + millis
│ │ └─ 否 → 继续
│ └─ 是否仍是 OA TC6 协议?
│ ├─ 是 → 保留 libtc6,仅重写回调实现
│ └─ 否 → 整体替换协议层(最复杂)
└─ 否(仅改 MCU 型号)
├─ 是 Lumissil SoC 单芯片方案?
│ └─ 是 → 删除 TC6_Io + libtc6,保留 lwIP + Arduino_UDP
│ 实现 MAC descriptor 驱动(详见 Lumissil_SoC 决策文档)
└─ 否(仍外挂不同 MAC-PHY)
├─ MAC-PHY 兼容 OA TC6?
│ ├─ 是 → 替换 TC6_Io(SPI)+ tc6-regs.cpp(寄存器表)
│ └─ 否 → 整体替换协议层

7. 关键设计决策(FAQ 风格)

Q: 为什么 Arduino_10BASE_T1S_UDP::endPacket() 返回值是 0-1,而不是统一的 1/0

A: 历史原因——endPacket() 实现混合了 lwIP udp_sendto()(返回 err_t,枚举)的失败码 -1_udp_pcb == nullptr 检查的 0。调用方必须同时处理这两个值。详见文档 [1] 第 2.4 节。

Q: T1SPlcaSettings 默认构造为什么是 Node 0(Coordinator)?

A: 库作者把 DEFAULT_NODE_ID = 0 设为默认值。在 sketch 中必须显式传入自己的 Node ID,否则两块板子都是 Node 0 会冲突。常见错误:

1
2
3
4
5
// 错误:两块板子都用默认 = 都是 Node 0
T1SPlcaSettings plca;

// 正确:每块板子指定不同 Node ID
T1SPlcaSettings plca(MY_NODE_NUMBER);

Q: 库禁用 TCP,怎么启用?

A: 编辑 src/lib/liblwip/cfg/lwipopts.h,把 LWIP_TCP 改为 1,并相应调大 MEMP_NUM_TCP_PCB 等。注意:UNO R4 Minima(32KB RAM)启用 TCP 会立即超 RAM。详见文档 [4] 第 4.10 节。

Q: begin() 失败后能否重试?

A: 不建议。当前 TC6_Arduino_10BASE_T1S::begin() 失败时不会清理已经创建的 TC6_t* 实例和已注册的 netif。重试会导致内存泄漏和重复注册。修复需要修改 begin() 入口加 cleanup 逻辑。

Q: 如何在多个 sketch 之间共享同一个 PHY 实例?

A: 当前不支持Arduino_10BASE_T1S_PHY_TC6 宏产生固定名字 t1s_io / t1s_phy,无法在多个 .ino 文件中分别调用。

Q: 为什么 lwipopts.h 重命名所有符号?

A: 防止与 Arduino 核心库、其他库的符号冲突。详见文档 [4] 第 4.9 节。

8. 常见错误恢复流程

8.1 libtc6 错误到动作

错误 默认恢复
TC6Error_NoHardware TC6Regs_Reinit
TC6Error_BadChecksum TC6Regs_Reinit
TC6Error_UnexpectedCtrl TC6Regs_Reinit
TC6Error_BadTxData TC6Regs_Reinit
TC6Error_SyncLost TC6Regs_Reinit
TC6Error_SpiError TC6Regs_Reinit

8.2 TC6Regs 事件到动作

事件 默认恢复
TC6Regs_Event_Loss_of_Framing_Error TC6Regs_Reinit
TC6Regs_Event_RX_Non_Recoverable_Error TC6Regs_Reinit
TC6Regs_Event_TX_Non_Recoverable_Error TC6Regs_Reinit
TC6Regs_Event_Unsupported_Hardware 标记 init failed
其他 仅日志(默认 PRINT 空)

9. 调试入口汇总

9.1 启用 libtc6 错误打印

修改 src/microchip/TC6_Arduino_10BASE_T1S.cpp:493

1
2
3
4
5
// 原始:
#define PRINT(...)

// 改为:
#define PRINT(...) Serial.printf(__VA_ARGS__)

9.2 添加 RX/TX 计数器

TC6_CB_OnRxEthernetPacketlwIpOut 加 static 计数器,定期打印:

1
static uint32_t rx_count = 0, tx_count = 0, err_count = 0;

9.3 监控 service() 频率

1
2
3
4
5
6
7
8
9
static uint32_t prev = 0, cnt = 0;
cnt++;
uint32_t now = millis();
if (now - prev > 1000) {
Serial.print("service() rate: ");
Serial.print(cnt);
Serial.println(" Hz");
prev = now; cnt = 0;
}

9.4 lwIP 调试

1
2
3
// lwipopts.h 临时打开
#define LWIP_DEBUG 1
extern "C" unsigned char debug_flags = LWIP_DBG_ON;

10. 完整阅读路径建议

路径 A:应用开发者(只想用库)

  1. 文档 [1] 速读 → UDP API 完整契约
  2. 文档 [4] 速读 → 三个辅助类的用法
  3. 直接用 UDP_Client.ino / UDP_Server.ino 起步

路径 B:移植者(改 HAL 或 PHY 后端)

  1. 文档 [2] 全文 → TC6_IoTC6_Arduino_10BASE_T1S 接口
  2. 文档 [3] 第 4-6 节 → libtc6 回调契约
  3. 现有移植指南作为 checklist

路径 C:芯片替换(移植到非 LAN865x)

  1. 文档 [3] 第 6 节 → tc6-regs.cpp 寄存器表
  2. 文档 [3] 第 8 节 → 移植到非 Arduino 平台的步骤
  3. 现有移植指南第 3-4 节

路径 D:架构理解(不想改代码)

  1. 架构概览 → 四层架构
  2. 协议栈深度分析 → OA TC6 帧结构
  3. 本系列文档 [0] 全局速查 + 文档 [1-2] 关键 API

11. 小结

本系列四篇文档的目标是让读者在不读 libtc6 源码的情况下,能正确使用和修改这个库

  • 文档 [1] 解决了 “怎么用“ 的问题——逐函数告诉你行为和陷阱
  • 文档 [2] 解决了 “怎么替换 HAL“ 的问题——SPI HAL 5 个方法 + PHY 抽象
  • 文档 [3] 解决了 “怎么对接新芯片“ 的问题——回调契约 + 寄存器表
  • 文档 [4] 解决了 “怎么调优“ 的问题——辅助类 + lwIP 配置
  • 文档 [0](本篇)提供了 “全局视图“——索引、速查、决策树

任何 API 行为疑惑,先查本系列对应章节;任何代码报错,先查文档 [3] 第 10 节调试入口;任何移植决策,先查本篇第 6 节决策树。