物联网实战--驱动篇之(三)LoRa(sx1278)

目录

一、LoRa简介

二、sx1278模块

三、硬件抽象层

四、SX1278初始化

五、发送时间计算

六、发送模式

七、接收模式

八、总结


一、LoRa简介

        LoRa在物联网传输领域有着举足轻重的地位,平时大家可能比较少听说,因为它主要还是在行业应用,跟后面会讲的NB-Iot差不多,主要特点都是低功耗、广域网。它们的差别在于,NB-Iot是运营商主导的,规则得按他们的来,还得流量费;LoRa是相对自由的,可以用LoRaWAN,也可以自组网,灵活多变。在使用场景上,有交集,但大部分是互补,NB-Iot适合政企项目,比如远程抄表,节点成千上万个;LoRa适合节点几百个的小规模自组网,它的优势在于不用担心有没有运营商的基站信号,到哪里都可以部署,特别是郊区、户外、地下配电房这些容易没有信号的地方,还有一个优势是不用流量费,相对来讲成本有一定优势,再者,数据可以不用经过互联网平台,一定程度上更安全。

        当然了,这不是绝对的,很多时候是要看甲方的选择,比如我家的水表就是LoRa的。

二、sx1278模块

        sx1278是比较经典的LoRa芯片,大部分人也是从这款芯片开始接触LoRa的,正如上面所说的,正因为足够自由开放,所以用起来还是很麻烦的,寄存器一大堆,各种状态切换,参数设置,刚开始学一头雾水,这里是中文资料,自己先过过眼。https://download.csdn.net/download/ypp240124016/89095343

        根据使用经验,我先提炼出一些关键信息,如果只是应用,了解这些信息也就差不多了。

1、sx1278采用SPI通讯,SPI是一种类似于IIC的通讯总线,核心还是初始化+读写数据,STM32配置SPI很简单的,后面代码会体现;

2、sx1278有两类无线调制模式,一种是传统的FSK等模式,类似于NRF24L01,传输距离比较近;另一种就是LoRa模式,通过扩频技术,增加链路的鲁棒性和接收端的灵敏度,达到远距离传输的目的,对于我们来讲,都是用LoRa模式的,不然没必要用这款芯片了,成本比较高;

3、两个sx1278模块之间要通讯的话,它们的这几个参数要一致,频率、带宽、扩频因子和纠错编码率,一般纠错编码率都是固定的,可以忽略,剩下的3个参数是我们在使用中要经常接触的内容;

4、具体地,sx1278的频率(Freq)范围是137~525MHz,但是根据我国的频段使用规范,正常分为433M和475M两个免费段,合计范围是下图中的410~525M,其实核心还是看模块厂家给的参数了,然后结合测试情况和当地法规自己看下这里的频段哪些比较合适。正常这么宽的范围足够使用了。

5、扩频因子(SF)如下图所示,我们主要关注的是最左侧一列,设置参数范围一般是7~12,这里6比较特殊,一般没怎么用,要用的话设置繁琐一点。扩频因子对传输的影响是 数值越高速度越慢,距离越远,即其它条件固定的情况下,扩频因子为12时比7传的远,但是速度慢。

6、带宽(BW)比较好理解了,直接跟速度挂钩的,好比河道宽度跟水流量的关系。如下定义,我们设置的范围一般是4~9,9速度最快,4是极限了,很多厂家的模块用不了4,会收不到,跟模块用料有关系,一般也用不到这么低了,正常6差不多了。

7、SX1278的数据缓冲区最大是256字节,也就是一次最多发送256字节数据,另外它是半双工设备,也就是说同一时刻只能处在发状态或者接收状态或者其他状态。

        模块的基本信息就是这些了,下面结合代码作讲解。

三、硬件抽象层

        因为SPI的读写函数涉及到应用层,我们这里为了可移植性,依然采用回调的方式,结合sx1278的特性,写一个SPI驱动程序,先看下头文件定义。

        结构体内的函数在应用层都要具体定义,这样才能正常驱动,其中的片选是SPI的特征,选中后对应的模块才会回应数据,具体的应用层代码如下所示。


/*		
================================================================================
描述 : 硬件复位
输入 : 
输出 : 
================================================================================
*/
static void app_sx1278_reset(void)
{GPIO_ResetBits(GPIOA, GPIO_Pin_15);delay_ms(10);GPIO_SetBits(GPIOA, GPIO_Pin_15);delay_ms(10);	
}/*		
================================================================================
描述 : 片选0
输入 : 
输出 : 
================================================================================
*/
static void app_sx1278_cs0(void)
{GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}/*		
================================================================================
描述 : 片选1
输入 : 
输出 : 
================================================================================
*/
static void app_sx1278_cs1(void)
{GPIO_SetBits(GPIOB, GPIO_Pin_12);
}/*		
================================================================================
描述 : 字节读写
输入 : 
输出 : 
================================================================================
*/
static u8 app_sx1278_spi_rw_byte(u8 byte)
{while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);SPI_I2S_SendData(SPI2,byte);while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);return SPI_I2S_ReceiveData(SPI2);  
}/*		
================================================================================
描述 :应用层sx1278初始化,注册
输入 : 
输出 : 
================================================================================
*/
static void app_sx1278_hal_init(void)
{GPIO_InitTypeDef GPIO_InitStructure;SPI_InitTypeDef SPI_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);//复位引脚初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//SPI初始化	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//SPIGPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(GPIOB,&GPIO_InitStructure);		//CSGPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOB,&GPIO_InitStructure);   //CLK   MOSIGPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOB,&GPIO_InitStructure);  //MISOSPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(SPI2,&SPI_InitStructure);SPI_Cmd(SPI2,ENABLE);g_sDrvSx1278.tag_hal_sx1278.sx1278_reset = app_sx1278_reset;g_sDrvSx1278.tag_hal_sx1278.sx1278_cs_0 = app_sx1278_cs0;g_sDrvSx1278.tag_hal_sx1278.sx1278_cs_1 = app_sx1278_cs1;g_sDrvSx1278.tag_hal_sx1278.sx1278_spi_rw_byte = app_sx1278_spi_rw_byte;drv_sx1278_init(&g_sDrvSx1278);//初始化printf("app_sx1278_hal_init ok!\n");
}

        这里使用的是硬件SPI2,最后将具体函数赋值注册到结构体内,这样底层驱动就能调用了。硬件抽象层的函数具体定义如下:

#include "hal_sx1278.h"/*		
================================================================================
描述 :复位LORA设备
输入 : 
输出 : 
================================================================================
*/
void hal_sx1278_rst(HalSx1278Struct *psx1278) 
{psx1278->sx1278_reset();      
}/*		
================================================================================
描述 : SPI字节读写
输入 : 
输出 : 
================================================================================
*/
u8 hal_sx1278_spi_rw_byte(HalSx1278Struct *psx1278, u8 byte)
{uint32_t dat;dat=psx1278->sx1278_spi_rw_byte(byte);return dat;  
}/*		  
================================================================================
描述 : SPI写数据流
输入 : 
输出 : 
================================================================================
*/
void hal_sx1278_write_buffer(HalSx1278Struct *psx1278, u8 addr, u8 *buff, u8 size)
{u8 i;psx1278->sx1278_cs_0();//选中	  addr |= 0x80;hal_sx1278_spi_rw_byte(psx1278, addr);for(i=0;i<size;i++){hal_sx1278_spi_rw_byte(psx1278, buff[i]);}		psx1278->sx1278_cs_1();//取消选择	
}/*		
================================================================================
描述 : SPI读数据流
输入 : 
输出 : 
================================================================================
*/
void hal_sx1278_read_buffer(HalSx1278Struct *psx1278, u8 addr, u8 *buff, u8 size)
{u8 i;psx1278->sx1278_cs_0();//选中addr &= 0x7F;hal_sx1278_spi_rw_byte(psx1278, addr);for(i=0;i<size;i++){buff[i]=hal_sx1278_spi_rw_byte(psx1278, 0);}	psx1278->sx1278_cs_1();//取消选择
}/*		
================================================================================
描述 :往指定寄存器地址写入数据
输入 : 
输出 : 
================================================================================
*/
void hal_sx1278_write_addr(HalSx1278Struct *psx1278, u8 addr, u8 data)
{hal_sx1278_write_buffer(psx1278, addr, &data, 1);
}/*		
================================================================================
描述 :读取指定寄存器地址的数据
输入 : 
输出 : 
================================================================================
*/
u8 hal_sx1278_read_addr(HalSx1278Struct *psx1278, u8 addr)
{u8 data=0;hal_sx1278_read_buffer(psx1278, addr, &data, 1);return data;  	
}/*		
================================================================================
描述 :往LORA芯片FIFO写入数据
输入 : 
输出 : 
================================================================================
*/
void hal_sx1278_write_fifo(HalSx1278Struct *psx1278, u8 *buff, u8 size)
{hal_sx1278_write_buffer(psx1278, 0, buff, size);
}/*		
================================================================================
描述 :读取LORA芯片FIFO的数据
输入 : 
输出 : 
================================================================================
*/
void hal_sx1278_read_fifo(HalSx1278Struct *psx1278, u8 *buff, u8 size)
{hal_sx1278_read_buffer(psx1278, 0, buff, size);
}

        这里面函数都有具体描述,SX1278驱动层要用的就是最后四个函数了,驱动SX1278本质上就是读写其寄存器的过程。

四、SX1278初始化

        先看驱动文件的头文件,RSSI_OFFSET_LF和RSSI_OFFSET_HF是计算接收强度用的,这样就能大概知道两个模块的信号质量了,从而优化通讯参数,这在组网时候很有用。接下来的版本号是固定的,如果读出来不是等于0x12就说明硬件模块有故障。剩下的频率、扩频因子、带宽和纠错编码率之前说过了,这里定义的默认值。发射功率也是可调的,在低功耗设备里有用。硬件的CRC校验一般是打开的,显式报头和隐式报头文档我截图出来了,如下所示。

具体初始化代码如下所示:

/*		
================================================================================
描述 :
输入 : 
输出 : 
================================================================================
*/
void drv_sx1278_init(DrvSx1278Struct *psx1278)
{hal_sx1278_rst(&psx1278->tag_hal_sx1278);//复位 drv_sx1278_set_on(psx1278);drv_sx1278_set_default_param(psx1278);printf("drv_sx1278b version=0x%02X\n", drv_sx1278_get_version(psx1278));
}/*		
================================================================================
描述 :设置lora的默认参数
输入 : 
输出 : 
================================================================================
*/
void drv_sx1278_set_default_param(DrvSx1278Struct *psx1278 )
{hal_sx1278_write_addr( &psx1278->tag_hal_sx1278, REG_LR_LNA, RFLR_LNA_GAIN_G1);drv_sx1278_set_rf_freq( psx1278, LR_SET_RF_FREQ );     // 设置载波频率drv_sx1278_set_sf( psx1278, LR_SET_SF );      // 设置扩频因子 SF6 only operates in implicit header mode.drv_sx1278_set_error_coding( psx1278, LR_SET_CR );          // 设置纠错编码率drv_sx1278_set_packet_crc_on( psx1278, LR_SET_CRC_ON );      // CRC开关drv_sx1278_set_bw( psx1278, LR_SET_RF_BW );    // 设置带宽drv_sx1278_set_implicit_header_on( psx1278, LR_SET_IM_ON);    // 设置显式/隐式报头模式drv_sx1278_set_symb_timeout( psx1278, 0x3FF );//设置RX超时drv_sx1278_set_payload_length(psx1278, LR_SET_PAY_LEN);//负载字节长度drv_sx1278_set_lowdatarateoptimize( psx1278, true );       // 低速率优化模式,符号长度超过 16ms时必须打开drv_sx1278_set_pa_output( psx1278, RFLR_PACONFIG_PASELECT_PABOOST );//选择PA输出引脚drv_sx1278_set_rf_power( psx1278, LR_SET_RF_PWR ); hal_sx1278_write_addr( &psx1278->tag_hal_sx1278, REG_LR_OCP, (hal_sx1278_read_addr(&psx1278->tag_hal_sx1278, REG_LR_OCP)& RFLR_OCP_TRIM_MASK) | RFLR_OCP_TRIM_180_MA );//为PA开启过流保护 180mA        drv_sx1278_set_op_mode( psx1278, RFLR_OPMODE_STANDBY );//进入待机模式
} 

        下面对初始化细节做介绍。

1、硬件复位,这是常规操作,必要时复位一下,可以避免模块死机。

2、drv_sx1278_set_on开启LoRa模式。

3、默认参数初始化,首先设置AGC阈值,无线领域的专业了;然后依次设置频率等参数,这里面有个设置接收超时的drv_sx1278_set_symb_timeout,函数内容挺长,核心就是设置下图红框的寄存器内容为0x3FF。

4、低速率优化和PA输出都打开,这样才能发挥出LoRa的效果,PA类似于放大器,可以增加发射功率。

5、初始化接收后就进入待机模式,其他的应用层就能操作了。

五、发送时间计算

        如果SF和BW等参数知道后,就可以计算发送某个数据包所需要的时间了,具体公式如下:

        是不是看得有点懵,懵就对了,直接上代码:


/*		
================================================================================
描述 : 
输入 : 
输出 : 
================================================================================
*/ 
static u32 bsp_pow2(u8 n)
{u8 i;u16 sum=1;for(i=0;i<n;i++)sum=sum*2;return sum;
}/*		
================================================================================
描述 : 根据射频参数计算发送时间
输入 : 
输出 : 发送时间,返回0表示无效,单位:ms
================================================================================
*/ 
u32 drv_sx1278_calcu_air_time(u8 sf, u8 bw, u16 data_len)
{float bw_value=0.f, t_s;u32 tx_time=0;switch(bw){case 0:bw_value=7.8;break;case 1:bw_value=10.4;break;case 2:bw_value=15.6;break;case 3:bw_value=20.8;break;case 4:bw_value=31.25;break;case 5:bw_value=41.6;break;case 6:bw_value=62.5;break;case 7:bw_value=125;break;case 8:bw_value=250;break;case 9:bw_value=500;break;default: return 0;	}if(sf<7 || sf>12){return 0;}t_s=1.f*bsp_pow2(sf)/bw_value;int payload_nb=0;int k1=8*data_len-4*sf+24;int k2=4*(sf-2);payload_nb=k1/k2;if(payload_nb<0){payload_nb=0;}else if(k1%k2>0){payload_nb++;}payload_nb=payload_nb*5+8;tx_time=(u32)(payload_nb+12.5)*t_s;if(tx_time==0){tx_time=1;}return tx_time;
}

        实际验证这个时间基本准确,这有什么用呢?因为发送的时候要等待,这期间我们可以先去干别的事,等差不多时间再回来检测是否发送成功,就不会浪费时间和系统资源了。

六、发送模式

        发送数据前要进行一系列操作,首先模式切换前都要先进入待机模式,然后依次是屏蔽中断、设置数据长度、设置缓冲区起始地址为0(这样256字节都能用来发送了,默认是一半发送一半接收)、写入数据、配置中断引脚,最后切换到发送模式。这里中断引脚一般没用,我们是直接查询寄存器状态的,算是保留功能吧。具体代码如下:

/*		
================================================================================
描述 :用户层Lora数据发送 
输入 : 
输出 : 
================================================================================
*/
void drv_sx1278_send(DrvSx1278Struct *psx1278, u8 *buff, u16 len)
{u8 RegIrqFlagsMask=0,RegFifoTxBaseAddr=0,RegDioMapping1=0,RegDioMapping2=0;drv_sx1278_set_op_mode( psx1278, RFLR_OPMODE_STANDBY );//待机模式printf("send start!\n");RegIrqFlagsMask = RFLR_IRQFLAGS_RXTIMEOUT |RFLR_IRQFLAGS_RXDONE |RFLR_IRQFLAGS_PAYLOADCRCERROR |RFLR_IRQFLAGS_VALIDHEADER |//RFLR_IRQFLAGS_TXDONE |RFLR_IRQFLAGS_CADDONE |RFLR_IRQFLAGS_FHSSCHANGEDCHANNEL |RFLR_IRQFLAGS_CADDETECTED;//置位表示该位中断屏蔽hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_IRQFLAGSMASK, RegIrqFlagsMask );hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_PAYLOADLENGTH, len);//设置数据长度RegFifoTxBaseAddr = 0x00; // Full buffer used for Txhal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_FIFOTXBASEADDR, RegFifoTxBaseAddr );//设置待发送数据 写入的RAM起始地址,芯片内部有256字节RAM,要跟FIFO区别hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_FIFOADDRPTR, RegFifoTxBaseAddr);hal_sx1278_write_fifo(&psx1278->tag_hal_sx1278, (uint8_t*)buff, len);//向FIFO写入数据// TxDone               RxTimeout                   FhssChangeChannel          ValidHeader         RegDioMapping1 = RFLR_DIOMAPPING1_DIO0_01 | RFLR_DIOMAPPING1_DIO1_00 | RFLR_DIOMAPPING1_DIO2_00 | RFLR_DIOMAPPING1_DIO3_01;//将DIO_0配置成发送完成中断// PllLock              Mode ReadyRegDioMapping2 = RFLR_DIOMAPPING2_DIO4_01 | RFLR_DIOMAPPING2_DIO5_00;hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_DIOMAPPING1, RegDioMapping1 );//写入配置hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_DIOMAPPING2, RegDioMapping2 );drv_sx1278_set_op_mode( psx1278, RFLR_OPMODE_TRANSMITTER );//设置成发送模式}/*		
================================================================================
描述 : 发送完成检测
输入 : 
输出 : 
================================================================================
*/ 
u8 drv_sx1278_send_check(DrvSx1278Struct *psx1278)
{if(( hal_sx1278_read_addr(&psx1278->tag_hal_sx1278, REG_LR_IRQFLAGS) & RFLR_IRQFLAGS_TXDONE ) == RFLR_IRQFLAGS_TXDONE)		{hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_IRQFLAGS, RFLR_IRQFLAGS_TXDONE );//一次写操作清除 IRQ	return true;}return false;
}

        结合发送时间计算函数,应用层的发送函数如下:

七、接收模式

        下面是接收模式的代码,流程差不多,进入待机模式、设置中断、设置缓冲区地址,最后进入连续接收模式。

/*		
================================================================================
描述 : 接收模式初始化
输入 : 
输出 : 
================================================================================
*/ 
void drv_sx1278_recv_init(DrvSx1278Struct *psx1278) 
{u8 RegIrqFlagsMask,RegDioMapping1,RegDioMapping2;drv_sx1278_set_op_mode( psx1278, RFLR_OPMODE_STANDBY );RegIrqFlagsMask = RFLR_IRQFLAGS_RXTIMEOUT |//RFLR_IRQFLAGS_RXDONE |//RFLR_IRQFLAGS_PAYLOADCRCERROR |RFLR_IRQFLAGS_VALIDHEADER |RFLR_IRQFLAGS_TXDONE |RFLR_IRQFLAGS_CADDONE |//RFLR_IRQFLAGS_FHSSCHANGEDCHANNEL |RFLR_IRQFLAGS_CADDETECTED;hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_IRQFLAGSMASK, RegIrqFlagsMask );RegDioMapping1 = RFLR_DIOMAPPING1_DIO0_00 | RFLR_DIOMAPPING1_DIO1_00 | RFLR_DIOMAPPING1_DIO2_00 | RFLR_DIOMAPPING1_DIO3_00;// CadDetected               ModeReadyRegDioMapping2 = RFLR_DIOMAPPING2_DIO4_00 | RFLR_DIOMAPPING2_DIO5_00;hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_DIOMAPPING1, RegDioMapping1);hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_DIOMAPPING2, RegDioMapping2);hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_FIFORXBASEADDR, 0x00 );hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_FIFOADDRPTR, 0x00 );   //指定接收基地址         drv_sx1278_set_op_mode( psx1278, RFLR_OPMODE_RECEIVER );//连续接收	
}/*		
================================================================================
描述 : 
输入 : 
输出 : 
================================================================================
*/ 
u8 drv_sx1278_recv_check(DrvSx1278Struct *psx1278, u8 *buff)
{u8 RegFifoRxCurrentAddr;u8 RxPacketSize=0; if(( hal_sx1278_read_addr(&psx1278->tag_hal_sx1278, REG_LR_IRQFLAGS) & RFLR_IRQFLAGS_RXDONE ) == RFLR_IRQFLAGS_RXDONE)		{hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_IRQFLAGS, RFLR_IRQFLAGS_RXDONE );//一次写操作清除 IRQ	RegFifoRxCurrentAddr = hal_sx1278_read_addr(&psx1278->tag_hal_sx1278, REG_LR_FIFORXCURRENTADDR );//接收到最后一个数据包的起始地址(256B的数据缓冲区中)		RxPacketSize = hal_sx1278_read_addr(&psx1278->tag_hal_sx1278, REG_LR_NBRXBYTES);//读取 接收到的数据字节数hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_FIFOADDRPTR, RegFifoRxCurrentAddr);//设置要读取的起始地址hal_sx1278_read_fifo( &psx1278->tag_hal_sx1278, buff, RxPacketSize );//读取数据hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_FIFORXBASEADDR, 0x00 );hal_sx1278_write_addr(&psx1278->tag_hal_sx1278, REG_LR_FIFOADDRPTR, 0x00 );   //指定接收基地址 }return RxPacketSize;
}

        正常来讲,模块就是在接收和发送两种模式下切换的,大部分时间是在接收检测,如果是低功耗设备的话,那大部分时间是在休眠。

        

八、总结

        SX1278的驱动层差不多就这样了,内容比较多,对于LoRa而言,这只是开始,因为更复杂的是应用层的组网协议,比如LoRaWAN,但是那个对服务器有要求,网关也是比较昂贵的,小规模组网不合适。现在也有一些公司是做LoRa自组网协议,把协议封装在带MCU的LoRa模块里,用户用AT指令或者二次开发的模式集成到自己的应用,方便用户开发LoRa产品。那我们初学者暂时不要搞那么复杂,下一篇就结合之前的modbus协议,做个简单的应用层轮询协议,带大家看看LoRa具体是怎么应用的。

        对于驱动程序,由于文件比较多,单独建立了个文件夹,所以Keil要做相应设置,头文件才能正常包含,具体如下。

代码链接:https://download.csdn.net/download/ypp240124016/89096542

本项目的交流QQ群:701889554

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/798423.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言整数和小数的存储

1.整数在内存中的存储 计算机使用二进制进行存储、运算&#xff0c;整数在内存中存储使用的是二进制补码 1.1原码、反码、补码 整数的2进制表⽰⽅法有三种&#xff0c;即 原码、反码和补码 三种表⽰⽅法均有符号位和数值位两部分&#xff0c;符号位都是⽤0表⽰“正”&am…

鸿蒙内核源码分析 (Fork 篇) | 一次调用,两次返回

第一次看到 fork 时&#xff0c;说是一次调用&#xff0c;两次返回&#xff0c;当时就懵圈了&#xff0c;多新鲜&#xff0c;真的很难理解。因为这足以颠覆了以往对函数的认知&#xff0c; 函数调用还能这么玩&#xff0c;父进程调用一次&#xff0c;父子进程各返回一次。而且只…

机器学习贝叶斯算法是什么

参考一篇理解性文章&#xff1a;如何让10岁的表弟也能理解贝叶斯公式 问&#xff1a; 机器学习贝叶斯算法是什么&#xff0c;它的会被用于分类或者回归分析吗&#xff0c;它有什么优势&#xff1f; 答&#xff1a; 机器学习中的贝叶斯算法是一种基于贝叶斯定理的算法&#…

设计原则、设计模式、设计模式项目实战

设计原则 封装、继承、多态、抽象分别可以解决哪些编程问题 封装&#xff1a;也叫做信息隐藏或数据保护访问。数据 通过暴露有限的访问接口&#xff0c;授权外部仅能通过类提供接口访问&#xff0c;对内的类private私有化属性&#xff0c;通过封装简化操作&#xff0c;让用户更…

记Postman参数化

因为需要在WEB页面上处理部分数据&#xff0c;手动操作太慢&#xff0c;所以考虑使用接口方式处理&#xff0c;因急于使用&#xff0c;用Python Request的方式&#xff0c;写代码也来得慢&#xff0c;故采用Postman加外部文件参数化方式来实现。 接口请求是Post方式&#xff0c…

Healthcare医疗健康领域常见的几个单词

有几个医疗健康领域的单词总是傻傻分不清楚 inpatient&#xff0c;住院病人 outpatient &#xff0c;门诊病人 urgentcare &#xff0c;急需护理 ambulatory&#xff0c;非卧床病人 emergency&#xff0c; 急诊&#xff08;比前面那个病情或者伤情更加严重&#xff09; wellne…

电商平台混战之下,天猫破解品牌增长奥秘

行业共识是追上风&#xff0c;才有好生意&#xff0c;但风很多时候不会只有一个方向。 4月2日&#xff0c;上海&#xff0c;TopTalk 2024天猫超级品牌私享会举行。这个活动已举办数年&#xff0c;每一年天猫都会发布新一年度的品牌经营策略&#xff0c;只是与往年不同的是&…

YOLOv9改进策略 :原创自研 | 自研MSAM注意力,通道注意力升级,魔改CBAM

💡💡💡本文自研创新改进:MSAM(CBAM升级版):通道注意力具备多尺度性能,多分支深度卷积更好的提取多尺度特征,最后高效结合空间注意力 1)作为注意力MSAM使用; 推荐指数:五星 MSCA | 亲测在多个数据集能够实现涨点,对标CBAM。 改进1结构图如下: 《YOLOv…

linux安全加固

1.登录账号加固 /etc/login.defs 创建⽤户的默认设置⽂件 grep -Ev "^#|^$" /etc/login.defs /etc/login.defs ⽂件⽤于在创建⽤户时&#xff0c;对⽤户的⼀些基本属性做默认设置&#xff0c;例如指定⽤户 UID 和 GID 的范围&#xff0c;⽤户的过期时间&#xff0…

寻找排序数组中的最小值

题目描述 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到&#xff1a; 若旋转 4 次&#xff0c;则可以得到 [4,5,6,7,0,1,2]若旋转 7 次…

【前端】CSS(引入方式+选择器+常用元素属性+盒模型+弹性布局)

文章目录 CSS一、什么是CSS二、语法规范三、引入方式1.内部样式表2.行内样式表3.外部样式 四、选择器1.选择器的种类1.基础选择器&#xff1a;单个选择器构成的1.标签选择器2.类选择器3.id 选择器4.通配符选择器 2.复合选择器1.后代选择器2.子选择器3.并集选择器4.伪类选择器 五…

Linux 内核优化简笔 - 高并发的系统

简介 Linux 服务器在高并发场景下&#xff0c;默认的内核参数无法利用现有硬件&#xff0c;造成软件崩溃、卡顿、性能瓶颈。 当然&#xff0c;修改参数只是让Linux更好软件的去利用已有的硬件资源&#xff0c;如果硬件资源不够也无法解决问题的。而且当硬件资源不足的时候&am…

AcWing 788. 逆序对的数量——算法基础课题解

AcWing 788. 逆序对的数量 题目描述 给定一个长度为 n 的整数数列&#xff0c;请你计算数列中的逆序对的数量。 逆序对的定义如下&#xff1a;对于数列的第 i 个和第 j 个元素&#xff0c;如果满足 i<j且 a[i]>a[j]&#xff0c;则其为一个逆序对&#xff1b;否则不是。…

vscode开发java的插件和配置

推荐插件 .vscode/extensions.json {"recommendations": ["redhat.fabric8-analytics","ms-azuretools.vscode-docker","vscjava.vscode-java-pack","eamodio.gitlens","obkoro1.korofileheader","redhat.j…

代码随想录算法训练营第三十一天|455.分发饼干、376.摆动序列、53.最大子数和

文档链接&#xff1a;https://programmercarl.com/ LeetCode455.分发饼干 题目链接&#xff1a;https://leetcode.cn/problems/assign-cookies/ 思路&#xff1a;我采用的是优先用小饼干喂小孩。 贪心&#xff1a; class Solution { public:int findContentChildren(vecto…

点云滤波与匹配进阶

0. 简介 之前作者专门为点云匹配写了几篇博客&#xff0c;但是我们发现最近几年有更多的新方法已经在不断地被使用。同时之前有些内容也没有很好的概括&#xff0c;所以这里我们将作为一篇进阶文章来介绍这些方法的使用。 1. 地面点去除 处了使用一些复杂的方法&#xff08;…

Cute Background FX

Cute Background FX是环境背景粒子系统的集合。非常适合作为菜单的背景。 该包包括: -20个独特预制件+20个URP预制件 -5种独特的环境设计 -15种纹理 -2个自定义着色器+2个URP着色器 -共59项独特资产 -一个演示场景,您可以在其中概述所有内容。 所有纹理都是512x512分辨率的P…

基于SSM框架实现的在线心理评测与咨询系统(技术栈 spring+springmvc+mybatis+jsp+jquery+css)

一、项目简介 本项目是一套基于SSM框架实现的在线心理评测与咨询系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&am…

iOS 应用内网络请求设置代理

主要通过URLSessionConfiguration 的connectionProxyDictionary 属性 为了方便其他同学使用&#xff0c;我们可以通过界面来进行设定&#xff08;是否开启代理、服务端、端口&#xff09;&#xff0c;从而达到类似系统上的设定 具体链接参考&#xff1a;为 iOS 网络请求设置代理…

代码随想录算法训练营33期 第二十九天(补第二十八天)| 93.复原IP地址、78.子集、 90.子集II

93.复原IP地址 class Solution { public:vector<string> result;bool isValid(const string& s, int start, int end){if (start > end){return false;}if (s[start]0 && start ! end){return false;}int num 0;for (int istart; i<end; i){if (s[i]…