4. SPI 消息与传输:消息队列与同步/异步机制

SPI 从设备通信的核心是消息(Message)与传输(Transfer)对象。VxWorks 将一次 SPI 事务抽象为 struct spi_message,将每个片选连续的收发段抽象为 struct spi_ioc_transfer。理解这两个结构体的关系、消息队列的管理方式以及同步/异步传输模型,是编写高效 SPI 驱动的关键。

4.1 核心数据结构:spi_message 与 spi_ioc_transfer

结构体作用关键字段
struct spi_message一次完整的 SPI 事务,包含一个或多个 transfer 段transfers(链表头)、complete(回调)、context、status、actual_length
struct spi_ioc_transfer单个片选连续的收发段,描述一次 tx/rx 操作tx_buf、rx_buf、len、speed_hz、delay_usecs、cs_change
关键概念: 一个 spi_message 可以包含多个 spi_ioc_transfer,这些 transfer 在同一个片选有效期内连续执行,中间不会释放片选。每个 transfer 可以独立配置速率、延迟和片选行为。

4.1.1 spi_message 结构详解

typedef struct spi_message {
    struct list_head    transfers;      /* transfer 链表头 */
    struct spi_device   *spi;           /* 目标 SPI 设备 */
    unsigned            is_dma_mapped : 1; /* DMA 映射标志 */
    /* 完成回调 */
    void                (*complete)(void *context);
    void                *context;       /* 回调上下文 */
    int                 status;         /* 传输状态,0 成功 */
    unsigned            actual_length;  /* 实际传输字节数 */
    /* 内部链表节点 */
    struct list_head    queue;          /* 消息队列节点 */
    /* ... 其他内部字段 */
} SPI_MESSAGE;
  • transfers:通过 list_add_tail() 将多个 spi_ioc_transfer 挂入链表。
  • complete:异步传输完成后的回调函数,在中断上下文或任务上下文中执行。
  • status:驱动在传输完成后设置,0 表示成功,负值表示错误码。
  • actual_length:所有 transfer 实际传输的总字节数。

4.1.2 spi_ioc_transfer 结构详解

typedef struct spi_ioc_transfer {
    const void  *tx_buf;       /* 发送缓冲区,NULL 表示只接收 */
    void        *rx_buf;       /* 接收缓冲区,NULL 表示只发送 */
    unsigned    len;           /* 本次 transfer 的字节数 */
    unsigned    speed_hz;      /* 本次传输速率,0 使用设备默认 */
    uint16_t    delay_usecs;   /* 传输结束后延迟(微秒) */
    uint8_t     bits_per_word; /* 每字位数,0 使用设备默认 */
    uint8_t     cs_change;     /* 传输结束后是否改变片选 */
    /* ... 其他字段 */
} SPI_IOC_TRANSFER;
开发提示: 当 tx_buf 和 rx_buf 同时为 NULL 时,该 transfer 仅用于产生时钟(常用于 dummy 读)。cs_change 设置为 1 时,传输结束后会释放片选,下一个 transfer 重新拉低片选。

4.2 消息队列管理

VxWorks SPI 核心层维护一个全局消息队列,所有待处理的 spi_message 按优先级或 FIFO 顺序排队。驱动通过 spi_async() 将消息挂入队列,由 SPI 控制器中断或轮询机制逐个处理。

队列管理流程:
  1. 调用 spi_async() 将消息加入队列尾部。
  2. 如果控制器空闲,立即启动第一个消息的传输。
  3. 每个 transfer 完成后,通过中断或 DMA 回调推进到下一个 transfer。
  4. 所有 transfer 完成后,调用消息的 complete 回调,并从队列中移除该消息。
  5. 调度队列中的下一个消息开始传输。
SPI 消息队列管理流程 spi_async() 消息队列 控制器空闲? 启动传输 complete 回调 从队列移除 队列非空 控制器忙时消息排队等待 空闲时立即启动

4.3 同步传输 vs 异步传输

特性同步传输(spi_sync)异步传输(spi_async)
调用方式阻塞等待传输完成立即返回,通过回调通知完成
适用场景简单读写、任务上下文中断驱动、高并发、DMA 传输
CPU 占用等待期间占用 CPU等待期间可执行其他任务
实现复杂度低,直接调用高,需管理回调上下文
典型 APIspi_sync()spi_async()

4.3.1 同步传输实现

STATUS spi_sync(SPI_MESSAGE *pMsg)
{
    /* 初始化完成标志 */
    SEM_ID sem = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
    pMsg->complete = sync_complete;
    pMsg->context  = sem;

    /* 将消息加入队列 */
    spi_async(pMsg);

    /* 阻塞等待完成信号量 */
    semTake(sem, WAIT_FOREVER);
    semDelete(sem);

    return pMsg->status;
}

static void sync_complete(void *context)
{
    SEM_ID sem = (SEM_ID)context;
    semGive(sem);  /* 释放信号量,唤醒等待任务 */
}
注意事项: 同步传输不能在中断上下文中调用,否则 semTake() 会导致死锁。在中断上下文中必须使用异步传输。

4.3.2 异步传输实现

/* 异步传输示例 */
void my_spi_async_transfer(SPI_DEVICE *pDev, uint8_t *tx, uint8_t *rx, int len)
{
    SPI_MESSAGE msg;
    SPI_IOC_TRANSFER transfer;

    /* 初始化 transfer */
    memset(&transfer, 0, sizeof(transfer));
    transfer.tx_buf = tx;
    transfer.rx_buf = rx;
    transfer.len    = len;
    transfer.speed_hz = 1000000;  /* 1MHz */
    transfer.cs_change = 0;

    /* 初始化 message */
    memset(&msg, 0, sizeof(msg));
    msg.spi      = pDev;
    msg.complete = my_complete_callback;
    msg.context  = my_context;

    /* 将 transfer 加入 message */
    spi_message_init(&msg);
    spi_message_add_tail(&transfer, &msg);

    /* 异步提交 */
    spi_async(&msg);
}

static void my_complete_callback(void *context)
{
    /* 在中断或任务上下文中执行 */
    /* 处理传输完成后的数据 */
    /* 唤醒等待的任务或发送信号 */
}

4.4 传输流程与状态机

SPI 控制器驱动内部维护一个传输状态机,管理消息队列的推进。典型状态包括:IDLE(空闲)、ACTIVE(传输中)、WAIT_CS(等待片选稳定)、COMPLETE(完成)。

SPI 传输状态机 IDLE ACTIVE WAIT_CS COMPLETE 消息入队 transfer 完成 cs_change=1 队列非空 → 下一个消息

4.5 消息队列深度与性能考量

  • 队列深度:默认队列深度由 SPI_MSG_QUEUE_SIZE 配置,通常为 16~64。超出深度时 spi_async() 返回 ERROR。
  • 优先级反转:高优先级任务提交的消息可能被低优先级消息阻塞,建议使用优先级队列或单独的消息通道。
  • DMA 对齐:使用 DMA 传输时,tx_buf 和 rx_buf 必须满足 DMA 对齐要求(通常 4 字节或缓存行对齐)。
  • 超时处理:同步传输建议增加超时机制,防止硬件故障导致任务永久阻塞。
最佳实践: 对于频繁的小数据量传输(如传感器寄存器读写),使用同步传输简化代码;对于大数据块传输(如 Flash 页编程),使用异步传输配合 DMA 提高吞吐量。

4.6 本章小结

知识点核心要点
spi_message一次 SPI 事务的抽象,包含多个 transfer 段和完成回调
spi_ioc_transfer单个片选连续的收发段,可独立配置速率和片选行为
消息队列FIFO 管理待处理消息,控制器空闲时自动调度
同步传输阻塞等待,适合任务上下文,简单可靠
异步传输非阻塞,回调通知,适合中断/DMA 场景
状态机IDLE → ACTIVE → WAIT_CS → COMPLETE 循环