振ると文字が浮かび上がるLEDライト、ステッパマウス、高速回転。これらが頭の中で結びついたとき、「ステキなもの」ができました。このブログはその製作記録です。
はじめに
ステッパマウスをその場で高速回転させられる前提で話を進めます。
あと必要な技術はLチカだけです。
LEDを縦に5個並べてステッパマウスに取り付け、ステッパマウスを高速回転させながらLEDをちょうどいいタイミングで点滅させ、「HELLO WORLD ! 」という文字列を表示させます。
文字表示原理
LEDを5個使って文字を表示させる原理を、例として「H」を取り上げることで説明します。
以下では5個のLEDを上から順にLED1, LED2, LED3, LED4, LED5という番号で呼ぶことにします。また、文字を構成する点(LEDのパルス発光によるもの)をドットと呼ぶことにします。
次に示す原理説明図を見てください。これは各LEDが担当するドットの位置と発光タイミングを表しています。赤マルはドットでありLEDのパルス発光を表し、何もないマスはLEDが光ってないことを表しています。LEDxの行はLEDxが担当します(x=1,2,3,4,5)。ステッパマウスが左回転しているときにLED計5個を1列目、2列目、3列目、4列目、5列目の順にパルス発光させると、残像効果で人の目にはHと表示されているように見えます。
用意したもの
材料
ステッパマウス 1個
マイコン(とマイコンを使うために必要な電子部品たち) 1セット
片面ユニバーサル基板(穴数36×27) 2枚
スペーサー 数個
LED(5mm砲弾型、今回は赤色) 5個
保護抵抗1kΩ 5個
空中配線用の導線(ポリウレタン銅線または被覆線。場合によってはすずメッキ線)
アングル(TAMIYAのユニバーサルプレートLの付属品) 1個
養生テープ 少し
黒いビニールテープ 少し
熱収縮チューブ 少し
M3ボルト(ねじ部の長さは6mm以上) 3個
M3ナット 3個
道具
基板を切るために必要なもの(糸鋸など)
φ3.2の穴を開けるために必要なもの(ドリルビットと手回しドリル)
はんだ付けに必要なもの(はんだごてとはんだ)
ニッパー
工作
作業の流れ
LEDを縦に5個並べる基板(以降LED基板と呼びます。)の加工
LED5個を光らせるためのマイコンのための基板(以降第2基板と呼びます。(第1基板は元からマウスに付いている基板のことです。))の加工
LED基板と第2基板を固定するためのアングルの加工
第2基板にマイコンと書き込み回路と電源回路をはんだ付け
LED基板にLEDと抵抗をはんだ付け
LED基板と第2基板を固定
第2基板とLED基板を空中配線
余計な光を遮断
マウスに設置
作業の説明
LED基板加工
まず、LED基板を加工しました。穴数27×8の大きさで片面ユニバーサル基板を糸鋸で切り取ってφ3.2の穴を2つ開けました。穴の位置はランドの面がこちら側を向くようにして基板が縦長になるように見たとき、右下の穴から数えて上に5個目で左に2個目の穴と、右下の穴から数えて上に5個目で左に6個目の穴です。φ3.2のドリルビットを使って基板に元から開いている穴を広げました。この加工が終わると下図のようになります。
第2基板加工
次に第2基板を加工しました。第2基板が必要となった理由は、私のマウスに元から付いているマイコンだけだとピンが足りなかったので新たにマイコンを追加しなければならず、そのためのスペースが第1基板上にはなかったからです。第2基板をマウスに取り付けるために必要な加工をし、さらにφ3.2の穴を1つ開けました。この穴はLED基板と接続するためのものなので位置はどこでもいいですが、マウス本来の機能(迷路探索)の邪魔にならない位置にしました。(下図ではLED基板取り付け用の穴を開けただけの第2基板の様子を示します。)
アングル製作
次にLED基板と第2基板を固定するためのアングルを作りました。
TAMIYAのユニバーサルプレートLの付属品をニッパーで必要な大きさに小さく切り取りました。このアングルを使ってLED基板と第2基板を直交させた状態で互いに固定します。私はこんな感じに切り取りました。
第2基板はんだ付け
次に第2基板にマイコンと書き込み回路と電源回路をはんだ付けしました。第2基板上の電源スイッチとリセットボタンと書き込みピンを第2基板の端に配置し、その位置に対応する第1基板の一部を切り取りました。こうすることによって、あとで第1基板と第2基板を重ねてマウスに設置したときに第2基板を操作しやすくなります。下図は第2基板をマウスに取り付けたあとの写真で、上側の基板が第1基板、下側の基板が第2基板です。
もう1つ工夫した点は第1基板と第2基板のGNDと+3.3V電源を電気的に接続したところです。ピンソケットとピンヘッダを使いました。下図のように、第1基板の下側のピンソケットと第2基板の上側のピンヘッダが第1基板と第2基板を重ねたときにはまるような位置にはんだ付けしました。今回は7ピン分はんだ付けしましたが使っているのはGNDと+3.3V電源用の2ピンだけです。残りの5ピンは少なくとも今のところは使ってません。
LED基板回路
次にLED基板にLEDと抵抗をはんだ付けしました。以下に回路図と配線図を示します。回路図内のPIN1~PIN5はそれぞれマイコンの汎用入出力ピンを表しています。配線図内のLED基板の向きは上述したLED基板加工図とは反対で、ランドがある面がむこう側を向いていることに注意してください。
第2基板とLED基板の固定
次にLED基板と第2基板をアングルによって固定しました。ボルトとナットで固定すると下図のようになります。(このCAD画像では簡単のためにLEDなどの電子部品は省略してあります。)
第2基板とLED基板の電気的接続
次に第2基板上のマイコンのピンとLED基板上のLEDのアノードを空中配線し、さらに第2基板のGNDとLED基板のGNDを空中配線しました。実際に空中配線したところを撮った写真を示します。左がLED基板で右が第2基板です。銅色のポリウレタン銅線が第2基板からLEDのアノードにつながっていて、銀色のすずメッキ線が第2基板からLED基板のGNDにつながっているのが見えると思います。
遮光
次に不要な発光部分を隠しました。LEDの光で文字を表示させるわけですから、余計な光があっては困るからです。黒いビニールテープは使いやすいくて遮光性能が高いですが、ベトベトするのでそのまま使ったらマウスもベトベトしてしまいます。そこでマウスに養生テープを貼って、その上から黒いビニールテープを貼りました。LED基板の背面(LEDがない側)を黒いビニールテープで遮光しました。マウスに元から付いているLEDも邪魔なので隠しますが、完全に隠してしまったらマウスとして使うことができなくなってしまいます。そこで熱収縮チューブを使って遮光しました。こうすれば熱収縮チューブの開口部だけから光が漏れるので、マウス横方向からは光が見えないけれど上からみれば光が見えるという状況を作り出せます。その他の不要な発光部も隠しました。
第2基板をマウスに設置
最後に第2基板をマウスに取り付けました。これでハードは完成です。スペーサーを使って第1基板の下に重ねて設置しました。マウスの全体像を示します。写真右側の縦に長い基板がLED基板です。
プログラム
これから第2基板のマイコンに書き込むプログラムの説明をしていきます。第1基板用のプログラムの説明は省略します。C言語を使います。
待機マクロ
ここではLEDの発光時間待機と消灯時間待機をfor文の空ループで実現します。今回第2基板用に使ったマイコンはLPC1114FN28/102(偶然持ってたから)であり、その動作周波数は48[MHz]らしい(適当)です。ということは4800000を何倍かした回数だけループを回せば1秒間待機できます。試しに24000000回ループを回したら10秒間待機できました。つまり2クロックで1ループですね。私はまだ機械語を話せないのでなぜ2クロックなのかは分かりませんが、その理由を知らなくても今回のプログラムは書けました。
さて、この考え方を使って望みの時間待機するためのマクロを作ります。ループ回数は試行錯誤して決めました。このマクロについてはあとで説明します。
#define DOT_WIDTH_WAIT for(uint32_t i = 0; i < 2400; i++) // wait 1[ms] #define INTERVAL_ADJUSTMENT for(uint32_t i = 0; i < 33600; i++) // wait 14[ms] #define LETTER_SPACING_WAIT for(uint32_t i = 0; i < 24000; i++) // wait 10[ms] #define SPACE_WIDTH_WAIT for(uint32_t i = 0; i < 204000; i++) // wait 85[ms] #define EXCLAMATION_MARK_WAIT for(uint32_t i = 0; i < 120000; i++) // wait 50[ms]
LED点灯消灯関数
文字列を表示するためにはLED5個を別々に点灯・消灯できなければなりません。「HELLO WORLD !」という文字列を表示するためには次の15個の関数が必要です。これらをまとめてLED点灯消灯関数と呼ぶことにします。
void led00000(); void led00001(); void led00010(); void led00100(); void led00110(); void led01001(); void led01100(); void led01110(); void led10001(); void led10100(); void led10110(); void led10101(); void led11000(); void led11101(); void led11111();
例としてLEDを全部消灯させる関数led00000()と、LED5だけ点灯させる関数led00001()と、LED4だけ点灯させる関数led00010()について説明します。関数名に数字(0または1)が5個ありますが、それぞれ別のLEDを表しています。一番左の数字はLED1、左から2番目の数字はLED2、(略)という順に対応しています。数字の1は点灯、0は消灯を表します。
void led00000(){ led1(OFF); led2(OFF); led3(OFF); led4(OFF); led5(OFF); DOT_WIDTH_WAIT; } void led00001() { led1(OFF); led2(OFF); led3(OFF); led4(OFF); led5(ON); DOT_WIDTH_WAIT; led00000(); } void led00010() { led1(OFF); led2(OFF); led3(OFF); led4(ON); led5(OFF); DOT_WIDTH_WAIT; led00000(); }
一度光らせたあとに必ず全消灯しています。ledx()関数はLEDxを点灯・消灯する自作関数です(x=1,2,3,4,5)。
LED文字関数
次にLED点灯消灯関数を使って文字を表示させる関数を作りました。'H', 'E', 'L', 'O', 'W', 'D', '!'を表示する関数が必要です。これらをLED文字関数と呼ぶことにします。
void led_char_H() { led11111(); led00100(); led00100(); led00100(); led11111(); } void led_char_E() { led11111(); led10101(); led10101(); led10101(); led10001(); } void led_char_L() { led11111(); led00001(); led00001(); led00001(); led00001(); } void led_char_O() { led01110(); led10001(); led10001(); led10001(); led01110(); } void led_char_W() { led11000(); led00110(); led00001(); led00010(); led01100(); led00010(); led00001(); led00110(); led11000(); } void led_char_R() { led11111(); led10100(); led10110(); led10101(); led01001(); } void led_char_D() { led11111(); led10001(); led10001(); led10001(); led01110(); } void led_char_exclamation_mark() { led11101(); }
LED文字列関数
次にLED文字関数を使って文字列を表示させる関数を作りました。この関数をLED文字列関数と呼ぶことにします。文字と文字の間に隙間がないと読みづらいのでスペースを挟みます(LETTER_SPACING_WAIT)。文字列のあとにもスペースをつけます(SPACE_WIDTH_WAIT)。文字列"HELLO"と"WORLD"の長さをそろえるためにもスペースをつけます(INTERVAL_ADJUSTMENT)。びっくりマーク'!'専用のスペースも使います(EXCLAMATION_MARK_WAIT)。
void led_str_HELLO() { led_char_H(); LETTER_SPACING_WAIT; led_char_E(); LETTER_SPACING_WAIT; led_char_L(); LETTER_SPACING_WAIT; led_char_L(); LETTER_SPACING_WAIT; led_char_O(); INTERVAL_ADJUSTMENT; SPACE_WIDTH_WAIT; } void led_str_WORLD() { led_char_W(); LETTER_SPACING_WAIT; led_char_O(); LETTER_SPACING_WAIT; led_char_R(); LETTER_SPACING_WAIT; led_char_L(); LETTER_SPACING_WAIT; led_char_D(); SPACE_WIDTH_WAIT; } void led_str_exclamation_marks() { led_char_exclamation_mark(); EXCLAMATION_MARK_WAIT; }
main()関数
main()関数を示します。init()関数のなかでLチカ用の初期設定を行います。次にマウスの回転が安定するまで待機します。そして文字列"HELLO"を表示します。少し待機したあとに文字列"WORLD"を表示します。少し待機したあとにびっくりマーク"! ! ! ! !"を表示し続けます。
int main(void) { init(); // setup for LEDs for(uint32_t i = 0; i < 12000000; i++); // wait 5[s] for(uint8_t i = 0; i < 20; i++) { led_str_HELLO(); } for(uint32_t i = 0; i < 2400000; i++); // wait 1[s] for(uint8_t i = 0; i < 20; i++) { led_str_WORLD(); } for(uint32_t i = 0; i < 2400000; i++); // wait 1[s] while(1) { led_str_exclamation_marks(); } return 0 ; }
調整
あと必要なのは根気だけです。2つのマイコン間で通信し合っていないため、自動でマウス回転速度とLED点滅間隔を調整することはできません。文字列が読み取れるようになるまでマウスの回転速度とLED点滅間隔を少しずつずらしながら地道に調整を繰り返すしか方法はありません。私はマウス回転速度を固定してLED点滅間隔を少しずつずらしていったのですが、始めのうちはLED点滅間隔が長すぎるのか短すぎるのかすら分からない状態でした。何度か試行錯誤を繰り返すうちにちょうどいいLED点滅間隔を見つけられました。
LED点滅間隔を調整するためには先ほど示した待機マクロを使います。待機マクロに使われているループ回数を少しずつ変更しながらちょうどいいループ回数を見つけました。ドットサイズを調節するためにはDOT_WIDTH_WAITを使います。INTERVAL_ADJUSTMENTはDOT_WIDTH_WAITの8倍の回数ループさせます(と、思いきや14倍がちょうどよかったです。意味不明。)。文字間隔LETTER_SPACING_WAITは自分が読みやすいと思う回数ループさせます。SPACE_WIDTH_WAITによって文字列が移動する速さを変えます。EXCLAMATION_MARK_WAITで'!'の数と移動する速さを変えます。
当然ながら第1基板上のマイコン(マウス用)にはマウス用のプログラムを書きこみます。ただしその場高速回転プログラム付きですが。待機マクロを変更したら第2基板に書き込んで実験という動作を繰り返し、必要に応じてマウス回転速度を変更して第1基板に書き込んで実験しました。
動画
実際に動かしてみた様子を示します。この動画では右から左へ文字列"HELLO"が流れたあとに文字列"WORLD"が流れ、最後に"! ! ! ! ! !"と表示されます。
回転LED文字列マウス pic.twitter.com/01dJaCmbT3
— sophia (@tlo_olb) March 18, 2019