Tutorial: LoRa

From MobiNetS
Jump to: navigation, search

This page shows the tutorial for LoRa experiments.

LoRa basics

Platform and programming environments

Arduino + Lora

  • Lora is a wireless communication module,we also need a MCU to contral it like Raspberry Pi(树莓派)Arduino and so on. (Here we choose Arduino).
  • More about Arduino
  • Lora

Lora通信实例

  1. 使用Arduino进行Lora开发,首先需要环境配置:
  • 下载和安装Arduino IDE [Arduino下载]
  • 在IDE中添加开发板 我们要在IDE上编写代码烧录到板子上运行,首先需要把板子信息加载到IDE里面,这样,IDE才能识别到开发板。以我们使用的Dragino开发板为例:
具体步骤:
 在PC上打开IDE --> 点击File --> preference-->在Additional Boards Manager URLs里添加以下URL:http://www.dragino.com/downloads/downloads/YunShield/package_dragino_yun_test_index.json
 转到tools --> Boards --> Boards Manager,找到Dragino信息并安装它
 这样,我们的Dragino开发板就成功的关联到了IDE里面,可以打开IDE --> Tools的开发板,就能找到Dragino
    • 要使用Lora通信,需要调用Lora的库文件,当然,我们有现成的库可以用。点击下载[RadioHead],将其拷贝到Arduino安装目录下的libraries里面(我的是在 C:\Program Files (x86)\Arduino\libraries),就可以从IDE当中直接导包,使用里面提供的接口
    • 用数据线将开发板连接到电脑,打开IDE --> Tools --> 开发板 --> 选择Arduino/Genuino UNO(因为我们使用的是UNO型号的板子),然后:Tools --> 端口(选择开发板连接的端口)。下面是Lora通信的示例代码:

Client:

#include <SPI.h>   //lora跟Arduino之间使用的是SPI通信方式
#include <RH_RF95.h>  //lora的库文件,提供了关于设置lora发射参数等一系列函数
RH_RF95 rf95;
float frequency = 433.0;  //在国内,ISM频段只有433MHz可用,这里需要注意,如果我们买的是868M或者915M的lora板子,是无法使用的
void setup() 
{
 Serial.begin(9600);  //设置波特率,为了Arduino开发板跟电脑端的串口通信使用,有时候使用串口助手发现电脑端显示的全是乱码,就很有可能是波特率设置的不一样。
 while (!Serial) ; // Wait for serial port to be available
 Serial.println("Start LoRa Client");   //串口打印函数
 if (!rf95.init())
   Serial.println("init failed");
 rf95.setFrequency(frequency);  // Setup ISM frequency
 rf95.setTxPower(13); // Setup Power,dBm
 rf95.setSpreadingFactor(7);  // Setup Spreading Factor (6 ~ 12)
 // Setup BandWidth, option: 7800,10400,15600,20800,31200,41700,62500,125000,250000,500000
 //Lower BandWidth for longer distance.
 rf95.setSignalBandwidth(125000);
 // Setup Coding Rate:5(4/5),6(4/6),7(4/7),8(4/8) 
 rf95.setCodingRate4(5);
//如果我们需要修改lorat通信参数,只需要一行代码,非常方便
} //setup函数体只执行一遍,执行完了过后,开始进入到loop函数里面
void loop()
{
 Serial.println("Sending to LoRa Server");
 // Send a message to LoRa Server
 uint8_t data[] = "Hello, this is device 1";  //lora需要发射的数据,都是存放在一个字符数组里面
 rf95.send(data, sizeof(data));  //发送函数
 rf95.waitPacketSent(); //等待数据发送完成
 // Now wait for a reply
 uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
 uint8_t len = sizeof(buf);
 if (rf95.waitAvailableTimeout(3000))
 { 
   // Should be a reply message for us now   
   if (rf95.recv(buf, &len))
  {
     Serial.print("got reply: ");
     Serial.println((char*)buf);
     Serial.print("RSSI: ");
     Serial.println(rf95.lastRssi(), DEC);    
   }
   else
   {
     Serial.println("recv failed");
   }
 }
 else
 {
   Serial.println("No reply, is LoRa server running?");
 }
 delay(1000);    //延迟1000毫秒,通过修改这里的值,我们可以控制发包间隔
}

Server:

#include <SPI.h>
#include <RH_RF95.h>
RH_RF95 rf95;
int led = A2;
float frequency = 433.0;
void setup() 
{
 pinMode(led, OUTPUT);     
 Serial.begin(9600);
 while (!Serial) ; // Wait for serial port to be available
 Serial.println("Start Sketch");
 if (!rf95.init())
   Serial.println("init failed");
 rf95.setFrequency(frequency);
 rf95.setTxPower(13);
 // Defaults BW Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
 Serial.print("Listening on frequency: ");
 Serial.println(frequency);
}
void loop()
{
 if (rf95.available())
 {  
   uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];  //开辟一个buffer便于接收lora数据
   uint8_t len = sizeof(buf);
   if (rf95.recv(buf, &len))
   {
     digitalWrite(led, HIGH);
     RH_RF95::printBuffer("request: ", buf, len);
     Serial.print("got request: ");
     Serial.println((char*)buf);
     Serial.print("RSSI: ");
     Serial.println(rf95.lastRssi(), DEC);  //调用现成的接口可以直接获取lora信号的RSSI值
     
     // Send a reply
     uint8_t data[] = "And hello back to you";
     rf95.send(data, sizeof(data));
     rf95.waitPacketSent();
     Serial.println("Sent a reply");
     digitalWrite(led, LOW);
   }
   else
   {
     Serial.println("recv failed");
   }
 }
}

Change lora parameters online

当我们需要修改lora节点的通信参数,但是却无法手动去更改的时候(比如节点已经布置到各个区域,不可能再一个一个去现场修改),我们就需要在网关处发送命令,让节点收到命令后自己修改掉参数。 要实现这样的功能,主要需要两点:

  • arduino板子的软重置。我们修改了参数,需要重启一下板子,以新的参数进行通信。这里arduino为我们提供了一个函数:resetFunc(). 当程序执行到这里的时候,板子会重新启动
  • 一块掉电后仍能保存数据的区域。光是能重启,那么板子还是原来的参数,我们要修改参数,通过网关发射参数过去后,节点收到参数,需要保存在一个区域,掉电了后,这里的数据还在。所以重启后读这里面的数据,就能读到新的参数。这块掉电保存的区域,在Arduino里叫做EEPROM,我们使用的UNO型号的Arduino开发板,EEPROM的大小为 1KB,但是我们就放几个参数是完全够用的。
  • 下面例程代码:
    • Client
#include <SPI.h>
#include <RH_RF95.h>
#include <EEPROM.h>  //需要用到EEPROM
void(*resetFunc)(void) = 0;  //将函数入口设为0,
RH_RF95 rf95;
float frequency = 433.0;
int TxPower;
int SpreadingFactor;
int CodingRate4;
const unsigned int Buffer_len = 15;//接收要修改的参数的长度:"Reset,传输功率,扩频因子,码率," 。网关按照这样的格式发命令给节点
struct
{
 char Buffer[Buffer_len];
 bool isGetCommand;   //是否获取到command数据
 bool isParseData; //是否解析完成
 char TxPower[3];    //发送功率 12-20
 char SpreadingFactor[3];   //扩频因子 7-12
 char CodingRate4[2]; //码率5-8
} Command;  
void setup()  //初始化内容
{
 Serial.begin(9600);      //定义波特率9600
 Serial.println("Wating...");
 Command.isGetCommand = false;
 Command.isParseData = false;
 if (!rf95.init())
   Serial.println("init failed");
 TxPower = EEPROM.read(0); //先读取传输功率,来判断是否写入过参数
 if (TxPower == 0||TxPower == 255) //如果还没有写入,设置默认的通信参数
 {
   TxPower = 20;  //初始化传输功率为13
   SpreadingFactor = 12;
   CodingRate4 = 5;
   }
 else
 {
   SpreadingFactor = EEPROM.read(1);
   CodingRate4 = EEPROM.read(2);
   }
 rf95.setFrequency(frequency);
 rf95.setTxPower(TxPower);
 Serial.println(TxPower); //检查一下传输功率
 rf95.setSpreadingFactor(SpreadingFactor);
 Serial.println(SpreadingFactor);//检查一下扩频因子
 rf95.setCodingRate4(5);
 Serial.println(CodingRate4);
 rf95.setSignalBandwidth(125000);
} 
void loop()   
{
 Send_Message(); //发送数据
 Command_Get();  //接收命令 
 Command_Parse();//解析数据
}
void Send_Message()
{
 Serial.println("Sending to lora Server");
 uint8_t data[] = "Hello,this is device 1";
 rf95.send(data,sizeof(data));
 rf95.waitPacketSent(); 
}
void Command_Get()
{
 uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
 uint8_t len = sizeof(buf);
 if (rf95.waitAvailableTimeout(3000))
 { 
   // Should be a reply message for us now   
   if (rf95.recv(buf, &len))
  {
     Serial.print("got reply: ");
     Serial.println((char*)buf);
     if(buf[0] == 'R')  //Reset
     {
       memcpy(Command.Buffer, buf, Buffer_len);//将lora传过来的数据拷贝到结构体buffer里面,15位
       Command.isGetCommand = true; //如果收到Reset命令,就表示得到了数据        
       }    
   }
   else
   {
     Serial.println("recv failed");
   }
 }
 else
     Serial.println("No message");
}
void Command_Parse()
{
 char *subString;
 char *subStringNext;
 if (Command.isGetCommand)
 {
   Command.isGetCommand = false;
   Serial.println("**************");
   
   for (int i = 0 ; i <= 3 ; i++) //将命令切分,依次放入对应的数组中
   {
     if (i == 0)
     {
       if ((subString = strstr(Command.Buffer, ",")) == NULL)
         Serial.println("error1");  //解析错误
     }
     else
     {
       subString++;
       if ((subStringNext = strstr(subString, ",")) != NULL)
       {
         switch(i)
         {
           case 1:memset(Command.TxPower,'\0',3);memcpy(Command.TxPower, subString, subStringNext - subString);break; //获取传输功率
           case 2:memset(Command.SpreadingFactor,'\0',3);memcpy(Command.SpreadingFactor, subString, subStringNext - subString);break; //获取扩频因子
           case 3:memset(Command.CodingRate4,'\0',2);memcpy(Command.CodingRate4, subString, subStringNext - subString);break;  //获取码率
           default:break;
         }
         subString = subStringNext;
         Command.isParseData = true;
       }
       else
       {
         Serial.println("error2");  //解析错误
         Command.isParseData = false;
       }
     }
   }//for 
 }//if (Command.isGetCommand) 
 if(Command.isParseData) //如果解析完成,就执行更新函数
   {
     Command.isParseData = false; //解析完成,标志位置位假,并且重启
     reset_f(); //解析完成,写入参数并重启
     }
}
void reset_f()
{
 EEPROM.update(0,atoi(Command.TxPower)); //将TxPower更新到第一个位置
 delay(300);
 EEPROM.update(1,atoi(Command.SpreadingFactor)); //将SpreadingFactor更新到第二个位置
 delay(300); 
 EEPROM.update(2,atoi(Command.CodingRate4)); //将CodingRate4更新到第三个位置
 delay(300);
 resetFunc(); //将修改的参数写入了EEPROM,然后重置。
 }
    • Server
#include <SPI.h>
#include <RH_RF95.h>
RH_RF95 rf95;
float frequency = 433.0;
bool send_flag;
void setup() 
{ 
 Serial.begin(9600);
 while (!Serial) ; // Wait for serial port to be available
 Serial.println("Start Sketch");
 if (!rf95.init())
   Serial.println("init failed");
 send_flag = false;//初始设置为0,如果要发送修改参数,就将flag设置为1
 rf95.setFrequency(frequency);
 rf95.setTxPower(20);
 rf95.setSpreadingFactor(8);
 rf95.setSignalBandwidth(125000);
 rf95.setCodingRate4(5);
 // Defaults BW Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
 Serial.print("Listening on frequency: ");
 Serial.println(frequency);
}
void loop()
{
 if (rf95.available())
 { 
   uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
   uint8_t len = sizeof(buf);
   if (rf95.recv(buf, &len))
   {
     Serial.print("got request,");
     Serial.print((char*)buf);
     Serial.print("RSSI,");
     Serial.print(rf95.lastRssi(), DEC); 
     Serial.print(",");
     Serial.print("SNR,");
     Serial.println(rf95.lastSNR(), DEC);
   }
   else
   {
     Serial.println("recv failed");
   }
 }
 if(send_flag == true)
     {
       send_flag = false;//如果手动修改了flag为1,则说明要发送命令了
       uint8_t data[] = "Reset,16,8,6,"; //要发送的参数
       rf95.send(data, sizeof(data));
       rf95.waitPacketSent();
       Serial.println("Sent a commamd");
      }
}

Sensory data measurement

Data collection via USB cables