Lora通信应用开发
时间:2022-12-17 05:30:00
概述:
1.了解Lora技术基础知识
2.了解通信协议的用途
3.掌握Lora模块SPI配置方法
4.掌握简单Lora对传模块数据的方法
5.掌握Lora使用通信协议的方法
一、什么是LoRa
LoRa(LongRangeRadio,远距离无线电)是一种基于扩频技术的远距离无线传输技术LPWAN其中一种通信技术是Semtech公司创建的低功耗局域网无线标准。该方案为用户提供了实现远距离、低功耗无线通信的简单手段。它最大的特点是在相同的功耗条件下,它远离其他无线传输,实现了低功耗和远距离的统一。在相同的功耗下,它比传统的无线射频通信距离扩大了3~5倍。目前,LoRa主要在ISM主要包括433MHz、868MHz、915MHz等。
2.LoRa的特性
传输距离:城镇可达2~5km,郊区可达15km;
工作频率:ISM频段,包括433MHz、868MHz、915MHz等;
标准:IEEE802.15.4g;
调制方法:基于扩频技术,线性调制扩频(CSS)有前向纠错的变种(FEC)能力,是Semtech公司私有专利技术;
容量:一个LoRa网关可以连接成千上万的网关LoRa节点;
电池寿命:10年;
安全:AES128加密;
传输速率:几十到几百kbit/s,传输距离越低,速率越长。
二、Lora和LoraWAN
Lora是LoraWAN子集,Lora仅包括物理定义,LoraWAN包括链路层定义。其实LoraWAN它不是一个完整的通信协议,因为它只定义物理层和链路层,没有网络层和传输层,没有完美的功能,没有漫游,没有网络管理和其他重要的通信协议功能。
三、Lora模块
采用的LSD4RF-2F717N30是LoRaSX1278470M100mW基于标准模块Semtech射频集成芯片SX127X射频模块是高性能物联网无线收发器。
1.SX1276/77/78收发器
SX1276/77/78是137~1020MHz主要采用低功耗远距离收发器LoRa超长距离扩频通信采用远程调制解调器,抗干扰性强,能最大限度地减少电流消耗。
借助Semtech的LoRa专利调制技术,SX1276/77/78使用低成本的物体和材料可获得超过-148dBm高灵敏度。此外,高灵敏度和20dBm功率放大器的集成使这些设备的链路预算达到行业领先水平,成为远程传输和高可靠性应用的最佳选择。与传统的调制技术相比,LoRa调制技术在抗堵塞和选择性方面也具有明显的优势,解决了传统设计方案不能同时考虑距离、抗干扰和功耗的问题。
支持这些设备WM-BusIIEEE802.15.4g高性能等系统(G)FSK模型。与同类设备相比,SX在大幅降低电流消耗的基础上,1276/77/78显著优化了相位噪声、选择性、接收机线性、三级输人截取点(IIP3)等。
SX1276的带宽范围为7.8~500kHz,扩频因子为6~12,覆盖所有可用频段。SX1277段范围及1277SX1276相同,但扩频因子为6~9。SX选择1278带宽和扩频因子SX1276是一样的,但只覆盖UHF频段。
(1)关键产品特性
LoRa调制解调器;
最大链路预算可达168dB;
20dBm-100mW恒定的射频功率输出;
14dBm高效功率放大器;
可编程比率高达3000kbit/s;
高灵敏度:低至-148dBm;
高可靠性的前端:IIP3=-11dBm;
优异的阻塞特性;
9.9mA低接收电流,200nA寄存器保持电流;
分辨率为61Hz、全集成频率合成器;
支持FSK、GFSK、MSK、GMSK、LoRa及OOK调制方式:
时钟恢复采用内置同步;
前导码检测:
127dB的RSSI动态范围:
自动射频信号检测,CAD模型和超高速AFC;
带有CRC、高达256B数据包引擎;
内置温度传感器和低功率指示器。
(2)应用
远程灌溉系统;
自动抄表;
家庭和建筑自动化:
无线报警和安全系统;
工业监测与控制;
四、SPI
1.SPI简介
LoRa芯片与MCU通过SPI进行通信。SPI(SerialPeripheralInterfaceBus)由摩托罗拉公司开发的高速全双工同步串行通信协议。SPI支持一主多从,类似于IC,但是又与I2C选通从设备的方式不同,IC通过发送从机地址选择从机,SPI它通过拉低连接到从机NSS引脚选择从机。SPI一般应用由四个引脚组成(一主一从):
SCLK(SerialClock):主机发出串行时钟;
MOSI(MasterOutput,SlaveInput):主机输出从机输入信号,由主机发出;
MISO(MasterInput,SlaveOutput):主机输入从机输出信号,由从机输出:NSS:由主机发出的信号选择,一般低电位有效。
2. SPI代码配置
(1)引脚初始化
通过SpiInit()实现此函数BoardDisableIrq( )中调用。
void SpiInit( Spi_t *obj, PinNames mosi, PinNames miso, PinNames sclk, PinNames nss )参数说明:Spi_t *obj:指向待初始化SPI结构体;PinNames mosi:主机输出从机输入引脚;PinNames miso:主机输入从机输出引脚;PinNames sclk:串行时钟引脚;PinNames nss:片选引脚;
void SpiInit( Spi_t *obj, PinNames mosi, PinNames miso, PinNames sclk, PinNames nss ) { BoardDisableIrq( );//禁止中断 // Choose SPI interface according to the given pins if( mosi == PA_7 ) { __HAL_RCC_SPI1_FORCE_RESET( ); __HAL_RCC_SPI1_RELEASE_RESET( ); __HAL_RCC_SPI1_CLK_ENABLE( ); obj->Spi.Instance = ( SPI_TypeDef* )SPI1_BASE;//建立SPI,也就是obj就是SPI1 //将GPIO口初始化 GpioInit( &obj->Mosi, mosi, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI1 ); GpioInit( &obj->Miso, miso, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI1 ); GpioInit( &obj->Sclk, sclk, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI1 ); GpioInit( &obj->Nss, nss, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_UP, GPIO_AF5_SPI1 ); if( nss == NC ) { obj->Spi.Init.NSS = SPI_NSS_SOFT;//NSS软件单独控制选片信号 SpiFormat( obj, SPI_DATASIZE_8BIT, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, 0 );//设置SPI主机模式配置通信模式 } else { SpiFormat( obj, SPI_DATASIZE_8BIT, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, 1 );//设置SPI通讯方式,配置为从机模式 } } else if( mosi == PB_15 ) {///初始化SPI2 __HAL_RCC_SPI2_FORCE_RESET( ); __HAL_RCC_SPI2_RELEASE_RESET( ); __HAL_RCC_SPI2_CLK_ENABLE( ); obj->Spi.Instance = ( SPI_TypeDef* )SPI2_BASE;//建立SPI obj也就是SPI2 //将GPIO口初始化 GpioInit( &obj->Mosi, mosi, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI2 ); GpioInit( &obj->Miso, miso, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI2 ); GpioInit( &obj->Sclk, sclk, PIN_ALTERNATE_FCT PIN_PUSH_PULL, PIN_PULL_DOWN, GPIO_AF5_SPI2 );
GpioInit( &obj->Nss, nss, PIN_ALTERNATE_FCT, PIN_PUSH_PULL, PIN_PULL_UP, GPIO_AF5_SPI2 );
if( nss == NC )
{
obj->Spi.Init.NSS = SPI_NSS_SOFT;//NSS片选信号由软件单独控制
SpiFormat( obj, SPI_DATASIZE_8BIT, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, 0 );//设置SPI通讯方式,配置为主机模式
}
else
{
SpiFormat( obj, SPI_DATASIZE_8BIT, SPI_POLARITY_LOW, SPI_PHASE_1EDGE, 1 );//设置SPI通讯方式,配置为从机模式
}
}
SpiFrequency( obj, 10000000 );//设置SPI速度
HAL_SPI_Init( &obj->Spi );//生效
BoardEnableIrq( );//使能中断
}
(2)设置SPI通讯方式
SpiFormat( Spi_t *obj, int8_t bits, int8_t cpol, int8_t cpha, int8_t slave )中各参数如下:
Spi_t *obj:SPI结构体;
int8_t bits:帧格式,选择8位
int8_t cpol:设置时钟极性。这里是低电平
int8_t slave:主从模式,0主,1从
(3)Lora调制解调
(1)频率:频率建议在433MHZ附近,也430,431,432,用户根据设置频率确定合适的信道。
(2)发射功率:Lora发射功率由参数TX_OUTPUT_POWER;这个值越大,传输的距离越远,最大值不超过20dBm。
(3)Lora数据包结构:
3.编写关键函数 NS_Radio.c
void NS_RadioEventsInit( void )//射频初始化函数
{
// Radio initialization
RadioEvents.TxDone = OnTxDone;
RadioEvents.RxDone = OnRxDone;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxTimeout = OnRxTimeout;
RadioEvents.RxError = OnRxError;
Radio.Init( &RadioEvents );
}
void OnTxDone( void )//发送完成调用此函数
{
Radio.Sleep( );
Radio.Rx( RX_TIMEOUT_VALUE );
}
void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )//接收完成调用,读取数据、数据长度、信号强度、信噪比。
{
Radio.Sleep( );
LoRaBufferSize = size;
memcpy( LoRaBuffer, payload, LoRaBufferSize );
RssiValue = rssi;
SnrValue = snr;
// printf( "Rx=%s\r\nRssiValue=%d\r\nSnrValue=%d\r\n",LoRaBuffer,RssiValue,SnrValue );
Radio.Rx( RX_TIMEOUT_VALUE );
}
void OnTxTimeout( void )//发送超时调用
{
Radio.Sleep( );
Radio.Rx( RX_TIMEOUT_VALUE );
}
void OnRxTimeout( void )//接收超时调用
{
Radio.Sleep( );
Radio.Rx( RX_TIMEOUT_VALUE );
}
void OnRxError( void )//接收错误调用
{
Radio.Sleep( );
Radio.Rx( RX_TIMEOUT_VALUE );
}
项目实验
项目介绍:方圆5平方千米的植物园,粗放式管理,管委会要求对园区环境进行检测,温湿度光照等。要求:低成本,节约经费;先实现点对点,能够在上位机进行数据获取,后期升级位云平台获取。
(1) 关键接口函数解析:
void LoRa_Send( uint8_t *TxBuffer, uint8_t len )//TxBuffer是一个指针,指向用户需要发送的Lora无线数据首地址。
{
Radio.Send( TxBuffer, len);
}
void MyRadioRxDoneProcess( void )//接收Lora无线数据,用户需要在函数中解析无线数据的功能代码或函数。
{
uint16_t BufferSize = 0;
uint8_t RxBuffer[BUFFER_SIZE];
BufferSize = ReadRadioRxBuffer( (uint8_t *)RxBuffer );
if(BufferSize>0)
{
//用户在此处添加接收数据处理功能的代码
;
printf("LoRa TempRh\r\n");
hal_temHumInit();//初始化温湿度模块
connectionreset();//重置温湿度模块IIC通信
Tim3McuInit(1);//定时器初始化,设置定时中断1ms中断一次
}
}
void PlatformInit(void)//硬件平台初始化
{
// 开发板平台初始化
BoardInitMcu();
BoardInitPeriph();
// 开发板设备初始化
OLED_Init();//液晶初始化
USART1_Init(115200);//串口1初始化
OLED_Clear();
OLED_InitView();//OLED屏幕显示初始信息
printf("新大陆教育 LoRa \r\n");
//Lora模块初始化
NS_RadioInit( (uint32_t) RF_PING_PONG_FREQUENCY, (int8_t) TX_OUTPUT_POWER, (uint32_t) TX_TIMEOUT_VALUE, (uint32_t) RX_TIMEOUT_VALUE );
//请在下方添加用户初始化代码
//IWDG_PrmInit(2048);//独立看门狗初始化,超时设置为2048ms
}
//-----------------------------------------------------------
//根据通信协议制定响应命令结构
#define START_HEAD 0x55//帧头
#define CMD_READ 0x01//读数据
#define ACK_OK 0x00//响应OK
#define ACK_NONE 0x01//无数据
#define ACK_ERR 0x02//数据错误
//定义网络编号和设备地址
#define MY_NET_ID 0xD0C2 //网络ID
#define MY_ADDR 0x01 //设备地址
/*全局变量*/
int8_t temperature = 25; //温度,单位:℃
int8_t humidity = 60; //湿度,单位:%
//函数声明
void LoRa_Send( uint8_t *TxBuffer, uint8_t len );
void MyRadioRxDoneProcess( void );
void OLED_InitView(void);
void PlatformInit(void);
uint8_t CheckSum(uint8_t *buf, uint8_t len)//计算校验和
{
uint8_t temp = 0;
while(len--)
{
temp += *buf;
buf++;
}
return (uint8_t)temp;
}
/**********************************************************************************************
*函数:uint8_t *ExtractCmdframe(uint8_t *buf, uint8_t len, uint8_t CmdStart)
*功能:从一串数据中提取出命令帧出现起始地址
*输入:
* uint8_t *buf,指向待提取的数据缓冲区地址
* uint8_t len,缓冲区数据长度
* uint8_t CmdStart,命令帧起始字节
*输出:无
*返回:返回首次出现命令帧的地址,若数据中无命令帧数据,则返回NULL
*特殊说明:无
**********************************************************************************************/
uint8_t *ExtractCmdframe(uint8_t *buf, uint8_t len, uint8_t CmdStart)
{
uint8_t *point = NULL;
uint8_t i;
for(i=0; i
(2)请求命令参照数据通信结构,传感器接收网关读传感数据命令后,节点需要按照通信规约格式上报网关。请求命令解析数据代码如下:
//--------------------------------------------------------
//数据解析
void LoRa_DataParse( uint8_t *LoRaRxBuf, uint16_t len )
{
uint8_t *DestData = NULL;
#define HEAD_DATA *DestData //帧头
#define CMD_DATA *(DestData+1) //命令
#define NETH_DATA *(DestData+2) //网络ID高字节
#define NETL_DATA *(DestData+3) //网络ID低字节
#define ADDR_DATA *(DestData+4) //地址
#define ACK_DATA *(DestData+5) //响应
#define LEN_DATA *(DestData+6) //长度
#define DATASTAR_DATA *(DestData+7) //数据域起始
DestData = ExtractCmdframe((uint8_t *)LoRaRxBuf, len, START_HEAD);//输入数据 长度 帧给DestData
if(DestData != NULL)//检索到数据帧头
{
if((DestData - LoRaRxBuf) > (len - 6)) return;//数据长度不足构成一帧完整数据
if(CMD_DATA != CMD_READ) return;//命令错误
if(CheckSum((uint8_t *)DestData, 5) != (*(DestData+5))) return;//校验不通过,仅适用于校验读数据命令的校验
if(((((uint16_t)NETH_DATA)<<8)+NETL_DATA) != MY_NET_ID) return;//网络ID不一致
//发送读响应
if(ADDR_DATA != MY_ADDR) return;//地址不一致
/--------------------------------------------------------------------
//根据通信协议发送数据又称响应
uint8_t RspBuf[BUFFER_SIZE]= {0};
memset(RspBuf, '\0', BUFFER_SIZE);
RspBuf[0]=START_HEAD;//帧头
RspBuf[1]=CMD_READ;//命令 ,0x01读传感数据
RspBuf[2]=(uint8_t)(MY_NET_ID>>8);//网络ID低字节
RspBuf[3]=(uint8_t)MY_NET_ID;//网络ID高字节
RspBuf[4]=MY_ADDR;//地址
RspBuf[5]=ACK_OK;//响应OK 0x00
sprintf((char *)(RspBuf+7),"temperature(℃):%d|humidity(%%):%d", temperature, humidity);//数据域,sprintf中,两个“%”表示输出“%”。在采集传感函数中已经将采集值放入这两个变量中,发送即可。
RspBuf[6]=strlen((const char *)(RspBuf+7))+1;//数据域长度
RspBuf[6+RspBuf[6]]=CheckSum((uint8_t *)RspBuf, 6+RspBuf[6]);
Radio.Send( RspBuf, 7+RspBuf[6]);//发送响应数据
GpioToggle( &Led1 );//发送数据切换亮灯指示
}
}
(3)主机接收代码如下:
/**********************************************************************************************
*函数:void MyRadioRxDoneProcess( void )
*功能:无线模块数据接收完成处理进程函数
*输入:无
*输出:无
*返回:无
*特殊说明:接收到的无线数据保存在RxBuffer中,BufferSize为接收到的无线数据长度
**********************************************************************************************/
void MyRadioRxDoneProcess( void )
{
uint16_t BufferSize = 0;
uint8_t RxBuffer[BUFFER_SIZE];
BufferSize = ReadRadioRxBuffer( (uint8_t *)RxBuffer );
if(BufferSize>0)
{
//用户在此处添加接收数据处理功能的代码
GpioToggle( &Led2 );//收到数据切换亮灯指示
LoRa_DataParse( (uint8_t *)RxBuffer, BufferSize );//调用数据解析函数
}
}
(4)主函数将硬件平台初始化,不断循环接收发送函数。
//-----------------------------获取传传感器数据------------------------
void LoRa_GetSensorDataProcess(void)
{
const uint16_t time = 1000;
if(User0Timer_MS > time) //1ms进中断User0Timer_MS ++;
{
User0Timer_MS = 0;
uint16_t Temp, Rh;
call_sht11((uint16_t *)(&Temp), (uint16_t *)(&Rh));//采集温湿度数据
temperature = (int8_t)Temp; //温度,单位:℃ 将采集值传给前边定义的两个变量
humidity = (int8_t)Rh; //湿度,单位:% 将采集值传给前边定义的两个变量
//------------------oled显示--------------------------------------------
char StrBuf[64]= {0};
memset(StrBuf, '\0', 64);
sprintf(StrBuf, " %d DegrCe",temperature);
OLED_ShowString(0,4,(uint8_t *)StrBuf);//OLED显示当前温度
memset(StrBuf, '\0', 64);
sprintf(StrBuf, " %d %%",humidity);
OLED_ShowString(0,6,(uint8_t *)StrBuf);//OLED显示当前相对湿度
}
}
int main( void )
{
PlatformInit();
while( 1 )
{
//IWDG_PrmRefresh( );//喂独立看门狗
MyRadioRxDoneProcess();//LoRa无线射频接收数据处理进程
LoRa_GetSensorDataProcess();
}
}