前回に続き、段階圧密試験用データロガーをつくります。今度は少し趣向を変え、Wi-Fiでデータをインターネットに送り、自宅や外出先からでも長期試験の様子を確認できるようにします。ご存じの通り、標準圧密試験は1日ごとに荷重を増やしていくので、家から途中経過を見られても特段のメリットはないですが(どっちみち重りを置きに行かないといけないし・・・)、クリープ試験など長期にわたる特殊な試験では、心の平安のためにも進捗を見ておきたいことがあります。ここでは第32回で説明したESP32とAmbientを利用したロガーをつくります。繰り返すと、ESP32はArduinoの高性能版のようなマイコンボードで、WiFiやBluetoothモジュールが内蔵されています。また、Ambientはインターネット上でデータの記録と図化をやってくれるサービスです。ここではディスプレイとして、第18回で紹介したLCDではなく、OLED(有機LED)ディスプレイSSD1306の128×32ピクセル版を使います。1個500円程度で買えます。LCDよりも解像度が高く製品が小さいので、コンパクトに収まります。このOLEDはArduinoでも使うことができるのですが、解像度が高いため、文字を表示するライブラリのサイズが大きく、これをArduino UnoやPro Miniなどに入れると、他のスケッチがほとんど入らなくなります。ESP32(第31回で触れた通り、メモリ・処理能力ともにArduinoより格段に上)の場合、SSD1306を制御するライブラリがESP32のボードマネージャとともにArduino IDEにインストールされるので、新たにライブラリをダウンロードせずに使うことができます。ESP32のボードマネージャのインストールについては第32回を読んで下さい。

 

段階圧密試験用データロガー(ESP32+Ambient利用版)

 

■  基本性能

 4ゲージのひずみゲージ型センサー(段階圧密試験では変位計)の読みを24-bit分解能のHX711で取得し、毎秒OLEDディスプレイに表示する。Micro SDカードへの記録は、電源ONから1秒ごと120回(2分)、10秒ごと108回(18分)、1分ごと40回(40分)、10分ごと300回(約2日)、以降1時間ごと、とします。Wi-Fiでのインターネットへのデータ転送は毎時00分とします。ただし、電源ONから初期化にかかる時間を考慮し、5秒カウントダウンしてから記録を始めます(その間に、載荷用の重りを持ち上げてスタンバイします)。

 

■  必要部品*

*価格は執筆の時点でAmazonで見つけた最安値に基づいて単価として表しています。「\320」とは「5個セット\1,600」という意味の場合もあります。

 

コア部

・ESP32開発ボード

(ここではNodeMCU-32SではなくESP32 Dev Moduleのほうとします)・・・互換品 \1,000

・HX711ブレークアウトモジュール(ひずみゲージアンプ)・・・\143

・OLEDディスプレイ SSD1306(128×32ピクセル)・・・\500

・SD(フルサイズ)シールド・・・\121

・RTCモジュール(DS3231、コイン電池付き)・・・\170

・ユニバーサル基板5cm×7cm・・・\100

・SD 4GB・・・\500

・その他、ジャンパーワイヤーの切れ端少々

計 約\2,500

 

付属部

・(変位計のシールドケーブル先がバラなら)端子台5極

 

 

 

 ここで、RTC DS3231のモジュールは第7回で記したものではなく、先の写真のようにより小型(その代わり、アラーム用のパルス出力端子などが引き出していない)のものを使っています(I2Cで接続するなど、使い方は同じです)。ピンヘッダ―用のソケットがついていますが、ペンチでひっぺがしたうえで残ったはんだを溶かすことでフラットにでき、ユニバーサル基板に実装しやすくなります。なお、このモジュールはSDA・SCLではなくD・Cと書いてあることが多いです。

 

 また、あえてMicro SDではなくフルサイズのSDカードを使っています。第5回で触れたように、SDカードのシールドにはいくつかタイプがあります(以下の写真)。真ん中のものはコンパクトで3.3V対応なのでESP32(3.3V駆動)には良さそうなのですが、なぜかESP32ではうまく動きませんでした。他のウェブサイトによると、SDカードシールドにはMOSI・MISO・SCK・CSに10kΩのプルアップ抵抗が必要とのことでしたが、このボードには見たところプルアップ抵抗はすでについてそうですし、やってみたところやはり動きませんでした。右のモジュールを使うとすんなり動きました(左も動きましたが、5Vで印加する必要があります)。

 

 

■  必要外部ライブラリ

HX711.h(第19回参照)

Ambient.h(第32回参照)

SSD1306.h(先に書いた通り、ESP32ボードマネージャとともにArduino IDEにインストールされているので、その意味では「外部ライブラリ」ではない)

 

DS3231は第7回で使った外部ライブラリではなくESP32用のRTClib.h(ESP32ボードマネージャとともにArduino IDEにインストール済み)を使います。

 

■  接続図

下図のように接続します。ESP32では、AtMega328PをつかったArduino(Uno、Nano、Pro Miniなど)とI2CやSPIのピンが全く異なることに注意。ESP32では、

I2C: SDA – IO21, SCL – IO22

SPI: MISO – IO19, MISO – IO23, SCK – IO18, SS(CS) – IO5

です。

 

 

■  出来姿

RTCとSDシールドは見える必要もないですし、裏側に隠してしまいました。以下は地盤工学会誌の2021年1月号論説に掲載予定の写真です(西村 聡 論説「室内土質試験の新たなオプション」)。

 

 

上の写真の後、次にあげるスケッチで微妙に表示フォーマットを修正しました。このように見えます。

 

 

 

■  スケッチ

 留意点は以下の通りです。

・例によって、一度RTCの時間合わせをした後、その文をコメントアウトしてもうスケッチをESP32に送ってください(「// RTCの設定」の次の2行))。

・WiFiのSSIDとPasswordは使っているルーター等を参照してください。

・ESP32へのスケッチの送り方は第32回に書いたとおりです。Dev Moduleの場合、何も工夫は要らず、Arduinoと同じようにただ送り込めばよいです。けっこう時間がかかります(数分かかることも)。

・第32回に書いたように、Ambientのアカウントを持っている(登録無料)ことが前提です。

・ESP32で使うSD.hは、Arduinoで使うSD.hと名前は同じですが異なります(異なるフォルダにあります)。ですので、使い勝手も微妙に異なります。これまで、FILE_WRITEでもデータは追記されてきましたが、ここではFILE_APPENDにしないと追記されず上書きされます(データが常に1つしかSDカードに残りません)。また、スケッチにあるようにファイルパスに”/”が必要です。このように、Arduinoに慣れているといろいろとクセがあるのがESP32です。

・ループは第35回に書いたように約1秒周期です。これはHX711のサンプリング周波数が10Hzで、サンプリングを10回行い平均をとっているからでした。この理屈だと、loop()関数全体で周期は1秒とちょっとかかるはずなのですが、実際には0.9secほどで回るようです。ということは、同じ秒の間に2回ループが通ることがときどき起こるので、同じ秒のデータを2回とらないようにprev_secとprev_minという変数を使って条件判定をしています(今の秒や分が先のループと同じならSDカードに記録しない)。

 

 

#include <Wire.h>
#include "SSD1306.h"
#include <WiFi.h>
#include "Ambient.h"
#include <RTClib.h>
#include <SPI.h>
#include <SD.h>
#include "HX711.h"

RTC_DS3231 rtc;
WiFiClient client;
Ambient ambient;
SSD1306  display(0x3c, 21, 22); //SSD1306インスタンスの作成(I2Cアドレス,SDA,SCL)
HX711 channel1;

const char* ssid = "SPWN_N35_791949";
const char* password = "3abce32a55646";
unsigned int channelId = 22048; // AmbientのチャネルID
const char* writeKey = "d7e39511a66a2df7"; // ライトキー

// Calibration factors for the sensor
float CF0=0; // [mm]: Offset at 0 reading
float CF1=0.001; // [mm/reading]: Slope

// Logging interval setting
int log_mode = 0; // 0: Every second, 1: Every 10 seconds, 2: Every 1 minute, 3: Every 10 minutes, 4: Every 1 hour
int log_num[4]={120, 108, 40, 300}; // それぞれのlog_modeでのログ回数
int log_cnt=0; // それぞれのlog_modeでの現在のカウント
bool log_fg=false;
int prev_sec;
int prev_min;
int ref_min;
int ref_sec;

void setup()
{
  // シリアル通信の設定
  Serial.begin(115200);
  delay(10);
  Serial.println("Start");

  // WiFiの設定
  WiFi.begin(ssid, password);  //  Wi-Fi APに接続
  while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
    delay(100);
  }
  Serial.print("WiFi connected\r\nIP address: ");
  Serial.println(WiFi.localIP());

  // Ambientの設定
  ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化

  // OLEDの設定
  display.init();    //ディスプレイを初期化
  display.setFont(ArialMT_Plain_24);    //フォントを設定
  
  // SDカードモジュールの設定
  Serial.println("SD_conect...");
  delay(10);
  if (!SD.begin(5))
    Serial.println("Card failed, or not present");
  else
    Serial.println("card initialized.");

  // RTCの設定
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  rtc.adjust(DateTime(2019, 1, 12, 10, 32, 0)); // Year, Month, Day, Hour, Minute, Second

  // HX711の設定
  channel1.begin(13, 14);  // HX711.DOUT  - pin IO13, HX711.PD_SCK - pin IO14 

  // 5秒のカウントダウン(PCとOLEDに表示)
  display.drawString(0, 0, "Starting");
  display.display();   //指定された情報を描画 
  delay(1000);
  int cnt1;
  char cnt[2];
  for(cnt1=0;cnt1<5;cnt1++)
  {
    Serial.println(5-cnt1);   
    snprintf(cnt, 2, "%d", 5-cnt1);
    display.clear();
    display.drawString(0, 20, cnt);
    display.display();   //指定された情報を描画
    delay(1000);
  }
}

void loop()
{
  DateTime now = rtc.now();
  
  // Displacement sensor reading: Convert to [mm] by CF0 and CF1
  float sensor1=CF0+channel1.read_average(10)*CF1;
  char disp[10];
  snprintf(disp, 10, "%6.3f", sensor1);

  // Display to PC via serial
  Serial.print(now.year()); Serial.print("/"); Serial.print(now.month()); Serial.print("/"); Serial.print(now.day());Serial.print(" ");
  Serial.print(now.hour()); Serial.print(":"); Serial.print(now.minute()); Serial.print(":"); Serial.print(now.second());Serial.print(" ");
  Serial.print(sensor1); Serial.print(" log_cnt="); Serial.print(log_cnt); Serial.print(" log_mode="); Serial.println(log_mode); 

  // log_modeごとのSDカードへの記録判定
  switch(log_mode)
  {
    case 0: // Every second
      if(now.second()!=prev_sec)
        log_fg=true;
      break;
    case 1: // Every 10 seconds
      if((abs(now.second()-ref_sec)%10==0)&&(now.second()!=prev_sec))
        log_fg=true;
      break;
    case 2: // Every 1 minute
      if((ref_sec==now.second())&&(now.minute()!=prev_min))
        log_fg=true;
      break;
    case 3: // Every 10 minutes
      if((abs(now.minute()-ref_min)%10==0)&&(now.second()==ref_sec)&&(now.minute()!=prev_min))
        log_fg=true;
      break;
    case 4: // Every 1 hour
      if((now.minute()==ref_min)&&(now.second()==ref_sec)&&(now.minute()!=prev_min))
        log_fg=true;
      break;
  }

  // log_mode更新の判定
  if((log_mode<4)&&(log_cnt==log_num[log_mode]))
  {
    log_cnt=0;
    log_mode++;
    ref_min=now.minute();
    ref_sec=now.second();
  }
  else
  {
    log_cnt=0; // log_mode=4の場合、(現実的ではないが理論上)永遠に続けるとlog_cntの値がオーバーフローするので、毎回0に戻す  
  }
  
  File dataFile = SD.open("/datalog.csv", FILE_APPEND);
  if(dataFile&&log_fg)
  {
    dataFile.print(now.year(), DEC); dataFile.print("/");
    dataFile.print(now.month(), DEC); dataFile.print("/");
    dataFile.print(now.day(), DEC); dataFile.print(" ");
    dataFile.print(now.hour(), DEC); dataFile.print(":");
    dataFile.print(now.minute(), DEC); dataFile.print(":");
    dataFile.print(now.second(), DEC); dataFile.print(" ");
    dataFile.println(disp);
    dataFile.close();
    prev_sec=now.second();
    prev_min=now.minute();
    log_fg=false;
    log_cnt++;
  }

  // OLEDに表示
  display.clear();
  display.drawString(0, 0, disp);
  display.drawString(80,20, "mm");
  display.display();

  // Ambientに送信
  if((now.minute()==0)&&(now.second()==0))
  {
    ambient.set(1, sensor1); // センサー値をデータ1にセット
    ambient.send(); // データをAmbientに送信
  }
}

 

 

圧密試験に限らず、長期試験の経過を週末も家から見たいときに応用がきく装置です。

 ここから「実践編」になります。基本的に、「基礎編」(第1~18回)と「発展編」(第19~34回)を読んで理解できれば、ここで紹介する装置はこれ以上の説明なしに作れるのですが、料理本のように必要部品と接続図・スケッチを載せておけば便利かと思って、いろいろ紹介していきたいと思います。個々の部品のはたらきの説明は省略します(過去の回への参照を載せます)。要は、「書かれている通りにつないで、書かれているスケッチを入れればよい」というレシピ本にしたいと思います。

 

 ここでは、段階圧密試験用データロガーのレシピを載せます。「段階圧密試験用」といっても、要はひずみゲージ型変位計用の1chロガーです。他の用途にも当然使えますし、4ゲージのひずみゲージ型センサーならロードセルでも何でも使えます。今までのブログを読んでくれた人は、特段にここを読まなくても作ることができると思いますが、復習も兼ねてこの回を設けます。ディップスイッチを使って、記録のインターバルを調整する機能も持たせます。「基礎編」で時間を記録するためのRTC(Real-Time Clock)と、データをSDカードに残すためのSDカードシールドをそれぞれ第7回、第5回で説明しました。これらを個別に組み込んでもよいのですが、これらを一体にした「データロガーシールド」なるものも売っています(シールドというのは、マイコンボードにぴったりとはまるように作られたボードのことを指すことが多いです。ぴったりはまらなくてもシールドと呼ぶことはありますが)。これを買うと、接続の手間が省けます。データロガーシールドについているRTCはDS3231ではなくDS1307が多いようです。Arduinoの標準ライブラリで制御でき、むしろ簡単です。

 

  

データロガーシールド:左がArduino Nano用(表と裏)、右がArduino Uno用(表)。コイン電池には小さめのCR1220がはまる(100円ショップで2個100円)。Nano用にはMicro SDカードが、Uno用にはSDカードがはまる

 

 このロガーを買って、SDカードに記録されない、という問題がある場合は、容量の小さいSDカードを試してみて下さい。容量4GB程度までが無難です(SDHCには対応していない場合があります)。

 

 段階圧密試験用データロガー(データロガーシールド利用版:Arduino Nano) 

 

■  基本性能

 4ゲージのひずみゲージ型センサー(段階圧密試験では変位計)の読みを24-bit分解能のHX711で取得し、毎秒LCDに表示する。Micro SDカードへの記録はDIPスイッチにより8段階(記録なし、1sec, 10sec, 30sec, 1min, 10min, 30min, 60min:ただし、モードが切り替わった際には、前回記録からの間隔ではなく、00秒あるいは00分からの間隔)に手動切り替えできる。

 

■  必要部品*

*価格は執筆の時点でAmazonで見つけた最安値に基づいて単価として表しています。「\320」とは「5個セット\1,600」という意味の場合もあります。

 

コア部

・Arduino Uno・・・互換品 \670

・Arduino Uno用データロガーシールド・・・\665

・HX711ブレークアウトモジュール(ひずみゲージアンプ)・・・\143

・LCDモジュール(16×2文字:I2Cコント―ラ付き)・・・\320

・DIPスイッチ8ピン4Pポジション・・・\63

・基板表面実装用10kΩ抵抗×4・・・\8(300個入り\612)

・ユニバーサル基板4cm×6cm・・・\100

・コイン電池(CR1220)・・・\55

・Micro SD 4GB・・・\438

・その他、ジャンパーワイヤーの切れ端少々

計 約\2,500

 

付属部

・コア部を固定する木版とネジ

・(変位計のシールドケーブル先がバラなら)端子台5極

・(変位計のシールドケーブル先がNDIS端子なら)7極NDIS端子メス

・(スケッチ書き込みおよび給電のため)USB Type A - Type Bケーブル

(電池から給電する場合は電池ボックス+電池でもよい)

・(家庭用AC100Vコンセントから給電する場合)USB Type A用ACアダプタ

・ロガーをON/OFFするためのスライドスイッチ

 

 

■  必要外部ライブラリ

LiquidCrystal_I2C.h(第18回参照)

HX711.h(第19回参照)

 

■  接続図

HX711ブレークアウトモジュールの形状に応じて2種示します(第19回参照)。ディップスイッチは3極(D6~D4)しか使わないのですが、3極のDIPスイッチというのはあまり売っていないようで、将来の拡張を見越して4極接続しています。スケッチではD6~D4しか使っていません(D6をLSB、D4をHSBとして扱っています。LSB: Least Significant Bitとは2進数の一番下の桁(ここでは1の桁)で、MSB: Most Significant Bitとは一番上の桁(ここでは4の桁)のことです)。

 

 

 

 

■  出来姿

Arduino Uno、HX711、LCDの3つのモジュールは、ユニバーサル基板へのはんだの直付けを避け、簡単に再利用できるように組みました。それぞれが損傷した際の交換も簡単です。下の写真は表面・裏面の様子です。裏面には抵抗が4つ並んでいますが、この下にディップスイッチがあり赤い線(5V)につながっています。表面の左上に赤いスライドスイッチがありますが、これは同じロガーを並べた際に。電源配線をタコ足にして個々のロガーをON/OFFできるようにするためのもので、この時点では使っていません(裏面左下を見てわかる通り)。

 

 

なお、このブログではI2CのSDA・SCLをそれぞれ緑・青にしてきましたが、ここでは逆転させています。というのも、新しく買ったジャンパーワイヤーが、先端ソケットが互いに切り離せないタイプで、色の並びの順番を変えられなかったので・・・

 

■  スケッチ

 留意点は以下の通りです。

・スケッチ中のコメントにあるように、RTC.adjust(DateTime(__DATE__, __TIME__));の一文は、RTCの時間合わせです。これにより、RTCがスケッチのコンパイル時のPCの時間に合わせられるようです。これをArduinoに書き込んだままにしておくと、電源がリセットされるたびにコンパイル時の時間にRTCが戻されてしまいます。上の一文を有効化したうえでまず書き込み、Arduinoを走らせてRTCの時間合わせをしたら、この一文を無効化(文頭に//を加えてコメント文にする)したうえでもう一度書き込みます。これで、ロガーシールドにはめたコイン電池CR1220を外すまで時間は刻み続けられます。

・LcdTimeDisplay()という関数を定義しています。LCDに表示する際に、これがないと、例えば12:43:59の1秒後は、12:43:09になってしまいます(now.second()は00秒ではなく0秒なので、0が一つ、「:」の次に書かれてしまう)。これを12:43:00にするための関数です。

・channel1.read_average(10)とあるように、変位計の値は10回読んで平均をとっています。第19回で説明したように、HX711のサンプリング間隔はデフォルトで0.1secなので、10回読むと1秒になります。ここ以外にほとんど時間を使うタスクがないスケッチなので、これでループさせると動作はほぼ1秒間隔になります(厳密には1秒とちょっとかかっているわけですが)。

・冒頭にCF0とCF1の値を決める箇所があります。これがセンサーの較正係数(それぞれオフセットと電圧に対する感度)になります。まずCF0=0、CF1=1として較正をしてから、それぞれの係数を書き込み、またそのスケッチをArduinoに書き込む必要があります。

 

 

#include <SPI.h>

#include <SD.h>

#include <Wire.h>

#include "RTClib.h"

#include <Time.h>

#include <TimeLib.h>

#include <LiquidCrystal_I2C.h>

#include "HX711.h"

 

RTC_DS1307 RTC;

LiquidCrystal_I2C lcd(0x27,16,2);

HX711 channel1;

const int chipSelect = 10;

File dataFile;

 

int rec_mode=0;

bool rec_flag=false;

int prev_sec=0;

int prev_min=0;

int prev_hour=0;

 

// Calibration factors for the sensor

float CF0=0; // [mm]: Offset at 0 reading

float CF1=1; // [mm/reading]: Slope

 

void setup(void)

{

  // Preparation for dip switches

  pinMode(3, INPUT);

  pinMode(4, INPUT);

  pinMode(5, INPUT);

  pinMode(6, INPUT);

 

  Serial.begin(9600);

 

  // SD card

  Serial.print("Initializing SD card...");

  // see if the card is present and can be initialized:

  if (!SD.begin(chipSelect))

  {

    Serial.println("Card failed, or not present");

  }

  Serial.println("card initialized.");

 

  // RTC

  Wire.begin();

  RTC.begin();

  delay(1000);

 

  if(!RTC.begin())

  {

    Serial.println("RTC failed");

  }

  else

  {

    // 次の一文を有効化して書き込み、まず走らせる。それでRTCの時間がPCの時間に合わせられる。

    // 次にこの文をコメントアウトしてして書き込む(そうしないと、電源ONのたびにスケッチコンパイルの

    // 時間にリセットされてしまう)   

    RTC.adjust(DateTime(__DATE__, __TIME__));

  }

 

  // HX711

  channel1.begin(A0, A1);  // HX711.DOUT  - pin #A0, HX711.PD_SCK - pin #A1

   

  // LCD

  lcd.init();

  lcd.backlight();

}

 

void loop(void)

{

  DateTime now;

 

  // fetch the time

  now = RTC.now();

 

  // Displacement sensor reading: Convert to [mm]

  float sensor1=CF0+channel1.read_average(10)*CF1;

 

  // Update the recording mode

  rec_mode=4*digitalRead(4)+2*digitalRead(5)+digitalRead(6);

 

  // Judge the SD card recording flag

  switch(rec_mode)

  {

    case 0: // No recording

      rec_flag=false;

      break;

    case 1: // Every second

      rec_flag=true;

      break;

    case 2: // Every 10 seconds

      if(now.second()%10==0) rec_flag=true;

      else rec_flag=false;

      break;

    case 3: // Every 30 seconds

      if((now.second()%30==0)) rec_flag=true;

      else rec_flag=false;

      break;

    case 4: // Every minute

      if(now.second()==0) rec_flag=true;

      else rec_flag=false;  

      break;

    case 5: // Every 10 minutes

      if((now.minute()%10==0)&&(now.minute()!=prev_min))

      {

        rec_flag=true;

        prev_min=now.minute();

      }     

      else rec_flag=false;  

      break;

    case 6: // Every 30 minutes

      if((now.minute()%30==0)&&(now.minute()!=prev_min))

      {

        rec_flag=true;

        prev_min=now.minute();

      }     

      else rec_flag=false;   

      break;

    case 7: // Every hour

      if((now.hour()!=prev_hour))

      {

        rec_flag=true;

        prev_hour=now.hour();

      }     

      else rec_flag=false;  

      break;

  }

 

  // Display to Serial Monitor

  Serial.print(now.year(), DEC);

  Serial.print("/");

  Serial.print(now.month(), DEC);

  Serial.print("/");

  Serial.print(now.day(), DEC);

  Serial.print(" ");

  Serial.print(now.hour(), DEC);

  Serial.print(":");

  Serial.print(now.minute(), DEC);

  Serial.print(":");

  Serial.print(now.second(), DEC);

  Serial.print(", ");

  Serial.print(sensor1);

  if(rec_flag) Serial.println(" -> Recorded to SD");

  else Serial.println("");

  dataFile.println("");

 

  // Display to LCD

  LcdTimeDisplay(0, 0, now.month()); 

  lcd.setCursor(2, 0);lcd.print("/"); 

  LcdTimeDisplay(3, 0, now.day()); 

  lcd.setCursor(5, 0);lcd.print(" "); 

  LcdTimeDisplay(6, 0, now.hour()); 

  lcd.setCursor(8, 0);lcd.print(":");

  LcdTimeDisplay(9, 0, now.minute()); 

  lcd.setCursor(11, 0);lcd.print(":");

  LcdTimeDisplay(12, 0, now.second()); 

 

  lcd.setCursor(0, 1);

  switch(rec_mode)

  {

    case 0: lcd.print("NoRec"); break;

    case 1: lcd.print(" 1sec"); break;

    case 2: lcd.print("10sec"); break;

    case 3: lcd.print("30sec"); break;

    case 4: lcd.print(" 1min"); break;

    case 5: lcd.print("10min"); break;

    case 6: lcd.print("30min"); break;

    case 7: lcd.print("1hour"); break;

  }

   

  lcd.setCursor(6, 1);lcd.print(sensor1);

  lcd.setCursor(12, 1);lcd.print("mm  ");

   

  // Write to SD card

  if(rec_flag)

  {

    File dataFile = SD.open("datalog.txt", FILE_WRITE);

    dataFile.print(now.year(), DEC);

    dataFile.print("/");

    dataFile.print(now.month(), DEC);

    dataFile.print("/");

    dataFile.print(now.day(), DEC);

    dataFile.print(" ");

    dataFile.print(now.hour(), DEC);

    dataFile.print(":");

    dataFile.print(now.minute(), DEC);

    dataFile.print(":");

    dataFile.print(now.second(), DEC);

    dataFile.print(", ");

    dataFile.print(sensor1); 

    dataFile.println("");

    dataFile.close();

    rec_flag=0;

  }

}

 

void LcdTimeDisplay(int x, int y, int a)

{

  if(a<10)

  {

    lcd.setCursor(x, y);

    lcd.print("0");       

    lcd.setCursor(x+1, y);

    lcd.print(a);   

  }

  else

  {

    lcd.setCursor(x, y);

    lcd.print(a);      

  }

}

 

 

このスケッチを使った時に、ディップスイッチを使ってSDカード記録間隔を設定する際の早見図です。

 

 

 下の写真は北海道大学にある4連の段階圧密試験装置です。しばらくの間死んでいましたが、実験のクラス用にリノベーションしたいと思っています。4連なので、同じロガーを4つつくりました。もちろん、1つのマイコンで4つの変位計の計測値を記録することも可能ですが、学生実験のときは4つのグループが個別に作業するので、完全独立のほうがいろいろとよかろうと思い、同じものを4つつくりました。NDISコネクタで変位計がワンタッチでつなげられるようにしています。NDISコネクタもブラケットもモノタロウで買えます。

 

 

 

基盤を取付ベース(木板)に木ネジで固定するときのコツです。基板の裏にはいろいろついているので、そのまま木板に押し付けて固定するわけにはいかず、浮かせる形にしますが、そのためのスペーサーとして、土質試験室によくある空気圧や排水用のナイロンチューブを使うとよいです。下の写真では、ニッタ・ムアーの外径6mm・内径4mmのナイロンチューブ(青)を使っています。これを5mm~10mmくらいに切るとスペーサーになります。

 

 

先の写真では、個々のロガーを4つ付けただけなので、電源が個々に4個必要です。これをタコ足配線し、1つの電源だけを共有するとともに、個々のロガーのON/OFFができるように、途中に赤いスライドスイッチを付けたのが下の写真です。電圧は、ロガーシールドについているPBCターミナルブロック(緑の端子台)から与えています。ロガーシールド内の詳しい配線はわかりませんが、ここに与えた電圧は電圧レギュレータを通ってArduino Nanoに印加されるようなので、5Vよりやや大きい電圧が必要です(写真ではエネループ6本で7.7V与えています。5Vちょうどを与えると3.8VくらいになってArduino内部に与えられるので、正常に動きません)。Arduino NanoのUSB端子に直接電圧を与える場合は逆に、5Vちょうどでないといけないはずです。

 

 

これでそれぞれの赤いスライドスイッチをONすると・・・

 

 

動き始めます。変位計をつないでいない状態の写真なので、変位の値は不定値です(ちょっと時間合わせが甘いか)。LCDの上段に時刻、左下にSDカードへの記録間隔設定を示しています。電池で印加しているといずれ切れるので、9Vのアダプタを使って印加しようと思います。

 今回は番外編としてArduino Microを紹介します。今日はものづくりの部品というよりは、実験室でのお助けツールとして紹介するので番外編扱いです。Arduino Microは、第31回でも紹介しましたがArduino Leonardoという製品の小型版です。Arduino UnoやPro Miniなど典型的なArduinoはATmega328Pというマイコンを使い、スケッチ書き込み用のシリアル-USB変換チップとしてATmega16u2あるいはCH340などを使用しています。これら2つのチップを統合した機能を持つATmega32u4を使っているのが MicroおよびLeonardoの特徴です。チップの統合により廉価となった、と紹介しているサイトもありますが、実際のところUno / Pro MiniとLeonardo / Microの価格の違いはほとんどありません。

 

 価格よりも大きな違いとして、ATmega32u4はPCに接続したときにHID(Human Interface Device)として認識されるという特徴があります。HIDとはマウスやキーボードなどといった装置を指します。つまり、MicroやLeonardoをPCにつなぐと、これらからの信号によりPCを容易に操作できます。例えば、20秒に1度左クリックをするようなスケッチを書き込んだMicroをPCにつなぐと、マウスを20秒に1度左クリックしているのと同じ現象が起こります。クリック以外にもマウス移動、タイプ(文字入力)などを実行可能です。

 

 この機能を使えば、自身で書き変えることのできない既存の実験制御ソフトウェアをある程度自動制御することができます。たとえば、夜の21:00に実験「開始」ボタンをクリックしたいが、それだけのために居残りたくない、という場合(リモートデスクトップでも使えれば話は別ですが)、21:00に画面のある場所を左クリックするスケッチを書いた MicroをPCのUSB端子に接続しておけば、それだけで実施してくれます。もちろん、複数の動作を書き込むことも可能なので、左クリックした後にファイル名を書き込んでreturnを押して、画面の別の場所をもう一度クリック、など実行することもできます(ファミコンで使えたなら、スターソルジャーやグラディウスといったシューティングゲームで最強の連打補助装置をつくることができるでしょう)。このようなこと(マウスを動かすのと等価なアクションを起こすこと)はWindowsのAPIでもできるのですが、ArduinoをUSBにつなげるだけである特定の作用をPCに対して起こすことができるというのは便利な面があります(一度つくれば、どのPCにでも突き刺すだけなので)。

 

 ここでは簡単な作例として、先ほどの例のように、21:00に左クリックを行う装置とスケッチを用意します。21:00という時間を指定するために以下の2つの方法が考えられます。

 

① Microのマイコン内部のクロックを使う方法

この方法では、内部クロックは電源が切れるたびにリセットされるので、「~時間~分後」という設定しかできません。つまり、3時間後にクリックせよというスケッチを書いて18:00に接続するといった方法しかありません。一方で、外部クロック(RTC:第7回参照)が不要という利点もあります。この場合のスケッチ(例として3時間後に動作)は以下の通りです。Mouse.hというライブラリはArduino IDEに標準インストールされています。

 

 

#include "Mouse.h"

 

int flg=1; //このフラグを使わないと3時間おきに繰返しクリックが起こってしまう

 

void setup()

{   

  Mouse.begin();

}

 

void loop()

{

  delay(3*3600*1000);

  if(flg==1)

  {

    Mouse.click();

    flg=0;

  }

}

 

 

この場合、Micro単体をPCに接続するだけです。接続して3時間後に左クリックが起こります。

 

② 外部RTCを使う方法

 ここでは外部RTCとしてDS3231を使います。RTCやDS3231については第7回を参照してください。第7回に従って、接続は以下の通り(DS3231をMicroにI2C接続)です。MicroのI2Cは、D2 – SDA、D3 – SCLで、UnoやPro Miniとは異なるので気を付けて下さい(これらではD4 – SDA、D5 – SCL)。

 

 

スケッチは以下の通りです。なお、DS3231の時間は第7回にしたがってあらかじめ設定しておきます。

 

 

#include <DS3232RTC.h>    //http://github.com/JChristensen/DS3232RTC

#include <Time.h>

#include <TimeLib.h>

#include "Mouse.h"

 

int flg=1; //クリック動作実行を一度だけにするためのフラグ

 

void setup()

{   

  Serial.begin(9600);

 

  setSyncProvider(RTC.get);   // get the time from the RTC and set it to Arduino

  if(timeStatus() != timeSet)

    Serial.println("Unable to sync with the RTC");

  else

    Serial.println("RTC has set the system time");

  delay(1000);

 

  Mouse.begin();

}

 

void loop()

{

  // RTCの時間設定がわかるように、シリアルモニタに時間を表示

  Serial.print(hour());Serial.print(":");Serial.print(minute());Serial.print(":");Serial.print(second());Serial.println(":");

 

  if((hour()==21)&&(flg==1)) // 21時になったらマウスを左クリック

  {

    Mouse.click();

    flg=0;

  }

 

  delay(1000);

}

 

 

もし日付けまで指定したければ、スケッチの赤字の部分にいろいろ条件を加えます。例えば:

if(((hour()==21)$$(day()==12))&&(flg==1))

とすると、毎月12日の21時に動作します。

 

マウスポインタをソフトウェアのボタンの上に置いておき、Pro MicroをUSB端子に接続すれば終わりです。22:00に左クリックが起こります。

 

このようにつないで・・・

 

マウスを右側のタスクバーの「電卓」上に置いておくと・・・(スクリーンショットなのでカーソルが消えてしまっていますが)

 

 

21:00になるとクリックが起こり、「電卓」が立ち上がる。ここで21:00は当然、Windowsの時間ではなくRTCの設定時間で21:00です。

 

 ただし、いずれの方法にしてもWindowsではなにかしらのポップアップが出て、想定していたソフトウェアのウィンドウのアクティベーションが外れたりすることもあり、そうなると話は別であることには注意してください。

 

Mouse.hライブラリでできることは、左クリック(Mouse.click())の他に例えば

Mouse.move():x, yを引数としてマウスカーソルを移動

Mouse.press():マウスボタンのプレス(クリックして戻さない)を実行

Mouse.release():プレスを解放する

Mouse.isPressed():マウスボタンのプレス状態を返す

 

Keyboard.hライブラリでできることとして、マウスと同様にKeyboard.press()やKeyboard.release()などがありますが、一番シンプルな例はKeyboard.write()で、例えばKeyboard.write(‘n’)とするとメモ帳などにnと現れます。

 データをフィールドからクラウド(インターネット)まで無線で飛ばし、オフィスからデータにアクセスできれば、これ以上のことはありません。これまで、この無線通信には3G(少し前の携帯電話回線)を使うことが多かったのですが、近年はLPWA(Low Power Wide Area)ネットワークというものが注目されています。もちろん4Gや5Gを使うことも技術的に可能でしょうが、我々がフィールドから送りたいデータは、たとえば間隙水圧や変位の値など、データ量としてはごく小さいものです。LPWAの長所は、名前が示す通り非常に省電力であり、また第三者のサービスを利用する場合、通信料が非常に安いということです。

 

Sigfox

 LPWAというのは低電力・広域ネットワークの総称で、いくつかの規格があります。これについては他のサイトにまとめられているので、適当に検索して読んで下さい。特に有名なのがSigfoxとLoRaです。LoRaはあくまで通信規格であり、送信機・受信機(ゲートウェイ)を自身で設置する必要があります(2020年現在の話です)。一方、Sigfoxはサービスまで含んでおり、基地局が世界各地にすでにあるため、携帯電話と同じように、端末だけ用意し、オペレータに料金を払えば基地局を通してインターネット上にデータを転送することができます。Sigfoxの利用は敷居が低いので、今日はSigfoxを使ったデータの無線転送方法を説明します。

 

 まず、Sigfoxの現在のサービスの概要です。一回に送れるデータは12バイト(0, 1, 2, …, a, b, …, fの16進数を24桁)で、これを1日に2/50/100/140回送れます(それぞれ年間通信料700/800/900/1000円+消費税)。送られた24桁(文字)のテキストはSigfox Backendのサイトからタイムスタンプとともに確認することができ、csvで出力することも可能です。通信圏は基地局から約60kmですが、山間部・丘陵地帯には届きません。カバーエリアは以下から確認できます。

https://www.kccs-iot.jp/area/

カバーエリアが円形になっているので、これを見るとどこに基地局があるかだいたいわかります。私がいる北海道では、函館・室蘭・苫小牧・札幌・小樽・留萌・旭川・滝川・帯広・釧路・根室・北見・網走・紋別・名寄・中標津といったところにあるようです。この近郊で地形が許すところで使えます(京セラの方々、読んでいたら、私の観測サイトがある黒松内にも基地局建ててください)。

 

 Sigfoxというのはフランスの会社ですが、日本では京セラコミュニケーションシステムが運営しています。

https://www.kccs-iot.jp/service/

上記HPは日本語で書かれていますが、実際にライセンスを購入したり、クラウド上のデータを扱うサイト(バックエンドと呼ばれています)は全て英語です。また、言語は抜きにしても、チュートリアルがなく、ライセンスアクティベーションのシステムがはっきりしない(2年くらい使っていますが、いまだにバックエンドの使い方がよくわからない)など、率直にいってかなりわかりづらい部分があります。これを使うほぼ唯一の手がかりとなるのが、Gaku Hibiさんという方(Sigfoxの社員なのでしょうか?)のQiita上のブログです。今日紹介するモジュールの使い方も、この方のブログがほぼ唯一の手がかりです。

 

Sigfox送信モジュール

 日本で手に入るSigfoxモジュールとして主に以下の二つがあります。

・BRKWS01 RC3

韓国のWisolという会社のマイコンを積んでいます。今日はこれを使います。スイッチサイエンスのウェブサイトなどで購入できます。5000円強です。1年間の通信ライセンスを含んでいます(140回/日のものだったように記憶しています。アクティベーションに期限があるので注意。アクティベートしてから1年使えます)。

https://www.switch-science.com/

 

 

・KCCS-UNASHIELD-V2S

Arduino Unoのシールドの形状に実装されています。これも1年のライセンスが含まれています。2018年にスイッチサイエンスから1つ買いましたが、その後、ずっと入荷未定になっており、手に入るかわかりません。

 

 

 上記の通り、BRKWS01 RC3の使い方を紹介します。ブレークアウトボードに加え、アンテナとおまけのピンヘッダが同封されています。静電気保護袋に貼ってあるラベルにはモジュールのIDとPACが書かれているので、少なくともアクティベーションまではこれを捨てないでください。IDとPACはマイコン内のメモリに入っているので、シリアル通信で読み出せますが、面倒です。

 

 

 ボードにはいろいろピンが出ていますが、実質上使うのは4つ(+, -, TX, RXのシルクがある部分)だけです。ピンヘッダ―もここだけつければOKです。使い方は簡単で、Arduinoなどのマイコンから3.3V印加します(5Vでも大丈夫かもしれません)。つまり、Arduinoなら3.3Vピン・GNDピンをBRKWS01 RC3の+・-にそれぞれつなぎます。TX・RXとあることからわかるように、マイコンとはシリアル(UART)通信でコマンドをやりとりします(UARTについては第29回参照)。ArduinoとPCの間でハードウェアシリアルを使った通信をしながら(つまり、動作状況をArduino IDEのシリアルモニタで確認しながら)使いたいので、ArduinoとこのBRKWS01 RC3の通信にはソフトウェアシリアルを使いたいと思います(第29回参照)。Arduino UnoやPro MiniなどAtmega328Pを使ったマイコンでは、たとえばピン2・3が使えます(他にも使えるピンはあります)。以下のような接続になります。

 

 

この装置を使って、例えばクラウドに「0123456789abcdef01234567」という24字のメッセージを送ってみます。スケッチは以下のようになります。

 

 

#include <SoftwareSerial.h>

#include <Time.h>

#include <TimeLib.h>

 

SoftwareSerial mySerial(2, 3); // 2 - RX, 3 - TX

 

void setup()

{

    /* Hardwar serial: Arduino - PC */

    Serial.begin(9600);

 

    /* Software serial: Arduino - BRKWS01 RC3 */

    mySerial.begin(9600);

 

    /* AT commandの例 */

    //Serial.println("Connecting to the Sigfox Breakout board...");

    //Serial.println("AT$I=10 : get Device ID");

    //Serial.println("AT$I=11 : get PAC");

    //Serial.println("AT$T? : get Temperature");

    //Serial.println("AT$V? : get Voltages");

    //Serial.println("AT$P=unit : set Power mode (unit = 0:software reset 1:sleep 2:deep_sleep)");

    //Serial.println("AT$TR=unit : set Transmit repeat (unit = 0..2)");

    //Serial.println("AT$WR : save config");

    //Serial.println("AT$SF=[payload] : SEND SIGFOX MESSAGE");

  

    mySerial.print("AT$I=10\r"); //BRKWS01 RC3にDevice IDを返すようコマンドを送る

}

 

void loop()

{

    /* Creat a sigfox message */

    char dataline[31]="AT$SF=0123456789abcdef012345678";

    dataline[30]='\r'; //最後の1文字は改行文字(リターン)に置き換える

 

    Serial.println(dataline); //PCのシリアルモニタに確認のため送信メッセ―ジを表示

    Serial.print("Device ID : "); //PCのシリアルモニタに"Device ID:"と記す

       

    /* Send Sigfox message */

    mySerial.print(dataline);

    int flg=0;

    int minute0 = minute();

    int second0 = second();

   

    while(flg==0)

    {

      if (mySerial.available()) { // BRKWS01 RC3からメッセージが返っていれば

        Serial.write(mySerial.read()); // そのメッセージをPCに送りシリアルモニタ上に表示(まず最初に、要求していたDevice IDが返ってくる)

      }

      if (Serial.available()) { //シリアルモニタからメッセージが入力されたなら

        mySerial.write(Serial.read()); // BRKWS01 RC3にそのメッセージを送る

      }

 

      //10sec経過すると送受信のルーチンを終了

      if((second0>=50)&&(second()>(second0+10-60))&&(second()<50))

      {

        flg=1; 

      }

      else if((second0<50)&&(second()>(second0+10)))

      {

        flg=1; 

      }

    }

   

    Serial.println("After sigfox routine");

 

    delay(300000); //5分間停止

}

 

 

スケッチについていくつか説明です。

・BRKWS01 RC3にはATコマンドというものを送ります。例えば、"AT$I=10\r"という文字列をUARTでBRKWS01 RC3に送ると、ArduinoにはID(先ほど、袋のラベルに書いてあると記したもの)を返します。その他、コメントアウトしている文に、PACを要求するコマンドなど書いてあります。温度や電圧も計測できるようです(使うことはあまりないと思いますが)。"AT$SF=[payload]”の[payload]のところに24字(24桁)の文字を入れると、それをLPWAで基地局まで送信することになります。

・mySerialというのは、Arduino – BRKWS01 RC3間のソフトウェアシリアル、SerialというのはArduino – PC間のハードウェアシリアルのことです。ArudinoのTX・RXをBRKWS01 RC3のRX・TXというように、互い違いに接続することに注意して下さい(第12・29回参照)

・通信がうまくいかない場合に最後のほうのwhileのルーチンで無限ループに入ると(フィールド観測の場合)無駄にバッテリーを消費するので、10秒経ってうまく行かなければ諦めることにします(フィールド観測の場合、この後にたとえばスリープに入る、あるいは電源装置に終了の信号を送るといった省電過程に入ります:第13・14回参照)。

 

BRKWS01 RC3にアンテナをぱちっとはめれば、これでハードウェアは完了です。マイコン(Arduino)に電源を入れれば、10秒以内にメッセージがクラウドに送信されます。ただ、これとは別にSigfox Backendでこのモジュールを登録しなければなりません。以下のウェブサイトに行ってください。

https://backend.sigfox.com/auth/login

アカウントをつくってログインしたら、モジュールを登録します。この方法ですが、最初からライセンス込みのモジュールを買った場合は比較的簡単です(それらしいところをクリックしていけばできるはずなのでやってみてください)。ライセンスがない、あるいは切れたモジュールに対し、別途購入したライセンス(「token」と呼びます。以下から買えます:https://buy.sigfox.com/)をあてがうのは、なかなかわかりづらいです。イメージとしては、tokenをためておき、device groupに結び付け、このdevice groupにtransferした新しいdeviceから最初の信号が送られるとtokenが消費されるとともにアクティベーション(購入から1年以内にアクティベートしないといけません)が完了する、といった構図のようです。とにかく、サイトを30分くらいクリックしまくればなんとかなります。

 

データの確認

アクティベーションが完了すると、メッセージがバックエンドに届くようになります。メッセ―ジは、「DEVICE」のページから該当するモジュールのIDをクリックし、左に現れる「MESSAGES」をクリックすると現れます。上記の通り接続しスケッチをいれてもうまく行かない場合はtokenの登録がうまくいっていないことなどが考えられます。しかし、私の経験上、メッセージが現れない原因として一番多いのは、単に装置が圏外にあったということです(1~2時間かけてさんざん配線やバックエンド設定を確認したが原因がわからず、場所をちょっと動かしたらうまくいった、ということは何度もあります)。LPWAの電波は障害物に弱いので、屋内ではうまくいかないことが多いです。札幌の街の真ん中にある我が家のベランダでも失敗することがあります。そもそも場所が圏内であることを確認したうえで、見通しのよい場所で試してみて下さい。

 

 

 

以下の画面は別の例からとってきたので送信メッセージ(Data/Decodingの欄)が0123456789abcdef01234567になっていませんが、上のスケッチを走らせればそうなるはずです。

 

 

センサー値をテキストデータにして送信

 実際の応用では、センサーの値などをこの24桁を使って送信することになります。10進法を16進法にすれば多少圧縮できますが(たとえば2341mVは10進法では4桁ですが、16進法にすれば92cで3桁に収まります)、その必要がなければ10進法のままでもいいです。

 

 ここでは例として、アナログピンA0-A4の5本を使って電圧を読み込み(mV単位ではなく、ビット数0-1023のままとします)、最初の20桁を使って4桁の電圧(mV)を5チャネル送信し、残りの4桁は0にするスケッチを示します。先ほどと異なる箇所を赤字で示します。

 

 

#include <SoftwareSerial.h>

#include <Time.h>

#include <TimeLib.h>

 

SoftwareSerial mySerial(2, 3); // 2 - RX, 3 - TX

 

const int apins[5]={A0, A1, A2, A3, A4};

 

void setup()

{

    /* Hardwar serial: Arduino - PC */

    Serial.begin(9600);

 

    /* Software serial: Arduino - BRKWS01 RC3 */

    mySerial.begin(9600);

 

    /* AT commandの例 */

    //Serial.println("Connecting to the Sigfox Breakout board...");

    //Serial.println("AT$I=10 : get Device ID");

    //Serial.println("AT$I=11 : get PAC");

    //Serial.println("AT$T? : get Temperature");

    //Serial.println("AT$V? : get Voltages");

    //Serial.println("AT$P=unit : set Power mode (unit = 0:software reset 1:sleep 2:deep_sleep)");

    //Serial.println("AT$TR=unit : set Transmit repeat (unit = 0..2)");

    //Serial.println("AT$WR : save config");

    //Serial.println("AT$SF=[payload] : SEND SIGFOX MESSAGE");

  

    mySerial.print("AT$I=10\r"); //BRKWS01 RC3にDevice IDを返すようコマンドを送る

}

 

void loop()

{

    /* Creat a sigfox message */

    int ainput;

    int cnt;

    int digit1000, digit100, digit10;

    char dataline[31]="0000000000000000000000000000000";

    dataline[0]='A';dataline[1]='T';dataline[2]='$';dataline[3]='S';dataline[4]='F'; dataline[5]='=';

    dataline[30]='\r'; //最後の1文字は改行文字(リターン)

      

    for(cnt=0;cnt<5;cnt++)

    {

      // 電圧を読む(ビット数のまま:0-1023)

      ainput=analogRead(apins[cnt]);

      // 1000の位

      digit1000=int(ainput/1000); dataline[6+4*cnt]='0'+ digit1000;

      // 100の位

      digit100=int((ainput-1000*digit1000)/100); dataline[7+4*cnt]='0'+ digit100;

      // 10の位

      digit10=int((ainput-1000*digit1000-100*digit100)/10); dataline[8+4*cnt]='0'+ digit10;

      // 1の位

      dataline[9+4*cnt]='0'+int((ainput)%10);

 

      // PC上のシリアルモニタに表示

      Serial.print("Analog input CH"); Serial.print(cnt); Serial.print(": "); Serial.print(ainput); Serial.println(" (bit)");

    }

   

    Serial.println(dataline); //PCのシリアルモニタに確認のため送信メッセ―ジを表示

   

    /* Send Sigfox message */

    mySerial.print(dataline);

    int flg=0;

    int minute0 = minute();

    int second0 = second();

 

    Serial.print("Device ID : "); //PCのシリアルモニタに"Device ID:"と記す

   

    while(flg==0)

    {

      if (mySerial.available()) { // BRKWS01 RC3からメッセージが返っていれば

        Serial.write(mySerial.read()); // そのメッセージをPCに送りシリアルモニタ上に表示(まず最初に、要求していたDevice IDが返ってくる)

      }

      if (Serial.available()) { //シリアルモニタからメッセージが入力されたなら

        mySerial.write(Serial.read()); // BRKWS01 RC3にそのメッセージを送る

      }

 

      //10sec経過すると送受信のルーチンを終了

      if((second0>=50)&&(second()>(second0+10-60))&&(second()<50))

      {

        flg=1; 

      }

      else if((second0<50)&&(second()>(second0+10)))

      {

        flg=1; 

      }

    }

   

    Serial.println("After sigfox routine");

 

    delay(300000); //5分間停止

}

 

 

この例をいじれば、電圧に限らず、温度でも水圧でも、なんでも送信できるはずです。             

 第31回で紹介したESP32を使うと、WiFiによる通信を簡単に行うことができます。ESP32は上海のEspressifという会社が開発したマイコンであり、1つのチップ上にプロセッサの他、WiFiやBluetooth機能を加えたSoP(System on Chip)として実装されたものです。ESP32と呼ばれているものにはいくつかバージョンがあり、ESP-WROOM-02が主なものです。これはESP8266というものの後継機にあたり、700円くらいで買うことができます。ESP-WROOM-02単体を動かすのは、電源周りやピンアウトなどなかなか手間がかかるので、抵抗やコンデンサが載った開発ボードを使うのが手軽です。第31回で紹介した通り、NodeMCU-32SとESP32-DevKitCの二種類(二規格)が主流になっています。後者のほうが改良版といえるようです。後述の通り、スケッチの書き込みが少し楽になっています。いずれも1000円くらいから買えます。

 ESP32(ESP-WROOM-02)の性能諸元については第31回で示していますが、WiFi・Bluetoothを除いても、処理能力・メモリともにArduino UNOを大きく上回っており(アナログ入力も、Arduino UNOの10ビットに対し、12ビットと4倍も分解能が高いです)、価格も安いことから、いろいろなプロジェクトに役に立ちます。

 

 

左がNodeMCU-32S、右がESP32-DevKitC。後者が若干大きい。

 

 

裏面。左はESP32 DEKITV1と書かれています。

 

ESP32開発ボードの利用:Arduino IDEによるスケッチの書き込み

 まず、これらのESP32開発ボードにArduino IDEからスケッチを書き込む方法を説明します。ESP32はArduinoとある程度互換性があり、Arduino IDEからArduino言語(C++とほぼ同じ)により動かすことができ、多くのライブラリも互換性があります。例えば過去の回で説明したRTC、ADS1115、SDカードシールド、その他ほぼ全て同じように使うことができます。一方、プロセッサの種類が異なるため、プロセッサそのものに直接関わる部分、例えばスリープ関係などは互換性がありません(これはArduinoシリーズの中でも、AVR系のUno・Mega・Pro MiniなどとARM系のDueで互換性がないのと同じです)。

 

 まずはArduino IDEの設定方法です。いろいろなウェブサイトにやり方が書いてあるのですが、いくかのサイトでは古い情報が載っており、うまくいきません。ここに書くやり方も古くなる可能性はあるのですが、2020/10/1現在動いていることを確認しています(Arduino IDE 1.8.13)。まず、「ファイル」→「環境設定」より「追加のボードマネージャのURL」のテキストボックスに以下のURLを書き込みます(多くのウェブサイトでコピー&ペーストせよ、と書いてありますが、私の持っているどのPCでもこのテキストボックスではペースト機能が動作しませんでした:手で打ち込む必要があります)。

https://dl.espressif.com/dl/package_esp32_index.json

この後、「ツール」→「ボード」→「ボードマネージャ」で現れるウィンドウの検索フォームに「ESP32」と入力すると現れるものを「インストール」します。これで、「ボード」から「ESP32 Dev Module」を選べるようになります。これとは別に「NodeMCU-32S」も選べるのですが、ここまで試したところ、どちらを選んでも同じスケッチが同じように動きました。ここでは「ESP32 Dev Module」を選ぶものとします。

 

 ESP32開発ボードはmicro USB Type-BでPCにつなぎます。PCにつなぎ、ボードとして上記の通り「ESP32 Dev Module」を選び、シリアルポートを選択してやると、後はArduinoと同じように使えます。つまり、スケッチをコンパイルして書き込みます。この際、注意が必要なのですが、ESP32-DevKitCはArduinoと同じようにArduino IDE上の書き込みアイコンをクリックするだけでこのままスケッチが書き込まれるのですが、NodeMCU-S32については以下の手順が必要になります。

① ボード上のIO0ボタンを押し続け(まだ離さない)、後からENボタンを押し、ENボタンを離す

② スケッチの転送を始める。Arduino IDEの下部のメッセージ欄で書き込みが始まったらすぐにIO0ボタンを離す

③ 書き込みが始まったらENボタンを押してリセットする(押さなくてもリセットされる場合もある)

これを行わないと書き込みエラーになります。

 

 ここまでの回で紹介してきたことはスリープ関係を除いてほぼEPS32でもできるので、試してみてください。ESP32のスリープについては第31回にサンプルスケッチがあります。ちなみにArduinoシリーズにはボードにリセットボタンが1つ付いていることが多いですが、ESP32開発ボードでは上記2つのボタンのうちENがリセットボタンです。

 

WiFiを使ってデータを転送:Ambientの利用

 Ambientというのはクラウドでのデータ保存・図化サービスで、アカウントを無料で作って利用することができます。他にも同様のサービスがいくつかありますが、Ambientが最も有名なようです。その使い方自体は、Ambientのウェブサイトそのものが最も明瞭に説明しています。

https://ambidata.io/docs/gettingstarted/

 Ambientに値を渡すのにライブラリが必要になります。いくつか異なるものがあるようですが、ここではこれを使います。ESP8266とありますが、ESP32で使えます。

https://github.com/AmbientDataInc/Ambient_ESP8266_lib

Arduinoを使う場合、第7回で説明したように、GitHubからライブラリをZIPファイルとしてダウンロードし、解答してArduinoのlibrariesフォルダに入れるという作業をしました。ここではESP32用ライブラリとして別の場所に保存するため、以下のようにします。Arduino IDEの「スケッチ」→「ライブラリをインクルード」→「ライブラリを管理」として現れるウィンドウの検索欄で、「Ambient」とすると上記のものが現れ、「インストール」とすれば使えるようになります。

 上記のライブラリのフォルダ中(場所は、Windows内で「Ambient.h」を検索して見つけて下さい)には例えば第22回で紹介したセンサーBME280で取得した温度・気圧・湿度をクラウドに送り図化するスケッチ例があります。ほぼ同じスケッチになりますが、BMP280(湿度が計れない)のほうを使って30分に一度、温度と気圧をAmbientに送るもの接続図・写真とともに下に示します。

 

 

#include <WiFi.h>

#include <Adafruit_Sensor.h>

#include <Adafruit_BMP280.h>

#include "Ambient.h"

 

WiFiClient client;

Ambient ambient;

 

Adafruit_BMP280 bmp;

 

const char* ssid = "XXXXXXXXXXXXXX"; // ここにはWiFiルーターのSSIDを書き込む

const char* password = "YYYYYYYYYYYYYY"; // ここにはWiFiルーターの接続パスワードを書き込む

 

unsigned int channelId = 2XXX8; // AmbientのチャネルID

const char* writeKey = "aaaaaaaaaaaaaa"; // Ambientのアカウントから得られるライトキー

 

void setup()

{

    Serial.begin(115200);

    delay(10);

    Serial.println("Start");

    WiFi.begin(ssid, password);  //  Wi-Fi APに接続

    while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち

        delay(100);

    }

 

    Serial.print("WiFi connected\r\nIP address: ");

    Serial.println(WiFi.localIP());

 

    ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化

 

    /* ----- Starting up BME280 ----- */

    if (!bmp.begin()) { 

      Serial.println(F("Could not find BMP280!"));

      while (1);

    }

}

 

void loop()

{

    Serial.println("Writing");

    ambient.set(1, bmp.readTemperature()); // 温度をデータ1にセット

    ambient.set(2, bmp.readPressure()); // 湿度をデータ2にセット

    ambient.send(); // データをAmbientに送信

    delay(1800 * 1000);

}

 

 

 

 

接続について数点:

・ESP32はGPIO(ピン)のうち22番がI2CのSCL、21番がSDAです。

・上の模式図のボードと写真のボードでピン配置が異なることに気が付くと思いますが、製品によってピンアウトが微妙に異なるので注意してください。

・ESP32 DevKitCはボードが大きく、通常のブレッドボードに挿すとジャンパーワイヤーを指す穴が残りません。上の写真ではブレッドボードを2つ並べて片側ずつ使っています

・BMP280(BME280)の説明については第22回参照。SDOをVCC(3.3V)につなぐかGNDにつなぐかでI2Cアドレスを変えられます。VCCのほうに繋いでおけばAdafruitのライブラリがそのまま使えます。

 

 このようにしてスケッチを走らせると(スケッチ書き込み後、なぜか1回立ち上げても、プログラムは走るのですがWiFiにつながらないことがありました。ENボタンでリセットするとつながりました。うまく行かないときはリセットしてみてください)、下図のようにAmbientのデータ系列1に温度、データ系列2に圧力が転送され、グラフを設定することで図化できます。データはCSVとしてダウンロードもできます。ウェブブラウザ上でのグラフの表示の仕方は非常に直感的なので、ここでは説明しません。

 

 

 

BMP280(BME280)はデモとしてはベタな例ですが、土質試験室ではひずみゲージセンサーの値を読んで転送したいことのほうが多い(例えば長期圧密試験の変位など)かと思います。そのような例を応用編で紹介していきたいと思います。

 

 ここまで、マイコンボードとして主にArduino Uno、たまにArduino Pro Miniを使った説明をしてきました。Pro Miniは物理的に小さく、特に3.3V 8MHz版は消費電力も小さいというメリットがありますが、Unoは単にソケット(ピンヘッダーがはまる部分)がついている、DC入力端子・USB端子があるなど、少し便利な部分があるだけで、プロトタイプ(試作品)をつくる目的以外に対してメリットがありません。どちらも使っているプロセッサ自体は同じで、現在のスタンダードでいえば非力な部類に入るので、プロジェクトによっては限界があります。そこで、今回はいくつかの異なるタイプのArduinoと、互換性のあるマイコンボードを紹介します。詳細については検索すればいくらでも情報が手に入るので、あくまで地盤工学の目的(室内試験・原位置観測) の視点から紹介します。

 

① Arduino Uno

 Arduinoは皆これから始める、というベーシックな製品です。簡単なプロジェクトなら、プロトタイプのみならず、これで実用に供するものもつくれます。少しだけ便利(USBケーブルでPCにすぐつなげられる)で使いやすいという以上のメリットは特にありません。

 

マイコン:ATmega328P

電圧系:5V

フラッシュメモリ:32kB(うち0.5kBはブートローダ*)

SRAM:2kB

EEPROM:1kB

クロック:16 MHz

 

*ブートローダとは、スケッチをメモリに読み込むためのプログラムで、Arduinoのマイコンのメモリ中にあらかじめ書き込まれているものです。昔の電子工作は、PICなどのマイコンにスケッチを書き込むハードルが高かったのですが、Arduinoはメモリ内にブートローダを入れた状態で販売するため、USBでPCとつなぐだけでプログラミングが可能となったことで、素人でも扱えるようになった点が画期的、とのことです。

 

② Arduino Pro Mini

 第12回で紹介済み。3.3V 8MHz版と5V 16MHz版があります。シリアル-USB変換チップが載っていないので、PCとの接続のたびに変換モジュールを間にかませてやらないといけないものの、その分、単独使用時の消費電力が小さいという大きなメリットがあります。また、サイズも小さいため、フィールドへの実装に向いています。マイコンの性能は基本的にUnoと同じです。

 

マイコン:ATmega328P

電圧系:3.3V/5V

フラッシュメモリ:32kB(うち0.5kBはブートローダ)

SRAM:2kB

EEPROM:1kB

クロック:8/16 MHz

 

③ Arduino Mega

 Unoの拡張版的な立ち位置のボードで、Unoより少し物理的に長くなっています。メモリが大きく(Unoの8倍)、ピンの数(Unoがアナログ6、デジタル14に対してMegaアナログ16、デジタル54)も各段に多いのが特徴で、Unoよりも大きなプロジェクトをサポートできます。原位置観測用ロガーとして、様々なデジタルセンサーを接続する際に、多くのライブラリをインポートする必要があります。そうすると、Unoではフラッシュメモリの容量が厳しくなり、正常に動作しないことがあります。そういった際に、Megaを使えば互換性をほぼ保ったまま拡張することができます。また、アナログピンが多いので、外付けのアナログ-デジタル変換器(ADC:第8回参照)に加えて、16ch分のアナログセンサーの読みを10bit分解能とはいえ、(配線を分岐して)バックアップとしてこちらでも計測・記録しておくことができます。スケッチはほぼそのまま使えるのですが、例えばI2C(SDA・SCL)・SPI(CS、MOSI、MISO、CLK)のピンアサインはUnoとは異なるので、接続に注意が必要です。

 

マイコン:ATmega2560

電圧系:5V

フラッシュメモリ:256kB(うち8kBはブートローダ)

SRAM:8kB

EEPROM:4kB

クロック:16 MHz

 

④ Arduino Due

 Megaよりもさらに強力なボードで、Arduinoシリーズの中では珍しく、AVRではなくARMのマイコンを搭載しています。アナログピン12、デジタルピン54に加え、アナログ出力(DAC)ピンが2本あるのが特徴です(このブログではパルス幅変調:PWMについて説明していませんが、この2本はPWMではなく真のDACです)。また、他のほとんどのArduinoのアナログ入力は10bitですが、このモデルのみ12bitです。この分解能にひかれて、最初はデータロガーとしての使用を検討しましたが、マイコンのアーキテクチャが他のArduinoと異なることもあり、例えばenergy.hなどの一部のライブラリに互換性がなかったりするうえに、第8回で紹介した通り、外付けのADCモジュールを取り付ければアナログ分解能は解決する問題なので、結局のところ、このボードにそれほど大きなメリットを見出しませんでした。また、消費電力が格段に大きいです。一般に、あまり広く使われているという印象を持ちません(Arduino IDE 1.8.13をダウンロードしたら、もはやDueはデフォルトではボードマネージャにインストールされていませんでした)。

 

マイコン:AT91SAM3X8E

電圧系:3.3V

フラッシュメモリ:512kB

SRAM:96kB

クロック:84 MHz

 

⑤ Arduino Nano

 UNOとPro Miniの中間のような製品です。Pro Miniに近い大きさでコンパクトですが、シリアル-USB変換チップが載っていて、USBケーブルだけで接続できます。厳密にいうとUNOのコンパクト版ではなく、より古いArduino Duemilanoveという製品のコンパクト版のようですが、使った感じは、ほぼUNOが小さくなったものと思ってよさそうです。

 

マイコン:ATmega328P

電圧系:5V

フラッシュメモリ:32kB(うち0.5kBはブートローダ)

SRAM:2kB

EEPROM:1kB

クロック:16 MHz

 

⑥ Arduino Pro Micro

 Mini、NanoときてMicroなのでとてもまぎらわしい(しかも大きさがみな同じくらい)のですが、これはArduino Leonardoのコンパクト版です。これはATmega32u4というマイコンを使っているのが特徴で、シリアル-USB変換とメインのプロセッサが一体化したものです。詳しくは以下を読んでもらうと特徴がわかります。

http://trac.switch-science.com/wiki/Guide/ArduinoLeonardo

 何ができるかというと、PCでキーボードやマウスと同じようなHID(Human Interface Device)として認識させることができるので、このArduino Pro Microに入れたプログラムに基づいて、例えばWindows PC上でクリックや文字タイプを実行させることができます(例えば、10秒ごとに「R」キーを打つというプログラムを書いたPro MicroをPCに接続すると、PC上でアクティベートされているウィンドウに10秒ごとにRの文字が現れます)など。最初は、だから何、と思っていましたが、よく考えれば究極の自動化が可能で、既成のPCソフトウェアに対して、このArduino Pro Microにマウスの操作をさせて自動で使うなどできるようになります。これを用いたトリックを後の回で紹介したいと思います。その他の性能はUnoとほぼ同等です。

 

マイコン:ATmega32u4

電圧系:5V

フラッシュメモリ:32kB(うち4kBはブートローダ)

SRAM:2.5kB

EEPROM:1kB

クロック:16 MHz

 

⑦ ESP32

 ここ数年、Arduinoと並んで主流となりつつあるマイコン(ボード)です。中国のEspressif社が開発したもので、マイコン自体は700円くらい(ボード実装版は1000円強くらい)で購入できる安価なものですが、非常に多機能で、なんといってもWiFi・Bluetooth通信機能を内蔵しており、IoTデバイスの製作に適しているというのが人気の理由です。アナログ-デジタル変換(ADC)、デジタル-アナログ変換(DAC)、省電力機能なども充実しています。Arduinoとの互換性も高く、Arduino IDEにアドインするだけでそのままプログラミングできます。これを使った例も紹介していきます。実装ボードにはいくつかありますが、NodeMCU-32SとESP32-DevKitCの二種類(二規格)が主流になっています。この違いについては以下のサイトがわかりやすく説明しています:

https://qiita.com/usashirou/items/eb3f5a83dfc82c0ae17a

上記のサイトはブレッドボードで使いやすいボード幅ということでNodeMCU-32Sを推していますが、私の個人的な使用感では、DevKitCのほうが素直に感じました(Arduinoに近い感覚で使えました)。DevKitCと書いておらず、Dev Moduleなどと書いてある製品もありますが、基本的には同じものだと思います。基板上面のピン横にシルク(ピン番号やGNDなどの機能の記載)があるものがDevKitC系のことが多いようです。

 

マイコン:Xtensaデュアルコア32ビットLX6マイクロプロセッサ(ただし、シングルコア版のESP32Sあり)

メモリー:520KiB SRAM

電圧系:3.3V

クロック:160または240 MHz

 

⑧ Seeeduino Xiao

 最近初めて使ってみて、お気に入りのボードの一つです。Xiaoというのは中国語の「小」で、SeeeduinoというArduino UNOに似た互換ボードのコンパクト版です。ARM Cortex M0+を使ったもので、ピンの数は最小限しかないのですが、そこそこの能力があり、省電力モードでは消費電力は1.5mA程度と非常に小さくなります。何よりとても小さくシンプルで、かつ約600円程度と安いので、使い回しがききそうな製品です。USB端子がC-Typeなのが特徴的です。Arduino IDEのボードマネジャーからアドインを追加することでArduino IDEにてプログラム可能です。

 

マイコン:Arm Cortex-M0+ 32ビット

フラッシュメモリ:256 KB

電圧系:3.3V

SRAM:32 KB

クロック:48 MHz

 

⑨ Teensy 4.0

 マイコン界最速ともいわれ、AMR Cortex M7を使用しており、600MHz以上の圧倒的な高速クロック(クロックは自動的に変動するらしいです。オーバークロックも可能とのこと)を誇ります。マイコン界のイングヴェイ=マルムスティーンといえます。Arduino IDEの比較的新しいバージョン(1.8.11以降)であればアドインを使用してプログラムできます。まだほとんど使ったことがないので、使用感については何も書けませんが、高速な処理が必要な場合の切り札になるかもしれません。他のサイトではDigitalWrite(電圧出力)を高速で繰り返した例がありましたが、MHzオーダーの周波数までならファンクションジェネレータとしても使えるかもしれません。

 

マイコン:ARM Cortex-M7

フラッシュメモリ:2048K

電圧系:3.3V

RAM:1024 K

クロック:~600 MHz(変動)

 

消費電流

 上記は各種サイトから情報をまとめただけ(それに私見を加えた)なのですが、屋外での使用、あるいは屋内でもバッテリーによる駆動を考えた際、(消費)電流が気にかかります。そこで、それぞれのボードの電流をマルチメータ―での計測してみました。入力電圧は電圧レギュレータで落とされるので、ここで消費される電力もありますし、周波数変動型の場合はマイコンに処理させるプログラムに依存する部分もあるので、ここに記す値はおよそのものと思って下さい。また、同じ名前でも、メーカーの実装に依存する部分もあると思います(2種類のUnoの電流が異なることを第12回で示しました)。表中の「アイドリング時」とはdelay()を実行しているとき、(ディープ)スリープ時とは、選択できるスリープモードの中で最も深いものを選んだときです。

 

ボード

アイドリング時 (mA)

(ディープ)スリープ時 (mA)

Arduino Uno (AtMega16u2)

Arduino Uno (CH340)

48

63

33

48

Arduino Mega

54

10

Arduino Due

115

-*

Arduino Nano

24

5

Arduino Pro Mini 3.3V 8MHz

Arduino Pro Mini 5V 16MHz

5

16

1.5

3

Arduino Pro Micro

42

10

ESP32 (‘Dev Module’)

50

11

Seeeduino Xiao

14

1.5

Teensy 4.0

71

-*

*スリープは可能ですが、まだやり方を十分に理解していないので載せていません

 

 上の表は、5.0Vのバッテリーを使って計測したものです。5.0V系のボードにはVcc – GND間に直接、3.3V系のボードにはボード上のレギュレータを介して(Vin端子を通して)印加しています。参考までに、上記を計測するためのスケッチを示します。10秒間アイドリングした後、スリープモードに入ります(ESP32のみ、10秒たつとまたアイドリングに戻ります)。

 

AVR系ボード(Dueを除くArduino)

 

#include <Enerlib.h>

#include <avr/sleep.h>

#include <avr/power.h>

Energy energy;

 

void setup() {}

 

void loop()

{

  delay(10000);

  energy.PowerDown();

}

 

 

ESP32 Dev module

 

void setup() {}

 

void loop()

{

  delay(10000);

  esp_sleep_enable_timer_wakeup(10 * 1000 * 1000);  // wakeup every 10secs

  esp_deep_sleep_start();

}

 

 

Seeeduino Xiao

 

#include <EnergySaving.h>

EnergySaving nrgSave;

 

void setup()

{

  nrgSave.begin(WAKE_EXT_INTERRUPT, 8, dummy);  //standby setup for external interrupts

}

 

void loop()

{

  delay(10000);

  nrgSave.standby();  //now mcu goes in standby mode

}

 

void dummy(void)  //interrupt routine (isn't necessary to execute any tasks in this routine

{

}

 

 さて、本題のArduinoからWindows PCへのデータ送信について説明します。ここではVisual Basic 2010 Expressを使います。2010年版をよく持ってるな!と言われそうですが、2019年版のVisual Studioでも同じことができることは確認しています。VB6はいろいろな素人プログラマーに愛されたIDEでしたが、.NETになってからはBASICのよさがなくなりました。インタープリターなので、三軸制御ソフトを実験動作中に一瞬停止してコードを書き変え、何事もなかったかのように実験を再開する、といったことができたのですが、.NETになってからBASICも中間言語に一度コンパイルされているようで、このようなことができなくなりました。もはやBASICで書く理由もあまりないのですが、惰性でVisual Basicを使っています。

 

ArduinoからのデータをPCに読み込む

 ここでは、Arduinoから1秒に一回、アナログピンA0で読んだ電圧をPCに転送し、表示するプログラムを書きます。もちろん、Arduino IDEのシリアルモニタでも同じことができますが、Visual Studioで読めば、自身の開発プログラムでこの値をいぢることができます(例えば、ファイルとして保存していくなど)。このブログはVSのチュートリアルではないので、ここでは値を表示するだけにします。

 

 Visual Basic (Visual Studio: VS)にはSerialPortというコンポーネントがあります。昔のVB6でいうMS Commに相当するものです。これをフォームに貼り付けます。VB6時代は電話機のアイコンがフォーム上に置かれましたが、最近のVSでは画面の下のほうに置かれます。

 

 

これのプロパティを以下のように設定します(Arduinoがつながれたときに、ポート番号がCOM3だのCOM8だの自動的にふられるので、Arduino IDEあるいはWindowsのデバイスマネージャでこの番号を確認して、ここに入力してください)。基本的にデフォルトのままでこうなっていると思います。名前はSerialPort1となると思うので、このままにします。

 

 

フォームに、ボタンを配置します。プロパティで、NameをbtnOpenClose、TextをOpenとします。

 

 

ダブルクリックして、以下のコードを書きます。これは、プログラム実行時にこのボタンをクリックすると実行される内容を書いていることになります。説明するまでもありませんが、シリアルポートの開閉を行うボタンです。

 

 

Private Sub btnOpenClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOpenClose.Click

 

        If btnOpenClose.Text = "Open" Then

            btnOpenClose.Text = "Close"

            SerialPort1.Open()

            System.Threading.Thread.Sleep(500) '500mSecほど待つ

        Else

            btnOpenClose.Text = "Open"

            SerialPort1.Close() '500mSecほど待つ

            System.Threading.Thread.Sleep(500)

        End If

 

End Sub

 

 

もう一つボタンを配置し、NameをbtnReadStartStop、TextをRead startとし、ダブルクリックで以下のコードを書きます。これはArduinoからの送信を読むためのルーチンを開始・停止するボタンです。

 

 

Private Sub btnReadStartStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReadStartStop.Click

 

        If tmrRead.Enabled = True Then

            btnReadStartStop.Text = "Read start"

            tmrRead.Enabled = False

        Else

            btnReadStartStop.Text = "Read stop"

            tmrRead.Enabled = True

        End If

 

End Sub

 

 

次に、フォームにテキストボックス(NameをtxtReadに)を配置します。そして、タイマーのコンポーネント(NameをtmrReadに)を追加します。ここをダブルクリックし、以下のコードを書きます。これは、タイマーのプロパティ「Enabled」がTrueのとき、Interval(ミリ秒)の値に従って一定間隔で実行されるコードです。プロパティでIntervalの値を1000(1秒)にしておきます。EnabledはFalseのままでいいです(先につくったボタンでTrue/Falseを切り替えます)。

 

 

Private Sub tmrRead_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrRead.Tick

 

        SerialPort1.WriteLine("") 'Arduinoに改行文字を送る

        System.Threading.Thread.Sleep(100) '100mSec待つ

        txtRead.Text = Format(Val(SerialPort1.ReadLine()), "0.0")

 

End Sub

 

 

これでVSのほうは完成で、見てくれはこうなっています。

 

 

ハードウェアのほうは以下のように用意します。ここでは素直に、Arduino UNOのUSBポートとPCをつなぎます。前回説明したように、基板上の結線を通してデジタルピン0と1を使ってPCとUART通信することになります。

 

 

 

Arduinoには以下のスケッチを入れます。ここで、PCに送ったデータがVSのほうで読まれないと、PC側の受信バッファにデータが溜まったままになります。そこにまた送ると、データが重なります(読まれないうちに1023mV、1025mV、と続けて送ってしまうと、10231025となってしまいます)。VSからの「読みました」という信号が来たら新しいデータを送ることにしています。このあたりのやり方はいろいろあると思います。

 

 

 

void setup(void)

{

   Serial.begin(9600);

   while (!Serial) {

    ; // wait for serial port to connect. Needed for native USB port only

   }

}

 

void loop(void)

{

  char dummy[1];

 

  while(!Serial.available()){} //何もPCから受信がない場合は待ち続ける

  while(Serial.available()){

  dummy[0]=Serial.read();} //(改行文字の)受信があった場合、ダミーに読み込む

 

  Serial.println(5000.0*analogRead(A0)/1023); //アナログピンA0の読みを出力

}

 

 

ArduinoがPCにつながれている状態でVSのコードを走らせ(画面上のほうの横向き三角をクリック:ラジカセ世代でない若い衆にこのマークは通用するのか?と思ったが、スマホの動画再生マークもこれか)ます。ここで、フォーム上のOpenボタンをクリックすると、PCのポートが開かれ、Arduinoとシリアル通信の準備ができます。そこで(1秒程度待ってから)Read startボタンを押すとタイマーのEnabledがTrueにされるので、Arduinoとの通信が始まります。毎秒、PCにデータがArduinoから読み込まれ表示されます。

 

 

 

これを応用すれば、例えば第19回で説明したひずみアンプモジュールを用いて、ロードセルや変位計の値をPCに転送し、PC上でファイル記録・グラフ描画などいろいろできます。標準圧密試験などのように、載荷は人力で、データを読むだけ自動化したい、しかしSDカードに記録するだけでなく、Windows上で図化したい、といったときにこの方法が使えます。

 

PCからArduinoにデータを送る

 今度はPCから任意の電圧値を入力し、Arduinoを使って出力します。これにより、例えばEP(電空変換器)を使って、三軸試験のセル圧を調整するなどできます。

 

 ハードウェアは以下のようにします。第9回で説明したように、Arduino UNOにはDAC(デジタル-アナログ変換)がないので、MCP4725を使います。

 

 

 

Arduinoには以下のスケッチを入れます。第9回にならって、MCP4725のライブラリをダウンロードしておいて下さい。

 

 

#include <Adafruit_MCP4725.h>

#include <Wire.h>

 

Adafruit_MCP4725 dac;

 

void setup(void)

{

   Serial.begin(9600);

   while (!Serial) {

    ; // wait for serial port to connect. Needed for native USB port only

   }

 

   dac.begin(0x60); // 0x61 if address pin is connected to Vcc 

}

 

void loop(void)

{

  int cnt1;

  char dac_read[4]="0000";

  int dac_value=0;

 

  while(!Serial.available()){} // PCからの入力を待つ

  delay(300);

 

  cnt1=0;

  while(Serial.available())

  {

    dac_read[cnt1]=Serial.read(); //PCからの入力数字を1文字ずつ読み、dac_read配列に並べていく

    cnt1++;

  }

 

  dac_value=atoi(dac_read); //文字列を整数に

  dac.setVoltage(dac_value, false); //MCP4725に入力

  delay(100);

}

 

 

先ほどのフォームに、ボタン(NameをbtnOutput、TextをWriteに)を配置して以下のコードを書きます(SerialPort開閉のコンポーネントは先ほどつくったものを使い回します)。

 

 

Private Sub btnOutput_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOutput.Click

 

        SerialPort1.WriteLine(Int(4096 * Val(txtWrite.Text) / 5000))

 

End Sub

 

 

今度はテキストボックス(NameをtxtWriteに)を貼ります。先ほど追加したコンポーネントに加えて、このように見えます。

 

 

これで(「Open」ボタンでSerialPortが開いた状態で。また、Arduinoからの入力は止めておいてください。つまり、ボタン表示が「Read stop」になった状態です)、この新しい右下のテキストボックスに電圧値(0-5000mVの範囲:これ以外を入れるとどうなるかは知りません)を書き込み、Writeボタンをクリックすると、MCP4725のGNDとOUTの間に、指定した電圧が生じます。北海道大学の地盤物性学研究室で三軸試験装置のセル圧・ベロフラム圧制御に使っている藤倉コンポジットの電空変換器RT E/P-8-2は、十分にこのやり方で動かせます。

 

 今回はセンサーの話ではなく、センサーで読んだ値をどうするかという話になります。ここまでは、SDカードに記録したり(第5回)、PCやLCDに表示したり(第3・18回)することでArduinoに接続したセンサーの出力値を読んできました。しかし、例えば三軸試験装置などのように、Windows PC上に記録・制御ソフトウェアをつくり、そこにセンサーの値を読み込みたい場合があります。当然、Windows PCにSPIやI2C接続用のデジタルセンサーを直接つなぐことはできません(やろうにも、そもそもどこにつなぐ?って話です)。この場合、以下の図のように、Arduinoなどのマイコン(ボード)を介して、センサーとWindows PCをつなぐことができます。つまり、まずArduinoでデジタル値としてセンサー出力を読み込み、その数値をPCに転送します。

 

 

ここで、マイコンとPCの通信にはUART(Universal Asynchronous Receiver/Transmitter)という非同期シリアル通信を使い、USBに変換してPCに入力します。実は第3回でやったことと同じなのですが、第3回では、Arduino IDEの「シリアルモニタ」に数字が表れるだけで、ただ文字が現れるのを見るだけでした。第30回では、Windows Visual Studioを使って、Arduinoを介してデジタルセンサーの値をPCで読んだり、PCから値を入力してArduinoを介してDAC(デジタル-アナログ変換:第9回)から任意の電圧を出力する方法を紹介します。この第29回では、その前準備としてUART通信に慣れるため、Arduino同士のUART通信を説明をします。

 

ハードウェアシリアルとソフトウェアシリアル

Arduino UNOのデジタルピン0と1を見ると、製品によってはそれぞれRXD・TXD(RX・TXと意味は同じ)と書いてあります。これらのピンはチップ内でUART通信を行うハードウェアに接続されており、「ハードウェアシリアル」と呼ばれます。ArduinoをUSBケーブルでPCと接続したとき、実はこれで通信が行われています。一方、他のデジタルピンを、あたかもUARTハードウェアにつながっているかのようにRXやTXとしてふるまうようにさせるライブラリがArduinoには標準で存在し(SoftwareSerial)、これはソフトウェアによりUARTを可能にしているので、このようなUARTはソフトウェアシリアルといいます。Arduinoのモデルによって、RXとして割り当てられるデジタルピンに制約がある(ピン13はダメ、など)ようで、これについては他のリファレンスを見て下さい。

 

2台のArduino間の通信

先に述べたように、Arduinoを介してWindows PCでデジタルセンサーのデータを読むという本題に入る前に、2台のArduino間でUART通信によりデータを交換することを考えます。このことにそれほどの実用性はないのですが、この前置きには二つの意味があります。一つは、これまでI2CやSPIといったシリアル通信を使うセンサーを紹介してきましたが、UARTによりデータを交換することで機能するモジュール(後の回で紹介するLPWAのSigfox通信モジュールなど)もあるので、その模擬試験としての意味で、もう一つは、Arduino – PCの通信は途中にUSB変換が介在するため、UARTの基本がわかりづらいことです。まあ、面倒ならここは読まなくてもいいです。

 

ここでは、Arduino AのアナログピンA0にかかる電圧値をArduino Aで読み、UARTでArduino Bに送り、Arduino Bに接続したLCDに表示します。もちろん、LCDをArduino Aに直接接続すればArduino Bは要らないわけですが、ここは練習なので。UARTはGNDに加え、送信線TXと受信線RXを使います。ここでポイントなのが、第12回で説明したように、Arduino AのTXとRXは、Arduino BのRXとTXにそれぞれ接続することです。糸電話で、二人とも同じ糸でつながれたコップを口に当てていては、話ができませんよね。TXとRXのイメージは、以下の絵(西村紫6歳に加筆)です。

 

 

 まずは、デジタルピン0と1を使った「ハードウェアシリアル版」です。以下のように接続します。

 

 

スケッチはArduino A・Bそれぞれ以下のようにします。UART通信はバイト単位でデータを送信し、受信もバイト単位となります。つまり、00~FFの256段階の値をやりとりするわけですが、これを送る・読むにはSerial.write()・Serial.read()という関数を使います。これは、例えば「3,056」mVという値を送る場合、値として”3” (0x03), “0” (0x00), “5” (0x05), “6” (0x06)を送ることになります(3056は256より大きいので、それ自体を一つの数値として送ることはできません)。一方、文字列として”3” (ASCIIコード表で51番、つまり0x33), “0” (0x30), “5” (0x35), “6” (0x36)をまとめてやりとりすることがソフトウェア上可能です。この場合、Serial.print()・Serial.readString()という関数を使います(最後に改行文字を加える場合はSerial.println()を使うことは以前にも説明済みです)。どちらのやり方がよいかは場合によるのでしょうが、文字を送りたい場合もあるので、後者のほうが汎用性があるかもしれません。ここでは文字列として通信を行います。なお、シリアルでデータを送る場合、受信バファー(64バイト)に蓄積され、そこから1バイトずつ読まれて消えていくのですが、そこに残っているバイト数はSerial.available()で読めます。つまり、while(!Serial.available())というのは「もう読み込むものがないうちは」という条件で、逆にwhile(Serial.available())というのは「まだ受信バファーに読まれていないデータが残っているうちは」という条件です。

 

Arduino A:

 



void setup()

{

  Serial.begin(9600); // opens serial port, sets data rate to 9600 bps

}

 

void loop()

{

  int ainput; //読み取ったbit数

  float vinput; //bit数を電圧に変換したもの

 

  while(!Serial.available()){} // 受信バファーにArduino Bから送信要求がないうちは何もせずに待つ

 

  String dummy;

  dummy=Serial.readString(); //受信バファーから送信要求を読み捨てる

 

  ainput=analogRead(A0); //ピンA0から電圧をbitとして読む

  vinput=5000.0*ainput/1023; //上記を電圧mVに変換

  Serial.println(vinput); //Arduino Bに送信する

}

 

 

Arduino B:

 



#include <LiquidCrystal_I2C.h>

 

LiquidCrystal_I2C lcd(0x3F,16,2);

 

void setup()

{

  Serial.begin(9600); // opens serial port, sets data rate to 9600 bps

  lcd.init();

  lcd.backlight();

}

 

void loop()

{

  Serial.println(); // Arduino Aに文字(改行のみ)を送る

 

  delay(1000);

 

  lcd.clear();

  lcd.setCursor(0, 0);

  lcd.print(Serial.readString()); // 文字列を受信バファーから読み込み、LCDに表示

}

 

 

 次に、デジタルピン2と3を使った「ソフトウェアシリアル版」です。

 

 

 

スケッチはArduino A・Bそれぞれ以下のようにします。SoftwareSerialというライブラリはArduino IDEに標準でインストールされているはずです。スケッチ内で、どのピンをTX・RXとするのか指定しなくてはならないことに注意が必要です。ハードウェアシリアル版のスケッチからの変更箇所を赤で示します。

 

Arduino A:

 



#include <SoftwareSerial.h>

SoftwareSerial mySerial(2, 3); // RX, TX

 

void setup()

{

  mySerial.begin(9600); // opens serial port, sets data rate to 9600 bps

}

 

void loop()

{

  int ainput; //読み取ったbit数

  float vinput; //bit数を電圧に変換したもの

 

  while(!mySerial.available()){} // 受信バファーにArduino Bから送信要求がないうちは何もせずに待つ

 

  String dummy;

  dummy=mySerial.readString(); //受信バファーから送信要求を読み捨てる

 

  ainput=analogRead(A0); //ピンA0から電圧をbitとして読む

  vinput=5000.0*ainput/1023; //上記を電圧mVに変換

  mySerial.println(vinput); //Arduino Bに送信する

}

 

 

Arduino B:

 



#include <LiquidCrystal_I2C.h>

#include <SoftwareSerial.h>

 

LiquidCrystal_I2C lcd(0x3F,16,2);

SoftwareSerial mySerial(2, 3); // RX, TX

 

void setup()

{

  mySerial.begin(9600); // opens serial port, sets data rate to 9600 bps

  lcd.init();

  lcd.backlight();

}

 

void loop()

{

  mySerial.println(); // Arduino Aに文字(改行のみ)を送る

 

  delay(1000);

 

  lcd.clear();

  lcd.setCursor(0, 0);

  lcd.print(mySerial.readString()); // 文字列を受信バファーから読み込み、LCDに表示

}

 

 

どちらでやっても、LCDに電圧値が表示されると思います。値だけでなく、メッセージなどを文字列として通信することももちろん可能です。

 

 今回でセンサー・モジュール関係は当面、最後になるかもしれません。私の実験室では、他にもより「土質試験室」らしい多くのセンサーを使用していますが、そもそもセンサー自体を紹介するのが目的ではないので、「ひずみゲージ型」「(増幅後)電圧出力型」などにまとめて紹介してきました。今回は3軸加速度センサーADXLを紹介します。

 

 

 この加速度センサーで地震動や風による振動を計測しようとしたことはないので、応答速度等について詳しく気にしたことはありません。3軸(X-Y-Z)なので、重力場でドローンの姿勢を決めるなどに使えるのだと思います。私は、ロガーが倒れていないかなどを遠隔で知るために使っています。傾斜計として使えるかもしれませんが、その用途には別途、傾斜計用のチップを使ったほうが正確だと思います。ちょっと計算したところ、傾き計測の精度は理論上0.1°程度、実際にはもっと粗いかと思います。

データシートはこちら:

https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345-EP.pdf

レンジは可変で、最大で±16Gとのこと。出力はデジタルで、内部のAD変換は最大で13bit(レンジが16Gのとき)。マイコンとの接続にはSPIとI2Cのどちらも使えるようです。ここではI2Cを使います。

 

 以下のように接続します。ここでは、ADXL345をあっちこっち向けやすいように、PCにシリアル出力するのではなく、LCD(第18回)を使って結果表示します。給電も電池で行うことで、完全にPCから切り離して使います(第6回)。

 

 

 

 スケッチには以下を使います。以下のライブラリが必要です。

https://www.arduinolibraries.info/libraries/accelerometer-adxl345

ライブラリに同封されているサンプルコードから、ADXL345の動作に最低限必要な箇所を抜き出し、LCDに表示するコマンドを加えています。LCDのライブラリについては第18回を参照。この他にもSparkFunのライブラリなどもあるようです。

 



#include <Wire.h>

#include <ADXL345.h>

#include <LiquidCrystal_I2C.h>

 

ADXL345 adxl;

LiquidCrystal_I2C lcd(0x3F,16,2);

 

void setup()

{

  /* ----- Start ADXL345 ------ */

  adxl.powerOn();

 

  /* ----- Initialisation of LCD display ------ */

  lcd.init();

  lcd.backlight();

}

 

void loop()

{

  double xyz[3];

  double ax,ay,az;

 

  adxl.getAcceleration(xyz);

  ax = xyz[0];

  ay = xyz[1];

  az = xyz[2];

 

  lcd.clear();

  lcd.setCursor(0, 0);lcd.print("X:"); 

  lcd.setCursor(2, 0);lcd.print(ax);

  lcd.setCursor(8, 0);lcd.print("Y:"); 

  lcd.setCursor(10, 0);lcd.print(ay);

  lcd.setCursor(0, 1);lcd.print("Z:"); 

  lcd.setCursor(2, 1);lcd.print(az); 

  delay(500);

}

 

 

重力場で試してみると・・・

 

 

ADXL345ボードを地面と水平にしたとき、LCDの左下(Zのチャネル)が0.83Gになりました。ちょっとずれているようです。

 

 

ADXL345ボードを地面に垂直(壁と平行)にしたとき、LCDの左上(Xのチャネル)が0.97Gになりました。

 

 

ADXL345ボードを先ほどとは違う向きで地面に垂直(壁と平行)にしたとき、LCDの右上(Yのチャネル)が1.00Gになりました。チャネルによって応答がやや異なるので、正確に使うためには較正が必要になりそうです。

 

 

 業界(学界というべき?)内で、このブログを見てくれている人が少しずつ増えてきてくれているようです。更新もやる気がおきます。

 

 前回はI2C通信でデジタル化されたデータを返す水圧計を紹介しました。ところで、第5回で、I2Cというものについて簡単に説明しました。センサー自体が固有のアドレスを持っているので、異なるセンサーを複数、芋づる式に同じ端子(具体的にはA4 – SDAとA5 – SCL)につないでも、センサーのほうで自分が呼ばれているのかどうか判別して応答してくれるというものです。ところがこの方式の問題として、同じアドレスを持った複数のセンサーをつなげないということがあります(アドレスの競合)。つまり、前回紹介した水圧計MS5837-30BAは、そのままでは2個以上同じマイコン(Arduino)に繋げないことになります。この問題を解決するのがI2Cマルチプレクサーです。

 

 アナログのマルチプレクサーについては第10回で紹介しました。役割としては似ています。要は交換機で、マルチプレクサーという一つのモジュールを通して複数のスレーブ(と呼んではいけないんでしたっけ)と通信できます。ここではTCA9548AというI2Cマルチプレクサーを紹介します。

 

 

写真の2つは同じものです。Adafruit社製のものと(右)、そのジェネリック製品?(左)です。価格はいろいろですが、1000円はしないことが多いです。Adafruitのウェブサイト上では$6.95とあります。見てなんとなくわかるように、ここから複数のI2Cセンサーにつなげるように、SD0, SC0, SD1, SC1, …と端子があります。そして、このマルチプレクサー自体が固有のアドレスを持っており、ArduinoのA4(SDA)、A5(SCL)につなぐのはこのマルチプレクサーだけです。

 

 説明するよりも接続図を見たほうが早いです。水圧計MS5837-30BAを4つつなぎ、それらから圧力・温度を読む例を示します。ここでのポイントとして、

・TCA9548AのI2Cアドレスはデフォルトで0x70ですが、TCA9548A上のA0-A2ピン(Arduino上のA0-A2ではないです)をVINに短絡(つまり、直結)することで、0x70-0x77の範囲で変えられるようです。Adafruitのウェブサイトを読む限り、たとえばA0がVINにつながれた状態をA0=1、つながれていない状態をA0=0とし、A1・A2も同様とすると、アドレスの最後のケタはA0+A1×2+A2×4となるようで、A0=A1=A2=1のとき、アドレスが0x77になります。

・スケッチではsensor[4]とあるように、4つのMS5837-30BAのそれぞれに対し、sensorというインスタンスを生成しています。どれかが初期化に失敗すると”Init failed!”と出ます。第26回にしたがって自作した場合、このシグナルは、作製失敗(MS5837-30BAの基板パッド部がうまくはんだ付けされていないなど)を意味することが多いです。

・特段のライブラリは使っていません。最後のほうにvoid TCA9548A()という関数がありますが、この関数だけでI2Cマルチプレクサー上の接続チャネルを切り替えています。

 

 

 



#include <Wire.h>

#include "MS5837.h"

 

MS5837 sensor[4];

const int CHNUM=4;

 

void setup()

  Serial.begin(9600);

  Wire.begin();

 

  int cnt1;

  for(cnt1=0; cnt1<CHNUM; cnt1++)

  {

    TCA9548A(cnt1);

    if(!sensor[cnt1].init())

    {

      Serial.print("Ch");Serial.print(cnt1);Serial.println(": Init failed!");Serial.println("\n\n\n");

      delay(1000);

    }

    sensor[cnt1].setModel(MS5837::MS5837_30BA);

    sensor[cnt1].setFluidDensity(997); // kg/m^3 (freshwater, 1029 for seawater)

    delay(200);

  }

}

 

void loop()

{

  /* Pressure sensor MS5837 */

  float temperat[CHNUM];

  float pres[CHNUM];

  int cnt1;

 

  for(cnt1=0;cnt1<CHNUM;cnt1++)

  {

    TCA9548A(cnt1);

    sensor[cnt1].read();

    temperat[cnt1]=sensor[cnt1].temperature();

    pres[cnt1]=0.1*sensor[cnt1].pressure(); //kPaに直す

    Serial.print("CH");Serial.print(cnt1);Serial.print(": ");Serial.print(pres[cnt1]); Serial.print(" kPa, ");

    Serial.print(temperat[cnt1]);Serial.println(" oC");

  }

 

  delay(1000);

}

 

void TCA9548A(uint8_t bus)

{

  Wire.beginTransmission(0x70);  // TCA9548A address is 0x70

  Wire.write(1 << bus);          // send byte to select bus

  Wire.endTransmission();

}