4. SPI总线详解:SPI协议原理与飞控应用

大家好,我是你们的飞控总线讲师。今天我们来聊聊SPI总线——这个在飞控系统里出镜率极高的通信接口。

说实话,SPI这玩意儿看着简单,但坑不少。我最早做飞控时,就因为在SPI时钟极性上栽过跟头,导致IMU数据死活读不对。后来花了整整一个下午,拿着逻辑分析仪一根一根线对,才发现是CPOL和CPHA配反了。嗯,从那以后,我对SPI的每个细节都格外上心。

4.1 SPI协议原理

SPI,全称Serial Peripheral Interface,串行外设接口。它由摩托罗拉公司提出,现在已经是嵌入式领域的标配了。

4.1.1 主从模式

SPI是典型的主从架构。一个主设备(Master)可以挂多个从设备(Slave),但同一时刻只能跟一个从设备通信。

主设备负责产生时钟信号,控制整个通信节奏。从设备呢,被动响应,主设备叫它干啥它就干啥。

我个人习惯把主设备想象成乐队指挥,从设备就是乐手。指挥挥动指挥棒(时钟),乐手们跟着节奏演奏(数据交换)。

多从设备怎么选通?靠片选信号(CS)。主设备把某个从设备的CS拉低,就表示“嘿,我要跟你说话”。其他从设备的CS保持高电平,自动进入高阻状态,不参与通信。

关键点:SPI通信是全双工的。主设备发数据的同时,也在收数据。这一点比I2C强,I2C是半双工,同一时刻只能一个方向传数据。

4.1.2 4线制与3线制

标准的SPI是4线制:

  • SCLK:串行时钟,由主设备产生
  • MOSI:主出从入,主设备发数据给从设备
  • MISO:主入从出,从设备发数据给主设备
  • CS:片选,选通从设备

3线制呢?其实就是把MOSI和MISO合并成一根双向数据线。说白了,就是半双工模式。这种模式在引脚紧张的场合会用,比如某些小封装MCU。

但我要提醒你:3线制SPI的速率会下降,因为同一时刻只能一个方向传数据。飞控里对IMU数据读取要求高实时性,我建议尽量用4线制。

4.1.3 时钟极性与相位

这是SPI最让人头疼的地方,也是出错率最高的地方。

时钟极性(CPOL)决定时钟空闲时的电平:

  • CPOL=0:空闲时时钟为低电平
  • CPOL=1:空闲时时钟为高电平

时钟相位(CPHA)决定数据在哪个边沿采样:

  • CPHA=0:在时钟的第一个边沿采样(上升沿或下降沿,取决于CPOL)
  • CPHA=1:在时钟的第二个边沿采样

组合起来就是4种模式:

模式 CPOL CPHA 采样边沿
模式0 0 0 上升沿采样,下降沿输出
模式1 0 1 下降沿采样,上升沿输出
模式2 1 0 下降沿采样,上升沿输出
模式3 1 1 上升沿采样,下降沿输出

我曾经遇到过一个问题:某款IMU芯片手册上写的是“模式0”,但我用逻辑分析仪抓波形,发现它实际工作在模式3。后来仔细看手册小字,才发现它内部有个配置寄存器可以切换模式,默认是模式3。你说坑不坑?

避坑指南:拿到一个新器件,别光看手册上的模式描述。最好用逻辑分析仪抓一下实际波形,确认CPOL和CPHA。我吃过这个亏,希望你少吃。

4.2 飞控中SPI的典型应用

飞控系统里,SPI主要用在三个地方:高速IMU、外部Flash、SD卡。咱们一个一个说。

4.2.1 高速IMU

飞控的核心传感器就是IMU(惯性测量单元),包含加速度计和陀螺仪。IMU的数据输出速率很高,动不动就几kHz甚至几十kHz。

为什么用SPI而不用I2C?因为SPI快啊!

举个例子,某款常用IMU芯片,I2C模式下最大速率只有400kHz,而SPI模式下可以跑到10MHz。你想想看,飞控要在1ms内完成姿态解算,如果IMU数据读取就占了500μs,那还解算个啥?

我习惯把IMU挂在SPI1上,因为这个总线通常直接连到MCU的高速外设总线,延迟最小。而且IMU的CS引脚要单独控制,不能跟其他设备共用片选。

// SPI读取IMU数据的典型流程
uint8_t spi_read_imu(uint8_t reg_addr) {
    uint8_t tx_data[2];
    uint8_t rx_data[2];
    
    // 第一个字节:寄存器地址(最高位为1表示读)
    tx_data[0] = reg_addr | 0x80;
    // 第二个字节:任意数据,用于产生时钟
    tx_data[1] = 0x00;
    
    // CS拉低
    HAL_GPIO_WritePin(IMU_CS_GPIO_Port, IMU_CS_Pin, GPIO_PIN_RESET);
    // SPI传输
    HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 2, HAL_MAX_DELAY);
    // CS拉高
    HAL_GPIO_WritePin(IMU_CS_GPIO_Port, IMU_CS_Pin, GPIO_PIN_SET);
    
    return rx_data[1];  // 第二个字节才是有效数据
}
小技巧:读取IMU数据时,尽量用DMA方式,别用阻塞式。阻塞式会占用CPU,影响其他任务的实时性。我一般用SPI+DMA+双缓冲,这样CPU可以一边处理上一帧数据,一边让DMA搬运下一帧数据。

4.2.2 外部Flash

飞控需要存储参数、日志、固件等数据。MCU内部Flash容量有限,一般只有几百KB到几MB。所以需要外挂一个SPI Flash芯片。

SPI Flash的容量从几MB到几百MB都有,常见的型号有W25Q系列、GD25系列等。它们支持标准的SPI接口,也支持Quad SPI(四线SPI),速率可以更高。

我个人习惯用W25Q64(8MB)或W25Q128(16MB),性价比高,而且市面上货源充足。

SPI Flash的读写操作跟普通存储器不太一样。它需要先擦除再写入,而且擦除是按扇区(通常4KB)或块(64KB)进行的。你不能像操作RAM那样直接写一个字节。

// SPI Flash写入一页(256字节)的示例
void spi_flash_page_write(uint32_t addr, uint8_t *data, uint16_t len) {
    // 1. 使能写操作
    spi_flash_write_enable();
    
    // 2. CS拉低
    CS_LOW();
    
    // 3. 发送页写指令(0x02)
    spi_send_byte(0x02);
    
    // 4. 发送24位地址
    spi_send_byte((addr >> 16) & 0xFF);
    spi_send_byte((addr >> 8) & 0xFF);
    spi_send_byte(addr & 0xFF);
    
    // 5. 发送数据
    for(uint16_t i = 0; i < len; i++) {
        spi_send_byte(data[i]);
    }
    
    // 6. CS拉高
    CS_HIGH();
    
    // 7. 等待写完成
    while(spi_flash_read_status() & 0x01);
}
注意:SPI Flash的写操作完成后,一定要等待内部编程完成(检查状态寄存器的BUSY位)。我见过有人没等写完就断电,结果数据全丢了。嗯,血的教训。

4.2.3 SD卡

SD卡在飞控里主要用于存储飞行日志。日志数据量大,而且需要频繁写入,对速率和可靠性要求都高。

SD卡支持两种通信模式:SDIO模式和SPI模式。飞控里常用SPI模式,因为MCU的SPI外设比较通用,不需要专门的SDIO控制器。

但SPI模式有个缺点:速率比SDIO模式慢。SDIO模式可以跑到几十MB/s,而SPI模式一般只能到几MB/s。不过对于飞控日志来说,几MB/s的写入速度已经足够了。

SD卡在SPI模式下初始化比较麻烦,需要先发74个时钟脉冲,然后发CMD0复位,再发CMD8检查版本,最后发ACMD41激活卡。这一套流程走下来,代码量不小。

我建议直接用现成的FatFS文件系统库,它已经封装好了底层驱动接口。你只需要实现SPI读写函数和延时函数就行。

经验之谈:SD卡在飞控上容易出问题的地方是热插拔和电压不稳。我建议在SD卡供电引脚上加一个100μF的电解电容,防止瞬间掉电导致数据损坏。另外,SD卡的CS引脚要加10kΩ上拉电阻,防止悬空时误触发。

4.3 SPI总线设计要点

最后,我总结几个SPI总线设计的要点:

  • 信号完整性:SPI时钟频率高,走线要短,最好控制在10cm以内。如果走线长了,要加终端匹配电阻。
  • 电平匹配:MCU是3.3V,有些外设是5V或1.8V。要用电平转换芯片,别直接连。
  • 片选控制:多个从设备共用SPI总线时,片选信号要独立控制。别用菊花链,那玩意儿容易出问题。
  • 中断处理:SPI传输完成中断里,别做复杂操作。我一般只在中断里设置一个标志位,然后在主循环里处理数据。

好了,SPI总线就讲到这里。内容不少,但都是干货。你回去可以拿逻辑分析仪抓一下自己板子上的SPI波形,对照着看,理解会更深刻。

SPI总线在飞控系统中的典型应用架构 MCU(主设备) IMU(从设备) 高速姿态数据 SPI Flash(从设备) 参数/固件存储 SD卡(从设备) 飞行日志存储 SCLK MOSI MISO CS1 CS2 CS3 图例说明 SCLK:串行时钟(主设备产生) MOSI:主出从入数据线 MISO:主入从出数据线 CS:片选信号(低电平有效) 主设备(MCU) 从设备(外设)

公众号:蓝海资料掘金营,微信deep3321