少し前ですが、自分がArdinoを触り始めた頃に、それを使ってLED点灯/消灯(いわゆる「Lチカ」)を試していたこと、加えてArduinoで動くHTTP通信が可能なESP8266を試してみたいという動機から、ESP8266(特にESP-12Fを搭載したD1 Mini)とLEDを使って何か変わったものが作れないかなと考えていました。
特に、LEDについてはamazonで2,000円以上で無料配送になる誘惑から、そんなに使う機会もないようなLED 200個セットのものをamazonで購入してしまったので、何十個かLEDをまとめて使えないものか、と思い巡らせていたところ、「そういえば、ESP8266って何個までLEDを付けられるんだろ?」という素人的な疑問が湧いてきました。
GPIOのピンの数は10ピンに満たないぐらい少ないと思いますので、増やせる手段はあるとは思っていましたが、どうやって増やせるかあんまりわからなかったので、インターネットで様々なサイトを下調べをすると、デジタルピンの増設が可能なシフトレジスタを使用すれば可能であることがわかりました。
使用するシフトレジスタについては、いくつか選択肢があると思いますが、今回は自分でもトライできそうな「74HC595」を使ってピンを増設してみようと思います。
で、作ってみるものなのですが、LEDをそれなりに使いそうな「時計」でも作ってみようかなと考えました。考えていたときに身近にたまたまデジタル時計があったのと、ESP8266はHTTP通信ができるので、NTPサーバから時刻とか取得できるので、なんか良さげのものが作れるんじゃないかなぁと。ということで学習も含めて製作にトライしてみました。
用意したもの
- ESP8266:HiLetgo 5pcs D1 Mini ESP8266 ESP-12F NodeMCU Lua WeMos D1 Mini 4M Bytes WiFi Module with Pin Headers
ESP-12Fが搭載されている小型のESP8266(D1 Mini)を使用しました。
今回シフトレジスタについてはマルツオンラインで購入しました。「TC74HC595AF」でも形が異なるだけで、仕様は同じのようですが、他のサイトでは「TC74HC595AP」を使用されているケースが多かったので、こちらを使うことにしました。
その他準備したもの:
- 抵抗 200Ω(LED個数分)
- 積層セラミックコンデンサ 50V 0.1μF(レジスタ個数分、ノイズ対策用)
- LED用スペーサー(高さ調整用)
- USBケーブル(Type A – Micro USB Type-B オス/オス)
- 時計本体(機器を入れる木製トレイ)・・・セリアで購入+フタ用の薄いベニア板
- LED点灯用の穴のアクセント:KIYOHARA サンコッコー 両面ハトメ 10組入り 18 内径6mm アンティックゴールド SUN11-188
衣類・装飾用のものを使用しました。
- フタ/木箱接着用マグネットシート:ダイソー、セリア等の100均で購入
動作イメージ
時計として動かすイメージは次の通りです。実用的か?といえばそうと言い難いとは思いますが、LEDをたくさん使って動作を試すための試作品ということで見ていただければと思います。
WiFi接続後にNTPサーバから時刻を取得後、その時刻(24時間形式、時と分を使用)をもとに、対応するLEDを点灯させます。例えば上記例のように、8:30の場合は時の部分の一の位の8、分の部分の十の位に3、一の位に0に対応するLEDを点灯させます。これを1秒毎に時刻をチェックすることで時計として機能させます。
使用するLEDは合計で最低で29個になります。
なので、その各部分で点灯しているLEDの数字を読み取り、時刻として認識します。通常のデジタルやアナログで表記する方法とは少し変わってますよね・・・
シフトレジスタの使用イメージ
今回購入したシフトレジスタを販売していたマルツさんのサイトにあったデータシートから、各レジスタでは次の図の通り、合計8ピン(QA〜QH)まで拡張可能となっています。
上記仕様を踏まえると、今回使用するLEDは最低29個ですので、シフトレジスタを4個用意する必要があります。これらを使って29個のLEDを制御するためには、各レジスタ間をカスケード接続(QH’の9番→SIの14番)する必要があります。
そのため、今回のケースについては、次のようにレジスタを接続し、使用するピン毎に時刻表示する数字を割り当てます。
最大32ピンまで拡張できますが、LEDを29個使用するので残り3ピンは余ります。せっかくなので、そのうちの1ピンはハートビートの用途で使用することにしました。
それ以外の10番から14番については次の通り接続をしました。
- 10番(SCLR):VCCに接続(データシートの真理値表からSIの変化によらず、HIGH固定)
- 11番(SCK):D2に接続(クロック)
- 12番(RCK):D3に接続(ラッチ)
- 13番(G):GNDに接続(出力イネーブル、データシートの真理値表からLOW固定)
- 14番(SI):D4に接続(①のみ、シリアルデータ入力用)
ブレッドボードでの配置例
レジスタ4つをカスケード接続した際の配置例です。LEDの色は時分の各桁の位が分かりやすいように、色分けしています。(実際はすべて赤色のLEDにしています)
サンプルコード
正直もっと効率のよさそうなコードは組めると思いますが、こんな感じに動かしたサンプルです。特にどのシフトレジスタを使用しているか容易に把握したかったので、レジスタ毎に設定するバイト変数を設けています。
#include <ESP8266WiFi.h>
#include <time.h>
#define JST 3600*9
const char* ssid = "ssid";
const char* password = "password";
// LED
const int PIN_CLK = D2; //74HC595 Pin 11 SRCLK
const int PIN_LATCH = D3; //74HC595 Pin 12 RCLK
const int PIN_SER = D4; //74HC595 Pin 14 SER
const byte ALL_OFF = B00000000;
void setup() {
Serial.begin(9600);
delay(100);
Serial.println("Start");
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(500);
}
Serial.println();
Serial.print("Connected, IP address: ");
Serial.println(WiFi.localIP());
// Set a time from NTP servers
configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
// Initialize LED OUTPUTs
pinMode( PIN_SER, OUTPUT );
pinMode( PIN_LATCH, OUTPUT );
pinMode( PIN_CLK, OUTPUT );
}
void loop() {
time_t t;
struct tm *tm;
t = time(NULL);
tm = localtime(&t);
byte b1 = ALL_OFF;
byte b2 = ALL_OFF;
byte b3 = ALL_OFF;
byte b4 = ALL_OFF;
int tmp = 0;
// Minute: i1: Ones, i2: Tens
tmp = tm->tm_min;
int i1 = tmp % 10;
int i2 = (tmp - i1) / 10;
// Hour: i3: Ones, i4: Tens
tmp = tm->tm_hour;
int i3 = tmp % 10;
int i4 = (tmp - i3) / 10;
// DEBUG
Serial.printf("%d %d %d %d\n", i4, i3, i2, i1);
// 時分の各桁でのLED点灯をセット
// Minute - Ones
if (0 <= i1 && i1 <= 7) {
// 0 - 7 -> b1
bitSet(b1, i1);
} else if (8 <= i1 && i1 <= 9) {
// 8, 9 -> b2
bitSet(b2, i1 - 8);
}
// Minute - Tens
if (0 <= i2 && i2 <= 5) {
// 0 - 5 -> b2
bitSet(b2, i2 + 2);
}
// Hour - Ones
if (0 <= i3 && i3 <= 7) {
// 0 - 7 -> b3
bitSet(b3, i3);
} else if (8 <= i3 && i3 <= 9) {
// 8, 9 -> b4
bitSet(b4, i3 - 8);
}
// Hour - Tens
if (0 <= i4 && i4 <= 2) {
// 0 - 2 -> b4
bitSet(b4, i4 + 2);
}
// Heatbeat -> b4
if ((tm->tm_sec % 2) == 0) {
bitSet(b4, 7);
}
// Turn on/off LED lights
digitalWrite( PIN_LATCH, LOW );
shiftOut( PIN_SER, PIN_CLK, MSBFIRST, b4 ); // Set values to Register 4
shiftOut( PIN_SER, PIN_CLK, MSBFIRST, b3 ); // Set values to Register 3
shiftOut( PIN_SER, PIN_CLK, MSBFIRST, b2 ); // Set values to Register 2
shiftOut( PIN_SER, PIN_CLK, MSBFIRST, b1 ); // Set values to Register 1
digitalWrite( PIN_LATCH, HIGH );
delay(1000);
}
シフトレジスタの動作の仕組みや値の格納方法については、「基礎からのIoT入門サイト」さん、「しなぷすのハード製作記」さんのコンテンツが非常に参考になりました。
クロック(SRCLK)のHIGH /LOWのタイミングに合わせて、SERから入力されたビット列が順番にシフト動作していきます。これにより、QAにセットされていたものがQBへ、QBにセットされていたものがQCへ、といった形でビット列がセットされていきます。
各ピン(QA〜QH)へのビットの割り当てはshiftOut関数を使っていますが、今回レジスタを4つ、レジスタ①→②→③→④の順番でカスケード接続しているため、一番奥に接続されているレジスタ④から逆の順番でビットを格納していくイメージになります。
例えば、8時30分のLEDを点灯する場合、図のようなビット配置になり、一番遠いレジスタである④から順番にLEDのon/offの設定をしていきます。
shiftOut関数の第3引数であるビット順のオプションとして、「MSBFIRST(最上位ビットから設定)」、「LSBFIRST(最下位ビットから設定)」があります。
例えば、ビット列が「B11000000」を与える場合、MSBFIRST、LSBFIRSTで設定した時のビット順は次のようになります。
- MSBFIRST
- LSBFIRST
今回は処理の都合上、時刻の値とビット列番号をできるだけ同じに合わせたかったので、「MSBFIRST」で設定することにしました。
これらのビットデータをセットする前に、RCLK(ラッチ)をLOWにしておき、すべてのデータをセットした後にHIGHにて変更を有効にします。
実際の組み立て
ユニバーサル基板上で組み立てた状態です。見た目スッキリしているように見えますが、裏は配線だらけで非常に見苦しいです・・・
フタを被せるので、LEDにはスペーサーを入れて、高さを均等にしています。
次は機器を入れる箱の作成です。まず、フタについてはこんな感じで自作しました。数字や線は手元にあったハンダごてを使ってウッドバーニングっぽく書いてみました。
次に、自宅にあったインパクトドライバを使ってLED点灯用の6mmの穴を開けます。
穴を開けたあと、ハトメを使って装飾します。
木箱のほうは機器固定用に四隅にビスで固定し、USBケーブル用の通し穴を開けておきます。
試しに給電させると、こんな感じに点灯します。
フタを被せて完成です。
実際の動作例はこんな感じです。秒毎に右下のハートビート用のLED(白)が点滅し、分が変わる毎にLED点灯の位置が変わります。この点灯位置によって、時刻を読み取ることができます。
振り返り
今回の試作を通じて、シフトレジスタを使って制御できるピンの数を拡張することができました。
ただ、実際に動かしてみると、配線が悪いのか、プログラムが悪いのか、時刻によっては意図しないピンで点灯したり、点滅する箇所が出てくるなど、うまく制御できていないように見えました。プログラム的には問題ないように見えるのですが、やはり配線がまずかったのかな・・・原因を見つけるのにもう少し時間がかかりそうです。
ただ、学習の観点では今回の試作を通じてその制御の仕組みの「さわり」の部分は理解することができたかなと思います。まだまだ細かい動きや仕組みは分からないことだらけですが、これを機にもっと理解できるようになればと思います。