RCP/AVTP 10BASE-T1S 二次开发架构分析

前言

基于 Microchip LAN8650/LAN8651 + OA TC6 协议栈的现有 Arduino 10BASE-T1S 库(lwIP + UDP/IP 架构),本文分析切换到 RCP(Remote Control Protocol)/AVTP 协议栈的可行性、架构差异、所需移植修改的模块,以及如何利用 oa-tc6-libOpen1722 开源库实现二次开发。

一、现有架构回顾

1.1 当前分层结构

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


Arduino_10BASE_T1S_UDP (Arduino API 包装层)
│ ← lwIP udp_pcb,UDP Socket 抽象

lwIP TCP/IP 协议栈 (~45KB Flash)
│ ← ARP, IP, ICMP, UDP, DHCP, DNS...

TC6_Arduino_10BASE_T1S (MAC 封装层)
│ ← 将 Ethernet Frame 映射为 TC6 Data Chunk

libtc6 (OA TC6 协议状态机, ~15KB Flash)
│ ← Data/Control Transaction, SPI 读写时序, CRC

TC6_Io (SPI HAL 层)
│ ← SPI 数据收发 (TC6_CB_OnSpiTransaction)

LAN8650/LAN8651 (MAC-PHY, SPI 接口)
│ ← 10BASE-T1S 物理层, PLCA 总线仲裁

10BASE-T1S 总线 (单对线, 半双工)

1.2 编译产物实测数据(已知)

模块 Flash 占用
lwIP ~45KB
libtc6 ~15KB
库包装层 ~8KB
Arduino Core ~30KB
FSP HAL ~12KB
总计 ~110KB(剩余 130KB 给应用)

1.3 现有库关键文件

文件 职责
src/Arduino_10BASE_T1S_UDP.cpp UDP Socket API 实现
src/microchip/TC6_Arduino_10BASE_T1S.cpp TC6 + lwIP 绑定层
src/microchip/TC6_Io.cpp SPI HAL 抽象
src/lib/liblwip/ lwIP 源码
src/microchip/lib/libtc6/ OA TC6 协议库

二、RCP/AVTP 协议栈本质分析

2.1 RCP 与 AVTP 的关系

RCP(Remote Control Protocol) 构建在 IEEE 1722 AVTP 之上,是一个汽车专用扩展。理解这个关系至关重要:

1
2
3
4
5
6
7
8
9
┌──────────────────────────────────┐
│ RCP (OA Spec v0.4.7) │ ← Remote Control Protocol
│ ACF_ABB / ACF_GBB 格式 │ 13种端点类型 (SPI/GPIO/I2C/ADC...)
├──────────────────────────────────┤
│ IEEE 1722 AVTP (EtherType=0x22F0) │ ← Layer 2 Transport Protocol
│ NTSCF / TSCF 头部格式 │ 音视频/控制时间同步
├──────────────────────────────────┤
│ Ethernet Layer 2 │ ← EtherType = 0x22F0 (无 IP!)
└──────────────────────────────────┘

关键点:RCP 完全工作在 Ethernet Layer 2,EtherType = 0x22F0不需要 IP/UDP 协议栈

2.2 RCP 的核心价值:架构解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
传统架构(现状):
┌─────────────┐ ┌─────────────┐
│ 应用逻辑 │ │ 应用逻辑 │
│ + 驱动 │ │ + 驱动 │
└──────┬──────┘ └──────┬──────┘
│ │
lwIP/IP lwIP/IP
│ │
LAN8650 LAN8650

RCP 架构(目标):
┌──────────────────────────────────────────┐
│ HPC / Central ECU (RC Client) │
│ 应用逻辑 + 驱动 + RCP Client 栈 │
└────────────────────┬─────────────────────┘
│ 车载以太网 (RCP PDU)

┌────────────────────────────┐
│ Edge Node (RC Server) │
│ LAN8650 + RCP Server │
│ = 标准化"哑"设备 │
└────────────────────────────┘

这正是用户提到的”基于底层 MACPHY 数字电路,做网络包处理”——MCU 只做 Ethernet Frame 的收发,所有协议解析和业务逻辑集中在远端。

2.3 AVTP 的两种传输头部

Open1722 库支持两种 AVTP 传输格式,RCP 协议依赖它们:

头部类型 用途 AVTP Timestamp Stream ID 适用场景
NTSCF (Non-TSCF) 立即执行/响应 64-bit 标准请求、ACK、带时间戳的响应
TSCF (Time-SCF) 定时执行 32-bit 64-bit 定时请求(呈现时间控制)

NTSCF 头部结构(12 字节):

1
2
3
Byte 0     : subtype (0x82) + sv + version
Byte 1-7 : stream_id (64-bit)
Byte 8-11 : ntscf_data_length + sequence_num

ACF_GBB 消息头结构(16 字节,在 AVTP payload 中):

1
2
3
Byte 0-3   : ACF common (msg_type=0x0D, length)
Byte 4-11 : message_timestamp (48-bit, gPTP 同步)
Byte 12-15 : evt + transaction_num + op + err + byte_bus_id...

2.4 RCP 端点类型

RCP 定义了 13 种端点类型,对应不同的外设:

EP 编号 类型 说明
EP0 RC Server 配置、状态、发现(必须实现)
EP1 唤醒控制 休眠/唤醒(可选)
SPI SPI Controller 远程访问 SPI 外设
GPIO GPIO 数字输入输出
I²C I²C Controller I²C 总线访问
UART UART 串口通信
ADC ADC 模数转换读取
CAN CAN Controller CAN 总线
PWM_OUT/IN PWM 脉宽调制
LIN LIN Commander LIN 总线
MDIO MDIO PHY 管理

三、UDP/IP vs RCP/AVTP:核心对比

3.1 协议栈复杂度

维度 UDP/IP 方案(现有) RCP/AVTP 方案(目标)
协议层数 5层 (App→UDP→IP→Eth→TC6) 3层 (App→AVTP→TC6)
最大 Flash 节省 基准 ~45KB(去掉 lwIP)
最大 RAM 节省 基准 ~15-20KB
EtherType 0x0800 (IPv4) 0x22F0 (AVTP)
需要 IP 地址 否(纯 MAC 寻址)
需要 ARP
需要 DHCP 是(通常)
网络发现 ARP/NDP RCP 发现协议(EP0)
时间同步 NTP(可选) gPTP(可选,但 RCP 推荐)
路由支持 是(IP 层) 否(纯广播/单播 MAC)

3.2 性能与资源

指标 UDP/IP RCP/AVTP
最大包长 1472B (UDP) 受 MTU 限制,典型 1500B
协议开销 IP(20)+UDP(8)=28B AVTP(12)+ACF(16~)≈28B+
网络发现延迟 ARP ~1s RCP 发现更快
确定性 尽力而为 可通过 TSCF + gPTP 实现确定性
多节点访问 Socket 绑定端口 byte_bus_id 端点寻址

3.3 场景适配性评估

场景 UDP/IP RCP/AVTP 推荐
通用数据通信 ⚠️ UDP/IP
高速流媒体 ⚠️ AVTP
远程外设控制(SPI/GPIO/I2C) RCP
低资源嵌入式节点 ⚠️ RCP
需要路由的多子网 UDP/IP
汽车 ECU 远程控制 RCP

结论:用户提到”基于底层 MACPHY 数字电路,做网络包处理”——这正是 RCP/AVTP 的设计目标。去掉 lwIP 是合理的,节省 ~45KB Flash。


四、开源库嫁接方案

4.1 库文件映射

现有库依赖两个开源项目,可以与 RCP/AVTP 栈组合:

1
2
3
4
5
开源库                    作用                保留?
─────────────────────────────────────────────────────
oa-tc6-lib (Microchip) SPI + TC6 协议 ✅ 必须保留
Open1722 (COVESA) AVTP/ACF 编解码 ✅ 核心依赖
lwIP TCP/IP 协议栈 ❌ 去掉 (可选)

4.2 Open1722 库关键模块

Open1722 提供了完整的 IEEE 1722 实现,文件组织如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Open1722/
├── include/avtp/
│ ├── Defines.h ← 常量定义 (AVTP_QUADLET_SIZE=4)
│ ├── CommonHeader.h ← 通用 AVTP 头部解析 (subtype/ver)
│ ├── Utils.h ← 字段打包/解包工具
│ ├── Udp.h ← AVTP over UDP (可选)
│ ├── acf/
│ │ ├── AcfCommon.h ← ACF 通用头部 (AVTP_ACF_TYPE_BYTE_BUS=0x0D)
│ │ ├── Gbb.h ← ACF_GBB 格式 (RCP 核心)
│ │ ├── Ntscf.h ← NTSCF 头部 (RCP 必需)
│ │ └── Tscf.h ← TSCF 头部 (RCP 可选)
│ ├── acf/custom/ ← 厂商自定义 ACF 类型
│ ├── aaf/ ← 音频格式 (AVTP Audio Format)
│ ├── cvf/ ← 视频格式 (Compressed Video Format)
│ └── crf/ ← 时钟参考格式 (Clock Reference Format)
├── src/avtp/
│ ├── CommonHeader.c ← 通用头部实现
│ ├── acf/
│ │ ├── Ntscf.c ← NTSCF 验证逻辑
│ │ └── Tscf.c ← TSCF 验证逻辑
│ └── cvf/aaf/... ← 音视频编解码
└── unit/ ← 单元测试

关键发现

  • AVTP_ACF_TYPE_BYTE_BUS = 0x0D 是 RCP 的 ACF 消息类型
  • Avtp_Gbb_* 函数族提供 ACF_GBB 格式的完整解析
  • Avtp_Ntscf_* 函数族提供 NTSCF 头部的完整解析
  • 全部是 header-only 内联函数,无需额外编译

4.3 RCP 消息构建示例

基于 Open1722 + oa-tc6-lib,RCP 请求的构建流程:

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
#include "avtp/CommonHeader.h"
#include "avtp/acf/AcfCommon.h"
#include "avtp/acf/Gbb.h"
#include "avtp/acf/Ntscf.h"

// RCP 帧缓冲 (Ethernet MTU = 1500B)
uint8_t rcp_frame[1500];

// 1. 构建 NTSCF 头部 (12 bytes)
Avtp_Ntscf_t* ntscf = (Avtp_Ntscf_t*)rcp_frame;
Avtp_Ntscf_Init(ntscf);
Avtp_Ntscf_SetStreamId(ntscf, 0xAABBCCDDEEFF0011ULL); // 64-bit stream ID
Avtp_Ntscf_SetSequenceNum(ntscf, seq++);

// 2. 构建 ACF_GBB 消息 (16 bytes header + payload)
Avtp_Gbb_t* gbb = (Avtp_Gbb_t*)(rcp_frame + AVTP_NTSCF_HEADER_LEN);
Avtp_Gbb_Init(gbb);
Avtp_Gbb_SetByteBusId(gbb, 0x001); // EP1 = SPI Controller
Avtp_Gbb_SetTransactionNum(gbb, 0x5A); // 事务号(用于匹配响应)
Avtp_Gbb_SetOp(gbb, true); // true=Write, false=Read
Avtp_Gbb_SetMtv(gbb, false); // 无 message_timestamp

// 3. 设置 payload (SPI 写操作示例)
uint8_t* payload = rcp_frame + AVTP_NTSCF_HEADER_LEN + AVTP_GBB_HEADER_LEN;
uint16_t payload_len = 4; // 4 bytes SPI data
memcpy(payload, spi_data, payload_len);

// 4. 设置长度
Avtp_Gbb_SetPayloadLen(gbb, payload_len);
uint16_t total_len = AVTP_NTSCF_HEADER_LEN + AVTP_GBB_HEADER_LEN + payload_len;
Avtp_Ntscf_SetNtscfDataLength(ntscf, total_len);

// 5. 添加 Ethernet 头部 (14B) 并通过 TC6 发送
// Ethernet DA(6B) + SA(6B) + EtherType(2B=0x22F0)
// ...
// TC6_SendRawEthernetPacket(tc6, rcp_frame, ETH_HEADER + total_len, ...);

五、架构重新设计方案

5.1 目标架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────┐
│ Arduino Sketch (应用层) │
│ RCP Client API / AVTP Talker/Listener │
├─────────────────────────────────────────┤
│ RCP Client (新开发) │ ← ~8KB
│ ACF_ABB/ACF_GBB 消息编解码 │
├─────────────────────────────────────────┤
│ Open1722 AVTP 协议栈 │ ← header-only, ~5KB
│ NTSCF/TSCF 头部 + CommonHeader │
├─────────────────────────────────────────┤
│ TC6_Arduino_10BASE_T1S (复用) │ ← 复用现有代码
│ Ethernet Frame ↔ TC6 Data Chunk │
├─────────────────────────────────────────┤
│ libtc6 (oa-tc6-lib) │ ← 必须保留
│ SPI Transaction + OA TC6 状态机 │
├─────────────────────────────────────────┤
│ TC6_Io (SPI HAL 适配 RA4M1) │ ← 修改 SPI 参数
└─────────────────────────────────────────┘

节省:去掉 lwIP (45KB) + UDP 包装层 (8KB) = ~53KB

5.2 模块保留/修改/新增矩阵

模块 动作 原因
libtc6/ 保留 TC6 协议状态机是 LAN8650 通信基础
TC6_Io.cpp 修改 SPI 速率可能调整(TC6 最高 25MHz),但基本架构不变
TC6_Arduino_10BASE_T1S.cpp 大幅修改 去掉 lwIP 绑定,换接 RCP/AVTP 栈
Arduino_10BASE_T1S_UDP.cpp 删除/替换 替换为 RCP Client API
lwIP/ 删除/禁用 不再需要,节省 ~45KB
Open1722/ 新增 Open1722/include/avtp/ 引入
RCP Client/ 新增 RCP 协议状态机、消息编解码、端点管理
AVTP Listener/ 新增 接收 RCP 响应帧,路由到 RCP Client

5.3 数据流对比

发送路径(去掉 lwIP 后):

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
Arduino Sketch

│ RCP_Client_SendRequest(byte_bus_id, op, data, len)

RCP Client 状态机
│ 分配 transaction_num,构建 ACF_GBB

Open1722 AVTP 栈
│ 封装 NTSCF 头部,设置 stream_id

Ethernet 帧 (EtherType=0x22F0)
│ DA + SA + 0x22F0 + NTSCF + ACF_GBB + payload

TC6_Arduino_10BASE_T1S
│ 复用:填充 Ethernet 帧到 TC6 Frame Buffer

libtc6 (oa-tc6-lib)
│ 复用:TC6_DataTx → SPI 控制序列

TC6_Io (SPI HAL)
│ 复用:renesas_spi_transfer()

LAN8650 MAC-PHY
│ 10BASE-T1S 总线传输 (PLCA 总线仲裁)

远端 RC Server (Edge Node)

接收路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LAN8650 MAC-PHY
│ SPI IRQ 触发,从 TC6 RX FIFO 读取

libtc6
│ TC6_DataRx → process_rx() → TC6_CB_OnRxEthernetSlice

TC6_Arduino_10BASE_T1S
│ 接收完整 Ethernet 帧

Open1722 AVTP 栈
│ Avtp_Ntscf_IsValid() → NTSCF 解析
│ 检查 subtype = 0x82

RCP Client 状态机
│ 匹配 transaction_num
│ Avtp_Gbb_GetEvt() → 0xF(Ack)/0x0(Response)/0x1-8(Error)
│ Avtp_Gbb_GetPayloadLen() → 读回数据

Arduino Sketch
│ RCP_Client_OnResponse(transaction_num, data, len)

应用处理

六、移植修改详细计划

阶段 1:基础通信验证(保留 lwIP,逐步替换)

  1. 引入 Open1722

    • Open1722/include/avtp/ 复制到 src/lib/open1722/include/
    • 验证编译:AVTP 头部解析(Avtp_Ntscf_Init, Avtp_Gbb_Init
  2. 创建 AVTP 帧收发测试

    • 利用现有 TC6_CB_OnRxEthernetPacket 回调
    • TC6_Arduino_10BASE_T1S 层添加 EtherType 过滤
    • 当 EtherType = 0x22F0 时,走 AVTP 路径;否则走 lwIP 路径
  3. 验证 LAN8650 对 0x22F0 帧的收发

    • LAN8650 硬件支持 EtherType = 0x22F0(AVTP 标准)
    • 通过 TC6Regs_Init 配置 MAC 接收过滤器

阶段 2:RCP Client 实现

  1. 实现 RCP Client 状态机

    • 状态:Idle → Request_Sent → Awaiting_Response → Response_Rx
    • transaction_num 分配器(8-bit,0-255)
    • 请求存储队列(支持多事务并发)
  2. 实现 RCP 请求类型

    • 标准请求(ACF_ABB):写 SPI/GPIO
    • 标准请求(ACF_ABB):读 SPI/GPIO
    • ACK 响应处理(evt = 0xF)
    • 数据响应处理(evt = 0x0,op = 0)
  3. gPTP 时间戳集成(可选)

    • LAN8650 内置 gPTP 硬件时间戳(通过 TC6 寄存器访问)
    • TSC (Transmit Timestamp Capture) 字段在 TC6 头部

阶段 3:lwIP 逐步退出

  1. 创建 RCP API 替代层

    • RCP_Client.begin()
    • RCP_Client.writeRegister(byte_bus_id, reg_addr, value)
    • RCP_Client.readRegister(byte_bus_id, reg_addr, callback)
    • RCP_Client.setStreamId(uint64_t stream_id)
  2. 替换 Arduino_10BASE_T1S_UDP

    • 保留 Arduino_10BASE_T1S 主类接口风格
    • beginUDP(port) 替换为 beginRCP(stream_id)
    • 测试现有 sketch 示例的兼容迁移

阶段 4:完整验证

  1. 与远端 RC Server 通信验证

    • 使用开源 RCP Server 实现(如有)或模拟 PC 端
    • 抓包验证:EtherType=0x22F0,ACF_GBB 格式正确
  2. 性能与资源测量

    • Flash/RAM 占用对比(去掉 lwIP 前 vs 后)
    • 吞吐量测试(UDP pingpong vs RCP 请求响应延迟)

七、关键风险与注意事项

7.1 gPTP 时间同步

RCP 的复合请求(定时执行)和 TSCF 头部依赖 gPTP 同步:

  • 需要:LAN8650 内置 gPTP 支持,或在 MCU 侧实现软件 gPTP
  • 现状:现有库没有 gPTP 实现
  • 建议:先从 NTSCF(无时间戳)开始,等通信稳定后再加 gPTP

7.2 RC Server 端

RCP 是客户端-服务器协议,需要有对应的 RC Server 端点:

  • 如果目标是与现有设备通信:确认对端是否支持 RCP
  • 如果是自研 RC Server:需要独立实现(或使用 COVESA 社区参考实现)
  • 注意:Open1722 库只提供 AVTP 层的编解码,不包含 RCP Server 实现

7.3 EtherType 配置

LAN8650 默认可能只接收特定 EtherType 的帧:

  • 需要通过 TC6 寄存器配置 MAC 接收过滤器
  • TC6Regs_Init 中的 promiscuous 参数控制是否开启混杂模式

7.4 PLCA vs CSMA/CD

10BASE-T1S 使用 PLCA(Physical Layer Collision Avoidance)作为总线仲裁机制:

  • RCP 请求-响应模式天然适配 PLCA 的单节点发送特性
  • 与 lwIP 的广播/多播场景不同,RCP 倾向于点对点事务

7.5 Flash 预算

去掉 lwIP 后的预期 Flash 占用:

模块 估算 Flash
Arduino Core ~30KB
FSP HAL ~12KB
libtc6 ~15KB
Open1722 ~5KB
TC6 HAL 封装 ~5KB
RCP Client ~8KB
总计 ~75KB(节省 ~35KB)

八、总结

8.1 架构决策

决策点 选择 理由
协议栈 RCP/AVTP Layer 2 去掉 lwIP,节省 ~45KB,符合”网络包处理”定位
传输层 NTSCF(非时间同步) 最简实现,先跑通再优化
消息格式 ACF_GBB 兼容 ABB,支持 byte_bus_id 端点寻址
时间戳 暂不使用 先跳过 gPTP 依赖
lwIP 条件移除 提供条件编译开关,保留调试能力

8.2 下一步行动

  1. 立即执行:将 Open1722/include/avtp/ 集成到项目 src/lib/open1722/
  2. 第一个里程碑:实现 AVTP NTSCF 帧的收发解析,验证 EtherType = 0x22F0 在 LAN8650 上正常工作
  3. 第二个里程碑:实现 RCP Client 状态机和 ACF_GBB 编解码
  4. 第三个里程碑:替换 Arduino_10BASE_T1S_UDP,保留 Arduino_10BASE_T1S 主类 API

8.3 参考资源


本文基于 Arduino 10BASE-T1S 项目现有上下文数据库、oa-tc6-lib v3.1.6 源码、Open1722 库源码、RCP 协议规范 v0.4.7 rev1 和 IEEE 1722-2025 标准综合分析。协议细节以官方规范为准。