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,排查了两天才发现是模式没对上。
4.1.2 SPI 读写操作的单字节格式
LoRa 芯片的 SPI 读写操作,每帧固定是 2 个字节:
- 第一个字节:地址字节。最高位是读写标志位(1=读,0=写),低 7 位是寄存器地址。
- 第二个字节:数据字节。写操作时发送要写入的值,读操作时发送 0x00(或任意值)来产生时钟,同时接收从机返回的数据。
举个例子,读地址 0x42 的寄存器:
- 主机发 0x82(0x42 | 0x80),同时收一个字节(忽略)
- 主机发 0x00,同时收一个字节(这就是寄存器的值)
写地址 0x42 的寄存器,值为 0x1A:
- 主机发 0x42,同时收一个字节(忽略)
- 主机发 0x1A,同时收一个字节(忽略)
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]; // 第二个字节才是读回来的数据
}
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();
}
4.4 本章知识体系
下面这张图把本章的核心逻辑串起来了,你可以对照着看:
说白了,这一章就三件事:搞懂 SPI 怎么读写、记住常用寄存器地址、写好底层驱动函数。这三件事做好了,后面配置 LoRa 的各种工作模式、发射接收参数,就是往这些函数里填不同的数字而已。