勝手な電子工作・・

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

簡単にできる指紋照合(Arduino-Unoで)2021.9.12にプログラム更新

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

殆どのスマホ指紋認証ができるようになりましたが、指紋は登録したくないという人が多いかもしれません。私もですがね^^; 指紋は個人が確実に特定できる優秀なバイオメトリクス。なのでネット機器に入れるのは躊躇してしまいます。

ところが、最近出回っている指紋センサーモジュールの性能とコスパが凄いのに気づいてしまいました。そうなると、どうしても試してみたくなりますね・・。もう長い長い自粛期間ですからね。

ネットにつながないDIY機器に使う目的なら、こんなに便利なものはない。安心して使ってみるということにしましょう^^;

指紋取込のしかけ

多く使われているのは、光学式、静電容量式、電界式など。スマホ内蔵センサーは静電容量式ですが、ここで使うのは光学式です。

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

説明が少し長くなりそうな予感がします。自分の記録用も兼ねた記事のためすみませんねm(_ _ )m

製作に直接関係しないところは青い文字で書いておきますので、お急ぎの方はそこは読み飛ばしてもかまいません。

光学式ではプリズムの45度斜面に指紋を当て二等面の片側からLED光を照射、反対側面から撮った像が加工されます。ガラス内の45度斜面なので光は全反射しますが、指紋の凸部(隆線)があたる部分だけが乱反射するのを利用して指紋を撮像しています。

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

上の写真は、前面すべてがプリズムの45度の斜面です。そこに指をあてるわけです。

使うセンサーモジュール

試すセンサーモジュールは海外ネットで購入。驚くほど低価格で売られていました。

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

“fingerprint”と&で“AS608”や”FPM-10a”などの検索語を使い、多く出回っていそうなものを選びました。注文後10日で届きました。書かれているカタログ仕様では、セキュリティレベルの設定(1~5)により、FAR(他人を通す間違いFalse Acceptance Ratio)は0.001%未満、FRR(本人を否定する間違いFalse Rejection Ratio)は1.0%未満にセットできます。

指紋登録は127種をセットできるので、1つの指を複数エントリー登録したりすればFRR(否定される間違い)の心配はないでしょうね。別の指を入れるのもいいですし。

指紋の自動照合システムは一昔前ならこの100~1000倍も高価だったと思います。今のしくみならAIが多少使われているかな?しかし調べた限り畳み込み層のマトリクスなどは使われていません。つまり長年の指紋照合技術が撮像素子やMCUの能力向上のおかげでこの性能とコストになった感じです。

このセンサーの場合、内蔵MCUはARM Cortex-MベースのSynoChip社AS608で、144MHz、Flash512KB、RAM128KB。データシートでみると通信インターフェイスはひととおりを装備しています。ただしモジュールの実装は次のようになっていて、UART接続が想定されています。製品によってコネクターのピンの並びが異なりますが、どれもUARTを想定している感じです。

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

さて、こういうDIY向きセンサーモジュールの使い方を調べる最近の格好のサイトは"EleCrow"だと思います。とくに中国で作られたものは満載です。

https://www.elecrow.com/wiki/index.php?title=Main_Page

Fingerprintセンサーのガイドとしては次のようなページがあります。

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

PCでの接続テスト

このサイトでWindows10 PC 用の”SFGdemo”プログラムが紹介され、無料でダウンロードできます。オープンソース・ハードの指紋センサー共通に使えるプログラムのようです。

なお、別途AdafruitのFingerprintライブラリーから同じプログラムがたどれますが、そこに示されているダウンロード対象は、なんとウィルスチェッカーで弾かれます。

EleCrowからのダウンロードは何も問題ありませんでした。

SFGdemoを使うには、PCとセンサーの間をシリアル通信で繋ぐ必要があります。センサーにシリアル~USBコンバータを接続して使ってももいいですが、Arduino-IDEでUSB接続したArduinoUnoをダミーで仲介役にすれば、より簡単ですしセンサーへの電源供給もできて便利ですね。

下に示すダミースケッチをIDEArduino Unoに書き込みIDEを終了します。次にセンサーのRxとTxの端子をそれぞれUnoの同名端子につなぎ、PCでSFGdemoを起動します。そのやり方やSFGdemo操作手順も、EleCrowではしっかり説明されています。

電源供給に注意センサーモジュールへの電源供給は仕様上は3.3Vでも5Vでも良いことになっていますが、使ったセンサーモジュールでは、5V供給でコミュニケーションエラーがしばしば生じていました。調べるとセンサーモジュールが返事をしない状態になります。PCBを観察するとQ6のレギュレータが省かれている感じなので、試しにArduino Uno側から3.3Vを供給してみたら、なんと問題は解消しました!(シリアル信号はそのままです、5V-tolerantのようです)。これはArduinoとの接続の場合も同じですから、製品によってはご注意ください。(2021.9.12追記:コネクターに3.3Vと直接書いてあるモデルなら、信号レベルコンバーターを介して信号線2本とも5Vから製品側を3.3Vに変換する方が安心ですね。それを指紋センサーの複数のモデルやってみましたらどれもそれでOKです。また、製品の若干の発熱もだいぶ減るので3.3Vがお勧めかも。)

PCとの接続テストに使ったダミープログラムは次です。

/***********************************************
 * Dummy sketch for Arduino as;
 *      PC-USB to sensor-Serial converter
 *      and a DC Power-Supply for sensors
 **********************************************/
void setup() {
}
void loop() {
}
// ***** end of sketch *****

 

指紋センサーモジュールが妥当に動くかをこれで調べるついでに、使う指をこのプログラムで登録しておくこともできますね。

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

取り込んだ指紋の画像は解析されバイナリーデータにして扱われます。つまり画像自体はメモリーに取り込まず、データ化されたものがセンサーモジュール内にテンプレートとして蓄積されます。

調べる対象の指紋もデータ化されてから使われます。そういうわけなので、蓄積データは少量ですみ、そこから画像自体を復元することはできませんしシステム的にはその必要もないわけです。

指紋の自動照合

指紋は隆線(凸部)と谷線(凹部)の高低差が約50μm、隆線間が約400μm。模様全体としての特徴もあります。そして幼児から何年たっても変わらない、さらにDNAが同じ双子でも違うという驚くほど精巧なバイオメトリクスですね。

指紋照合には、2世紀以上にわたる知恵により、各指紋の「特徴点」(minutiaマイヌーシア)が現代でも使われています。

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

自動照合の歴史上は指紋の中央点、デルタ部分、ドットなど他の特徴点、あるいは特徴点間の隆線数など、撮像品質や処理性能を補う工夫がなされたものもあります。しかし現代(参照:ISO/IEC19794-2:Cor.1:2012)では、例えば上図に◎で示したような限られた特徴点だけでも照合に十分です。

国際標準では特徴点のxy座標と隆線の方向(右水平からの時計回り角度t)の3データ(x,y,t)が使われます。

xy座標原点は左端中央ですが、(x,y,t)は原点の移動も照合角度の調整も演算だけで簡単にできるわけなので、どこに設定してもよいわけです。特徴点は1指紋がもつ通常50~150点の中の数十だけ(統計学的には12点以上でよい)を使えば十分。ISOの指紋テンプレートではx,yを各2バイト(14ビット)、方向を1バイト、それに品質1バイトとし、1特徴点を6バイトに収めます。ただし中身がどのシステムも共通かと言えばそうではなく、原点位置やアルゴリズムなどにも依存するものです。

照合する際の1指紋は隆線が読みやすいように加工され、このセンサーの場合は検査対象の数十の特徴点が128バイトに収められています。テンプレートファイルとしても1つの指(ID)登録に512バイトです。画像を直接蓄えるよりもずっと効率が良いわけ。

特徴点による指紋照合の原理の発案は、実に19世紀!英国のゴルトン(Sir Francis Galton)が現代の認証技術に使われているMinutia(マイヌーシア=特徴点)での照合法を見出しました。著書Finger Prints(1892年)に詳しく書かれています。分厚いですが次のリンクから無料でダウンロード可能です。

https://galton.org/books/finger-prints/galton-1892-fingerprints-1up.pdf

そして、照合の自動化は1970年にNBS(米国標準局)のJ. H. Wegsteinらが初めて開発しました。この原理が現在の照合方法のベースとして長年使われてきています。このセンサーモジュールでも同じことです。

https://nvlpubs.nist.gov/nistpubs/Legacy/TN/nbstechnicalnote538.pdf

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

余談が長引きましたがここからようやく本題です^^;

センサーに当てる指の向き

指紋センサーにあてる指先はおそらく上向きだろうと想像しますが、モジュール単体だと角度が邪魔をして難しいです。テストのために適当な台をつけてみました。

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

PCにつないでセンサーを確認

上で少し説明しましたが、Arduinoをツールとして使い、センサーをPCと直接シリアル接続する際の様子は次の写真です。使う端子はV+、Gnd、Tx、Rxだけなので他の余計なワイヤーは後でソケットからピンごと抜き取るとか、面倒ならカットしてもいいですね。センサーへの電源供給は前に書いたとおり最終的には3.3Vに変更しました。

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

PCでのテストは全てうまく動きました。SFGdemoでは指紋の映像が見えるので、適切な指の当て方などがわかりました。そして、台に載せてわざわざ指を上向きに当てなくとも、センサーをつかむ形で指を下向きにして登録しても、照合時も同じ向きにすれば何ら問題ないことがわかりました^^

PCでのこのテストではセンサーのTx,RxをそれぞれArduinoピンのTx,Rxにつなぎますが、以後のArduino自体でのテストでのソフトウェアシリアルで使うTx,Rxピンでは逆につなぎますから十分ご注意ください。

 

Arduinoでのテスト開始

ここからはいよいよArduinoでのテストをします。センサーからのTxをArduino―UnoのPin2(ソフトウェアシリアルのRx)へ、RxをPin3(ソフトウェアシリアルのTx)へ接続し直します。

まずは指紋の登録(Enroll)です。

Adafruit_Fingerprintライブラリーのスケッチ例"Enroll"がそのまま使えます。ただし、グローバル変数を922バイト(Unoの45%)も使う状況では、後での好きな加工には支障が出そうです。

「最大32256バイトのフラッシュメモリのうち、スケッチが7002バイト(21%)を使っています。最大2048バイトのRAMのうち、グローバル変数が922バイト(45%)を使っていて、ローカル変数で1126バイト使うことができます。」

よって書き換えて次のように減量しておきました。これだとグローバル変数の18%しか使いません。

(この型の指紋センサーの他のモデルでも動くよう、スケッチを2021.09.12に更新しました。それをつけます。)

/*******************************************************************
   Fingerprint enrollment test V.01  as of Sept.12,2021
      Initial version: Aug.23, 2021  modified by Akira Tominaga
      V.01: Retry-interval shortened for all FP models * 9/12/'21
      using Adafruit_Fingereprint library and example,
      with BSD license, described in the bottom of this sketch.
*******************************************************************/
#include "Adafruit_Fingerprint.h"
#define sRx 2
#define sTx 3
SoftwareSerial sSer(sRx, sTx);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&sSer);
uint8_t id;           // ID# for fingerprint

void setup() { // ***** Arduino setup() *****
  Serial.begin(9600);
  while (!Serial) {}
  delay(100);
  Serial.println(F("\n*Enroll*"));
  finger.begin(57600);
  if (finger.verifyPassword() == false) {
    Serial.println(F("No sensor :("));
    while (true) {}
  }
  Serial.print(F("Sensor"));
  finger.getParameters();
  Serial.print(F(" Stat=0x")); Serial.print(finger.status_reg, HEX);
  Serial.print(F(" ID=0x")); Serial.print(finger.system_id, HEX);
  Serial.print(F(" Capa=")); Serial.print(finger.capacity);
  Serial.print(F(" Sec.lvl=")); Serial.print(finger.security_level);
  Serial.print(F(" Dev.adr=")); Serial.print(finger.device_addr, HEX);
  Serial.print(F(" Pkt=")); Serial.print(finger.packet_len);
  Serial.print(F(" Baud=")); Serial.println(finger.baud_rate);
}

void loop() { // ***** Arduino loop() *****
  Serial.println(F("\nGive ID#(1-127) to set"));
  id = readnumber();
  if ((id == 0) | (id > 127)) { // invalid #, retry!
    return;
  }
  Serial.print(F("#"));
  Serial.println(id);
  while (!getFPtoEnroll() );
}
/*********************************************
 *    User defined functions
 *********************************************/
// ***** read number from Serial input *** readnumber() *****
uint8_t readnumber(void) {
  uint8_t num = 0;
  while (num == 0) {
    while (! Serial.available());
    num = Serial.parseInt();
  }
  return num;
}
// ***** get fingerprint enroll ***  getFPtoEnroll() *****
uint8_t getFPtoEnroll() {
  int p = -1;
  Serial.println(F("Place finger")); 
  while (p != FINGERPRINT_OK) {
    p = finger.getImage();
    switch (p) {
      case FINGERPRINT_OK:
        Serial.println(F("Img taken"));
        break;
      case FINGERPRINT_NOFINGER:
        break;
      case FINGERPRINT_PACKETRECIEVEERR:
        Serial.println(F("Com.err"));
        break;
      case FINGERPRINT_IMAGEFAIL:
        Serial.println(F("Img err"));
        break;
      default:
        Serial.println(F("Unknown err"));
        break;
    }
  }
  // OK success!
  p = finger.image2Tz(1);
  switch (p) {
    case FINGERPRINT_OK:
      Serial.println(F("Img Okay")); 
      break;
    case FINGERPRINT_IMAGEMESS:
      Serial.println(F("Img messy"));
      return p;
    case FINGERPRINT_PACKETRECIEVEERR:
      Serial.println(F("Com.err"));
      return p;
    case FINGERPRINT_FEATUREFAIL:
      Serial.println(F("No feature"));
      return p;
    case FINGERPRINT_INVALIDIMAGE:
      Serial.println(F("Img err"));
      return p;
    default:
      Serial.println(F("Unknown err"));
      return p;
  }
  Serial.println(F("Remove finger"));
  delay(2000);
  p = 0;
  while (p != FINGERPRINT_NOFINGER) {
    p = finger.getImage();
  }
  Serial.print("For ID#"); 
  Serial.print(id);
  p = -1;
  Serial.println(F(", same finger again"));
  while (p != FINGERPRINT_OK) {
    p = finger.getImage();
    switch (p) {
      case FINGERPRINT_OK:
        Serial.println(F("Img taken"));
        break;
      case FINGERPRINT_NOFINGER:
        break;
      case FINGERPRINT_PACKETRECIEVEERR:
        Serial.println(F("Com.err"));
        break;
      case FINGERPRINT_IMAGEFAIL:
        Serial.println(F("Img err"));
        break;
      default:
        Serial.println(F("Unknown err"));
        break;
    }
  }
  // OK success!
  p = finger.image2Tz(2);
  switch (p) {
    case FINGERPRINT_OK:
      Serial.println(F("Img converted"));
      break;
    case FINGERPRINT_IMAGEMESS:
      Serial.println(F("Img messy"));
      return p;
    case FINGERPRINT_PACKETRECIEVEERR:
      Serial.println(F("Communication .err"));
      return p;
    case FINGERPRINT_FEATUREFAIL:
      Serial.println(F("No feature"));
      return p;
    case FINGERPRINT_INVALIDIMAGE:
      Serial.println(F("Img err"));
      return p;
    default:
      Serial.println(F("Unknown err"));
      return p;
  }
  // OK converted!
  Serial.print(F("Checking FP#"));  Serial.println(id);
  p = finger.createModel();
  if (p == FINGERPRINT_OK) {
    Serial.println(F("Matched!"));
  } else if (p == FINGERPRINT_PACKETRECIEVEERR) {
    Serial.println(F("Com.err"));
    return p;
  } else if (p == FINGERPRINT_ENROLLMISMATCH) {
    Serial.println(F("No match"));
    return p;
  } else {
    Serial.println(F("Unknown err"));
    return p;
  }
  Serial.print(F("*** FP#")); Serial.print(id);
  p = finger.storeModel(id);
  if (p == FINGERPRINT_OK) {
    Serial.println(F(" enrolled! ***"));
  } else if (p == FINGERPRINT_PACKETRECIEVEERR) {
    Serial.println(F("Com.err"));
    return p;
  } else if (p == FINGERPRINT_BADLOCATION) {
    Serial.println(F("Bad loc."));
    return p;
  } else if (p == FINGERPRINT_FLASHERR) {
    Serial.println(F("Flash err"));
    return p;
  } else {
    Serial.println(F("Unknown err"));
    return p;
  }
  return true;
}
/*******************************************************************
  This is an example sketch for our optical Fingerprint sensor
  Designed specifically to work with the Adafruit BMP085 Breakout
  ----> http://www.adafruit.com/products/751
  These displays use TTL Serial to communicate, 2 pins are required to
  interface
  Adafruit invests time & resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!
  Written by Limor Fried/Ladyada for Adafruit Industries.
  BSD license, all text above must be included in any redistribution.
*******************************************************************/
// ***** end of program *****

 

指紋の登録

そして指を登録(Enroll)しますが、テストでは右手は操作に使うので、左手の人差し指、中指、薬指を登録しました。念のためにそれぞれIDを3つ登録して、人差し指(1,11,21)、中指(2,12,22)、薬指(3,13,23)としておきました。こうすれば多少乱暴に指をあててもRejectされて困るようなことはないでしょうからね。

Arduino IDEのシリアル出力は次のようなものになります。


*Enroll*
Sensor Stat=0x0 ID=0x0 Capa=150 Sec.lvl=3 Dev.adr=FFFFFFFF Pkt=128 Baud=57600

Give ID#(1-127) to set
#1
Place finger
Img taken
Img Okay
Remove finger
For ID#1, same finger again
Img taken
Img converted
Checking FP#1
Matched!
*** FP#1 enrolled! ***

Give ID#(1-127) to set
#2
Place finger
Img taken
Img Okay
Remove finger
For ID#2, same finger again
Img taken
Img converted
Checking FP#2
Matched!
*** FP#2 enrolled! ***

Give ID#(1-127) to set

 

指紋の照合

次に、指紋の照合(Identify)をします。

Adafruit_Fingerprintライブラリーのスケッチ例Fingerprintがそのまま使えますが、グローバル変数を同様にたっぷり使うため、そうはいきません。こちらは今後確実に応用に使うわけなので。次のように大きく減量して必要最小限に書き換えました。十分小さいですね!これならUnoでも余裕たっぷりです。エラー検出時の処理は、本番ではどっちみち再処理を実行するわけなので、余計なメッセージ出力を削減。

(この型の指紋センサーの他のモデルでも動くよう、スケッチを2021.09.12に更新しました。それをつけます。)

/*******************************************************************
   Fingerprint identification test V.01
      Initial version: Aug.23, 2021  modified by Akira Tominaga
      V.01: Retry-interval shortened for all FP models   * 9/12/'21
      using Adafruit_Fingereprint library and examples,
      with BSD license, described in the bottom of this sketch.
*******************************************************************/
#include "Adafruit_Fingerprint.h"
#define sRx 2
#define sTx 3
SoftwareSerial sSer(sRx, sTx);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&sSer);

void setup() { // ***** Arduino setup() *****
  Serial.begin(9600);
  Serial.println(F("\n*FP identification*"));
  finger.begin(57600);
  if (finger.verifyPassword() == false) {
    Serial.println(F("No sensor :("));
    while (true) {}
  }
  finger.getTemplateCount();
  if (finger.templateCount == 0) {
    Serial.print(F("No template"));
    while (true) {};
  }
}
void loop() { // ***** Arduino loop() *****
  Serial.print(F("\nPlace finger."));
  while (finger.getImage() != FINGERPRINT_OK) {
  }
  while (finger.image2Tz() != FINGERPRINT_OK) {
  }
  uint8_t p = finger.fingerSearch();
  if (p == FINGERPRINT_OK) {
    uint8_t fID = finger.fingerID;
    uint8_t fCf = finger.confidence;
    Serial.print(F("\n*** Match! with #")) ;
    Serial.print(fID);
    Serial.print(F(" confidence="));
    Serial.println(fCf);
    delay(5000);
  } else if (p == FINGERPRINT_NOTFOUND) {
    Serial.println(F("\n*** No match!"));
    delay(1500);          // wait after unmatch
  } else {
  }                       // in other cases, do nothing
  delay(500);             // loop in a slow pace
}
/*******************************************************************
  This is an example sketch for our optical Fingerprint sensor
  Designed specifically to work with the Adafruit BMP085 Breakout
  ----> http://www.adafruit.com/products/751
  These displays use TTL Serial to communicate, 2 pins are required to
  interface
  Adafruit invests time & resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!
  Written by Limor Fried/Ladyada for Adafruit Industries.
  BSD license, all text above must be included in any redistribution.
*******************************************************************/
// ***** end of program *****

 

これで照合をするとArduino IDEのシリアルに結果が正しく出ます。指の置き方によってリジェクト(FRR)が出ることがありますが、指の置き方をやり直せばよいだけです。

間違い容認(FAR)のほうは色々やっても全く生じません。たいしたものです!!

Arduino IDEのシリアル出力は次のようなものです。


*FP identification*

Place finger.
*** No match!

Place finger.
*** Match! with #1 confidence=169

Place finger.

Confidenceは合致のスコア集計数値です。50ぐらい以上が合致とされるようですが、セキュリティレベルの設定によってその閾値が決まるようです。セキュリティレベルの設定は今後やってみることにします。

 

プロトコルの確認

センサーとはどのようなやり取りをしているでしょうか。オシロで観察します。

最初のパスワード確認プロトコルは次のもの。

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

意外に単純そうですね。それなら、というわけでロジックアナライザーでやり取りを確認してみました。これは照合ですがプロトコルはわりと単純で意外にスピーディです。

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

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

全体をデコードした例が次です。

	Time [s]	Decoded 
	Tx	
	0.0000000	0xEF
	0.00018625	0x01
	0.0003795	0xFF
	0.00056525	0xFF
	0.00075125	0xFF
	0.00093725	0xFF
	0.0011230	0x01
	0.00131675	0x00
	0.00150425	0x07
	0.00169225	0x13
	0.0018805	0x00
	0.00206925	0x00
	0.0022580	0x00
	0.0024530	0x00
	0.00264150	0x00
	0.00282950	0x1B
	Rx	
	0.00395725	0xEF
	0.00414875	0x01
	0.00434025	0xFF
	0.00453175	0xFF
	0.00472325	0xFF
	0.0049150	0xFF
	0.0051065	0x07
	0.0052980	0x00
	0.0054895	0x03
	0.0056810	0x00
	0.0058725	0x00
	0.0060640	0x0A
	Tx	
	0.00634925	0xEF
	0.0065415	0x01
	0.00672875	0xFF
	0.0069145	0xFF
	0.0070945	0xFF
	0.00728025	0xFF
	0.00747225	0x01
	0.0076600	0x00
	0.00784725	0x03
	0.00803575	0x1D
	0.0082235	0x00
	0.0084115	0x21
	Rx	
	0.00975175	0xEF
	0.00994325	0x01
	0.0101350	0xFF
	0.0103265	0xFF
	0.0105180	0xFF
	0.0107095	0xFF
	0.01090125	0x07
	0.01109275	0x00
	0.01128425	0x05
	0.01147575	0x00
	0.0116675	0x00
	0.0118590	0x09
	0.01205075	0x00
	0.01224225	0x15
	Tx	
	0.01254425	0xEF
	0.0127365	0x01
	0.01292375	0xFF
	0.0131095	0xFF
	0.0132955	0xFF
	0.0134815	0xFF
	0.01368125	0x01
	0.01386875	0x00
	0.01405625	0x03
	0.0142445	0x01
	0.01443275	0x00
	0.01463475	0x05
	Rx	
	0.095171	0xEF
	0.0953625	0x01
	0.095554	0xFF
	0.0957455	0xFF
	0.095937	0xFF
	0.0961285	0xFF
	0.096320	0x07
	0.0965115	0x00
	0.096703	0x03
	0.0968945	0x02
	0.097086	0x00
	0.0972775	0x0C

 

データシートに基づいてコマンドとレスポンスをよく整理すれば、Arduino以外のマイコンでも使えるようになりそうですね。今後のお楽しみとして。。

単体で使える形に

ここまでで、このセンサーが優れものであることと、うまく扱えることがよくわかりました。

Arduino単体で動かす例として、とりあえずはLCD出力をしてみました。それがこの記事冒頭の写真です。テストの様子の動画は次です。

youtu.be

この例のスケッチは次です。

(この型の指紋センサーの他のモデルでも動くよう、スケッチを2021.09.12に更新しました。それをつけます。)

/*******************************************************************
   Fingerprint identification & show the results to LCD   V.01
      Initial version: Aug.23, 2021  modified by Akira Tominaga
      V.01: Retry-interval shortened for all FP models * 9/12/'21
      using Adafruit_Fingereprint library and examples,
      with BSD license, described in the bottom of this sketch.
*******************************************************************/
#include "Adafruit_Fingerprint.h"
#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);  // Set address and pins
#define lcdC 20
#define lcdL 4
#define sRx 2
#define sTx 3
SoftwareSerial sSer(sRx, sTx);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&sSer);

void setup() { // ***** Arduino setup() *****
  Serial.begin(9600);           // init. h/w serial for debug
  lcd.begin(20, 4);             // init. 20 x 4 LCD
  lcd.backlight();              // turn on lcd-backlight
  lcd.setCursor(0, 0);
  lcd.print(F("* Fingerprint test *"));
  lcd.setCursor(1, 3);
  lcd.print(F("(a-tomi.hatenablog)"));
  finger.begin(57600);          // init sSer and finger
  if (finger.verifyPassword() == false) {
    lcd.setCursor(3, 1);
    lcd.print(F("Error: No sensor."));
    while (true) {}
  }
  finger.getTemplateCount();
  if (finger.templateCount == 0) {
    lcd.setCursor(1, 1);
    lcd.print(F("Error: No template."));
    while (true) {};
  }
}
void loop() { // ***** Arduino loop() *****
  lcd.setCursor(2, 1);
  lcd.print(F("Place finger...   "));
  while (finger.getImage() != FINGERPRINT_OK) {
    lcd.setCursor(2, 2);
    lcd.print("  ...             ");
  }
  while (finger.image2Tz() != FINGERPRINT_OK) {
    lcd.setCursor(2, 2);
    lcd.print("  ,,,              ");
  }
  uint8_t p = finger.fingerSearch();
  if (p == FINGERPRINT_OK) {
    uint8_t fID = finger.fingerID;
    uint8_t fCf = finger.confidence;
    lcd.setCursor(2, 1);
    lcd.print(F("Match! with ID="));
    lcd.print(fID);
    lcd.setCursor(2, 2);
    lcd.print(F("confidence = "));
    lcd.print(fCf);
    lcd.print("  ");
    delay(5000);
  } else if (p == FINGERPRINT_NOTFOUND) {
    lcd.setCursor(2, 1);
    lcd.print(F("No match!         "));
    lcd.setCursor(2, 2);
    lcd.print(F("                  "));
    delay(2500);          // wait after unmatch
  } else {
  }                       // in other cases, do nothing
  delay(500);             // loop in a slow pace
}
/*******************************************************************
  This is an example sketch for our optical Fingerprint sensor
  Designed specifically to work with the Adafruit BMP085 Breakout
  ----> http://www.adafruit.com/products/751
  These displays use TTL Serial to communicate, 2 pins are required to
  interface
  Adafruit invests time & resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!
  Written by Limor Fried/Ladyada for Adafruit Industries.
  BSD license, all text above must be included in any redistribution.
*******************************************************************/
// ***** end of program *****

 

おまけですが、単にLEDでどの指かを示す装置。

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



おわりに

これなら指紋認証ロックなどはとても簡単にできそうですね。そのうち応用したいと思います。

以上、自分用の記録も兼ねて少し冗長でしたが、何らかのお役に立てば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

 

LEDテープで大ディスプレイを! (LEDテープその3)

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

お馴染みピクトグラムArduinoが次々に描きます。このディスプレイは安価なLED配列フィルムですが、Arduinoから1ワイヤで高速に描画できます。

これまでに書いたLEDテープ記事の続編ですが、前の2つの記事のリンクは次です。

カラーLEDテープ照明を自由にコントロール - 勝手な電子工作・・

LEDテープを使ったディスプレイの実験(Arduino-Unoで) - 勝手な電子工作・・

 

次のような大型、といっても8x32で256個のLEDが配列された柔らかいシートが売られているのを見つけました。AliExpressで1枚約800円でしたが、注文したら珍しく翌週にサッと到着。LEDテープというよりLEDシートというべきかもしれません。

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

しめしめ、これを3段つなげば32x24ドットができるぞ!

毎度ながら簡単・迅速に作りたいですし、また、後で部品に戻せるようにしたいもの。ここでは両面テープでプラバンに貼り付け、裏から配線することにします。

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

真ん中は電源供給ラインを並列につなぎます。信号線は最初の入り口は左上に(信号線とグランドのみ)が入り、右上から出た最後を真ん中左へ(信号線のみ1本でOK、グランドは電源側で接続されているわけなので)と配線。同じく中右から出た最後を左したへと配線して終わり。じつに単純ですね^^ 動かないようにセロテープで止めて出来上がり。電源は5V2AのACアダプターで十分でした。

32x24とはいえ、24x32と見る方が分かりやすい。とはいってもLEDの接続順序は表から見ると次のように少し複雑になります。これをXY座標として扱って、プログラム論理でこれにあうようにすればよいだけです。

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

柔らかいシートとはいっても、両面テープは時間が経つと、次の写真のようにジワリと剥がれて浮くところが少しでます。強力両面テープを使えばこうはなりませんが、後で分解しいやすくするためには、この程度ですかね。

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

見易さを兼ねて、たまたま手元にある薄黒い半透明アクリル板をこの上に重ねて使ってみます。なにせLEDは相当に眩しいですから。また、こうすることでディスプレイの平面も浮くことなく保てています。

FastLEDライブラリーは容量を食わずに便利ですが、この場合はLED数768個x3バイト(3色の値)ぶんの配列を内部でこしらえるため、ArduinoはMega2560を使うことにします。

画像データはPCのシリアルから送る方法でもよいと思いますが、持ち運び用に、ここではマイクロSDカードから入力することにしました。

1つのファイルが画面1頁です。中身は3色のデータ#RRGGBBを768個収容したファイルで、ファイル名を順次変化させることで、自動的にページを順番に読ませることにしました。

そのArduinoスケッチは後ろにつけておきます。

簡単にファイルを作るために、画像を24x32ピクセルBMPにして、それをPCで読み込ませます。簡単なSmallBasicでテスト用にちょこっと作ったWindows用のプログラムですが、ご参考のため記事の最後に添付しておきます。

そして表示の一例が次です。

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

実際はとても綺麗なのですが、LEDの輝度が高過ぎてうまく撮れませんm(_ _ )m

活字も、「ドットフォント」を絵と同じように扱えば大丈夫。ここで使っているドットフォントはフリーのKHドットフォントのなかにある、小伝馬町16というフォントです。True Typeフォントがしっかり提供されていますので、絵として使うのにも適しています。色々便利に使わせていただいています。OLEDなどの表示も画像としてとり扱うと手間がかからないのでなかなかいい手段ではないかなと勝手に思います。もちろんコードで直接扱うのがベストではありますが。

 

次がArduino-Megaのスケッチです。

/********************************************************
   LED panel display for Arduino-Mega
      Read & show RGB data from "RGBtnnn.txt" files.
      Version 00  Aug.8, 2021 by Akira Tominaga
*********************************************************/
#include "FastLED.h"
#include "SPI.h"
#include "SD.h"
#define dPin 8                // data pin to LED tape
#define CS 53                 // SD MISO=50,MOSI=51,SCK=52
uint16_t X = 0, Y = 0, Pnl = 0; // column, row, and panel
#define maxX 24               // maximum X in total
#define maxY 32               // maximum Y
#define nPnls 3               // nnumber of panels (films) 
#define pnlX (maxX/nPnls)     // X length of each panel
#define NinPnl (pnlX*maxY)    // number of LEDs in a panel   
#define nLEDs (NinPnl*nPnls)  // number of total LEDs
CRGB myTp[nLEDs];             // construct myTp array
File mySD;                    // construct mySD
String fNbase = "RGBt";       // for file name RGBtnnn.txt
String fNext = ".txt";        // file name extention
uint16_t fNum = 0;            // file number = page (max999)
char fN[4];                   // file number in char
String fName = "";            // file name
#define fRlen 9               // file rec length 7char+CRLF
char SDin[fRlen + 1];         // SD-in area with CRLF + Null
uint8_t vRGB[3];              // temporally RGB work data

void setup() { // ***** Arsuino setup() *****
  Serial.begin(9600);
  delay(2000);                // enough time for LED pwr
  if (!SD.begin(CS)) {
    Serial.println(F("SD err."));
    while (true);
  }
  FastLED.addLeds<WS2812, dPin, GRB>(myTp, nLEDs);
  clrLED();
}
void loop() { // ***** Arduino loop() *****
  setFname();                 //next file name to open
  Serial.println(fName);
  clrLED();
  XY2LED();       // read RGB@(X,Y) and set to LED positions
  FastLED.show();             // show the page
  delay(1000);                // time to display a page
  mySD.close();               // close current page
  fNum++;                     // set next file(=page) number
}
/************************************************
    User defined functions
 ************************************************/
// ***** clear LED *** clrLED() *****
void clrLED(void) {
#define Black (0,0,0)
  for (int i = 0; i < nLEDs; i++) {
    myTp[i].setRGB Black;
  }
  FastLED.show();
}
// ***** set file name of next page *** setFname() *****
void setFname(void) {
  sprintf(fN, "%03d", fNum);
  fName = fNbase + String(fN) + fNext;
  mySD = SD.open(fName);      // open a page
  if (!mySD) {                // if file not found
    Serial.print(F("No "));
    Serial.println(fName);
    if (fNum > 0) {           // if no more page
      delay(1000);            // wait for a second
      fNum = 0;               // and return to page 0
      fName = fNbase + "000" + fNext; // restart from p.0
      mySD = SD.open(fName);  // and open page 0
    } else {                  // if page 0 file not found
      while (true) {}         // then stop
    }
  }
}
// ***** get RGB[](X,Y) val and set to LED pos. *** XY2LED() *****
void XY2LED(void) {
  uint16_t sLED;              // sequence # of LED from the top 
  for (Y = 0; Y < maxY; Y++) {
    for (X = 0; X < maxX; X++) {
      Pnl = X / pnlX;         // get panel number
      uint16_t nC = X % pnlX; // get X (column #) in that panel
      readRGB();              // read record for RGB values
      if (Y % 2 == 1) {       // for Ys 1,3,5,,,
        sLED=nC + Pnl * NinPnl + Y*pnlX;
      } else {                // for Ys 0, 2,4,,, reverse X sequence
        sLED=(pnlX - 1 - nC) + Pnl * NinPnl +  Y*pnlX;
      }
      myTp[sLED].setRGB(vRGB[0], vRGB[1], vRGB[2]);
    }
  }
}
// ***** read SD for RGB data *** readRGB() ***
void readRGB(void) {
  while (!mySD.available()) {}
  mySD.read(SDin, fRlen);     // read a record
  for (int i = 0; i < 3; i++) {  // for RGB data
    byte Vh = SDin[i * 2 + 1] & 0x0F; // if numeric, get value
    if (SDin[2] > 0x40) {     // if alfabet,
      Vh = SDin[i * 2 + 1] - 55; // set alphabetic value
    }
    byte Vl = SDin[i * 2 + 2] & 0x0F; // if numeric, get value
    if (SDin[2] > 0x40) {     // if alfabet,
      Vl = SDin[i * 2 + 2] - 55; // set alphabetic value
    }
    vRGB[i] = (Vh * 16 + Vl) / (3); // divide R|G|B w/ 3 
  }
}
// ***** end of program *****

 

読み込むファイルには次の形式の固定長レコードが、LEDの個数分並んでいます。

#RRGGBB\n

RR、GG、BBは赤、緑、青の強さで、0からFFで表します。しかし、そのままではLEDが明るすぎるため、Arduinoで読んだら各値を1/3に落としています。節電にも重要ですね。

ファイル1個が画面の1頁となり、Arduinoはファイル番号を増やしながら次々読み、次のファイルがなくなると最初に戻ります。

そのファイルをWindowsPCでBMP画像から自動生成する簡単なプログラムは次です。Small Basicでちょこっと作ったテスト用ですが。

myTitle="RGB text builder   Aug.8,2021 (c)Akira Tominaga"
TextWindow.Title=myTitle
TextWindow.BackgroundColor="Black"

' Get data path
myPath=File.GetSettingsFilePath()
myPath=Text.GetSubText(myPath,1,8)
myPath=myPath+"DatW\"

' Prepare picture
TextWindow.WriteLine("")
KeyIn:
TextWindow.Write("     Please type-in RGBt#. -> ")
keyinN=TextWindow.Read()
myPict=myPath+"RGBt"+keyinN+".bmp"
PsizeX=ImageList.GetWidthOfImage(myPict)
If PsizeX>5 Then
  Goto ImageOK
Else
  TextWindow.Write("*** No such bmp as ")
  TextWindow.WriteLine(myPict)
  Goto Keyin
EndIf
ImageOK:
PsizeY=ImageList.GetHeightOfImage(myPict)
TextWindow.Write("Xsize=")
TextWindow.WriteLine(PsizeX)
TextWindow.Write("Ysize=")
TextWindow.WriteLine(PsizeY)

'Setup output file
myFile=myPath+"RGBt"+KeyinN+".txt"
'lnum=1 'PsizeX,PsizeY (3chars left-zero)
Xs=Text.GetSubText(1000+PsizeX,2,3)
Ys=Text.GetSubText(1000+PsizeY,2,3)
header=Xs+","+Ys
'File.WriteLine(myFile,lnum,header)
lnum=0

'Graphics window
GraphicsWindow.BackgroundColor = "Black"
GraphicsWindow.Width=PsizeX*45
GraphicsWindow.Height=PsizeY*22
GraphicsWindow.Title=myTitle
inPic=ImageList.LoadImage(myPict)
GraphicsWindow.DrawImage(inPic,0,0)
For j= 0 to PsizeY-1
  For i=0 To PsizeX-1
    color=GraphicsWindow.GetPixel(i,j)
     TextWindow.WriteLine(Color)
     lnum=lnum+1
     File.WriteLine(myFile,lnum,Color)
     
    'Draw big image
    XXb=PsizeX+2
    YYb=PsizeY+2
    XX=i*20+XXb
    YY=j*20+YYb
    GraphicsWindow.BrushColor=Color
    GraphicsWindow.FillRectangle(XX,YY,20,20)

  EndFor
EndFor

lnum=lnum+1
eof="FileEnd"
File.WriteLine(myFile,lnum,eof)
TextWindow.WriteLine("**** txt file has been made. Next pls!")
Goto KeyIn

 

表示が粗くてもとにかく大きなディスプレイが要る際には、こうして作ると便利だと思います。

以上、簡単な記事でしたが皆様のなんらかのお役に立てば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

 

LEDテープを使ったディスプレイの実験(Arduino-Unoで)

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

LEDテープの自由な操作のために、前は速いマイコン(Teensy4.0)を使いましたが、今回はArduino-UnoでLEDテープをディスプレイ代りに使えるかのごく簡単な実験です。よって記事も短いですが。

前回の記事は次です。

カラーLEDテープ照明を自由にコントロール - 勝手な電子工作・・

その後、ネットで次のような密なLEDテープ(の半端1m)を入手しました。

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

ほぼ長さ7mmに1つのRGB-LED (SMD-5050) が並んでいて、コントロール用のIC(WS2812B)もそれぞれに組み込まれているという密なLEDテープです。

こうなると、これでどうしてもDisplayを作ってみたくなってしまいます^^;

しかしそれにはテープを切り崩さなければならないうえ、配線にもちょっとした手間がかかりそう。いい方法がないかなあ・・思っていたら、既にプレート状に配線されている手ごろなのをみつけました。

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

これを並べる方がテープを切って並べるよりは簡単かな?しかし4x4のエレメントではどうにもなりません。そうはいっても数字を出す実験なら最低4x6でできそうだから、モノは試し、やってみよう・・・

というわけで、4つを次のように組み合わせてみます。

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

Arduinoからの信号線は1本。これでもかなり速度の速い表示が可能ですからやってみる価値はありますね。今回はFastLEDライブラリーを使います。

適当につなぎましたが、ややこしいことに次の接続順序になってます。

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

各行を左から右へとつなぐと使いやすいでしょうが、まあ、これでもよいとします。どっちみちソフトでコントロールするわけだから。

というわけで、2桁の数値を表示してみます。どうせならカウントダウン装置にしてみます。最小の4x6を2桁表示すると下に2行あまるわけなので、ここに下線を入れ、残り秒数が少なくなるにつれて下線の色が変わるとか・・

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

LEDがあまりにも明るくて写真が撮りにくいのなんの・・・

というわけできれいな実物と違って、次の動画も周りは暗く映らざるを得ませんが。

youtu.be

LEDの明るさ設定コントロールと撮影のテクニックでもっときれいに撮れるように思いますが、とりあえずはご勘弁くださいm(_ _ )m

Unoのテスト用スケッチは次の通りです。

/********************************************************
*	LED tape panel using FastLED  for Arduini-UNO	*
*	  Test version 00  Aug.1, 2021 by A.Tominaga	*
*********************************************************/
#include "FastLED.h"
#define nLEDs 64            	// number of total LEDs
#define dPin 14             	// data pin D14=A0
CRGB myTp[nLEDs];		// construct myTp array
// define pattern of numeric char
const byte Num[10][3] = {{0x69, 0x99, 0x96}, {0x26, 0x22, 0x27},
  {0x69, 0x12, 0x4F},  {0xF1, 0x21, 0x96}, {0x22, 0x6A, 0xF2},
  {0xF8, 0xE1, 0x1E},  {0x24, 0x8E, 0x9E}, {0xF1, 0x12, 0x48},
  {0x69, 0x69, 0x96},  {0x69, 0x71, 0x24}
};
// define color
#define White (18,18,18)
#define Blue (0,0,24)
#define Green (0,24,0)
#define Yellow (20,20,0)
#define Red (24,0,0)
#define Black (0,0,0)
// for count-down 50 to 0
int8_t Nr = 50;             	// number from 50 to 0
uint8_t lNr, rNr;           	// left & right digit of Nr

void setup() { // ***** Arsuino setup() *****
  Serial.begin(9600);
  delay(2000);
  FastLED.addLeds<WS2812, dPin, GRB>(myTp, nLEDs);
  clrLED();
}

void loop() { // ***** Arduino loop() *****
  lNr = Nr / 10;		// left digit
  rNr = Nr - lNr * 10;		// right digit
  editPat('L', lNr);		// edit left digit
  editPat('R', rNr);		// edit right digit
  drawBar();			// draw under-bar
  FastLED.show();		// and show them
  delay(1000);
  if (Nr==0){			// if counted done
    delay(5000);		// add long delay
  }
  Nr = Nr - 1;			// update number
  if (Nr < 0) {
    Nr = 50;
  }
  clrLED();			// clear LED tape
}

void editPat(byte LR, uint8_t Su) {
  for (uint8_t i = 0; i < 3; i++) {
    byte Byt = Num[Su][i];       // get row i
    for (uint8_t j = 0; j < 8; j++) { // column j
      uint8_t mask = B10000000  >> j;  // set mask
      if (mask & Byt) {         // if Num[Su,bit] on,
        uint8_t k = i * 8 + j;  // then calc. position
        //  if row 1,3,5,.. byte-sequences are opposite
        uint8_t l = k;
        if ((k / 4) % 2 == 1) { // if rows 1,3,5,..
          l = k + 3 - (k % 4) * 2; // reverse sequences
        }
        if (LR == 'L') {        // for left digit
          myTp[l].setRGB White;
        } else {                 // for right digit
          myTp[l + nLEDs / 2].setRGB White;
        }
      }
    }
  }
}

void drawBar(void) {
  for (uint8_t l = 28; l < 64; l++) {
    if ((l < 32) | (l >= 60)) {
      myTp[l].setRGB Blue;
      if (Nr < 40) {
        myTp[l].setRGB Green;
      }
      if (Nr < 30) {
        myTp[l].setRGB Yellow;
      }
      if (Nr < 20) {
        myTp[l].setRGB Yellow;
      }
      if (Nr < 10) {
        myTp[l].setRGB Red;
      }
    }
  }
}
  void clrLED(void) {
    for (int i = 0; i < nLEDs; i++) {
      myTp[i].setRGB(0, 0, 0);
    }
    FastLED.show();
  }

 

エレメントをどんどん増やせば明るくて見やすいきれいなディスプレイができますね!ただ、この状態でもLEDテープは100mAほど消耗しますから、エレメント数を大幅に増やすときは、別電源から供給する必要があります。

 

以上、ごく簡単ですが、どうしてもやってみたかったので^^ ・・・ 簡単ですが記録として残しておきます。

もしもお役にたてば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

鳥除け器(ひょっとすると画期的!?)

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

毎朝欠かさずベランダに来てたくさんのフンをしていくスズメ達。前回の記事で書いた鳥除け用ブリンカーを3つ設置後は、なんと全く来なくなりました!ピタッとやんで1週間・・・これが続けばビックリだし拍子抜けですね。

な~んだ、こんなので良かったのか!

世に出回る鳥除けテープや、様々な道具もそのうち併設が必要になるかな?と考えてはいたのですが、今のところはたったのこれだけで済んでいます。

ちなみに、これにまつわる前回の記事は次です。

省エネ用のSleep + Watch Dog Timer(Arduinoでもできる!) - 勝手な電子工作・・

ただ、前の装置は夜中にもピカリと強烈な光を出すため、近所に迷惑そう。暗い時は、鳥が来ないわけなので光らないようにしたい。この際きちんと作り直すことにしました。

明るさ検知用にI2C照度センサーをと考えましたが、何よりもしかけが単純ですむCdSセルを使うことにします。CdSは最近は敬遠され気味ですが、明るさで抵抗値が大きく変わる硫化カドミウム半導体です。 しかも暗い環境でも変化をよく検出します。

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

CdSの精度は大抵の人が考えるよりも高く、照度と抵抗変化の直線性、ヒステリシスの少なさ、温度補正が通常ほぼ不要な点、などの利点があります。価格は今Aliでみる限りは、1例として次のように20個で72円。送料をいれても1個10円しません。

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

こういうCdSセンサーの典型的な仕様の例を示します。 本来ずいぶん暗い状態までを測れるのが分かります。

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

通常の使用範囲(0~30℃)だと、あまり変化せず次のとおり、温度係数は僅か±0.002/℃です。

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

ただし測定回路には少し工夫を要します

単に固定Rと直列にして間の分圧をADCに入力する形では、暗い領域内での変化の検知はできません。なぜなら、暗いと1MΩオーダーにも変化するわけなのに、入力抵抗が低い10~20kΩのADCで分圧測定をすることはできないからです(ADCがCdSと並列にはいってしまうためです)。

そこで、測定回路をチョットだけ工夫すれば、暗闇の中でも変化を正確に測定できます。次は筆者のやり方ですが、エミッタフォロワー回路をつなぐもの。これによって測定の入力インピーダンスを100倍以上高めることができます。そして、測定値は正確な分圧-0.65V(Vbe)となり、温度の影響も受けないごく単純な回路でできます。

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

R1には高抵抗(例:51kΩとか100kΩとか)を安心して使うことができます。R2を3kΩ程度にすればADC入力インピーダンスの影響が防げます。トランジスタはNPN型ならこの型に限りませんが、要するにインピーダンス変換をするわけです。

そして次の回路にしてArduinoで実験しました。

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

D2ピンから先ほどの回路へ電力供給をオンオフしますが、2mA未満なので問題ないです。そしてエミッタフォロワーの出力(上図でDarkness)をA3ピンで読みます。値が高いほど暗いわけです。

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

Arduinoのテストプログラムは次のものです。

/*******************************************************
   Bird-Guard-Blinker for Arduino-Uno (ATmega328P)
      Initial version V00 7/24, 2021 (c) Akira Tominaga
      Function:
        Blink bright LED to expel birds away
         -Stop blinks if in enough dark environment.
         -Save battery energy by deep-sleep
 *******************************************************/
#include "avr/sleep.h"
#include "avr/wdt.h"
#define LED 9             // super bright LED
#define CdSval A3         // darkness val. w/ CdS circuit
#define CdSpwr 2          // Power supply for CdS circuit

void setup() { // ***** Arduino setup() *****
  // sleep time definitions for Watch-dog-timer
#define sT1 B00000110     // 1 second
#define sT2 B00000111     // 2 seconds
#define sT4 B00100000     // 4 seconds
#define sTQ B00000100     // 1/4 second
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);
  pinMode(CdSpwr, OUTPUT);
  digitalWrite(CdSpwr, LOW);
  Serial.begin(9600);
  // for Sleep and Watch-dog-timer
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}

void loop() { // ***** Arduino loop() *****
  #define cdsPon 40	   // CdS power-on time before ADC
  #define LEDoT 25         // LED-on time = 25mS
  digitalWrite(CdSpwr, HIGH);
  delay(cdsPon); 	   // wait for stability
  uint16_t Darkness = analogRead(CdSval);
  digitalWrite(CdSpwr, LOW);
  //Serial.print("Darkness=");
  //Serial.println(Darkness);
  if (Darkness <= 767) {    // if not dark (<= 75% Vref)
    // *** Step1: LED-on once and wait 1 sec
    digitalWrite(LED, HIGH);// LED on
    delay(LEDoT);           // 
    digitalWrite(LED, LOW); // and off
    deepSleep(sT1);         // sleep + ADCpower-off
    // ** Step2: LED-on once again and wait 1 sec
    digitalWrite(LED, HIGH);// LED on
    delay(LEDoT);           // 
    digitalWrite(LED, LOW); // and off
    deepSleep(sT1);         // sleep + ADCpower-off
    // ** Step: LED-on twice with short sleep, & 2 sec
    digitalWrite(LED, HIGH);// LED on
    delay(LEDoT);           // 
    digitalWrite(LED, LOW); // and off
    deepSleep(sTQ);         // sleep + ADCpower-off
    digitalWrite(LED, HIGH);// LED on
    delay(LEDoT);           // 
    digitalWrite(LED, LOW); // and off
    deepSleep(sT2);         // sleep + ADCpower-off
  } else { // if dark enough, sleep for a minute
    for (uint8_t n=0;n<15;n++){
      deepSleep(sT4);
    }
  }
}

ISR(WDT_vect) { // *** Intrpt svc rtn for WDT ISR(vect) *****
}                    // do nothing here

/**********************************
   User defined functions
 **********************************/
// ***** Setting Watch dog timer *** setWDT(seconds) *****
void setWDT(byte sleepT) {
  sleepT += B00010000;    // sleepTime + Enalbe WD-change bit-on
  MCUSR &=  B11110111;    // Prepare WDT-reset-flag in MCU-status-Reg
  WDTCSR |= B00011000;    // Enable WD-system-reset + WD-change
  WDTCSR  = sleepT;       // Set sleepTime + Enable WD-change
  WDTCSR |= B01000000;    // Finally, enable WDT-interrrupt
}
// ***** Sleep + Stop ADC pwr *** deepSleep(WDtime) *****
void deepSleep(byte WDtime) {
  setWDT(WDtime);
  ADCSRA &= B01111111;    // disable ADC to save power
  sleep_enable();
  sleep_cpu();            // sleep until WDT-interrup
  sleep_disable();
  ADCSRA |= B10000000;    // enable ADC again
}
// *** End of program

 色々実験してOK!

月夜など明るい夜もあるため、回路を消灯する暗さは真っ暗闇よりは少し明るく設定しています。使う場所にもよるでしょう。場合によってはR1を200kΩの可変抵抗にするなどして、いちいちプログラムで調整しない手もあるでしょうね。

よし、これならPICで作ればさらに小さくてすむなと、8ピンPICで次のようにしてみます。

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

 

だいぶ小さくできそうですね。もっとも8pinのAVRでもなんでも良いわけですが手持ちがないだけなのです^^;

  ブレッドボードでの比較は次です。下がPICの配線ですが右上RA5からの小LEDはデバッグ用で、完成したら外します。そうすると消灯用以外の回路はほぼ何もないのは同様です。

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

 PICのプログラム(アセンブラー)は次のとおり。デバッグ用のルーチンは使ってないですがつけたままです。外してかまいませんが。

;U210724-Bird-Guard-Blinker-CdS-WDT-12F1822-V00.asm		As of 7/24, 2021
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;										;
;    Bird-Guard Blinker for PIC12F1822 V00 					;
;	        (C)2021 Akira Tominaga, All rights reserved.			;
;		Initial Version 00 on July 24, 2021				;
;	  Function								;
;		1. Blink bright LED to expel birds away				;
;		  stop blinking in very dark environment			;
;		  Save battery energy using sleep+Watch-dog-Timer		;
;	  Input/output       							;
;		RA0 LED output to super bright LED				;
;		AN2(Ra2) AD input from CdS-circuit (Dark >= half Vdd)		;
;		RA4 Power supply to CdS-circuit					;
;	  Remarks								;
;		1. Clock = HFINTOSC (	16MHz)					;
;			Hence 1 step = 0.25 micro seconds			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
	list		p=12F1822      ; list directive to define processor
	#include	"p12F1822.inc" ; device specific variable definitions
    __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_SWDTEN & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
    __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_LO & _LVP_OFF
;	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Macro definitions					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Device dependent Macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
DEVset	macro	
	BANKSEL	OSCCON		; Bank=1
	movlw	B'01111010'	; 16MHz and internal oscillator
	movwf	OSCCON
;
;	BANKSEL	INTCON		; Interrupt Con (in all banks hence comment)
	clrf	INTCON		; Disable all interrupts
;
; PORTA initialization
	BANKSEL	PORTA		; Bank=0
	clrf	PORTA
	BANKSEL	LATA		; Bank=2
	clrf	LATA
;
	BANKSEL	TRISA		; Bank=1
	movlw	B'11001110'	; RA0, RA4-5 are output
	movwf	TRISA
;
	BANKSEL	ANSELA		; Bank=3
	movlw	B'00000100'	; AN2(RA2) is analog input
	movwf	ANSELA		; set it to ANSELA
	BANKSEL	FVRCON		; Bank=2
	clrf	FVRCON		; Not use Fixed Voltage Reference
;
	BANKSEL	ADCON0		; Bank=1
	movlw	B'00001000'	; Analog channel AN2 and ADON 0(disabled)
	movwf	ADCON0		;
;	BANKSEL	ADCON1		; ADCONs in Bank1, hence comment
	movlw	B'00100000' 	; Leftjustified, Fosc/32, Reference Vdd
	movwf	ADCON1		;
;	BANKSEL	ADRESH		; ADC Registeres in Bank1, hence comment
	clrf	ADRESH		;
;	BANKSEL ADRESL		; ADC Registeres are in Bank1, hence commen
	clrf	ADRESL		;

	BANKSEL	OPTION_REG	; Bank=1
	bcf	OPTION_REG,7	; Enable weak pull-up
	BANKSEL	WPUA		; Bank=4
	movlw	B'00001010'	; Weak Pull-up  RA1and RA3
	movwf	WPUA		;  
;
	BANKSEL WDTCON		; Bank=1
	clrf	WDTCON		; WDT is set by program
;
	clrf	BSR		; Bank=0
	InitP			; Initialize ports
	endm
;
InitP	macro			; Initialize ports
	movlw	B'11001010'	; All inputs pulled-up and outputs & ADC L
	movwf	PORTA
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	General purpose macros	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LEDon	macro			; LED on
	goto	$+1
	bsf	PORTA,LED
	goto	$+1
	endm
;
LEDoff	macro			; LED off
	goto	$+1
	bcf	PORTA,LED
	goto	$+1
	endm
;
WDTt	macro tLen		; Set WDT 1, 2, 4 or 0(1/4) Sec
  if tLen==1
	movlw  	B'00010101' 	; Set 1-sec WDT
  endif
  if tLen==2
	movlw 	B'00010111' 	; Set 2-sec WDT
  endif
  if tLen==4
	movlw 	B'00011001' 	; Set 4-sec WDT
  endif
  if tLen==0
	movlw 	B'00010001' 	; Set a-quarter-sec WDT
  endif
  	call	WDTtr
	endm
;
WDToff	macro			; Off the WDT
	call	WDoffr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Comparison macro		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Compare A w/ literal and branch to Large, or Others
CalLO	macro	Aadr,Blit,Ladr
	movlw	Blit		; W=B
	subwf	Aadr,W		; W=A-B
	btfss	STATUS,C	; A≧B? Yes, skip next
	goto	$+3		; 	If A<B goto Others
	btfss	STATUS,Z	; A=B? Yes skip next
	goto	Ladr		;	If A>B goto Laddr
;				; 	If A≦B then next (here)
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Time cosuming macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic	macro			;Consume 1 μS only
	goto	$+1
	goto	$+1
	endm
;
Mic2	macro	mic2p		;Consume 2 μS x n
	movlw	mic2p
	call	Mic2r
	endm
;
Mic2p5	macro 	mic25p		; Consume 2.5μS x n
	movlw	mic25p
	call	Mic25r
	endm
;
Mic5	macro	mic5p		; Consume 5μS x n
	movlw	mic5p
	call	Mic25r
	movlw	mic5p
	call	Mic25r
	endm
;
Mic50	macro	mic50p		; Consume 50μS x n
	movlw	mic50p
	call	Mic50r
	endm
;
Milli	macro	millip		; Consume mS x n
	movlw	millip
	call 	Millir
	endm
;
Mil100	macro	mil100p		; Consume 100 mS x n
	movlw	mil100p
	call 	Mil100r
	endm
; 
Secs	macro	secsp		; Consume Second x n
	movlw	secsp
	call	Secsr
	endm
;
Mins	macro	minsp		; Consume Minute x n
	movlw	minsp
	call	Minsr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Debug and Abend macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
dLEDon	macro			; dLED on
	goto	$+1
	bsf	PORTdbg,dLED
	goto	$+1
	endm
;
dLEDoff	macro			; dLED off
	goto	$+1
	bcf	PORTdbg,dLED
	goto	$+1
	endm
;
Udebug	macro 	Area
	movf	Area,W
	call	Udbgr
	endm
;
Ustop	macro				; Stop until Sw2 on (=L)
	call	Udstopr			; 
	endm
;
Trigger	macro				; DSO external trigger
	goto	$+1
	bsf	PORTtrg,Trig
	Mic2
	bcf	PORTtrg,Trig
	goto	$+1
	endm
;
Uabend	macro	abn			; User Abnormal-end number
	movlw	abn
	goto	Uabendr
	endm
;
Ublink	macro	bno			; Blink LED for specified times
	movlw	bno
	call	Ublinkr
	endm
;
Ucp	macro	CPNo 		; Check point literal chr to display to LCD
	movlw	CPNo
	movwf	Ucpc
	Udebug	Ucpc
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Files and Equations					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Files				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	cblock	H'20'
;
; For Application support
	MeasH			; Measured high byte
	MeasL			; Measured low byte
	SkipC			; Counter for bypassing blinks
;
; Areas for time consuming subroutines
	Mic25c
	Mic50c
	Millic
	Mil100c
	Secsc
	Minsc
;
; Areas for debugging routine
	DmpA 		; Display byte area 
	Dmp8C 		; Bit counter for loop (Initial Dmp8V =8)
	BlinkC 		; Counter for blinking (set for Debugb or Abendb)
	Abendn		; Abend number
	Ucpc		; User Check point chr to trace 
;
	endc
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Equations			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LedOnT	equ	D'25'		; LED-on Time(25mS)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For PORTA		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LED	equ	0		; LED @ PORTdbg
Dkns	equ	2		; Darkness value
CdSpwr	equ	4		; Power for CdS circuit
;dLED	equ	5		; for debug

;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTdbg		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA		;
dLED	equ	5		; LED for debugging
Trig	equ	5		; DSO trigger pulse port for test use
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Values			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Dark 	equ	D'191'		; Min. threshold of darkness (>=2.5V)
SkipS	equ	D'15'		; Skipping 15x4 seconds when dark enough
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For Debug		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Dmp8V	equ	D'8'		; Debug display bit counter initial value
Debugb	equ	D'8'		; number of blinkings to show Debugging
Abendb	equ	D'30'		; number of blinkings to notify Abend
;
	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Initializing						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	org	0
	goto	Startp		; Go to start entry
	org	4		; This is Interrupt entry
	retfie			; 
;
Startp	DEVset			; Define ports
	call	CdSpOn		; Prepare CdS circuit power
	dLEDon
	Mil100	5		;
	dLEDoff
	Mil100	5
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Main program loop					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mainp	nop
;Step0  Measure darkness and skip if dark enough
	call	CdSpOn		; Power on the CdS circuit
	call	Measr		; Measure the darkness level
	call	CdSpOff		; Power off the CdS circuit
	CalLO 	MeasH,Dark,Skipr ; If not dark go on, else stop blinks
;
;Step1	Blink LED once and wait a second
	WDToff			; Off watch-dog-timer
	LEDon			; Blink LED once
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	WDTt	1		; On watch-dog-timer 1 Sec
	sleep			; This includes clrwdt
	WDToff			; Restart without WDT
;
;Step2	Blink LED once again and wait a second
	LEDon			; Blink LED once
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	WDTt	1		; On watch-dog-timer 1Sec
	sleep			; This includes clrwdt
	WDToff			; Restart without WDT
;
;Step3	Blink twice with 0.25 sec sleep and wait a few seconds
	LEDon			; Blink LED 1st time
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	WDTt	0		; On watch-dog-timer 1/4 Sec
	sleep			; This includes clrwdt
	WDToff			; Restart without WDT
	LEDon			; Blink LED 2ndt time
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	WDTt	2		; On watch-dog-timer 2 Sec long
	sleep			; This includes clrwdt
	WDToff			; Restart without WDT
	goto	Mainp		; Contilue infinite loop
;
Skipr	movlw	SkipS		; Get skip-time (# of 4 seconds)
	movwf	SkipC		; Set it to counter
Skiprl	WDTt	4		; Set 4 sec WDT
	sleep			; Sleep for 4 sec
	WDToff			; Restart without WDT
	decfsz	SkipC,F		; Done ?
	goto	Skiprl		;  No, loop WD4 x n (SkipS number)
	goto	Mainp		;  Yes, return to main loop
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Measuring with CdS + Tr (emitter follower) circuit	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Power on/off routine	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CdSpOn	bsf	PORTA,CdSpwr	; Cds circuit power on
	Milli	D'40'		; Stabilize power
	return
;	
CdSpOff bcf	PORTA,CdSpwr	; CdS circuit power off
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Measuring routine	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Measr	equ	$
	BANKSEL ADCON0		; Bank=ADCON0(Bank1)
	bsf	ADCON0,ADON	; ADON (bit0)
	clrf	BSR		; Bank=0
	Mic5	D'4'		; Wait for more than 15 micro secs
;
	BANKSEL	ADCON0
	bsf	ADCON0,ADGO	; Start AD conversion	
	btfsc	ADCON0,ADGO	; Check if ADGO on
	goto	$-1		; If still on, then continue to inquire it
;
	BANKSEL	ADRESH
	movf	ADRESH,W	; Get ADRESH content
	clrf	BSR		; Point Bank 0
	movwf	MeasH		; Move it to MeasH
;
	BANKSEL	ADRESL
	movf	ADRESL,W	; Get ADRESL content
	clrf	BSR		; Point Bank 0
	movwf	MeasL		; Move it to MeasL
;
	BANKSEL	ADCON0	
	bcf	ADCON0,ADON	; Cut analog connection
	clrf	BSR		; Bank=0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Watch-dog-timer subrooutines				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WDT bit0=0 : WDToff  bit0=1 : WDTon
; WDT bit5-1: Time value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set specified length of WDT	;
;		Called by WDTt macro	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WDTtr	equ	$
	BANKSEL	WDTCON
	movwf	WDTCON
	clrf	BSR		; Set Bank=0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set WDT off 			;
;		Called by WDToff macro	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WDoffr	equ	$
	BANKSEL	WDTCON
	bcf	WDTCON,SWDTEN	; Disable SW Watch Dog Timer
	clrf	BSR		; Bank = 0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Timing subrooutines for general purposes		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.0 micro S x n	(Mic2)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic2r	movwf	Mic25c		; + Wset + call = 1 micro sec 
;
Mic2l	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic2li		; else go out (2nd time 1.75 mic sec)
	return		
;
Mic2li	goto	$+1		;            (2nd time 2.25 mic sec)
	nop			;	     (2nd time 2.5 mic sec)
	goto	Mic2l		; go back    (2nd time 3 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.5 micro S x n (Mic2p5)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic25r	movwf	Mic25c		; + Wset + call = 1 micro sec 
	nop			; 1.25 micro sec
;
Mic25l	nop			; 1.5 micro sec (2nd time 4 mic sec)
	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic25li		; else go out (2nd time 2.25 mic sec)
	return		
;
Mic25li	Mic			;	      (2nd time 3.25 mic sec)
	goto	Mic25l		; go back    (2nd time 3.75 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	50 Microseconds	x n	Mic50  	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Mic50r	movwf	Mic50c	 	; set how many 50 microsec (1 micro sec to here)
	nop			; 1.25 micro sec up to here
;
Mic50l	Mic2p5	D'19'		; + 47.5 = 48.75 mic sec (2nd time 98.75 mic sec)
	nop			; + 0.25 = 49 micro sec (2nd time 99 mic sec)
; 
	decfsz	Mic50c,F	; If exhausted then 1 mic S hereafter
	goto	Mic50li		; else go out (2nd time 49.75 mic sec)
	return
;
;
Mic50li	Mic			; 	  (2nd time 50.75 mic sec)
	goto	Mic50l		; go back (2nd time 51.25 mic sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Milliseconds x n	(Milli)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Millir	movwf	Millic 		; set how many 1 mil sec (1 mic S up to here)
	nop			; 1.25 micro sec
;
Millil	Mic50	D'19'		; + 50 mic x 19 = 951.25 mic S (2nd, 1951.25) 
	Mic2p5	D'19'		; + 47.5 mic = 998.75 micro S  (2nd, 1998.75)
	nop			; +0.25 mic = 999 micro sec    (2nd, 1999)
;
	decfsz	Millic,F	; If  exhausted then 1 micro sec hereafter
	goto	Millili		; else go out (2nd, 999.75 mic S)
	return
;
Millili	Mic			; 		(2nd time 1000.75 mic S)
	goto	Millil		; go back (2nd time 1001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	100 Milliseconds x n	(Mil100);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mil100r	movwf	Mil100c		;set how many 100 ms(1 micr sec up to here)
	nop			; 1.25 micro sec
;
Milhl	Milli	D'99'		;+1ms x 99 = 99001.25 micS (2nd,199001.25mic)
	Mic50	D'19'		; + 950 mic = 99951.25 micS(2nd.199951.25mic)
	Mic2p5	D'19'		; + 47.5 mic = 99998.75micS(2nd,199998.75mic)
	nop			; + 0.25 mic = 99999 mic S (2nd,199999 micS)
;
	decfsz	Mil100c,F	; If exhausted then 1 micro sec hereafter
	goto	Milhli		; else go out (2nd time, 99999.75 mic S)
	return
;
Milhli	Mic			;  	    	(2nd time, 100000.75 mic S)
	goto	Milhl		; 		(2nd time, 100001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Seconds x n	 (Secs)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Secsr	movwf	Secsc 		; set how many sec ( 1 mic sec up to here)
	nop			; 1.25 micro sec
;
Secsl	Mil100	D'9'		; 
	Milli	D'99'		; + 999 milli sec = 999001.25 micro sec
;
	Mic50	D'19'		; + 950 mic = 999951.25 micro sec
	Mic2p5	D'19'		; + 47.5 mic = 999998.75 micro sec
	nop			; + 0.25 mic = 999999 micro sec
;
	decfsz	Secsc,F		; If exhausted then 1 micro sec hereafter
	goto	Secsli		; else, go out 
	return
;
Secsli	Mic
	goto	Secsl		; (Second time, Sec + 1.25 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Minutes	x n	 (Mins)		;
;	 Overhead ignored, that is only	;
;	 751.25 Mic S even when 100 Min	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Minsr	movwf	Minsc		;set how many minutes from parameter
;
Minsl	Secs	D'60'		; 1 Seconds x 60
	decfsz	Minsc,F
	goto	Minsl
	return
;
	space
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Followings are for debugging purpose only	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; User debug to show a specified byte	;
;	on dLED for debuggng purpose	;
;	shown from bit 7 to 0 		;
;	called by Udebug macro		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Udbgr	clrf	BSR		; Set Bank=0
	movwf	DmpA		; move data to Dmpa
	movlw	Dmp8V		; set counter 8
	movwf	Dmp8C		; to Dmp8C
;
Udblp	Ublink	Debugb		; Blink for Debugging = 8 times
	btfsc	DmpA,7		; check top bit 7
	goto	UdbOn		; if on then to UdbOn
	dLEDoff			;  Show 0
	Mil100	D'10'		;
	goto	Udbeck
;
UdbOn	dLEDon
	Mil100	D'10'		;
	dLEDoff
Udbeck	decfsz	Dmp8C,F
	goto	Udbnext		;if not end, next
	Ublink	4		;if end, blink 4 times and 
	Mil100	D'30'		; blank for 3 seconds to take memo
	return
;
Udbnext	rlf	DmpA,F		; shift for next bit
	goto	Udblp		; loop for next bit
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Blinking to indicate debugging mode	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Ublinkr	movwf	BlinkC
Ublinkl	dLEDon			; dLED on
	Milli	D'15'		; for 15ms
	dLEDoff			; dLED off
	Milli	D'60'		; for 60ms
	decfsz	BlinkC,F	; check blinking counter
	goto	Ublinkl		; if not exhausted, loop
	return			; exhausted, return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Abend (abnormal end) routine	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Uabendr	clrf	BSR
	movwf	Abendn			; Set Uabend number
	Ublink	Abendb			; Blinking 30 times
	Udebug	Abendn			; Show Abend number
	goto	$
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	End of program						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	end

 

テストの結果は特に問題なく完成。これで基板を4つ作ることにしました。といってもそれぞれ25mm x 16mmのごく小さな基板です。 

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

 例によって卓上CNCルーターで生基板から削り出します。

 

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

そして20分もかからずに削り終えたのが次。

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

 カットして急いではんだ付けし4つを完成。

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

電池ボックス(単3 x 3 = 4.5V)に貼り付けたものを、防水用にチャック袋に入れてできあがり。徹底的に省エネなので電池は半年以上は持つ計算です。

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

今回は試しにチャック袋を二重にかぶせてみました。前の場所(2Fベランダ)の両端から長手方向に中を向けて2つずつで照らすようにしました。

本日まで+2日もスズメは全く来ませんでした^^

余談ですが、調べてみると暗いと見えないトリメとは、本当は鶏目の意味のようで、ニワトリなどキジ科の鳥だけのことなのだそうです。そしてスズメやムクドリなどは人間と同じくらいに見えているそうな。

よって、明け方や夕方のうす暗い時でも来ないようにするには、各プログラム中に定義したDarknessの値をもう少し大きくするほうが良いのかもしれません。

それにしても、こんなので鳥除けができるとは・・・。

カカシみたいに慣れると来るようなことがないか、しばらくさらに様子をみるとします。今回はスズメを寄せ付けていないのに気をよくしています。もちろん、他の鳥でどうなのかは不明ですよ^^;

皆様、必要に応じてお試しあれ。非常に明るいLEDが要りますが、最近はLEDテープも結構明るいですよね。もし試されたら結果などをコメントで教えてもらえると嬉しいです。

 

では今回はこのへんで。皆様のなんらかのお役に立てば幸い。

 

以下 2021.7.30追記:

その後も鳥を全く寄せ付けず、これだけで問題なさそうです\(^_^)/。

よって、きちんと防水ケースにセットしました。回路基板の貼り付け位置は都合の良い位置にずらしました。

ついでにDarkの限界値もVref(=Vcc)の75%ほどに上げて設定しました。これでうす暗い状態まではちゃんと働き、さらに暗いとSleepします。上に掲げたプログラム両方の該当箇所の値を更新しておきます。

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

防水ケースは百均タッパウェアの一番小さなもの(数年前、たしかセリアで4つで百円^^; これだと大きさがぴったり、しかも側面が透明なので目的に合うしなかなか良いですね)。

これを2Fバルコニーの両端に設置。

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

上の写真ではわかりにくいですが、次の写真のようにエアコン室外機の下に2つずつです。晴れた日は日照が強いため、 少しでも陰になるように。前と横のパンチホールを通る日差しも結構きついので。

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

そしてバルコニーの反対方向にも同じように設置。

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

これだけで、約 6m x 1m のバルコニーのスズメ除けになるとは思いもよりませんでした!

そして照度計測時と超高輝度LED照射時以外はSleepしているし、夜もSleepしているので、単3アルカリ電池は1年以上もつ計算になると思います。

とりあえずメデタシ メデタシ! 

今後もし鳥がきたりしたらここに書き足そうと思います^^。

 

©2021 Akira Tominaga, All rights reserved.

 

省エネ用のSleep + Watch Dog Timer(Arduinoでもできる!)

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

単3電池を長期間(2~3年)もたせるため、これまではPICでSleepとWatch Dog Timer(WDT)を使うようにしていました。今回は、それをやったついでに、Arduinoで同じことができるかを試してみました。上の写真はArduino-UNOからとり出したATmega328P単体を使って実験している様子です。

WDTはマイコンの動きを監視し一定時間以上反応がなければリセットして再起動をする機能です。この記事で書くのは、何が普通でないのかといえば、WDT検出でマイコンをリセットはせず、Sleepの直後にある命令から自動的に再開したいという目的です。これがうまくできれば、別途に割り込みを作らずWDTだけですむので、しかけが単純になりとても便利です。そして、Arduinoでやってみて分かった方法を、まずはこの記事に書いておきたいというわけです。

事の発端は、2階のベランダにスズメ一家が立ち入らないようにするためのちょっとした工作です。

庭で遊んでいるときは雀の学校のようで可愛らしいのですが、どうやら早朝のトイレの教育(?)にベランダを使うというのは感心しません。最近は毎朝欠かさずですから、毎度の掃除はたいへんです。そして、「このベランダには来ないように何とかならない?」と家内に頼まれたわけです^^;

一般的にはCDを吊るすとか、鳥よけテープを使うとか、最悪ではネットを張るとか、方法は色々とあるようです。たぶんですが、いちばん簡単な方法は、眩しい超高輝度白色LEDを付近でチカチカさせることではないでしょうか。勝手な電子マンとしては、早速週末にそれを試すことに。

ベランダに設置して放置し、単3電池を数か月はもたせる必要がありそうで、どうしても省エネ処理が必要です。前に作った次のリンク先のドロボー除けと同じようなものです。

いちばん簡単なドロボー除け(かな?) - 勝手な電子工作・・

そちらは設置してから1年半近くたちますが、未だにどれも単3電池を代えずに完動中。その時はSleepに加えて外付けRCによりクロックの長周期化で、電池が数年間持つようににしたのがうまく実現できていますから。

今回は数か月もたせれば良さそうなので、内蔵クロック(16MHz)で動かすことにして回路を簡素にすることにしました。外付けは超高輝度LEDとその直列抵抗だけです。

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

超高輝度LEDは10mmφ砲弾型で、20mAで20カンデラも光ります(カタログ仕様)。前に千石電商で購入しました。詳しいデータシートがないので照射角を測ってみると半値角が僅か4.5°の鋭いビームです。最終的にはこれを水平に向けるように設置します。鳥の立場で考えれば、まぶしい目に合うと二度と来ないかと思うからです。

順方向3.3V以上が必要のため4.5V電源とし、10mA程度で点灯させるよう直列抵抗を100Ωにしました。これで消費電流を測るとSleep状態(全体時間の97%)は20μA、点灯状態(3%)はほぼ10mAです。よって平均400μA未満。これだと連続半年はもちそうです。

点滅は次の動画のようになります。動画の後ろのほうでWatchDogが吠えるのでビックリしないでください、冗談にいれてみたのですが^^

 

youtu.be

 このPICプログラムは記事の最後、Arduinoのスケッチの後ろにご参考用につけておきます。

動作確認後、ユニバーサル基板に作り電池ボックスに貼り付けました。ごく簡単なので短時間に3つ作りました。

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

そして雨除けのため、それぞれ全体をビニールのチャック袋に収めました。

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

この3つをベランダの長手方向に向けて並べて配置。翌朝はベランダへの来訪はぴたりと止まり、フンが全くありません^^ しめしめ!

とはいえ鳥も頭がよいので、そのうちに慣れてまた来るかもしれません。このまましばらく様子を見ることにします。

 

さて、省エネアプリケーションに毎度PICアセンブラを持ち出すのもどうかと思うわけですから、今回は同じことをArduinoでやってみるわけです

省エネが目的なので、Arduino UNOなどの開発ボードのまま動かす(電流ジャンジャン食う)のではなく、UNOでプログラムを書き込んだら、そこからATmega328P単体を取り出して製作に使うわけです。取り出しやすくするためには、UNOに無圧ソケットを挿しておくと楽です。

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


ATmega328Pを何度も取り出すための、無圧ソケット(ゼロプレッシャーソケット)の上手な取り付け方は、前に次の記事に詳しく書きましたのでご参照ください。

Zoom用かんたん操作ボタンをArduino-UNOで作る(その1) - 勝手な電子工作・・

 

ちょっと脱線しましたが、本論であるWatch Dog Timerによるリセットを伴わないSleepからの復帰方法についてです。

そもそもWDTは稼働していることをタイマーで見張る番犬なので、起動すればマイコンにリセットがかかる、つまり再起動させる使い方が普通なわけです。それでも省エネアプリケーションを工夫してできないことはないのですが、再起動時にはSleepの次の命令に自動的に戻って欲しいものです。

Watch Dog Timerではなく他の割り込みを使えばできるわけなのですが、毎度それを考えるのも面倒。PICのやり方と同じように、WDTにシステムリセットをしない割り込みとして使えれば、回路もプログラムも簡単になるわけです。

まずATmega328PのデータシートでWatch Dog Timerのあたりを何度も読み返してみました。一番主なところはWDTCSRというコントロールレジスターの使い方です。下の図では文言をカットしてありますが、長々と書かれています。やや複雑なのですがシツコク読みます。

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

そして、ついにMCUにResetをかけず割り込みだけしてSleepから目覚めそうな方法をUNOで試しました。結構複雑で何度かやりなおしましたが、しつこくやってやっと正解らしき方法にたどり着きました。そのスケッチを後ろにつけておきます。

省エネの電流を測るために、次のようにATmega328P単体用の回路を組みます。

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

普通に16MHzのクリスタルを使い5Vでテストしたところ、POWER-DOWNさせるSleep時でも80μAほどを消耗します。そこで、Sleepの間はADCへのPower供給を止めるようにしてみたところ、これで26.8μAしか消耗しないことがわかりました。

クロックを遅くするなど更に色々やってみることがあるかとは思いますが、とにかく今後はSleepとWatch Dog Timerの組み合わせだけで、Arduinoでも簡単に省エネ処理ができそう\(^_^)/

次が今回のArduino-UNOのテストスケッチです。その後ろにPICアセンブラーの実プログラムをつけておきます。

/*******************************************************
   Watch Dog Timer test for Arduino (ATmega328P)
      Initial version 7/18, 2021   by Akira Tominaga
 *******************************************************/
#include "avr/sleep.h"
#include "avr/wdt.h"
#define LED 13

void setup() { // ***** Arduino setup() *****
// sleep time definitions for Watch-dog-timer
#define sT1 B00000110     // 1 second
#define sT2 B00000111     // 2 seconds
#define sT4 B00100000     // 4 seconds
#define st8 B00100001     // 8 seconds
#define sleepTime sT4     // select sleep-time from above
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);
  // for Sleep and Watch-dog-timer
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  setWDT(sleepTime);
}

void loop() { // ***** Arduino loop() *****
  digitalWrite(LED, HIGH); // LED on
  delay(20);              // for 20mS
  digitalWrite(LED, LOW); // and off
  deepSleep();            // sleep + ADCpower-off
}

ISR(WDT_vect) { // *** Intrpt svc rtn for WDT ISR(vect) *****
}                    // do nothing here

/**********************************
   User defined functions
 **********************************/
// ***** Setting Watch dog timer *** setWDT(seconds) *****
void setWDT(byte sleepT) {
  sleepT += B00010000;    // sleepTime + Enalbe WD-change bit-on
  MCUSR &=  B11110111;    // Prepare WDT-reset-flag in MCU-status-Reg
  WDTCSR |= B00011000;    // Enable WD-system-reset + WD-change
  WDTCSR  = sleepT;       // Set sleepTime + Enable WD-change
  WDTCSR |= B01000000;    // Finally, enable WDT-interrrupt
}
// ***** Sleep + Stopp ADC power *** deepSleep() *****
void deepSleep(void) {
  ADCSRA &= B01111111;    // disable ADC to save power
  sleep_enable();
  sleep_cpu();            // sleep until WDT-interrup
  sleep_disable();
  ADCSRA |= B10000000;    // enable ADC again
}
// *** End of program

 

細かい説明は省略させていただきますが、とにかく別途の割り込みのしかけもいらず、短くて簡単ですね。要は単に休ませるというDelayの代わりに所定時間のSleepを入れるだけですから。おそらく4秒のWatchDogTimerだけ用意しておけば、Loopさせることで1分とか10分とかにも対応しやすいかと思います。

このやり方は簡単だしメモリー消費なども殆どない点がよいかと勝手に思います。

もし正確に10分単位などでの計測が必要なら、9分56秒だけスリープさせてから次にRTCの分の値の変化タイミングで処理することなどで精密にできると思います。

 

次に今回の雀よけPICプログラムをご参考までに載せておきます。

;U210718-WDT-12F1822-V00t.asm			     		As of 7/18, 2021
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;										;
;    Sleep and Restart by WDT for PIC12F1822 V00 				;
;	        (C)2015-2021 Akira Tominaga, All rights reserved.		;
;	  Function								;
;		1. Blink bright LED to drive mischievous birds away		;
;	  Input/output       							;
;		RA0 LED output							;
;	  Remarks								;
;		1. Clock = HFINTOSC (	16MHz)					;
;			Hence 1 step = 0.25 micro seconds			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
	list		p=12F1822      ; list directive to define processor
	#include	"p12F1822.inc" ; device specific variable definitions
    __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_SWDTEN & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
    __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_LO & _LVP_OFF
;	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Macro definitions					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Device dependent Macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
DEVset	macro	
	BANKSEL	OSCCON		; Bank=1
	movlw	B'01111010'	; 16MHz and internal oscillator
	movwf	OSCCON
;
;	BANKSEL	INTCON		; Interrupt Con (in all banks hence comment)
	clrf	INTCON		; Disable all interrupts
;
; PORTA initialization
	BANKSEL	PORTA		; Bank=0
	clrf	PORTA
;	BANKSEL	LATA		; Bank=2
;	clrf	LATA
	BANKSEL	ANSELA		; Bank=3
	clrf	ANSELA		; No use of ADC
	BANKSEL	ADCON0		; Bank=1
	clrf	ADCON0		; No use of ADC
;
	BANKSEL	TRISA		; Bank=1
	movlw	B'11111110'	; RA0 is output
	movwf	TRISA
;
	BANKSEL	OPTION_REG	; Bank=1
	bcf	OPTION_REG,7	; Enable weak pull-up
	BANKSEL	WPUA		; Bank=4
	movlw	B'00111110'	; Weak Pull-up for RA 1 to RA5
	movwf	WPUA		;  
;
	BANKSEL WDTCON		; Bank=1
	clrf	WDTCON		; WDT is set by program
;
	clrf	BSR		; Bank=0
	InitP			; Initialize ports
	endm
;
InitP	macro			; Initialize ports
	movlw	B'11111110'	; All inputs pulled-up and outputs L
	movwf	PORTA
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I/O macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For general purpose	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LEDon	macro			; LED on
	goto	$+1
	bsf	PORTdbg,LED
	goto	$+1
	endm
;
LEDoff	macro			; LED off
	goto	$+1
	bcf	PORTdbg,LED
	goto	$+1
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Time cosuming macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic	macro			;Consume 1 μS only
	goto	$+1
	goto	$+1
	endm
;
Mic2	macro	mic2p		;Consume 2 μS x n
	movlw	mic2p
	call	Mic2r
	endm
;
Mic2p5	macro 	mic25p		; Consume 2.5μS x n
	movlw	mic25p
	call	Mic25r
	endm
;
Mic5	macro	mic5p		; Consume 5μS x n
	movlw	mic5p
	call	Mic25r
	movlw	mic5p
	call	Mic25r
	endm
;
Mic50	macro	mic50p		; Consume 50μS x n
	movlw	mic50p
	call	Mic50r
	endm
;
Milli	macro	millip		; Consume mS x n
	movlw	millip
	call 	Millir
	endm
;
Mil100	macro	mil100p		; Consume 100 mS x n
	movlw	mil100p
	call 	Mil100r
	endm
; 
Secs	macro	secsp		; Consume Second x n
	movlw	secsp
	call	Secsr
	endm
;
Mins	macro	minsp		; Consume Minute x n
	movlw	minsp
	call	Minsr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Files and Equations					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Files				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	cblock	H'20'
;
; For Application support
;
; Areas for time consuming subroutines
; Do not change the sequences from Mic5c to Minsc
;	if co-used with calculation parameters
	Mic25c
	Mic50c
	Millic
	Mil100c
	Secsc
	Minsc
;
	endc		
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Equations			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LedOnT	equ	D'25'		; LED-on Time(25mS)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For PORTA		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;LED	equ	0
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTdbg		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA		;
LED	equ	0		; LED for debugging
Trig	equ	0		; DSO trigger pulse port for test use
;
	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Initializing						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	org	0
	goto	Startp		; Go to start entry
	org	4		; This is Interrupt entry
	retfie			; 
;
Startp	DEVset			; Define ports
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Main program loop					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mainp	nop
;Step1	Blink LED once and wait a second
	call 	WDoffr		; Off watch-dog-timer
	LEDon			; Blink LED once
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	call WD1Sr		; On watch-dog-timer 1 Sec
	sleep			; This includes clrwdt
;
;Step2	Blink LED once again and wait a second
	call 	WDoffr		; Off watch-dog-timer
	LEDon			; Blink LED once
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	call	WD1Sr		; On watch-dog-timer 1Sec
	sleep			; This includes clrwdt
;
;Step3	Blink twice with 0.5 sec sleep and wait a few seconds
	call 	WDoffr		; Off watch-dog-timer
	LEDon			; Blink LED 1st time
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	call	WDqSr		; On watch-dog-timer short
	sleep			; This includes clrwdt
	LEDon			; Blink LED 2ndt time
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	call	WD1Sr		; On watch-dog-timer long
	sleep			; This includes clrwdt
	goto	Mainp		; Contilue infinite loop
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Watch-dog-timer subrooutines				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WDT bit0=0 : WDToff  bit0=1 : WDTon
; WDT bit5-1: Time value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set 1-Second WDT		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WD1Sr	equ	$
	BANKSEL	WDTCON
	movlw	B'00010101'	; Enable 1 sec WDT
	movwf	WDTCON
	clrf	BSR		; Set Bank=0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set a quarter Second = 256mS WDT;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WDqSr	equ	$
	BANKSEL	WDTCON
	movlw	B'00010001'	; Enable 256mS WDT
	movwf	WDTCON
	clrf	BSR		; Set Bank=0
	return
;l
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set 8-Second WDT		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WD8Sr	equ	$
	BANKSEL	WDTCON
	movlw	B'00011011'	; Enable 8 sec WDT
	movwf	WDTCON
	clrf	BSR		; Set Bank=0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set WDT off			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WDoffr	equ	$
	BANKSEL	WDTCON
	bcf	WDTCON,SWDTEN	; Disable SW Watch Dog Timer
	clrf	BSR		; Bank = 0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Timing subrooutines for general purposes		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.0 micro S x n	(Mic2)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic2r	movwf	Mic25c		; + Wset + call = 1 micro sec 
;
Mic2l	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic2li		; else go out (2nd time 1.75 mic sec)
	return		
;
Mic2li	goto	$+1		;            (2nd time 2.25 mic sec)
	nop			;	     (2nd time 2.5 mic sec)
	goto	Mic2l		; go back    (2nd time 3 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.5 micro S x n (Mic2p5)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic25r	movwf	Mic25c		; + Wset + call = 1 micro sec 
	nop			; 1.25 micro sec
;
Mic25l	nop			; 1.5 micro sec (2nd time 4 mic sec)
	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic25li		; else go out (2nd time 2.25 mic sec)
	return		
;
Mic25li	Mic			;	      (2nd time 3.25 mic sec)
	goto	Mic25l		; go back    (2nd time 3.75 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	50 Microseconds	x n	Mic50  	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Mic50r	movwf	Mic50c	 	; set how many 50 microsec (1 micro sec to here)
	nop			; 1.25 micro sec up to here
;
Mic50l	Mic2p5	D'19'		; + 47.5 = 48.75 mic sec (2nd time 98.75 mic sec)
	nop			; + 0.25 = 49 micro sec (2nd time 99 mic sec)
; 
	decfsz	Mic50c,F	; If exhausted then 1 mic S hereafter
	goto	Mic50li		; else go out (2nd time 49.75 mic sec)
	return
;
;
Mic50li	Mic			; 	  (2nd time 50.75 mic sec)
	goto	Mic50l		; go back (2nd time 51.25 mic sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Milliseconds x n	(Milli)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Millir	movwf	Millic 		; set how many 1 mil sec (1 mic S up to here)
	nop			; 1.25 micro sec
;
Millil	Mic50	D'19'		; + 50 mic x 19 = 951.25 mic S (2nd, 1951.25) 
	Mic2p5	D'19'		; + 47.5 mic = 998.75 micro S  (2nd, 1998.75)
	nop			; +0.25 mic = 999 micro sec    (2nd, 1999)
;
	decfsz	Millic,F	; If  exhausted then 1 micro sec hereafter
	goto	Millili		; else go out (2nd, 999.75 mic S)
	return
;
Millili	Mic			; 		(2nd time 1000.75 mic S)
	goto	Millil		; go back (2nd time 1001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	100 Milliseconds x n	(Mil100);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mil100r	movwf	Mil100c		;set how many 100 ms(1 micr sec up to here)
	nop			; 1.25 micro sec
;
Milhl	Milli	D'99'		;+1ms x 99 = 99001.25 micS (2nd,199001.25mic)
	Mic50	D'19'		; + 950 mic = 99951.25 micS(2nd.199951.25mic)
	Mic2p5	D'19'		; + 47.5 mic = 99998.75micS(2nd,199998.75mic)
	nop			; + 0.25 mic = 99999 mic S (2nd,199999 micS)
;
	decfsz	Mil100c,F	; If exhausted then 1 micro sec hereafter
	goto	Milhli		; else go out (2nd time, 99999.75 mic S)
	return
;
Milhli	Mic			;  	    	(2nd time, 100000.75 mic S)
	goto	Milhl		; 		(2nd time, 100001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Seconds x n	 (Secs)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Secsr	movwf	Secsc 		; set how many sec ( 1 mic sec up to here)
	nop			; 1.25 micro sec
;
Secsl	Mil100	D'9'		; 
	Milli	D'99'		; + 999 milli sec = 999001.25 micro sec
;
	Mic50	D'19'		; + 950 mic = 999951.25 micro sec
	Mic2p5	D'19'		; + 47.5 mic = 999998.75 micro sec
	nop			; + 0.25 mic = 999999 micro sec
;
	decfsz	Secsc,F		; If exhausted then 1 micro sec hereafter
	goto	Secsli		; else, go out 
	return
;
Secsli	Mic
	goto	Secsl		; (Second time, Sec + 1.25 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Minutes	x n	 (Mins)		;
;	 Overhead ignored, that is only	;
;	 751.25 Mic S even when 100 Min	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Minsr	movwf	Minsc		;set how many minutes from parameter
;
Minsl	Secs	D'60'		; 1 Seconds x 60
	decfsz	Minsc,F
	goto	Minsl
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	End of program						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	end

 出かける寸前に慌てて書いた記事でしたため、行き届かない点があればすみません。省エネが必要な測定などにもしお役に立てば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

 

 

カラーLEDテープ照明を自由にコントロール

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

今回はLED照明テープをマイコンで好きなように点灯させる方法です。LED照明テープとは、RGB LEDがテープにならべられた装飾用の照明です。

名前はRGBテープ照明とかカラーLEDテープライトとか色々な呼称があるようで、電源とコントローラ装置がついているセットが多く販売されているようです。

今回はこのテープをArduino-IDEを使って好きなように動かそうというわけです。

必要な長さに切って使うことができるので、余りが出ます。手元にある余りは次の写真で、それぞれ半端な長さです。

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

この照明テープは次の写真のように、3つのLEDが1単位で自由に切って使えます。つまり、LED3つずつが1ユニットになっており、各ユニットが1つのIC (WS2811)でコントロールされています。電源はLED3つを直列に設定しているようで12Vと書いてあります。

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

3つ並んだ銅メッキの端子ランドの位置で、ハサミで自由にカットできます。

コントロールICの電源はその12Vから内部でレギュレータを通して供給されているようです。入り口に電源供給ワイヤが余分についていたりするためDIYで扱うときは気を付けないといけませんね。これをマイコン電源につないでこわさないよう。

電源はLEDとコントロールICの両方に使う用途ですが、原理から見れば12Vでなくて9V供給でも大丈夫そうですね。LEDは非常に明るい(眩し過ぎる)LEDですし。

少し新しい型では、LED3つずつの単位ではなく、1つずつ自由にコントロールできるものがあります。この場合はLED電源とIC(WS2812B)は共通の5V供給となっています。次の写真ですが、ICとLEDとが一体になっています。

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

さらに、WS2813になると途中のICが壊れてもそこから先のICに信号が伝わるように工夫されています。長いテープの途中では電圧降下が起きるため電源の追加供給が必要となりますが、最近のWS2815では12V電源にすることで追加供給が不要にされています。

 

どれも、制御するためのデータは「1ワイヤプロトコル(1線式プロトコル)」で送信します。1線式といってもCRC(巡回冗長検査符合)などはなく、RGBの明るさ(デューティ比)を255までの数値で3つ並べた3バイトを順次送信するという簡単なものです。

ここで使ったものは1ICあたり3バイトをBRGの順に送ります。ICのユニット数分だけ、バイト間をあけずに一気に送り込む必要があります。つまりICが10個並んでいる場合は30バイトを切れ目なく送るわけです。すると照明の状態がその並びの通りにセットされて維持されます。変更するときは、リセット信号(単に50μS以上のオフ)を出して、再び新しい並び順に送ればよいわけです。

 

ところが、

LEDテープの1ワイヤプロトコルはタイミングの制約が厳しく、遅いマイコンでやるのは難しいのです。

1ビットがHとLの組み合わせで、Hが長くLが短いのが1、逆が0です。それを1.25μS周期で送らないといけません。ビット表示の間を全くあけられませんから、遅いマイコンの場合は処理が到底間に合いません。

なぜ16MHzのArduino-Unoで動くかと言えば、ライブラリー(上の例で使っているのはFastLEDライブラリ)がハードウェア端子を直接操作しているからです。こういうライブラリーを使わない限りは、普通のC(Arduino)言語では無理です。

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

Arduino UnoでFastLEDライブラリーが出している0信号(H0.5μS+L0.75μS)

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

Arduino UnoでFastLEDライブラリーが出している1信号(H1μS+L0.25μS)

16MHzのPICをアセンブラーでどうかといえば、できそうですが簡単ではありませんね。PORTのビットをオンオフするには間に2サイクル取らないといけませんから、それだけで0.5μSかかってしまいます。1サイクルにするためにはPORTレジスタでなくLATレジスタを操作しなければなりませんが、それにはメモリーバンク番号を通常ではない設定のまま動かすことに。さらに信号間の処理ができないわけで・・・これじゃ普通は堪りませんね^^; 

ですがライブラリーのインターフェイスに縛られず、Arduino-IDEで好きなように使いたいと考える人は多い事でしょうね。LEDテープは飾りや照明でなく、色々な表示に応用が利きそうですからね。

そこで

Arduino-IDEで気軽に作るには、速いマイコンを使うに限ります。ここでは手持ちのTeensy4.0を使います。ある程度速ければそこまでの速度がなくともOKですが。

処理能力比較は例えば前に次の記事に書きましたが、ここでは1CPUあたりの速度で考える必要がありますね。

https://a-tomi.hatenablog.com/entry/2021/03/19/225327

 

さて、Arduino言語にタイミングをとるためのdelayMicrosecondsはあっても、delayNanosecondsがまだない^^; というわけで、LEDテープで好き勝手をするような自作例が見当たらないわけでしょうね。そこをどうするかさえ勝手に考えればArduino-IDEで楽勝となりそう!

まずは、切りっ放しのテープの端子にワイヤーをつなぎます。この余りテープには30個のLED、つまり10ユニットが並んでいます、右のほうに。

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

12V端子へオレンジ、Dinと記された端子へ黄色、そしてGnd端子に黒2本をはんだ付けし、接続部全体を透明熱収縮チューブで保護しました。LED電源としてオレンジと黒の間に9VDCを供給します。そこに念のために100μFを並列に入れてあります。

そして、Teensy4.0で、デジタル14ピンに信号を出すようにします。次の写真のように。

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

テストプログラムは次のように作りました。9ユニット分にそれぞれ別の色を出し、10番目のユニットには送りません(光らせない)。

/**************************************************
 *  LED-Tape test V.00                            *
 *        1st version 6/30,2021(c)Akira Tominaga  *
 *     Remarks:                                   *
 *        WS281X IC on the tape                   *
 **************************************************/
#define Out 14                // output pin d14
// *** color definitions
#define Blue (128,0,0)
#define Red (0,128,0)
#define Green (0,0,128)
#define Purple (62,66,0)
#define Yellow (0,64,64)
#define Orange (0,100,32)
#define BlueGreen (64,0,64)
#define BluePurple (88,40,0)
#define White (43,43,43)
#define Black (0,0,0)
// 
uint8_t colorD[3];            // 3 values for BRG 
byte Byte;                    // working byte 

void setup() { // ***** Teensy setup() *****
  pinMode(Out, OUTPUT);
  digitalWrite(Out, LOW);
  delay(3000);                // time to stabilize pwr
  Rst();                      // reset LED controller
  sendColor Red;              // unit 1
  sendColor Orange;           // unit 2
  sendColor Yellow;           // unit 3
  sendColor Green;            // unit 4
  sendColor BlueGreen;        // unit 5
  sendColor Blue;             // unit 6
  sendColor BluePurple;       // unit 7
  sendColor Purple;           // unit 8
  sendColor White;            // unit 9
  // *** add more units if required 
  Rst();                      // reset LED-controller
  while (true) {}             // stop w/ keeping lights
}
void loop() {  // ***** Teensy loop() ***** unused
}
/************************************************
 *    User defined functions			*
 * **********************************************/
// ***** Send Color *** sendColor(blue,red,green) *****
void sendColor(byte b, byte r, byte g) {
  Byte = b;
  sendByte();
  Byte = r;
  sendByte();
  Byte = g;
  sendByte();
}
// ***** send Byte *** sendByte() ***** 
void sendByte(void) {
  for (int k = 0; k < 8; k++) {
    if (Byte &(B10000000>>k)) {
      One();
    } else {
      Zero();
    }
  }
}
// ***** send a bit 0 *** Zero() *****
void Zero(void) {
  digitalWrite(Out, HIGH);
  shortDelay();
  digitalWrite(Out, LOW);
  delayMicroseconds(1);
}
// ***** send a bit 1 *** One *****
void One(void) {
  digitalWrite(Out, HIGH);
  delayMicroseconds(1);
  digitalWrite(Out, LOW);
  shortDelay();
}
// ***** delay about 0.25μS *** shortDelay() *****
// Adjust this, on your micro-controller speed
void shortDelay(void) {
  for (uint8_t m = 0; m < 20; m++) {
    delayMicroseconds(0);
  }
}
// ***** Reset LED controller *** Rst() *****
void Rst(void) {
  digitalWrite(Out, LOW);
  delayMicroseconds(100);
}
// end of program

 ライブラリーは何も使いませんが、かなり単純にできますね。いきなり実際のテストはせず、まずは動かして出力信号のタイミングをオシロで確認します。

点灯したいユニットの数だけsendColorを並べるだけですし、残りのユニットは点灯しません。実際の数より多くの信号を送った場合は、存在するユニット数だけが点灯します。

1を送る際は オン1μS+オフ0.25μS とし、0を送る際は

オン0.25μS+オフ1μS としました。

0.25μSを作るには、本当はdelyNanoseconds(250)とか、delayMicroseconds(0.25)とか書きたいわけなのですが、そんなことできませんね。ここではdelayMicroseconds(0)として、呼び出しのオーバーヘッド時間を使うことにしました。

オシロで見ながらぴったりになるように調整した結果、Teensy4.0の場合は空呼び出しを20回程度すればちょうどよいのですが、他の高速マイコンを使う場合は、速度に応じてshortDelay(関数内にあるこの回数を調整してください。

さてこうしてから動かしますと、問題なく意図通りに動きます!LEDが明るすぎるので、全体に色の数値を下げた状態が上のスケッチですが、それ以外は問題はないようです。これなら簡単に好きなようにプログラムをすることができますね。RGBの値を変数にしたりしてやりたい放題が^^

おお!これでカラーLEDテープ照明が自由自在だ!

 

ところがですよ、とても明るくきれいに光っているLEDテープをうまく写真に撮るのは結構難しい。露出を数段下げたぐらいじゃ色がリアルにでない。照明用LEDなので明るすぎますからね。次の写真の下段のように、ピントをわざと外すとようやくリアルに近い色が表現されます^^. 

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

では今回はこのへんで。

LEDテープをDIYで好き勝手に動かしたい方のお役にたてば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

 

 

Nixie管時計を安直に作る(番外編)

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

外出自粛で週末は例によって片付けを開始。そこへ古いNixie管が出てきました。数本なら思い切って断捨離というところですが、9本も出たのでとてもそんなことできません^^;

見るとどれも1982年ソ連製のIN-12A(ИH-12A)で揃っています。

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

ピンの傷みもない逸品で、たぶん1990年頃に秋葉原の「日米商事」で購入した使い残しと思います。その頃多忙でも週1回ぐらいは帰りに寄り道して珍品をみつけていました^^;

今寄せ集めたデータシートの主なページは次です。

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

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

LOWになったピンにつながった数値を表示をするという単純な機構です。1番ピンがアノード、2番から11番がその陰極用で0、9~1の数値表示、12番はIN-12Aは空きでIN-12Bはドット表示。

仕様では200V電源から抵抗を介してアノードに接続、LOWにした陰極がネオンガスで光るわけで、グローランプや冷陰極管みたいなものです。

電流は1~2mA(表示によって少し違う)となるよう、またアノード・カソード間電位差が150V以下(140Vあたりが適)になるように制御します。仮に1.5mAで50Vの電位差を作るにはアノード抵抗に33kΩが必要です。もし1.1mAなら45kΩなので、安全側の47kΩでテストしました。

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

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

寿命は7500時間程度とありますが、与える電圧や電流次第です。アノード抵抗47kΩの場合、電源を190Vに固定し各数値表示の電流を測ると、どの数値もほぼ1.1mA前後となりました。

ピンのどれか1つをLOWにして残りをハイインピーダンスするには、例えば①BCDデコーダーICで数値を各ピンの信号に変換(今なら直接マイコンで出す手もないわけではありませんね、ピン数を多く消耗しますが)。そこから②高耐圧トランジスタMOSFET)アレイなどのベース(ゲート)に接続しておき、オープンコレクター(オープンドレイン)接続にするわけです。

 

やり方とデコーダICなどについて詳しく書かれている次のサイトを見つけました。英語ですがGoogleで訳してもほぼ大丈夫。たいへん役立ちます。

IC drivers for Nixie Tubes – GRA & AFCH

 

表示方法は単純なのですが、そういうわけで1本の管を使うのにBCDデコーダ1個と11要素の高圧トランジスタアレイを使う必要があるわけで、配線はどうしても複雑になりますね。どうやって短時間で工作できるか考え込んでしまいました。数値表示の価値がその昔、いかに高かったかの証拠でもありますね^^;

 

時間を消耗したくないので、ここからは安直な方法へと直行です!!

ネットでまずは基板がないか探してみます。eBayなどを漁ると次がありました。

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

これだとまだまだ面倒ですから、さらに探していくと次が見つかりました。

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

SMDのはんだ付けが不要なので楽そうです。ただ、各管のメスピン48本をどうするか(デュポンメスピンでいけるかも)と、試すにはちと高価か・・ということで次にAli-expressで次を見つけました。

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

送料を足せば似たような価格になりますが、専用ピンがそろっているのがいいですね。組立にはあまり時間がかかりそうもない。管がない以外はほぼ出来上がり製品といえるかもしれません。

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

主要作業は48本のピンのはんだ付けだけですから、これでちゃんと動くのなら「勝手なオリジナル工作」とはいえない番外編の安直版になります・・。まあしかし時間はいちばん大事ですからね。

注文後10日ほどで到着しましたので、週末に組み立てました。ハンダ付けを含めてたったの1時間半ほどで完了!

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

コンパクトでなかなか恰好良いではありませんか!

 

ところがPCB表面のシルク印刷がダサくて気にいりません。当面はラベルライターのテープを貼って、Nerd云々の文字を隠すことにしました。あとで表を全部黒に塗り直そうかな、などと考えています・・^^;

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

Webの出来上がり写真として次が載っていますが、支柱の使い方が不適切ではないかな・・と勝手に思います。

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

一番短い支柱をPCBと底板の間に使うべきではないかと。そうしないと天板からのNixie管等の飛び出しが大きすぎますから。つまり次のようにするとよいと思います。

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

管球用のメスピンのはんだ付けを上手に行う方法についてですが、予めメスピンを各管にしっかり挿しておき、管ごとそのまま基板にはんだ付けしていくと、正確で楽ちんにできました。

RTC(リアルタイムクロック)用のボタン電池を入れれば完成。電池は普通は使っていないCR1220(3V)ですので慌てましたが、幸いトヨタ某車のキーに使っていたのと同じでした。

電源をつなぐと問題なく稼働しました!

 

さて使い方です。

電源はUSBで1Aとなっていますが、測ると450mA程度でした。

全ての操作は次の写真の下の方にある2つのキー(UpとDown)だけで行います。

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

この2つのキーの短押しと長押しの使いわけをしますがそれなりに難しい^^;

例えば時刻合わせの場合、Down短押しで各桁の設定モードになりUp/Downで操作桁を移動。Down長押しで数値を変化。Downをしばらくホールドすると設定が終わる(終わらないときもある(・・?)。

Ali-expressの同じ商品の説明の中で、いちばん詳しく書いてあるのを選んでコピーしておくのが良いと思います。その他にバックライトカラーの変更やらSleepモードやら色々あってややこしいですから。

 

最後に勝手な余談を少し。

ここで使ったNixie管を探すとeBayやSOVTUBEなどではまだ扱われているのがわかります。昨日見たときは安いのは1本500~600円程度ですかね。使用時間が7500時間程度の管なので、中古でなく新古がいいですね。Amazonにもありそうですが高価。

それと不使用時は電源オフがいいですね。時刻自体は電池で維持されますので。

さらにホントの余談:

1970年代から数値表示には7セグメントLED等が使われ始めたと思いますが、ソ連では1980年代初めまでNixie管全盛とか。ちらちら調べてみると、1976年秋、ソ連から低空飛行で函館に亡命着陸(!)したベレンコ中尉のミグ25戦闘機事件。機内にはNixie管どころか真空管までも使われ、機体に鉄材もあった事に西側諸国がビックリ。「枯れた技術は絶対確実」の典型例かも・・。ソユーズなどでトラブルが殆どなかったし。

 

断捨離・片付けのつもりが、とんだゴミづくり(?)と時間の消耗になってしまいました。レトロ表示がお好きな方々のご参考にでもなれば幸いです。

 

©2021 Akira Tominaga, All rights reserved.