【物联网无线通信技术】LoRa从理论到实现(SX1268)

news2024/11/15 11:55:38

文章先从LoRa的物联网通信技术前辈们讲起,慢慢引出了这种功耗又低,距离又远的无线通信技术,然后又似庖丁解牛一般,从物理层到链路层,详细的介绍了LoRa这种技术的组成,最后以一种实际的原理与嵌入式软件实现,让读者近距离接触到基于LoRa这种无线通信技术产品的开发过程。总而言之,博主在这一篇文章中集中的介绍了物联网无线通信技术-LoRa的前世今生,帮助各位对这门“新”的无线通信技术有一个全面且直观的了解。

文章目录

LoRa技术前序 

LoRa技术简介

LoRa应用

LoRa系统架构

LoRaWAN 

LoRa通信物理层

LoRa调制与解调

LoRa 编码与解码

STM32+SX1268实现LoRa

实现原理

嵌入式程序

参考文献


LoRa技术前序 

LoRa之前的主要无线通信协议分为一下三种:

  1. 第一类是远距离高速率的传输协议,典型协议包括蜂窝网络通信技术,如3G、4G、5G相关技术等,这是我们目前移动通信使用的典型技术。
  2. 第二类是近距离高速率传输技术,如WiFi、蓝牙等,这些技术传输距离在几十到几百米级别,主要用在家庭环境和日常应用中,使用非常广泛,前面两类可能是一般用户最常使用到的网络协议了,也符合传统网络应用的主要特点和需求。
  3. 第三类是近距离低功耗传输技术,如传统物联网中ZigBee、RFID、低功耗蓝牙等。

上面三类技术大都要求较高的信噪比,并且对障碍的穿透性较小,无法在复杂环境中实现远距离低功耗传输。低功耗广域网有效的弥补了现有物联网连接方法的不足,成为支持物联网连接的重要基础,得到了国内外的广泛关注,并成为了国内外的研究和应用前沿。

LPWAN (Low Power Wide Area Network)指的是低功耗广域网,其特点在于极低功耗,长距离以及海量连接,适用于物联网万物互联的场景。LPWAN不只是一种技术,而是代表了一族有着各种形式的低功耗广域网技术,如下图所示。其中LoRa使用的是一种扩频技术,NB-IoT使用的是窄带技术,这是两种有代表性的低功耗广域网技术。


LoRa技术简介

LoRa 是 Long Range Communication的简称,我们可以从三个不同的角度来理解LoRa这门技术。从而获得对LoRa这么技术完整的理解。

  • LoRa本质上指的是一种物理层的信号调制方式,是 Semtech 公司定义的一种基于Chirp扩频技术的物理层调制方式,可达到-148 dBm的接收灵敏度,以偏小的数据速率(0.3-50kbps)换取更高的通讯距离(市内3km,郊区15km)和低功耗(电池供电在特定条件下可以工作长达10年)。
  •  从系统角度看,LoRa也指由终端节点、网关、网络服务器、应用服务器所组成的一种网络系统架构:LoRa定义了不同设备在系统中的分工与作用,规定了数据在系统中流动与汇聚的方式。
  • 从应用角度看,LoRa为物联网应用提供了一种低成本、低功耗、远距离的数据传输服务:LoRa在使用10mW射频输出功率的情况下,可以提供超过25km视线传输距离,从而支持大量广域低功耗物联网应用。

LoRa应用

LoRa作为目前广泛使用的低功耗广域网技术(LPWAN),为低功耗物联网设备提供了可靠的连接方案。 如下图所示,相比于Wi-Fi、蓝牙、ZigBee等传统无线局域网,LoRa可以实现更远距离的通信,有效扩展了网络的覆盖范围; 而相比于移动蜂窝网络,LoRa具有更低的硬件部署成本和更长的节点使用寿命,单个LoRa节点可以在电池供电的情况下连续工作数年。 LoRa具有低数据率、远距离和低功耗的性质,因此非常适合与室外的传感器及其他物联网设备进行通信或数据交互。

考虑到LoRa在覆盖距离、部署成本等方面的巨大优势,近年来LoRa在全球范围内进行了大量的应用部署,在智能仪表(如智能水表、智能电表)、智慧城市、智能交通数据采集、野生动物监控等众多物联网场景中都可以看到LoRa的应用。例如LoRa通信模块与传统的水质传感器进行连接,从而使用户可以数十公里外远程监控饮用水在输送过程中的水质变化情况。而在荷兰的KPN项目中,工程人员通过广泛部署LoRa网关,实现LoRa网络全覆盖,为智慧运输、智能农业、智慧路灯等具体应用提供了通信支持。


LoRa系统架构

现在常用的LoRa架构由节点、网关及服务器所组成,各部分的关系如下图所示。 LoRa节点与网关之间采用单跳直接连接,这一阶段的物理层使用线性扩频调制(Chirp Spreading Spectrum, CSS),MAC层(Media Access Control,媒体访问控制)通常使用LoRaWAN协议。

网关收到数据包后,对数据包信号进行解码,并将解码结果通过蜂窝或有线网络传输给网络服务器,这一阶段使用传统的TCP/IP进行传输,同时网络服务器与网关之间的交互仍然遵守LoRaWAN协议。 网络服务器汇总多个网关的数据,过滤重复的数据包,执行安全检查,并根据内容将数据发送至不同的应用服务器,供用户读取和使用,这一阶段也使用TCP/IP和SSL进行传输和加密。


LoRaWAN 

在LoRa网络中,会有很多LoRa节点向同一网关发送数据,这就需要MAC协议来协调不同节点间的数据传输,在LoRa中比较典型的MAC协议就是开源的LoRaWAN。 LoRaWAN是由LoRa联盟在LoRa物理层编码技术的基础上提出的MAC层协议,由LoRa联盟负责维护。LoRaWAN规范1.0版本于2015年6月发布。LoRaWAN协议主要规定了节点与网关、网关与服务器之间的连接规范,确定了LoRa网络的星型拓扑结构。受LoRa节点成本和能耗的限制,现有的LoRaWAN协议基本采用纯ALOHA机制,即节点在发送数据前不进行载波侦听,也就是没有使用CSMA/CA,而是随机选择时间进行发送。

LoRaWAN定义了网络的通信协议和系统架构,还负责管理所有设备的通信频率,数据速率和功率。 在LoRaWAN的控制下,网络中的所有设备可以是异步的,并在只有可用数据时进行传输。 针对不同的应用场景,LoRaWAN定义了三种节点运行模式,分别是Class A(ALL)、Class B(Beacon)、Class C(Continuously Listening):

  • Class A模式主要提供低功耗上行连接,处于Class A模式的节点可以在任意时间发起上行传输,并只在传输结束时打开两个下行接收窗口,此时接收来自网关ACK。Class A模式下,网关无法主动连接到节点,当无数据传输时,节点处于休眠状态,因此该模式下节点能耗最低。
  • Class B模式提供节点与网关的周期性连接,该模式下网关节点周期性向节点广播信标帧,保持节点与网关的时间同步。
  • Class C模式提供节点与网关的持续性连接,该模式下节点始终处于唤醒状态,因此能耗最高。

三种网络模式中,Class A是所有LoRa网络都必须支持的模式,也是最常用的网络模式。这三个模式设计并不复杂,其实就是在网络灵活性、可用性和节能之间的一个平衡。Class A最节能,但是灵活性相对较低,例如下行数据只能依赖于上行数据的时间。Class C最耗电,但是也是上行和下行数据发送最灵活的。


LoRa通信物理层

我们来介绍LoRa通信的基本原理,包括调制、解调、编码和解码,着重于物理层协议的分析,关于上层协议(如LoRaWAN),有很多其他的资料和开源实现供读者学习[1][2],这里就不详述了。

需要说明的是,LoRa物理层是一个商用的私有协议,并没有完整公开的协议说明,因而已有的一些LoRa实现[3][4][5][6][7]都是依照Semtech公司的相关专利和文件猜出来的。[6]MATLAB版本用于原型验证和离线操作,[7]基于GNURadio平台的C++版本则是一个实时的高性能LoRa实现。很多对LoRa的说法只是基于大家的观察和理解,同时很多LoRa代码实现的性能是很差的,包括不少研究论文中使用的LoRa代码,实际性能也存在着很大的问题。

相关论文的介绍见[8]: Zhenqiang Xu, Pengjin Xie, Jiliang Wang. "Pyramid: Real-Time LoRa Collision Decoding withPeak Tracking", IEEE INFOCOM 2021. [PDF]


LoRa调制与解调

在这节我们介绍LoRa的调制与解调,也即如何在物理波形和比特数据之间进行转换。

LoRa 使用 CSS (Chirp Spread Spectrum)线性扩频调制,频率线性扫过整个带宽,因此抗干扰极强,对多径和多普勒效应的抵抗也很强。LoRa的基本通信单元是linear chirp,也即频率随时间线性增加(或减小)的信号。我们将频率随着时间线性增加的chirp符号叫做upchirp,将频率随着时间线性减小的chirp符号叫做downchirp。如下图分别从时域波形和时频域展示了一个upchirp的图像:

 一个chirp怎么编码数据呢?LoRa的做法是通过在频域循环平移chirp进行数据的编码,不同的起始频率代表不同的数据。如下图所示,在带宽B内四等分标定四个起始频率,我们可以得到4种类型的符号,分别表示00,01,10,11。所以在接收端,只需要将这个起始频率计算出来,就可以计算出每一个chirp对应的比特数据。我们将下图(a)所示从最低频率扫频到最高频率的chirp符号称为basic upchirp。

LoRa规定了一个参数SF(Spreading Factor,扩频因子),如上图所示,在带宽一定的情况下,扩频因子的增加意味着采样时长的增加(扫频速度减低)。可以看出,提高SF,虽然在相同时间减少了可以传输的实际数据,但是这样扩频后传输可以降低误码率,灵敏度越高,可以得到更远的传输距离。

LoRa解调过程,实质就是求出chirp符号的起始频率,其做法通常是这样的:首先将收到的基带upchirp信号与downchirp点乘,化为单频信号,这一操作叫做dechirp(解扩频)。

Dechirp之后,对得到的信号进一步做FFT(快速傅里叶变换),即可在频域获得一个峰值,这个峰值位置对应的频率即是起始频率,我们因此得到对应的SF个比特,然后根据所有SF个比特,来判断实际接收Bit数据为1还是0。如下图所示。

一个完整的LoRa数据包结构包含三个部分:前导码(Preamble)、SFD(Start Frame Delimiter)和数据部分(Data)。 前导码包含6~65535个basic upchirp和两个标识网络号的其他chirp符号。接着是2.25个basic downchirp,作为SFD标识数据段的开始。后面的数据段则包含着若干编码了数据的data chirp。

当我们使用软件无线电设备(Software-defined radio, SDR)接收一段LoRa设备发出的信号,并用inspectrum这个软件(其他可画时频图的软件或代码也可以)把信号的时频图画出来,那么它大概会是如下样子:


LoRa 编码与解码

LoRa物理层编码过程如下:

  • 包头共2.5个有效bytes,包尾循环纠错编码。
  • Hamming 编码
  • Interleaving将每字节的位转化到物理层对应的多chirp频率(8 bits/byte => SF bits/symbol),从下图可以看到实际的口空发射与数据之间的关系。

  • Gray 解码(Gray code => Binary code)

LoRa采用循环纠错编码(CRC)进行前向错误检测与纠错,但会产生传输开销。编码率越大前向纠错越强,链路抗干扰性越强,但是传输开销将会加大,进而加大传输时间。

LoRa物理层解码过程与编码相反,如下:

  • 格雷码编码(Gray coding)
  • 对角交织(Interleaving)
  • 海明解码(Hamming decoding)
  • 包头解析(Header Decoding)与CRC(校验CRC checksum)

补充说明一下:在仅仅增加带宽的情况下,能有效的提高传输速率。下图是带宽与SF对接收数据dbm底线以及传输速率的对应数据。


STM32+SX1268实现LoRa

实现原理

MCU选择STM81L101F3P6S实现超低功耗,其与LoRa射频芯片SX1268之间采用SPI通信接口。


嵌入式程序

下面的嵌入式程序为main函数,主要实现了三个部分功能的调用实现。

  • SX126x芯片初始化。
  • 将LoRa接收的数据通过串口打印的功能。
  • 通过摁键触发LoRa发送数据。
oid main(void)
{
    u8 ExtiDelay = 0;
    u8 Txbuffer[5] = {0x11,0x22,0x33,0x44,0x99};
    
	CLK_MasterPrescalerConfig(CLK_MasterPrescaler_HSIDiv1);	//1分频,16MHz

    //串口初始化
    USART1_Init();
    
    //SX126x初始化
	Sx1276M_GpioInt();
	CS_LOW;
	delayxms(5);
	CS_HIGH;
	RESET_HIGH;
	reset_sx1262();//reset RF
	sx1262_Config();//频率431.5M
	Rx_Init();//接收模式 
    
    //按键初始化
	GPIO_Init(GPIOB, GPIO_Pin_1, GPIO_Mode_In_FL_No_IT);//PB1按键
    
  	while(1)
  	{
    	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1))
    	{
            ExtiDelay++;
            delayxms(1);
            if(ExtiDelay > 10)
            {//消抖
            	ExtiDelay = 0;
            	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1))
            	{
    				KeyTrigger_Flag = 0;
    				LORA_TxData(Txbuffer, 5);//LORA发送数据
    				delayxms(30);//等待一会再接收,发射和接收不能同时进行
        			sx1262_Config();
        			Rx_Init();//切回接收模式
            	}
            }
    	}
        hal_Sx1268_RxHandle();//等待接收
	}
}

void USART1_Init(void)
{
	GPIO_Init(GPIOC, GPIO_Pin_3, GPIO_Mode_Out_PP_High_Fast);
	GPIO_Init(GPIOC, GPIO_Pin_2, GPIO_Mode_In_PU_No_IT);
	CLK_PeripheralClockConfig(CLK_Peripheral_USART,ENABLE);//使能串口外设时钟
	USART_Init(115200,                      //波特率115200
			   USART_WordLength_8D,         //数据位8
               USART_StopBits_1,            //停止位1
			   USART_Parity_No,             //无奇偶校验
			   USART_Mode_Tx|USART_Mode_Rx);//USART初始化		 
	USART_ITConfig(USART_IT_RXNE,ENABLE);//使能接收中断
	USART_Cmd(ENABLE);//使能USART
	//USART1_SendStr("usart_tx is ok");
}

void hal_Sx1268_RxHandle(void)
{
  	if(IRQ_DATABIT)//Wait for the IRQ RxDone or Timeout
	{
		Irq_Status = GetIrqStatus();//0x02
		if((Irq_Status&0x02) == RxDone_IRQ)
		{
			GetRxBufferStatus(&packet_size, &buf_offset);	//read rx packet_size
			if(packet_size>0)
			{//接收长度大于0
				ReadBuffer(buf_offset, &rxbuf[0], packet_size); 	//read rx data
           
				USART1_SendString(&rxbuf[0], packet_size);//串口打印接收数据
				ClearIrqStatus(RxDone_IRQ);//清除中断 
			}
			else
			{
				ClearIrqStatus(RxDone_IRQ); 		
			} 
			Rx_Init();
		}
	}
} 

下面是SX1628芯片的驱动代码,包括初始化,发送模式接收配置,数据发送等具体的接口实现。

/*
 * THE FOLLOWING FIRMWARE IS PROVIDED: (1) "AS IS" WITH NO WARRANTY; AND 
 * (2)TO ENABLE ACCESS TO CODING INFORMATION TO GUIDE AND FACILITATE CUSTOMER.
 * CONSEQUENTLY, SEMTECH SHALL NOT BE HELD LIABLE FOR ANY DIRECT, INDIRECT OR
 * CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE CONTENT
 * OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING INFORMATION
 * CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
 * 
 * Copyright (C) SEMTECH S.A.
 */

#include "stm8l10x.h"
#include "sx1268-LoRa.h"
#include "string.h"

void Sx1276M_GpioInt();
   
u8 gb_SF;
u8  gb_BW;
u8  CR;	//LR_RegModemConfig1 
  
#define CRC   0x01  //CRC Enable

#define DATA_LEN        (13)

/**********************************************************
**Parameter table define
**********************************************************/
//__root const u16 SX1276FreqTbl[3] = {0x066C, 0x0780, 0x0800}; //434MHz
__root const u16 SX1276FreqTbl[3] = {0x06D9, 0x0700, 0x0800}; //868MHz @ 26m

 
__root const u16 SX1276PowerTbl[4] =

{ 
  0x09FF,                   //20dbm  
  0x09FC,                   //17dbm
  0x09F9,                   //14dbm
  0x09F6,                   //11dbm 
};


__root const u8 SX1276LoRaBwTbl[10] =
{// 0        1     2       3      4       5         6      7       8      9
//7.8KHz,10.4KHz,15.6KHz,20.8KHz,31.2KHz,41.7KHz,62.5KHz,125KHz,250KHz,500KHz
  0,1,2,3,4,5,6,7,8,9
};

__root const u8 SX1276SpreadFactorTbl[7] =
{
  6,7,8,9,10,11,12
};


u8  SX1276Data[11];

u8  gb_RxData[256];                                         //Receive data buffer

#if 1  //sx1268  Aloma
#define payload_length    13
#define payload_length1    11

#if 0
u8  txbuf[payload_length] = {'s','e','n','d','_','t','e','s','t'};
#else
u8  txbuf[payload_length];
#endif
u8  txbuf_1[payload_length1] = {'r','e','t','u','r','n','_','t','e','s','t'};
u8 rxbuf[200];

u8 packet_size;
u8 buf_offset;

	
u8 Irq_Status;
u8 tx_buff_flag;
extern u16 Lux_Data;
extern u8 STM8S_ID[12];
extern u8 FrameID;
#endif

void delayms(unsigned int t)
{
	unsigned int i;
	unsigned char j; 
	for(i=0;i<t;i++)
	for(j=0;j<120;j++);
}
 
void delayxms(unsigned int t)//8M内部晶振下,已测试
{
	unsigned int i;
	unsigned int j; 
	for(i=0;i<t;i++)
	//for(j=0;j<1600;j++);//8M主频
	for(j=0;j<3200;j++);//16M主频
}
	
void Sx1276M_GpioInt()
{
    //***** RF_SCK         PC_ODR_ODR4
    GPIO_Init(SCK_PORT,SCK_PIN , GPIO_Mode_Out_PP_Low_Slow);
    //*****RF_MISO       PC_IDR_IDR5 //INPUT
    GPIO_Init(MISO_PORT,MISO_PIN , GPIO_Mode_In_PU_No_IT);//上拉输入
    //*****RF_MOSI       PC_ODR_ODR6
    GPIO_Init(MOSI_PORT,MOSI_PIN , GPIO_Mode_Out_PP_Low_Slow); 
    //*****RF_NSEL_PIN  
    GPIO_Init(CS_PORT,CS_PIN , GPIO_Mode_Out_PP_Low_Slow);
    RF SWT
    //BUSY
    GPIO_Init(BUSY_PORT,BUSY_PIN , GPIO_Mode_In_PU_No_IT);
    //IRQ
    GPIO_Init(LORA_RCV_IRQ_PORT,LORA_RCV_IRQ_PIN, GPIO_Mode_In_FL_No_IT);
    //RESET
    GPIO_Init(RESET_PORT,RESET_PIN , GPIO_Mode_Out_PP_Low_Slow); 
}

#if 1//Aloma  sx1268
u8 spi_rw(u8 data) 
{
	u8 i;
	
	for (i = 0; i < 8; i++)//高位先传	
	{				
		if (data & 0x80)
			MOSI_HIGH;
		else
			MOSI_LOW;
			
		data <<= 1;
		SCK_HIGH;
 		if (MISO_DATABIT)//高位先收
			data |= 0x01;
		else
			data &= 0xfe;
			
		SCK_LOW;
	}	
	MOSI_LOW;
	return (data);	
}

/***************sx1262*****************/
void reset_sx1262(void)
{
	RESET_LOW;
	delayxms(1);		//more thena 100us, delay 10ms
	RESET_HIGH;
	delayxms(1);	//delay 10ms
} 

void check_busy(void)//发送时,busy拉低
{
	unsigned char busy_timecnt = 0;			
	while(BUSY_DATABIT)
	{
		delayxms(1);
		busy_timecnt++;
		if(busy_timecnt>5)		//超时120ms复位	
		{
			busy_timecnt=0;
			//SetStandby(0);		//0:STDBY_RC; 1:STDBY_XOSC
			reset_sx1262();		//reset RF
			//sx1262_Config();
			//Rx_Init();
			break;		
		}
	}
}

void SetSleep(void)
{
	u8 Opcode,sleepConfig;
	
	check_busy();
	Opcode = SET_SLEEP;	//0x84
	sleepConfig = 0x00;	//bit2: 1:warm start; bit0: 0: RTC timeout disable
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(sleepConfig);
	CS_HIGH;
}

//0:STDBY_RC; 1:STDBY_XOSC
void SetStandby(u8 StdbyConfig)
{
	u8 Opcode;
	
	check_busy();
	Opcode = SET_STANDBY;	//0x80	
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(StdbyConfig);
	CS_HIGH;
}

void SetTx(u32 timeout)
{
	u8 Opcode;
	u8 time_out[3];
	
	check_busy();
	Opcode = SET_TX;	//0x83
	time_out[0] = (timeout>>16)&0xFF;//MSB
	time_out[1] = (timeout>>8)&0xFF;
	time_out[2] = timeout&0xFF;//LSB
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(time_out[0]);
	spi_rw(time_out[1]);
	spi_rw(time_out[2]);
	CS_HIGH;
}

void SetRx(u32 timeout)
{
	u8 Opcode;
	u8 time_out[3];
	
	check_busy();
	
	Opcode = SET_RX;	//0x82
	time_out[0] = (timeout>>16)&0xFF;//MSB
	time_out[1] = (timeout>>8)&0xFF;
	time_out[2] = timeout&0xFF;//LSB
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(time_out[0]);
	spi_rw(time_out[1]);
	spi_rw(time_out[2]);
	CS_HIGH;
}

//0:GFSK; 1:LORA
void SetPacketType(u8 PacketType)
{
	u8 Opcode;
	
	check_busy();
	Opcode = SET_PACKET_TYPE;	//0x8A	
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(PacketType);
	CS_HIGH;
}

u8 GetPacketType(void)
{
	u8 Opcode;
	u8 Status;
	u8 packetType;
	
	check_busy();
	Opcode = 0x11;	
	
	CS_LOW;
	spi_rw(Opcode);
	Status = spi_rw(0xFF);
	packetType = spi_rw(0xFF);
	CS_HIGH;
	
	return packetType;
}

//RF_Freq = freq_value * 32M / (2^25)  ----->  freq_value = (RF_Freq * (2^25)) / 32M
//431.5M : freq_value = (431.5M * (2^25)) / 32M = 452460544 = 0x1AF80000
//868M : freq_value = (868M * (2^25)) / 32M = 910163968 = 0x36400000
void SetRfFrequency(void)
{
	u8 Opcode;
	u8 Rf_Freq[4];
	u32 RfFreq = 0;
	
	//RfFreq = 0x1B200096;//434M
	RfFreq = 0x1AF80000;//431.5M : freq_value = (431.5M * (2^25)) / 32M = 452460544 = 0x1AF80000
	check_busy();
	
	Opcode = SET_RF_FREQUENCY;	//0x86
	
	Rf_Freq[0] = (RfFreq>>24)&0xFF;//MSB
	Rf_Freq[1] = (RfFreq>>16)&0xFF;
	Rf_Freq[2] = (RfFreq>>8)&0xFF;
	Rf_Freq[3] = RfFreq&0xFF;//LSB
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(Rf_Freq[0]);
	spi_rw(Rf_Freq[1]);
	spi_rw(Rf_Freq[2]);
	spi_rw(Rf_Freq[3]);
	CS_HIGH;
}

void SetPaConfig(void)
{
	u8 Opcode;
	
	check_busy();
	Opcode = 0x95;	
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(0x04);	//paDutyCycle//22dbm
	spi_rw(0x07);	//hpMax:0x00~0x07; 7:22dbm
	spi_rw(0x00);	//deviceSel: 0: SX1262; 1: SX1261
	spi_rw(0x01);
	CS_HIGH;
}

void SetRegulatorMode(void)
{
	u8 Opcode;
	
	check_busy();
	Opcode = 0x96;	
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(0x01);//regModeParam
	CS_HIGH;
}

void WriteRegister(u16 address, u8 *data, u8 length)
{
	u8 Opcode;
	u8 addr_l,addr_h;
	u8 i;
	
	if(length<1)			
		return;
	
	check_busy();
	addr_h = address>>8;
	addr_l = address&0xff;
	
	Opcode = 0x0D;
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(addr_h);//MSB
	spi_rw(addr_l);//LSB
	for(i=0;i<length;i++)
	{
		spi_rw(data[i]);
	}
	CS_HIGH;
}

void ReadRegister(u16 address, u8 *data, u8 length)
{
	u8 Opcode;
	u8 addr_l,addr_h;
	u8 i;
	
	if(length<1)			
		return;
	check_busy();
	
	addr_l = address&0xff;
	addr_h = address>>8;
	
	Opcode = 0x1D;
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(addr_h);//MSB
	spi_rw(addr_l);//LSB
	spi_rw(0x00);	//20190809 fix
	for(i=0;i<length;i++)
	{
		data[i]=spi_rw(0xFF);
	}
	CS_HIGH;
}

/*
@para 
power:
-17(0xEF) to +14(0x0E) dBm by step of 1 dB if low power PA is selected
-9(0xF7) to +22(0x16) dBm by step of 1 dB if high power PA is selected

RampTime:
-------------------------------------
RampTime 	  | Value | RampTime(μs)
-------------------------------------
SET_RAMP_10U    0x00    10
SET_RAMP_20U    0x01    20
SET_RAMP_40U 	0x02	40
SET_RAMP_80U 	0x03	80
SET_RAMP_200U 	0x04	200
SET_RAMP_800U 	0x05	800
SET_RAMP_1700U 	0x06	1700
SET_RAMP_3400U 	0x07	3400
*/

void SetTxParams(int power,u8 RampTime)
{
	u8 Opcode;
	u8 data_buf[2];
	
	// WORKAROUND - Better Resistance of the SX1262 Tx to Antenna Mismatch, see DS_SX1261-2_V1.2 datasheet chapter 15.2
    // RegTxClampConfig = @address 0x08D8
	ReadRegister(0x08D8,data_buf,1); 
	data_buf[0] = data_buf[0]|(0x0F << 1);
        WriteRegister(0x08D8,data_buf,1);
    // WORKAROUND END
	
	check_busy();
	Opcode = SET_TX_PARAMS;	//0x8E	
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(power);
	spi_rw(RampTime);
	CS_HIGH;
}


void SetBufferBaseAddress(u8 TX_base_addr,u8 RX_base_addr)
{
	u8 Opcode;
	
	check_busy();
	Opcode = SET_BUF_BASE_ADDR;	//0x8F	
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(TX_base_addr);
	spi_rw(RX_base_addr);
	CS_HIGH;
}

void WriteBuffer(u8 offset, u8 *data, u8 length)
{
	u8 Opcode;
	u8 i;
	
	if(length<1)			
		return;
	
	check_busy();
	Opcode = 0x0E;
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(offset);
	for(i=0;i<length;i++)
	{
		spi_rw(data[i]);
	}
	CS_HIGH;
}


void ReadBuffer(u8 offset, u8 *data, u8 length)
{
	u8 Opcode;
	u8 i;
	
	if(length<1)			
	return;
	check_busy();
	
	Opcode = 0x1E;
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(offset);
	spi_rw(0xFF);
	for(i=0;i<length;i++)
	{
		data[i]=spi_rw(0xFF);
	}
	CS_HIGH;
}

void GetRxBufferStatus(u8 *payload_len, u8 *buf_pointer)
{
	u8 Opcode;
	u8 Status;
	
	check_busy();
	
	Opcode = 0x13;
	CS_LOW;
	spi_rw(Opcode);

	Status = spi_rw(0xFF);
	*payload_len = spi_rw(0xFF);
	*buf_pointer = spi_rw(0xFF);
	CS_HIGH;
}

void SetModulationParams(u8 sf, u8 bw, u8 cr, u8 ldro)
{
	u8 Opcode;
	
	check_busy();
	Opcode = 0x8B;
	
	CS_LOW;
	spi_rw(Opcode);
	
	spi_rw(sf);//SF=5~12
	spi_rw(bw);//BW
	spi_rw(cr);//CR
	spi_rw(ldro);//LDRO LowDataRateOptimize 0:OFF; 1:ON;
	
	spi_rw(0XFF);//
	spi_rw(0XFF);//
	spi_rw(0XFF);//
	spi_rw(0XFF);//
	
	CS_HIGH;
}

void SetPacketParams(uint8_t payload_len)
{
	u8 Opcode;
	u16 prea_len;
	u8 prea_len_h,prea_len_l;
	u8 data_buf[2];
	u8 invertIQ;
	
	check_busy();
	
	Opcode = 0x8C;
	
	prea_len = 16;//前导码长度
	prea_len_h = prea_len>>8;
	prea_len_l = prea_len&0xFF;
	
	invertIQ = LORA_IQ_NORMAL;
	
	CS_LOW;
	spi_rw(Opcode);
	
	spi_rw(prea_len_h);//PreambleLength MSB
	spi_rw(prea_len_l);//PreambleLength LSB
	spi_rw(0x00);//HeaderType 0:Variable,explicit 1:Fixed,implicit
	//spi_rw(0x01);
	spi_rw(payload_len);//PayloadLength: 0x00 to 0xFF
	
	spi_rw(0X01);//CRCType 0:OFF 1:ON
	spi_rw(invertIQ);//InvertIQ 0:Standard 1:Inverted
	spi_rw(0XFF);//
	spi_rw(0XFF);//
	spi_rw(0XFF);//
	
	CS_HIGH;	
	
	// WORKAROUND - Optimizing the Inverted IQ Operation, see DS_SX1261-2_V1.2 datasheet chapter 15.4
	if( invertIQ == LORA_IQ_INVERTED )
	{
		// RegIqPolaritySetup = @address 0x0736		
		ReadRegister(0x0736,data_buf,1);
		data_buf[0] = data_buf[0] & ~( 1 << 2 );
		WriteRegister( 0x0736,data_buf,1);
	}
	else
	{
		// RegIqPolaritySetup @address 0x0736
		ReadRegister(0x0736,data_buf,1);//0x0D
		data_buf[0] = data_buf[0] | ( 1 << 2 );
		WriteRegister(0x0736,data_buf,1);
	}
	// WORKAROUND END
}

void SetDioIrqParams(u16 irq)
{
	u8 Opcode;
	u16 Irq_Mask;
	u8 Irq_Mask_h,Irq_Mask_l;
	u16 DIO1Mask;
	u8 DIO1Mask_h,DIO1Mask_l;
	u16 DIO2Mask;
	u8 DIO2Mask_h,DIO2Mask_l;
	u16 DIO3Mask;
	u8 DIO3Mask_h,DIO3Mask_l;
	
	Irq_Mask = irq;
	DIO1Mask = irq;
	DIO2Mask = 0;
	DIO3Mask = 0;
	
	Irq_Mask_h = Irq_Mask>>8;
	Irq_Mask_l = Irq_Mask&0xFF;
	DIO1Mask_h = DIO1Mask>>8;
	DIO1Mask_l = DIO1Mask&0xFF;
	DIO2Mask_h = DIO2Mask>>8;
	DIO2Mask_l = DIO2Mask&0xFF;
	DIO3Mask_h = DIO3Mask>>8;
	DIO3Mask_l = DIO3Mask&0xFF;
	Opcode = 0x08;
	
	check_busy();
	
	CS_LOW;
	spi_rw(Opcode);
	
	spi_rw(Irq_Mask_h);//Irq_Mask MSB
	spi_rw(Irq_Mask_l);//Irq_Mask LSB
	spi_rw(DIO1Mask_h);//
	spi_rw(DIO1Mask_l);//
	
	spi_rw(DIO2Mask_h);//
	spi_rw(DIO2Mask_l);//
	spi_rw(DIO3Mask_h);//
	spi_rw(DIO3Mask_l);//
	
	CS_HIGH;
}

u16 GetIrqStatus(void)
{
	u8 Opcode;
	u8 Status;
	u16 IrqStatus;
	u8 temp;
	
	check_busy();
	
	Opcode = 0x12;
	
	CS_LOW;
	spi_rw(Opcode);
	Status = spi_rw(0xFF);
	temp = spi_rw(0xFF);
	IrqStatus = temp;
	IrqStatus = IrqStatus<<8;
	temp = spi_rw(0xFF);
	IrqStatus = IrqStatus|temp;
	CS_HIGH;	
	
	return IrqStatus;
}
void ClearIrqStatus(u16 irq)
{
	u8 Opcode;
	u16 irq_h,irq_l;
	check_busy();
	
	irq_h = irq>>8;
	irq_l = irq&0xFF;
	
	Opcode = 0x02;
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(irq_h);
	spi_rw(irq_l);
	CS_HIGH;
}

void SetDIO2AsRfSwitchCtrl(void)
{
	u8 Opcode;
	
	check_busy();
	Opcode = 0x9D;
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(0x01);   //DIO2 is selected to be used to control an RF switch; DIO2 = 1 in TX mode
	CS_HIGH;
}

#define  DIO3_1_6V  0x00
#define  DIO3_1_7V  0x01
#define  DIO3_1_8V  0x02
#define  DIO3_2_2V  0x03
#define  DIO3_2_4V  0x04
#define  DIO3_2_7V  0x05
#define  DIO3_3_0V  0x06
#define  DIO3_3_3V  0x07

void SetDIO3AsTCXOCtrl(uint8_t tcxoVoltage)
{
	u8 Opcode;
	
	check_busy();
	Opcode = 0x97;
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(tcxoVoltage);   //
	spi_rw(0x00);		   //Timeout MSB ; Timeout duration = Timeout *15.625 μs
	spi_rw(0x00);
	spi_rw(0x64);          //Timeout LSB
	
	CS_HIGH;
	}

void ClearDeviceErrors(void)
{
	u8 Opcode;
	
	check_busy();
	Opcode = 0x07;
	
	CS_LOW;
	spi_rw(Opcode);
	spi_rw(0x00);   
	spi_rw(0x00);  
	CS_HIGH;
	}

void sx1262_Config(void)
{
	u8 bw_temp;
	u8 data_buf[2];
	
	SetStandby(0);//0:STDBY_RC; 1:STDBY_XOSC
	SetRegulatorMode();
	SetPaConfig();
	
	/*************************************
	** Uncomment below two lines if you **
	** used the SX1262 module of nicerf,**
	** keep comment if SX1268 used.     **
	**************************************/
	/*SetDIO3AsTCXOCtrl(DIO3_1_8V);*/
	/*ClearDeviceErrors();*/
	
	SetDIO2AsRfSwitchCtrl();
	
	SetPacketType(1);	//0:GFSK; 1:LORA
	SetRfFrequency();		//设置频率
	SetTxParams(22,SET_RAMP_10U);	//set power and ramp_time
	
	bw_temp = LORA_BW_500;//9501
	//bw_temp = LORA_BW_125;//4338: SF7, LORA_BW_125  9501: SF8, LORA_BW_500
	SetModulationParams(SF8, bw_temp, LORA_CR_4_5, LDRO_ON);//9501速率

	// WORKAROUND - Modulation Quality with 500 kHz LoRa mode Bandwidth, see DS_SX1261-2_V1.2 datasheet chapter 15.1
    if(bw_temp == LORA_BW_500)
    {
        // RegTxModulation = @address 0x0889
		ReadRegister(0x0889,data_buf,1);//0x04
		data_buf[0] = data_buf[0] & ~( 1 << 2 );		 
        WriteRegister(0x0889,data_buf,1);
    }
    else
    {
        // RegTxModulation = @address 0x0889		
		ReadRegister(0x0889,data_buf,1);
		data_buf[0] = data_buf[0] | ( 1 << 2 ); 
        WriteRegister(0x0889,data_buf,1);
    }
    // WORKAROUND END
	
	SetPacketParams(payload_length);//PreambleLength;HeaderType;PayloadLength;CRCType;InvertIQ
}

void LORA_TxData(unsigned char *data, unsigned char len)
{
	//unsigned char busy_timecnt;
	//u8 i;
	u8 busy_timecnt = 0;
	SetStandby(0);//0:STDBY_RC; 1:STDBY_XOSC
	//SetBufferBaseAddress(0,0);//(TX_base_addr,RX_base_addr)
	
	//if(tx_buff_flag==0)

	WriteBuffer(0,data,len);//(offset,*data,length)
	SetPacketParams(len);//PreambleLength;HeaderType;PayloadLength;CRCType;InvertIQ
	
	//else
	//{
		//WriteBuffer(0,txbuf_1,payload_length1);//(offset,*data,length)
		//SetPacketParams(payload_length1);//PreambleLength;HeaderType;PayloadLength;CRCType;InvertIQ
	//}
	
	SetDioIrqParams(TxDone_IRQ);//TxDone IRQ

	//Define Sync Word value
	SetTx(0);//timeout = 0
	

	//Wait for the IRQ TxDone or Timeout
	#if 1
	while(!IRQ_DATABIT)
	{
		delayxms(1);//if time out, reset the module//超时120ms复位//复位时间与发送数据的长度有关
		busy_timecnt++;
		if(busy_timecnt>60)
		{	
			busy_timecnt=0;	
			ClearIrqStatus(TxDone_IRQ);	//Clear the IRQ TxDone flag
        	SetStandby(0);				//0:STDBY_RC; 1:STDBY_XOSC
        	reset_sx1262();				//reset RF
        	sx1262_Config();
			break;		
		}
	}
	busy_timecnt=0;	
	ClearIrqStatus(TxDone_IRQ);	//Clear the IRQ TxDone flag
	SetStandby(0);				//0:STDBY_RC; 1:STDBY_XOSC
	reset_sx1262();				//reset RF
	sx1262_Config();
	#else
	delayxms(30);//发送数据的长度越长,延时时间越长。发送速率越低,延时时间越长。
	if(!IRQ_DATABIT)
	{
		ClearIrqStatus(TxDone_IRQ);	//Clear the IRQ TxDone flag
		SetStandby(0);				//0:STDBY_RC; 1:STDBY_XOSC
		reset_sx1262();				//reset RF
		sx1262_Config();
	}
	#endif
	Irq_Status = GetIrqStatus();//0x8C
	ClearIrqStatus(TxDone_IRQ);//Clear the IRQ TxDone flag
	//UART1_printf("Irq_Status=%d\r\n",Irq_Status);
	//Irq_Status = GetIrqStatus();
}

void Rx_Init(void)
{
	//SetBufferBaseAddress(0,0);			//(TX_base_addr,RX_base_addr)
	//SetPacketParams(payload_length);	//PreambleLength;HeaderType;PayloadLength;CRCType;InvertIQ
	
	SetDioIrqParams(RxDone_IRQ);		//RxDone IRQ
	SetRx(0);//timeout = 0
}
#endif

参考文献

  1. https://github.com/Lora-net/LoRaMac-node
  2. GitHub - brocaar/chirpstack-network-server: ChirpStack Network Server is an open-source LoRaWAN network-server.
  3. https://github.com/rpp0/gr-lora
  4. GitHub - BastilleResearch/gr-lora: GNU Radio OOT module implementing the LoRa PHY, based on https://github.com/matt-knight/research/tree/master/2016_05_20_jailbreak
  5. LoRa PHY based on GNU Radio ‒ TCL ‐ EPFL
  6. GitHub - jkadbear/LoRaPHY: Complete LoRa physical layer (LoRa PHY) implementation in MATLAB.
  7. GitHub - jkadbear/gr-lora: LoRa physical layer collision decoding based on GNU Radio
  8. Zhenqiang Xu, Pengjin Xie, Jiliang Wang. "Pyramid: Real-Time LoRa Collision Decoding withPeak Tracking", IEEE INFOCOM 2021. [PDF code]

十六宿舍 原创作品,转载必须标注原文链接。

©2023 Yang Li. All rights reserved.

欢迎关注 『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。

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

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

相关文章

MySQL实战解析底层---如何正确地显示随机消息

目录 前言 内存临时表 磁盘临时表 随机排序方法 前言 现在说说MySQL中的另外一种排序需求&#xff0c;希望能够加深对MySQL排序逻辑的理解从一个单词表中随机选出三个单词这个表的建表语句和初始数据的命令如下&#xff1a; 为了便于量化说明&#xff0c;在这个表里面插入…

ChatGPT科研阅读论文应用插件(txyz.ai)使用初探

前言 ChatGPT没有办法直接阅读论文&#xff0c;但使用txyz.ai插件可以使用ChatGPT来帮助快速得到论文中想要的信息&#xff0c;特别是对于专业名词较多的文章&#xff0c;而且可以问它关于这篇文章的问题&#xff0c;能够加快研究的进程。刚开始了解到这个应用是一个ChatGPT插件…

06- c语言指针 (C语言)

一 指针的引入 1、一般把内存中的一个字节称为一个内存单元。 2、为了正确地访问这些内存单元&#xff0c;必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址&#xff0c;通常也把这个地址称为指针。 3、如果在程序中定义…

设计模式之策略模式笔记

设计模式之策略模式笔记 说明Strategy(策略)目录策略模式示例类图抽象策略类策略A类策略B类策略C类促销员类测试类 说明 记录下学习设计模式-策略模式的写法。JDK使用版本为1.8版本。 Strategy(策略) 意图:定义一系列的算法&#xff0c;把它们一个个封装起来&#xff0c;并且…

【数据分享】全国县市2000-2021年综合经济数据(地区生产总值\一二三产业增加值等)

《中国县域统计年鉴》是一部全面反映我国县域社会经济发展状况的资料性年鉴&#xff0c;收录了上一年度全国2000多个县域单位的基本情况、综合经济、农业、工业、教育、卫生、社会保障等方面的资料。 本篇文章我们给大家带来从2001年—2022年《中国县域统计年鉴》整理的数据—…

MySQL:事务

事务 在介绍事务之前&#xff0c;我们先来了解一个案例&#xff1a; 在一个买票的软件中&#xff0c;当客户端A检查还有一张票时&#xff0c;将票卖点&#xff0c;但是还没有更新数据库&#xff0c;客户端B检查了票数&#xff0c;发现大于0&#xff0c;于是又卖掉了一张票。然…

ROS:通信机制

目录 一、通信机制简介二、话题通信机制2.1话题通信简介2.2话题通信实操&#xff08;C&#xff09;2.2.1分析2.2.2发布方代码2.2.3订阅方代码2.2.4配置CMakeLists.txt2.2.5执行2.2.6注意 2.3话题通信实操&#xff08;python&#xff09;2.3.1分析2.3.2发布方代码2.3.3订阅方代码…

【计算机网络】第二章 物理层(上)

文章目录 2.1 物理层的基本概念2.2 物理层下面的传输媒体2.3 传输方式2.4 编码与调制2.4.1 介绍2.4.2 常用编码2.4.3 编码习题2.4.4 基本调制方法2.4.5 混合调制 2.1 物理层的基本概念 物理层考虑的是怎样在连接各种计算机的传输媒体上传输数据比特流。 物理层为数据链路层屏蔽…

4.0、Java_IO流 - 流的概念细分

4.0、Java_IO流 - 流的概念细分 按照流的方向分类&#xff1a; 输入流&#xff1a;数据流从数据源到程序&#xff08;以 InputStream 、Reader 结尾的流&#xff09;&#xff1b; 输出流&#xff1a;数据流从程序到目的地&#xff08;以 OutputStream 、Writer 结尾的流&#x…

【雕爷学编程】Arduino动手做(120)---游戏摇杆扩展板

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

2023-06-23:redis中什么是缓存击穿?该如何解决?

2023-06-23&#xff1a;redis中什么是缓存击穿&#xff1f;该如何解决&#xff1f; 答案2023-06-23&#xff1a; 缓存击穿是指一个缓存中的热点数据非常频繁地被大量并发请求访问&#xff0c;当该热点数据失效的瞬间&#xff0c;持续的大并发请求无法通过缓存获取到数据&…

Triton教程 --- Triton 响应缓存

Triton教程 — Triton 响应缓存 Triton系列教程: 快速开始利用Triton部署你自己的模型Triton架构模型仓库存储代理模型设置优化动态批处理速率限制器模型管理自定义算子解耦后端和模型 概述 在本文档中&#xff0c;推理请求是模型名称、模型版本和输入张量&#xff08;名称、…

ChatGPT从入门到精通,深入认识ChatGPT

ChatGPT从入门到精通&#xff0c;一站式掌握办公自动化/爬虫/数据分析和可视化图表制作 全面AI时代就在转角 道路已经铺好了 “局外人”or“先行者” 就在此刻 等你决定1、ChatGPT从入门到精通&#xff0c;一站式掌握办公自动化/爬虫/数据分析和可视( 点击观看完整版本 )https…

TreeMap数据结构及源码解析.跟学黑马

TreeMap数据结构及源码解析 1.TreeMap的特点2.TreeMap的数据结构2.1二叉查找树2.1.1二叉查找树的定义2.1.2二叉查找树的查找操作 2.2平衡二叉树2.2.1平衡二叉树的定义2.2.2平衡二叉树的旋转 2.3红黑树2.3.1红黑树的定义 2.TreeMap的源码分析2.1get()获取方法分析2.2put()添加方…

企业级开发项目和自学项目到底有什么区别

前言 好久不见了各位&#xff01;最近几个月都未更新&#xff0c;是因为从春招开始就在投简历面试实习岗位&#xff0c;然后入职&#xff0c;最后成功成为了一个半成品后端练习生&#xff0c;想说的话有太多太多 下面就站在一个在校实习生的身份&#xff0c;结合自己最近几个月…

科普 | 眼图

本文简要说明眼图相关的知识&#xff0c;参考是德科技的文章 1。 科普 | 眼图 基本知识串扰眼图眼图的产生原理及作用创建眼图 - 眼图波形的采样过程眼图的产生原理及作用眼图可以看出哪些性能指标&#xff1f;如何评判眼图质量&#xff1f;眼图测试模板眼图与存储深度实时的眼…

短视频seo源码开发部署技术解析

短视频seo开发需要哪些技术 应用程序优化技术&#xff1a;包括应用程序的各种元素&#xff08;如标题、描述、关键字、图标等&#xff09;的优化和设置&#xff0c;以及应用程序内部链接和导航的合理布局和设置。 视频内容优化技术&#xff1a;包括视频标题、描述、标签、封面…

人工智能数据集处理——数据获取

目录 1、从csv和txt文件中读取数据 pandas中可使用read_csv读取csv或txt文件中的数据 使用read_csv()函数读取phones.csv文件中的数据,并指定编码格式为gbk 使用head()方法指定获取phones.csv文件中前3行的数据 使用read_csv() 函数读取 itheima_books.txt文件中的数据,并指…

【Redis】2、Redis 的 Java 客户端(Jedis 和 SpringDataRedis)

目录 零、Redis 的 Java 客户端有哪些&#xff1f;二、Jedis 客户端(1) 引依赖(2) 连接 Redis 服务并测试(3) Redis 连接池 三、SpringDataRedis 介绍四、SpringBoot 中集成 SpringDataRedis(1) 引入依赖(2) 配置文件中书写相关配置(3) RedisTemplate 的默认序列化方式(4) 自定…

高校学生考勤系统

摘 要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括高校学生考勤系统的网络应用&#xff0c;在外国高校学生考勤系统已经是很普遍的方式&#xff0c;不过国内的高校学生考勤可能还处于起步阶段。高校学生考勤系统具有管理…