系列导读
本系列四篇从代码接口视角对 Arduino_10BASE_T1S 库 v0.1.1 进行完整拆解,逐函数、逐字段、逐回调地列出语义、约束、错误模式和移植要点。配套已发布的架构/协议/移植/构建文档共同构成完整的代码理解资料库。
1. 系列文章索引
本系列(代码接口级)
配套系列(架构级)
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
| Arduino_10BASE_T1S_PHY_TC6(SPI, CS_PIN, RESET_PIN, IRQ_PIN); Arduino_10BASE_T1S_UDP udp_client;
attachInterrupt(digitalPinToInterrupt(IRQ_PIN), []() { t1s_io.onInterrupt(); }, FALLING);
t1s_io.begin(); MacAddress const mac = MacAddress::create_from_uid(); T1SPlcaSettings const plca(1); T1SMacSettings const mac_settings;
bool ok = t1s_phy.begin(ip_addr, network_mask, gateway, mac, plca, mac_settings);
udp_client.begin(8889);
udp_client.beginPacket(server_ip, 8888); udp_client.write("hello", 5); int rc = udp_client.endPacket();
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(); }
void loop() { t1s_phy.service(); }
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, false); TC6_Service(tc6, true); 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*); 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
| T1SPlcaSettings plca;
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_OnRxEthernetPacket 和 lwIpOut 加 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
| #define LWIP_DEBUG 1 extern "C" unsigned char debug_flags = LWIP_DBG_ON;
|
10. 完整阅读路径建议
路径 A:应用开发者(只想用库)
- 文档 [1] 速读 → UDP API 完整契约
- 文档 [4] 速读 → 三个辅助类的用法
- 直接用
UDP_Client.ino / UDP_Server.ino 起步
路径 B:移植者(改 HAL 或 PHY 后端)
- 文档 [2] 全文 →
TC6_Io 和 TC6_Arduino_10BASE_T1S 接口
- 文档 [3] 第 4-6 节 → libtc6 回调契约
- 现有移植指南作为 checklist
路径 C:芯片替换(移植到非 LAN865x)
- 文档 [3] 第 6 节 →
tc6-regs.cpp 寄存器表
- 文档 [3] 第 8 节 → 移植到非 Arduino 平台的步骤
- 现有移植指南第 3-4 节
路径 D:架构理解(不想改代码)
- 架构概览 → 四层架构
- 协议栈深度分析 → OA TC6 帧结构
- 本系列文档 [0] 全局速查 + 文档 [1-2] 关键 API
11. 小结
本系列四篇文档的目标是让读者在不读 libtc6 源码的情况下,能正确使用和修改这个库:
- 文档 [1] 解决了 “怎么用“ 的问题——逐函数告诉你行为和陷阱
- 文档 [2] 解决了 “怎么替换 HAL“ 的问题——SPI HAL 5 个方法 + PHY 抽象
- 文档 [3] 解决了 “怎么对接新芯片“ 的问题——回调契约 + 寄存器表
- 文档 [4] 解决了 “怎么调优“ 的问题——辅助类 + lwIP 配置
- 文档 [0](本篇)提供了 “全局视图“——索引、速查、决策树
任何 API 行为疑惑,先查本系列对应章节;任何代码报错,先查文档 [3] 第 10 节调试入口;任何移植决策,先查本篇第 6 节决策树。