LoRa模块+STM32驱动竟这么简单?SPI/UART双方案手把手教,小白也能玩转远距离通信

你有没有过这样的困惑:想让两个设备隔着几十上百米传数据,蓝牙嫌近、WiFi不稳、网线又没法拉?别慌!今天要聊的LoRa模块,堪称无线通信界的“长跑冠军”——低功耗、远距离、抗干扰,搭配STM32单片机使用,简直是物联网项目的“黄金搭档”!

不管你是刚入门的电子小白,还是想快速落地项目的工程师,这篇文章都能帮你搞定:两种最常用的LoRa模块(SX1278、E22-400T22S),从硬件接线到软件驱动,一步一步拆解得明明白白,代码直接能用,踩坑点提前预警,看完就能上手实操~

一、先搞懂:LoRa模块怎么选?通信接口有啥区别?

LoRa模块虽多,但核心玩法就两类,不同模块的“脾气”不一样,选对了能少走很多弯路,一张表帮你理清:

模块类型 通信接口 核心芯片 特点(大白话版)
SX1278/SX1276 SPI SX1278 能手动调各种底层参数,像“手动挡汽车”,灵活度拉满
E22-400T22S UART SX1278 用简单指令配置,数据直接透传,像“自动挡”,上手快
RA-01/RA-02 SPI SX1278 国产平替版,引脚和SX1278通用,性价比高

简单说:想自定义频率、扩频因子等细节,选SPI接口的SX1278;想快速开发,不用抠底层,选UART接口的E22系列,两种模块咱们都详细讲~

二、SPI驱动SX1278:手动挡玩家的“进阶玩法”

SX1278是LoRa模块里的“经典款”,通过SPI接口和STM32通信,需要搭配片选、中断、复位等引脚,虽然步骤多一点,但能精准控制每一个参数,成就感满满~

1. 硬件接线:别接错!这些坑千万别踩

先上接线表,以STM32F103为例,每根线的作用都标得明明白白:

STM32F103 引脚 SX1278 引脚 功能说明(通俗版)
PA4(NSS) NSS 选中模块(低电平有效,相当于“叫醒模块干活”)
PA5(SCK) SCK SPI时钟线,传递同步信号
PA6(MISO) MISO 模块给STM32发数据
PA7(MOSI) MOSI STM32给模块发数据
PB0 DIO0 收发完成“提醒”(中断信号,相当于“干活完喊你”)
PB1 RST 模块复位(低电平有效,相当于“重启模块”)

⚠️ 重点提醒(踩过的坑都给你列出来了):

  • SX1278是3.3V电平!千万别直接接5V电源或引脚,否则模块会“瞬间罢工”烧毁,血的教训~
  • DIO0引脚建议接STM32的外部中断引脚,收发数据完成后会自动触发中断,不用一直“盯着”模块;
  • 要是觉得中断麻烦,也能通过轮询寄存器状态判断收发是否完成,就是效率稍低一点~

2. 软件驱动:STM32 HAL库手把手教(代码直接抄)

咱们用CubeMX搭框架,再写驱动代码,步骤清晰,小白也能跟上:

步骤1:CubeMX配置“搭积木”
  1. 配置RCC:选外部晶振8MHz,系统时钟设为72MHz(STM32常用配置,稳定不翻车);
  2. 配置SPI1:
    • 模式:Full-Duplex Master(全双工主机,STM32当“指挥官”);
    • 时钟极性(CPOL):Low;时钟相位(CPHA):1Edge(SX1278默认支持,别乱改);
    • 波特率:2MHz(SX1278最大支持10MHz,选2MHz足够稳定);
    • 数据位:8位,MSB先行(通用配置,不用动);
  3. 配置GPIO:
    • PA4(NSS)、PB1(RST):设为GPIO_Output_PP(推挽输出,能直接控制高低电平);
    • PB0(DIO0):设为GPIO_EXTI0(外部中断,下降沿触发,相当于“模块喊一声就响应”);
  4. 生成MDK-ARM工程,准备写代码~
步骤2:底层驱动代码(操作SPI和引脚)

先定义引脚操作宏,再写SPI读写、寄存器读写、模块复位函数,代码都加了详细注释,看不懂的地方看注释就懂:

#include "main.h"
#include "spi.h"
#include "gpio.h"
#include "sx1278.h"

// 引脚操作宏定义:一句话控制引脚高低,不用写一堆重复代码
#define SX1278_NSS_HIGH()  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)
#define SX1278_NSS_LOW()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
#define SX1278_RST_HIGH()  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET)
#define SX1278_RST_LOW()   HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET)

/**
 * @brief  SPI读写1字节数据:STM32和SX1278“对话”的基础
 * @param  data:要发送给模块的字节
 * @retval 从模块接收的字节
 */
uint8_t SX1278_SPI_ReadWriteByte(uint8_t data)
{
    uint8_t recv_data;
    // 调用HAL库函数,同时收发1字节数据
    HAL_SPI_TransmitReceive(&hspi1, &data, &recv_data, 1, 100);
    return recv_data;
}

/**
 * @brief  写SX1278寄存器:给模块“发指令”(比如设置频率、功率)
 * @param  addr:寄存器地址(最高位0表示“写”)
 * @param  data:要写入的参数
 * @retval 无
 */
void SX1278_WriteReg(uint8_t addr, uint8_t data)
{
    SX1278_NSS_LOW();          // 选中模块,准备“说话”
    SX1278_SPI_ReadWriteByte(addr & 0x7F); // 告诉模块要写哪个寄存器(最高位0)
    SX1278_SPI_ReadWriteByte(data);        // 写入具体参数
    SX1278_NSS_HIGH();         // 说完了,取消选中
}

/**
 * @brief  读SX1278寄存器:看模块“状态”(比如是否收到数据)
 * @param  addr:寄存器地址(最高位1表示“读”)
 * @retval 寄存器里的状态数据
 */
uint8_t SX1278_ReadReg(uint8_t addr)
{
    uint8_t data;
    SX1278_NSS_LOW();          // 选中模块,准备“听它说”
    SX1278_SPI_ReadWriteByte(addr | 0x80); // 告诉模块要读哪个寄存器(最高位1)
    data = SX1278_SPI_ReadWriteByte(0xFF); // 读取模块返回的状态
    SX1278_NSS_HIGH();         // 听完了,取消选中
    return data;
}

/**
 * @brief  SX1278模块复位:模块“不听话”时重启一下
 * @param  无
 * @retval 无
 */
void SX1278_Reset(void)
{
    SX1278_RST_LOW();  // 拉低复位引脚,触发复位
    HAL_Delay(10);     // 等10ms,让模块彻底复位
    SX1278_RST_HIGH(); // 拉高引脚,结束复位
    HAL_Delay(100);    // 等100ms,让模块恢复就绪
}
步骤3:模块初始化(配置频率、功率等核心参数)

初始化是关键!要设置LoRa模式、通信频率、扩频因子、输出功率,这些参数收发双方必须一致,否则没法通信:

/**
 * @brief  SX1278初始化:给模块“定规矩”,让它按咱们的要求工作
 * @param  无
 * @retval 0:成功;1:失败(模块没响应)
 */
uint8_t SX1278_Init(void)
{
    SX1278_Reset(); // 先复位模块,清空之前的配置

    // 读取版本号:SX1278的版本号是0x12,能读到说明模块连好了
    if(SX1278_ReadReg(0x42) != 0x12)
    {
        return 1; // 没读到版本号,可能接线错了或模块坏了
    }

    // 1. 进入睡眠模式,准备配置寄存器(睡眠模式下配置更稳定)
    SX1278_WriteReg(0x00, 0x00); // 睡眠模式 + LoRa模式

    // 2. 配置频率为433MHz(常用免授权频段,不用申请)
    // 频率计算公式:Frf = Fosc * (FreqReg) / 2^19,Fosc=32MHz
    uint32_t freq = 433000000; // 433MHz
    uint32_t freq_reg = (freq * (1UL << 19)) / 32000000;
    SX1278_WriteReg(0x06, (freq_reg >> 16) & 0xFF); // 频率高位
    SX1278_WriteReg(0x07, (freq_reg >> 8) & 0xFF);  // 频率中位
    SX1278_WriteReg(0x08, freq_reg & 0xFF);         // 频率低位

    // 3. 配置通信参数:扩频因子SF7,带宽125kHz,纠错码4/5(平衡速率和稳定性)
    SX1278_WriteReg(0x1D, 0x72); // 带宽125kHz,SF7(SF越大,距离越远但速率越慢)
    SX1278_WriteReg(0x1E, 0x04); // 纠错码4/5(纠错能力越强,抗干扰越好)
    SX1278_WriteReg(0x26, 0x00); // SF<=7时,关闭低数据率优化

    // 4. 配置输出功率20dBm(最大输出功率,传得最远)
    SX1278_WriteReg(0x09, 0xFF); // 开启PA_BOOST模式(增强功率)
    SX1278_WriteReg(0x0A, 0x0F); // 输出功率20dBm

    // 5. 配置完成,进入连续接收模式,等待接收数据
    SX1278_WriteReg(0x00, 0x85); // 连续接收模式
    return 0; // 初始化成功
}
步骤4:收发数据函数(核心功能,直接用)

初始化完成后,就可以写收发数据的函数了,支持中断和轮询两种方式,这里以中断为例(效率更高):

#define LORA_RX_BUF_LEN 256 // 接收缓冲区大小,最大255字节
uint8_t lora_rx_buf[LORA_RX_BUF_LEN]; // 接收数据的“仓库”
uint8_t lora_rx_len = 0;              // 接收数据的长度

/**
 * @brief  发送LoRa数据:把数据发给另一个模块
 * @param  buf:要发送的数据缓冲区
 * @param  len:数据长度(最大255字节)
 * @retval 无
 */
void SX1278_SendData(uint8_t *buf, uint8_t len)
{
    SX1278_WriteReg(0x00, 0x01); // 进入待机模式,准备发送
    HAL_Delay(10);                // 等10ms,让模块切换模式

    SX1278_WriteReg(0x22, len); // 告诉模块要发送的数据长度
    SX1278_NSS_LOW();           // 选中模块,准备写数据到FIFO缓冲区
    SX1278_SPI_ReadWriteByte(0x00 & 0x7F); // 指向FIFO写地址
    for(uint8_t i=0; i<len; i++)
    {
        SX1278_SPI_ReadWriteByte(buf[i]); // 把数据逐个写入FIFO
    }
    SX1278_NSS_HIGH(); // 写完数据,取消选中

    SX1278_WriteReg(0x00, 0x83); // 进入发送模式,触发发送
    while((SX1278_ReadReg(0x12) & 0x08) == 0); // 等待发送完成
    SX1278_WriteReg(0x12, 0x08); // 清除发送完成中断标志
}

/**
 * @brief  接收LoRa数据(中断触发时调用)
 * @param  无
 * @retval 接收数据的长度
 */
uint8_t SX1278_ReceiveData(void)
{
    // 检查是否有接收完成中断(0x12寄存器的第6位为1表示收到数据)
    if(SX1278_ReadReg(0x12) & 0x40)
    {
        uint8_t len = SX1278_ReadReg(0x22); // 读取收到的数据长度
        SX1278_NSS_LOW();                   // 选中模块,准备读FIFO数据
        SX1278_SPI_ReadWriteByte(0x00 | 0x80); // 指向FIFO读地址
        for(uint8_t i=0; i<len; i++)
        {
            lora_rx_buf[i] = SX1278_SPI_ReadWriteByte(0xFF); // 逐个读取数据
        }
        SX1278_NSS_HIGH(); // 读完数据,取消选中
        lora_rx_len = len; // 记录接收长度
        SX1278_WriteReg(0x12, 0x40); // 清除接收完成中断标志
        return len;
    }
    return 0; // 没收到数据
}
步骤5:中断回调函数(让模块“主动喊”接收数据)

DIO0引脚接了外部中断,收到数据后模块会触发中断,咱们要在中断函数里调用接收数据函数:

// 在stm32f1xx_it.c文件中找到EXTI0_IRQHandler函数,修改如下
void EXTI0_IRQHandler(void)
{
    // 检查是否是EXTI0线的中断
    if(EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        SX1278_ReceiveData(); // 触发接收数据
        EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志,避免重复触发
    }
}

三、UART驱动E22模块:自动挡玩家的“躺平玩法”

如果你觉得SX1278的底层配置太麻烦,那E22-400T22S绝对是你的菜!它是串口透传模块,不用操作寄存器,用简单的AT指令就能配置,数据直接“原样转发”,开发速度翻倍~

1. 硬件连接:比SPI简单,4根线搞定

E22模块的接线更简单,主要是UART通信+模式控制引脚,同样以STM32F103为例:

STM32F103 引脚 E22 模块引脚 功能说明(通俗版)
PA9(USART1_TX) TX STM32发数据 → 模块收数据(注意:TX接RX!)
PA10(USART1_RX) RX STM32收数据 ← 模块发数据(交叉接线别搞反)
PB2 AUX 模块状态指示灯(高电平=就绪,能收发数据)
PB3 M0 模式配置(0=透传模式,1=配置模式)
PB4 M1 模式配置(0=正常模式,1=休眠模式)

⚠️ 重点提醒:

  • UART接线要“交叉”:STM32的TX接模块的RX,STM32的RX接模块的TX,接反了会完全收不到数据;
  • M0和M1引脚组合控制模块模式,配置时设为“11”,透传时设为“00”,别搞混了~

2. 软件驱动:AT指令+UART中断,简单到飞起

同样用CubeMX配置UART,再写驱动函数,核心是用AT指令配置模块,数据透传不用管底层:

步骤1:CubeMX配置UART
  1. 配置USART1:波特率9600(E22默认波特率),8位数据位,1位停止位,无校验(通用串口配置);
  2. 配置GPIO:PB2(AUX)、PB3(M0)、PB4(M1)设为GPIO_Output_PP(推挽输出);
  3. 开启UART接收中断(方便接收模块的响应和透传数据);
  4. 生成工程,准备写代码~
步骤2:核心驱动函数(AT指令配置+收发数据)

先定义引脚和缓冲区,再写模式切换、AT指令发送、收发数据函数,注释详细,直接抄就行:

#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "string.h"

// 引脚操作宏定义:控制模块模式
#define E22_M0_HIGH()  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET)
#define E22_M0_LOW()   HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET)
#define E22_M1_HIGH()  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_SET)
#define E22_M1_LOW()   HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_RESET)
#define E22_AUX_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) // 读模块状态

// UART接收缓冲区:存模块响应和透传数据
#define E22_RX_BUF_LEN 128
uint8_t e22_rx_buf[E22_RX_BUF_LEN];
uint8_t e22_rx_len = 0;

/**
 * @brief  进入配置模式:给模块“改设置”时用(M0=1,M1=1)
 * @param  无
 * @retval 无
 */
void E22_EnterConfigMode(void)
{
    E22_M0_HIGH();
    E22_M1_HIGH();
    HAL_Delay(100); // 等100ms,让模块稳定进入配置模式
}

/**
 * @brief  进入透传模式:正常收发数据时用(M0=0,M1=0)
 * @param  无
 * @retval 无
 */
void E22_EnterTransparentMode(void)
{
    E22_M0_LOW();
    E22_M1_LOW();
    HAL_Delay(100); // 等100ms,让模块切换到透传模式
}

/**
 * @brief  发送AT指令:给模块“下命令”(比如设置频率、地址)
 * @param  cmd:AT指令(比如"AT+FREQ=433\r\n")
 * @param  resp:接收模块的响应(比如"OK")
 * @param  timeout:超时时间(ms)
 * @retval 0:成功;1:失败(没收到响应)
 */
uint8_t E22_SendATCmd(uint8_t *cmd, uint8_t *resp, uint16_t timeout)
{
    e22_rx_len = 0; // 清空接收缓冲区
    // 发送AT指令
    HAL_UART_Transmit(&huart1, cmd, strlen((char*)cmd), 100);
    HAL_Delay(10); // 等10ms,让模块接收指令

    uint32_t tick = HAL_GetTick();
    // 等待模块响应,超时没响应就返回失败
    while((HAL_GetTick() - tick) < timeout)
    {
        if(e22_rx_len > 0) // 收到响应
        {
            memcpy(resp, e22_rx_buf, e22_rx_len); // 把响应存到resp缓冲区
            return 0;
        }
    }
    return 1;
}

/**
 * @brief  E22模块初始化:配置频率、地址,让模块“按规矩干活”
 * @param  无
 * @retval 0:成功;1:失败
 */
uint8_t E22_Init(void)
{
    uint8_t resp[32];
    E22_EnterConfigMode(); // 进入配置模式

    // 1. 发送AT指令,读取模块版本(验证模块是否在线)
    if(E22_SendATCmd((uint8_t*)"AT+VER\r\n", resp, 500) != 0)
    {
        return 1; // 没收到响应,模块可能没接好
    }

    // 2. 配置频率为433MHz(和SX1278保持一致,方便互通)
    if(E22_SendATCmd((uint8_t*)"AT+FREQ=433\r\n", resp, 500) != 0)
    {
        return 1;
    }

    // 3. 配置地址为0x0001(收发双方地址要一致,不然收不到数据)
    if(E22_SendATCmd((uint8_t*)"AT+ADDR=0001\r\n", resp, 500) != 0)
    {
        return 1;
    }

    E22_EnterTransparentMode(); // 配置完成,进入透传模式
    return 0;
}

/**
 * @brief  UART接收中断回调:模块发数据时,自动存到缓冲区
 * @param  huart:UART句柄
 * @retval 无
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1) // 确认是USART1的中断
    {
        if(e22_rx_len < E22_RX_BUF_LEN) // 缓冲区没满
        {
            // 把收到的数据存到缓冲区
            e22_rx_buf[e22_rx_len++] = huart->Instance->DR;
        }
        // 重新开启接收中断,持续接收数据
        HAL_UART_Receive_IT(&huart1, &e22_rx_buf[e22_rx_len], 1);
    }
}

/**
 * @brief  开启E22接收中断:让模块能“主动发”数据给STM32
 * @param  无
 * @retval 无
 */
void E22_StartRxIT(void)
{
    HAL_UART_Receive_IT(&huart1, e22_rx_buf, 1);
}

/**
 * @brief  发送透传数据:直接把数据发给模块,模块自动转发
 * @param  buf:要发送的数据缓冲区
 * @param  len:数据长度
 * @retval 无
 */
void E22_SendData(uint8_t *buf, uint8_t len)
{
    while(E22_AUX_READ() == GPIO_PIN_RESET); // 等模块就绪(AUX高电平)
    HAL_UART_Transmit(&huart1, buf, len, 100); // 发送数据
}

/**
 * @brief  接收透传数据:从缓冲区读取模块转发来的数据
 * @param  buf:接收数据的缓冲区
 * @retval 接收数据的长度
 */
uint8_t E22_ReceiveData(uint8_t *buf)
{
    if(e22_rx_len > 0) // 缓冲区有数据
    {
        memcpy(buf, e22_rx_buf, e22_rx_len); // 复制数据到用户缓冲区
        uint8_t len = e22_rx_len;
        e22_rx_len = 0; // 清空缓冲区,准备下次接收
        return len;
    }
    return 0; // 没收到数据
}
步骤3:主函数测试代码(直接跑通)

初始化完成后,主函数里就能直接收发数据了,测试代码如下:

int main(void)
{
    // 初始化HAL库
    HAL_Init();
    // 配置系统时钟
    SystemClock_Config();
    // 初始化GPIO
    MX_GPIO_Init();
    // 初始化USART1
    MX_USART1_UART_Init();

    E22_StartRxIT(); // 开启E22接收中断

    // 初始化E22模块,失败就一直循环(方便排查问题)
    if(E22_Init() != 0)
    {
        while(1);
    }

    uint8_t send_data[] = "LoRa Test Data"; // 要发送的数据
    uint8_t recv_data[32];                  // 接收数据的缓冲区

    while (1)
    {
        E22_SendData(send_data, sizeof(send_data)); // 发送数据
        HAL_Delay(1000); // 每隔1秒发一次

        uint8_t len = E22_ReceiveData(recv_data); // 接收数据
        if(len > 0) // 收到数据
        {
            // 这里可以加数据处理代码(比如打印、解析)
        }
    }
}

四、必看!这些坑千万别踩(血泪经验总结)

  1. 电源供电别马虎:LoRa模块发射时电流不小(SX1278发射20dBm时约120mA),电源输出能力要够,建议在模块电源引脚旁并一个100uF电解电容滤波,不然电压不稳会导致数据丢包;
  2. 天线必须接对:一定要用50Ω阻抗的LoRa天线(比如弹簧天线、PCB天线),没接天线或天线不匹配,不仅传不远,还会烧毁模块的功率放大器(血的教训!);
  3. 参数必须一致:收发双方的频率、扩频因子(SX1278)、带宽、纠错码、地址必须完全一致,差一个参数就没法通信(比如一方433MHz,一方470MHz,肯定收不到);
  4. 中断要消抖:SX1278的DIO0中断建议加消抖处理(比如延时1ms再读取状态),避免外界干扰导致误触发;
  5. 低功耗技巧:如果用电池供电,可把模块配置为休眠模式,定时唤醒收发数据(E22支持休眠模式,SX1278可通过寄存器配置),能大幅降低功耗。

五、常见问题排查(遇到问题不用慌)

  1. 模块不响应:检查电源是否3.3V、引脚接线是否正确(SPI的NSS/SCK,UART的TX/RX交叉)、模块是否复位成功;
  2. 收发无数据:核对收发双方的参数(频率、SF、地址)、天线是否接好、测试距离是否超出模块范围(一般空旷环境能传几百米,有遮挡会缩短);
  3. 数据丢包:增大扩频因子(比如把SF7改成SF12)、降低传输速率、提高纠错码等级(比如4/6),或启用E22模块的重传机制;
  4. 数据乱码:UART波特率、数据位、停止位配置不一致,或电源纹波太大(加滤波电容)。

看到这里,相信你已经掌握了两种LoRa模块和STM32的驱动方法~ 不管是想精准控制参数的“手动挡”玩家,还是想快速开发的“自动挡”玩家,都能找到适合自己的方案。赶紧拿起模块接线、烧录代码,体验远距离无线通信的乐趣吧!如果遇到问题,欢迎在评论区交流,咱们一起踩坑、一起解决~

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐