You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

应用层SPI读写:如何获取接收数据的长度?

解决SPI应用层接收长度不固定的问题

核心问题分析

SPI本身是同步全双工通信,默认发送N字节就会接收N字节。你遇到的接收长度不固定问题,本质是和从设备的通信协议约定问题——从设备返回的数据长度由它自身逻辑决定,需要先通过协议规则获取这个长度,再接收对应的数据。

解决方案

根据常见的SPI从设备通信逻辑,提供两种可落地的修改思路:

思路1:先读取长度标识字节,再接收完整数据

如果从设备会先返回1个(或多个)字节表示后续数据的长度,可分两次SPI传输:

  1. 发送命令帧,接收从设备返回的长度值
  2. 根据长度值动态分配缓冲区,再次传输(可发送空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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 21:27:11