文档
DX-LR22-433T22S LoRa 模块驱动代码
以下代码基于 ESP32 (Arduino框架) 编写,使用 HardwareSerial2 连接 DX-LR22 模块,实现透传收发、帧同步状态机、AT 指令配置及诊断功能。
1. 头文件 dx_lr22.h
/**
* @file dx_lr22.h
* @brief DX-LR22-433T22S 大夏龙雀 LoRa 透传模块驱动
* @note 基于 SX1262,UART AT 透传,433MHz / +22dBm
*/
#ifndef DX_LR22_H__
#define DX_LR22_H__
#include <Arduino.h>
// ===================== 硬件引脚定义 (按实际接线修改) =====================
#ifndef LORA_UART_RX
#define LORA_UART_RX 16 // ESP32 GPIO16 → 模块 TXD
#endif
#ifndef LORA_UART_TX
#define LORA_UART_TX 17 // ESP32 GPIO17 → 模块 RXD
#endif
#ifndef LORA_AUX_PIN
#define LORA_AUX_PIN 4 // ESP32 GPIO4 ← 模块 AUX (可选, 状态检测)
#endif
#ifndef LORA_MD0_PIN
#define LORA_MD0_PIN 18 // 模式配置脚 MD0
#endif
#ifndef LORA_MD1_PIN
#define LORA_MD1_PIN 19 // 模式配置脚 MD1
#endif
// ===================== 常量配置 =====================
#define LORA_UART_BAUD 9600 // 默认透传波特率
#define LORA_SERIAL Serial2 // ESP32 HardwareSerial2
#define LORA_RX_BUF_SIZE 1024 // 接收缓冲区大小
// ===================== 帧协议常量 =====================
#define FRAME_SYNC0 0xA5 // 同步字第一字节
#define FRAME_SYNC1 0x5A // 同步字第二字节
#define FRAME_PREAMBLE 0xAA // 前导填充字节
// ===================== 返回码 =====================
enum {
LORA_OK = 0,
LORA_TIMEOUT = -1,
LORA_BUSY = -2,
LORA_ERROR = -3,
};
// ===================== 工作模式 =====================
enum LoraMode : uint8_t {
MODE_TRANSPARENT = 0x00, // MD1=0 MD0=0 透传
MODE_AT = 0x01, // MD1=0 MD0=1 AT配置
MODE_CONFIG2 = 0x02, // MD1=1 MD0=0 保留
MODE_DEEPSLEEP = 0x03, // MD1=1 MD0=1 深度休眠
};
// ===================== 帧接收状态机状态 =====================
enum RxState : int {
ST_SYNC0, // 等待 0xA5
ST_SYNC1, // 等待 0x5A
ST_BODY, // 接收帧体
};
// ===================== API 声明 =====================
// 初始化
void dx_lr22_init(void);
void dx_lr22_setMode(LoraMode mode);
// 透传发送
void dx_lr22_send(const uint8_t *data, uint16_t len);
void dx_lr22_sendStr(const char *str);
// 透传接收 (非阻塞)
bool dx_lr22_recv(uint8_t *frame, uint16_t frame_size);
// AT 指令
int dx_lr22_sendAT(const char *cmd, uint32_t timeout_ms = 500);
int dx_lr22_checkResponse(const char *expected, uint32_t timeout_ms = 500);
int dx_lr22_atCmd(const char *cmd, char *resp, uint16_t respMax, uint32_t timeout_ms = 500);
// 状态与诊断
bool dx_lr22_isIdle(void);
uint32_t dx_lr22_diag_bytes(void);
int dx_lr22_getRSSI(void);
// 循环回调 (放在 loop() 中用于异步处理)
void dx_lr22_task(void);
#endif // DX_LR22_H__
2. 驱动实现 dx_lr22.cpp
/**
* @file dx_lr22.cpp
* @brief DX-LR22-433T22S 驱动实现
*/
#include "dx_lr22.h"
// ===================== 内部状态 =====================
static RxState rx_state = ST_SYNC0;
static int rx_idx = 0;
static uint32_t raw_bytes = 0;
static uint8_t rx_buf[256]; // AT响应缓存
static uint16_t rx_buf_len = 0;
// ===================== 初始化 =====================
void dx_lr22_init(void)
{
// 配置模式引脚
pinMode(LORA_MD0_PIN, OUTPUT);
pinMode(LORA_MD1_PIN, OUTPUT);
// 默认进入透传模式
dx_lr22_setMode(MODE_TRANSPARENT);
// 初始化 AUX 引脚 (输入上拉)
pinMode(LORA_AUX_PIN, INPUT_PULLUP);
// 配置 UART
LORA_SERIAL.setRxBufferSize(LORA_RX_BUF_SIZE);
LORA_SERIAL.begin(LORA_UART_BAUD, SERIAL_8N1, LORA_UART_RX, LORA_UART_TX);
Serial.printf("[LORA] Serial2 initialized: %u baud, RX=GPIO%d, TX=GPIO%d\n",
(unsigned)LORA_UART_BAUD, LORA_UART_RX, LORA_UART_TX);
// 排空上电残留字节
delay(100);
while (LORA_SERIAL.available()) {
LORA_SERIAL.read();
}
raw_bytes = 0;
rx_buf_len = 0;
rx_state = ST_SYNC0;
rx_idx = 0;
Serial.println("[LORA] DX-LR22 initialized OK");
}
// ===================== 模式切换 =====================
void dx_lr22_setMode(LoraMode mode)
{
digitalWrite(LORA_MD0_PIN, (mode & 0x01) ? HIGH : LOW);
digitalWrite(LORA_MD1_PIN, (mode & 0x02) ? HIGH : LOW);
delay(20); // 模式切换稳定时间
}
// ===================== AUX 状态读取 =====================
bool dx_lr22_isIdle(void)
{
// AUX 高电平 = 空闲可写
return digitalRead(LORA_AUX_PIN) == HIGH;
}
// ===================== 透传发送 =====================
void dx_lr22_send(const uint8_t *data, uint16_t len)
{
// 4 字节前导 → 帮助对端 LoRa 模块 RF 链路训练
static const uint8_t preamble[4] = { 0xAA, 0xAA, 0xAA, 0xAA };
// 等待 AUX 空闲 (简单超时保护)
uint32_t t0 = millis();
while (!dx_lr22_isIdle()) {
if (millis() - t0 > 100) {
Serial.println("[LORA] WARNING: AUX timeout, force send");
break;
}
delay(1);
}
LORA_SERIAL.write(preamble, sizeof(preamble));
LORA_SERIAL.write(data, len);
LORA_SERIAL.flush();
}
void dx_lr22_sendStr(const char *str)
{
dx_lr22_send((const uint8_t *)str, strlen(str));
}
// ===================== 透传接收 (帧同步状态机) =====================
bool dx_lr22_recv(uint8_t *frame, uint16_t frame_size)
{
while (LORA_SERIAL.available()) {
uint8_t b = (uint8_t)LORA_SERIAL.read();
raw_bytes++;
switch (rx_state) {
case ST_SYNC0:
if (b == FRAME_SYNC0) {
rx_state = ST_SYNC1;
}
break;
case ST_SYNC1:
if (b == FRAME_SYNC1) {
// 同步字匹配, 开始接收帧体
frame[0] = FRAME_SYNC0;
frame[1] = FRAME_SYNC1;
rx_idx = 2;
rx_state = ST_BODY;
} else if (b == FRAME_SYNC0) {
// 保持 ST_SYNC1, 可能连续收到同步字
rx_state = ST_SYNC1;
} else {
rx_state = ST_SYNC0;
}
break;
case ST_BODY:
frame[rx_idx++] = b;
if (rx_idx >= frame_size) {
rx_state = ST_SYNC0;
return true; // 帧接收完成
}
break;
default:
rx_state = ST_SYNC0;
break;
}
}
return false;
}
// ===================== AT 指令 =====================
int dx_lr22_sendAT(const char *cmd, uint32_t timeout_ms)
{
rx_buf_len = 0;
// 确保在 AT 模式
dx_lr22_setMode(MODE_AT);
delay(10);
LORA_SERIAL.print(cmd);
LORA_SERIAL.print("\r\n");
LORA_SERIAL.flush();
(void)timeout_ms;
return LORA_OK;
}
int dx_lr22_checkResponse(const char *expected, uint32_t timeout_ms)
{
uint32_t start = millis();
while ((millis() - start) < timeout_ms) {
while (LORA_SERIAL.available()) {
char c = (char)LORA_SERIAL.read();
if (rx_buf_len < sizeof(rx_buf) - 1) {
rx_buf[rx_buf_len++] = (uint8_t)c;
}
}
rx_buf[rx_buf_len] = '\0';
if (rx_buf_len > 0 && strstr((char *)rx_buf, expected) != NULL) {
return LORA_OK;
}
delay(10);
}
return LORA_TIMEOUT;
}
int dx_lr22_atCmd(const char *cmd, char *resp, uint16_t respMax, uint32_t timeout_ms)
{
// 切换到 AT 模式
dx_lr22_setMode(MODE_AT);
delay(10);
// 排空缓冲区
while (LORA_SERIAL.available()) LORA_SERIAL.read();
rx_buf_len = 0;
// 发送指令
LORA_SERIAL.print(cmd);
LORA_SERIAL.print("\r\n");
LORA_SERIAL.flush();
// 等待响应
uint32_t start = millis();
while ((millis() - start) < timeout_ms) {
while (LORA_SERIAL.available()) {
char c = (char)LORA_SERIAL.read();
if (rx_buf_len < sizeof(rx_buf) - 1) {
rx_buf[rx_buf_len++] = (uint8_t)c;
}
}
rx_buf[rx_buf_len] = '\0';
// 检测结束标志 (+OK 或 +ERR)
if (strstr((char *)rx_buf, "+OK") || strstr((char *)rx_buf, "+ERR")) {
if (resp) {
uint16_t copyLen = min(rx_buf_len, (uint16_t)(respMax - 1));
memcpy(resp, rx_buf, copyLen);
resp[copyLen] = '\0';
}
return (strstr((char *)rx_buf, "+OK")) ? LORA_OK : LORA_ERROR;
}
delay(10);
}
// 超时
if (resp && rx_buf_len > 0) {
uint16_t copyLen = min(rx_buf_len, (uint16_t)(respMax - 1));
memcpy(resp, rx_buf, copyLen);
resp[copyLen] = '\0';
}
return LORA_TIMEOUT;
}
// ===================== RSSI 查询 =====================
int dx_lr22_getRSSI(void)
{
char resp[64];
int ret = dx_lr22_atCmd("AT+RSSI?", resp, sizeof(resp), 500);
if (ret == LORA_OK) {
// 解析 "+OK=RSSI:-85dBm" 之类
int rssi = 0;
if (sscanf(resp, "+OK=RSSI:%d", &rssi) == 1) {
return rssi;
}
}
return 0; // 无效值
}
// ===================== 诊断 =====================
uint32_t dx_lr22_diag_bytes(void)
{
uint32_t n = raw_bytes;
raw_bytes = 0;
return n;
}
// ===================== 异步任务 (周期性调用) =====================
void dx_lr22_task(void)
{
// 在透传模式下, 这里可以处理帧分发逻辑
// 例: 收帧 → 校验 → 回调上层
// 具体实现取决于应用帧格式
}
3. 完整应用示例 main.ino
/**
* @file main.ino
* @brief DX-LR22-433T22S 完整使用示例
*
* 硬件连接 (ESP32):
* GPIO16 ← 模块 TXD
* GPIO17 → 模块 RXD
* GPIO4 ← 模块 AUX
* GPIO18 → 模块 MD0
* GPIO19 → 模块 MD1
*
* 流程:
* 1. 上电后进入 AT 模式, 查询/配置模块参数
* 2. 切换到透传模式
* 3. 周期性发送传感器数据
* 4. 非阻塞接收远端数据
*/
#include "dx_lr22.h"
// ===================== 应用帧格式 =====================
#pragma pack(push, 1)
typedef struct {
uint8_t sync0; // 0xA5
uint8_t sync1; // 0x5A
uint8_t type; // 帧类型: 0x01=传感器上报, 0x02=控制指令
uint16_t seq; // 序列号
float temperature; // 温度 °C
float humidity; // 湿度 %
uint16_t battery_mv; // 电池电压 mV
uint16_t crc16; // CRC16 校验
} sensor_frame_t;
#pragma pack(pop)
#define SENSOR_FRAME_SIZE sizeof(sensor_frame_t)
static_assert(SENSOR_FRAME_SIZE == 17, "Frame size mismatch");
// CRC16-CCITT 查表实现
static uint16_t crc16_ccitt(const uint8_t *data, uint16_t len)
{
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < len; i++) {
crc ^= (uint16_t)data[i] << 8;
for (uint8_t j = 0; j < 8; j++) {
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : crc << 1;
}
}
return crc;
}
// ===================== 全局变量 =====================
static sensor_frame_t rx_frame;
static uint16_t tx_seq = 0;
// ===================== 模拟传感器读取 =====================
static float read_temperature(void) { return 25.6f + (random(-50, 50) / 100.0f); }
static float read_humidity(void) { return 58.2f + (random(-30, 30) / 100.0f); }
static uint16_t read_battery(void) { return 3300; }
// ===================== 发送传感器帧 =====================
static void send_sensor_frame(void)
{
sensor_frame_t tx;
tx.sync0 = FRAME_SYNC0;
tx.sync1 = FRAME_SYNC1;
tx.type = 0x01; // 传感器上报
tx.seq = tx_seq++;
tx.temperature = read_temperature();
tx.humidity = read_humidity();
tx.battery_mv = read_battery();
// 计算 CRC (跳过 CRC 字段自身)
tx.crc16 = crc16_ccitt((uint8_t *)&tx, SENSOR_FRAME_SIZE - 2);
dx_lr22_send((uint8_t *)&tx, SENSOR_FRAME_SIZE);
Serial.printf("[TX] seq=%u temp=%.1f°C hum=%.1f%% batt=%umV\n",
tx.seq, tx.temperature, tx.humidity, tx.battery_mv);
}
// ===================== 处理接收帧 =====================
static void handle_frame(const sensor_frame_t *f)
{
// CRC 校验
uint16_t crc_calc = crc16_ccitt((const uint8_t *)f, SENSOR_FRAME_SIZE - 2);
if (crc_calc != f->crc16) {
Serial.printf("[RX] CRC FAIL: calc=0x%04X got=0x%04X\n", crc_calc, f->crc16);
return;
}
Serial.printf("[RX] seq=%u type=%u temp=%.1f°C hum=%.1f%% batt=%umV\n",
f->seq, f->type, f->temperature, f->humidity, f->battery_mv);
}
// ===================== AT 配置流程 =====================
static void configure_module(void)
{
char resp[128];
int ret;
Serial.println("=== DX-LR22 AT Configuration ===");
// 1. 测试通信
ret = dx_lr22_atCmd("AT", resp, sizeof(resp), 500);
Serial.printf("AT test: %s → %s\n", (ret == LORA_OK) ? "OK" : "FAIL", resp);
// 2. 查询版本
ret = dx_lr22_atCmd("AT+VER?", resp, sizeof(resp), 500);
Serial.printf("Firmware: %s\n", resp);
// 3. 查询当前频率
ret = dx_lr22_atCmd("AT+FREQ?", resp, sizeof(resp), 500);
Serial.printf("Frequency: %s\n", resp);
// 4. 查询扩频因子
ret = dx_lr22_atCmd("AT+SF?", resp, sizeof(resp), 500);
Serial.printf("Spreading Factor: %s\n", resp);
// 5. 查询带宽
ret = dx_lr22_atCmd("AT+BW?", resp, sizeof(resp), 500);
Serial.printf("Bandwidth: %s\n", resp);
// 6. 查询功率
ret = dx_lr22_atCmd("AT+POWER?", resp, sizeof(resp), 500);
Serial.printf("TX Power: %s\n", resp);
// 7. 查询 RSSI (当前环境底噪)
ret = dx_lr22_atCmd("AT+RSSI?", resp, sizeof(resp), 500);
Serial.printf("RSSI: %s\n", resp);
Serial.println("=== Configuration Done ===");
}
// ===================== Setup =====================
void setup()
{
Serial.begin(115200);
delay(500);
Serial.println("\n===================================");
Serial.println(" DX-LR22-433T22S LoRa Demo");
Serial.println("===================================");
// 随机种子
randomSeed(analogRead(0));
// 初始化 LoRa 模块 (默认进入透传模式)
dx_lr22_init();
// 进入 AT 模式进行配置
configure_module();
// 切回透传模式
dx_lr22_setMode(MODE_TRANSPARENT);
Serial.println("[MAIN] Enter transparent mode, ready to communicate.");
}
// ===================== Loop =====================
void loop()
{
static uint32_t last_tx_time = 0;
static uint32_t last_diag_time = 0;
// ---- 非阻塞接收 ----
if (dx_lr22_recv((uint8_t *)&rx_frame, SENSOR_FRAME_SIZE)) {
handle_frame(&rx_frame);
}
// ---- 周期性发送 (每 5 秒) ----
if (millis() - last_tx_time >= 5000) {
last_tx_time = millis();
send_sensor_frame();
}
// ---- 诊断打印 (每 10 秒) ----
if (millis() - last_diag_time >= 10000) {
last_diag_time = millis();
uint32_t bytes = dx_lr22_diag_bytes();
Serial.printf("[DIAG] UART raw bytes in 10s: %u\n", (unsigned)bytes);
}
// ---- 异步任务 ----
dx_lr22_task();
delay(10); // 避免空转占满 CPU
}
4. 使用说明
4.1 编译与烧录
- 将
dx_lr22.h和dx_lr22.cpp放入 Arduino 项目的src/目录 main.ino放在项目根目录- 根据实际接线修改
dx_lr22.h中的 GPIO 宏定义 - 选择 ESP32 Dev Module → 编译上传
4.2 两模块对传测试
- 准备两套 ESP32 + DX-LR22 模块
- 两模块参数保持一致:频率 433MHz、SF12、BW125kHz
- 烧录同一份固件
- 打开串口监视器,观察每 5 秒收发一次传感器帧
4.3 关键调试点
| 现象 | 排查方向 |
|---|---|
| 收不到数据 | 检查天线是否接好;双方频率/SF/BW是否一致;AUX是否正常 |
| 帧 CRC 频繁失败 | 空中误码率高 → 降低SF或减小距离再测 |
| AT 指令无响应 | 确认 MD1=0 MD0=1;波特率9600;模块供电正常 |
| AUX 始终为低 | 模块可能处于繁忙/死锁 → 硬件复位后再试 |
4.4 前导码 + 同步字设计说明
本驱动采用 4字节前导(0xAA) + 2字节同步字(0xA5 0x5A) 的帧格式。设计考量:
- 0xAA 前导: 0xAA = 0b10101010,提供丰富的边沿跳变,帮助对端 LoRa 模块的 PLL 和 AGC 快速锁定。
- 0xA5 0x5A 同步字: 互补字节对,汉明距离大,误同步概率低。状态机在 ST_SYNC0→ST_SYNC1→ST_BODY 三级跳转中能正确处理同步字内嵌和噪声干扰场景。
- 帧体 CRC: 应用层做 CRC16 校验,确保数据完整性(LoRa 物理层虽有 CRC,但端到端校验更可靠)。