应用层SPI读写:如何获取接收数据的长度?
解决SPI应用层接收长度不固定的问题
核心问题分析
SPI本身是同步全双工通信,默认发送N字节就会接收N字节。你遇到的接收长度不固定问题,本质是和从设备的通信协议约定问题——从设备返回的数据长度由它自身逻辑决定,需要先通过协议规则获取这个长度,再接收对应的数据。
解决方案
根据常见的SPI从设备通信逻辑,提供两种可落地的修改思路:
思路1:先读取长度标识字节,再接收完整数据
如果从设备会先返回1个(或多个)字节表示后续数据的长度,可分两次SPI传输:
- 发送命令帧,接收从设备返回的长度值
- 根据长度值动态分配缓冲区,再次传输(可发送空dummy数据触发返回)接收完整数据
思路2:超时+结束标志位循环接收
如果从设备没有明确的长度字节,但返回数据有固定结束标志(比如0xFF),可以通过循环逐字节接收,直到读到结束标志或触发超时。
修改后的代码示例(思路1实现)
以下是基于你原代码修改的版本,假设从设备在收到命令后,第一个返回字节为数据长度:
#include <linux/spi/spidev.h> #include <stdint.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <getopt.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/types.h> #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) enum { SPI_MODE = 0, SPI_DELAY = 0, SPI_SPEED = 500 * 1000, SPI_BITS = 8 }; // 基础SPI传输函数,支持发送/接收长度不一致的场景 int TransferSpi(int fd, const uint8_t *tx, int txSize, uint8_t *rx, int rxSize) { int ret; struct spi_ioc_transfer tr = { .tx_buf = (unsigned long)tx, .rx_buf = (unsigned long)rx, .len = (txSize > rxSize) ? txSize : rxSize, // 取发送/接收的最大长度,适配半双工场景 .delay_usecs = SPI_DELAY, .speed_hz = SPI_SPEED, .bits_per_word = SPI_BITS, }; ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 1) { perror("can't send spi message"); return -1; } return ret; } int OpenSpiDev(const char *devName) { uint8_t mode = SPI_MODE; uint8_t bits = SPI_BITS; uint32_t speed = SPI_SPEED; uint16_t delay = SPI_DELAY; int fd = -1; int ret = -1; fd = open(devName, O_RDWR); if (fd < 0) { perror("can't open device"); return -1; } /* * spi mode */ ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); if (ret == -1) { perror("can't set spi mode"); return -1; } ret = ioctl(fd, SPI_IOC_RD_MODE, &mode); if (ret == -1) { perror("can't get spi mode"); return -1; } /* * bits per word */ ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); if (ret == -1) { perror("can't set bits per word"); return -1; } ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits); if (ret == -1) { perror("can't get bits per word"); return -1; } /* * max speed hz */ ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); if (ret == -1) { perror("can't set max speed hz"); return -1; } ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed); if (ret == -1) { perror("can't get max speed hz"); return -1; } return fd; } int main(int argc, char *argv[]) { int fd = OpenSpiDev("/dev/spidev1.0"); if (fd < 0) { return -1; } // 1. 发送命令帧,读取返回的长度字节 uint8_t tx_cmd[] = {0x00, 0x01}; // 替换为你的实际命令帧 uint8_t rx_len_buf[1] = {0}; if (TransferSpi(fd, tx_cmd, ARRAY_SIZE(tx_cmd), rx_len_buf, ARRAY_SIZE(rx_len_buf)) < 0) { close(fd); return -1; } uint8_t data_len = rx_len_buf[0]; printf("Received data length: %d\n", data_len); // 2. 根据长度动态分配缓冲区,接收完整数据 uint8_t *rx_data = malloc(data_len); if (!rx_data) { perror("malloc failed"); close(fd); return -1; } // 发送dummy空数据触发从设备返回(根据协议调整是否需要) uint8_t *tx_dummy = calloc(data_len, sizeof(uint8_t)); if (TransferSpi(fd, tx_dummy, data_len, rx_data, data_len) < 0) { free(rx_data); free(tx_dummy); close(fd); return -1; } // 打印接收的数据 printf("Received data:\n"); for (int i = 0; i < data_len; i++) { if (!(i % 6)) puts(""); printf("%.2X ", rx_data[i]); } puts(""); // 释放资源 free(rx_data); free(tx_dummy); close(fd); return 0; }
关键修改点说明
- 重构
TransferSpi函数,新增rxSize参数,允许指定接收缓冲区长度,适配发送/接收长度不一致的场景 - 实现两次传输逻辑:先获取数据长度,再接收对应长度的数据
- 增加动态内存分配/释放逻辑,适配可变长度的接收数据
- 修正原代码中
transfer函数名大小写错误(原函数为TransferSpi,main调用时写错)
注意事项
- 实际使用时需根据从设备的通信协议调整:比如长度字节的数量、是否需要发送dummy数据、结束标志位规则等
- 若采用超时接收方案,可在循环中逐字节接收,同时用
select或usleep实现超时判断,直到读到结束标志或超时
内容的提问来源于stack exchange,提问作者Vimer
相关产品推荐
相关产品推荐

