4. 寄存器配置入门:SPI通信协议回顾、LoRa芯片寄存器映射表、读写寄存器的基础代码框架

好,咱们进入第四讲。前面几章我们把 LoRa 的基本原理、芯片选型和硬件连接都聊了一遍。从这章开始,要动真格的了——写代码配置寄存器。

说实话,很多初学者拿到 LoRa 芯片的数据手册,一看几百页,直接就懵了。我当年也一样,对着 SX1278 的寄存器表看了三天,愣是没敢下手写第一行代码。后来想通了,寄存器配置说白了就是通过 SPI 接口往芯片内部填数字,没什么神秘的。

4.1 SPI 通信协议快速回顾

LoRa 芯片和单片机之间的通信,几乎清一色用 SPI。为什么?因为 SPI 速度快、全双工、硬件实现简单。你想想看,无线传感器网络里动不动就要收发几十上百字节的数据,用 I2C 那速度能急死人。

4.1.1 SPI 的四根线

信号名 全称 作用
SCLK Serial Clock 时钟线,由主机(单片机)产生
MOSI Master Out Slave In 主机输出,从机输入
MISO Master In Slave Out 主机输入,从机输出
NSS Negative Slave Select 片选线,低电平有效

这里有个细节我提醒一下:LoRa 芯片的 SPI 时序通常要求时钟极性(CPOL)为 0,时钟相位(CPHA)为 0,也就是模式 0。换句话说,时钟空闲时是低电平,数据在时钟上升沿采样。我在项目中遇到过有人用错了 SPI 模式,读回来的寄存器全是 0xFF,排查了两天才发现是模式没对上。

注意:不同厂家的 LoRa 芯片 SPI 模式可能不同。SX127x 系列是模式 0,但某些国产兼容芯片可能支持模式 0 和模式 3。上电后第一件事,先读芯片的版本寄存器,确认 SPI 通信正常,再往下走。

4.1.2 SPI 读写操作的单字节格式

LoRa 芯片的 SPI 读写操作,每帧固定是 2 个字节:

  • 第一个字节:地址字节。最高位是读写标志位(1=读,0=写),低 7 位是寄存器地址。
  • 第二个字节:数据字节。写操作时发送要写入的值,读操作时发送 0x00(或任意值)来产生时钟,同时接收从机返回的数据。

举个例子,读地址 0x42 的寄存器:

  • 主机发 0x82(0x42 | 0x80),同时收一个字节(忽略)
  • 主机发 0x00,同时收一个字节(这就是寄存器的值)

写地址 0x42 的寄存器,值为 0x1A:

  • 主机发 0x42,同时收一个字节(忽略)
  • 主机发 0x1A,同时收一个字节(忽略)
小技巧:我习惯把 SPI 读写封装成两个函数:LoRa_ReadRegister(addr) 和 LoRa_WriteRegister(addr, value)。后面所有寄存器操作都调用这两个函数,代码会干净很多。

4.2 LoRa 芯片寄存器映射表

每个 LoRa 芯片内部都有一张寄存器映射表,就像一张地图。你要知道哪个地址对应什么功能,才能正确地配置芯片。

以 SX1278 为例,它的寄存器空间是 0x00 ~ 0x7F,共 128 个字节。我挑几个最常用的列出来:

地址 寄存器名 功能描述
0x00 FIFO 数据缓冲区,读写收发数据
0x01 OPMODE 工作模式选择(Sleep/Standby/FSTx/Rx等)
0x06 FRF_MSB 射频载波频率高位字节
0x07 FRF_MID 射频载波频率中位字节
0x08 FRF_LSB 射频载波频率低位字节
0x1D MODEM_CONFIG1 调制解调配置1(带宽、编码率等)
0x1E MODEM_CONFIG2 调制解调配置2(扩频因子、CRC使能等)
0x22 PA_CONFIG 功率放大器配置(发射功率)
0x4C RSSI_WIDEBAND 宽带 RSSI 值(用于信号强度检测)

嗯,这里要注意:寄存器地址和功能因芯片型号而异。SX126x 和 SX127x 的寄存器映射完全不同。我见过有人拿着 SX1278 的代码直接往 SX1262 上烧,结果芯片完全不工作。所以,一定要以你手里那颗芯片的数据手册为准。

4.3 读写寄存器的基础代码框架

理论说完了,上代码。下面是我个人一直在用的 LoRa 寄存器读写框架,基于 STM32 HAL 库,但逻辑是通用的。

4.3.1 宏定义和引脚配置

/* SPI 引脚定义 */
#define LORA_NSS_PORT       GPIOA
#define LORA_NSS_PIN        GPIO_PIN_4
#define LORA_RESET_PORT     GPIOA
#define LORA_RESET_PIN      GPIO_PIN_3

/* SPI 句柄(由 CubeMX 生成) */
extern SPI_HandleTypeDef hspi1;

/* 片选控制 */
#define LORA_CS_LOW()       HAL_GPIO_WritePin(LORA_NSS_PORT, LORA_NSS_PIN, GPIO_PIN_RESET)
#define LORA_CS_HIGH()      HAL_GPIO_WritePin(LORA_NSS_PORT, LORA_NSS_PIN, GPIO_PIN_SET)

/* 复位控制 */
#define LORA_RESET_LOW()    HAL_GPIO_WritePin(LORA_RESET_PORT, LORA_RESET_PIN, GPIO_PIN_RESET)
#define LORA_RESET_HIGH()   HAL_GPIO_WritePin(LORA_RESET_PORT, LORA_RESET_PIN, GPIO_PIN_SET)

4.3.2 单字节读写函数

/**
 * @brief  写一个字节到 LoRa 寄存器
 * @param  addr: 寄存器地址 (0x00 ~ 0x7F)
 * @param  value: 要写入的值
 */
void LoRa_WriteRegister(uint8_t addr, uint8_t value)
{
    uint8_t tx_buf[2];
    tx_buf[0] = addr & 0x7F;          // 写操作,最高位为 0
    tx_buf[1] = value;

    LORA_CS_LOW();
    HAL_SPI_Transmit(&hspi1, tx_buf, 2, HAL_MAX_DELAY);
    LORA_CS_HIGH();
}

/**
 * @brief  从 LoRa 寄存器读一个字节
 * @param  addr: 寄存器地址 (0x00 ~ 0x7F)
 * @retval 读取到的值
 */
uint8_t LoRa_ReadRegister(uint8_t addr)
{
    uint8_t tx_buf[2];
    uint8_t rx_buf[2];

    tx_buf[0] = addr | 0x80;          // 读操作,最高位为 1
    tx_buf[1] = 0x00;                 // 发送任意值,产生时钟

    LORA_CS_LOW();
    HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, 2, HAL_MAX_DELAY);
    LORA_CS_HIGH();

    return rx_buf[1];                 // 第二个字节才是读回来的数据
}
核心要点:读操作时,第一个字节发地址+0x80,第二个字节发0x00。返回值的第二个字节才是真正的寄存器内容。这个坑我踩过——一开始直接返回 rx_buf[0],结果读出来的值永远不对。

4.3.3 批量读写函数(用于 FIFO 操作)

LoRa 的 FIFO 寄存器(地址 0x00)支持连续读写。发送或接收多字节数据时,用批量函数效率更高。

/**
 * @brief  从 LoRa FIFO 连续读取多个字节
 * @param  buffer: 存放读取数据的缓冲区
 * @param  len: 要读取的字节数
 */
void LoRa_ReadFIFO(uint8_t *buffer, uint8_t len)
{
    uint8_t addr = 0x00 | 0x80;       // FIFO 地址,读操作

    LORA_CS_LOW();
    HAL_SPI_Transmit(&hspi1, &addr, 1, HAL_MAX_DELAY);
    HAL_SPI_Receive(&hspi1, buffer, len, HAL_MAX_DELAY);
    LORA_CS_HIGH();
}

/**
 * @brief  向 LoRa FIFO 连续写入多个字节
 * @param  buffer: 待发送的数据缓冲区
 * @param  len: 要写入的字节数
 */
void LoRa_WriteFIFO(uint8_t *buffer, uint8_t len)
{
    uint8_t addr = 0x00;              // FIFO 地址,写操作

    LORA_CS_LOW();
    HAL_SPI_Transmit(&hspi1, &addr, 1, HAL_MAX_DELAY);
    HAL_SPI_Transmit(&hspi1, buffer, len, HAL_MAX_DELAY);
    LORA_CS_HIGH();
}

4.3.4 验证 SPI 通信是否正常

每次上电初始化,我做的第一件事就是读版本寄存器。SX1278 的版本寄存器地址是 0x42,读回来应该是 0x12。如果读出来是 0xFF 或 0x00,说明 SPI 通信有问题。

uint8_t version = LoRa_ReadRegister(0x42);
if (version != 0x12) {
    // SPI 通信异常,进入错误处理
    Error_Handler();
}
避坑指南:我曾经在一个项目里,SPI 线序接反了——MOSI 和 MISO 焊反了。读版本寄存器返回 0xFF,排查了整整一天。所以,硬件连接好后,先用逻辑分析仪或示波器抓一下 SPI 波形,确认时序和线序都正确,再开始写代码。

4.4 本章知识体系

下面这张图把本章的核心逻辑串起来了,你可以对照着看:

第4章 知识体系:寄存器配置入门 SPI 通信协议回顾 LoRa 芯片寄存器映射表 读写寄存器的基础代码框架 单字节读写函数 批量读写(FIFO) 通信验证(读版本号) 核心原则 先验证 SPI 通信 → 再配置寄存器 → 最后操作 FIFO

说白了,这一章就三件事:搞懂 SPI 怎么读写、记住常用寄存器地址、写好底层驱动函数。这三件事做好了,后面配置 LoRa 的各种工作模式、发射接收参数,就是往这些函数里填不同的数字而已。