1. 系统为什么这样搭:FPGA负责采集,CH569负责送数
先说结论:这行当里做高速数据采集,方案看着五花八门,但归根到底就是在"数据从模拟前端进来到PC端看到波形"这条链路上做取舍。我之所以把FPGA和CH569拼在一起,核心原因只有一句话——CH569把USB3.0的PHY和控制器都集成进了芯片内部,且内嵌了一个RISC-V内核,这让USB3.0部分变成了一个"可用MCU思维操作的外设",而不是一个需要复杂驱动和协议栈的黑洞。
1.1 数据采集场景对总线的真实需求
采集系统到底需要多快的传输速度?这不是拍脑袋定的,得从信号链源头倒推。我用一个非常常见的场景:双通道、12位ADC、采样率50MSPS。
每秒产生的数据量:
单通道数据率 = 50M × 12bit = 600Mbps
双通道 = 1.2Gbps = 150MB/s
这意味着什么?如果只靠单通道USB2.0理论480Mbps(实际有效也就40MB/s上下),铁定不够,连1/3都跑不满。想要把150MB/s的数据流实时甩到PC端,USB3.0几乎是消费级接口唯一的选择。
CH569这颗芯片的USB3.0是SuperSpeed 5Gbps的物理层,理论值虽然被协议开销吃掉一部分,但实测批量传输做到350MB/s~380MB/s是稳的,峰值看过有人跑接近400MB/s。注意这里不是理论带宽,是扣除USB协议开销后能实际灌进上位机的有效速率。
1.2 为什么是CH569,而不是别的组合
做USB3.0高速传输,市面上还有其他路线:FPGA直接接USB3.0 PHY芯片(比如TUSB1310)、用带USB3.0硬核的FPGA(比如部分Intel Agilex或者Xilinx的Versal)、或者再挂一颗带USB3.0的高端MCU/MPU。但这些方案有一个共性——贵、难、布板费劲。
CH569方案直接把三个东西揉在一颗芯片里:
- RISC-V内核(240MHz主频)
- USB3.0设备控制器加PHY(SS 5Gbps)
- 内置DMA、FIFO、SPI、UART等接口
也就是说,FPGA只需要通过一组并口(我用的16位或32位并行总线)把数据灌给CH569,CH569内部自己完成打包、DMA搬移、USB协议处理,最后从Type-C口出去。工程量瞬间从"驱动一个USB3.0 PHY芯片"降级成"操作一组FIFO读写寄存器"。
这个选型逻辑本质上是在物理层复杂度和系统灵活性之间找平衡。USB3.0的PHY有大量模拟电路,包括时钟恢复、均衡器、LFPS检测、SSC扩频时钟,这些自己用分立方案做,布线和阻抗控制稍有不慎就枚举失败。CH569把这些全集成进去,硬件上你只需要处理USB线的差分对布线,JTAG调试口,以及3.3V和1.2V电源,开发难度直接降了一个量级。
2. 硬件架构与设计细节:板级工程师的视角
硬件设计是整个系统里最容易"看着简单但翻车隐蔽"的部分。USB3.0的差分线一旦阻抗、长度匹配、参考层处理不好,轻则速率上不去,重则设备无法枚举。这一章我把整块板子的架构和关键设计约束摊开来讲。
2.1 系统硬件框图
整块板子的核心链路可以分成四段:
- 模拟输入或数字输入:根据场景选择,可以是高速ADC(比如AD9226、ADS42LB69之类),也可以是LVDS/CMOS数字信号直接进FPGA。
- FPGA处理单元:完成采集控制、数据预处理(滤波、抽取、FFT等)、数据打包,然后通过并行总线把数据推给CH569。
- CH569桥接:负责将FPGA送过来的并行数据经内部FIFO缓冲,DMA搬运到USB3.0设备控制器,最终通过Type-C口输出。
- 上位机PC:接收USB3.0数据流,解析帧格式,显示波形/频谱或落盘存储。
我当时做的最初版,FPGA用的是Intel Cyclone 10 GX(后来换成了国产的紫光同创),CH569不带PHY那种外置方案,直接用的Type-C连接器。
2.2 CH569与FPGA之间的总线设计
FPGA与CH569的数据通路是整个桥接设计中最关键的部分。CH569对外提供一组并行总线接口,你可以在SDK里把它配置成类似FIFO读写模式,也可以配成带地址总线的寄存器读写模式。
我选的是FIFO模式。好处是FPGA侧逻辑非常干净——只需要关注两个信号方向:写请求(WR)和写数据(DATA),CH569内部有硬件FIFO做缓冲,数据不会因为USB总线繁忙而丢失(前提是FIFO深度够)。
引脚分配上要留意的点:并行数据总线最好使用FPGA的Bank内连续引脚,保证输出延迟一致性。我一开始为了布线方便,把数据线拆到了两个Bank,结果FPGA I/O的输出Tco不一致,在200MHz以上的并口速率下直接导致数据采样错误。后来规规矩矩用了一个Bank的16根IO,问题消失。
时钟方面,CH569的并行总线时钟我用了50MHz起步,后期优化到100MHz。以16位位宽计算,50MHz×16bit = 100MB/s,100MHz就是200MB/s,足够覆盖大部分采集需求。
2.3 电源与地平面处理:容易被忽视的翻车点
USB3.0的PHY对电源噪声极度敏感,尤其是PLL和高速SerDes部分的供电。CH569内部虽然集成了LDO,但外部输入电源的纹波如果超标,SS信号的眼图就会劣化,直接影响链路稳定性。
我的做法是:
- 3.3V系统电源先用一片低噪声LDO(比如LT3042类)稳压,禁止直接用DC-DC输出靠近模拟/USB部分
- 1.2V核心电压由3.3V经由稳压IC转换,并且滤波电容矩阵要靠近芯片电源引脚
- 地平面在CH569下方尽量完整,不要在芯片正下方走任何信号线,尤其是不要穿过USB差分对下方
USB3.0差分对(SSTX+/-、SSRX+/-)布线我用了100Ω差分阻抗控制,等长控制在5mil内,总长度不超过2英寸。这里插一句:USB3.0的SS差分对不像USB2.0的D+/D-那样可以容忍较长走线,5Gbps速率下过长的stub或者过孔阻抗不连续都会导致链路训练失败。如果板子空间允许,尽量做在表层,减少过孔换层。
3. RISC-V核侧:CH569的USB3.0固件开发要点
CH569最大的"反直觉"之处在于:它虽然是颗MCU,但内部USB3.0控制器是有硬件DMA的,所以固件层面主要工作并不是逐字节搬运数据,而是配置描述符、管理DMA缓冲区和处理端点事件。固件写得合理的话,CPU占用率可以做到非常低,大部分时间在休眠等待中断。
3.1 开发环境与SDK理解
CH569用沁恒自家的MounRiver Studio(基于Eclipse的IDE)或者直接用CMake工具链。芯片是RISC-V内核,指令集是基于RV32IMAC的变体,支持硬件乘法除法。SDK提供了USB3.0设备相关的库函数,包括设备枚举、端点配置、DMA描述符管理等。
刚开始用这套SDK时我走了一些弯路:SDK里的例程大多是"回环测试"或"按键控制"这类,真正做数据流传输的参考代码不多。所以如果你直接拿例程改,很容易被"USB发一次数据就进中断处理一次"这种思路带偏,导致传输速率上不去。正确做法是使用DMA模式+多缓冲乒乓结构,让硬件自己搬运数据,CPU只在缓冲区满了的时候做指针翻转。
3.2 批量传输端点与DMA配置
USB3.0的Bulk端点(批量传输端点)是数据采集类设备的标配,因为Bulk传输在USB3.0下带宽可以做到接近2.5Gbps以上(相对于USB2.0的Bulk传输只有几十MB/s有质的飞跃),而且是可靠传输,错了重传,不会出现数据损坏。
CH569的端点配置代码大致如下(以SDK接口为例):
// 配置端点6为BULK IN,用于数据上行 USBFS_Device_EndPoint_Config(EP6, USB_TRANSFER_TYPE_BULK, USB_DIR_IN, 1024);这里有个关键参数:端点最大包长。USB3.0的Bulk端点最大包长度是1024字节(USB2.0是512字节)。理论上每个包越大,USB协议开销占比越低,吞吐率越高。CH569支持最大包长1024字节,正好。
DMA描述符配置:
// 配置DMA描述符,将内部FIFO数据搬移到端点的DMA缓冲区 DMA_Descriptor_Init(DMA_CH0, (uint32_t)ep6_buffer, 1024 * 16, DMA_DIR_MEM2MEM);注意我要强调:DMA缓冲区的地址对齐非常关键。建议至少按照32字节对齐,最好按照64字节对齐。CH569的DMA控制器在搬运数据时,如果源地址或目的地址没有对齐,会有额外的总线周期开销,虽然不至于出错,但会影响吞吐率。实测下来,16字节对齐和64字节对齐在高速连续搬运时吞吐率差异可以到5%~8%。
3.3 固件主循环与中断处理
固件逻辑可以简化成三段:
- 初始化:时钟、USB控制器、端点、DMA描述符,然后开启USB设备(等待主机枚举)
- 主循环:等待事件标志位,处理控制传输(设备描述符请求等)以及DMA完成中断
- 中断处理:当DMA搬完一批数据,翻转缓冲区指针,重新启动DMA,同时把已满的缓冲区标记为"可被主机读取"
需要特别提的是,在USB3.0高速传输下,中断频率非常高。如果每个数据包都进一次中断,CPU会被连续打断到几乎无法做其他事情。我的做法是中断里只做标志位设置和缓冲区切换,不做任何数据拷贝,数据拷贝全部交给DMA。另外,中断服务函数里不要调用任何USB发送API,一切留在主循环处理。
伪代码示意:
volatile uint8_t dma_done_flag = 0; void DMA_IRQHandler(void) { // 翻转当前使用的缓冲区索引 current_buffer_index ^= 1; // 重新配置DMA指向另一个缓冲区 DMA_Config(current_buffer_index); dma_done_flag = 1; } int main(void) { SystemInit(); USB3_Init(); DMA_Init(); while(1) { if (dma_done_flag) { dma_done_flag = 0; // 告诉主机当前缓冲区数据有效 USB3_Send_Buffer(current_buffer_index); } } }这里有个坑:如果你在DMA中断里直接调用USB_Send,会发现偶尔出现数据错乱或者丢包。原因是DMA中断和USB发送逻辑之间有时序竞争——DMA刚完成搬运,但USB控制器的内部状态还没完全就绪,立刻触发发送可能会把未完全写入的数据发出去。改成主循环判断标志位后,这个问题消失。
4. FPGA侧逻辑设计与数据流控制
CH569再智能,也只是一个桥接芯片,真正决定数据长什么样的,是FPGA里的逻辑。FPGA侧的工作重心是:采集控制、数据组织、总线时序产生。这个部分设计得好不好,直接决定整个系统能跑多快、数据对不齐、上位机能不能正确解析。
4.1 采集侧设计:ADC时序与数据对齐
如果是接并口ADC,时序相对简单:ADC在采样时钟的上升沿输出数据,FPGA在下一个时钟沿抓取。但要注意的是,高速ADC的输出延迟tOD(Output Delay)并不是固定不变的,而是和采样时钟的相位有轻微抖动。为了保证裕量,我强烈建议在FPGA内部用IDELAY(I/O延时模块)或者整数时钟周期延迟链做一个动态校准。
以50MHz采样率为例,ADC输出数据和采样时钟相位偏差可能从0.5ns到3ns不等,直接用系统时钟抓取可能落在数据跳变沿处,导致采到错误数据。常见的处理办法是:先用一个固定相位偏移(比如90°)采样,然后跑一个简单的训练序列,程序自动调整IDELAY值,直到数据锁存稳定。
如果输入是LVDS接口的高速ADC(比如双通道、1GSPS的ADC12DJ3200这类),FPGA侧还要做串并转换(SerDes),这个复杂度和并口ADC完全不同,需要用到FPGA内部的ISERDESE2/BUFIO等原语。但CH569那侧接口速率上限摆在那里,这种级别的ADC前级不会直接接到CH569总线上,中间必须有降速/缓存环节。
4.2 跨时钟域处理与异步FIFO
采集域和发送域往往是两个时钟域:
- 采集域:由ADC采样时钟驱动,比如50MHz
- 发送域:由CH569并行总线时钟驱动,比如100MHz
两个时钟没有任何相位关系,直接传递数据必出亚稳态。所以中间必须加异步FIFO做缓冲,这是标准的跨时钟域处理结构。
FIFO深度选择是门学问:
FIFO深度(字节) > 突发传输长度 ×(写入速率/读出速率 - 1)举个例子:FPGA每1ms突发写入16KB数据,读出速率是写入速率的2倍,那么理论上FIFO只需要16KB就够。但USB3.0在主机侧有时会触发流控(主机忙、总线调度),导致CH569长时间不拉高读使能。这种情况下FIFO瞬间就会被塞满。所以我建议FIFO深度至少是最大突发数据量的4倍以上。我这里选了32KB(用FPGA内部Block RAM组合)。
异步FIFO设计注意点:读写指针要使用格雷码做跨时钟域同步,否则多比特指针在跨时钟域时会产生错误空满判断,轻则丢数据,重则读写出错。如果你用的是带FIFO IP核的FPGA(Vivado/Xilinx或Quartus/Intel),IP核内部已经处理了格雷码,但你自定义逻辑时一定要留意这一点。
4.3 发给CH569的并行总线时序
CH569的并行数据接口时序不是标准AHB或AXI,更像一个简化版的同步FIFO写接口。核心信号是:写时钟(FCLK)、写使能(FWR)、16位/32位数据总线。
时序要求关键在于建立保持时间。在100MHz时钟下,数据总线的建立时间要求大约是5ns~8ns,保持时间2ns左右。这个指标对FPGA的IO输出延迟提出了要求。
我在Quartus里做了如下约束:
set_output_delay -clock [get_clocks {fclk}] -max 6.0 [get_ports {data_out[*]}] set_output_delay -clock [get_clocks {fclk}] -min -1.0 [get_ports {data_out[*]}]如果你发现时序违例,第一反应不是降频,而是检查是不是把IO输出寄存器放在了逻辑单元(ALM/CLB)里而不是IOB(I/O Block)里。FPGA综合工具默认的IO寄存器的位置经常不是最优的,需要手动添加约束,强制综合器将输出寄存器放到IOB中,这样可以省掉从LE到IO的走线延迟,时序裕量会大幅提升。
4.4 数据帧格式设计:如何让上位机正确解析
数据流如果只是裸数据,上位机也能存盘,但想做实时显示、波形触发、数据回放等功能,就需要在FPGA侧对数据进行帧格式化。
我用的帧格式:
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头标识 | 2字节 | 固定值:0xAA55,用于同步检测 |
| 帧序号 | 4字节 | 自增计数,上位机用于检测丢帧 |
| 通道标识 | 2字节 | 表示数据来自哪个采集通道 |
| 采样点数 | 4字节 | 本帧有效数据长度 |
| 有效数据 | N字节 | 按通道顺序排列的采样数据 |
| CRC校验 | 2字节 | 对"帧序号+通道标识+采样点数+有效数据"做CRC16 |
为什么要加帧序号?因为USB虽然可靠传输,但主机程序和DMA缓冲机制可能导致乱序或丢帧。上位机检测到帧序号不连续,就可以确定丢帧发生,从而提示用户调整采样率或者增大缓冲。对于高速采集调试来说,这个功能几乎是必备的。
一点小提醒:帧头选取时,尽量选择不易和数据混淆的固定字节模式。0xAA55这种交替模式在随机数据中出现连续4字节匹配的概率非常低,基本可以安全使用。如果你想要更高的安全等级,可以用0xAA55AA55做4字节帧头,代价是每帧多2字节开销。
5. 上位机与联调实测:USB3.0链路真实表现
硬件、固件、FPGA逻辑都就位后,重头戏就来了——把整条链路跑起来,看它到底能不能达到设计指标。这个环节最能暴露问题,因为很多鲁棒性缺陷只有在连续跑大数据量时才显现。
5.1 上位机接收方案选型
USB3.0上位机通信主流方案有两条路线:
- WinUSB + libusb:免驱或使用WinUSB驱动,跨平台,基于批量传输,适合数据采集类设备
- 厂商驱动:写一个内核驱动,性能最好,但开发量大、签名麻烦、调试困难
对于项目开发,选libusb是性价比最高的,没有之一。libusb基于WinUSB(Windows)或libusbK,不需要自己写驱动,C/C++、Python、C#都能调。实测下来libusb在USB3.0批量传输上的性能损失很小,不太会造成瓶颈。
Python调试原型:
import usb.core import usb.util # 根据VID/PID找到设备 dev = usb.core.find(idVendor=0x1234, idProduct=0x5678) if dev is None: raise ValueError("Device not found") # 配置端点 dev.set_configuration() endpoint_in = 0x86 # 端点6, IN方向 # 持续读取数据 import time start = time.time() total_bytes = 0 while True: data = dev.read(endpoint_in, 16 * 1024, timeout=1000) total_bytes += len(data) elapsed = time.time() - start if elapsed >= 1: print(f"Throughput: {total_bytes / elapsed / 1e6:.2f} MB/s") total_bytes = 0 start = time.time()跑起来后,你会看到实时吞吐率。在CH569+FPGA 16位总线100MHz配置下,我的实测数据:
- 裸数据流(无帧格式,直接连续灌数据):约360MB/s
- 带帧格式(每帧头尾加26字节开销):约340MB/s
- 加上CRC计算开销:约330MB/s
这个数字已经超过了绝大多数中高速数据采集的需求(比如100MSPS×12bit双通道需要150MB/s,有接近2倍余量)。
5.2 带宽瓶颈分析:为什么不是USB3.0的理论5Gbps
很多人第一次测出来只有350MB/s,就会问:USB3.0不是5Gbps(约625MB/s)吗?怎么只剩一半多了?
几个层面解释:
- USB3.0总线是8b/10b编码的,5Gbps实际原始数据吞吐率是4Gbps(500MB/s)
- Bulk传输在USB3.0下每个突发最大16KB,产生一次突发需要发送一个NRDY/ERDY握手,存在调度开销
- PC侧PCIe总线、USB控制器驱动、操作系统调度也会吃一部分性能
- libusb的读取模式:如果每次read是固定16KB,用户态和内核态的切换开销会杀掉不少吞吐率
实测350MB/s是个非常健康的数据,意味着协议层、驱动层、设备固件都工作正常。如果掉到100MB/s以下,先别怪芯片,大概率是某个环节有阻塞——最常见的就是固件里中断处理太频繁,或者FPGA并行总线数据速率太低。
5.3 稳定性压测与丢帧检测
做完吞吐率测试,必须做稳定性测试。我的方法是:FPGA侧生成伪随机数据并插入递增帧序号,上位机循环读取24小时以上,同时校验帧序号连续性和CRC。
第一次压测我就抓到了问题:运行约40分钟后开始出现CRC错误包,但帧序号连续——这说明数据是对的,但传输过程中有bit翻转。排查后定位到是USB差分线靠近一个开关电源,电磁干扰导致偶发误码。USB协议自带的CRC会检测到这个错误并触发重传,所以最后到达PC端的数据不会错,但重传会悄悄吃掉带宽。
解决办法:重新布局,将USB差分线远离电感类器件;另外在Type-C接口的电源引脚附近加共模电感。改版后连续跑48小时,0 CRC错误,吞吐率保持不变。
如果你发现CRC错误频发且无法通过布局解决,可以检查CH569的USB PHY配置寄存器里是否启用了SSC(扩频时钟)。SSC设计用于降低EMI,但在某些板子上会略微恶化接收端眼图,关闭SSC可以让高速传输更稳。
6. 踩坑记录:列举几个浪费我大量时间的真问题
这一章相当于项目的隐性附件,是我在完整调试链路中遇到的最难啃的几块骨头。如果你也在做类似的项目,照着排查,能省下不少冤枉时间。
6.1 USB3.0设备"假成功"枚举问题
表现为:Windows设备管理器里能看到设备,但显示为"未知USB设备(设备描述符请求失败)",或者有时能识别但传输大量数据时设备掉线。
我第一次遇到时以为是驱动问题,重装驱动无果,换了三台电脑依然复现。后来用逻辑分析仪抓USB数据线才发现,CH569在上电后第一次枚举时发出了错误的设备描述符长度——这是固件初始化的时序问题:设备描述符缓冲区还没完全填充,USB控制器就已经响应了主机的GET_DESCRIPTOR请求。
解决办法:在USB设备使能之前,确保设备描述符数据结构已经在内存中就位,并且设置一个全局标志,只有初始化完成后才允许USB控制器响应总线事件。CH569的SDK里给了USB3_Dev_Init()这类函数,但需要确认它在调用时是否已经将描述符指针指向了正确的内存地址。
另外一个非常隐蔽的坑:CH569的USB3.0 PHY在上电后需要预热时间(PLL锁定时间)。如果MCU启动后立刻使能USB设备,PHY还没锁定时所有高速信号都会失败。SDK例程里对此有延时处理,但如果你裁剪了启动代码,一定要保留至少10ms的PHY稳定延时。
6.2 DMA缓冲区地址对齐导致的吞吐率"卡一半"
系统刚调通时,USB枚举正常、数据传输也正常,但速率死活只有180MB/s,换了好几台电脑都一样。当时误以为是FPGA总线瓶颈,花了两天优化FPGA时序,毫无改善。
最后静下心翻CH569的数据手册,发现DMA连接的内存区域如果地址不是64字节对齐,DMA控制器会退化为非突发模式,每个总线周期只搬运少量数据,严重降低吞吐率。
检查代码后确认:我在声明DMA缓冲区时使用了普通的全局数组,编译器把它放在了一个未对齐的地址上。改成以下写法后,速率直接翻倍到360MB/s:
__attribute__((aligned(64))) uint8_t ep6_buffer[16 * 1024];这件事的教训是:跑高速数据传输时,先查对齐,再查时序。DMA对齐的问题在低速场景完全无感,但一上高速立刻就是致命的。
6.3 FPGA侧跨时钟域FIFO的空满信号毛刺
调试时发现偶发数据错位——不是丢帧,而是同一帧内前一段数据出现错位,后一段又恢复。用ILA(集成逻辑分析仪)抓FPGA内部信号,发现异步FIFO的Empty信号偶尔会比实际FIFO状态早一个时钟周期拉高。
原因分析:异步FIFO的读指针同步到写时钟域、写指针同步到读时钟域本来就是拍一拍再判断的,所以空满信号天生有一个时钟周期的抖动。这个抖动在大部分场景可以忍受,但如果在FIFO为空的瞬间读取数据,读出的值就是未定义的(可能是旧数据,也可能是高阻态)。
我的处理方案:在读取逻辑里加一个空存保护状态机——只在FIFO有效数据量大于一定阈值(比如超过16个深度)时才允许读出,避免在空边界反复跳变。同时将FIFO的读使能信号打两拍后再用,滤掉亚稳态。这些改动看起来微小,但对数据可靠性的提升立竿见影。
6.4 上位机读取线程卡死问题
libusb的同步读取接口在设备意外断连时,会一直阻塞或者抛异常,处理不好程序直接卡死。批量传输还有一个坑:如果你设置了超时时间(比如1000ms),当USB总线繁忙或设备响应慢时,read调用会直接超时返回,代码里如果不区分"超时"和"数据错误",就会把正常的数据流误判为异常。
我的处理建议:上位机读取用独立的接收线程,超时返回时只是记录一次计数,而不是退出循环;只有在持续多次超时或者收到具体的USB错误码(比如LIBUSB_ERROR_NO_DEVICE)时,才判定设备断连并做重连处理。
7. 从这套系统能学到的通用方法论
写完这套系统,我想多说几句这套架构能复用到其他场景的通用方法论——因为很多人问"CH569和FPGA这个组合除了数据采集还能干嘛",其实它的可迁移性比想象中广得多。
第一个可复用点是**"FPGA做前端处理、MCU做协议桥接"的分工思想**。FPGA擅长并行、低延迟、确定性的数据搬运和预处理,MCU擅长复杂的协议栈和状态管理,两者天然互补。这套思想适用于非常多场景:高速图像采集(FPGA做去马赛克、降噪、格式转换,CH569做USB3.0图像传输)、软件无线电(FPGA做DDC/DUC上下变频,CH569做基带数据上传)、汽车总线数据记录仪(FPGA采集多路CAN/LIN数据,CH569通过USB3.0灌给PC上位机做分析)。
第二个可复用点是调试方法论。整个系统调通后,我复盘发现最大的问题往往不是某个单一模块不会写,而是各个模块之间的"接口缝隙"——FPGA和CH569之间的总线时序、CH569和USB主机之间的协议握手、USB主机和上位机之间的驱动交互,每一个接缝都可能成为吞噬性能或稳定性的黑洞。所以做这类项目,前期画好接口时序图,把每一段的时序约定明确写出来,比急着写代码重要得多。我自己当时的接口文档改了六版,每一版都发现了一些前期没想到的时序边界情况。
第三个可复用点是数据帧格式设计。不管传输的是什么类型的数据,帧头+帧序号+通道标识+长度+CRC这套结构几乎是万能的。我后来做图像数据传输时,只把"有效数据"字段改成了图像行数据,上位机解析逻辑稍微调整就能直接工作。先把基础传输管道做扎实,再在上层做数据类型的变化,是这套系统留给我最大的工程收益。