STM8 + SX1278 LoRa点对点系统实现

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 测试步骤

  1. 硬件检查:用万用表测量SX1278的3.3V供电是否正常
  2. SPI通信测试:读取版本寄存器,应返回0x12
  3. 发射测试:用频谱仪观察是否有433MHz信号输出
  4. 接收测试:两台设备互相发送数据

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点对点系统具有以下特点:

完整协议栈:包含地址识别、序列号、应答机制
稳定可靠:中断驱动,避免轮询阻塞
易于扩展:模块化设计,支持多种传感器接入
低功耗设计:支持睡眠模式,延长电池寿命

应用场景

专注于matlab/simulink,电子电路,编程