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