Android SPI DMA驱动开发与HAL联调 - 第3节

3.1 单工、半双工与全双工通信模式

SPI 总线在物理层上由四条信号线构成:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)和 SS(从机片选)。根据数据在 MOSI 与 MISO 上的传输方向与时机,SPI 通信可划分为三种模式:单工、半双工 与 全双工。理解这三种模式是配置 SPI 控制器、设计驱动数据流以及优化 DMA 传输路径的基础。

3.1.1 三种模式对比

通信模式 数据流向 同时收发 典型应用 DMA 通道需求
单工 (Simplex) 单向固定:仅 MOSI 或仅 MISO 否 LED 驱动、DAC 输出、只读传感器 1 个 TX 或 1 个 RX
半双工 (Half-Duplex) 分时双向:同一根线轮流收发 否 单线 SPI、某些 EEPROM 命令阶段 1 个 TX + 1 个 RX(分时复用)
全双工 (Full-Duplex) 同时双向:MOSI 发、MISO 收 是 ADC/DAC 同步、Flash 读写、LCD 控制 1 个 TX + 1 个 RX(同时)
驱动联调提示: Android 平台 SPI 控制器(如 Qualcomm QUP、Exynos SPI)默认工作在全双工模式。若从机仅支持单工或半双工,必须在 SPI 设备树节点中通过 spi-tx-bus-width 和 spi-rx-bus-width 属性显式声明,否则控制器会因等待 MISO 数据而超时。

3.1.2 全双工通信的“虚拟”数据问题

在全双工模式下,SPI 控制器在发送一个字节的同时,必定会接收一个字节。如果驱动只关心发送(例如写 Flash),接收到的字节是无效的“虚拟数据”(dummy byte);反之,如果只关心接收(例如读传感器),发送端必须填充 0xFF 或 0x00 作为虚拟数据来产生时钟。

// 全双工模式下,只发送不接收的典型处理
uint8_t tx_buf[] = { 0x02, 0x00, 0x10, 0xAA, 0xBB };
uint8_t rx_buf[sizeof(tx_buf)] = {0};

struct spi_transfer t = {
    .tx_buf = tx_buf,
    .rx_buf = rx_buf,   // 必须提供,否则驱动报错
    .len    = sizeof(tx_buf),
    .speed_hz = 1000000,
};
spi_sync_transfer(spi_dev, &t, 1);

// 只接收不发送时,tx_buf 填充 0xFF
uint8_t tx_dummy[] = { 0xFF, 0xFF, 0xFF, 0xFF };
uint8_t rx_data[4];
t.tx_buf = tx_dummy;
t.rx_buf = rx_data;
t.len = 4;
spi_sync_transfer(spi_dev, &t, 1);
常见错误: 在只接收的传输中,将 tx_buf 设为 NULL。部分 SPI 控制器在 tx_buf == NULL 时会自动发送 0x00,但某些从机(如 LCD 控制器)要求时钟高电平期间 MOSI 保持高电平,此时必须显式填充 0xFF。

3.2 多从机片选策略

SPI 总线支持一主多从,每个从机独占一条片选信号线(SS)。在 Android 驱动中,片选管理直接影响 DMA 传输的启动时机和总线仲裁。

3.2.1 硬件片选 vs. GPIO 片选

片选方式 实现 优点 缺点
硬件片选 SPI 控制器内置 SS 引脚,由硬件自动拉低/拉高 延迟低,适合高速 DMA 传输 片选数量固定(通常 1~4 个)
GPIO 片选 使用通用 GPIO 模拟片选,由软件控制 数量灵活,可扩展至任意从机 软件拉高/拉低引入额外延迟,DMA 需配合中断
Android BSP 实践: 在设备树中,通过 cs-gpios 属性指定 GPIO 片选。内核 SPI 子系统会在每次传输前自动操作 GPIO,驱动无需手动控制。
// 设备树示例:4 个从机,使用 GPIO 片选
spi@ff110000 {
    compatible = "qcom,spi-qup-v2";
    reg = <0xff110000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
    cs-gpios = <&msmgpio 20 0>,
               <&msmgpio 21 0>,
               <&msmgpio 22 0>,
               <&msmgpio 23 0>;

    device0: sensor@0 {
        compatible = "vendor,pressure-sensor";
        reg = <0>;  // 对应 cs-gpios[0]
        spi-max-frequency = <5000000>;
    };
    device1: display@1 {
        compatible = "vendor,lcd-driver";
        reg = <1>;  // 对应 cs-gpios[1]
        spi-max-frequency = <20000000>;
    };
};

3.2.2 多从机 DMA 传输的冲突避免

当多个从机共享 SPI 总线时,DMA 传输必须确保:

  • 互斥访问: 使用 spi_bus_lock / spi_bus_unlock 防止并发传输。
  • 片选去抖: 连续两次传输之间插入至少一个 SPI 时钟周期的延迟,避免从机片选释放不彻底。
  • DMA 通道释放: 每次 DMA 传输完成后,必须回调释放通道,否则下一个从机无法申请 DMA。
// 多从机 DMA 传输的互斥示例
static struct spi_device *spi_dev_sensor;
static struct spi_device *spi_dev_display;

void write_sensor_data(void) {
    spi_bus_lock(spi_dev_sensor->master);
    // 启动 DMA 传输
    dma_async_issue_pending(chan_tx);
    dma_async_issue_pending(chan_rx);
    // 等待 DMA 完成回调
    wait_for_completion(&dma_done);
    spi_bus_unlock(spi_dev_sensor->master);
}

void write_display_data(void) {
    spi_bus_lock(spi_dev_display->master);
    // 同样流程
    spi_bus_unlock(spi_dev_display->master);
}

3.3 菊花链拓扑

菊花链(Daisy-Chain)是一种特殊的 SPI 拓扑,所有从机的数据输入(SDI)和数据输出(SDO)串联成一条链,主机的 MOSI 连接第一个从机的 SDI,最后一个从机的 SDO 连接主机的 MISO。所有从机共享同一时钟和片选。

SPI 菊花链拓扑结构 SPI 主机 从机 1 (SDI→SDO) 从机 2 (SDI→SDO) 从机 3 (SDI→SDO) MOSI SDO→SDI SDO→SDI SDO MISO(返回路径) SCLK(共享) SS(共享,同时选中所有从机)

3.3.1 菊花链的驱动适配要点

  • 数据帧长度 = 从机数量 × 每个从机数据长度: 主机必须一次性发送所有从机的数据,每个从机在时钟驱动下将自身数据移出,同时接收下一级数据。
  • 片选保持: 在整个链式传输过程中,SS 必须保持低电平,直到所有数据移位完成。
  • DMA 缓冲对齐: 由于数据帧长度较大,DMA 描述符必须支持 scatter-gather 或使用连续物理内存。
// 菊花链传输示例:3 个从机,每个 2 字节
uint8_t tx_buf[6] = { 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6 };
uint8_t rx_buf[6];

struct spi_transfer t = {
    .tx_buf = tx_buf,
    .rx_buf = rx_buf,
    .len    = 6,  // 3 * 2
    .cs_change = 0,  // 片选保持低
};
spi_sync_transfer(spi_dev, &t, 1);

// 解析:rx_buf[0:1] 来自从机3,rx_buf[2:3] 来自从机2,rx_buf[4:5] 来自从机1

3.4 SPI 时序参数分析

SPI 时序参数直接影响信号完整性和通信可靠性。在 Android 驱动联调中,错误配置时序参数是导致 DMA 数据错位、CRC 校验失败的最常见原因。

3.4.1 核心时序参数

参数 符号 定义 典型范围 驱动配置接口
时钟频率 f_SCLK SCLK 信号频率 1 kHz ~ 100 MHz spi-max-frequency 设备树属性
时钟极性 CPOL 空闲时 SCLK 电平:0=低,1=高 0 或 1 spi-cpol 设备树属性
时钟相位 CPHA 数据采样沿:0=第一个沿,1=第二个沿 0 或 1 spi-cpha 设备树属性
片选建立时间 t_lead SS 拉低到第一个 SCLK 沿的时间 ≥ 1 个 SCLK 周期 spi-cs-setup-delay-ns
片选保持时间 t_trail 最后一个 SCLK 沿到 SS 拉高的时间 ≥ 0.5 个 SCLK 周期 spi-cs-hold-delay-ns
数据建立时间 t_setup 数据在采样沿之前必须稳定的时间 ≥ 5 ns 由控制器硬件保证
数据保持时间 t_hold 采样沿之后数据必须保持的时间 ≥ 5 ns 由控制器硬件保证

3.4.2 四种 SPI 模式(CPOL/CPHA 组合)

SPI 四种模式时序对比 模式 0 (CPOL=0, CPHA=0) SCLK MOSI D0 D1 D2 D3 ↑ 采样沿(上升沿) 模式 1 (CPOL=0, CPHA=1) SCLK MOSI D0 D1 D2 D3 ↑ 采样沿(下降沿) 模式 2 (CPOL=1, CPHA=0) SCLK MOSI D0 D1 D2 D3 ↑ 采样沿(下降沿) 模式 3 (CPOL=1, CPHA=1) SCLK MOSI D0 D1 D2 D3 ↑ 采样沿(上升沿) 时序参数配置要点 • CPOL 决定空闲时钟电平,CPHA 决定数据采样沿。 • 模式 0 和模式 3 最常用:模式 0 空闲低、上升沿采样;模式 3 空闲高、上升沿采样。 • 设备树中通过 spi-cpol 和 spi-cpha 属性配置,不配置时默认为模式 0。

3.4.3 时序参数在 DMA 传输中的影响

  • 时钟频率与 DMA 吞吐量: DMA 传输速率受限于 SPI 时钟频率。例如,50 MHz 时钟下,全双工理论吞吐量为 50 Mbps。实际 DMA 带宽需考虑描述符处理开销。
  • 片选延迟与 DMA 启动: 如果 t_lead 设置过短,DMA 可能在片选未稳定时就开始传输,导致第一个数据位错误。建议在设备树中设置 spi-cs-setup-delay-ns = <100>(100 ns)。
  • 时钟极性与 DMA 采样窗口: 错误配置 CPOL/CPHA 会导致 DMA 在错误的时钟沿采样数据,表现为所有数据位偏移一位或全部反转。联调时应使用示波器对比 SCLK 与 MOSI/MISO 的时序关系。
联调检查清单:
  1. 确认从机数据手册中的 CPOL/CPHA 要求,与设备树配置一致。
  2. 使用 spi-pump 工具或逻辑分析仪抓取实际波形,验证 t_lead 和 t_trail 是否满足从机要求。
  3. 在 DMA 传输完成中断中读取 SPI 控制器状态寄存器,检查是否有 RX_OVERFLOW 或 TX_UNDERFLOW 错误。
  4. 对于高速传输(> 20 MHz),在 PCB 布局上确保 MOSI 与 MISO 等长,减少信号偏移。

3.5 本章小结