LoRa协议在Arduino上的应用——原理及代码分析(二)

前述文章链接在此~~
LoRa协议在Arduino上的应用——原理及代码分析(一).

LoRa调制与解调模式

扩频循环纠错编码相结合
在这里插入图片描述
可以看到,LoRa调制模式下,有一个独立的双端口数据缓冲区FIFO,可通过所有模式共有的SPI接口进行访问。
可针对特定的应用优化LoRa调制:扩频因子调制带宽错误编码率
在这里插入图片描述
先说一下扩频:其实通俗来讲,原来我发送一个信号,这个信号中只包含两个bit10,现在我发送一个信号,这个信号中包含16个bit,这样我就相当于将信号拓宽,扩频因子就是16/2=8。那么我们就可以理解为什么扩频技术是扩展信号的带宽:把信道想象成一个通道,原来一次只能通过两个bit,现在一次性可以通过16个bit,信道是不是变宽了?
在这里插入图片描述
但有一点需要注意,就是扩频前后信号的能量是不变的
至于扩频的好处:可以根据香农公式,在相同的信息速率下,带宽和信噪比可以互换,扩频就是用大带宽,换接收端信噪比的低要求

再来看下循环纠错编码
其实就是使用纠错编码中的循环码:网上资料很多,不赘述了

代码分析

主程序

int counter = 0;
...
void loop() {
	...
  // send packet
  LoRa.beginPacket();
  LoRa.print("hello ");
  LoRa.print(counter);
  LoRa.endPacket();
  }

开始发包

int LoRaClass::beginPacket(int implicitHeader)
{
  if (isTransmitting()) {
    return 0;
  }

  // put in standby mode
  idle();

  if (implicitHeader) {
    implicitHeaderMode();
  } else {
    explicitHeaderMode();
  }

  // reset FIFO address and paload length
  writeRegister(REG_FIFO_ADDR_PTR, 0);
  writeRegister(REG_PAYLOAD_LENGTH, 0);

  return 1;
}

在头文件中,已经设置implicitHeader默认为false,因此会进入explicitHeaderMode()函数

void LoRaClass::explicitHeaderMode()
{
  _implicitHeaderMode = 0;

  writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) & 0xfe);
}

可以看到这个函数就是把原来调制模式配置寄存器中的数值读出来,将最后一位设置为0,表示Explicit Header mode,然后写回。

Packet Structure

LoRa的数据包包含三个部分:
在这里插入图片描述

  1. A preamble:前导码
    用于保持接收机与输入的数据流同步,作用提醒接收芯片,即将发送的是有效信号。默认size是12个符号长度,有时为了缩短接收机占空比,可以缩短前导码长度。接收机会定期检测前导码,因此接收和发射前导码长度需要一致。
  2. An optional header
    可以选择显示(explicit)或隐式(implicit)两种类型。
    显式报头:包括Payload长度,前向纠错编码率,是否使用CRC(16bit)
    隐式报头:需要手动设置无线链路两端的Payload长度、错误编码率、CRC(如果扩频因子SF=6,只能使用隐式报头模式
  3. The data payload
    长度不固定,在FIFO中读写

数据传输时间

LoRa数据包总传输时间 = 前导码传输时间Tpre + 数据包传输时间Tpay
前导码传输时间为:Tpre = (Npre + 4.25)*Tpay
Npre表示已设定的前导码长度(读取RegPreambleMsbRegPreambleLsb寄存器得到)
计算Payload符号数的公式为:
在这里插入图片描述
PL表示Payload字节数,H=0表示header使能,DE=1表示LowDataRateOptimize=1CR表示编码速率

重置FIFO地址与payload长度

结束发包

int LoRaClass::endPacket(bool async)
{
  
  if ((async) && (_onTxDone))
      writeRegister(REG_DIO_MAPPING_1, 0x40); // DIO0 => TXDONE

  // put in TX mode
  writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);

  if (!async) {
    // wait for TX done
    while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) {
      yield();
    }
    // clear IRQ's
    writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK);
  }

  return 1;
}

默认async = false_onTxDone(NULL)
在这里插入图片描述
在这里插入图片描述
SX112系列的6个通用DIO引脚在LoRa模式下均可用,其映射关系取决于RegDioMapping1RegDioMapping2这两个寄存器
第一句代码的意思就是如果处于异步通信状态,且在发射完成状态(此处有疑义),就将DIO0引脚映射为发射完成指示符
设置为发射模式,如果不处于异步通信状态,就等待发送完成,然后清除中断。
这里讲的不是很清楚,我们再来具体看一下:

  1. async:意思是异步,是在non-blocking mode下使用的一个变量。由于网关可能不会向后发送任何数据,因此我们在尝试接收之前使套接字成为非阻塞状态,以防止卡在等待永远不会到达的数据包中
    同步、异步、阻塞、非阻塞这四个是进程间通信的概念,进程间通信通过send()receive()两种基本操作完成:
    阻塞式发送:发送方进程会一直被阻塞,直到消息被接收方进程收到;
    非阻塞式发送:发送方调用send()后,可以一直进行其他操作;
    阻塞式接收:接收方进程会一直被阻塞,直到消息到达可用;
    非阻塞式接收:接收方调用receive()后,要么得到一个有效的效果,要么得到一个空值
    (此处参考文章
  2. void (*_onTxDone)();
    可以看到,这是一个类内私有的函数指针,_onTxDone是一个指针,该指针指向的函数的返回值是void
    初始化时,该指针指向的确实是一个NULL,但是如果执行了这个函数:
void LoRaClass::onTxDone(void(*callback)())
{
  _onTxDone = callback;
  .......
}

那么这个指针就不为空,也就是满足这个判断条件:

if ((async) && (_onTxDone))

在这里插入图片描述

writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);

这一句对应的就是上图的Mode Request Tx

while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0)

这一句就是在等待中断

中断源

  1. FifoEmpty:当整个FIFO为空,中断源为高,从FIFO检索数据的时候,FifoEmpty在NSS下降沿更新。也就是说每次读取操作之后检查FifoEmpty状态,以决定是否读取下一个字节,FifoEmpty=1表示不需要读取更多字节
  2. FifoFull
  3. FifoOverrunFlag:is set when a new byte is written by the user (in Tx or Standby modes) or the SR (in Rx mode) while the FIFO is already full,在FIFO已满的情况下,有一个新的字节被写入的时候。数据丢失,并且标志应通过写1清除,同时FIFO也将被清除。
  4. PacketSent:when the SR’s last bit has been sent
  5. FifoLevel

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#define IRQ_TX_DONE_MASK           0x08

只把TxDoneMask置为1,其余全部置为0。而如果要满足while中的循环条件,则REG_IRQ_FLAGS这个寄存器中的第3bit位TxDone必须为0,如果不满足就yield()就是一直执行这个空循环
跳出这个循环之后,就清除中断标志,并且设置TX完成标志位

Receiver

void loop() {
  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.print("Received packet '");

    // read packet
    while (LoRa.available()) {
      Serial.print((char)LoRa.read());
    }

    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
  }
}

parsePacket

int LoRaClass::parsePacket(int size)
{
  int packetLength = 0;
  int irqFlags = readRegister(REG_IRQ_FLAGS);

  if (size > 0) {
    implicitHeaderMode();

    writeRegister(REG_PAYLOAD_LENGTH, size & 0xff);
  } else {
    explicitHeaderMode();
  }

  // clear IRQ's
  writeRegister(REG_IRQ_FLAGS, irqFlags);

  if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {
    // received a packet
    _packetIndex = 0;

    // read packet length
    if (_implicitHeaderMode) {
      packetLength = readRegister(REG_PAYLOAD_LENGTH);
    } else {
      packetLength = readRegister(REG_RX_NB_BYTES);
    }

    // set FIFO address to current RX address
    writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR));

    // put in standby mode
    idle();
  } else if (readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)) {
    // not currently in RX mode

    // reset FIFO address
    writeRegister(REG_FIFO_ADDR_PTR, 0);

    // put in single RX mode
    writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE);
  }

  return packetLength;
}

这个函数很长,我们逐步分析。
首先观察返回值,可以知道这个函数是计算数据包的长度
在头文件中默认定义int parsePacket(int size = 0);
也就是说会进入显式数据包模式

#define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20
#define IRQ_RX_DONE_MASK           0x40

接收一个数据包的条件是:从中断寄存器中的读取的数据第6位为0或者第5位为0
在这里插入图片描述
读取完数据包长度之后,还需要设置FIFO的地址
在这里插入图片描述
具体就是跟着这张图来

是否存在数据包

int LoRaClass::available()
{
  return (readRegister(REG_RX_NB_BYTES) - _packetIndex);
}

这个看起来有点像串口检测字符的那个函数。。。。。
如果存在数据包,REG_RX_NB_BYTES这个寄存器的值就不为0,就会开始读取数据

数据读取

int LoRaClass::read()
{
  if (!available()) {
    return -1;
  }

  _packetIndex++;

  return readRegister(REG_FIFO);
}

返回从FIFO中读取的数据

RSSI

received signal strength indicator:接收信号强度指示符
常规
RSSI (dBm) = -157 + Rssi, (高频口)
RSSI (dBm) = -164 + Rssi, (低频口)

int LoRaClass::packetRssi()
{
  return (readRegister(REG_PKT_RSSI_VALUE) - (_frequency < 868E6 ? 164 : 157));
}

在这里插入图片描述
在这里插入图片描述

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
atk-lora模块是一款基于Arduino的无线通信模块,它采用LoRa无线技术,能够实现长距离、低功耗、低速率的数据传输。下面是一个基本的atk-lora模块的Arduino代码示例。 首先,我们需要引入LoRa库以便使用相关函数。在Arduino IDE中,点击Sketch -> 包含库 -> 管理库,搜索"LoRa"并安装。 然后,我们需要定义配置参数,如频段、传输功率等。例如: #include <LoRa.h> #define BAND 915E6 // 频段,根据所使用的模块和地区进行设置 #define POWER 20 // 传输功率,单位为 dBm,可根据需求进行调整 void setup() { Serial.begin(9600); // 初始化串口,用于调试输出 LoRa.setPins(ss, rst, dio0); // 设置SS引脚,复位引脚,中断引脚(根据模块的实际连接情况进行修改) if (!LoRa.begin(BAND)) { // 初始化LoRa模块 Serial.println("LoRa init failed. Check your wiring."); while(true); // 若初始化失败,则进入死循环 } LoRa.setTxPower(POWER); // 设置传输功率 } void loop() { // 在这里编写你的代码 } 在setup()函数中,我们首先初始化了串口,方便我们在调试时输出信息。然后通过调用LoRa.setPins()函数设置LoRa模块相关的引脚。接着,通过调用LoRa.begin()函数初始化LoRa模块。如果初始化失败,会打印错误信息,并进入死循环。 在loop()函数中,你可以编写你自己的代码。例如,可以使用LoRa库提供的函数发送和接收数据。例如,使用LoRa.beginPacket()函数开始一个数据包的传输,使用LoRa.write()函数写入数据,最后通过调用LoRa.endPacket()函数完成数据包的传输。 这只是一个基本的atk-lora模块的Arduino示例代码。你可以根据你的具体需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值