概述:
1.了解Lora技术的基本知识
2.了解通信协议用途
3.掌握Lora模块SPI配置方法
4.掌握简单的Lora模块数据对传方法
5.掌握Lora通讯协议使用方法
一、什么是LoRa
LoRa(Long Range Radio,远距离无线电)是一种基于扩频技术的远距离无线传输技术,是LPWAN通信技术中的一种,是Semtech公司创建的低功耗局域网无线标准。这一方案为用户提供一种简单的能实现远距离、低功耗无线通信的手段。它最大的特点就是在同样的功耗条件下比其他无线方式传播的距离更远,实现了低功耗和远距离的统一,它在同样的功耗下比传统的无线射频通信距离扩大3~5倍。目前,LoRa主要在ISM频段运行,主要包括433MHz、868MHz、915MHz等。
2.LoRa的特性
传输距离:城镇中可达2~5km,郊区可达15km;
工作频率:ISM 频段,包括433MHz、868MHz、915MHz等;
标准:IEEE 802.15.4g;
调制方式:基于扩频技术,是线性调制扩频(CSS)的一个变种,具有前向纠错(FEC)能力,是Semtech公司私有专利技术;
容量:一个LoRa网关可以连接成千上万个LoRa节点;
电池寿命:长达10年;
安全:AES128加密;
传输速率:几十到几百kbit/s,速率越低传输距离越长。
二、Lora和LoraWAN
Lora是LoraWAN的一个子集,Lora仅包括物理层定义,LoraWAN包括链路层定义。其实LoraWAN不是完整的通通信协议,因为他只定义了物理层和链路层,网络层和传输层没有,功能也并不完善,也没有漫游,没有组网管理等通信协议重要功能。
三、Lora模块
采用的LSD4RF-2F717N30是LoRa SX1278 470M100mW标准模块,是基于Semtech射频集成芯片SX127X的射频模块,是一款高性能物联网无线收发器。
1.SX1276/77/78收发器
SX1276/77/78是137~1020MHz低功耗远距离收发器,主要采用LoRa远程调制解调器,用于超长距离扩频通信,抗干扰性强,能够最大限度地降低电流消耗。
借助Semtech的LoRa专利调制技术,SX1276/77/78采用低成本的品体和物料即可获得超过-148dBm的高灵敏度。此外,高灵敏度与20dBm功率放大器的集成便这些器件的链路预算达到了行业领先水平,成为远距离传输和对可靠性要求极高的应用的最佳选择。相较传统调制技术,LoRa调制技术在抗阻塞和选择性方面也具有明显优势,解决了传统设计方案无法同时兼顾距离、抗干扰和功耗的问题。
这些器件还支持WM-BusIIEEE 802.15.4g等系统的高性能(G)FSK模式。与同类器件相比,SX1276/77/78在大幅降低电流消耗的基础上,还显著优化了相位噪声、选择性、接收机线性度、三阶输人截取点(IIP3)等各项性能。
SX1276的带宽范围为7.8~500kHz,扩频因子为6~12,并覆盖所有可用频段。SX1277的带宽和频段范围与SX1276相同,但扩频因子为6~9。SX1278的带宽和扩频因子选择与SX1276相同,但仅覆盖UHF频段。
(1)关键产品特性
LoRa调制解调器;
最大链路预算可达168dB;
20dBm-100mW电压变化时恒定的射频功率输出;
14dBm的高效率功率放大器;
可编程比特率高达300kbit/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(Serial Peripheral Interface Bus)是由摩托罗拉公司开发的高速全双工同步串行通信协议。SPI支持一主多从,这点类似于IC,但是又与I2C选通从设备的方式不同,IC是通过发送从机地址来选通从机,而SPI是通过拉低连接到从机的NSS引脚对从机进行选通的。SPI一般应用由四个引脚组成(一主一从):
SCLK(Serial Clock):串行时钟,由主机发出;
MOSI(Master Output,Slave Input):主机输出从机输入信号,由主机发出;
MISO(Master Input,Slave Output):主机输入从机输出信号,由从机发出: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 pinsif( 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 initializationRadioEvents.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<len; i++){if(CmdStart == *buf){point = buf;return point;}}return NULL;
}/**********************************************************************************************
*函数:uint16_t GetHexStr(uint8 *input, uint16_t len, uint8 *output)
*功能:生成数组的16进制形式的字符串格式,成员间以空格隔开
* 例如:由{0xA1,0xB2,0xC3}生成"A1 B2 C3"
*输入:uint8 *input-指向输入缓存区, uint16_t len-输入数据的字节长度
*输出:uint8 *output-指向输出缓存区
*返回:返回生成字符串的长度
*特殊说明:无
**********************************************************************************************/
uint16_t GetHexStr(uint8_t *input, uint16_t len, uint8_t *output)
{for(uint16_t i=0; i<len; i++){sprintf((char *)(output+i*3),"%02X ", *input);input++;}return strlen((const char *)output);
}
(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);//输入数据 长度 帧给DestDataif(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();}
}