気の向くままに辿るIT/ICT/IoT
IoT・電子工作

ArduinoとProcessingでアナログ・デジタル時計(日付・温度付き)

ホーム前へ次へ
Arduinoって?

ArduinoとProcessingでアナログ・デジタル時計(日付・温度付き)

ArduinoとProcessingでアナログ・デジタル時計(日付・温度付き)

ArduinoとProcessingによるアナログ+日付・温度付きデジタル時計
2018/03/10

 『Arduino側の時計、温度情報をシリアル通信で送受信しつつ、Processingでアナログ時計に加え、日付時刻、温度をデジタル表示』してみるページ。

 Lチカ、グラフプロットに続くArduinoとProcessing連携第3弾。

 ProcessingだけでもPCの日付時刻情報を元に時計の実装はできるわけですが、ここでは、Arduinoに接続したRTCモジュールと温度センサモジュールの情報を使い、シリアル通信については、Firmataライブラリを利用せず、ハンドシェイク式としました。

 今回は、公式サイトのProcessing 3+用clock example、前回のグラフプロットをベースとさせて頂き、PCシステム時計からではなく、シリアル通信したArduinoからの日付時刻からアナログ時計とデジタル時計に反映、加えて温度表示、背景画像の挿入と、これらに伴う微調整を行ないました。

回路

 配線も一目瞭然なスケッチは後述するとして回路として必要になるハードウェアは、ArduinoボードとRTCモジュール、温度センサ、ブレッドボードを各1個とジャンパワイヤだけ。(もちろん、USBケーブルとPC、Arduino IDEもしくは代替も要るけど。)

 今回は、RTCモジュールに余っていたDS1302、DS3231と違い、これには温度計機能がないため、温度センサには、37センサーキットに入っていたアナログ・デジタル両対応の温度センサを使い、デジタル値を送信することにしました。

 37センサーキットには、温湿度センサモジュールもありますが、他で使っているため、残り3つある温度センサの内、先のものを使うことにしました。

 必要なソフトウェアは、Arduino IDEか代替とProcessing。

Arduino側のスケッチ

// Arduino側のスケッチ
 
#include <MyRealTimeClock.h>
 
#define TEMP A0
#define NUM 3 // 転送値の数
 
MyRealTimeClock myRTC(6, 7, 8);
const int button = 5;
int inByte; // 受信するシリアル通信の文字列
unsigned int ac_date[1], ac_time[2];
unsigned int ac_yyyy[1];
 
void setup() {
 Serial.begin(9600);
 pinMode(temp, INPUT);
 pinMode(button, INPUT);
 myRTC.setDS1302Time(00, 24, 9, 12 , 10, 3, 2018);
 inByte = 0;
 // 通信を開始
 establishContact();
}
 
void loop() {
 myRTC.updateTime();
 // もしProcessingから何か文字を受けとったら
 if (Serial.available() > 0) {
  // 受信した文字列を読み込み
  inByte = Serial.read();
  // アナログセンサーの値を計測
  // コンマ区切りでセンサーの値を送出
  int iyyyy = myRTC.year;
  ac_yyyy[0] = iyyyy / 256;
  ac_yyyy[1] = iyyyy % 256;
  for (int i=0;i<2;i++) {
   Serial.print(ac_yyyy[i]);
   Serial.print(",");
  }
  ac_date[0] = myRTC.month;
  ac_date[1] = myRTC.dayofmonth;
  for (int i=0;i<2;i++) {
   Serial.print(ac_date[i]);
   Serial.print(",");
  }
  ac_time[0] = int(myRTC.hours);
  ac_time[1] = int(myRTC.minutes);
  ac_time[2] = int(myRTC.seconds);
  for (int i=0;i<3;i++) {
   Serial.print(ac_time[i]);
   Serial.print(",");
  }
  float ftempC = analogRead(TEMP);
  int itempC = ftempC;
  Serial.print(itempC);
  
  Serial.println();
 }
}
 
void establishContact() {
 // Processingから何か文字が送られてくるのを待つ
 while (Serial.available() <= 0) {
  // 初期化用の文字列
  Serial.println("0,0");
  delay(300);
 }
}

 Arduinoのスケッチは、後段リンクにもある前回のArduinoとProcessingによるグラフプロットとDS1302購入時に動作確認時に使わせて頂いたライブラリをベースとさせて頂きました。

 int型のyear()をシリアル送受信するにあたり、変に混乱したくなく、ビットシフト(演算)は回避したいなと思っていたら、ビットシフトを説明しつつ、Arduino側で小数点以下切り捨てる整数型の変数に0-1023のアナログ値/256(仮にA)アナログ値%256(仮にB)の結果を個別に入れて送信、Processing側で整数値としてA*256+Bのように計算すれば同じことができるというアナログ値分割情報を公開されていたので拝借、年の値に流用。

 当初、Arduino側で日付時刻の表示用加工([/]や[:])したものを1セットとして一度に送ろうと考えていましたが、結果、加工はProcessing側で行なうことにしました。

Processing側のスケッチ

// Processing側のスケッチ
 
import processing.serial.*;
 
Serial myPort; // シリアルポート
int MARGIN = 20;
int NUM = 7;
int iyyyy;
int itempC;
int ihh, imm, iss;
String[] rec_data = new String[NUM];
String s_yyyy = new String("");
String s_mm = new String("");
String s_dd = new String("");
String s_hh = new String("");
String s_min = new String("");
String s_ss = new String("");
String symd = new String("");
String shms = new String("");
String stempC = new String("");
PImage bg;
 
int cx, cy;
float secondsRadius;
float minutesRadius;
float hoursRadius;
float clockDiameter;
 
void setup() {
 //画面設定
 size(300,300);
 stroke(255);
 smooth();
 frameRate(60);
 bg = loadImage("/path/to/example.jpg");
 // シリアルポートのリスト取得
 println(Serial.list());
 // ポート番号とスピードを指定してシリアルポートをオープン
 myPort = new Serial(this, Serial.list()[4], 9600);
 println(Serial.list()[4]);
 // 改行コード(\n)が受信されるまで、シリアルメッセージを受けつづける
 myPort.bufferUntil('\n');
 textFont(createFont("IPAGothic", 20));
 
 int radius = min(width, height) / 2;
 secondsRadius = radius * 0.72;
 minutesRadius = radius * 0.60;
 hoursRadius = radius * 0.50;
 clockDiameter = radius * 1.8;
 
 cx = width / 2;
 cy = height / 2;
}
 
void draw() {
 // シリアルバッファーを読込み
 String myString = myPort.readStringUntil('\n');
 // 空白文字など余計な情報を消去
 myString = trim(myString);
 
 if(myPort.available() > 0 && myPort.available() < 7){
  if(myString != null && myString.length() != 0){
   rec_data = split(myString, ',');
   iyyyy = Integer.valueOf(rec_data[0])*256 + Integer.valueOf(rec_data[1]);
   s_yyyy = str(iyyyy);
   if (Integer.valueOf(rec_data[2]) < 10){ s_mm = "0"+rec_data[2]; } else { s_mm = rec_data[2]; }
   if (Integer.valueOf(rec_data[3]) < 10){ s_dd = "0"+rec_data[3]; } else { s_dd = rec_data[3]; }
   symd = s_yyyy + "/" + s_mm + "/" + s_dd;
   println(symd);
   ihh = Integer.valueOf(rec_data[4]);
   imm = Integer.valueOf(rec_data[5]);
   iss = Integer.valueOf(rec_data[6]);
   if (Integer.valueOf(rec_data[4]) < 10){ s_hh = "0"+rec_data[4]; } else { s_hh = rec_data[4]; }
   if (Integer.valueOf(rec_data[5]) < 10){ s_min = "0"+rec_data[5]; } else { s_min = rec_data[5]; }
   if (Integer.valueOf(rec_data[6]) < 10){ s_ss = "0"+rec_data[6]; } else { s_ss = rec_data[6]; }
   shms = s_hh + ":" + s_min + ":" + s_ss;
//   println(shms);
   stempC = rec_data[7]+"℃";
   println(stempC);
  }
 }
 // 読込みが完了したら、次の情報を要求
 myPort.write("A");
 
 // Draw the clock background
 background(bg);
// fill(80);
// fill(180, 80, 50);
// noStroke();
// ellipse(cx, cy, clockDiameter, clockDiameter);
 
 // Angles for sin() and cos() start at 3 o'clock;
 // subtract HALF_PI to make them start at the top
 float s = map(iss, 0, 60, 0, TWO_PI) - HALF_PI;
 float m = map(imm + norm(second(), 0, 60), 0, 60, 0, TWO_PI) - HALF_PI;
 float h = map(ihh + norm(minute(), 0, 60), 0, 24, 0, TWO_PI * 2) - HALF_PI;
 
 
 // Draw the hands of the clock
 stroke(255);
// stroke(100);
 strokeWeight(1);
 line(cx, cy, cx + cos(s) * secondsRadius, cy + sin(s) * secondsRadius);
 strokeWeight(2);
 line(cx, cy, cx + cos(m) * minutesRadius, cy + sin(m) * minutesRadius);
 strokeWeight(4);
 line(cx, cy, cx + cos(h) * hoursRadius, cy + sin(h) * hoursRadius);
 
 // Draw the minute ticks
 strokeWeight(2);
 beginShape(POINTS);
 for (int a = 0; a < 360; a+=6) {
  float angle = radians(a);
  float x = cx + cos(angle) * secondsRadius;
  float y = cy + sin(angle) * secondsRadius;
  vertex(x, y);
 }
 
 fill(255);
 text(symd, 100, 200);
 text(shms, 110, 220);
 text(stempC, 130, 240);
 
 endShape();
}

 Processing側のスケッチは、公式サイトのProcessing 3+用アナログ風時計サンプルと、やはり、後段リンクにもある前回のArduinoとProcessingによるグラフプロットをベースとさせて頂きました。

 全体的に変数宣言に統一感がなかったりしますが、気にしないことにし、当然、背景画像も使えるよね?と思っていたことから、[PImage]型変数を使い、とりあえず、GIMPで作った背景画像を設定、日付時刻データは細切れで受信し、[/]や[:]を入れる加工は、Processing側で行なうことにしました。

 尚、背景画像に使う画像の保存形式は.jpgはいけましたが、.pngはダメでした([2018/03/22:訂正・追記]RGB LEDデモでは.pngでもいけた)、また、size()指定したポップアップ画面のサイズと画像サイズはピッタリ同じにする必要がありました。

 もし、このスケッチを使う場合、針の色や文字色が白なので、針や文字の色を変更するか、濃いめの背景色を設定するか、これらを識別できる色合いの背景画像を用意するなどしないと何も見えなくなるので注意。

 ここで対象になるのは、温度の[℃]のみですが、Processingにおいてスケッチ上やポップアップ画面上で日本語表示する場合には、[ファイル] => [設定...]で[言語:]を[日本語]、[エディターとコンソールのフォント:]を[IPAゴシック]など日本語表示対応フォントに設定しておく必要がありました。

 今回のハマりポイントは、JavaやProcessingではお馴染みらしき[NullPointerException]、結果、String型宣言した変数を初期化していなかったことが原因と判明、初期化で解決。

 ちなみにArduinoとProcessingでシリアル通信、送受信データを参照しているスケッチにおいて、受信データの格納に配列を使っている場合、配列要素数が少なかったり、多かったりした場合や、Arduino側(シリアルポート)の準備ができていないとか、指定したシリアルポートが異なる状態でProcessingのスケッチを実行した場合もエラーの1つとして[NullPointerException]は起こり得るようです。

備考

 実用的とは言えませんが、タイムリーな日付時刻もシリアル通信で十分送受信できることがわかりました。

ホーム前へ次へ