MKR1310（LoRa通信）で低消費電力センサノードを開発する

# はじめに
<!-- 発端や概要を記載 -->
無線ネット環境が保証されていない農村で,水田の水位データや土壌のセンシングをしたい！という機会がありセンサノードから通知システムまでを開発することにしました.

この記事では開発したセンサノードについて書いていきます.
農村の制約条件として,頻繁に現場に行って電池交換や,SDカードのデータを取得するのは億劫な点があります.低消費電力＆データの遠隔監視が可能なデバイスの開発ができれば便利そうです.
そこで,LoRaモジュールとして CMWX1ZZABZ を搭載した[MKR1310](URL)を用いて開発をしていこうと思います.

SWITCH SCIENCEの製品紹介では

> LoRa へ対応することで,低消費電力が求められるプロジェクトに最適な製品となっています.

とあります.見せてもらおうか,MKR1310とLoRaの低消費電力の実力とやらを.

基本的には大変ありがたいことに参考文献におんぶに抱っこなのですが,MKR1310特有の癖があって詰まった部分があったので,同じ轍を踏まないために備忘録として開発記録を残そうと思います.
※もし記事の内容に誤りや改善の余地があると感じられた場合は,お手数ですがご指摘いただければ幸いです.

MKR1310についての記事はこちらでまとめていく予定↓
- MKR1310記事まとめ(工事中)

## 使用機材・使用技術
- Arduino MKR WAN 1310
- A02YYUW Waterproof Ultrasonic Sensor SKU_SEN0311(超音波センサ)
- 18650 充電式リチウムイオン電池 保護回路付
- 普及型SX1302-8チャンネル LoRaWANゲートウェイ LPS8N-JP
- ドライバ内蔵リレーモジュールキット[AE-G5V-DRV]

## 構成
**End Nodes**
- [A02YYUW Waterproof Ultrasonic Sensor SKU_SEN0311](https://wiki.dfrobot.com/_A02YYUW_Waterproof_Ultrasonic_Sensor_SKU_SEN0311)

  <img src="https://dfimg.dfrobot.com/nobody/wiki/8bff8cdd6a8d8da6e9d825d5cbde6777.jpg" width="250" height="250">
  
今回は水位を計測するため防水超音波センサを採用しました

- [MKRWAN 1310](https://www.switch-science.com/catalog/7385/)

  <img src="https://d2air1d4eqhwg2.cloudfront.net/images/7385/500x500/a215d5e8-3e9b-431e-90d5-cf17b8408cd1.jpg" width="250" height="250">


**Gateway**
- [Dragino Gateway 普及型8チャンネルLoRaWANゲートウェイ LPS8-JP](https://sales.dragino.jp/ca1/31/p-r-s/)

  <img src="https://cache.cart-imgs.fc2.com/user_img/draginodirect/befad9d51beeb8b2c3eb90948fbba060.png" width="250" height="200"></div>


**Network Server**
- [The Things Stack（The Things Network）](https://www.thethingsindustries.com/)


<!-- 各チャプター -->
<a id="#Chapter1"></a>
# LoRa通信を行う準備
- LoRa通信のセットアップ<br>
LoRa通信に関わる設定部分はほとんど公式の[Connecting MKR WAN 1310 to The Things Network (TTN)](https://docs.arduino.cc/tutorials/mkr-wan-1310/the-things-network)の通りで完了しました.
ただ,公式でも記述がある通り,ファームウェアのアップデートがあります.**合わせてライブラリはMKRWANとMKRWAN_V2というものがありますが,MKR1310購入時期の問題なのかどちらか一方でしか動作しないものがあり**,少し詰まりました.アップロードの度に確認しなくてはいけないので少し面倒です.

- LoRa用Gatewayのセットアップ<br>
ついで,ゲートウェイの使い方は以下を参考にすればすぐです.<br>
[普及版LoRaゲートウェイDRAGINO LPS8の使い方](https://zenn.dev/suzukiot/articles/8297eea7a9a00d)

> AS923 Asia (920-923,Default:923.2/923.4) AS923-1

この周波数帯域は日本において無免許で使える帯域ですので注意して設定してください.

<a id="#Chapter2"></a>
# 超音波センサデータをMKR1310で取得
[A02YYUW Waterproof Ultrasonic Sensor SKU_SEN0311](https://wiki.dfrobot.com/_A02YYUW_Waterproof_Ultrasonic_Sensor_SKU_SEN0311)の公式サイトには,サンプルコードがあるのですが,MKR1310では動作しません.調べたところ,どうやら**MKR1310はSoftwareSerialが使用できない**ようです.MKR1310の13,14PinのはそれぞれRX,TXにハードウェア的にシリアル通信に対応していますので,センサが1つならばそれで解決できます.
(プロセッサとして,Arduino UNOなどで採用されているATmega328Pではなく,MKR1310ではSAMD21を採用しているためよく使われるライブラリの互換がなくて少し苦戦しました...)
電源ですが,センサ側の動作電圧は
> Operating Voltage: 3.3~5V

とあります.MKR1310の内部電圧は3.3Vのため,写真のようにセンサの電源はVccに接続した方が良さそうです.具体的にどのような問題が起こるかはわかりませんが,これに気づくまで5VPinに電源を刺している際は,電流の消費が大きくなっていました.マイコンに何らかの負担がかかっていることは確かなので気をつけましょう！
<br>**写真**<br>
コードを下に記します.

```c++:ultra_sound_senser_formkr1310.ino
unsigned char data[4]={};
float distance;
void setup()
{
 Serial.begin(115200);
 Serial1.begin(9600);
}

void loop()
{
  do{
     for(int i=0;i<4;i++)
     {
       data[i]=Serial1.read();
     }
  } while (Serial1.read()==0xff);

  Serial1.flush();

  if (data[0]==0xff)
    {
      int sum;
      sum = (data[0]+data[1]+data[2])&0x00FF;
      if (sum == data[3])
      {
        distance = (data[1]<<8)+data[2];
        if(distance>30)
        {
           Serial.print("distance=");
           Serial.print(distance/10);
           Serial.println("cm");
        } else {
           Serial.println(c);
        }
      } else Serial.println("ERROR");
     }
  delay(100);//上の処理は100ms間隔で4回ループを回さないとうまく値が取得できない
}
```


```delay(100);```を入れていますが,これはこれよりも早くても遅くてもダメで,センサの仕様のようです.
<a id="#Chapter3"></a>
# 消費電力に対する考察
「1時間に1回超音波センサの値を取得し,LoRaで送信する」ソースコードです.
```c++:mkr1310_LoRa.ino
//#include <MKRWAN_v2.h>
#include <MKRWAN.h>  //v2かv1は機体によって異なります
LoRaModem modem;

// Uncomment if using the Murata chip as a module　←MKR1310はMurata chipのためUncommentする
// LoRaModem modem(Serial1);
unsigned char data[4]={};
float distance = 10;
String appEui = ""; //appEuiを入力
String appKey = ""; //appKeyを入力
String devAddr;
String nwkSKey;
String appSKey;
long previousMillis = 0;
long sendInterval = 20000;  //20秒に1回送信する
long sensorInterval = 100;

void setup() {
    //AS923に変更する
   if (!modem.begin(AS923)) {
      while (1) {}
   };

  int connected;
    appKey.trim();
    appEui.trim();

    connected = modem.joinOTAA(appEui, appKey);

  if (!connected) {
    while (1) {}
  }
  Serial1.begin(9600);
  Serial.begin(115200);
}
void sendFunction(float distance){
  int err;
  modem.setPort(3);
  modem.beginPacket();
  modem.print(distance/10);
  err = modem.endPacket(false);
}

void loop() {
  modem.poll();
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > sendInterval) {
    previousMillis = currentMillis;
    sendFunction(distance);
    Serial.print("send!");
  }
  do{
     for(int i=0;i<4;i++)
     {
       data[i]=Serial1.read();
     }
  } while (Serial1.read()==0xff);

  Serial1.flush();

  if (data[0]==0xff)
    {
      int sum;
      sum = (data[0]+data[1]+data[2])&0x00FF;
      if (sum == data[3])
      {
        distance = (data[1]<<8)+data[2];
        Serial.println(distance);
     }    
  }
  delay(100);  //ここのdelayは変更しない
}
```
## 現在の構成における問題点と解決策

### 送信していない時間にもマイコンが電流を使ってしまう.
  - Deep Sleepの使用<br>
  MKR1310はDeepSleepを使うことができます.
  ```c++:ファイル名.ino
#include "ArduinoLowPower.h"
void loop(){
    LowPower.sleep(3600000);//1時間のスリープ
}
```

  Arduino UNOなどと同様,プロセッサのクロックを止めて消費を完全にゼロにするものではないため,消費電力が0になることはありません.UNOの場合は動作時30mAに対してsleep中は20mAです.機械的な理解が深まればさらに電力消費量を小さくできるかもしれませんが,最強は電源からMKR1310流れる電流自体をストップさせてしまうタイマーを設置することなのでしょうか。。。
  
### Deep Sleep中にもセンサに電流が流れ続けてしまう.
  Deep Sleep中でも,MKR1310のVccや5VPINに負荷が繋がっていると,電流が流れてしまいます.そこで機械式リレーを用いてデータ送信の前だけにセンサに電流が流れるようにします.リレーとしてAE-G5V-DRVが搭載されたY14H-1C-5DSを使いました.(ドライバ内蔵リレーモジュールキット[AE-G5V-DRV ) ドライバがついているので,余計な設計を考えなくてよくて助かりました.こちらのサイトが大変参考になりました.

[リレーでON (2) Arduino MKRZERO | Arduinoクックブック]([URL]
(https://www.denshi.club/cookbook/arduino/mkrzero/on-2-arduino-mkrzero.html))
  
機械式リレーは接点を持っており,電磁作用により機械的に接点を開閉させて信号や電流・電圧を“入”“切”するものです.
    
<img src="https://www.amon.jp/upload/diy/contents/ID50-10L.jpg" width="250" height="200">


リレーの回路を組込みDeep Sleepを1時間ごとに入れたコードが以下です.
``` LowPower.deepSleep(3600000UL);```とありますが,delay同様単位はmsで,32767より大きい整数を指定するときは値の後ろにULを付け加えます。
http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2588

  ```c++:ファイル名.ino
//#include <MKRWAN_v2.h>
#include <MKRWAN.h>
#include <LoRa.h>
#include "ArduinoLowPower.h"
LoRaModem modem;

// Uncomment if using the Murata chip as a module
// LoRaModem modem(Serial1);
unsigned char data[4]={};
float distance = 10;
String appEui = ""; //appEuiを入力
String appKey = ""; //appKeyを入力
String devAddr;
String nwkSKey;
String appSKey;
int count;

void setup() {
  
   if (!modem.begin(AS923)) {
      while (1) {}
   };

  int connected;
    appKey.trim();
    appEui.trim();

    connected = modem.joinOTAA(appEui, appKey);

  if (!connected) {
    while (1) {}
  }
  Serial1.begin(9600);
  Serial.begin(115200);
  
  pinMode(8,OUTPUT);
  digitalWrite(8, HIGH);
  
  count = 0;
  Serial.print("Start setUP!");
}

void sendFunction(float distance){
  int err;
  modem.setPort(3);
  modem.beginPacket();
  modem.print(distance/10);
  err = modem.endPacket(false);
}
  
void loop() {
  modem.poll();
  do{
     for(int i=0;i<4;i++)
     {
       data[i]=Serial1.read();
     }
  } while (Serial1.read()==0xff);

  Serial1.flush();

  if (data[0]==0xff)
    {
      int sum;
      sum = (data[0]+data[1]+data[2])&0x00FF;
      if (sum == data[3])
      {
        distance = (data[1]<<8)+data[2];
        if(distance>30)
        {
           Serial.print("distance=");
           Serial.print(distance/10);
           Serial.println("cm");
        } else {
           Serial.println("Below the lower limit");
        }
      } else Serial.println("ERROR");
     }
  delay(100);
  count = count+1;
  Serial.print(count);
  if(count==20){
    sendFunction(distance);
    Serial.print(distance);
    Serial.println("send!");
    delay(3000);
    digitalWrite(8, LOW);
    LoRa.end();
    Serial1.end();

    LowPower.deepSleep(3600000UL); //1時間おきにDeepSleep
    digitalWrite(8, HIGH);
    Serial.print("Wake&reset");
    count = 0;
    if (!modem.begin(AS923)) {
      while (1) {}
   };
  int connected;
    appKey.trim();
    appEui.trim();

    connected = modem.joinOTAA(appEui, appKey);
  if (!connected) {
    while (1) {}
  }
  Serial1.begin(9600);
  }
}

```

![スクリーンショット 2023-07-11 14.12.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3507626/94d0f535-352d-b564-0085-f1791d44bdde.png)

## 消費電力の評価
| No |    内容     |  待機消費電力|
|---:|-------------|------------|
| 1  |  deepsleep有 |  100mW   |
| 2  |  リレー有     |   30μmW |

**電力消費の調査については実験中です**

<a id="#reference"></a>
# 参考文献
- [普及版LoRaゲートウェイDRAGINO LPS8の使い方](https://zenn.dev/suzukiot/articles/8297eea7a9a00d)
- [on-2-arduino-mkrzero]([URL](https://www.denshi.club/cookbook/arduino/mkrzero/on-2-arduino-mkrzero.html))
- [リレーでON (2) Arduino MKRZERO | Arduinoクックブック]([URL]
(https://www.denshi.club/cookbook/arduino/mkrzero/on-2-arduino-mkrzero.html))
- [Connecting MKR WAN 1310 to The Things Network (TTN)](https://docs.arduino.cc/tutorials/mkr-wan-1310/the-things-network)