1. 1. 系列导读
  2. 2. 1. MacAddress
    1. 2.1. 1.1 类签名
    2. 2.2. 1.2 三种构造方式
      1. 2.2.1. MacAddress() — 默认(全 0)
      2. 2.2.2. MacAddress(const uint8_t *mac) — 从字节数组
      3. 2.2.3. static MacAddress create_from_uid() — 从 MCU 唯一 ID
      4. 2.2.4. 各平台 UID 实现(get_unique_chip_id_3)
    3. 2.3. 1.3 序列化:printTo(Print&)
    4. 2.4. 1.4 与 TC6_Arduino_10BASE_T1S::begin() 的对接
    5. 2.5. 1.5 完整用法
  3. 3. 2. T1SPlcaSettings
    1. 3.1. 2.1 类签名
    2. 3.2. 2.2 四个字段语义
      1. 3.2.1. 默认值与典型配置
      2. 3.2.2. Burst 模式何时启用
    3. 3.3. 2.3 构造函数重载
    4. 3.4. 2.4 序列化输出
    5. 3.5. 2.5 运行时修改
    6. 3.6. 2.6 完整用法
  4. 4. 3. T1SMacSettings
    1. 4.1. 3.1 类签名
    2. 4.2. 3.2 三个字段语义
    3. 4.3. 3.3 寄存器写入逻辑
    4. 4.4. 3.4 序列化输出
    5. 4.5. 3.5 完整用法
  5. 5. 4. lwipopts.h 详解
    1. 5.1. 4.1 平台/内核选项
    2. 5.2. 4.2 模块开关(核心)
    3. 5.3. 4.3 内存池大小
    4. 5.4. 4.4 IP 选项
    5. 5.5. 4.5 UDP 选项
    6. 5.6. 4.6 缓冲区 / MTU
    7. 5.7. 4.7 校验和
    8. 5.8. 4.8 调试
    9. 5.9. 4.9 符号重命名(避免冲突)
    10. 5.10. 4.10 调优建议
      1. 5.10.1. 减小 RAM(UNO R4 紧张时)
      2. 5.10.2. 启用 TCP(如需要)
      3. 5.10.3. 启用 DHCP
  6. 6. 5. sys_now() 时基桥接
    1. 6.1. 5.1 实际编译符号
    2. 6.2. 5.2 调用频率
  7. 7. 6. 全系列速查:API 索引
    1. 7.1. 6.1 应用层
    2. 7.2. 6.2 PHY 实现层
    3. 7.3. 6.3 libtc6 协议层(C API)
    4. 7.4. 6.4 libtc6 必实现回调
    5. 7.5. 6.5 tc6-regs 寄存器层
    6. 7.6. 6.6 错误码全集
  8. 8. 7. 配置矩阵速查
    1. 8.1. 7.1 典型应用配置
    2. 8.2. 7.2 板卡引脚
  9. 9. 8. 调试速查清单
  10. 10. 9. 跨系列引用图
  11. 11. 10. 小结

10BASE-T1S Arduino 代码接口深度解析(四):辅助类与 lwIP 配置

系列导读

本篇是代码接口系列的最后一篇,覆盖三类小但关键的接口:

  1. 辅助类:MacAddress / T1SPlcaSettings / T1SMacSettings
  2. lwIP 集成层:lwipopts.h 配置、sys_now() 桥接
  3. 全系列索引速查

前序文章:

1. MacAddress

文件位置:src/MacAddress.h / src/MacAddress.cpp

1.1 类签名

1
2
3
4
5
6
7
8
9
10
11
12
13
class MacAddress : public arduino::Printable {
public:
MacAddress() : MacAddress(nullptr) { }
MacAddress(uint8_t const * mac);
static MacAddress create_from_uid();

uint8_t * data() { return _data; }
uint8_t const * data() const { return _data; }

private:
uint8_t _data[MAC_ADDRESS_NUM_BYTES]; // = 6
virtual size_t printTo(Print & p) const override;
};

1.2 三种构造方式

MacAddress() — 默认(全 0)

1
MacAddress mac;  // {0,0,0,0,0,0}
  • 内部委托给 MacAddress(nullptr)
  • memset(_data, 0, 6)

MacAddress(const uint8_t *mac) — 从字节数组

1
2
uint8_t bytes[6] = {0xA8, 0x61, 0x0A, 0x12, 0x34, 0x56};
MacAddress mac(bytes);
  • 如果 mac == nullptr,填充 0
  • 否则 memcpy(_data, mac, 6)

static MacAddress create_from_uid() — 从 MCU 唯一 ID

src/MacAddress.cpp:39-52

1
2
3
4
5
6
MacAddress MacAddress::create_from_uid() {
uint8_t mac_addr[6] = {0xA8, 0x61, 0x0A, 0, 0, 0}; // Arduino OUI
static uint8_t const MAC_NIC_SPECIFIC_OFFSET = 3;
get_unique_chip_id_3(mac_addr + MAC_NIC_SPECIFIC_OFFSET);
return MacAddress(mac_addr);
}
  • OUI 固定为 A8:61:0A — Arduino 在 IEEE 注册的组织唯一标识符
  • 后 3 字节取自 MCU 唯一 ID(不同平台实现不同)

各平台 UID 实现(get_unique_chip_id_3

src/MacAddress.cpp:78-102

平台宏 来源 字节序
ARDUINO_ARCH_SAMD *(volatile uint32_t*)(0x0080A048) — SAMD21 唯一 ID 寄存器 小端
ARDUINO_MINIMA / ARDUINO_UNOWIFIR4 / ARDUINO_PORTENTA_C33 R_BSP_UniqueIdGet() — Renesas FSP BSP API 由 BSP 决定
ARDUINO_GIGA / ARDUINO_PORTENTA_H7_* HAL_GetUIDw2() — STM32 HAL 小端
其他 # warning 编译警告,UID 全 0(多块板子 MAC 冲突

已知代码问题MacAddress.cpp:90-98):

1
2
3
4
5
6
7
8
9
#elif defined(ARDUINO_GIGA) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_H7_M4)
{
uint32_t const stm32_uid = HAL_GetUIDw2();
memcpy(uid, &stm32_uid, 3);
}
{
auto stm32_uid = HAL_GetUIDw2(); // ← 重复块!会展开两次
memcpy(uid, &stm32_uid, 3);
}

两个相同的 { ... } 块在 #elif都被编译——stm32_uid 写两次,memcpy 调两次,UID 被覆盖(但因为两次内容相同,结果仍然正确)。Bug 但不影响功能

1.3 序列化:printTo(Print&)

1
2
3
4
5
6
7
8
size_t MacAddress::printTo(Print & p) const {
char msg[32] = {0};
uint8_t const * const ptr_mac = (this->data());
snprintf(msg, sizeof(msg), "MAC\t%02X:%02X:%02X:%02X:%02X:%02X",
ptr_mac[0], ptr_mac[1], ptr_mac[2],
ptr_mac[3], ptr_mac[4], ptr_mac[5]);
return p.write(msg);
}
说明
输出格式 MAC\tAA:BB:CC:DD:EE:FF
大写十六进制 %02X
分隔符 :
前缀 MAC\t(含 tab)
用途 Serial.println(mac_addr) 直接打印

1.4 与 TC6_Arduino_10BASE_T1S::begin() 的对接

1
2
// src/microchip/TC6_Arduino_10BASE_T1S.cpp:137
memcpy(_lw.ip.mac, mac_addr.data(), sizeof(_lw.ip.mac));

data() 返回内部 _data[6] 指针,6 字节直接拷贝。

随后传递给:

1
2
3
// tc6-regs.cpp:372
regVal = ((uint32_t)pReg->mac[3] << 24) | ((uint32_t)pReg->mac[2] << 16) | ...
TC6_WriteRegister(pReg->pTC6, 0x00010024 /* SPEC_ADD2_BOTTOM */, regVal, ...);

字节序转换在 tc6-regs 内部完成,调用方不需要关心

1.5 完整用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方法 1:自动生成(推荐)
MacAddress const mac = MacAddress::create_from_uid();

// 方法 2:手动指定(生产环境,每块板子烧不同)
uint8_t bytes[6] = {0xA8, 0x61, 0x0A, 0x12, 0x34, 0x56};
MacAddress const mac(bytes);

// 方法 3:默认(全 0,仅测试)
MacAddress const mac;

// 打印
Serial.println(mac);
// 输出: MAC A8:61:0A:12:34:56

// 传入 PHY
t1s_phy.begin(ip, mask, gw, mac, plca, mac_settings);

2. T1SPlcaSettings

文件位置:src/T1SPlcaSettings.h / src/T1SPlcaSettings.cpp

2.1 类签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class T1SPlcaSettings : public arduino::Printable {
private:
uint8_t _node_id;
uint8_t _node_count;
uint8_t _burst_count;
uint8_t _burst_timer;
public:
static uint8_t const DEFAULT_NODE_ID = 0;
static uint8_t const DEFAULT_NODE_COUNT = 8;
static uint8_t const DEFAULT_BURST_COUNT = 0;
static uint8_t const DEFAULT_BURST_TIMER = 128;

T1SPlcaSettings(); // 全默认
T1SPlcaSettings(uint8_t const node_id); // 仅 node_id
T1SPlcaSettings(uint8_t const node_id, uint8_t const node_count,
uint8_t const burst_count, uint8_t const burst_timer);

virtual size_t printTo(Print & p) const override;

uint8_t nodeId() const { return _node_id; }
uint8_t nodeCount() const { return _node_count; }
uint8_t burstCount() const { return _burst_count; }
uint8_t burstTimer() const { return _burst_timer; }
};

2.2 四个字段语义

字段 范围 含义 OA TC6 寄存器
_node_id 0..255 本节点 PLCA ID
0 = Coordinator(必须唯一一个)
1..N = Follower
PLCA_CONTROL_1[7:0]
_node_count 1..255 总节点数上限
Coordinator 知道的最大 ID + 1
PLCA_CONTROL_1[15:8]
_burst_count 0..N 单次 PLCA 周期内允许连续发送的最大 chunk 数 PLCA_BURST_MODE[15:8]
_burst_timer 0..255 突发模式计时器(单位由 chip 决定) PLCA_BURST_MODE[7:0]

默认值与典型配置

角色 _node_id _node_count _burst_count _burst_timer
Coordinator 0 8 (MAX_NODES) 0 128
Follower 1 1 8 0 128
Follower 7 7 8 0 128

Burst 模式何时启用

  • _burst_count > 0:允许该节点在 PLCA turn 中连续发 burst_count 个 chunk(提升吞吐)
  • _burst_count = 0(默认):标准 PLCA,每 turn 一个 chunk
  • _burst_timer:两次 burst 之间的间隔(防止独占总线)

2.3 构造函数重载

1
2
3
T1SPlcaSettings();                                     // 全默认(Node 0,Coordinator)
T1SPlcaSettings(uint8_t node_id); // Node ID 定制,其他默认
T1SPlcaSettings(node_id, node_count, burst_count, burst_timer); // 完全定制

注意:默认构造 T1SPlcaSettings() 等价于 T1SPlcaSettings(0) = Coordinator。这是反直觉的——默认是 Coordinator 而不是普通 Follower。代码中典型用法:

1
2
static T1SPlcaSettings const t1s_plca_settings{T1S_PLCA_NODE_ID};
// ↑ 单参构造,Node ID = T1S_PLCA_NODE_ID,其他默认值

2.4 序列化输出

1
2
3
4
5
6
7
8
9
10
11
12
13
size_t T1SPlcaSettings::printTo(Print & p) const {
char msg[128] = {0};
snprintf(msg, sizeof(msg),
"PLCA\n"
"\tnode id : %d%s\n"
"\tnode count : %d\n"
"\tburst count : %d\n"
"\tburst timer : %d",
_node_id,
(_node_id == 0) ? " (PLCA Coordinator)" : "",
_node_count, _burst_count, _burst_timer);
return p.write(msg);
}

输出示例(Node 1):

1
2
3
4
5
PLCA
node id : 1
node count : 8
burst count : 0
burst timer : 128

Node 0 会带 (PLCA Coordinator) 后缀。

2.5 运行时修改

T1SPlcaSettings 对象一旦传入 begin() 就被 TC6_Arduino_10BASE_T1S 拷贝保存_t1s_plca_settings 成员。修改原对象不会影响 PHY 行为

要运行时修改 PLCA,必须调用:

1
t1s_phy.enablePlca();  // 用缓存值重新使能

或通过 TC6Regs_SetPlca()(间接,被 enablePlca() 内部调用):

1
2
3
4
5
6
// TC6_Arduino_10BASE_T1S.cpp:230
bool TC6_Arduino_10BASE_T1S::enablePlca() {
return TC6Regs_SetPlca(_lw.tc.tc6, true,
_t1s_plca_settings.nodeId(),
_t1s_plca_settings.nodeCount());
}

注意enablePlca() 只更新 node_id/count,更新 burst_count/burst_timer(那些只在 init 时写入)。

2.6 完整用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Coordinator
T1SPlcaSettings coordinator_plca; // 默认 = Node 0

// Follower
T1SPlcaSettings follower_plca(MY_NODE_NUMBER); // Node ID = MY_NODE_NUMBER

// 自定义全部
T1SPlcaSettings custom_plca(/*node_id*/2, /*count*/8, /*burst_count*/3, /*burst_timer*/64);

// 打印
Serial.println(follower_plca);

// 传入 PHY
t1s_phy.begin(ip, mask, gw, mac, follower_plca, mac_settings);

3. T1SMacSettings

文件位置:src/T1SMacSettings.h / src/T1SMacSettings.cpp

3.1 类签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class T1SMacSettings : public arduino::Printable {
private:
bool const _mac_promiscuous_mode;
bool const _mac_tx_cut_through;
bool const _mac_rx_cut_through;
public:
static bool const DEFAULT_MAC_PROMISCUOUS_MODE = false;
static bool const DEFAULT_MAC_TX_CUT_THROUGH = false;
static bool const DEFAULT_MAC_RX_CUT_THROUGH = false;

T1SMacSettings();
T1SMacSettings(bool const mac_promiscuous_mode,
bool const mac_tx_cut_through,
bool const mac_rx_cut_through);

virtual size_t printTo(Print & p) const override;

bool isMacPromiscuousModeEnabled() const { return _mac_promiscuous_mode; }
bool isMacTxCutThroughEnabled() const { return _mac_tx_cut_through; }
bool isMacRxCutThroughEnabled() const { return _mac_rx_cut_through; }
};

3.2 三个字段语义

字段 默认 含义 LAN8651 寄存器
_mac_promiscuous_mode false 混杂模式:接受所有帧(包括非本机 MAC 的) NETWORK_CONFIG bit[4] = 0x10
_mac_tx_cut_through false TX 切通:帧未完全进入 MAC FIFO 就开始发送(降低延迟,需要 PHY 支持 CONFIG0 bit[9] = 0x200
_mac_rx_cut_through false RX 切通:帧未完全接收就开始向上传递(降低延迟,增加 CRC 错误风险 CONFIG0 bit[8] = 0x100

const 设计:所有字段都是 bool const——对象一旦构造就不可修改。要运行时切换模式必须重新构造对象 + 重新 begin()

3.3 寄存器写入逻辑

tc6-regs.cpp:386-407

1
2
3
4
5
6
7
8
9
// 混杂模式
regVal = pReg->promiscuous ? 0x10 : 0x0;
TC6_WriteRegister(pReg->pTC6, 0x00010001 /* NETWORK_CONFIG */, regVal, true);

// Cut-through 模式(最后写入)
regVal = 0x9026; // CONFIG0 基础值
if (pReg->txCutThrough) regVal |= 0x200u;
if (pReg->rxCutThrough) regVal |= 0x100u;
TC6_WriteRegister(pReg->pTC6, 0x00000004 /* CONFIG0 */, regVal, true);

注意 0x9026 是 LAN8651 的基础 CONFIG0 值(不是 0),含:

  • SPI chunk 大小
  • 其他 LAN8651 特定设置

移植时:基础值 0x9026 需要替换为竞品的 CONFIG0 默认值。

3.4 序列化输出

1
2
3
4
5
6
7
8
9
10
size_t T1SMacSettings::printTo(Print & p) const {
char msg[128] = {0};
snprintf(msg, sizeof(msg),
"MAC\n"
"\tpromisc. mode : %d\n"
"\ttx cut through: %d\n"
"\trx cut through: %d",
_mac_promiscuous_mode, _mac_tx_cut_through, _mac_rx_cut_through);
return p.write(msg);
}

输出:

1
2
3
4
MAC
promisc. mode : 0
tx cut through: 0
rx cut through: 0

3.5 完整用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 默认(全 false)
T1SMacSettings mac_settings;

// 启用 cut-through 降低延迟(注意:仅当链路质量好时)
T1SMacSettings low_latency_mac(false, true, true);

// 混杂模式(监听所有流量)
T1SMacSettings sniffer_mac(true, false, false);

// 打印
Serial.println(low_latency_mac);

// 传入 PHY
t1s_phy.begin(ip, mask, gw, mac, plca_settings, low_latency_mac);

4. lwipopts.h 详解

文件位置:src/lib/liblwip/cfg/lwipopts.h

4.1 平台/内核选项

含义
NO_SYS 1 裸机模式——lwIP 不启动内部线程,由用户主循环驱动
SYS_LIGHTWEIGHT_PROT 0 不需要并发保护(单线程 + 中断)
MEM_ALIGNMENT 4 内存对齐(ARM Cortex-M 自然对齐)
MEM_SIZE 0 私有 heap 0(MEM_LIBC_MALLOC=1 时忽略)
MEM_LIBC_MALLOC 1 使用 libc malloc/free

4.2 模块开关(核心)

备注
LWIP_ARP 1 ARP 必需
LWIP_IPV4 1 仅 IPv4
LWIP_IPV6 0 禁用
LWIP_ICMP 1 让 ping 工作
LWIP_RAW 1 原始 PCB(库内部未使用,保留默认)
LWIP_UDP 1 必需——本库只支持 UDP
LWIP_TCP 0 禁用 TCP(减小 RAM)
LWIP_DHCP 0 静态 IP(可手动改 1 + 配 DHCP 调用)
LWIP_AUTOIP 0 禁用 AutoIP
LWIP_IGMP 0 禁用多播
LWIP_DNS 0 解释 beginPacket(host, port) 返回 0
LWIP_SNMP 0 禁用 SNMP
LWIP_NETCONN 0 禁用 Netconn API
LWIP_SOCKET 0 禁用 BSD Socket API
LWIP_HAVE_LOOPIF 0 无 loopback

4.3 内存池大小

单池 RAM 说明
MEMP_NUM_PBUF 20 ~640B 通用 pbuf 池
MEMP_NUM_UDP_PCB 4 ~80B UDP 控制块(4 个并发 socket)
MEMP_NUM_TCP_PCB 0 0 TCP 禁用
MEMP_NUM_TCP_PCB_LISTEN 0 0 TCP 禁用
MEMP_NUM_TCP_SEG 0 0 TCP 禁用
MEMP_NUM_ARP_QUEUE 6 ~144B ARP 请求排队
MEMP_NUM_RAW_PCB 4 ~80B 原始 PCB
MEMP_NUM_NETBUF/NETCONN 0 0 Netconn API 禁用
PBUF_POOL_SIZE 1 ~1520B PBUF_POOL 池(1 个 1500 字节)

4.4 IP 选项

备注
IP_FORWARD 0 单网口不转发
IP_OPTIONS_ALLOWED 1 允许但不解 IP options
IP_REASSEMBLY 0 禁用 IP 分片重组(10Mbps 通常不分片)
IP_FRAG 0 禁用 IP 分片发送(MTU 1500 通常不需要)
IP_REASS_MAXAGE 3 重组超时
IP_DEFAULT_TTL 255 标准 TTL
ICMP_TTL IP_DEFAULT_TTL

4.5 UDP 选项

备注
LWIP_UDP 1
LWIP_UDPLITE 0 禁用 UDP-Lite
UDP_TTL IP_DEFAULT_TTL

4.6 缓冲区 / MTU

备注
PBUF_LINK_HLEN 14 标准以太网头
ETHERNET_MTU 1500 注意:TC6_Arduino_10BASE_T1S::lwIpInitnetif->mtu = 1536(多 36 字节给 VLAN 等)

4.7 校验和

含义
CHECKSUM_CHECK_IP 0 不校验 IP 头——LAN8651 PHY 已做
CHECKSUM_CHECK_UDP 0 不校验 UDP——同上
CHECKSUM_CHECK_TCP 0 TCP 禁用,但即使启用也不校验

注意:lwIP 在发送时仍会生成校验和(由 MAC 硬件或软件添加),仅是 RX 时不验证。pbuf_take() 后 lwIP 内部用 inet_chksum 计算。

4.8 调试

1
2
3
4
#define LWIP_DEBUG 0  // 已注释
// 所有模块的 *_DEBUG 都设为 LWIP_DBG_OFF
extern unsigned char debug_flags;
#define LWIP_DBG_TYPES_ON debug_flags // 全局开关

debug_flags 是用户自定义的全局变量,可以运行时切换调试级别(默认未定义)。

4.9 符号重命名(避免冲突)

1
2
3
#define sys_now t1s_sys_now
#define etharp_output t1s_etharp_output
// ... 100+ 项

为什么需要

  1. Arduino 核心库有自己的 sys_now
  2. 不同模块 include lwIP 时可能出现符号冲突
  3. 重命名保证这个 lwIP 实例的符号与其他模块共存

src/lib/lwip_sys_now.cpp 实现的 sys_now 因为宏重定义,实际编译为 t1s_sys_now

4.10 调优建议

减小 RAM(UNO R4 紧张时)

1
2
3
4
#define MEMP_NUM_PBUF       10   // 默认 20
#define PBUF_POOL_SIZE 1 // 默认 1(已最小)
#define MEMP_NUM_ARP_QUEUE 3 // 默认 6
#define MEMP_NUM_UDP_PCB 2 // 默认 4(足够单 socket)

启用 TCP(如需要)

1
2
3
4
5
6
7
#define LWIP_TCP               1
#define MEMP_NUM_TCP_PCB 4
#define MEMP_NUM_TCP_PCB_LISTEN 1
#define MEMP_NUM_TCP_SEG 8
#define TCP_MSS (1500 - 40) // = 1460
#define TCP_WND (2 * TCP_MSS)
#define TCP_SND_BUF (2 * TCP_MSS)

注意:在 UNO R4 Minima(32KB RAM)上启用 TCP 会立即超出 RAM

启用 DHCP

1
#define LWIP_DHCP 1

然后在 setup() 中:

1
2
// t1s_phy.begin() 之后
dhcp_start(&t1s_phy.getNetif()); // 需要 expose netif

当前库没有 expose netif——启用 DHCP 需要修改 TC6_Arduino_10BASE_T1S 添加 getter。

5. sys_now() 时基桥接

文件位置:src/lib/lwip_sys_now.cpp

1
2
3
4
5
6
7
#include <Arduino.h>
#include "liblwip/include/lwip/opt.h"
#include "liblwip/arch/cc.h"

extern "C" u32_t sys_now(void) {
return millis();
}

5.1 实际编译符号

因为 lwipopts.h 定义了 #define sys_now t1s_sys_now,实际编译时 sys_now 标识符被替换为 t1s_sys_now。所以链接器看到的是:

1
extern "C" u32_t t1s_sys_now(void) { return millis(); }

lwIP 内部调用 sys_now() 时,预处理器先替换为 t1s_sys_now()

5.2 调用频率

sys_check_timeouts()TC6_Arduino_10BASE_T1S::service() 中被调用,依赖主循环频率。sys_now() 的精度直接决定 lwIP 内部定时器精度:

  • ARP 表老化(默认 60s 更新、20min 失效):毫秒级足够
  • DHCP 租约更新:秒级
  • TCP 重传:毫秒级(TCP 禁用,无关)

如果主循环很慢(如 100ms 一次 service()),lwIP 计时会偏移——但本库没有 TCP,偏移影响有限。

6. 全系列速查:API 索引

6.1 应用层

类型 头文件 主要 API
Arduino_10BASE_T1S_PHY_TC6(宏) Arduino_10BASE_T1S.h 实例化 PHY + HAL
Arduino_10BASE_T1S_PHY_Interface(抽象) Arduino_10BASE_T1S_PHY_Interface.h begin() / service()
Arduino_10BASE_T1S_UDP Arduino_10BASE_T1S_UDP.h begin/beginPacket/write/endPacket/parsePacket/read/peek/flush/remoteIP/remotePort/bufferSize/stop
MacAddress MacAddress.h 构造 / data() / create_from_uid() / printTo()
T1SPlcaSettings T1SPlcaSettings.h 构造 / nodeId/nodeCount/burstCount/burstTimer() / printTo()
T1SMacSettings T1SMacSettings.h 构造 / isMac*Enabled() / printTo()

6.2 PHY 实现层

类型 头文件 主要 API
TC6::TC6_Arduino_10BASE_T1S microchip/TC6_Arduino_10BASE_T1S.h begin() / service() / getPlcaStatus() / enablePlca() / sendWouldBlock() / digitalWrite()
TC6::TC6_Io microchip/TC6_Io.h begin() / onInterrupt() / isInterruptActive() / releaseInterrupt() / spiTransaction()
TC6::DIO microchip/TC6_Arduino_10BASE_T1S.h 枚举 A0 / A1

6.3 libtc6 协议层(C API)

函数 头文件 用途
TC6_Init / TC6_Destroy / TC6_Reset tc6.h 生命周期
TC6_Service tc6.h 主循环驱动
TC6_EnableData tc6.h 数据开关
TC6_SendRawEthernetPacket / TC6_SendRawEthernetSegments tc6.h 发送
TC6_GetRawSegments tc6.h 获取发送 segment
TC6_ReadRegister / TC6_WriteRegister / TC6_ReadModifyWriteRegister / TC6_MultipleRegisterAccess tc6.h 寄存器
TC6_GetState / TC6_GetInstance tc6.h 状态
TC6_SpiBufferDone tc6.h SPI 异步完成
TC6_UnlockExtendedStatus tc6.h 扩展状态解锁

6.4 libtc6 必实现回调

函数 用途
TC6_CB_OnSpiTransaction 平台 SPI
TC6_CB_OnRxEthernetSlice RX 切片累积
TC6_CB_OnRxEthernetPacket RX 帧完成
TC6_CB_OnNeedService 通知主循环
TC6_CB_OnError 错误处理
TC6_CB_OnExtendedStatus 扩展状态(由 tc6-regs 实现)

6.5 tc6-regs 寄存器层

函数 用途
TC6Regs_Init / TC6Regs_GetInitDone / TC6Regs_Reinit 初始化
TC6Regs_SetPlca 运行时 PLCA 切换
TC6Regs_GetChipRevision 读 chip revision
TC6Regs_EnableDio_A0/A1 / TC6Regs_ToggleDio_A0/A1 GPIO 控制
TC6Regs_CheckTimers 超时检查(必须周期调用)
TC6Regs_CB_GetTicksMs 必实现回调
TC6Regs_CB_OnEvent 必实现回调
TC6_CB_OnExtendedStatus 必实现回调

6.6 错误码全集

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
typedef enum {
TC6Error_Succeeded, TC6Error_NoHardware, TC6Error_UnexpectedSv,
TC6Error_UnexpectedDvEv, TC6Error_BadChecksum, TC6Error_UnexpectedCtrl,
TC6Error_BadTxData, TC6Error_SyncLost, TC6Error_SpiError, TC6Error_ControlTxFail,
} TC6_Error_t;

typedef enum {
TC6Regs_Event_UnknownError,
TC6Regs_Event_Transmit_Protocol_Error,
TC6Regs_Event_Transmit_Buffer_Overflow_Error,
TC6Regs_Event_Transmit_Buffer_Underflow_Error,
TC6Regs_Event_Receive_Buffer_Overflow_Error,
TC6Regs_Event_Loss_of_Framing_Error, // ← 触发 Reinit
TC6Regs_Event_Header_Error,
TC6Regs_Event_Reset_Complete,
TC6Regs_Event_PHY_Interrupt,
TC6Regs_Event_Transmit_Timestamp_Capture_Available_A/B/C,
TC6Regs_Event_Transmit_Frame_Check_Sequence_Error,
TC6Regs_Event_Control_Data_Protection_Error,
TC6Regs_Event_RX_Non_Recoverable_Error, // ← 触发 Reinit
TC6Regs_Event_TX_Non_Recoverable_Error, // ← 触发 Reinit
TC6Regs_Event_FSM_State_Error,
TC6Regs_Event_SRAM_ECC_Error,
TC6Regs_Event_Undervoltage,
TC6Regs_Event_Internal_Bus_Error,
TC6Regs_Event_TX_Timestamp_Capture_Overflow_A/B/C,
TC6Regs_Event_TX_Timestamp_Capture_Missed_A/B/C,
TC6Regs_Event_MCLK_GEN_Status,
TC6Regs_Event_gPTP_PA_TS_EG_Status,
TC6Regs_Event_Extended_Block_Status,
TC6Regs_Event_SPI_Err_Int,
TC6Regs_Event_MAC_BMGR_Int,
TC6Regs_Event_MAC_Int,
TC6Regs_Event_HMX_Int,
TC6Regs_Event_GINT_Mask,
TC6Regs_Event_Chip_Error,
TC6Regs_Event_Unsupported_Hardware,
} TC6Regs_Event_t;

7. 配置矩阵速查

7.1 典型应用配置

场景 T1SPlcaSettings T1SMacSettings
Coordinator(默认) {0} 默认
Follower(普通) {MY_NODE_ID} 默认
低延迟通信 {MY_NODE_ID, 8, 1, 64} (false, true, true)
网络嗅探 任意 (true, false, false)
高吞吐 Burst {MY_NODE_ID, 8, 5, 32} 默认

7.2 板卡引脚

板卡 CS RESET IRQ SPI
Arduino Zero / UNO R4 Minima / WiFi 9 6 2 SPI
Portenta H7 (MID carrier) PH_6 PH_15 PC_7 SPI
Portenta C33 (MID carrier) 25 6 2 SPI1
GIGA 9 6 2 SPI1

8. 调试速查清单

现象 排查方向
begin() 失败 1) SPI 连线 2) 芯片 ID 寄存器读 3) 中断脚配置
service() 看不到 RX 1) IRQ 是否触发 2) tc6NeedService 是否置位 3) lwIP PBUF_POOL_SIZE
UDP 收不到包 1) FilterRxEthernetPacket 是否过滤 2) pbuf_take 失败 3) pbuf->next 分段丢失
UDP 发不出去 1) ARP 未解析(ping 一下) 2) TX segment 队列满 3) _t1s_plca_settings 配置
PLCA 频繁回退 CSMA/CD 1) Coordinator 是否在线 2) 线缆质量 3) Node ID 冲突

9. 跨系列引用图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
应用 Sketch (.ino)

├─→ Arduino_10BASE_T1S_UDP [文档 1:UDP Socket API]
│ │
│ └─→ lwIP UDP/ARP/IP
│ │
│ └─→ netif.linkoutput = TC6_Arduino_10BASE_T1S::lwIpOut
│ │
│ ▼
│ libtc6 (TC6_t instance) [文档 3:libtc6 C API]
│ │
│ ├─→ TC6_SendRawEthernetSegments
│ ├─→ TC6_ReadRegister / WriteRegister
│ └─→ TC6_CB_OnSpiTransaction ──→ TC6_Io [文档 2:HAL]

└─→ TC6_Arduino_10BASE_T1S [文档 2:PHY Interface]

├─→ TC6Regs_Init / Reinit / SetPlca
├─→ TC6_Io(SPI + 中断)
└─→ MacAddress / T1SPlcaSettings / T1SMacSettings [本篇]

10. 小结

辅助类(MacAddress / T1SPlcaSettings / T1SMacSettings)虽然小,但承载了几个关键设计决策

  • MacAddress::create_from_uid() 利用 MCU 唯一 ID 自动生成 MAC,省去产线烧录
  • T1SPlcaSettings 默认 node_id=0(Coordinator),与典型使用(Follower)相反——需要在 sketch 显式指定
  • T1SMacSettings 字段 const,无法运行时切换模式

lwIP 配置(lwipopts.h)和 sys_now 桥接是连接”PC 上的 lwIP 概念”与”嵌入式平台”的最后一公里。本库的设计是最小可用 lwIP——禁用 TCP、DHCP、DNS、IGMP,仅保留 IPv4 + ICMP + ARP + UDP,配合 32KB RAM 的 UNO R4。

至此,本系列四篇结束。读者现在应能:

  • 调用任一公开 API 并理解其语义
  • 排查接口调用失败的常见原因
  • 在保持应用层不变的前提下替换 HAL 或 PHY 实现
  • 移植整个栈到非 Arduino 平台或竞品 MAC-PHY