3、SPI通信协议详解:SPI四线制原理、LoRa芯片SPI时序要求、寄存器读写操作流程、SPI速率与稳定性权衡

各位好,我是老张。今天咱们聊聊SPI通信协议,这是LoRa芯片打交道的基础。说实话,我见过不少工程师在SPI上栽跟头——不是速率上不去,就是数据偶尔错位。其实搞懂了原理,这些问题都能避免。

3.1 SPI四线制原理:SCK/MOSI/MISO/NSS

SPI,全称Serial Peripheral Interface,说白了就是同步串行通信。它用四根线搞定一切:

  • SCK(Serial Clock):时钟线,由主机产生。我习惯叫它“心跳线”,因为所有数据都跟着它跳。
  • MOSI(Master Out Slave In):主机输出,从机输入。数据从主机流向从机。
  • MISO(Master In Slave Out):主机输入,从机输出。数据从从机流回主机。
  • NSS(Negative Slave Select):片选线,低电平有效。用来选中你要通信的从机。

你想想看,这四根线其实就干了一件事:主机通过SCK产生时钟,在时钟的上升沿或下降沿,同时发送和接收数据。嗯,这里要注意,SPI是全双工的——发送和接收是同时进行的。

核心要点:SPI没有应答机制,主机发一个字节的同时,从机也会回一个字节。如果你只关心发送,那收到的数据直接丢弃就行。

我在项目中遇到过一个问题:有次调试LoRa模块,发现MISO上始终没数据。查了半天,原来是NSS引脚没拉低。你看,片选信号虽然简单,但忘了它,整个通信就废了。

3.2 LoRa芯片SPI时序要求

LoRa芯片(比如SX1278)对SPI时序有明确要求。我直接说重点:

  • 时钟极性(CPOL)和相位(CPHA):LoRa芯片要求CPOL=0,CPHA=0,也就是模式0。时钟空闲时为低电平,数据在上升沿采样。
  • 最大时钟频率:通常支持到10MHz。但我个人建议,实际使用时控制在5MHz以内,尤其是走线较长时。
  • 片选建立时间:NSS拉低后,至少等待100ns再开始发送时钟。这个时间很短,但如果你用GPIO模拟SPI,很容易忽略。

我的经验:如果你用硬件SPI,记得检查MCU的SPI配置是否匹配模式0。我曾经用STM32的SPI1,默认是模式0,但换了另一款MCU,默认是模式3,结果LoRa芯片死活不响应。

为什么会这样?因为模式不匹配,数据采样点就错了。你发出去的指令,芯片根本看不懂。

3.3 寄存器读写操作流程

LoRa芯片的寄存器读写,遵循一个固定流程。我画了个图,你一看就明白:

1. 拉低NSS 2. 发送地址字节 3. 发送/接收数据 4. 拉高NSS 地址字节格式:Bit7=0(写)/1(读) | Bit6-Bit0=寄存器地址 例如:0x80 表示读地址0x00的寄存器;0x01 表示写地址0x01的寄存器 写操作示例: MOSI: 0x01 (写地址0x01) → 0xAA (写入数据) 读操作示例: MOSI: 0x80 (读地址0x00) → 0x00 (任意数据) MISO: 忽略 → 0x12 (读取到的数据)

具体流程是这样的:

  1. 拉低NSS:选中LoRa芯片。注意,NSS必须保持低电平直到整个操作结束。
  2. 发送地址字节:最高位表示读写方向(1=读,0=写),低7位是寄存器地址。
  3. 发送/接收数据:如果是写操作,继续发送数据字节;如果是读操作,发送一个任意字节(通常发0x00),同时从MISO读取数据。
  4. 拉高NSS:结束本次操作。

避坑指南:我曾经在连续读写时犯过一个错误——两次操作之间没有拉高NSS。结果LoRa芯片把第二次的地址当成了数据,寄存器值全乱了。记住:每次操作必须完整执行拉低→通信→拉高

3.4 SPI速率与稳定性权衡

SPI速率越高,通信越快,但稳定性会下降。这个道理谁都懂,但实际怎么选?我分享几个经验:

速率范围 适用场景 注意事项
1MHz以下 长线通信(>10cm)、抗干扰要求高 几乎不会出错,但速度慢
1MHz ~ 5MHz 大多数LoRa应用场景 我推荐的折中方案,稳定性和速度都OK
5MHz ~ 10MHz 短距离、低噪声环境 需要留意PCB走线,避免串扰
10MHz以上 不推荐用于LoRa芯片 容易丢数据,得不偿失

你可能会问:为什么速率高了就不稳定?说白了,SPI是同步通信,时钟和数据必须严格对齐。速率高了,信号上升沿变陡,反射和串扰的影响就大了。尤其是MISO和MOSI两条线挨着走,互相干扰是常有的事。

我的建议:如果你不确定选多少速率,直接上2MHz。这个速率在大多数MCU上都能稳定工作,LoRa芯片也完全吃得消。等系统调通了,再考虑要不要提速率。

嗯,这里还要提一句:SPI速率不是越高越好。LoRa芯片本身处理数据的速度有限,你发太快了,它可能来不及响应。我见过有人把SPI调到8MHz,结果读回来的数据全是0xFF——因为芯片根本没准备好。

3.5 实际代码示例

最后,给一段我常用的SPI读写代码(基于STM32 HAL库)。你直接拿去用,注意改一下SPI句柄:

// SPI读写一个字节
uint8_t SPI_ReadWriteByte(uint8_t data)
{
    uint8_t rx_data;
    HAL_SPI_TransmitReceive(&hspi1, &data, &rx_data, 1, 100);
    return rx_data;
}

// 写寄存器
void LoRa_WriteReg(uint8_t addr, uint8_t data)
{
    HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);  // 拉低NSS
    SPI_ReadWriteByte(addr & 0x7F);  // 写操作,地址最高位为0
    SPI_ReadWriteByte(data);         // 写入数据
    HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);    // 拉高NSS
}

// 读寄存器
uint8_t LoRa_ReadReg(uint8_t addr)
{
    uint8_t data;
    HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);  // 拉低NSS
    SPI_ReadWriteByte(addr | 0x80);  // 读操作,地址最高位为1
    data = SPI_ReadWriteByte(0x00);  // 发送任意数据,读取返回值
    HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);    // 拉高NSS
    return data;
}

这段代码我用了好几年,从来没出过问题。你唯一要确认的是:NSS引脚在初始化时一定要拉高,否则芯片一开始就被选中了,后续操作会乱套。


专注资料整理