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

news2024/11/15 23:28:57

目录

一、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

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

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

相关文章

精心整理-数据分类分级赋能企业数据安全建设资料合集

以下是资料目录&#xff0c;如需下载请前往知识星球下载&#xff1a;https://t.zsxq.com/18KTZnJMX 企业数据安全建设数据分类分级架构.pdf 企业数据分类分级模板.xls 数据分类分级的实践与挑战.pdf 数据分类分级制度评述.pdf 电信和互联网大数据安全管控分类分级实施指南.pdf …

嵌入式学习49-单片机2

指令周期 1M 机器周期 12M &#xff08;晶体震荡器产生&#xff09; 中断两种方式 …

STL--list和vector有什么区别

list 和 vector 是 C STL 中的两种常见容器&#xff0c;它们在底层实现、性能特性和适用场景方面有着显著的区别&#xff1a; 底层数据结构&#xff1a; vector 底层是一个动态数组&#xff0c;提供快速的随机访问&#xff0c;但在中间插入或删除元素效率较低。 list 是一个双…

鸿蒙ArkUI实例:【自定义组件】

组件是 OpenHarmony 页面最小显示单元&#xff0c;一个页面可由多个组件组合而成&#xff0c;也可只由一个组件组合而成&#xff0c;这些组件可以是ArkUI开发框架自带系统组件&#xff0c;比如 Text 、 Button 等&#xff0c;也可以是自定义组件&#xff0c;本节笔者简单介绍一…

ERC314协议代币开发及合约开发详解

ERC314 是一种新的代币标准&#xff0c;旨在为 BASE 链上的代币提供更便捷、高效的交易体验。它由 DAPJ 项目团队开发&#xff0c;并于 2023 年 8 月首次发布。 ERC314 的特点 无需依赖 DEX 或 SWAP 进行交易: ERC314 代币可以像原生代币一样直接转账&#xff0c;无需借助 DEX …

Lightroom Classic 2024成就专业摄影梦想mac/win版

Lightroom Classic 2024是一款功能强大的数字图像处理和管理工具&#xff0c;专为摄影师和摄影爱好者设计。它提供了丰富的照片调整、处理、管理和分享功能&#xff0c;帮助用户轻松管理、编辑和展示他们的照片。 Lightroom Classic 2024软件获取 首先&#xff0c;Lightroom C…

Vector Laboratories的凝集素--莲藕凝集素(Lotus Tetragonolobus Lectin)

莲藕凝集素&#xff08;lotustetragonolobus lectin&#xff09;是一个密切相关的糖蛋白家族&#xff0c;对含α-linked L-fucose具有相似的特异性。虽然莲藕凝集素的许多结合特性与荆豆凝集素I相似&#xff0c;但这些岩藻糖特异性凝集素之间的结合亲和力和某些寡糖特异性明显不…

【小白学机器学习11】假设检验之2:Z检验(U检验,正态检验)

目录 1 什么是Z检验 1.1 Z检验的别名 Z-test /U-test / 正态检验 1.2 维基百科定义 1.2 百度百科定义 1.3 定义提炼关键点 1.4 Z检验量 : Z(X-θ)/s (X-u)/s 2 Z检验量的构造 2.1 Z检验量 : Z(X_-u)/s 2.2 Z检验变量的构造 2.4 Z检验量的核心参数 2.4.1 原始公式 …

性能优化-如何爽玩多线程来开发

前言 多线程大家肯定都不陌生&#xff0c;理论滚瓜烂熟&#xff0c;八股天花乱坠&#xff0c;但是大家有多少在代码中实践过呢&#xff1f;很多人在实际开发中可能就用用Async&#xff0c;new Thread()。线程池也很少有人会自己去建&#xff0c;默认的随便用用。在工作中大家对…

数据库表设计18条黄金规则

前言 对于后端开发同学来说&#xff0c;访问数据库&#xff0c;是代码中必不可少的一个环节。 系统中收集到用户的核心数据&#xff0c;为了安全性&#xff0c;我们一般会存储到数据库&#xff0c;比如&#xff1a;mysql&#xff0c;oracle等。 后端开发的日常工作&#xff…

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

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

损失函数L1Loss、L2loss区别

损失函数 L1Loss 平均绝对误差&#xff08;Mean Absolute Error&#xff0c;MAE&#xff09; 预测值和真实值之差的绝对值 L2Loss 均方误差&#xff08;Mean Square Error&#xff0c;MSE&#xff09; 是预测值和真实值之差的平方 Smooth L1 Loss/Huber Loss 平滑版本的…

wordpress全站开发指南-面向开发者及深度用户(全中文实操)--php数组与基本循环

php数组与基本循环 <?php$myName"xixi";$namesarray(xixi1,xixi2,xixi3); ?> <p> Hi ,my name is <?php echo $myName; ?> </p> <p> Hi,my name is <?php echo $names[0] ?> </p> <p> Hi,my name is <?…

全国月度平均风速空间分布数据/月度降雨量分布/月均气温分布

引言 风速是指空气相对于地球某一固定地点的运动速率。一般来讲&#xff0c;风速越大&#xff0c;风力等级越高&#xff0c;风的破坏性越大。平均风速&#xff0c;一定时段内&#xff0c;数次观测的风速的平均值。一般表达方式为[m/s]。 正文 我国位于欧亚大陆东部、太平洋西岸…

哪些医疗器械申请FDA,需要准备网络安全文件?需要提交的文件都是什么样的?

一、什么类型的医疗器械需要递交网络安全文件&#xff1f; FD&C法案第524B条(c) 条将“网络设备”定义为&#xff1a; 1&#xff09;经申请人验证、安装或授权的软件或设备&#xff1b; 2&#xff09;具备连接互联网的能力&#xff1b; 3&#xff09;包含经申请人验证、…

YOLO算法改进Backbone系列之:PVT

摘要&#xff1a;尽管基于CNNs的backbone在多种视觉任务中取得重大进展&#xff0c;但本文提出了一个用于密集预测任务的、无CNN的的简单backbone——Pyramid Vision Transformer&#xff08;PVT&#xff09;。相比于ViT专门用于图像分类的设计&#xff0c;PVT将金字塔结构引入…

每日OJ题_两个数组dp①_力扣1143. 最长公共子序列

目录 力扣1143. 最长公共子序列 解析代码 力扣1143. 最长公共子序列 1143. 最长公共子序列 难度 中等 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样…

BPMN建模示例

背景 对BPMN已经有了初步的了解&#xff0c;并且已经理解了BPMNJS这种流程引擎的工具&#xff0c;那么接下来我们已具体的案例来感性认知BPMN的能力&#xff0c;为接下来BPMN的学习做个铺垫。 是的&#xff0c;我们需要一个通俗易懂的例子&#xff0c;于是乎&#xff0c;我在…

阿里云服务器的主要用途是什么?

阿里云服务器可以干嘛&#xff1f;能干啥你还不知道么&#xff01;简单来讲可用来搭建网站、个人博客、企业官网、论坛、电子商务、AI、LLM大语言模型、测试环境等&#xff0c;阿里云百科aliyunbaike.com整理阿里云服务器的用途&#xff1a; 阿里云服务器活动 aliyunbaike.com…

文心一言指令词宝典之咨询分析篇

作者&#xff1a;哈哥撩编程&#xff08;视频号、抖音、公众号同名&#xff09; 新星计划全栈领域优秀创作者博客专家全国博客之星第四名超级个体COC上海社区主理人特约讲师谷歌亚马逊演讲嘉宾科技博主极星会首批签约作者 &#x1f3c6; 推荐专栏&#xff1a; &#x1f3c5;…