1. 项目背景与方案选型:为什么要做这块“伺服内嵌小板”
这几年伺服驱动器的通信接口越来越卷,客户动不动就问“支不支持 EtherNet/IP”“能不能直接对接 AB 的 PLC”。但真正把一套完整 EtherNet/IP 协议栈跑进伺服主控芯片里,成本并不低——主控算力、内存开销、协议栈授权费,都是实打实的压力。于是“内嵌小板”这种架构就流行起来了:伺服主板上插一块嵌入式通信小板,由小板负责 EtherNet/IP 协议解析,再通过 SPI 接口把控制字、状态字、目标位置、实际速度这些数据快速“喂”给伺服主控。
我这次拿到的任务,就是给一款伺服内嵌 EtherNet/IP 方案做适配改造,具体工作是:基于厂商提供的嵌入式小板 SPI 通讯固件 Demo 程序,把它从默认的“演示模式”改造成能匹配我们伺服主控寄存器地址、数据长度和刷新节奏的正式通讯固件。整个过程听起来很简单,但真正动手之后才发现,SPI 通讯的时序匹配、数据帧格式协商、掉线重连机制,每一个环节都能让你加班到深夜。
这个方案适合谁参考?如果你手头也在做伺服、变频器、机器人控制器之类的产品,正好需要给设备加工业以太网接口,又不想把协议栈塞进主控,那么“嵌入式小板 + SPI 桥接”这条路非常值得了解。即使你用的是 EtherCAT、PROFINET、Modbus TCP 等其他协议,只要方案架构是“协处理器小板 + SPI”模式,这篇博文的适配思路和调试方法照样能复用。文章里我会把项目从头到尾的拆解过程、Demo 程序的关键代码路径、以及我踩过的坑全部摊开来讲,希望能帮你少走几个月的弯路。
2. 整体架构与设计思路:SPI 为什么是这块小板的最优解
2.1 内嵌小板的硬件角色与通信链路
先把这个方案里各个角色的定位说清楚。伺服驱动器主控板负责电机控制算法,电流环、速度环、位置环,全都是微秒级实时任务;嵌入式通信小板负责 EtherNet/IP 协议处理,它有一颗独立的 MCU 或带协议栈的专用芯片,外接以太网 PHY。两块板子之间不走并行总线,也不走 UART,而是走 SPI。
为什么选 SPI?这个选择背后是硬核考量。首先 SPI 是全双工同步通信,适合周期性交换定长数据帧;其次它的速率上限高,常见配置下能到 10MHz 甚至更高,对于 EtherNet/IP 这种典型 1ms 到 10ms 的 RPI(请求包间隔)周期来说,SPI 的数据吞吐绰绰有余。再者,SPI 从机模式在小板侧实现简单,不需要像以太网 MAC 那样占用大量资源,裁剪掉协议栈之外的开销,主控侧用硬件 SPI 外设或者 GPIO 模拟都能对接。
我在项目里用的就是“主控做 SPI 主机、小板做 SPI 从机”的标准结构。伺服主控按固定周期(比如 1ms)发起一次 SPI 传输,一次传输包含下行数据(16 字节控制字)和上行数据(16 字节状态字)。这块小板和主控之间还需要额外的握手信号线,用来通知主控“EtherNet/IP 连接已建立”或“连接丢失”,不然主控还得傻乎乎地每个周期去解析状态字里的连接位。
2.2 Demo 程序为什么要“适配改造”而不重写
很多工程师拿到 Demo 的第一反应是“这代码能跑就行,直接改改参数就上”。但 Demo 程序的定位是给芯片原厂做演示验证的,它默认的数据结构、数据内容、刷新策略,都是针对原厂自己的评估板设计的,直接搬到我们的伺服主控上根本没法用。
这次要适配的内容集中在三个层面:第一是数据帧格式层面——Demo 默认帧里可能包含协议状态字、版本号、调试计数器这些字段,我们不需要;我们需要的是实际控制字和状态字,两者帧长不一样,甚至字节序都可能不一样。第二是寄存器映射层面——EtherNet/IP 侧的 Assembly 对象(就是 IO 数据映射表)要和伺服主控的寄存器地址一一对应,Demo 里没有这一层映射,它只是在内存里固定位置塞了几组测试数据。第三是交互策略层面——Demo 程序通常用定时器中断或轮询方式处理 SPI 请求,但这套逻辑要适配伺服主控的“周期+事件”混合模式,既要保证正常周期正常刷新,又要能快速上报报警和故障代码。
如果选择重写,代码量其实不小,而且必须重新做协议栈和 SPI 驱动之间的数据流设计,风险远大于收益。适配改造的目的,是把 Demo 中已经验证过的 SPI 从机时序逻辑、DMA 搬运、中断处理这些底层实现完整保留下来,只替换掉上层的协议语义和寄存器映射关系,这才是最稳妥的工程路径。
3. 核心细节解析:SPI 通讯固件的关键实现要点
3.1 SPI 参数协商与时序要求
先把最底层的事情做对:主控侧和小板侧的 SPI 参数必须完全对齐,否则全双工模式下收上来的数据全是错的。这里有几个参数需要重点确认。
表:SPI 参数协商清单
| 参数项 | Demo 默认值 | 本项目最终配置 | 注意事项 |
|---|---|---|---|
| 通信速率 | 1 MHz | 4 MHz | 速率受 PCB 走线和电平转换芯片限制,别盲目拉高 |
| 时钟极性 CPOL | 0 | 0 | 与从机采样边沿严格匹配 |
| 时钟相位 CPHA | 1 | 1 | 配置错误会导致采样点错位,数据全乱 |
| 数据位宽 | 8 bit | 16 bit | 按帧格式协定,改位宽要同步改从机侧 |
| MSB/LSB | MSB First | MSB First | 别小看,字节序错了排查大半天 |
| CS 片选模式 | 硬件自动 | 硬件自动 | 确保 CS 低电平有效,释放高电平复位从机时序 |
Demo 程序里最重要的一个底层逻辑是 SPI 从机的“帧同步机制”。主控通过硬件片选(CS)信号拉低来启动一次传输,拉高表示一帧结束。从机在检测到 CS 下降沿时把内部收发指针归零,然后按固定长度收发数据,帧长度必须在初始化阶段就确定好,不能中途改变。片选时序必须严格控制:CS 拉低的持续时间要和数据字节数匹配,不能提前拉高。
这里特别提醒一个很多人忽略的点:SPI 从机在 CS 拉高期间必须把 MISO 引脚置为高阻态,否则多个从机共用一个 SPI 总线时会冲突。Demo 代码里通常已经处理了这一点,但有些移植者在修改 GPIO 配置时不小心把 MISO 的复用功能改掉了,导致通信时好时坏,排查起来非常折磨人。
3.2 数据帧格式设计与控制字/状态字约定
Demo 程序里默认的 SPI 数据帧结构是固定的,但实际使用时必须按伺服控制需求重新设计。这层设计直接关系到后续寄存器映射和 PLC 程序编写的难易程度。
我设计的帧格式是 32 字节定长帧,分上行和下行两个方向。下行(主控发给小板)包含 16 字节用户数据,第 0~1 字节是控制字,bit0 ~ bit3 分别对应伺服使能、急停、复位报警、点动正转,bit4 是“请求读取当前报警代码”,其余位备用;第 2~5 字节是目标速度(32 位 IEEE 754 浮点数),第 6~9 字节是目标位置;第 10~11 字节是主控心跳计数器,每周期递增;第 12~15 字节备用,全部置零,供扩展使用。
上行(小板发给主控)也是 16 字节用户数据,第 0~1 字节是状态字,bit0 表示 EtherNet/IP 连接建立,bit1 表示 IO 数据有效,bit2 表示报警发生;第 2~5 字节是当前速度反馈;第 6~9 字节是当前位置反馈;第 10~11 字节是小板心跳计数器;第 12~15 字节是报警代码区,连接建立前为 0,报警发生时写入具体错误码。
数据长度为什么一定要固定?因为 SPI 是全双工同步通信,主控发多少字节就得收多少字节,长度不一致会导致后续数据错位。EtherNet/IP 侧的 Assembly 实例大小也要和这个帧长度对齐,否则 PLC 组态数据长度会不一致。建议在 Demo 程序的配置头文件里用宏定义把帧长度定死,方便后续统一修改。
3.3 中断处理、DMA 搬运与超时机制
Demo 程序的底层数据处理路径通常有两种做法:一种是在 SPI 接收中断里逐个字节处理,另一种是 DMA 批量搬运。前者的优点是代码直观,但主控频率不够时容易丢数据;后者的优点是 CPU 占用率低,适合高速传输,但调试不直观。
我在这块项目里选择保留 Demo 原有的 DMA 路径。SPI 从机端配置 DMA 接收和发送,主控一发片选,从机 DMA 就把接收缓冲区填好,同时从预置的发送缓冲区里把数据发出去。整个传输过程 CPU 不参与逐字搬运,只在 DMA 传输完成中断里做数据解析和标志位更新。实测下来,在 4 MHz SPI 速率下,32 字节帧的传输时间不到 0.1ms,完全满足 1ms 周期刷新要求。
超时机制是必须补上的东西,Demo 里往往没有。我踩过的坑是:主控程序跑飞或者 SPI 线接触不良时,小板还在等从机模式下的数据,这时候 EtherNet/IP 侧的 PLC 一直显示连接正常,但伺服早就罢停了。后来我在小板固件里加了“看门狗逻辑”——如果超过 10ms 没有收到来自主控的有效 SPI 帧,就把 EtherNet/IP 状态字里的 IO 数据有效位置 0,同时向 PLC 发送一个连接故障代码。这个超时参数放在配置宏里,可根据实际需要调整,一般建议是 PLC RPI 周期的 3 到 5 倍。
4. 实操过程与核心环节实现:一步步把 Demo 改造到位
4.1 软硬件环境与工具准备
动手之前先明确环境。我用的主控是某国产 Cortex-M4 内核的伺服专用 MCU,带硬件 SPI 外设,系统时钟 168MHz;通信小板是方案商提供的评估板,主芯片是带 EtherNet/IP 协议栈的 Cortex-M3。调试工具是 J-Link,配合 Wireshark 抓 EtherNet/IP 报文分析协议栈行为。
软件方面,主控侧用 IAR 工程,小板侧是 Keil 工程。两边的工程都要先确认一个事情:原厂 Demo 使用的是哪个 SDK 版本,SDK 版本之间的寄存器定义和协议栈接口可能有差异,盲目替换头文件会导致编译报错或者运行异常。我这次拿到的 Demo 是 v2.1,协议栈版本是 3.4,编译后 HEX 文件烧录到小板能直接跑默认逻辑,先确认这个基础环境没问题,再做后续改造。
调试 SPI 通讯最痛苦的一点是看不到波形时就像瞎子摸象。强烈建议在正式改代码前,先拿逻辑分析仪或者示波器抓一遍主控和小板之间的 SPI 时序,确认时钟极性和相位对不对。我用的逻辑分析仪是 16 通道的,采样率 100MHz,抓 SPI 信号的四个通道绰绰有余。抓完波形确认基本时序正确后,再去改应用层逻辑,否则底层时序错了,上层调得再欢也是白搭。
4.2 第一步:解耦数据缓冲区与协议栈接口
拿到 Demo 程序后,不要急着改 SPI 参数或者寄存器地址,而是先做一件事:理清 Demo 的数据流。打开工程后,找到 spi_slave.c 文件,你会看到类似下面的核心接口:
void SPI_Slave_Init(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len); void SPI_Slave_Start(void); void SPI_Slave_IRQHandler(void);这里的 tx_buf 和 rx_buf 是直接暴露给协议栈层的全局数组,Demo 的 EtherNet/IP 协议栈会直接读这两个数组。我的第一步改造是把这两个数组从“业务层直接操作”改成“通过接口函数读写”,相当于加了一层抽象,把 Demo 的实现细节隔离起来。
改造后的接口:
/* 数据帧结构体 */ typedef struct { uint8_t control_word[2]; uint8_t target_speed[4]; uint8_t target_position[4]; uint8_t heartbeat[2]; uint8_t reserved[4]; } Servo_Downlink_Frame_t; typedef struct { uint8_t status_word[2]; uint8_t actual_speed[4]; uint8_t actual_position[4]; uint8_t heartbeat[2]; uint8_t alarm_code[4]; } Servo_Uplink_Frame_t; void Servo_SPI_GetDownlinkFrame(Servo_Downlink_Frame_t *frame); void Servo_SPI_SetUplinkFrame(Servo_Uplink_Frame_t *frame);为什么要加这一层?因为直接改 Demo 里的数组索引,虽然代码量少,但后续 EtherNet/IP 的 Assembly 对象映射改动时,你得翻遍整个工程去找数组下标,极其容易漏改。封装成结构体后,数据帧的长度和字段顺序都一目了然,改字段只需要对这个结构体下手。
4.3 第二步:寄存器映射表与 EtherNet/IP Assembly 对象对齐
这一步是整个适配改造的核心。EtherNet/IP 协议栈侧通过 Assembly 对象来映射 IO 数据,你需要找到协议的EDS 文件(电子数据文档),或者直接翻协议栈的接口函数,找到类似下面的代码:
void App_Object_GetAssemblyData(uint8_t instance_id, uint8_t *data, uint16_t *len); void App_Object_SetAssemblyData(uint8_t instance_id, uint8_t *data, uint16_t len);在 Demo 里,这两个函数的数据源是固定的测试数组,比如每秒递增的计数器、固定字节 0xAA 之类的。我们需要把这些测试数据源替换成伺服主控的实际控制字和状态字。
映射关系我设计成一张表,直接对应到 Assembly 实例 IO 数据区:
表:寄存器映射对照表
| 地址偏移 | 数据长度 | 含义 | 对应变量 |
|---|---|---|---|
| 0x00 | 2 字节 | 控制字 | Servo_Downlink_Frame_t.control_word |
| 0x02 | 4 字节 | 目标速度 | Servo_Downlink_Frame_t.target_speed |
| 0x06 | 4 字节 | 目标位置 | Servo_Downlink_Frame_t.target_position |
| 0x0A | 2 字节 | 主控心跳 | Servo_Downlink_Frame_t.heartbeat |
| 0x0C | 4 字节 | 保留 | 置零 |
| 0x10 | 2 字节 | 状态字 | Servo_Uplink_Frame_t.status_word |
| 0x12 | 4 字节 | 实际速度 | Servo_Uplink_Frame_t.actual_speed |
| 0x16 | 4 字节 | 实际位置 | Servo_Uplink_Frame_t.actual_position |
| 0x1A | 2 字节 | 小板心跳 | Servo_Uplink_Frame_t.heartbeat |
| 0x1C | 4 字节 | 报警代码 | Servo_Uplink_Frame_t.alarm_code |
这里必须注意字节序问题。EtherNet/IP 协议里多字节数据默认是大端(Big-Endian)存储,而 Cortex-M 系列的 MCU 默认是小端(Little-Endian),如果直接 memcpy 过去,PLC 读到速度值会是 0x00000001 或者反过来,轻则显示错误,重则导致控制异常。解决办法是在赋值时做一次字节序转换,写一个 Swap16 和 Swap32 的宏,或者在结构体定义里加 packed 属性后逐字节赋值。
4.4 第三步:状态机完善与异常场景覆盖
Demo 程序通常是一个“假连接”的状态机——它不管你 PLC 是否真的连上,反正 SPI 有数据就发。但实际产品必须区分“PLC 未连接”“连接建立”“运行中”“连接丢失”这几个状态,否则伺服主控侧无法做出正确的安全响应。
我在适配时重写了小板的通信状态机:
- 空闲态:小板复位后进入此状态,SPI 正常收发控制字,但状态字的 bit0(连接建立)和 bit1(IO 数据有效)都是 0,此时伺服主控不应执行任何使能动作。
- 连接建立态:EtherNet/IP 的 Forward Open 成功后,协议栈回调通知应用层,此时将状态字 bit0 置 1,主控收到后允许用户通过上位机做点动调试,但仍不执行自动运行。
- 运行态:主控将控制字 bit0 置 1(伺服使能)后,EtherNet/IP 侧自动把 bit1 也拉高,PLC 开始周期性下发目标速度/位置,伺服进入闭环控制。
- 连接丢失态:超过设定时间没有收到 EtherNet/IP 的 IO 报文,或者收到连接关闭请求,状态字 bit0 和 bit1 清零,向主控上报报警代码。
这个状态机的实现不是一蹴而就的,我建议先在仿真器里单步调通每个状态转换,确认协议栈回调函数绑定的中断优先级没有和 SPI DMA 中断冲突,否则会出现偶发性死锁。
4.5 第四步:Demo 自检逻辑的裁剪与保留
Demo 程序里通常自带一些上电自检逻辑,比如 SPI 环回测试、LED 闪烁指示、内部 Flash 读写测试等。这些逻辑在产品化时必须裁剪,否则会占用不必要的启动时间,甚至干扰正式通信。
但不要一股脑全删了。建议保留两类自检:第一是协议栈版本读取,方便现场用串口或调试器确认固件烧录是否正确;第二是 SPI 环回测试的代码块,把它做成编译开关(宏定义控制),调试阶段打开,正式发布关闭。这样下次客户现场通信异常时,可以烧一版带自检的固件快速定位问题点,而不是直接在线上猜。
我在裁剪时犯过一个低级错误:把 LED 闪烁代码也删了,结果调试时完全不知道小板当前处于什么状态。后来我在状态字里加了一个“调试模式”位,通过主控侧上位机可以强制进入调试模式,此时小板的 LED 用不同闪烁频率表示通信状态、连接状态、报警状态,排查问题效率瞬间提升。
5. 联调测试与常见问题排查:实战中踩过的坑与速查表
5.1 联调前的准备:伪造 PLC 与抓包验证
联调阶段最忌讳直接上真 PLC,原因很简单:PLC 程序没写好或者组态不对时,你根本不知道是固件问题还是 PLC 问题。我的习惯是先拿 PC 端的以太网调试工具模拟 EtherNet/IP 主站,比如用 Python 的 cpppo 库写个简单脚本,发 Forward Open 请求和 IO 数据,先验证小板的响应是否正确。
这一步很值得投入时间。我用 cpppo 做了一轮基础验证:发一个长度为 32 字节的 IO 数据帧,检查小板通过 SPI 上行的状态字是否按预期翻转。通过 Wireshark 抓包,可以非常直观地看到 EtherNet/IP 的请求和响应报文,确认协议栈的 CIP 路径、连接 ID 等参数都匹配。等所有逻辑都在模拟环境验证通过后,再上真 PLC,问题就能被控制在 PLC 程序中。
5.2 高频问题:SPI 数据错位、DMA 中断丢失、连接不稳定
项目过程中我遇到了三个典型问题,每个都是排查了很久才定位的,整理成速查表:
表:SPI 通讯固件适配改造常见问题速查表
| 问题现象 | 可能原因 | 排查方向与解决方案 |
|---|---|---|
| SPI 读回来的数据总是隔一段时间错位一次 | 片选控制时序不严格,CS 拉低期间刚好有小板复位 | 用示波器抓 CS 与 SCLK 波形,确认 CS 拉低提前量足够;检查从机复位引脚滤波电容 |
| 主控偶尔收不到上行数据,DMA 完成中断不触发 | DMA 配置错误或中断优先级和协议栈定时器冲突 | 检查 NVIC 优先级分组,把 SPI DMA 中断优先级提高到高于协议栈定时器;确认 DMA 传输长度和实际数据长度一致 |
| EtherNet/IP 连接建立后 1 秒左右自动断开 | 组装数据长度不一致,IO 数据区的长度不匹配 | 用 Wireshark 抓包对比 Forward Open 请求中的 T→O 和 O→T 数据长度与本地配置是否一致 |
| PLC 读到速度值是负数或乱码 | 大小端字节序未转换 | 在 Assembly 数据赋值接口里统一做 Swap 处理,用固定值测试验证 |
| SPI 通信偶尔丢失一帧,但不影响整体控制 | SPI 速率超过从机极限,或者布线导致信号质量差 | 用逻辑分析仪检查 SCLK 占空比和信号上升沿,降低速率到 2 MHz 后观察是否消失 |
5.3 高价值经验:SPI 与 EtherNet/IP 的双心跳机制
我把一个最有价值的经验单独拿出来说。伺服控制系统最怕的不是通信慢,而是“不知道对方掉线了”。EtherNet/IP 侧本身有 RPI 超时机制,PLC 会检测连接超时;但 SPI 侧呢?主控和小板之间是物理直连,没有网络层的心跳协议,只能靠应用层自己约定。
我在主控和小板的固件里做了一个双心跳机制:主控每 1ms 发送 SPI 帧时,把自己的心跳计数器递增并放在下行帧的固定位置;小板收到后,在下一帧的上行数据里回传这个小板心跳计数器。主控侧每收到一帧,就检查小板的心跳计数是否在递增,如果在连续 10 个发送周期内没有递增,就判定 SPI 链路异常,立即封锁伺服使能并上报“通讯故障”报警。
这个机制救了我一次现场问题。有一次客户反映伺服偶尔“抽风”,我们远程分析主控侧日志才发现是 SPI 插座接触不良导致的偶发丢帧,但 EtherNet/IP 连接始终没断过,如果不是心跳机制先从 SPI 侧爆出报警,根本定位不到插座接触问题。后来让客户重新插拔并加螺丝紧固后故障消失。
5.4 固件烧录、版本管理与现场升级建议
最后说说固件烧录和版本管理。小板固件的烧录方式通常是 SWD 接口,我用的是 J-Link Commander 脚本一键烧录,把编译好的 HEX 文件烧进小板,然后通过串口打印验证固件版本和启动日志。
现场升级方面,如果客户设备已经装到产线上,专门开盖烧录不现实。建议在做产品方案时就预留 IAP(In-Application Programming)能力,也就是 EtherNet/IP 的固件升级通道走网络侧下发固件,或者通过主控的 BOOT 程序转发固件给小板。我在这个项目里是先打通了“小板侧驻留 BOOT 程序,主控通过 SPI 转发固件”,虽然写起来费了点功夫,但后续现场更新固件只需要上位机操作,不用拆设备,省下的售后成本非常可观。
固件版本管理也是一门学问,我习惯在固件里定义一个版本信息结构体,包含主版本、次版本、修订版本、编译日期时间,同时通过 EtherNet/IP 的诊断对象(Diagnostic Object)暴露给 PLC 或上位机读取。这样每次更新固件后,现场人员只要看一眼 PLC 画面上的版本号就能确认固件是否刷对了。
6. 适配改造之外:规范和流程的一些体会
做这类嵌入式通信固件适配,除了代码本身,工程规范这块也想多说几句。很多人觉得 Demo 程序改一改能跑就行,但真正进入量产阶段后,你会被各种文档、测试、版本管理问题搞到崩溃。
我在这个项目里的做法是,开工前先建一份《通信协议接口规格书》,把 SPI 帧格式、寄存器映射表、心跳超时时间、报警代码定义全部写清楚,并让硬件、嵌入式、上位机三方共同评审确认。这份文档在后来的联调阶段成了唯一的真理来源——凡是发现数据对不上,先查文档,而不是先猜代码。
测试方面,不要只测“正常通信”这一条路径。我整理了至少二十个测试用例,包括 SPI 线断开、EtherNet/IP 连接断开重连、小板断电上电、PLC 组态参数错误、通信负载压力测试等。每个用例都记录预期行为和实际结果,形成一份回归测试报告。你会发现,很多隐藏的 bug 都出现在异常路径上,而不是正常路径上。
版本管理方面,强烈建议用小板的固件版本号和主控的固件版本号做联动绑定。我遇到过一种情况:现场工程师把新版本小板固件刷到旧版本主控的伺服上,结果 SPI 帧长度对不上,位置控制完全乱掉。后来我在协议里加了一个“协议版本号”字段,上电后主控和小板先交换协议版本,版本不一致时直接报警拒绝运行,从根上杜绝了这个问题。
7. 写在最后:实际调试中的一点个人感受
回头来看这个项目,最耗时间的不是改代码本身,而是定位那些“似懂非懂”的通信异常。SPI 是一条非常“物理级”的协议,它没有数据包的重传机制,也没有帧校验纠错能力,一切质量问题都会如实暴露在数据上。所以适配改造中最重要的工具不是什么高级调试器,而是耐心和逻辑分析仪。每一次数据错乱,都要回到波形上去看,回到时序上去推演,而不是盲目改代码碰运气。
我个人还有一个习惯值得分享:在调试阶段把小板的 SPI 速率故意降到 1 MHz,先用慢速把通信逻辑完全调通,再把速率提上去。这样做的原因是,如果通信逻辑本身有 bug,高速率下很难分辨是逻辑问题还是信号完整性问题。慢速调通后,再逐步提速,如果某一档速率开始出错,就说明是信号质量或者从机时序裕量问题,这时候再针对性地调整 SPI 相位、边沿或者 PCB 布线,定位效率要高得多。
这个项目的后续扩展空间还挺大,比如把小板的 SPI 帧长度从 32 字节扩展到 64 字节,用来传输更多诊断数据;或者在主控侧把 SPI 从 DMA 模式改成“中断+RTOS 信号量”模式,进一步释放 CPU 资源。但这些都是建立在底层 SPI 通讯稳定可靠的基础上。希望这篇文章能帮你把这块地基打好,后面再怎么扩展,都有底气。