文章先从LoRa的物联网通信技术前辈们讲起,慢慢引出了这种功耗又低,距离又远的无线通信技术,然后又似庖丁解牛一般,从物理层到链路层,详细的介绍了LoRa这种技术的组成,最后以一种实际的原理与嵌入式软件实现,让读者近距离接触到基于LoRa这种无线通信技术产品的开发过程。总而言之,博主在这一篇文章中集中的介绍了物联网无线通信技术-LoRa的前世今生,帮助各位对这门“新”的无线通信技术有一个全面且直观的了解。
文章目录
LoRa技术前序
LoRa技术简介
LoRa应用
LoRa系统架构
LoRaWAN
LoRa通信物理层
LoRa调制与解调
LoRa 编码与解码
STM32+SX1268实现LoRa
实现原理
嵌入式程序
参考文献
LoRa技术前序
LoRa之前的主要无线通信协议分为一下三种:
- 第一类是远距离高速率的传输协议,典型协议包括蜂窝网络通信技术,如3G、4G、5G相关技术等,这是我们目前移动通信使用的典型技术。
- 第二类是近距离高速率传输技术,如WiFi、蓝牙等,这些技术传输距离在几十到几百米级别,主要用在家庭环境和日常应用中,使用非常广泛,前面两类可能是一般用户最常使用到的网络协议了,也符合传统网络应用的主要特点和需求。
- 第三类是近距离低功耗传输技术,如传统物联网中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
参考文献
- https://github.com/Lora-net/LoRaMac-node
- GitHub - brocaar/chirpstack-network-server: ChirpStack Network Server is an open-source LoRaWAN network-server.
- https://github.com/rpp0/gr-lora
- 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
- LoRa PHY based on GNU Radio ‒ TCL ‐ EPFL
- GitHub - jkadbear/LoRaPHY: Complete LoRa physical layer (LoRa PHY) implementation in MATLAB.
- GitHub - jkadbear/gr-lora: LoRa physical layer collision decoding based on GNU Radio
- 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.
欢迎关注 『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。