先日M5StickC Plusを使ってスマホからのラジオコントロールに対応したロボットカー(下図)に、遊び心を取り入れて見たいと思います。
世の中、遊びが無いとギスギスとして、うまく回りませんから、大切な事だと思います。
前回、328duino @8MHzを使ってシリアルRGB LEDチップWS28112Bを制御するアイディアまで解説してありましたので、今回は、実際のプログラムへ実装する所から続きます。
前回解説した技術を実際のプログラムに適用したコードをご覧に入れます。
結論としては以下コードの通り、命令の実行順を入れ替えたり、ループ処理などで費やされる命令の分、波形生成用のnop命令を削ったりして、タイミングを合わせ込みました。
uint8 trg; // temporary register
uint8 pd3_0; // PD3=0
uint8 pd3_1; // PD3=1
const int reg_portd=0x0b; // PORTD register address
pd3_0=PORTD & (~0x08);
pd3_1=pd3_0 | 0x08;
__asm__ __volatile__ (
"ld %[trg],z+\n\t" // 2 cyc
"\nL%=_for:\n\t"
"out %[portd],%[pd3_1]\n\t" // 1 cyc
"and %[trg],%[trg]\n\t" // 1 cyc
"brne L%=_else\n\t" // 1/2 cyc
// "0": HIGH=400ns=3cyc @8MHz
//# "nop\n\t" // "out"
//# "nop\n\t" // "and"
//# "nop\n\t" // "brne"
// "0": LOW=850ns=7 + 1[cyc] @8MHz
"out #[portd],%[pd3_0]\n\t" // 1 cyc
//# "nop\n\t" // "out"
"ld #[trg],z+\n\t" // 2 cyc
//# "nop\n\t" // "ld"
//# "nop\n\t" // "ld"
//# "nop\n\t" // "cp"
//# "nop\n\t" // "cpc"
//# "nop\n\t" // "brne"
//# "nop\n\t" // "brne"
"nop\n\t"
// end of loop "0"
"cp r28,r30\n\t" // 1 cyc
"cpc r29,r31\n\t" // 1 cyc
"brne L#=_for\n\t" // 1/2 cyc
"rjmp L#=_break\n\t"
"\nL#=_else:\n\t"
// "1": HIGH=800ns=6cyc @8MHz
//# "nop\n\t" // "out"
//# "nop\n\t" // "and"
//# "nop\n\t" // "brne"
//# "nop\n\t" // "brne"
"ld #[trg],z+\n\t" // 2 cyc
//# "nop\n\t" // "ld"
//# "nop\n\t" // "ld"
// "1": LOW=450ns=4 + 1[cyc] @8MHz
"out #[portd],%[pd3_0]\n\t" // 1 cyc
//# "nop\n\t" // "out"
//# "nop\n\t" // "cp"
//# "nop\n\t" // "cpc"
//# "nop\n\t" // "brne"
//# "nop\n\t" // "brne"
// end of loop "1"
"cp r28,r30\n\t" // 1 cyc
"cpc r29,r31\n\t" // 1 cyc
"brne L%=_for\n\t" // 1/2 cyc
"\nL%=_break:\n\t"
: [trg]"=&r"(trg)
: [portd]"I"(reg_portd),
[pd3_0]"r"(pd3_0),
[pd3_1]"r"(pd3_1),
"x"(dbuf + rgb_MAX_PIX*rgb_SIZ_PIX*8 + 1),
"z"(dbuf)
);
波形生成用に配置したnop命令の他にも、送信するビット値(0/1)の判定やループ処理などに命令実行が必要です。そうした命令の実行時間も含めて、所望の波形を生成する必要があるため、他の処理に使った命令に相当する分のnop命令をコメント(//#)して行きました。
最終的に、波形生成用nop命令のほとんどがコメント(//#)されていることからも、パフォーマンス的にはギリギリだったことが判ります。もう、これ以上CPUのクロック周波数が低下したら、ATmega328でWS2812Bを制御することは不可能です。
なお、WS2812Bの通信波形は約1,250nsと規定されていますが、上のコードでは、1サイクル@8MHz追加して、1,375nsで送信しています。WS2812Bデータシートでは、HIGH幅およびLOW幅に±150nsのマージンが規定されているため、スペック内使用です。
また、上に示したコードは、F_CPU=8MHzで合わせ込んであるため、他の周波数ではタイミングが合いません。
前節に示したコードの通り、実行する命令数で実時間を調整しているため、WS2812Bへのデータ送信処理を実行している間に、割り込み処理が実行されてしまうと、タイミングが変化してしまって失敗します。よって、データ送信処理中は割り込みを禁止しなければなりません。
データ送信時間は、以下の様に計算することができます。
以上より、データ送信時間は、8ピクセル×3色×8ビット×1,250ns=約240μsです。RGB LEDパネルへデータを送信する時間(約240μs)が、割り込みが禁止される時間と言う事です。
割り込みは、音楽演奏用のplay_mml()関数でも使っていますし、UARTのデータ受信、millis()関数等の実時間計測関数でも使っています。しかし、割り込みを禁止するのは約240μsという短時間だけなので、連続して、あるいは高頻度でデータ送信しない限り、問題ないはずです。
前の節で示したコードでは、GCCのインラインアセンブラでループを形成しました。
ループは、継続判定と、条件分岐命令で構成されます。分岐命令では分岐先アドレスを指定する必要があり、アセンブラでは一般にラベルが使われます。
GCCのインラインアセンブラで、分岐先アドレスを指定するためのラベルを生成する時、C言語のプログラム全体でユニークなラベル名となる様に、「%=」と言う記号を使います。
この記号「%=」は、インラインアセンブラが展開される度にユニークな数字列に置換されるため、例えば、「L%=_else」等と記述すると、「L1234_else」等に置換され、ユニークなラベル名を生成して使うことができるのです。
具体的な使用方法については、こちらの節で示したコードを参照して見てください。
ロボットカーの電飾用に使ったRGB LEDパネルは、8ピクセルのシリアルRGB LEDチップ(WS2812B)がカスケードに接続されたモジュールです。今回は、このモジュールを左右、横方向に使います。
例えば、自動車を模擬するモードでは以下の様に使う事を想定しています。
● | ● | ● | ● | ● | ● | ● | ● |
#7 | #6 | #5 | #4 | #3 | #2 | #1 | #0 |
右 ウ イ ン カ |
右 ヘ ッ ド ラ イ ト |
右 ブ レ | キ ラ ン プ |
フ ォ グ ラ ン プ |
左 ブ レ | キ ラ ン プ |
左 ヘ ッ ド ラ イ ト |
左 ウ イ ン カ |
その他、非常灯(パトランプ)等を表現する場合には、上の設定に関わらず、8ピクセル全てを使って表現する事とします。以下に、今回作り込む電飾動作モードについてまとめます。
電飾動作モード:
各動作モードは、シリアルモニタコマンドで遷移可能とします。以下に、電飾操作関係のシリアルモニタコマンド仕様を示します。
電飾用シリアルモニタコマンド仕様:
前の節にて、RGB LEDパネルを低電圧(3.3V)駆動する事に決めたにせよ、明るく光らせると、その分電流を喰い、バッテリの持続時間に影響を与えます。ただでさえ、ロボットカーは充電池駆動でかつ、電流喰らいのモータを駆動するため、消費電力には気を配っておく必要があります。
そこで、WS2812Bの最大輝度は、rgb_MAX_LUM(=15)で上限値を抑えてあります。仕様上の最大値が255なので、1/10以下に絞っていますが、それでも十分明るいので安心してください。
以上の検討を踏まえ、328duino用ロボットカー制御プログラムを改定しました。以下にリンクします。
改定したプログラム:robot_car.ino
コンパイルして328duinoへ書き込むと、シリアルモニタから以下のコマンドを受け付けています。
シリアルモニタコマンドへのリンク:
シリアルモニタコマンドが充実してきました。それだけロボットカーの制御範囲が広がったことを表しています。328duino(ATmega328)のメモリ容量には、まだ余裕があるので、もう少し拡張してもへっちゃらです。
ロボットカーとして組み上げる前に、328duinoと演奏電飾モジュールを接続して、部分的な結合検証をしておきましょう。UNO 3.3V化328duino、演奏電飾モジュール、FTDI等のUSB⇔UART変換モジュールを以下接続表に従って繋いでください。
USB⇔UART変換 モジュール(FTDI等) |
接続 | UNO 3.3V化328duino | 接続 | 演奏電飾モジュール |
5V | → | Vcc | → | Vcc |
GND | ⇔ | GND | ⇔ | GND |
Vdd(3.3V) | → | Vdd | ||
D9(OC1A) | → | BUZO | ||
D3(OC2B) | → | RGBS | ||
RXD | ← | TXO | ||
TXD | → | RXI | ||
DTR | → | DTR |
※FTDIの電圧ジャンパは3.3V側、かつ、5V出力端子が必要。詳しくは、こちらを参照。
なお今回は、まだM5StickC Plusを接続しないので、M5StickC Plus周りの接続は実施しないでください。328duinoには、こちらのプログラムをコンパイル&書き込んでおいてください。
以下、実験中の様子です。
ロボットカーとして組み上げていないのでモータは回りませんが、上図の通り、電飾として使うRGB LEDパネルをロボットカーの前面に固定して実験しました。自動車モードのウインカなど、左/右が認知できる状態で確認すべき項目もあったため、です。
以下の様に実験を進めて行きましょう。
実験サンプル:【 】内はシリアルモニタからの送信データ
【pix,-2】
OK: pix -2, 0,0,0
自動車モードに遷移しました。停止したまま暫く(〜8s)放置すると、ハザードランプ(左右端の1ピクセルずつ、計2ピクセル)がチカチカと点滅し始めます。
【mtr1,255】
OK: motor #1 duty=255 turn=1
モータは接続していないので回転しないはずですが、もし、ロボットカーに組み込んで実験している場合は、ロボットカーが走行してしまわないようにタイヤを浮かせて実験してください。上のコマンドで、ロボットカーは右旋回しますので、右側のウインカが点滅しているはずです。
【stp】
OK: stop motor
【mtr2,255】
OK: motor #2 duty=255 turn=1
ブレーキランプが点灯し、今度は左旋回なので、左側のウインカが点滅しましたか?
【pix,-2,1】
OK: pix -2, 1,0,0
ヘッドライト(ウインカの内側の左右2ピクセル)が白く光った筈です。左側ウインカも点滅し続けていますか?
【stp】
OK: stop motor
【pix,-2,1,1】
OK: pix -2, 1,1,0
ヘッドライトに加えて、フォグランプ(中央2ピクセル)が黄色く光りましたか?【stp】コマンドで走行を停止したので、暫くすると、再びハザードランプが点滅し始めるはずです。
ここまで期待通りに動作していれば、RGB LEDパネル(WS2812B)の制御は正常に機能していると考えて良いでしょう。続いて、音楽演奏処理の検証に移ります。
実験サンプル:【 】内はシリアルモニタからの送信データ
【pix,-3】
OK: pix -3, 0,0,0
救急車モードに遷移しました。RGB LEDパネルが非常灯(パトランプ)を模擬した赤色ピクセルの回転に切り替わった筈です。同時に、パッシブブザーからサイレン【bgm,B44,G44】が鳴り続けます。日本では、お馴染みの、救急車のサイレンです。
【pix,-4】
OK: pix -4, 0,0,0
虹色モードに遷移しました。RGB LEDパネルが虹色に回転すると同時に、パッシブブザーからチャルメラが鳴り始めましたか?別名、「ラーメン屋台トラック」モードです。
救急車モード、虹色モード共、モータの制御やシリアルモニタコマンドの受付など、ロボットカー制御に何ら影響することなく、バックグラウンドで音楽を演奏し続ける事などにも着目してください。
パッシブブザーから音楽が聞こえていれば、音楽演奏機能も正常に動作していると考えて良いです。
動作モードをいくつか追加して、ロボットカーに「遊び」ができました。ただ、このままだとシリアルモニタを接続してコマンドを送信しない限り、動作モードを変更することができません。次回、M5StickC Plusの制御プログラムに、新たなコマンドを送信するコードを追加して行きたいと思います。