STM8 + SX1278 LoRa点对点系统实现
一、硬件连接与系统架构
1.1 硬件连接表
| SX1278模块 | STM8S103F3引脚 | 说明 |
|---|---|---|
| VCC | 3.3V | 电源(注意:SX1278为3.3V器件) |
| GND | GND | 共地 |
| SCK | PC5 (SPI_SCK) | SPI时钟 |
| MISO | PC7 (SPI_MISO) | 主入从出 |
| MOSI | PC6 (SPI_MOSI) | 主出从入 |
| NSS | PC4 (SPI_NSS) | 片选信号 |
| RST | PD0 | 复位引脚 |
| DIO0 | PD1 | 中断引脚(接收完成/发送完成) |
1.2 系统架构
┌─────────────┐ SPI ┌─────────────┐
│ STM8S103 │◄────────►│ SX1278 │
│ 主控芯片 │ │ LoRa模块 │
└──────┬──────┘ └──────┬──────┘
│ │
│ 串口调试 │ 无线传输
▼ ▼
┌─────────────┐ ┌─────────────┐
│ USB-TTL │ │ 另一台 │
│ 调试工具 │ │ SX1278设备 │
└─────────────┘ └─────────────┘
二、代码实现
2.1 头文件(sx1278.h)
#ifndef __SX1278_H
#define __SX1278_H
#include "stm8s.h"
// SX1278寄存器地址定义
#define REG_FIFO 0x00
#define REG_OP_MODE 0x01
#define REG_FRF_MSB 0x06
#define REG_FRF_MID 0x07
#define REG_FRF_LSB 0x08
#define REG_PA_CONFIG 0x09
#define REG_LNA 0x0C
#define REG_FIFO_ADDR_PTR 0x0D
#define REG_FIFO_TX_BASE_ADDR 0x0E
#define REG_FIFO_RX_BASE_ADDR 0x0F
#define REG_FIFO_RX_CURRENT_ADDR 0x10
#define REG_IRQ_FLAGS 0x12
#define REG_RX_NB_BYTES 0x13
#define REG_MODEM_CONFIG1 0x1D
#define REG_MODEM_CONFIG2 0x1E
#define REG_SYMB_TIMEOUT_LSB 0x1F
#define REG_PREAMBLE_MSB 0x20
#define REG_PREAMBLE_LSB 0x21
#define REG_PAYLOAD_LENGTH 0x22
#define REG_HOP_PERIOD 0x24
#define REG_IRQ_FLAGS_MASK 0x11
#define REG_DIO_MAPPING1 0x40
#define REG_VERSION 0x42
// 工作模式
#define MODE_LONG_RANGE_MODE 0x80
#define MODE_SLEEP 0x00
#define MODE_STDBY 0x01
#define MODE_TX 0x03
#define MODE_RX_CONTINUOUS 0x05
#define MODE_RX_SINGLE 0x06
// IRQ标志位
#define IRQ_TX_DONE_MASK 0x08
#define IRQ_RX_DONE_MASK 0x40
#define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20
// 频率定义(单位:Hz)
#define FREQ_433MHZ 433000000
#define FREQ_868MHZ 868000000
#define FREQ_915MHZ 915000000
// 结构体定义
typedef struct {
uint32_t frequency; // 频率
uint8_t spreadingFactor; // 扩频因子 (6-12)
uint8_t bandwidth; // 带宽 (0: 125kHz, 1: 250kHz, 2: 500kHz)
uint8_t codingRate; // 编码率 (1-4)
uint8_t power; // 发射功率 (0-15)
uint8_t preambleLength; // 前导码长度
uint8_t implicitHeader; // 隐式报头模式
uint8_t crcOn; // CRC校验
} LoRaConfig_t;
// 全局变量
extern volatile uint8_t rxDone;
extern volatile uint8_t txDone;
extern uint8_t rxBuffer[256];
extern uint8_t rxLength;
// 函数声明
void SX1278_Init(void);
void SX1278_SetConfig(LoRaConfig_t *config);
void SX1278_SendPacket(uint8_t *data, uint8_t length);
void SX1278_StartRx(void);
void SX1278_Sleep(void);
void SX1278_Standby(void);
uint8_t SX1278_GetVersion(void);
void SX1278_SetFrequency(uint32_t frequency);
void SX1278_SetPower(uint8_t power);
#endif /* __SX1278_H */
2.2 SPI驱动(spi.c)
#include "spi.h"
/**
* @brief SPI初始化
*/
void SPI_Init(void)
{
// 配置SPI引脚
// PC4: NSS (软件控制)
// PC5: SCK
// PC6: MOSI
// PC7: MISO
// 配置PC4为推挽输出
GPIOC->DDR |= (1 << 4); // 输出
GPIOC->CR1 |= (1 << 4); // 推挽输出
GPIOC->CR2 |= (1 << 4); // 高速输出
// 配置PC5为浮动输入(外部上拉)
GPIOC->DDR &= ~(1 << 5); // 输入
GPIOC->CR1 &= ~(1 << 5); // 浮动输入
// 配置PC6为推挽输出
GPIOC->DDR |= (1 << 6);
GPIOC->CR1 |= (1 << 6);
GPIOC->CR2 |= (1 << 6);
// 配置PC7为浮动输入
GPIOC->DDR &= ~(1 << 7);
GPIOC->CR1 &= ~(1 << 7);
// SPI配置
SPI->CR1 = 0x00; // 先清零
SPI->CR1 |= (1 << 2); // 主模式
SPI->CR1 |= (1 << 3); // 软件管理NSS
SPI->CR1 |= (1 << 1); // 先发送MSB
SPI->CR1 |= (1 << 0); // 波特率分频 fsys/2
SPI->CR2 = 0x00;
SPI->CR2 |= (1 << 7); // 双线单向模式
SPI->CR2 |= (1 << 2); // 使能主模式接收
// 使能SPI
SPI->CR1 |= (1 << 6);
}
/**
* @brief SPI发送接收一字节
* @param data: 发送的数据
* @retval 接收到的数据
*/
uint8_t SPI_Transfer(uint8_t data)
{
// 等待发送缓冲区为空
while (!(SPI->SR & (1 << 1))); // 等待TXE置位
// 发送数据
SPI->DR = data;
// 等待接收完成
while (!(SPI->SR & (1 << 0))); // 等待RXNE置位
// 返回接收到的数据
return SPI->DR;
}
/**
* @brief SPI片选控制
* @param state: 0-选中,1-取消选中
*/
void SPI_CS(uint8_t state)
{
if (state == 0)
GPIOC->ODR &= ~(1 << 4); // 拉低NSS
else
GPIOC->ODR |= (1 << 4); // 拉高NSS
}
2.3 SX1278驱动(sx1278.c)
#include "sx1278.h"
#include "spi.h"
#include "delay.h"
// 全局变量
volatile uint8_t rxDone = 0;
volatile uint8_t txDone = 0;
uint8_t rxBuffer[256];
uint8_t rxLength = 0;
// 私有函数
static void SX1278_WriteReg(uint8_t addr, uint8_t data);
static uint8_t SX1278_ReadReg(uint8_t addr);
static void SX1278_WriteFifo(uint8_t *data, uint8_t length);
static void SX1278_ReadFifo(uint8_t *buffer, uint8_t length);
static void SX1278_SetMode(uint8_t mode);
/**
* @brief 写寄存器
*/
static void SX1278_WriteReg(uint8_t addr, uint8_t data)
{
SPI_CS(0);
SPI_Transfer(addr | 0x80); // 最高位为1表示写
SPI_Transfer(data);
SPI_CS(1);
}
/**
* @brief 读寄存器
*/
static uint8_t SX1278_ReadReg(uint8_t addr)
{
uint8_t data;
SPI_CS(0);
SPI_Transfer(addr & 0x7F); // 最高位为0表示读
data = SPI_Transfer(0x00);
SPI_CS(1);
return data;
}
/**
* @brief 写FIFO
*/
static void SX1278_WriteFifo(uint8_t *data, uint8_t length)
{
SPI_CS(0);
SPI_Transfer(REG_FIFO | 0x80);
for (uint8_t i = 0; i < length; i++)
{
SPI_Transfer(data[i]);
}
SPI_CS(1);
}
/**
* @brief 读FIFO
*/
static void SX1278_ReadFifo(uint8_t *buffer, uint8_t length)
{
SPI_CS(0);
SPI_Transfer(REG_FIFO & 0x7F);
for (uint8_t i = 0; i < length; i++)
{
buffer[i] = SPI_Transfer(0x00);
}
SPI_CS(1);
}
/**
* @brief 设置工作模式
*/
static void SX1278_SetMode(uint8_t mode)
{
SX1278_WriteReg(REG_OP_MODE, mode | MODE_LONG_RANGE_MODE);
Delay_ms(10); // 等待模式切换完成
}
/**
* @brief SX1278初始化
*/
void SX1278_Init(void)
{
// 复位SX1278
GPIOD->DDR |= (1 << 0); // PD0为输出
GPIOD->CR1 |= (1 << 0); // 推挽输出
GPIOD->ODR &= ~(1 << 0); // 拉低复位
Delay_ms(10);
GPIOD->ODR |= (1 << 0); // 释放复位
Delay_ms(10);
// 配置DIO0为输入中断
GPIOD->DDR &= ~(1 << 1); // PD1为输入
GPIOD->CR1 |= (1 << 1); // 上拉输入
// 检查版本
uint8_t version = SX1278_GetVersion();
printf("SX1278 Version: 0x%02X\r\n", version);
// 设置为LoRa模式
SX1278_SetMode(MODE_SLEEP);
SX1278_WriteReg(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY);
// 设置FIFO基地址
SX1278_WriteReg(REG_FIFO_TX_BASE_ADDR, 0x00);
SX1278_WriteReg(REG_FIFO_RX_BASE_ADDR, 0x00);
// 默认配置
LoRaConfig_t defaultConfig = {
.frequency = FREQ_433MHZ,
.spreadingFactor = 7,
.bandwidth = 0, // 125kHz
.codingRate = 1, // 4/5
.power = 15, // 17dBm
.preambleLength = 8,
.implicitHeader = 0,
.crcOn = 1
};
SX1278_SetConfig(&defaultConfig);
// 配置DIO0映射
SX1278_WriteReg(REG_DIO_MAPPING1, 0x00); // DIO0映射为RxDone/TxDone
// 清除中断标志
SX1278_WriteReg(REG_IRQ_FLAGS, 0xFF);
printf("SX1278 Init Complete!\r\n");
}
/**
* @brief 设置LoRa配置
*/
void SX1278_SetConfig(LoRaConfig_t *config)
{
// 设置频率
SX1278_SetFrequency(config->frequency);
// 设置发射功率
SX1278_SetPower(config->power);
// 设置扩频因子、带宽、编码率
uint8_t modemConfig1 = (config->bandwidth << 4) | (config->codingRate << 1) | 0x01;
uint8_t modemConfig2 = (config->spreadingFactor << 4) | (config->crcOn << 2) | 0x03;
SX1278_WriteReg(REG_MODEM_CONFIG1, modemConfig1);
SX1278_WriteReg(REG_MODEM_CONFIG2, modemConfig2);
// 设置前导码长度
SX1278_WriteReg(REG_PREAMBLE_MSB, config->preambleLength >> 8);
SX1278_WriteReg(REG_PREAMBLE_LSB, config->preambleLength & 0xFF);
// 设置隐式/显式报头模式
if (config->implicitHeader)
SX1278_WriteReg(REG_MODEM_CONFIG1, modemConfig1 | 0x01);
// 设置符号超时
SX1278_WriteReg(REG_SYMB_TIMEOUT_LSB, 0x64); // 100 symbols
}
/**
* @brief 设置频率
*/
void SX1278_SetFrequency(uint32_t frequency)
{
uint64_t frf = ((uint64_t)frequency << 19) / 32000000;
SX1278_WriteReg(REG_FRF_MSB, frf >> 16);
SX1278_WriteReg(REG_FRF_MID, frf >> 8);
SX1278_WriteReg(REG_FRF_LSB, frf);
}
/**
* @brief 设置发射功率
*/
void SX1278_SetPower(uint8_t power)
{
if (power > 15) power = 15;
SX1278_WriteReg(REG_PA_CONFIG, 0x80 | power); // PA_BOOST使能
}
/**
* @brief 发送数据包
*/
void SX1278_SendPacket(uint8_t *data, uint8_t length)
{
// 设置为待机模式
SX1278_SetMode(MODE_STDBY);
// 设置FIFO指针
SX1278_WriteReg(REG_FIFO_ADDR_PTR, 0x00);
// 写入数据到FIFO
SX1278_WriteFifo(data, length);
// 设置负载长度
SX1278_WriteReg(REG_PAYLOAD_LENGTH, length);
// 清除中断标志
SX1278_WriteReg(REG_IRQ_FLAGS, 0xFF);
// 开始发送
SX1278_SetMode(MODE_TX);
txDone = 0;
}
/**
* @brief 开始接收
*/
void SX1278_StartRx(void)
{
// 设置为连续接收模式
SX1278_SetMode(MODE_RX_CONTINUOUS);
// 清除中断标志
SX1278_WriteReg(REG_IRQ_FLAGS, 0xFF);
rxDone = 0;
}
/**
* @brief 读取接收到的数据
*/
void SX1278_ReadPacket(void)
{
// 设置FIFO指针到当前接收地址
uint8_t currentAddr = SX1278_ReadReg(REG_FIFO_RX_CURRENT_ADDR);
SX1278_WriteReg(REG_FIFO_ADDR_PTR, currentAddr);
// 读取接收字节数
rxLength = SX1278_ReadReg(REG_RX_NB_BYTES);
// 读取数据
if (rxLength > 0 && rxLength <= 256)
{
SX1278_ReadFifo(rxBuffer, rxLength);
}
}
/**
* @brief 进入睡眠模式
*/
void SX1278_Sleep(void)
{
SX1278_SetMode(MODE_SLEEP);
}
/**
* @brief 进入待机模式
*/
void SX1278_Standby(void)
{
SX1278_SetMode(MODE_STDBY);
}
/**
* @brief 获取版本号
*/
uint8_t SX1278_GetVersion(void)
{
return SX1278_ReadReg(REG_VERSION);
}
2.4 主程序(main.c)
#include "stm8s.h"
#include "sx1278.h"
#include "spi.h"
#include "delay.h"
#include "uart.h"
// 设备地址(用于点对点通信)
#define DEVICE_ID 0x01 // 本机地址
#define TARGET_ID 0x02 // 目标设备地址
// 通信协议
typedef struct {
uint8_t destAddr; // 目标地址
uint8_t srcAddr; // 源地址
uint8_t seq; // 序列号
uint8_t cmd; // 命令字
uint8_t dataLen; // 数据长度
uint8_t data[32]; // 数据
} Packet_t;
volatile uint8_t sendTimer = 0;
uint8_t seqNum = 0;
/**
* @brief 构建数据包
*/
void BuildPacket(Packet_t *packet, uint8_t cmd, uint8_t *data, uint8_t len)
{
packet->destAddr = TARGET_ID;
packet->srcAddr = DEVICE_ID;
packet->seq = seqNum++;
packet->cmd = cmd;
packet->dataLen = len;
for (uint8_t i = 0; i < len; i++)
{
packet->data[i] = data[i];
}
}
/**
* @brief 解析数据包
*/
uint8_t ParsePacket(Packet_t *packet, uint8_t *rxData, uint8_t rxLen)
{
if (rxLen < 5) return 0;
packet->destAddr = rxData[0];
packet->srcAddr = rxData[1];
packet->seq = rxData[2];
packet->cmd = rxData[3];
packet->dataLen = rxData[4];
if (packet->dataLen > 32) packet->dataLen = 32;
for (uint8_t i = 0; i < packet->dataLen; i++)
{
packet->data[i] = rxData[5 + i];
}
// 检查是否是发给本机的数据
return (packet->destAddr == DEVICE_ID);
}
/**
* @brief 发送数据
*/
void SendData(uint8_t cmd, uint8_t *data, uint8_t len)
{
Packet_t packet;
BuildPacket(&packet, cmd, data, len);
// 构建发送缓冲区
uint8_t txBuffer[64];
uint8_t txLen = 0;
txBuffer[txLen++] = packet.destAddr;
txBuffer[txLen++] = packet.srcAddr;
txBuffer[txLen++] = packet.seq;
txBuffer[txLen++] = packet.cmd;
txBuffer[txLen++] = packet.dataLen;
for (uint8_t i = 0; i < packet.dataLen; i++)
{
txBuffer[txLen++] = packet.data[i];
}
// 发送数据
printf("Sending packet to device 0x%02X, Seq: %d\r\n",
packet.destAddr, packet.seq);
SX1278_SendPacket(txBuffer, txLen);
}
void main(void)
{
// 系统初始化
SystemInit();
Delay_Init();
UART_Init(9600);
SPI_Init();
printf("STM8 + SX1278 LoRa Point-to-Point System\r\n");
// 初始化SX1278
SX1278_Init();
// 开始接收
SX1278_StartRx();
uint8_t counter = 0;
Packet_t receivedPacket;
while (1)
{
// 检查是否接收到数据
if (rxDone)
{
rxDone = 0;
SX1278_ReadPacket();
if (ParsePacket(&receivedPacket, rxBuffer, rxLength))
{
printf("Received from device 0x%02X, Cmd: 0x%02X, Seq: %d\r\n",
receivedPacket.srcAddr, receivedPacket.cmd, receivedPacket.seq);
// 回复确认
uint8_t ackData[1] = {0xAA};
SendData(0x80 | receivedPacket.cmd, ackData, 1);
}
// 重新开始接收
SX1278_StartRx();
}
// 定时发送数据(每5秒)
if (sendTimer >= 50) // 50 * 100ms = 5s
{
sendTimer = 0;
uint8_t sensorData[4];
sensorData[0] = counter++;
sensorData[1] = 25; // 温度
sensorData[2] = 60; // 湿度
sensorData[3] = 1013; // 气压
printf("Sending sensor data...\r\n");
SendData(0x01, sensorData, 4);
}
Delay_ms(100);
sendTimer++;
}
}
// 中断服务函数
INTERRUPT_HANDLER(EXTI_PORTD_IRQHandler, 6)
{
if (GPIOD->IDR & (1 << 1)) // DIO0中断
{
uint8_t irqFlags = SX1278_ReadReg(REG_IRQ_FLAGS);
if (irqFlags & IRQ_RX_DONE_MASK)
{
rxDone = 1;
printf("RX Done!\r\n");
}
if (irqFlags & IRQ_TX_DONE_MASK)
{
txDone = 1;
printf("TX Done!\r\n");
SX1278_StartRx(); // 发送完成后开始接收
}
// 清除中断标志
SX1278_WriteReg(REG_IRQ_FLAGS, 0xFF);
}
}
2.5 延时函数(delay.c)
#include "delay.h"
static uint32_t timingDelay = 0;
void Delay_Init(void)
{
// 配置定时器4为1ms中断
TIM4->PSCR = 0x07; // 预分频器 = 128
TIM4->ARR = 0xFA; // 自动重装载值 = 250
TIM4->IER |= 0x01; // 使能更新中断
TIM4->CR1 |= 0x01; // 使能计数器
}
void Delay_ms(uint32_t nTime)
{
timingDelay = nTime;
while (timingDelay != 0);
}
void TimingDelay_Decrement(void)
{
if (timingDelay != 0)
{
timigDelay--;
}
}
// 定时器4中断
INTERRUPT_HANDLER(TIM4_IRQHandler, 23)
{
TimingDelay_Decrement();
TIM4->SR1 &= ~0x01; // 清除更新中断标志
}
2.6 串口驱动(uart.c)
#include "uart.h"
void UART_Init(uint32_t baudrate)
{
// 配置PD5为TX,PD6为RX
GPIOD->DDR |= (1 << 5); // PD5输出
GPIOD->CR1 |= (1 << 5); // 推挽输出
GPIOD->DDR &= ~(1 << 6); // PD6输入
GPIOD->CR1 |= (1 << 6); // 上拉输入
// 配置UART
UART1->BRR2 = 0x01;
UART1->BRR1 = 0x68; // 9600bps @ 16MHz
UART1->CR1 = 0x00; // 8位数据,无奇偶校验
UART1->CR2 = 0x0C; // 使能发送和接收
}
void UART_SendChar(char ch)
{
while (!(UART1->SR & 0x80)); // 等待发送缓冲区空
UART1->DR = ch;
}
void UART_SendString(char *str)
{
while (*str)
{
UART_SendChar(*str++);
}
}
// printf重定向
int putchar(int ch)
{
UART_SendChar((char)ch);
return ch;
}
参考代码 LoRa点对点系统源代码 www.youwenfan.com/contentcnu/56229.html
三、通信协议设计
3.1 数据包格式
| 目标地址(1B) | 源地址(1B) | 序列号(1B) | 命令字(1B) | 数据长度(1B) | 数据(N字节) |
3.2 命令字定义
| 命令字 | 说明 |
|---|---|
| 0x01 | 传感器数据上报 |
| 0x02 | 控制命令 |
| 0x03 | 配置命令 |
| 0x80 | 应答标志(与命令字或运算) |
四、调试与测试
4.1 测试步骤
- 硬件检查:用万用表测量SX1278的3.3V供电是否正常
- SPI通信测试:读取版本寄存器,应返回0x12
- 发射测试:用频谱仪观察是否有433MHz信号输出
- 接收测试:两台设备互相发送数据
4.2 常见问题
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 无法读取版本号 | SPI通信失败 | 检查SPI引脚连接和时序 |
| 发射无信号 | 频率配置错误 | 确认频率寄存器设置正确 |
| 接收不到数据 | 扩频因子不匹配 | 确保两台设备参数一致 |
| 通信距离短 | 天线不匹配 | 使用50Ω阻抗匹配的433MHz天线 |
五、性能优化
5.1 参数优化
| 应用场景 | 扩频因子 | 带宽 | 编码率 | 预期距离 |
|---|---|---|---|---|
| 室内近距离 | SF7 | 125kHz | 4/5 | 500m |
| 室外中距离 | SF9 | 125kHz | 4/5 | 2km |
| 远距离通信 | SF12 | 125kHz | 4/8 | 5km+ |
5.2 功耗优化
// 低功耗模式
void EnterLowPowerMode(void)
{
SX1278_Sleep(); // LoRa模块进入睡眠
// STM8进入停机模式
PWR->CSR2 |= 0x02; // 使能停机模式
asm("halt"); // 进入停机模式
}
六、总结
这套STM8+SX1278 LoRa点对点系统具有以下特点:
完整协议栈:包含地址识别、序列号、应答机制
稳定可靠:中断驱动,避免轮询阻塞
易于扩展:模块化设计,支持多种传感器接入
低功耗设计:支持睡眠模式,延长电池寿命
应用场景:
- 无线传感器网络
- 智能家居控制
- 工业数据采集
- 农业环境监测
- 资产追踪定位