通过SPI实现Arduino从机编码器速度值向主机传输的故障排查
排查SPI传输编码器速度始终为0的问题
咱们先从几个容易踩坑的点入手,一步步解决你的问题:
首先纠正一个关键认知:Arduino的unsigned int是2字节
你提到unsigned int是4字节,但在Arduino(AVR架构,比如Uno/Nano)里,unsigned int是2字节,4字节的是unsigned long。这个数据长度的误解很可能是导致传输错误的根源,得先明确这点。
核心排查方向
1. SPI基础配置与硬件接线检查
- 接线必须准确:主机的SCK、MOSI、MISO要对应连接从机的同名引脚;主机的SS引脚要连接从机的SS引脚,且主机必须主动拉低SS才能选中从机进行通信。
- SPI配置完全一致:两边的SPI模式(SPI_MODE0~SPI_MODE3)、时钟分频(比如
SPI_CLOCK_DIV16)、数据位长度(默认8位)必须完全匹配。比如主机用SPI.setDataMode(SPI_MODE0),从机也要对应配置。 - 从机必须启用SPI中断:从机需要通过
SPCR |= _BV(SPE)启用从机模式,并且调用SPI.attachInterrupt()来触发数据传输的中断服务函数(ISR)。
2. 数据传输逻辑的问题
SPI是同步通信,主机必须发送一个“dummy字节”来触发从机发送数据。如果是多字节数据(比如2字节的unsigned int),要注意传输顺序和拼接逻辑:
- 字节序一致性:不管你用union还是手动拆分字节,从机发送字节的顺序(先低字节还是高字节)必须和主机拼接的顺序一致。比如从机先发低字节,主机就要用
(高字节 << 8) | 低字节来还原。 - union的使用是可行的:union确实是转换整数和字节数组的便捷方式,但要确保两边的内存布局一致(AVR架构都是小端模式,所以低字节存在数组的0索引)。示例union:
union SpeedData { unsigned int speed; byte bytes[2]; }; - 从机ISR的正确性:从机的
SPI_STC_vect中断函数里,必须把要发送的数据写入SPDR寄存器。比如每次中断发送一个字节,用静态变量记录当前发送的是第几个字节:ISR(SPI_STC_vect) { static byte byteIndex = 0; SPDR = speedData.bytes[byteIndex]; // 发送当前字节 byteIndex = (byteIndex + 1) % 2; // 循环切换字节索引 }
3. 等待时长的误区
SPI的transfer()函数本身就是阻塞式的,会等待传输完成后再返回结果,所以额外加100ms等待其实没用——问题根本不在处理时间,而是配置或传输逻辑错误。
4. 调试技巧
- 先隔离编码器问题:在从机里先固定发送一个已知值(比如
1234),不要读编码器,看主机能不能接收到这个值。如果能,说明SPI通信正常,问题出在编码器读取逻辑;如果还是0,说明SPI本身有问题。 - 用串口辅助调试:在从机的loop里打印当前的
encoderSpeed,确认编码器读取的数值是正确的;在从机的ISR里也可以加串口打印(注意ISR里串口打印要谨慎,最好用非阻塞方式,或者先测试通信正常后再去掉)。
参考代码框架
主机代码
#include <SPI.h> #define SS_PIN 10 void setup() { Serial.begin(9600); SPI.begin(); pinMode(SS_PIN, OUTPUT); digitalWrite(SS_PIN, HIGH); // 默认拉高SS,不选中从机 } void loop() { digitalWrite(SS_PIN, LOW); // 选中从机开始通信 // 发送两个dummy字节,接收从机返回的两个字节 byte lowByte = SPI.transfer(0x00); byte highByte = SPI.transfer(0x00); digitalWrite(SS_PIN, HIGH); // 结束通信,拉高SS unsigned int speed = (highByte << 8) | lowByte; // 拼接成unsigned int Serial.print("Received speed: "); Serial.println(speed); delay(100); }
从机代码
#include <SPI.h> unsigned int encoderSpeed = 1234; // 先固定一个测试值 union SpeedData { unsigned int speed; byte bytes[2]; } speedData; void setup() { Serial.begin(9600); pinMode(MISO, OUTPUT); // 从机MISO必须设为输出 SPCR |= _BV(SPE); // 启用SPI从机模式 SPI.attachInterrupt(); // 启用SPI中断 speedData.speed = encoderSpeed; } ISR(SPI_STC_vect) { static byte byteIndex = 0; // 读取主机发送的dummy字节(可选,这里不用处理) (void)SPDR; // 发送当前字节 SPDR = speedData.bytes[byteIndex]; byteIndex = (byteIndex + 1) % 2; } void loop() { // 这里替换成你的编码器读取逻辑,比如: // encoderSpeed = readYourEncoder(); speedData.speed = encoderSpeed; // 可选:打印当前速度,确认编码器读取正常 // Serial.print("Current speed: "); // Serial.println(encoderSpeed); delay(50); }
先测试这个固定值的版本,确认SPI能正常传输后,再替换成编码器读取的逻辑,这样就能一步步定位问题啦。
内容的提问来源于stack exchange,提问作者mecheng6769
相关产品推荐
相关产品推荐

