勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

Zoom用美顔照明ー眩しくない簡単調光器 (後ろに少し追記2020.11.05)

f:id:a-tomi:20201027111732j:plain

小型コントローラーで連続調光をするオンライン会議用の顔照明です。ディスプレイ上のカメラの両脇にセットすることで、顔に影を作らずに照明します。

市販ではこういうのが見つからないので、そこらへんの材料で自作したものですが^^; レンズのそばにセットできるので顔に影がでないわけです。かなり明るくもできますが、Zoomの場合は、ほのかな明るさでも十分です。

10月は例年少し忙しいので、投稿がとどこおりがちなのですがそうはいきません。今回はごく簡単な電子工作について説明したいと思います。

今や普通になったオンライン会議では、自分の元気な表情を見せることが自分のためでなく他の参加者の気持ちのために大事ですね。ところが顔が暗くなって見えたり影になっている参加者が多いのに気づきます。例えば次の写真で顔の色だけをご覧ください。せっかく背景がよくても顔が暗いと今一つになりがち。

f:id:a-tomi:20201027112835j:plain

顔の明るさは会議全体の雰囲気にも影響しそうです。

スマホの場合は市販のリングライトスマホの周りを取り囲む)を使えばよいですが、デスクトップのディスプレイからではそうはいきません。大体32インチ以内なら、今回の照明でずいぶん改善されますが、より大きいディスプレイでは画面の中心からレンズまでだいぶ遠いので一工夫が要ります。それについては後日書きたすことにします。

なにしろ普通の部屋では、ほぼ例外なく照明が天井のほうにあるため、禿頭をピカリと強調したり、顔の下半分に影を作ったりし勝ちですね。

この電子工作は、このブログでは珍しくマイコンなど使わない簡単なもので、照明の光源にはLEDモジュールを使います。クルマの車内照明用に12V対応の白色LEDモジュールがネットでたくさん売られていますが、とても明るく超低消費電力です。次の写真のものは昨年eBayで購入、1個1ドルでした。

f:id:a-tomi:20201027132941j:plain

これは5センチx2センチの大きさで36個のLEDエレメントが配置されています。

測定すると12Vでは100mAですが明るすぎて直視できません。11Vでも十分すぎる明るさで電流は50mAです。もう少し低くてちょうど良い感じすが、この場合は0.5Wほどなので放熱をほとんど気にしなくてすみます。

ネットにはいろんな種類がありますが、ほぼ同様と思われます。使う場合はもちろん事前に測る必要はあるでしょう。

f:id:a-tomi:20201027133941j:plain

 

自由に明るさを変えるための調光器はごく単純な次の回路としました。

f:id:a-tomi:20201027134416j:plain

テストするとこれで計算通りになります。

15V入力は秋月の超小型ACアダプターで電流は十分です。

LEDモジュール複数を並列につないで明るさを比較すると、低い電圧の時に個体間のばらつきが少しありますが、どれでも特に問題ない感じです。

f:id:a-tomi:20201027134716j:plain

これを入れるケースは、ディスプレイの上にセットするわけなので脚部に少し工夫を要します。ここでは、不要となった古いWebカメラの脚を流用しました。

f:id:a-tomi:20201027135209j:plain

10年以上前のものなので、ディスプレイにひっかける足の前部が深さ12mmもあるため、現代のディスプレイの縁幅を超えて画面を隠してしまいます。

よって次のようにカットします。

f:id:a-tomi:20201027135423j:plain

黒い丸いものは、カメラが自由に回転したり角度を変えられるようにするだいじな部品です。

そして適当なプラ容器にねじ止めしてセットします。

f:id:a-tomi:20201027135725j:plain

ちなみにこの容器は百均で4個百円だった小さな食品容器です。念のために天端に5mmの放熱穴をあけてあります。一番明るくしない場合はほぼ必要ない感じですが、その穴から取り付けねじを締めることができます。

そして、デフューザーとして蓋の内側にトレーシングペーパーを二つ折りにして小さな両面テープで中央部に張り付けてあります。これにより眼の負担が軽減されます。短時間でうまくできました、我ながら^^

調光器は秋月の一番小さなプラケースに収容するため、基板は60mmx30mmにします。また深さがないので、1A用の可変電圧レギュレータはまげて取り付ける必要がありますし、これに大型の可変抵抗器をセットしなければなりません。

よって、回路図自体は同じなのですが次のような配線図(上からみた図です)とします。

f:id:a-tomi:20201027140709j:plain

これを次のような60mmx30mmの基板にします。銅箔面なので左右が逆ですが。

f:id:a-tomi:20201027140905j:plain

CNCルーターで削って出来上がり。

f:id:a-tomi:20201027141009j:plain

表からは次。

f:id:a-tomi:20201027141052j:plain

ずいぶん小さく組みあがりました。

f:id:a-tomi:20201027141313j:plain

そして32インチディスプレイにとりつけ。上の棚にぎりぎりですが、幸いうまく収まりました。

f:id:a-tomi:20201027141432j:plain

カメラのレンズからの距離を同じあたりにセットしています。

早速PCをオンにします(百ボルト電源はPC元電源からとっています)。

f:id:a-tomi:20201027141942j:plain

いい感じですね。

コントローラーもごく小さいので置き場所に困りません。

f:id:a-tomi:20201027142053j:plain

このPCからは過去にオンライン会議には出てませんが今後使う予定。早速Zoomで実験。

おお!これはいい!

顔に影がなく明るく映ります!頭の輝きが強調されませんでさらによい^^;

小さなプーさんを出してみます。

f:id:a-tomi:20201027142525j:plain

これならバーチャル背景にまけませんね。第一、頭や額がピカっとしてない(笑)

というわけで400円ほどのこの工作はメデタシメデタシ。。。

 

なお、もしさらに大きなディスプレイを使う場合は、照明を上にセットするだけでは不十分なことがあります。また、メガネの反射をどうするかなどの留意点もありますね。時間のある時に記事の後ろに書き足したいと思います。

とにかく、こういう工夫をして出ると会議が明るくなりますね。プロの面々がでるときはみなさん顔を明るくして表情豊かですね。それが他の参加者のためなのですから。次の例のように。

f:id:a-tomi:20201027143655j:plain

 

海外では、オンライン会議で自分の顔の照明にずいぶん気を付けて参加される方も多いようです。たとえばですが、、

youtu.be

では今回はこのへんで。もし皆様のお役に立てば幸いです。

 

2020.11.5追記:大画面の場合の勝手なやりかたと、メガネの反射改善について、少し書き足させていただきます。

大画面ですとカメラの位置が中心からだいぶはずれますし、照明もなかなか難しいところです。次の例は事務所から43インチ・ディスプレイで出る場合の、勝手な照明の方法です。主にディスプレイ背面の大きな白い壁の反射による間接照明のつもりですが、さらに工夫するとだいぶ改善されます。

f:id:a-tomi:20201105150159j:plain

この43インチ・ディスプレイは4K画素なので精細で、ウィンドウがあちこち配置できて楽です。しかし、できるだけカメラに近い目線となるようにするために、Zoomウィンドウや共有する際のウィンドウの位置を、カメラに近い上のほうにもっていくようにしています。そして余分に「照明ウィンドウ」を表示しておきます。白い画面ならなんでもOK。ついでに、大事な仕事の場合にはこの左側にある別PCマルチディスプレイである27インチも、「照明ウィンドウ」として動員。

しかし、これらだけではどうしても下からの照明が少し不足がち。そこで、左下に今回の記事と同様の眩しくない程度の照明をおいて改善をすることができました。実は、上にご紹介した工作よりも数か月前に試作して使っているものですが。回路は同じですがユニバーサル基板にちょこっと作ったものです。ケースは百均のもう少しだけ大きい食品容器(2個百円なり^^)です。

f:id:a-tomi:20201105151728j:plain

全体で1W程度は発熱するわけなので、アルミ板にはりつけてあります。また、上に放熱穴をあけましたが、実際は熱くはなりませんからそこまでしなくともよいかも。

蓋の内側には同様にトレーシングペーパーを二つ折りにしてはりつけてあるので、柔らかい白色照明となっています。これで顔に影がなくなりましたが机上の反射も役立っているようです。

f:id:a-tomi:20201105152043j:plain

 

さて、カメラの近くから照明するとメガネをかけたときに反射が気になるわけですが、こちらの照明方法では殆ど気になりません。カメラの両脇につけるパターンの場合には、カメラからある程度は左右に離すことでそれなりに改善されるのに気づきました。

以上、追加の記述でした。

 

©2020 Akira Tominaga, All rights reserved.

勝手なリモート・サーボ?!(ステッパーとESP32)

f:id:a-tomi:20200928101554g:plain

ESP32の実験ついでにステッパーで遠隔サーボを作ってみました。ここではSP32 Dev Board (38ピン)を2つ使い、片側(Slave)で位置の信号を作り、もう一方(Master)へ送り、そこでステッパーをサーボとして動かしています。上の動画では両方をすぐ隣においてあります^^ が、部屋の中ではかなり遠くでも問題ありません。どこまで届くかは実験してませんが。

これを前にはWiFiで苦労して作りましたが、こういう用途ではBlueToothがずいぶん楽だと気付きましたので、Bluetooth SPP(Serial Port Profile)にしました。送受の両方のESP32プログラムはこの記事の最後につけておきます。

Arduino IDEでプログラミングしましたが、ESP32をArduino IDEで開発する際のソフトウェア導入方法については、次の記事の中に簡単に書いておきました。

ESP32のアクセスポイント (つまりスタンドアローンWiFi-LAN) - 勝手な電子工作・・

 なお、強力なESP32Devlopment Boardは海外ネットではずいぶん安価になり、この例で使ったのは1つあたり3.6ドルでした。

 

まず、マスター側の結線は次のとおりです。毎度汚い手書きですみませんが。

f:id:a-tomi:20200928103519j:plain

これをブレッドボードに次のように試作しました。

f:id:a-tomi:20200928103624j:plain

ごらんのとおりへんなブレッドボードですが、真ん中にある普通のブレッドボードの外側上下に、もう1枚のブレッドボードを2つに割ったものをはめ込んだだけ。ブレッドボードの受け金具のはいってないところで自由に切ればよいわけ。金鋸でもバンドソーでも何でも簡単に切ることができますから^^ うまく作るコツは、組み合わせの凹凸部がはまるように考えるだけです。あるいは切らずに組み合わせるだけでOKですね(ただし余分な面積が増える)。

次にスレーブですが、これはもう簡単そのもの、結線図というほどのものではありません。この場合、もしVRにセンターノッチがはいっているとサーボコントロールに使いやすいですが、特に必要はありません。

f:id:a-tomi:20200928104404j:plain

これはブレッドボード(1列6ピン)1枚に試作。

f:id:a-tomi:20200928104513j:plain

電源はUSB供給でもいいですが、持ち歩きのリモコンにする場合は単三電池3本(4.5V)をV5端子に入れるとよいです。

 

ステッパーをサーボとして使うときに一番難しいのは、回転の絶対位置を何で決めるかということ。リミッターとかセンサーとかなんらかの「とりつくしま」を付ければごく簡単なことなのですが、そこをつけずにやりたいもの。実は、A4988コントローラーなどのリセット機能を用いてHome positionを決め、後は決まったステップ数単位で動かせば再現性をもってできます。前に次のレーザーカッターの記事に書いたとおりです。

オリジナル・レーザープロッター その2 Arduinoでの刻印プログラム - 勝手な電子工作・・

今回は荒っぽい実験でとくにそうはしてませんがほとんど狂いません。

f:id:a-tomi:20200928104838j:plain

我ながらこれはなかなか面白い実験でした^^

なお、サーボの位置を細かく決めるには、スレーブ側のVRをCoarse(右の10Kオーム)+Fine(左の500オーム)と組み合わせると微調整が効きます。VRのケースは秋月で売っている一番小さなプラケースです。また、A4988で分周比の設定ができますので、角度はさらに細かい単位にすることができます。

f:id:a-tomi:20200928105136j:plain

このVRは2.4GHz電波の近傍にさらされるわけなので、どちらの方法でもVRへのワイヤはシールドワイヤが望ましいかも。そうしないと電波を拾ってわずかながら不安定になる感じで、その場合のセンター位置(511)の受信例が次。

f:id:a-tomi:20200928105946j:plain

あるいはVRの抵抗値を下げて例えば10Kオームを2Kオームにすればノイズは拾いにくくなるし、また、高周波をカットするようにC(キャパシター)をADC端子間に入れる手もよいでしょうね。

 

最後にそれぞれのプログラムを添付しますが、Arduino IDEからESP32 Dev Boardに書き込みます。

まずスレーブ側で、とても小さなプログラムです。

/* ************************************************************************
  ESP32 Serial communication with Bluetooth-SPP as a Slave
    Example; ADC data sending to master's remote-Servo
      Initial version Sept.26, 2020   (c)Akira Tominaga
 *************************************************************************/
#include "BluetoothSerial.h"
BluetoothSerial btSPP;
int ADpin = 36;                   // ADC pin
uint16_t adVal = 0;               // ADC value
byte mH;                          // High byte for ADC measured value
byte mL;                          // Low byte for ADC measured value

void setup() { // ***** ESP32 setup *****
  Serial.begin(9600);
  btSPP.begin("HOGEHOGE-Slave-xx"); //Bluetooth name of this slave
  Serial.println("");             // NewLine
  uint8_t mA[6];                  // for blue-tooth MAC adr
  esp_read_mac(mA, ESP_MAC_BT);   // get my blue-tooh MAC adr
  Serial.printf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", mA[0], mA[1], mA[2], mA[3], mA[4], mA[5]);
  // upper result to be used by master
  Serial.println("btSPP started, pair with master.");
  // for AD conversion
  pinMode(ADpin, INPUT);
  adcAttachPin(ADpin);
  analogReadResolution(10);       // ADC with 10 bits (from 0 to 1023)
  delay(5);
}

void loop() { // ***** ESP32 Loop *****
  while (btSPP.available() < 3) {} // wait for master message
  String s = btSPP.readString();   // read whole message
  Serial.println(s);
  adVal = analogRead(ADpin);
  Serial.println(adVal);
  mH = adVal >> 8;
  mL = adVal - mH * 256;
  Serial.print("mH=");  Serial.print(mH, HEX);
  Serial.print(" mL=");  Serial.println(mL, HEX);
  btSPP.write(mH);              // send measured-high byte
  btSPP.write(mL);              // send measured-low byte
}

 

次にマスター側です。これもあまり大きくありません。

 

/* ************************************************************************
  Serial communication with Bluetooth - Serial Port Profile (Master)
      Example: Stepper as a remote-Servo
      Initial version Sept.26, 2020  (c)Akira Tominaga
 *************************************************************************/
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
uint8_t Adr[6]  = {0x7C, 0x9E, 0xBD, 0xF4, 0xDB, 0x56}; // slave adr
String name = "HOGEHOGExxx";
byte mH;                            // high byte of measured ADC at slave 
byte mL;                            // low byte of measured ADC at slave
uint16_t adVal;                     // ADC value reproduced from mH&mL
#define adVmax 1023                 // 10bits-ADC max value
#define adVmid 512                  // 10 bits-ADC center value
uint16_t adVsv = adVmid;            // ADC value saved last time
// for stepper with A4988
int16_t Move;                       // moving-steps for stepper
const char Roger[] = "Rgr";         // ready message to slave
uint16_t vRot;                      // value for rotation steps (oneRot=200)
gpio_num_t Dir = GPIO_NUM_33;       // Direction for A4988
gpio_num_t Step = GPIO_NUM_25;      // Step for A4988
gpio_num_t Nsleep = GPIO_NUM_26;    // !sleep for A4988
gpio_num_t Nreset = GPIO_NUM_27;    // !reset for A4988
#define tmStp 2500                  // stepper phase timing in μS
#define oneRot 200                  // steps req'd for one rotation (360°)
#define sMax  150.0                 // max steps as a servo (270°)

void setup() { // ***** Arduino (ESP32) Setup *****
  pinMode(Nsleep, OUTPUT);
  sSleep();                         // sleep A4988 to begin with 
  pinMode(Dir, OUTPUT);
  pinMode(Step, OUTPUT);
  digitalWrite(Step, LOW);
  Serial.begin(9600);
  pinMode(Nreset, OUTPUT);
  sReset();                         // set stepper at home position
  
  // Bluetooth slave shoud be on, first
  SerialBT.begin(name, true);
  Serial.println("master started");
  if (SerialBT.connect(Adr)) {
    ;     // connect with slave adr
    Serial.println("Connected!");
  } else {
    while (!SerialBT.connected(10000)) {
      Serial.println("Failed to connect!");
    }
  }
  delay(5);
  for (int n = 0; n < 3; n++) {
    SerialBT.write(Roger[n]);              // send ready to slave
  }
}

void loop() { // ***** Arduino (ESP32) Loop *****
  //Serial.println("Loop");
  while (SerialBT.available() < 2) {}     // wait for 2 bytes from slave
  mH = SerialBT.read();
  mL = SerialBT.read();
  adVal = mH * 256 + mL;
  //Serial.print(" adVal=");
  Serial.println(adVal);
  Move = adVal - adVsv;
  adVsv = adVal;

  if (Move > 0) {
    vRot = (float)(Move * sMax / adVmax);
    //Serial.print("Positive ");
    //Serial.println(vRot);
    sP(vRot);
  }
  if (Move < 0) {
    Move = -Move;
    vRot = (float)(Move * sMax / adVmax);
    //Serial.print("Negative ");
    //Serial.println(vRot);
    sN(vRot);
  }
  if (Move = 0) {
    // do nothing
  }
  for (int n = 0; n < 3; n++) {
    SerialBT.write(Roger[n]);              // send ready to slave
  }
}

/**********************************************
      User defined functions
 **********************************************/
//   *** sP(n-rotations) *** move stepper positive
void sP(int nRot) {
  digitalWrite(Dir, LOW);
  mvS(nRot, tmStp);
}
//  *** sN(n-rotations) *** move steppe negative
void sN (int nRot) {
  digitalWrite(Dir, HIGH);
  mvS(nRot, tmStp);
}
//  *** mvS(nRot, ndelay) *** move stepper
void mvS(int nRot, long nDelay) {
  sWake();          // wake-up A4988
  for (int l = 0; l < nRot; l++) {
    digitalWrite(Step, HIGH);
    delayMicroseconds(nDelay);
    digitalWrite(Step, LOW);
    delayMicroseconds(nDelay);
  }
  sSleep();         // Sleep A4988 to avoid heat
}

// *** sReset() *** Reset at current position & sleep
void sReset() {
  sWake();
  digitalWrite(Nreset, LOW);
  delay(10);
  digitalWrite(Nreset, HIGH);
  //delay(1);
  sSleep();
}
// *** sSleep() *** Sleep stepper to avoid heat
void sSleep(void) {
  delay(2);           // wait 2mS until end of inertia 
digitalWrite(Nsleep, LOW); } // *** sWake() *** Wake-up stepper void sWake(void) { digitalWrite(Nsleep, HIGH); delay(2); // wait for charge-pump full } // end of program

ステッパー停止時には、いちいちA4988をSleep させていますが、こうするとA4988もステッパーも熱をもたせずにすみます(逆に停止時にSleepさせず放置すると直流が流れるのですぐ過熱しますからご注意)。このあたりは、前に次の記事などに詳しく書きましたのでご興味のある方は次をご覧ください。

a-tomi.hatenablog.com

 

以上、自分のまとめとして書いた記事ですが、これはなかなか楽しい実験でした。この応用が皆様の何かのお役に立てば幸いです。

 

 

©2020 Akira Tominaga, All rights reserved.

 

ESP32のアクセスポイント (つまりスタンドアローンWiFi-LAN)

f:id:a-tomi:20200927201624g:plain

この週末にESP32で2つの実験をしたので、簡単にまとめます。プログラムは記事の後ろのほうにつけておきます。

1つ目はスタンドアロンーンのWeb.サーバー(つまり既存のWiFi LANを使わないアクセスポイント)。2つ目は勝手な「リモート・サーボ」をESPとステッパーで作ってみたので紹介。

この記事は、まず1つ目の独立WiFiサーバーです。ありきたりのようにも見えますが、これが便利だと思う勝手な理由は次の3つ。

①ふつうは立ち上げ時にメインのWiFi-LANにつないで、その中のIPアドレスとして動かすのですが、場合によりいちいちMACアドレスフィルタリングを通さないといけません。その手間がたいへん(MACアドレスフィルタリングを使ってない場合は手間はないですがSecurity心配ですね)。

②AlexaやGoogleHomeなどがたくさん加わり、WiFiルーターによっては数の限界に近づいています。そして電子工作を楽しむ人にとっては、とにかくESPマイコンの最近の低価格化はありがたい事でESP8266Development Boardに至っては、海外ネットで今や200円台で買えますから、何にでも使うわけでどんどん増えるわけ^^;

③既存のWiFi-LANの中で動かすと、場所を変える場合には、最初の接続SSIDの変更または追加を要する。スタンドアローンのアクセスポイントにすれば、その手間がいらない、つまりプログラムをいじらなくてよい。

 

ここで使うESP32DevelopmentBoardは次の38ピンのもので、次のように結線。

f:id:a-tomi:20200927204436j:plain

電源はUSBからの5Vでよいですが、自由に持ち回るときは上のように電池をつなぎます。最近のESP development Boardは比較的消費電流が小さくなりました(とはいえ実行中は100mA程度)。

ブレッドボードは、普通の1列5ピンのは使いにくいので6ピン配置のもの。これは秋月でも売っています。

f:id:a-tomi:20200927204901j:plain

とはいっても、実はどんなブレッドボードでも工夫すれば自由に使えます。そのことは次回の記事で書くことにします。

なお、Arduino-IDEでESP32ボードを開発するためには、ESP32用のボードマネジャーをArduino IDEへインストールする必要があります。導入法はネットの色々なところに出ていますが、Windowsでの現時点の入れ方を簡単に書いておきます。

====================

・ESP32をArduino-IDEで開発するためのソフト導入

Arduino IDE自体は次のアドレスから導入します。https://www.arduino.cc/

そしてIDEを開いたら、上のメニューバーにある「ファイル」→「環境設定」をクリックします。

環境設定画面の下のほうにある「追加のボードマネジャのURL欄」にボードマネジャーのあるURLアドレスを入力しますが、ESP32の場合は次を入力。

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

次に、IDE本体のメニューバーで「ツール」→「ボード」→「ボードマネジャ」と進むと使えるボードマネジャーのメニューが表示されるので、その中から

esp32by Espressif Systemsと表示されている欄内の右のほうにある「インストール」をクリックすれば、導入が始まり、しばらくすると終わります。

====================

IDEで後述するプログラムを作って、「ツール」で「ESP32 Dev Module」を選んで書き込む際に、画面の下の欄に赤色で書き込み開始の合図「・・・___」が始まったらESP32開発ボード上でUSB端子の隣にあるBootボタンを押すと書き込まれます。Arduinoと違って書き込み量が多いので少し時間がかかります。

そうすると「勝手なWiFi-LANサーバー」が立ち上がり、勝手に決めたアドレスが独立のアクセスポイントとなります。PCやスマホなどでこれを選んで(PWはもちろん入れて)接続します。もちろんPCやスマホでなく、工作用には他のESPからでもよいわけですが、、(あたりまえか)。

ここでのプログラム(この記事の後ろのほうに載せます)ではIPアドレス192.168.32.2を指定しています。PCやスマホならインターネットブラウザーでそこをアクセスすると次の画面。PCでも同じです。簡単な実験のためにテキストだけのページですが。

そしてリンクをタッチすれば指定LEDが点きます。

f:id:a-tomi:20200927205313j:plain

この実験装置全体の様子は次です。下の写真ではサーバーの電源をUSBで供給しています。

f:id:a-tomi:20200927205638j:plain

このサーバーの電源としては、前述のように電池(単三電池を3本直列)をつかっても問題ありません。そうすれば動くものやロボカーなどにも便利です。

さて、こうやって工作室に勝手なWiFi-LANをどんどん作るとどうなるか?

PCやスマホSSIDがじゃんじゃん表示されますし、運用場所によっては近所からも丸見えですからあまり変なSSID名を使わないのがいいですね^^;

f:id:a-tomi:20200927210049j:plain

 

最後にこの実験用に作ったプログラムをつけておきます。

/********************************************************
 * Independent Access-Point - ESP32 simple example  
 *        Initial version V.00 Sept.26 2020
 *                                   by Akira Tominaga
********************************************************/
#include "WiFi.h"
const char ssid[]  = "HOGEHOGE-00X"; 	// *** set any ssid ***
const char pass[] = "hoge00xpw";	// *** set any pw   ***
const IPAddress ip(192, 168, 32, 2); 	// *** set any addr ***
const IPAddress subnet(255, 255, 255, 0); 
WiFiServer server(80);
// ESP32 GPIO pin definitions (should not be #define)
gpio_num_t Red = GPIO_NUM_21;               // Red LED
gpio_num_t Green = GPIO_NUM_22;             // Green LED
gpio_num_t Blue = GPIO_NUM_23;              // Blue LED

void setup() { // ***** Arduino (ESP32) Setup *****
  Serial.begin(9600);
  pinMode(Red, OUTPUT);                     // Red pin as output
  digitalWrite(Red, LOW);
  pinMode(Green, OUTPUT);                   // Green pin as output
  digitalWrite(Green, LOW);
  pinMode(Blue, OUTPUT);                    // Blue pin as output
  digitalWrite(Blue, LOW);
  delay(100);

  WiFi.softAP(ssid, pass);
  delay(100);
  WiFi.softAPConfig(ip, ip, subnet);
  IPAddress myIP = WiFi.softAPIP();
  server.begin();
  Serial.print("SSID= ");
  Serial.println(ssid);
  Serial.print("Fixed IP addr= ");
  Serial.println(myIP);
  Serial.println("Server starting!");
}

void loop() { // ***** Arduino (ESP32) Loop *****
  WiFiClient client = server.available();
  if (client) {                             // if accessed
    Serial.println("Accessed");
    String inMsg = "";
    while (client.connected()) {            // loop while client connected
      if (client.available()) {             // if a message,
        char c = client.read();             // read each byte, and
        Serial.write(c);                    // write to Serial
        if (c == '\n') {                    // if  LF and
          if (inMsg.length() == 0) {        //  if new, send response
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();               // send NewLine
            client.print("
- Touch to switch LED on/off -
"); client.print("

 

 

 

    "); client.print("
  • Switch Off
");
            client.println();               // send another NewLine
            break;                          // go out of while loop
          } else {                          // if not new
            inMsg = "";                     // initialize inMsg
          }
        } else if (c != '\r') {             // when not (c == '\n') ,too
          inMsg += c;                       // add to inMsg
        }

        if (inMsg.endsWith("GET /R")) {
          digitalWrite(Red, HIGH);          // Red on
        }
        if (inMsg.endsWith("GET /G")) {
          digitalWrite(Green, HIGH);        // Green on
        }
        if (inMsg.endsWith("GET /B")) {
          digitalWrite(Blue, HIGH);         // Blue on
        }
        if (inMsg.endsWith("GET /F")) {
          digitalWrite(Red, LOW);           // Red off
          digitalWrite(Green, LOW);         // Green off
          digitalWrite(Blue, LOW);          // Blue off
        }
      } // end of (client.available())
    }  // end of (client.connected())
    client.stop();
    Serial.println("Client Disconnected.");
  } // end of (client)
}
// end of program

上のプログラムはPreとCodeで挟みましたが、肝心のHTMLを送るところはソースが表示されず、結果が表示されてしまいますね。はてなブログでのタグの認識防止方法がまだしらべきれてないので、当面はそこの行以下最後までを次に直接かいておきます^^;  良い方法がわかった時点で直しますが、たいへんすみません<(_ _)>


if (c == '\n') { // if LF and
if (inMsg.length() == 0) { // if new, send response
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println(); // send NewLine
client.print("<font size=15 clolor=black><br>- Touch to switch LED on/off -<br>");
client.print("<ul><li>Switch <a href=\"/R\"><font color=red>Red-on</font></a></li><br>");
client.print("<li>Switch <a href=\"/G\"><font color=green>Green-on</font></a></li><br>");
client.print("<li>Switch <a href=\"/B\"><font color=blue>Blue-on</font></a></li><br>");
client.print("<li>Switch <a href=\"/F\"><font color=black>Off</font></a></li></ul></font>");
client.println(); // send another NewLine
break; // go out of while loop
} else { // if not new
inMsg = ""; // initialize inMsg
}
} else if (c != '\r') { // when not (c == '\n') ,too
inMsg += c; // add to inMsg
}

if (inMsg.endsWith("GET /R")) {
digitalWrite(Red, HIGH); // Red on
}
if (inMsg.endsWith("GET /G")) {
digitalWrite(Green, HIGH); // Green on
}
if (inMsg.endsWith("GET /B")) {
digitalWrite(Blue, HIGH); // Blue on
}
if (inMsg.endsWith("GET /F")) {
digitalWrite(Red, LOW); // Red off
digitalWrite(Green, LOW); // Green off
digitalWrite(Blue, LOW); // Blue off
}
} // end of (client.available())
} // end of (client.connected())
client.stop();
Serial.println("Client Disconnected.");
} // end of (client)
}
// end of program

 

この記事は、自分のまとめのために書きましたが、もしもお役に立つことがあれば幸いです。

次回はESPとステッパーで作った「勝手なリモートサーボ」について、近日中に書きたいと思います。

 

©2020 Akira Tominaga, All rights reserved.

 

ガチャポンの回転ダイヤルをデジタル化

f:id:a-tomi:20200919104840g:plain

上の回転ダイヤルは、ガチャポンから出てくる玩具で、なぜかこの春から突然人気となったようです。ネット通販で4つセットが売られていましたので5月末に予約して最近ようやく手に入れました^^;

小さいですが精巧によくできていて、ダイヤルが戻る感触は抜群です、なるほど・・・人気に納得できそう。

f:id:a-tomi:20200919133446j:plain

説明書には対象年齢がまさかの「15歳以上」と書いてありますが、こんな電話機はひょっとすると50歳以上しか使ったことがないかもしれませんね。

単に回すだけで楽しむ玩具ですが、キーボードに飽きたらこれを使ったデジタル入力も面白そう。

裏蓋を開けてみると中にはゼンマイ仕掛けのごく小さなギアボックスが入っているだけです。それでもご丁寧にちゃんとネジ2本を使って正確に蓋をしてあります。これならデジタル化できないものかと、ふと考えついてしまったのが事の始まり^^;

ギアボックス自体は開かないし、軸のでっぱりなど、余計なものもありません。もしそういうのがあれば磁石を仕込むとか、何らかのしかけができそyですが、一見取りつくしまがない感じ。こうなるとますます挑戦したくなりますね^^

しばらくは考えるだけでしたが、この4連休週末がきたので、ついにやってみることにしました。

 

気付いた中で一番かんたんそうな解き方:ダイヤルが戻る際の音の時間を測定してはどうかな!

戻る時間を測ってみた結果、回転量で安定して決まるようです。たぶんぜんまいギアの油による定速と思われます。それならきちんとマイクで音を拾って詳しく調べてみることに。

というわけで小さなコンデンサマイク・モジュールを仕込むことにしました。

f:id:a-tomi:20200919135241j:plain

f:id:a-tomi:20200919135322j:plain

1ドルで数個買えるモジュールですがゲインの調整もできるので一応使えそう。やってみると中になぜかぴったりと収まり、蓋をするとちょうどうまく固定されます。

f:id:a-tomi:20200919135541j:plain

よけいな飾りの鎖部分はカットして、その付近にそれらしくケーブルを通しました。

表から見るとサマになってますね(・・?

f:id:a-tomi:20200919135711j:plain

ダイヤルをしながらオシロスコープで測定します。

マイクモジュールは若干のノイズ(というより明らかに一定の寄生発振)を出していますが、この用途には問題ないですね。この際音のゲインが振り切るように調整しました。ダイヤル5を回すと次の出力になります。

f:id:a-tomi:20200919140030j:plain

オフセットは2.5V(電源電圧の丁度半分)で、ダイヤル音で0ボルトから電源電圧(5V)まで振り切るようにゲインを調整しました。

これでダイヤルごとの戻り時間を調べた結果、つぎのようになっています。

f:id:a-tomi:20200919140301j:plain

 

式に従って計算をし、次の判定表を作りました。

f:id:a-tomi:20200919140445j:plain

とはいえ、音の出ている間の波形は次のように乱れていますから、ダイヤルの識別には少し工夫が要ります。

f:id:a-tomi:20200919140649j:plain

絶対値のオンオフだけで識別するわけにはいきません。しかし1回の測定に数ミリセカンドかかっても問題ないわけなので、多数回のサンプリングをして「オフセットからのずれ幅が一定割合以上起きている場合にダイヤルが戻り中」と判定すればよいと考えつきました。

というわけで値をTM1637ィスプレイに表示するとして、次の回路にしました。ATmega328pで書いてありますが、Arduino-UnoでOKです。毎度きたない手書きのメモですみませんが、今回は回路を書くほどのものではありませんね。

f:id:a-tomi:20200919141341j:plain

実験した写真は次です。Arduinoのプログラムはこの記事の最後につけておきます。

f:id:a-tomi:20200919141601j:plain

これでテストすると、マイクを閉じ込めただけあって周囲の雑音はほぼ拾いません。玩具をガンガンたたいたりすると時々1が表示されます。そこで、短い信号を無視したり、定数を調整したり、ダイヤルがずれにくいように滑り止めマットを敷いたりして、無事にうまくできあがりました。

調整には思いのほか時間がかかりました^^;

ちなみに、このおもちゃはガチャポン(たぶん¥300)での購入用のようですが、次のネット販売で5月末に予約して9月に入手しました。1個250円程でした。

f:id:a-tomi:20200919142604j:plain

1パックに4個はいっています。どれも精巧ですばらしい!

f:id:a-tomi:20200919153056j:plain

 

というわけで、今回試験したArduinoスケッチをつけておきます。あまりきれいな書き方ではありませんが。

/* *********************************************************
   Rotating dial to TM1637 (for Arduino demonstration)
      Initial version V00  Sept. 19, 2020 (c) Akira Tominaga
 *  ********************************************************/
// for measurement
#define aPin 0                // A0 pin for analogRead 
uint32_t onS = 0;             // signal-on started time
uint32_t Now;                 // current time mS from start
uint8_t nSamp;                // number of voltage samplings
#define maxSamp 40            // max number of samplings
uint8_t nOn;                  // counter of signal-on
#define thOn 10               // threshold to detect signal-on
#define Vcenter 511           // analog voltage center value
#define Vth 300               // threshold offset from center 
uint32_t sigLen;              // signal length in milli second

#define offSt 0               // last satus was off
#define onSt 1                // last satus was on 
uint8_t lastSt = offSt;       // last status code

uint16_t aVal;                // analog read value
uint16_t vVal;                // offset = | Vcenter - aVal |

// each dial max duration milli seconds
#define d1max 417
#define d2max 546
#define d3max 674
#define d4max 802
#define d5max 931
#define d6max 1059
#define d7max 1188
#define d8max 1316
#define d9max 1445
#define d0max 1650
#define d1min 340
#define dAdj 27               // adjusting lenght for handling

// for led TM1637
#define lDIO 6                // DIO for TM1637 LED
#define lCLK 7                // CLK for TM1637 LED
#define lBrt 0x03             // duty-r 1/16x 02:4,03:10,05:12,07:14
#define lTu 50                // time unit in micro-second
byte lChr;                    // single byte sent to LED
byte Data[] = { 0x08, 0x08, 0x08, 0x08 }; // LED init 8888

void setup() { // ***** Arduino setup *****
  pinMode(lCLK, OUTPUT);
  pinMode(lDIO, OUTPUT);
  digitalWrite(lCLK, HIGH);
  digitalWrite(lDIO, HIGH);
  Serial.begin(9600);
  lDispData();                // display 8888
  delay(1000);
  for (uint8_t i = 0; i < 4; i++) {
    Data[i] = 0x10;           // display blank
  }
  lDispData();
  delay(1000);
  Serial.println("Starting");

  /*****************************************
     Measure voltage to find signal
   *****************************************/
mainLp:
  nOn = 0;                    // set sig counter zero
  for (nSamp = 0; nSamp < maxSamp; nSamp++) {
    aVal = analogRead(aPin);
    vVal = Vcenter - aVal;
    if (Vcenter < aVal) {     // get abs offset vVal
      vVal = aVal - Vcenter;
    }
    if (vVal > Vth) {
      nOn++;
    }
  }
  Now = millis();
  if (nOn >= thOn) {
    goto onRtn;
  }
  // goto offRtn;

offRtn:
  if (lastSt == offSt) {
    goto mainLp;
  }
  sigLen = Now - onS;
  if (sigLen < (d1min+dAdj)) goto mainLp; // if short, noise
  lastSt = offSt;
  onS = 0;

  // valid on length here
  //Serial.println(sigLen);
  if (sigLen <= (d1max+dAdj)) {
    dispDial(0x01);
    goto mainLp;
  }
  if (sigLen <= (d2max+dAdj)) {
    dispDial(0x02);
    goto mainLp;
  }
  if (sigLen <= (d3max+dAdj)) {
    dispDial(0x03);
    goto mainLp;
  }
  if (sigLen <= (d4max+dAdj)) {
    dispDial(0x04);
    goto mainLp;
  }
  if (sigLen <= (d5max+dAdj)) {
    dispDial(0x05);
    goto mainLp;
  }
  if (sigLen <= (d6max+dAdj)) {
    dispDial(0x06);
    goto mainLp;
  }
  if (sigLen <= (d7max+dAdj)) {
    dispDial(0x07);
    goto mainLp;
  }
  if (sigLen <= (d8max+dAdj)) {
    dispDial(0x08);
    goto mainLp;
  }
  if (sigLen <= (d9max+dAdj)) {
    dispDial(0x09);
    goto mainLp;
  }
  if (sigLen <= (d0max+dAdj)) {
    dispDial(0x00);
    goto mainLp;
  }
  dispDial(0x11);  // for others, too long signal "L"
  goto mainLp;

  /*****************************************
     Signal on case
   *****************************************/
onRtn:
  if (lastSt == offSt) {
    onS = Now;
  }
  lastSt = onSt;
  goto mainLp;

}             // end of Setup

void loop() { // ****** Arduino Loop *****
  // never comes here
}

/**************************************
      User defined functions
* *********************************** */
// *** dispDial(#) *** display dial number ***
void dispDial(byte dN) {
  for (int k = 0; k < 3; k++) {
    Data[k] = Data[k + 1];    // shift Data up
  }
  Data[3] = dN;               // set dial number to digit 3
  lDispData();
}

// *** lDispData *** display data to 4dig LED TM1637
void lDispData(void) {
#define showDat 0x40          // show this is data
#define showAd0 0xC0          // LED data addr is zero
#define showDcB 0x88+lBrt     // show dCtl + brightness
  lStart();                   // start signal
  lChr = showDat;             // identifier for data
  lSend();                    // send it
  lStop();                    // stop signal
  lStart();                   // and restart
  lChr = showAd0;             // identifier for address
  lSend();                    // send it
  for (int j = 0; j < 4; j++) { // for Data[0] to Data[3]
    byte edChr = Data[j];       // set a byte to edChr for edit
    switch (edChr) {
      case 0x00: lChr = 0x3F; break; // 0
      case 0x01: lChr = 0x06; break; // 1
      case 0x02: lChr = 0x5B; break; // 2
      case 0x03: lChr = 0x4F; break; // 3
      case 0x04: lChr = 0x66; break; // 4
      case 0x05: lChr = 0x6D; break; // 5
      case 0x06: lChr = 0x7D; break; // 6
      case 0x07: lChr = 0x07; break; // 7
      case 0x08: lChr = 0x7F; break; // 8
      case 0x09: lChr = 0x6F; break; // 9
      case 0x10: lChr = 0x00; break; // blank
      case 0x11: lChr = 0x38; break; // L for too large value
      default:   lChr = 0x79;        // E for Error
    }
    lSend();                  // send each byte continuously
  }                           // end of for bytes
  lStop();                    // stop signal
  lStart();                   // restart
  lChr = showDcB;             // identifier for display brightness
  lSend();                    // send it
  lStop();                    // stop signal
}

// *** lSend *** send a charater in lChr to TM1637
void lSend(void) {
#define LSb B00000001         // Least Significant bit
  for (int i = 0; i < 8; i++) { // do the following for 8bits
    if ((lChr & LSb) == LSb) {  // if one then
      digitalWrite(lDIO, HIGH); // set lDIO high
    } else {                    // else
      digitalWrite(lDIO, LOW);  // set lDIO low
    }
    lCLKon();                 // clock on to show lDIO
    lChr = lChr >> 1;         // shift bits to right
  }
  digitalWrite(lDIO, LOW);    // pull down during Ack
  lCLKon();                   // clock on to simulate reading
}

// *** lStart *** send start signal to TM1637
void lStart(void) {
  digitalWrite(lDIO, LOW);
  delayMicroseconds(lTu);
  digitalWrite(lCLK, LOW);
  delayMicroseconds(lTu);
}

// *** lStop *** send stop signal to TM1637
void lStop(void) {
  digitalWrite(lCLK, HIGH);
  delayMicroseconds(lTu);
  digitalWrite(lDIO, HIGH);
  delayMicroseconds(lTu);
}

// *** lCLKon ** clock on to show data to TM1637
void lCLKon(void) {
  digitalWrite(lCLK, HIGH);
  delayMicroseconds(lTu);
  digitalWrite(lCLK, LOW);
  delayMicroseconds(lTu);
}
/* End of program */

 何とかできることが分かったので、同じ処理を小さなPICなどに組んでケース内に収め、本物の回転ダイヤルと同じように数値パルスを出せば完璧な模型となりますね^^

 

では今回はこのへんで。

 

©2020 Akira Tominaga, All rights reserved.

 

型取りゲージの電子化?(ToFで実験)

f:id:a-tomi:20200830203817j:plain

これは「型取りゲージ」の写真です。複雑な形に合わせて床材やカーペット等をカットするための巧妙なゲージで、型取り定規と言われることもあります。英語ではProfie GaugeとかContour Gaugeとか。

もしこの道具を使わずに、例えば次のような複雑断面の柱に合わせてカーペットを切ろうとすると大変なことになります^^;

f:id:a-tomi:20200830204523j:plain

1mmφのピアノ線のエレメントが自由にズレて型をとる仕掛けで、これを使えば切断材料に相手の形をなぞり書きすることができます。それなりの精度とスムーズな動きが必要なために安価な道具ではありません。

しかし最近では安価なABS樹脂製(中国製)が出回っているようです。少し目が粗い(次の写真のものはエレメントが1.5ミリ幅)とはいっても、材料の革新だなあと勝手に感心します。型をとった状態でロックするメカニズムもなかなか素晴らしい!

f:id:a-tomi:20200830205054j:plain

とはいうものの、現代のことですからデジタル化などできないの?と、ふと思いついてしまいました^^;

仕組みはいくつか考えられますが、手始めにToFセンサー(Time of flight 光が反射して戻るまでの時間で距離を測るセンサー)を使うとどうなるでしょうか?できるような気がしますので、まずはやってみることにします。

ToFセンサーはカメラで近距離を測る用途などにも使われるようになり、ずいぶん安価になりました。たとえば次のToFセンサーVL6180Xのブレークアウトモジュールは海外ネットでは約2ドル強で入手できます。

 

f:id:a-tomi:20200830210212j:plain

真ん中にあるデバイスがVL6180X 本体で、赤外線ビームを出す部分と反射した赤外線を受ける部分があります。使われている赤外線の波長は850nmですので、もちろん目には全く見えません。

このセンサーを型取りゲージと同じ1ミリ単位で正確に水平移動させながら、反射対象までの距離を測っていくことで、同じことができるのではないかと考えました。実験用回路は、毎度手書きのきたないメモですみませんが、次のような簡単なものです。ステッパーの駆動にA4988というモジュールを使い、送りねじ機構で動かします。

f:id:a-tomi:20200830210940j:plain

プログラムではArduinoから測定データをPCへ送って、PC側でリアルタイムのグラフ表示などをしています。Arduino側とPC側の両方の実験プログラムをこの記事の最後の方につけます、とても小さいですが。

実験装置全体は次のように作りました。

f:id:a-tomi:20200831103553j:plain

動かすセンサーを前から見たのが次の写真です。センサーにある丸い窓2つ(赤外線ビーム放出窓と反射光検出窓だろうと思います)が見えています。

f:id:a-tomi:20200831103727j:plain

 

スライドユニットにセンサーをとりつけるために、木片をつけそれにブレッドボードをとりつけてあります。大きさを合わせるため、次の写真のように上のボードを下のサイズへ強引にカットして加工^^

f:id:a-tomi:20200830211500j:plain

ToFセンサーが揺れないよう、ブレークアウトモジュールに足をしっかりつけます。穴のない側にもホットボンドで無理に台と足2ピンだけをつけます。

f:id:a-tomi:20200830211734j:plain

表から見ると次。2つのネジ穴にホットボンドとピンの端が見えています。

f:id:a-tomi:20200830211858j:plain

 

まずはこういう紙型をいじりながら試験。暗い時のほうが結果は良いかと思いますが、かなり明るい状態でも特に問題なさそう。

f:id:a-tomi:20200830211337j:plain

まずは次の型取りができました。なんとなくうまくできてる!かな?

f:id:a-tomi:20200830212243j:plain

それでもよくみると、対象への距離は設定どおりで正確ですが、形が実際より滑らかです。変ですね。

測ったときの断面は下から見るとこれです。不細工ながら、円弧、台形、三角形にしたつもり。

f:id:a-tomi:20200831105148j:plain

手でほぼ正確に測った緑の破線と重ね合わせると次のようになっています。

 

f:id:a-tomi:20200830212513j:plain

台形は丸身を帯び、三角形も丸身を帯びてます(・・? なぜ? ん?

 

原因を調べるため、もっと厳しい直方体の木材や柱を立ててはかってみましたら、ありゃ? 少し差がでるという状況ではありませんね、これは! とくに形の間の深いところはひどく浅くなっています、、、

f:id:a-tomi:20200830212915j:plain

原因をあれこれ考え、ToFの赤外線は勝手に想定していた細いビームではない?!と勘づきます。あちこちで反射している可能性がありそう。

850ナノメーターの赤外線放出の様子を見るのは難しそうですが、この際は勝手な赤外線カメラで観察することにします。そのためにセンサーだけをつけた単体をこしらえて、これでじっくり観察します。

f:id:a-tomi:20200830214541j:plain

使う赤外線カメラは、古いデジカメの撮像面にセットされていたIRカットフィルター(赤外線を遮蔽し可視光だけを通すフィルター)を外して(というよりピンセットで無理にはぎ取って)、代わりにローパスフィルター(可視光を通さず波長780ナノメーター以上の赤外線だけ通すもの)をとり付けたまさしく勝手な赤外線カメラです。

これにより、センサーから出ている赤外線はビームの形どころか、びっくりする程広がっているのがわかりました。

f:id:a-tomi:20200830213856j:plain

これは想定外です!

ひょっとしてこのToFセンサー固有のことなのかなと微かな希望をもって、他のセンサーも調べてみることに。手元にある別のToFセンサーはVL50L0Xで、そちらでも観察することにしました。

こちらはVL6180X よりも数年前の製品です。

f:id:a-tomi:20200830214411j:plain

f:id:a-tomi:20200830214439j:plain

同じ方法で観察したビーム形状が次です。

f:id:a-tomi:20200830214754j:plain

赤外線が弱いようですが、やはり細いビームではなくこちらも光芒がずいぶん広がっています。

他の製品についてもよく調べる必要がありますが、センサーの精度を高める目的から言えば細いビームのもあってよいかな?と考えゆっくり調べることにします。あるとしてもこの目的には高価ではないかな?もしよいのがない場合は、センサーの前に広がりを絞る反射しにくい筒でもつけるのがよいかな?などともあれこれ考え中。

というわけで、すぐできると思ったこの実験、すこしずつ長引きそうです。精度の高いのが完成するまで待つといつになるかわからないので、いったんご紹介することにしました。どなたか良い案を考えたら是非デジタル化してくださいね^^;

 最後に、実験に使ったかんたんなArduinoスケッチとPC側のプログラム(例によって簡単なSmall Basicで書いてます)をつけておきます。

まずArduinoのスケッチ。

/********************************************************** 
 *  Profile gauge with VL6180X to Xfer data to Small Basic
 *    Initial version V00    Aug.29,2020 Akira Tominaga
***********************************************************/
#include "Wire.h"
#include "VL6180X.h"
VL6180X ToF;
#define Dir 3           // A4988 stepper direction
#define Step 4          // A4988 Step
#define Nreset 5        // A4988 !reset
#define Nsleep 6        // A4988 !sleep
#define Led 13          // LED
uint8_t nTimes = 32;    // number of ToF measures for average

void setup() { // ***** Arduino Setup *****
  float Kyori;
  float Tot;
  float Ave;
  uint16_t iAve;
  pinMode(Nsleep, OUTPUT);
  sSleep();  // sleep A4988
  pinMode(Led, OUTPUT);
  digitalWrite(Led, LOW);
  Serial.begin(9600);
  Wire.begin();
  bLED(2, 300, 100);    // show I am awake
  ToF.init();
  ToF.configureDefault();
  ToF.setTimeout(200);
  pinMode(Dir, OUTPUT);
  pinMode(Step, OUTPUT);
  digitalWrite(Step, LOW);
  sWake();              // wake A4988 up
  pinMode(Nreset, OUTPUT);
  sReset();             // home at current position
Repeat:
  while (Serial.available() < 1) {} // wait for Smallbasic ready
  String s = Serial.readString(); // read Smallbasic message
  delay(1000);

  // move stepper to left end
  sN(1600);              // X-1600 times (X-80 mm)negative
  sSleep();             // stop and sleep to avoid heat
  sWake();              // and wake stepper
  sReset();             // reset at Left end
  delay(100);           //
  bLED(3, 300, 100);    // blink 3 times for start..

  /*******************************************
     Measurement
   *******************************************/
 #define xMax 160
  for (uint8_t j = 0; j < xMax; j++) {
    sP(20);             // X + 1mm = 20 cycles
    sSleep();           // stop and sleep to avoid heat
    Tot = 0;
    for (uint8_t i = 0; i < nTimes; i++) {
Try:
      Kyori = ToF.readRangeSingleMillimeters();
      if (ToF.timeoutOccurred()) {
        // Serial.println("TO");
        goto Try;
      }
      Tot = Tot + Kyori;
    }
    Ave = (float)(Tot / nTimes);
    iAve = Ave * 10.00 + 0.50;

    uint8_t yh = iAve >> 8;       // get y-high byte
    uint8_t yl = iAve - 256 * yh; // get y-low byte
    Serial.write(yh);
    Serial.write(yl);
  } // end of For loop

  bLED(5, 300, 100);    // end..
  // return stepper to center
   sReset();            // Reset at right end
  sN(1600);             // X - 1600 times (X-80 mm)
  sSleep();             // sleep to avoid heat
  bLED(100, 20, 300);   // blink LED 100 times
  goto Repeat;          // and repeat again
}

void loop() { // ***** Arduino Loop *****
}                       // never comes here

/**************************************
      User defined functions
 **************************************/
#define tmStp 2000      // stepper phase timing in μS

// *** bLED() *** Blink LED
void bLED(int nTimes, int onLen, int offLen) {
  for (int m = 0; m < nTimes; m++) {
    digitalWrite(Led, HIGH);
    delay(onLen);
    digitalWrite(Led, LOW);
    delay(offLen);
  }
}

//   *** sP(n-rotations) *** move stepper positive
void sP(int nRot) {
  sWake();                // wake-up stepper
  digitalWrite(Dir, LOW);
  mvS(nRot, tmStp);
}
//  *** sN(n-rotations) *** move steppe negative
void sN (int nRot) {
  sWake();                // wake-up stepper
  digitalWrite(Dir, HIGH);
  mvS(nRot, tmStp);
}
//  *** mvS(nRot, ndelay) *** move stepper
void mvS(int nRot, long nDelay) {
  for (int l = 0; l < nRot; l++) {
    digitalWrite(Step, HIGH);
    delayMicroseconds(nDelay);
    digitalWrite(Step, LOW);
    delayMicroseconds(nDelay);
  }
}

// *** sReset() *** Reset stepper
void sReset() {
  digitalWrite(Nreset, LOW);
  delay(1);
  digitalWrite(Nreset, HIGH);
  delay(1);
}

// *** sSleep() *** Sleep stepper to avoid heat
void sSleep(void) {
  digitalWrite(Nsleep, LOW);
}

// *** sWake() *** Wake up stepper
void sWake(void) {
  digitalWrite(Nsleep, HIGH);
  delay(1);         // wait for charge pump readiness
} 
// end of program

次はPC側のプログラムです。

myTitle="Receiving ToF data from Arduino  Aug.29, 2020   (c)Akira Tominaga"
TextWindow.Title=myTitle
TextWindow.Write("Opening CommPort..")
connectPort()
' ***** Define Graphics Window *****
gw = 854
gh = 480
margin = 50
xMax=160 ' max X = 160mm
yMax=100 ' max Y =100mm
yMaxX10=yMax*10
sp =10 ' space between lines (mm)
GraphicsWindow.Width = gw+margin
GraphicsWindow.Height = gh+margin
GraphicsWindow.Title=myTitle
GraphicsWindow.BackgroundColor="Black"
GraphicsWindow.PenWidth=3
GraphicsWindow.FontName="Arrial"
GraphicsWindow.FontSize=15
GraphicsWindow.BrushColor="White"

'  ***** Y scale and horizontal lines *****
For xx=0 To xMax
 X=gw*(xx/160)+margin/2  ' Set x position X
 For yy=0 To yMaxX10 Step sp*10
   Y=gh*(yy/(yMaxX10))+Margin/2 'Set y position Y
   GraphicsWindow.SetPixel(X,Y,"White")
   If xx=0 Then
    X=margin*0.1              'set scale position
    Y=gh*(yy/yMaxX10)+Margin*0.4 'adjust scale position
    GraphicsWindow.DrawText(X,Y,yy/10) ' draw Y scale
  EndIf
 EndFor
EndFor

' ***** X scale and vertical lines *****
For yy=0 To yMaxX10
 For xx=0 To xMax Step sp
  X=gw*(xx/xMax)+margin/2      ' Set x position X
  Y=gh*(yy/yMaxX10)+margin/2      'Set y position Y
  GraphicsWindow.SetPixel(X,Y,"White")
  If yy=0 Then
    X=gw*(xx/xMax)+margin*0.3 
    Y=margin*0.1              'adjust scake position
    GraphicsWindow.DrawText(X,Y,xx) ' draw X scale
  EndIf
 EndFor
EndFor

' ***** Draw a graph, receiving data from UNO ToF *****
GraphicsWindow.PenWidth=3
GraphicsWindow.PenColor="Yellow"
'EOT=254*256 ' define 0xFExx as End of Transmission from Arduino
LDCommPort.TXString("SB") ' tell to UNO  "SB is ready"
For xx=0 to xMax
  yh=LDCommPort.RXByte() 
  yl=LDCommPort.RXByte()
  yy=256*yh+yl
  TextWindow.Write("yy=")
  TextWindow.WriteLine(yy)
  X=gw*(xx/xMax)+margin/2  ' Set x position X
  TextWindow.Write("X=")
  TextWindow.writeLine(X)
  Y=gh*(yy/yMaxX10)+Margin/2  ' Set y position Y
  TextWindow.Write("Y=")
  TextWindow.writeLine(Y)
  If xx>0 Then
    GraphicsWindow.DrawLine(xs, ys,X,Y)
  EndIf
  xs=X ' save X
  ys=Y ' save Y
EndFor
LDCommPort.ClosePort()
Sound.PlayBellRing()

' ***** Subroutines *****
Sub connectPort
  for i=1 to 16
    portName="COM"+i
    status=LDCommPort.OpenPort(portName,9600)
    If status="SUCCESS" Then 
      Goto Out
    EndIf
  EndFor
  Out:
  TextWindow.Write("Connected to ")
  TextWindow.WriteLine(portName)
  If status="CONNECTIONFAILED" Then
    ComFailed:
    Goto ComFailed ' stop here
  EndIf
EndSub

 

 

2020.9.9追記

精度がなんとかならないか・・、その後も少しずつですが実験したので以下に追加します。

このセンサー自体は2.8mmx4.8mmx1.0mmの大きさで、顕微鏡を低倍率にして見ると次のような形。

f:id:a-tomi:20200909203951j:plain

顕微鏡のIR遮蔽が弱いので850nmの平面Laser光が写っています。上の穴が反射光の検知窓で、穴の間はおよそ3.5mm離れています。ですので、ここに何か細工ができないかなあと考えました。

最初にレンズで集光してみることに。次の状態は何もしない場合の赤外線写真です。

f:id:a-tomi:20200909204819j:plain

この出力窓の前に樹脂製のレンズをおいてみると、だいぶ収束するのがわかります。

f:id:a-tomi:20200909204946j:plain

この写真では高さの位置を出力窓にあわせるために、レンズの下にティッシュペーパーを敷いています。この状態でレンズを前後させるとさらに収束できる位置があるようです。赤外線カメラで見て一番良い位置を確認したのですが写真を撮りそこないました^^;

とはいえ直径が10mmほどのレンズのため、反射が受光窓にもかぶるのでもっとずっと小さいレンズでないといけません。プラスチックレンズは頑張れば切れるかも^^;  モノはためし、幅2.5mmほどの凸レンズを作ってみました。

f:id:a-tomi:20200909205328j:plain

こういう形でも受光窓を避けるにはなんとかなりそうな感じがします。

これは度の強いメガネ(リーディンググラス、f=16.7cm)から、愛用のProxxon「サーキュラーソー」で下の写真のようにエイヤッと切りとったもの。

f:id:a-tomi:20200909205618j:plain

ひどいことをしますが、ヒンジが壊れて不要になった百均のグラスなのでご安心を^^;

 これで早速やってみると、測定結果の距離が極端に小さくでます。何のことはない、出力窓から少し離しただけで、レンズの反射がどうやっても受光窓にはいります。レンズを出力窓にほぼくっつけないとだめそう。焦点距離の相当短いものを使わないと無理そうなのでレンズでの収束はとりあえず断念。

それなら、次はパイプで広がらなくする案。外径2.5mm、穴径1.5mmのアルミパイプを切り出して、木片を支えにしてやってみました。光が漏れないように出力窓にピッタリ付けます。

f:id:a-tomi:20200909210400j:plain

赤外線のカメラで見るとパイプによる集光はバッチリだったのですが。。。測定値が不安定。どうやら測定対象からの反射光がパイプをセットした台で反射しているようです。なぜ短く計測されることがあるのかはわかりませんが、ひょっとして単に往復時間だけでなく位相なども検知してるのかなあ、う~ん ;; 

どっちにしてもパイプが使えるかを確かめるには、パイプ自体の支持方法を工夫しないといけない感じです・・ICに接着してしまえばいいかな?それも気が引ける・・

 

そこでふと気づいたのですが、スリットで広がりを防ぐのはどうだろう?左右だけ広がらなければよいわけなので・・。

しかし、どっこい。そうはいきませんでした。左右の壁で反射して短く検出されてしまいます。それなら反射を防ぐ紙を貼るとどうなるか・・。下のように広げてみてもやっぱり両脇での反射が検出され、短くでます。

f:id:a-tomi:20200909211337j:plain

木製の台が光線の近くにあること自体もまずいかも。

ここまででのところ、測距対象が近くにあるときは正しい距離が得られる場合もありますが、対象が離れると測定値が異常に小さくなるのに気づきました。反射光の強度が小さい場合(測定対象からの反射光が弱いと)センサーは次第にゲインを上げて近くの弱い反射を検出するようにみえます。ゲインは40倍までコントロールできる仕様です。測り方の指定にもよるでしょうが。

他の用途ではどうしているのか?自動掃除機などのLidarに使われているToFセンサーでも光線の性格は同じで、かなり広がっています。ロボットや掃除機が壁にぶつからないようにするためには、それでもとくに問題ないわけです・・・。ポピュラーなこの種のToFセンサーでは正面の距離は正確に測れても、回転時の角度にまつわる分解能は高くはないということでしょうか。

 というわけで、まだ光線が絞れてません。もし別の知恵が出たらこの続きをやろうと思います。ここまでおつきあいいただきありがとうございます (o*。_。)o tks

 

©2020 Akira Tominaga, All rights reserved.

マイコンとPCのデータ授受ーSmallbasicなら簡単 (2020.12.05 LiquidCrystal_I2Cについて補足しました)

f:id:a-tomi:20200823175731g:plain

ArduinoのデータをPCで受けて表示した例

マイコンのデータをPCで受けて自由にグラフィック処理したり、逆にPCからデータをマイコンへ渡したりするには、PC側にそれなりの言語環境が必要。ところがちゃんと維持していないと、Anacondaなどのバージョン不整合を起こし、まるで王手飛車とりで攻めまくられたりして・・・^^; 思わぬことで手こずりたくないものです。

私はマイクロSDカードを介したりすることが多いですが、リアルタイムで処理したい場合はそうはいきません。そんなとき、単純・簡単で便利なのがマイクロソフトが無償提供している Small Basic という教育用言語です。

ここではマイコンとの間のインターフェイスを、単純で特別な機器の要らないシリアル通信でつなぎます(それなりのデバイスを用意すれば、BlueToothなど他の方法ももちろん可能ですが)。

使うにはまず、Small BasicをPCにインストールします。バージョンアップの頻度は少なくとても安定しています。今は次のサイトが本家になっています。

https://smallbasic-publicwebsite.azurewebsites.net/

 英語版でとくに困ることはありませんが、日本語版(各国言語版)を入れたいときは次からインストールします。

https://www.microsoft.com/ja-jp/download/details.aspx?id=46392

 Small Basic自体はいちじるしく単純ですが、便利な拡張APIが沢山出ています。なかでも英国の団体が運営するLitDevには豊富な関数がそろっていて世界中で多く使われています。LitDevの最新版(現時点は LitDev 1.2.23.0)を次からインストールします。

http://litdev.co.uk/

LitDevのZipを解凍してすべてを次のフォルダーに入れます。C:\Program Files (x86)\Microsoft\Small Basic\Lib

これで後はプログラミングが自由自在です。めったにバージョンアップがないのは却ってありがたいことです^^

Small Basicの使い方は、非常にわかりやすい日本語サイトも沢山でていますので必要に応じてご参照下さい。LitDevで使える多数の関数については上のサイトで、英語ではありますがわかりやすく説明されています。ここではそのなかのLDCommPortだけを使いますが、他に便利なAPIは多数あります。LitDevは、SmallBasicの開発環境で言語そのものに交じって同じに使えます。

 

まずは、ArduinoからPCのSmall Basicへ送る場合の例です。

f:id:a-tomi:20200823210916j:plain

次がこのArduinoスケッチです。

/******************************************************************
    Sending data to Microsoft-Smallbasic via Serial port, example.
     Initial version V.00 Aug.22, 2020  (c) Akira Tominaga
     Function:
      Send data of Lissage graph to PC Smallbasic.
  *****************************************************************/
#include "Wire.h"
#include "LiquidCrystal_I2C.h"
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
#define cPl 20                    // characters per line
#define lMax 4                    // number of lines
#define wSize 854                 // graphics window width
#define hSize 480                 // graphics window height
uint8_t Line = 0;                 // set LCD initial line
byte EOT = 0xFE;

void setup()  {  // ***** Arduino Setup *****
  Serial.begin(9600);
  lcd.begin(20, 4);        // initialize 20 x 4 LCD turn on backlight
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Talk with SmallBasic");
Hajime:
  lcd.setCursor(0, 1);
  lcd.print("Start Smallbasic pgm");
  while (Serial.available() < 1) {}
  String s = Serial.readString(); // read message from Smallbasic pgm
  uint8_t nPads=20-s.length();    // # of blanks req'd to pad
  lcd.setCursor(0, 1);
  lcd.print(s);
  for (uint8_t i=0;i<nPads;i++){
    lcd.print(" ");
  }
  delay(10);
  lcd.setCursor(0, 1);
  lcd.print("Lissage data sent");
  for (float t = 0; t < 2.1 * 3.1416; t = t + 0.01) {
    uint16_t px = (float)((wSize/2) * sin(3 * t) + wSize/2);
    uint16_t py = (float)((hSize/2) * sin(4 * t) + hSize/2);
    uint8_t xh=px>>8;             // get x-high byte
    uint8_t xl=px-256*xh;         // get x-low byte
    uint8_t yh=py>>8;             // get y-high byte
    uint8_t yl=py-256*yh;         // get y-low byte
    Serial.write(xh);             // send x-high byte
    Serial.write(xl);             // send x-low byte
    Serial.write(yh);             // send y-high byte
    Serial.write(yl);             // send y-low byte
    delay(1);
  }
  for (uint8_t i=1;i<4;i++){      // LCD lines from 1 to 3 
    lcd.setCursor(0,i);           // set cursor 
    for (uint8_t j=0;j<20;j++){   // and clear each line  
      lcd.print(" ");
    }
  }
  Serial.write(EOT);              // send End of Transimission
  Serial.write(0x00);             // of which low byte to add
  goto Hajime;                    // return to Hajime and wait
}

void loop() { // ***** Arduino Loop *****
}             // not used in this example

 

この記事のいちばん最初に掲げた動画は、これをSmallBasicの次のプログラムで処理したものです。

myTitle="Receiving data from Arduino  Aug.22, 2020 (c)Akira Tominaga"
TextWindow.Title=myTitle
TextWindow.Write("Open CommPort..")
status = LDCommPort.OpenPort("COM5",9600)
TextWindow.WriteLine(status)
gw = 854
gh = 480
margin = 50
GraphicsWindow.Width = gw+margin
GraphicsWindow.Height = gh+margin
GraphicsWindow.Title=myTitle
GraphicsWindow.BackgroundColor="Green"
GraphicsWindow.PenColor="White"
GraphicsWindow.PenWidth=3
EOT=254*256 ' define 0xFE.. as End of Transmission from Arduino
xs=gw/2 ' initial x position in Graphics window
ys=gh/2 ' initial y position in Graphics window
LDCommPort.TXString("SB started.")

Loop1:
xh=LDCommPort.RXChar()
xl=LDCommPort.RXChar()
xx=xh*256+xl+Margin/2  ' Set x position
'TextWindow.Write("XX=")
'TextWindow.WriteLine(Text.GetCharacterCode(XX))
If XX>=EOT Then
  Goto Lpe
EndIf
yh=LDCommPort.RXChar()
yl=LDCommPort.RXChar()
yy=yh*256+yl+Margin/2  ' Set y position
GraphicsWindow.DrawLine(xs, ys,XX,YY)
xs=xx ' save xx
ys=yy ' save yy
Goto Loop1

Lpe:
LDCommPort.ClosePort()
GraphicsWindow.BrushColor="Yellow"
GraphicsWindow.FontSize=30
GraphicsWindow.DrawText(gw/2-60,gh/2,"Completed!")
Sound.PlayBellRing()

 グラフはSmallBasicのGraphicsWindowでごく簡単にかいていますが、かなり高度の事も色々できます。一例:https://a-tomi.hatenablog.com/entry/2018/09/04/162615

LDCommPortのうち、OpenPort()、TXString()、RXChar()、ClosePort()しか使っていませんが、他に、RXByte()、RXAll()、TXByte()などの関数も使えます。

他の命令は全てSmallBasicそのものですが、変数に型の概念がないこと(つまり宣言不要)や、ファイルやイメージなどの扱いも含めて楽ちんそのものです^^

 

次に、SmallBasicが非常に長い文章をArduinoへ送り、Arduinoで液晶に次々に表示する例です。

f:id:a-tomi:20200823211138j:plain

 このSmallBasic側プログラムは次です。

myTitle="Sending text to Arduino   Aug.22, 2020 (c)Akira Tominaga"
TextWindow.Title=myTitle
TextWindow.Write("Open CommPort..")
status = LDCommPort.OpenPort("COM5",9600)
TextWindow.WriteLine(status)

Bun=File.ReadContents("D:\SmallBasic\SBtxt\Test00.txt")
Bl=Text.GetLength(Bun)
If Bl=0 Then
  TextWindow.WriteLine("*** File not found")
EndIf

tLen=20 ' text length to be sent at one time
sP=1    ' start pposition of  Text reading
LF=Text.GetCharacter(10) ' ending char sent by Arduino

While(sP<Bl)
  Bpart=Text.GetSubText(Bun,sP,tLen)
  TextWindow.Write("SB sent: ")
  TextWindow.WriteLine(Bpart)
  LDCommPort.TXString(Bpart)
  sP=sP+tLen
  TextWindow.Write("Arduino: ")
  Resp=""
    While(Resp <> LF)
      Resp=Text.GetCharacter(LDCommPort.RXByte())
      TextWindow.Write(Resp)
    EndWhile 
EndWhile
status=LDCommPort.ClosePort()
Sound.PlayBellRing()

 ここでは簡単のため、テキストファイルを開いて、全量を読み込んでそれを送っていますが、Webの特定場所から読み込んで送るなども、もちろんできます。

SmallBasicのTextWIndowに、この送受の内容が次のように順次表示されます。

f:id:a-tomi:20200823212742j:plain

  次がこれを受けて表示するArduinoのスケッチです。

実行順序としてはArduinoを先に接続して動かしてから、次にSmallBasicのプログラムを立ち上げます。なお接続Port番号が一定しないマイコンの場合は、SmallBasicでのOpenPortの処理方法をこの記事の最後にかいておきます。

/*****************************************************************
    communications with MS Smallbasic via Serial port, example.
     Initial version V.00 Aug.22, 2020  (c) Akira Tominaga
     Function:
      Receive text from Smallbasic and display to LCD 20x4.
     Major revisions:
 *****************************************************************/
#include "Wire.h"
#include "LiquidCrystal_I2C.h"
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
#define cPl 20                    // characters per line
#define lMax 4                    // number of lines
uint8_t Line = 0;                 // set LCD initial line

void setup()  { // ***** Arduino Setup *****
  lcd.begin(cPl, lMax);           // initialize I2C and LCD
  lcd.backlight();                // and turn on backlight
  lcd.setCursor(0, 0);            // LCD home position
  lcd.print("Receive text from PC");
  Serial.begin(9600);             // the same bps with PC
}

void loop() {  // ***** Arduino Loop *****
  while (Serial.available() < 1) {}
  String s = Serial.readString(); // receive text string
  lcd.setCursor(0, Line);         // point line to show text
  lcd.print(s);                   // and show text to LCD
  int j = Line + 1;               // point next line to use
  if (j >= lMax) j = 0;
  lcd.setCursor(0, j);
  for (int k = 0; k < cPl; k++) { // and clear it
    lcd.print(" ");
  }
  delay(1000);                    // time to read text
  Serial.write("OK!");            // send message OK! to PC
  Serial.write(0x0A);             // LF as end of message
  Line++;                         // point next line of LCD
  if (Line >= lMax) Line = 0;
}

 上の例は電光掲示板のように表示するだけですが、適度な速度でやれば結構よみやすいものです。

 

以上での留意点は

①先にマイコンをシリアルポート(今ではUSBシリアル)につなぎ、そのあとでSmallBasicプログラムを立ち上げる

②COMポート番号が接続のつど一定にならないマイコン接続の場合は、LDCommPort.AvailablePorts()関数でポート番号を得てからOpenPort処理するように組むようになっていますが、これは文字の操作が結構面倒です。しかしポートを1つしか使わないことが圧倒的に多いでしょうから、その場合は次のコーディングでPort番号を得てからOpenPortをするコーディングにすれば大丈夫です。

kekka="x"
for i=1 to 16
  portName="COM"+i
  kekka=LDCommPort.OpenPort(portName,9600)
  If kekka="SUCCESS" Then 
    Goto Out
  EndIf
EndFor
Out:
TextWindow.Write("CommPort is ")
TextWindow.WriteLine(portName)

 

では今回はこのへんで。

一番簡単な言語のひとつであるSmall Basicを使い始めていただき、プログラミングする習慣が少しでも広まるきっかけになればと思います。

 

2020.12.05追記:

I2Cシリアル変換モジュールで接続する液晶文字ディスプレイは種類が複数あり、ライブラリーが混乱しがちです。ここではArduinoのLiquidCrystal_I2C.hライブラリー(New-liquidCrystal-master)を使う方法を補足させていただきます。

元祖の日立1602LCDモジュール(HD44780)と同じ仕様のLCDディスプレイをI2Cシリアル変換モジュール(PCF8574ボード)で4ビットデータを使って動かす価格の安い(海外ネットで16文字x2行は200円台、20文字x4行は400円台で購入できた)ものが圧倒的に多く出ている感じです。次の写真で、各LCDモジュール裏の右上にはんだ付けされているのがその変換モジュールです。

f:id:a-tomi:20201205121018j:plain

その便利な変換ICは同じでもボードには複数種があるため、LCDへのピン接続の明示的な指定の必要性があるわけです。ピン接続部を表から見ればどれでも、元祖の1602LCDモジュール(HD44780)と同じで海外製なのにカタカナを含む中身のフォントも全く同じものが多いです。

f:id:a-tomi:20201205121404j:plain

ところが、同じに見えても4ビット接続の場合は8ビット接続と異なり、データの授受にD0~D3を使わないためI2Cプロトコルは煩雑になります。

このため、現時点ではArduinooのLiquidCrystal_I2Cライブラリーをスーパーセットである次のものへ入れ替え、他のLiquidCrystal_I2Cライブラリーを削除することで問題なく使えるようになっています。16桁2行でも20桁4行でも上のスケッチ例のように指定できます。

GitHub - fmalpartida/New-LiquidCrystal: Clone of the new liquid crystal library from: https://bitbucket.org/fmalpartida/new-liquidcrystal

名称が重なる他のLiquidCrystal_I2Cライブラリーは以後使わないかと思います。万一必要になれば、そのときに元のを特定スケッチのフォルダーに改めて再導入すればよいかと思われますし、もし心配なら上記を入れる前に古いのをどこか別フォルダーにバックアップしておくとか(参照先の名前が重なるのを防ぐために)。

今やOLEDが多く使われるようになりましたが、そちらはメモリーの必要量が多いためこういう安価で楽なI2C接続LCDはまだまだ長く使われるのではないかと思います。

以上、ご自分がお使いになる同様な液晶文字ディスプレイについてご判断の上、自己責任にてお願いしますね。

 

 

©2020 Akira Tominaga, All rights reserved.

 

 

 

回路不要のかんたんなRTCライター:Arduinoで

f:id:a-tomi:20200816121455j:plain

RTC(リアルタイムクロック)は今やとても正確で、1年間の最大の狂いが秒単位。上のArduinoにつないだRTCは、なかでも正確なDS3231というICのブレークアウトモジュールです。

f:id:a-tomi:20200816121832j:plain

このモジュールは、比較的容量の大きいCR2032電池を使い、大体5年ほどはそのまま動きます。小さな電池を入れる型であればもちろんもっと短いですが。

f:id:a-tomi:20200816122012j:plain

この精密なクロックが海外ネットでは1ドル未満で買えるので驚きです。今はコロナの影響でなんでも少し高いですが、今ちらりとAliexpressをみると、やはりそんな廉価です。中には、ばか高い同じものもあるので、気を付けないといけませんが。

 

f:id:a-tomi:20200816122243j:plain

何かの自作にRTCを使うためには、普通はカレンダーや時刻の設定機能を作りこみます。そのため少なくとも3つ(Mode、 Time、Set)のボタンかタクトスイッチが要ります。

しかし、RTCがこれだけ正確だと、自作機器では読み込むだけにしたい感じもします。必要に応じて外して更新したり、あるいはRTC更新の際は外部からの入力に差し替えるなどすればよいわけなので。(ブレークアウトモジュールではなく、DS323xのICを直接組みこむ場合はもちろんそうはいきませんね。ただし千個とか1万個ではなく趣味用の少量購入ならブレークアウトモジュールが圧倒的に廉価。)

読み込むだけにする場合は、別途RTCへの書き込み装置を必要とするわけですが、それをArduinoで回路なしに楽に行う勝手な方法をご紹介したいと思います。

電池を入れて最初の写真のように接続し、まずArduinoIDEで次のプログラムを書き込みます。

/*************************************************************
    Simple RTC writer with Arduino-IDE Serial-Monitor only
    How to use:
    	1) Connect RTC to I2C pins (ex.UNO; SCL=A5, SDA=A4).
    	2) Use Serial-Monitor for dialogues.
    V.00 Aug.15,2020 (c) Akira Tominaga, All rights reserved.
**************************************************************/

#include "Wire.h"             // use I2C library
#define RTC_addr 0x68
byte  vR[8];                  // values in RTC format
uint8_t vI[8];                // integer values
#define mds 1                 // mode=ss
#define mdm 2                 // mode=mm
#define mdh 3                 // mode=hh
#define mdW 4                 // mode=W : Day of Week (Sunday=1)
#define mdD 5                 // mode=DD
#define mdM 6                 // mode=MM
#define mdY 7                 // mode=YY
char YMDwHMS[20];             // editing area to show time
String strTyped;              // characters typed-in
uint8_t lTyped;               // length of typed chars + enter
byte Data[2];                 // 2 bytes work area

void setup() { // ***** Arduino setup *****
  byte rYN;     // work area for response value
  Serial.begin(9600);
  Wire.begin();
  Serial.println("** RTC editor **");
  getRTC();
  Serial.println("YY/MM/DD-W-hh:mm:ss =") ; Serial.println(YMDwHMS);
Inq1st: Serial.print("Is it OK? (Y or N)");
  rKBin();
  rYN = Data[0] & (0xFF - 0x20); // get upper-case letter
  switch (rYN) {
    case 'N': Serial.println("=N"); goto UpdInput;  // No, goto input proc
    case 'Y': Serial.println("=Y"); goto doNothing; // Yes, do nothing
    default: goto Inq1st;     // else, re-inquiry
  }
UpdInput: updateVal();
Inq2nd: Serial.print("OK? to update RTC (Y or N)");
  rKBin();
  rYN = Data[0] & (0xFF - 0x20); // get upper-case letter
  switch (rYN) {
    case 'Y': Serial.println("=Y"); goto UpdRTC;    // Yes, write to RTC
    case 'N': Serial.println("=N"); goto UpdInput;  // No, set values again
    default: goto Inq2nd;     // else, re-inquiry
  }
UpdRTC: wRTC();
  Serial.println("** RTC updated **"); Serial.println("");
doNothing: delay(100);
}

void loop() { // ****** Arduino Loop *****
  getRTC();                 // get and edit RTC contents
  Serial.println(YMDwHMS);  // show Real time clock
  delay(1000);
}

/**************************************
      User defined functions
* *************************************/
// *** read serial Keyboard input
void rKBin(void) {
  while (Serial.available() < 1) {}
  strTyped = Serial.readStringUntil(0x10); // read KB until DLE
  Data[0] = strTyped.charAt(0);
  Data[1] = strTyped.charAt(1);
}

// *** get RTC contents into chars YMDwHMS
void getRTC(void) {
  rRTC();      // read RTC device and get integers
  edTMchr();   // edit Time to characters
}

//*** edit Time to characters YMDwHMS
void edTMchr(void) {
  sprintf(YMDwHMS, "%02d/%02d/%02d-%01d-%02d:%02d:%02d", vI[mdY], vI[mdM], vI[mdD], vI[mdW], vI[mdh], vI[mdm], vI[mds]);
}

// *** prepare values to update RTC
void updateVal(void) {
  char mC[] = {0xFF, 's', 'm', 'h', 'W', 'D', 'M', 'Y' };  // mode chars
  uint8_t maxvI[8] = { 0, 59, 59, 23, 7, 31, 12, 99}; // max values
  uint8_t minvI[8] = {0, 0, 0, 0, 1, 1, 1, 0};        // min values
  uint8_t mdN = mdY;        // index to RTC field sequence
byMode:
  Serial.print("Input ");
  Serial.print(mC[mdN]);
  if (mdN != mdW) {   // excluding day-of-week,
    Serial.print(mC[mdN]);  // duplicate characters
  }
  Serial.print("=");
  rKBin();
  uint8_t inVal = (Data[0] & 0x0F) * 10 + (Data[1] & 0x0F);

  if (mdN == mdW) inVal = (Data[0] & 0x0F);
  if (Data[0] == 0x0A) {  // if just enter only
    inVal = vI[mdN];      // use as-is value
    goto byMskip;   // and skip validation
  }
  if ((inVal > maxvI[mdN]) | (inVal < minvI[mdN])) {
    Serial.println("Err "); goto byMode;
  }
byMskip:
  if ((inVal < 10) & (mdN != mdW)) { // if value<10 and not mdW
    Serial.print("0");        // insert left zero
  }
  Serial.println(inVal);
  vI[mdN] = inVal;
  mdN = mdN - 1;          // next mode
  if (mdN == 0) {
    goto byMend;
  } else {
    goto byMode;
  }
byMend:
  edTMchr();             // edit Time to characters YmDwHMS
  Serial.println("YY/MM/DD-W-hh:mm:ss =") ; Serial.println(YMDwHMS);
}

// *** read RTC and convert values to integers ***
void rRTC(void) {
  Wire.beginTransmission(RTC_addr);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(RTC_addr, 7);
  while (Wire.available() < 7) {} // wait for data ready
  for (int i = 1; i < 8; i++) {
    vR[i] = Wire.read();
  }
  Wire.endTransmission();
  // convert RTC values to integers
  vI[mds] = ((vR[mds] & 0x70) / 16) * 10 + (vR[mds] & 0x0F);
  vI[mdm] = ((vR[mdm] & 0x70) / 16) * 10 + (vR[mdm] & 0x0F);
  vI[mdh] = ((vR[mdh] & 0x20) / 32) * 20 + ((vR[mdh] & 0x10) / 16) * 10 + (vR[mdh] & 0x0F);
  vI[mdW] = vR[mdW];
  vI[mdD] = ((vR[mdD] & 0x70) / 16) * 10 + (vR[mdD] & 0x0F);
  vI[mdM] = ((vR[mdM] & 0x10) / 16) * 10 + (vR[mdM] & 0x0F);
  vI[mdY] = ((vR[mdY] & 0xF0) / 16) * 10 + (vR[mdY] & 0x0F);
}

// *** write to RTC - converting Integers to RTC values ***
void wRTC(void) {
  vR[mds] = (vI[mds] / 10) * 16 + vI[mds] % 10;
  vR[mdm] = (vI[mdm] / 10) * 16 + vI[mdm] % 10;
  vR[mdh] = (vI[mdh] / 20) * 32 + ((vI[mdh] % 20) / 10) * 16 + vI[mdh] % 10;
  vR[mdW] = vI[mdW];
  vR[mdD] = (vI[mdD] / 10) * 16 + vI[mdD] % 10;
  vR[mdM] = (vI[mdM] / 10) * 16 + vI[mdM] % 10;
  vR[mdY] = (vI[mdY] / 10) * 16 + vI[mdY] % 10;
  Wire.beginTransmission(RTC_addr);
  Wire.write(0x00);
  for (int i = 1; i < 9; i++) {
    Wire.write(vR[i]);
  }
  Wire.endTransmission();
}
/* End of program */

 シリアルモニターを起動すると操作ができます。

シリアルモニターを起動するには、IDEのメニューで「ツール」→「シリアルモニター」とクリックするか、または、IDEの右上あたりにある虫眼鏡のマークをクリックします。

次にシリアルモニターで、操作を次の様に行います。

f:id:a-tomi:20200816133338j:plain

詳細は次のとおりです。

①現在の数値を更新しないでよいかを聞いてきます。更新する場合はNかnを入力してEnterキーをおします。その場合入力は一番上②の送信欄です。また入力の最後には必ずEnterキーを入れてください。答がYかyだと更新せずに終えて、そのまま⑥の時計表示へ行ってしまいます。

④で、設定したいYY、MM、DD、W(曜日で1が日曜で月曜は2、・・・土曜が7)、hh、mm、ssとして年、月、日、曜、時、分、秒を順番に聞いてきます。W以外は2桁の数値を入力します。この時に、何も入れずにEnterキーを押せば、①に出た値を使います。

最後にss(秒)をいれたら、⑤でそれでよいかを聞いてきます。Yまたはyと答えればそのタイミングでRTCが更新されます。もしNまたはnと答えれば、再び④を繰り返します。

間違えて更新を終えた時には、ArduinoのUSB接続を外して再度つけなおしてやりなおせばよいだけです。

 

なお、上記のスケッチはよいコーディングの構造とは言えません。このままで動きますが、必要ならお好きなようにお直しください。またDS3231以外のRTCでも、デバイスがもつレジスターの内容はよく似ています。データシートをご参照のうえ比較してもし必要な個所があれば直してお使いください。

 

短いですが、この記事は以上です。お役に立てば幸いです。

 

©2020 Akira Tominaga, All rights reserved.