小熊派-物联网+LoRa——实现LoRa组网(1.LoRa初始化)

ttking 发表于 2020/12/19 21:31:24 2020/12/19
【摘要】 LoRa作为低功耗广域网的一员,同NB-IoT一样在物联网的地位崇高;理论上通信距离最远可达15km,在城市中达3Km本贴,将使用小熊派作为嵌入式平台,采用安信可Ra01(其内核为SX127x芯片),以SPI方式对其相应的寄存器进行读写,完成基本环境搭建。

LoRa作为低功耗广域网的一员,同NB-IoT一样在物联网的地位崇高;理论上通信距离最远可达15km,在城市中达3Km。但因为工作在非工业授权频段(而NB-IoT工作于授权频段,三大移动运营商嘛),因此为保证稳定、合理的应用,使用起来就相对复杂。本贴,将使用小熊派作为嵌入式平台,采用安信可Ra01(其内核为SX127x芯片),以SPI方式对其相应的寄存器进行读写,完成基本环境搭建。

一、实验准备

1.实验环境

  • 一块stm32开发板(推荐使用小熊派-物联网·仲 BearPi-IoT Std),以及数据线

  • 已经安装STM32CubeMX

  • 已经安装KeilMDK,并导入stm32开发板对应的芯片包(嵌入式平台使用的是STM32L431RCT6)

  • 准备一个串口调试助手,我使用的是UartAssist(包含在附件中)

  • 一个安信可Ra01模块(或者其他的以SPI通信的lora模组)

image.png

image.png

2.目标效果

  • 通过CubeMX创建工程并配置参数

  • STM32以SPI协议完成对LoRa寄存器的读取与写入函数

  • 初始化LoRa模组引脚

二、通过CubeMX生产MDK工程

A.芯片选择

  • 打开CubeMX,进入芯片选择:

image.png

  • 选择自己的stm32芯片(即STM32L431RCT6):

image.png


B.时钟源RCC设置

  • 更改系统时钟源

系统时钟默认使用内部的高速时钟(HSI),选择使用HSE,时钟更精确

  • 设置外部时钟对应的端口

image.png

  • 配置时钟树

STM32L431RCT6系统时钟最大可以为80MHz,我们配置到最大即可

image.png


C.参数配置(对应端口设置)

1)配置USART1

使用USART,模式为异步,波特率为115200,无硬件流控制


image.png


2)配置SPI协议

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。

选择使用SPI1,但需要注意小熊派引出的spi引脚的映射。同时,不使用硬件片选方式,配置如下:

image.png

3)其他引脚

PA4作为SPI协议的软件片选;PB8作为LoRa模组DIO0引脚输入;PB9作为LoRa模组复位引脚

  • PA4->GPIO_Output
  • PB8->GPIO_Input
  • PB9->GPIO_Output

image.png

同时,为了方便引脚名字的使用,我们设置一下引脚的UserLabel
image.png

D.工程设置

一些基础的设置,包括工程名、存储位置、工程环境、工程中各个文件的组成


image.png


image.png


E.生成代码

image.png


三、代码补充

1. 串口1输出重定向

我们知道printf是打印函数,原理是根据传入的字符串参数格式化打印输出到stdout中。我们需要让printf打印到串口之中,只需要在usart.c文件中模仿printf写一个输出函数即可

  • 在添加头文件

/* USER CODE BEGIN 0 */

#include <stdarg.h>

#include <string.h>

#include <stdio.h>

/* USER CODE END 0 */
  • 写输出函数

/* USER CODE BEGIN 1 */

void UsartPrintf(UART_HandleTypeDef *huart, char *fmt,...)

{



    unsigned char UsartPrintfBuf[296];

    va_list ap;

    unsigned char *pStr = UsartPrintfBuf;



    va_start(ap, fmt);

    vsprintf((char *)UsartPrintfBuf, fmt, ap);    //格式化

    va_end(ap);



    while(*pStr != 0)

    {

        USART1->TDR = *pStr++;

        while((USART1->ISR & 0x40)  == 0);

    }

}

//使用方法:UsartPrintf(&huart1,"hello world\r\n");

/* USER CODE END 1 */

注意:自己添加的代码,需要在begin和end之间

2. 添加LoRa基本函数

为了方便管理,我们创建LoRa_hal.c和LoRa_hal.h两个并加入工程。主要参考LoRa sx1278/76官方驱动

在此代码中,主要为后面寄存器操作做铺垫;主要函数有:

  • 初始化引脚
  • SPI发送和接受
  • SPI片选
  • LoRa硬件复位
  • 读取DIO0电平
  • LoRa01Restart(),即LoRa复位函数
  • SX1278ReadBuffer(),即LoRa读寄存器函数
  • SX1278WriteBuffer(),即LoRa写寄存器函数

前5个函数就是普通的SPI、引脚操作,函数如下:

void LoRaInit( void )
{
    // Configure SPI-->NSS as output
    HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(RESET_GPIO_Port, RESET_Pin, GPIO_PIN_SET);
}


//spi发送和接收函数
uint8_t SpiInOut(uint8_t TxData)
{
    uint8_t Rxdata;
    HAL_SPI_TransmitReceive(&hspi1,&TxData,&Rxdata,1, 1000);      
    return Rxdata;    
}


//spi片选,status=0使能(NSS拉低)status=1失能(NSS拉高)
void SpiNSSEnable(GPIO_PinState status)
{
    HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, status);
}
//复位
void LoRa_ResetPinControl(GPIO_PinState status)
{
    HAL_GPIO_WritePin(RESET_GPIO_Port, RESET_Pin, status);
}
//读取DIO0电平,返回值0低电平,1高电平
uint8_t LoRaReadDio0(void)
{
    return HAL_GPIO_ReadPin(DIO0_GPIO_Port,DIO0_Pin);
}

LoRa复位函数

通过拉低复位引脚10ms后再拉高复位引脚,完成复位,并设置为空闲状态

void LoRa01Restart(void)
{
    LoRa_ResetPinControl(GPIO_PIN_RESET);
    HAL_Delay(10);
    LoRa_ResetPinControl(GPIO_PIN_SET);
    HAL_Delay(10);
    loraStatus = RFLR_STATE_IDLE; //复位后,处于空闲状态
}

LoRa写寄存器函数

需要注意,首先设置片选信号,之后向地址写入数据(地址不包含最高位,需要将最高位设置为1,即写)

static void SX1278WriteBuffer(uint8_t addr,uint8_t *buffer,uint8_t size)
{
    uint8_t i;
    //片选SPI1,NSS=0
    SpiNSSEnable(GPIO_PIN_RESET);

    SpiInOut(addr | 0x80);
    for(i=0;i<size;i++)
    {
        SpiInOut(buffer<i>); //写入数据
    }
    //NSS=1
    SpiNSSEnable(GPIO_PIN_SET);

}

LoRa读寄存器函数

需要注意,首先设置片选信号,之后向地址写入数据(地址不包含最高位,需要将最高位设置为0,即读)

static void SX1278ReadBuffer(uint8_t addr,uint8_t *buffer,uint8_t size)
{
    uint8_t i;
    SpiNSSEnable(GPIO_PIN_RESET);//片选SPI1,NSS=0
    SpiInOut(addr & 0x7F);
    for(i=0;i<size;i++)
    {
        buffer<i> = SpiInOut(0x00);
    }
    SpiNSSEnable(GPIO_PIN_SET);//NSS=1
}

最后,我们在优化一下读写函数,因为每次读和写都是8位的,因为默认读写的都是8位,函数如下:

//写sx1278寄存器
void Write127xReg(uint8_t addr,uint8_t data)
{
    SX1278WriteBuffer(addr,&data,1);
}
//读sx1278寄存器
uint8_t Read127xReg(uint8_t addr)
{
    uint8_t u8_recive;
    SX1278ReadBuffer(addr,&u8_recive,1);
    return u8_recive;
}

最后,在LoRa_hal.h中声明这些函数,供下一步调用


下面。我们完成对LoRa的初始化函数



一、前提准备

为了使得代码的逻辑更加清晰,我们这里在.h文件中定义三个结构体,用于存放LoRa的各个参数、任务状态以及工作模式。

typedef struct _sLoRaSettings
{
    uint32_t RFFrequency;        //频率,单位Hz
    int8_t Power;                        //发射功率2~20
    uint8_t SignalBw;       // 带宽 [0: 7.8 kHz, 1: 10.4 kHz, 2: 15.6 kHz, 3: 20.8 kHz, 4: 31.2 kHz,
                          // 5: 41.6 kHz, 6: 62.5 kHz, 7: 125 kHz, 8: 250 kHz, 9: 500 kHz, other: Reserved]  带宽
    uint8_t SpreadingFactor;// 扩频因子 [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096  chips] 扩频因子
    uint8_t ErrorCoding;    // 误码编码率 [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]    编码率
    uint16_t PreambleLength;    //前导码长度 
}tLoRaSettings;

//定义模块状态机
typedef enum
{
    RF_IDLE,    //空闲状态
    RF_BUSY,    //模块执行任务中
    RF_RX_DONE,    //接收完成
    RF_RX_TIMEOUT,    //接收超时
    RF_TX_DONE,    //发送完成
    RF_TX_TIMEOUT,    //发送超时
    RF_CAD_DETECTED,    //CAD检测到前导码
        RF_CAD_EMPTY,    //CAD检测完成,没有检测到前导码
        RF_CAD_TIMEOUT,    //CAD超时
        RF_UNKNOW_STATUS    //异常状态机
}tRFProcessReturnCodes;

//硬件工作模式
typedef enum{
    LORA_OPMODE_SLEEP=0,//睡眠
    LORA_OPMODE_STANDBY,//待机
    LORA_OPMODE_SYNTHESIZER_TX,
    LORA_OPMODE_TRANSMITTER,//发送
    LORA_OPMODE_SYNTHESIZER_RX,//
    LORA_OPMODE_RECEIVER,//接收
    LORA_OPMODE_RECIVER_SINGLE,//单次接收
    LORA_OPMODE_CAD,//CAD
}LoRaOpModeType;

同时,为了使得我们快速完成对各个寄存器的读写,我们通过宏定义来定义一些寄存器。

二、设置LoRa模式的函数

lora一共有八种模式,同时更具数据手册,初始化LoRa时需要不断改名其相应的模式

读写LoRa模式的函数如下:

//设置Lora模式
void SX127xSetLoRaMode(void)
{
    if(0 != (Read127xReg(REG_LR_OPMODE) & RFLR_OPMODE_LONGRANGEMODE_ON))
    {
        // &0x80  设置第七位,0-fsk,1-lora
        return; //如果已经是lora模式返回
    }
    SX127xSetOpMode(LORA_OPMODE_SLEEP);
    Write127xReg(REG_LR_OPMODE,Read127xReg(REG_LR_OPMODE) | RFLR_OPMODE_LONGRANGEMODE_ON);
    //先设置为sleep,然后设置为lora模式
}
//设置opMode
void SX127xSetOpMode(LoRaOpModeType opMode)
{
    if(opMode == SX127xGetOpMode())
    {
        return;
    }
    Write127xReg(REG_LR_OPMODE,(Read127xReg(REG_LR_OPMODE) & RFLR_OPMODE_MASK) | opMode | RFLR_OPMODE_FREQMODE_ACCESS_LF);
    HAL_Delay(1);
}
//获取opMode
LoRaOpModeType SX127xGetOpMode(void)
{
    return (LoRaOpModeType)(Read127xReg(REG_LR_OPMODE) & RFLR_OPMODE_MASK);
}

三、设置载波频率

LoRa工作在非工业授权频段,最好不要将频率设置为32的整数倍。需要向载波频率地址0x06开始连续写三个数,来表示频率。

void SX127xSetFrf(void)
{
    SX127xSetOpMode(LORA_OPMODE_SLEEP);
    Write127xReg( REG_LR_FRFMSB, Frequency[0]);//射频载波频率最高有效位
    Write127xReg( REG_LR_FRFMID, Frequency[1]);//射频载波频率中间有效位
    Write127xReg( REG_LR_FRFLSB, Frequency[2]);//射频载波频率最低有效位
}

四、初始化函数

初始化函数,参入一共结构体参数,完成对LoRa的载波频率、带宽、纠错码、CRC校验、超时时间、超时时间、功率、前导码长度设置。

//SX127x初始化
void LoRa01Init(tLoRaSettings *stting)
{
    LoRaInit(); //完成初始化
    LoRa01Restart();
    while(0x6c!=Read127xReg(0x06))
    {
        UsartPrintf(&huart1,"error %d pi error\r\n",Read127xReg(0x06));
        HAL_Delay(100);
    }
    UsartPrintf(&huart1,"SPI init OK\r\n");

    SX127xSetLoRaMode(); //设置为lora模式

    if(NULL != stting)
    {//复制配置信息
        memcpy(&localSettingSave,stting,sizeof(tLoRaSettings));
    }
    //setting指向备份数据,避免修改导致setting原值改变
    stting = &localSettingSave;

    if(stting->SignalBw > 9) //带宽不大于9
    {
        UsartPrintf(&huart1,"WARRING SignalBw setting error,auto fix\r\n");
        stting ->SignalBw = 9;
    }
    if(stting->ErrorCoding > 4)
    {
        UsartPrintf(&huart1,"WARRING ErrorCoding setting error,auto fix\r\n");
        stting->ErrorCoding = 4;
    }
    if(stting->ErrorCoding < 1)
    {
        UsartPrintf(&huart1,"WARRING ErrorCoding setting error,auto fix\r\n");
        stting->ErrorCoding = 1;
    }
    if(stting->SpreadingFactor > 12)
    {
        UsartPrintf(&huart1,"WARRING SpreadingFactor setting error,auto fix\r\n");
        stting->SpreadingFactor = 12;
    }
    if(stting->SpreadingFactor <6)
    {
        UsartPrintf(&huart1,"WARRING SpreadingFactor setting error,auto fix\r\n");
        stting->SpreadingFactor = 6;
    }
    if(stting->Power > 20)
    {
        UsartPrintf(&huart1,"WARRING Power setting error,auto fix\r\n");
        stting->Power = 20;
    }
    SX127xSetFrf(stting->RFFrequency); //设置载波频率
    Write127xReg(REG_LR_MODEMCONFIG1,u8_BWList[stting->SignalBw] | u8_CRList[stting->ErrorCoding -1] | RFLR_MODEMCONFIG1_IMPLICITHEADER_OFF);//带宽、纠错码
    Write127xReg(REG_LR_MODEMCONFIG2,u8_SFList[stting->SpreadingFactor-6] | RFLR_MODEMCONFIG2_TXCONTINUOUSMODE_OFF|RFLR_MODEMCONFIG2_RXPAYLOADCRC_ON|0x03);//设置SD,CRC,超时时间
    Write127xReg(REG_LR_SYMBTIMEOUTLSB,0xFF);//设置超时时间
    Write127xReg(REG_LR_MODEMCONFIG3,0x0C);//设置低速率(包长超过16ms必须打开),开启低速率

    if(stting->Power > 17)
    {
        Write127xReg(REG_LR_PACONFIG,0x80+stting -> Power - 5);
        Write127xReg(0x4d,0x87);//更改为更大功率
    }else{
        Write127xReg(REG_LR_PACONFIG,0x80+stting->Power-2);
        Write127xReg(0x4d,0x84);  //关闭更大功率
    }

    Write127xReg(REG_LR_OCP,0x3B); //过流保护

    //设置前导码长度
    Write127xReg(REG_LR_PREAMBLELSB,stting->PreambleLength >> 8); //高八位前导码
    Write127xReg(REG_LR_PREAMBLELSB,stting->PreambleLength&0x00ff);    //第八位前导码
}

注意,这里的

    while(0x6c!=Read127xReg(0x06))
    {
        UsartPrintf(&huart1,"error %d pi error\r\n",Read127xReg(0x06));
        HAL_Delay(100);
    }

是为了等待SPI协议正确读取,可以读取其他的寄存器如版本号寄存器。











最后:编译+下载

点击编译后,0 error,0 warning


image.png

通过本文,完成对LoRa模组的底层驱动并完成LoRa模组的初始化函数,即对LoRa模组的载波频率、带宽、纠错码、CRC校验、超时时间、超时时间、功率、前导码长度等设置

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。