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保持高电平,自动进入高阻状态,不参与通信。
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。你说坑不坑?
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]; // 第二个字节才是有效数据
}
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);
}
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读写函数和延时函数就行。
4.3 SPI总线设计要点
最后,我总结几个SPI总线设计的要点:
- 信号完整性:SPI时钟频率高,走线要短,最好控制在10cm以内。如果走线长了,要加终端匹配电阻。
- 电平匹配:MCU是3.3V,有些外设是5V或1.8V。要用电平转换芯片,别直接连。
- 片选控制:多个从设备共用SPI总线时,片选信号要独立控制。别用菊花链,那玩意儿容易出问题。
- 中断处理:SPI传输完成中断里,别做复杂操作。我一般只在中断里设置一个标志位,然后在主循环里处理数据。
好了,SPI总线就讲到这里。内容不少,但都是干货。你回去可以拿逻辑分析仪抓一下自己板子上的SPI波形,对照着看,理解会更深刻。
公众号:蓝海资料掘金营,微信deep3321