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。所有从机共享同一时钟和片选。
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 组合)
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 的时序关系。
联调检查清单:
- 确认从机数据手册中的 CPOL/CPHA 要求,与设备树配置一致。
- 使用
spi-pump工具或逻辑分析仪抓取实际波形,验证t_lead和t_trail是否满足从机要求。 - 在 DMA 传输完成中断中读取 SPI 控制器状态寄存器,检查是否有
RX_OVERFLOW或TX_UNDERFLOW错误。 - 对于高速传输(> 20 MHz),在 PCB 布局上确保 MOSI 与 MISO 等长,减少信号偏移。