物联网实战--驱动篇之(三)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);//SPI
	
	GPIO_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);		//CS

	GPIO_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   MOSI

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOB,&GPIO_InitStructure);  //MISO

	SPI_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 Tx
	hal_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 Ready
	RegDioMapping2 = 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               ModeReady
	RegDioMapping2 = 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python第四次作业

周六&#xff1a; 1. 找出10000以内能被5或6整除&#xff0c;但不能被两者同时整除的数&#xff08;函数&#xff09; def find_number():for number in range(0,10000):if number % 5 0 or number % 6 0:if number % 5 ! number % 6:ls.append(number)print(ls)ls [] fin…

HTTP的介绍

一.什么是HTTP&#xff1f; Hyper Text Transfer Protocol,超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。 二.HTTP的特点 &#xff08;1&#xff09;基于TCP协议&#xff1a;面向连接&#xff0c;安全 &#xff08;2&#xff09;基于请求-响应模型的&…

windows上使用influx2.7学习

参考 官方文档&#xff1a;https://docs.influxdata.com/influxdb/v2/ 下载 需要下载两样东西&#xff1a;influxd.exe和influx.exe influxd:influx数据库的服务端。下载地址&#xff1a;https://dl.influxdata.com/influxdb/releases/influxdb2-2.7.5-windows.zipinflux:连…

中文分词源码阅读(jiedi)

文章目录 structure.p文件pd.read_excelenumerate思维导图核心源码jiedi.pytrain.py 总结 structure 点击左边的Structure按钮就如Structure界面。从Structure我们可以看出当前代码文件中有多少个全局变量、函数、类以及类中有多少个成员变量和成员函数。 其中V图标表示全局变…

chrome google浏览器添加插件扩展失败怎么办,无法从该网站添加应用、扩展程序和用户脚本确定,

无法从该网站添加应用、扩展程序和用户脚本确定 chrome google浏览器添加插件扩展失败怎么办&#xff0c;无法从该网站添加应用、扩展程序和用户脚本确定&#xff0c; 需要打开调试模式 chrome://extensions/

24考研-东南大学916经验贴

文章目录 一、个人情况二、初试备考经验1.政治 67&#xff0c;客观382.英语 60&#xff0c;客观大概40左右3.数学 136&#xff0c;客观应该满分4.专业课 数据结构计网 114小分不清楚 三、复试备考经验笔试&#xff1a;C面试复试流程 附一下成绩单&#xff1a; 一、个人情况 本…

【蓝桥杯嵌入式】Cubemx新建工程引脚配置与点亮LED

【蓝桥杯嵌入式】Cubemx新建工程引脚配置与点亮LED cubemx基础配置LED 引脚配置按键配置按键引脚配置定时器扫描配置 工程管理配置点亮LED程序设计keil配置与程序下载 参考博文1&#xff1a;STM32 | 利用STM32CubeMX初始化一个STM32工程 参考博文1&#xff1a;点亮LED灯&#x…

基于ARM内核的智能手环(day8)

心率模块 输入模拟量 MPU6050 IIC 接线引脚&#xff1a; 因为这两个模块官方都提供了详细的资料和源码&#xff0c;这里不再过多赘述 项目结果展示 待机页面 有开场动画 所有页面无操作20s自动返回待机页面 主页 展示时间和温度到达预定时间蜂鸣器响起&#xff0c;按键后关…

【御控物联】JavaScript JSON结构转换(14):对象To数组——规则属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON数组》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0…

【网站项目】校园订餐小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【计算机网络】应用层——HTTPS协议详解

文章目录 1. HTTPS 协议简介2. 了解“加密”3. HTTPS 保证数据安全传输的三大机制3.1 引入对称加密3.2 引入非对称加密3.3 引入“SSL/TLS证书”&#xff08;防止中间人攻击&#xff09;3.4 HTTPS安全机制总结 &#x1f4c4;前言&#xff1a; 前面的文章已经对 HTTP 协议 进行了…

亚洲运动用品与时尚展(北京,上海,南京,厦门)

亚洲运动用品与时尚展&#xff08;北京&#xff0c;上海&#xff0c;南京&#xff0c;厦门&#xff09; 主办单位&#xff1a;慕尼黑展览集团 承办单位&#xff1a;慕尼黑展览&#xff08;上海&#xff09;有限公司 Nanjing Sports Carnival 2024 亚洲运动用品与时尚展&…

你真的会写接口自动化测试断言吗?

你真的会写自动化测试断言吗&#xff1f; 在接口测试中&#xff0c;断言是一项非常重要的操作&#xff0c;它是用来校验接口返回结果是否符合预期的一种手段。一般来说&#xff0c;接口测试断言大致可以分为以下几类&#xff1a; 状态码断言&#xff1a;这是最基本也是最常用的…

POLY - Survival Melee Weapons

一个轻便、有趣且灵活的低多边形资源包,非常适合原型设计或添加到低多边形世界中。超过50种近战武器、刀、斧、棍棒、棍棒等。 此套餐非常适合第三人称或自上而下的观看。 除此之外,资产还包括开发生存游戏可能需要的任何细节。 整个包是以多边形风格创建的,可以与其他多边…

项目:自主实现Boost搜索引擎

文章目录 写在前面开源仓库和项目上线其他文档说明 项目背景项目的宏观原理技术栈与环境搜索引擎原理正排索引倒排索引 去标签和数据清洗模块html文件名路径保存函数html数据解析函数文件写入函数 建立索引模块检索和读取信息建立索引建立正排索引建立倒排索引jieba工具的使用倒…

八数码问题(bfs)

方式一&#xff1a;string存储状态 题目传送门&#xff1a;845. 八数码 - AcWing题库 BFS适用于边权为1的最短路问题 &#xff0c;而这题要求最少的交换次数&#xff0c;将每一次的九宫格状态当作一个“状态结点”&#xff0c;由当前这个结点可以扩展出其它状态【即 x 可以与其…

设计模式总结-组合模式

组合设计模式 模式动机模式定义模式结构组合模式实例与解析实例一&#xff1a;水果盘实例二&#xff1a;文件浏览 更复杂的组合总结 模式动机 对于树形结构&#xff0c;当容器对象&#xff08;如文件夹&#xff09;的某一个方法被调用时&#xff0c;将遍历整个树形结构&#x…

day03 51单片机

51单片机学习 1 模块化编程 1.1 什么是模块化编程 随着我们的代码越来越复杂,我们的main.c越来越长,阅读性也越来越差。如果将来开始做项目,我们可能要同时操作好几个模块,这种情况下我们无法再把代码写到同一个文件,而是要分模块管理代码。 具体实现方法,就是将源码…

原型变量、原子操作、原子性、内存序

一、原子变量、原子操作 锁竞争&#xff1a;互斥锁、条件变量、原子变量、信号量、读写锁、自旋锁。在高性能基础组件优化的时候&#xff0c;为了进一步提高并发性能&#xff0c;可以使用原子变量。性能&#xff1a;原子变量 > 自旋锁 > 互斥锁。 操作临界资源的时间较长…

【leetcode】动态规划::前缀和

标题&#xff1a;【leetcode】前缀和 水墨不写bug 正文开始&#xff1a; &#xff08;一&#xff09;简单前缀和 描述 给定一个长度为n的数组a1​,a2​,....an​. 接下来有q次查询, 每次查询有两个参数l, r. 对于每个询问, 请输出al​al1​....ar​ 输入描述&#xff1a; 第一…