今日は12月3日。「いずみ」と読むことができ、大石泉の日。
WS2812B
WS2812B は、フルカラーLEDである。
マイコンが内蔵されており、信号線1本だけで色の設定が可能である。
さらに、複数のWS2812Bを数珠つなぎにして、まとめて色を設定することが可能である。
WS2812B を用いた様々な製品が発売されているが、今回は以下の16×16のマトリックスを用いる。
同じ「16×16のマトリックス」の製品でも複数の種類がある。
今回使用するのは、文字が普通の方向になるように置いたとき、以下の順番にLEDが並んでいる製品である。
- 最下段の一番左のLEDが最初である
- そこから、右に向かってLEDが配置されている
- LEDは段ごとにジグザグに配置されている。すなわち、下から奇数番目の段は左から右に、偶数番目の段は右から左にLEDが配置されている
WS2812B と IchigoJam の連携
IchigoJam (1.4系) には、WS2812B を操作する機能がある。
※IchigoJamはjig.jpの登録商標です。
BASIC コマンド
WS.LED LEDデータ数
WS.LED LEDデータ数, リピート数
配列に格納されたデータを色データとして WS2812B に送信する。
「LEDデータ数」は、配列からLED何個分のデータを読み取るかを指定する。配列の3要素でLED 1個分のデータとなるので、参照する要素数はこの数の3倍である。データは配列の先頭 ([0]
) から順に読み取られる。
「リピート数」は、「LEDデータ数」で指定した数のデータを1セットとして、何セット繰り返し送信するかを指定する。これにより、少ないデータで長い WS2812B の列を制御できる。「リピート数」は省略可能で、省略した場合は1となる。
IchigoJam の配列は102要素しか無いため、このコマンドで使用可能な最大のLEDデータ数は34である。
マシン語API
void ws_led (uint32_t countrepeat, const uint8_t* data, uint32_t gpiomask);
アドレス #EE
から符号なしの2バイトを読み取り、これを関数のアドレスとして呼び出す。
countrepeat
は、WS2812B に送信するデータ数を指定する。「LED数」を指定する BASIC コマンドとは異なり、送信する要素数をそのまま指定する。
data
は、WS2812B に送信するデータが格納された先頭アドレスを指定する。IchigoJam の配列の要素は2バイトだが、ここで指定するデータは1バイトで1要素を表す。
gpiomask
は、送信先のポートを表すデータを指定する。LED端子に出力する場合は、以下で定義された GPIO_LED
を指定する。
#define GPIO1 0x50010000
#define GPIO_LED (GPIO1 + (1 << (5 + 2))) // LED
IchigoJam の VRAM は 32×24=768 バイトあるので、これを利用して LED 256個の色情報を個別に設定できる。(3×256=768)
「大石泉すき」を描くプログラム
16×16の WS2812B マトリックスに「大石泉すき」をスクロールさせて表示するプログラムを作成した。
まずは画面に表示する
10 ' オオイシイズミ スキ (ガメン)
20 CLV:LET[0],0,64,64,64,64,8190,192,448,352,800,560,1552,3096,6156,4102,0,0,0,0,8190,384,192,96,48,8184,4140,4134,4128,4128,8160,0,0,0,256,128,8188,4100,8188,4100,8188
30 LET[40],128,3324,960,416,664,1156,6336,0,0,128,128,4092,128,128,240,144,144,240,128,192,96,48,16,0,0,32,96,3904,508,128,7296,2044,256,256,256,388,12,24,8176,0
40 CLT:T=0:FOR I=-1 TO 4:FOR O=0 TO 15:CLS
50 IF I<0 GOTO 90
60 FOR X=0 TO 15-O:FOR Y=0 TO 15
70 IF ([16*I+Y]>>(O+X))&1 LOCATE 8+X,4+Y:PRINT "*";
80 NEXT:NEXT
90 IF O=0 OR I=4 GOTO 130
100 FOR X=16-O TO 15:FOR Y=0 TO 15
110 IF ([16*I+16+Y]>>(X-(16-O)))&1 LOCATE 8+X,4+Y:PRINT "*";
120 NEXT:NEXT
130 T=T+10
140 X=T-TICK():LOCATE 0,0:PRINT X:IF X>0 WAIT X
150 NEXT:NEXT
160 GOTO 40
20~30行目で、「大石泉すき」のデータを定義する。
それぞれの文字を16×16ピクセルで表す。
1行が16ビットなので、ちょうど配列の1要素で表すことができる。
1文字は上の行から下の行に向かう順番で表し、1行はLSBを左端、MSBを右端として表す。
40行目で、経過時間管理の初期化と文字配置のループを行う。
変数 I
で、どの文字を表示するかを決める。
変数 O
で、文字をどの位置に表示するかを決める。
50~80行目で、左側の文字の描画を行う。
スクロールさせるため、基本的に文字は2個を表示する。
変数 O
に従って表示する文字の範囲を決定し、表示する。
90~120行目で、右側の文字の描画を行う。
条件やデータの位置は異なるが、基本的に50~80行目と同じである。
130~140行目で、画面の更新間隔を制御する。
130行目で「このフレームの終了時、経過しているべき時間」を計算する。
140行目で、130行目で計算した時間になるようにウェイトを入れる。
150~160行目で、40行目のループを回す。
OneFiveCrowdで実行すると、画面上に「大石泉すき」が流れていく。
WS2812B マトリックスに表示する
10 ' オオイシイズミ スキ (WS.LED)
20 CLV:LET[0],0,64,64,64,64,8190,192,448,352,800,560,1552,3096,6156,4102,0,0,0,0,8190,384,192,96,48,8184,4140,4134,4128,4128,8160,0,0,0,256,128,8188,4100,8188,4100,8188
30 LET[40],128,3324,960,416,664,1156,6336,0,0,128,128,4092,128,128,240,144,144,240,128,192,96,48,16,0,0,32,96,3904,508,128,7296,2044,256,256,256,388,12,24,8176,0
40 POKE#8A0,15,0,20,15,0,20,15,0,20,2,20,2,5,15,0:POKE#700,63,161,128,49,128,49,3,32,0,2,2,74,238,35,27,136,24,71,0,0,128,0,1,80
50 CLT:T=0:FOR I=-1 TO 4:C=#8A0+3*I:FOR O=0 TO 15:CLS
60 IF I<0 GOTO 100
70 FOR Y=0 TO 15:M=[16*I+Y]>>O:FOR X=0 TO 15-O:IF Y%2 THEN U=X ELSE U=15-X
80 IF M>>X&1 COPY#BD0-48*Y+3*U,C,3
90 NEXT:NEXT
100 IF O=0 OR I=4 GOTO 140
110 FOR Y=0 TO 15:M=[16*I+16+Y]:FOR X=16-O TO 15:IF Y%2 THEN U=X ELSE U=15-X
120 IF M>>(X+O-16)&1 COPY#BD0-48*Y+3*U,C+3,3
130 NEXT:NEXT
140 Z=USR(#700)
150 T=T+90
160 X=T-TICK():PRINT X:IF X>0 WAIT X
170 NEXT:NEXT
180 GOTO 50
マシン語のソースコード
R1 = @ARRAY
R1 += #80
R1 += #80
R0 = 3
R0 = R0 << 8
R2 = [@GPIO_LED]L
R3 = #EE
R3 = [R3]W
GOTO R3
ALIGN 4,0,0
@GPIO_LED
DATAL #50010080
ORGR #100
@ARRAY
「大石泉」を大石泉をイメージした青色で表示し、「すき」をニューウェーブの他の2人をイメージした赤色と黄色で表示するようにした。
画面に描画するプログラムと比べて、以下の点を変更している。
- 配列の文字データの後に、LEDに設定する色のデータを追加した。各文字についてそれぞれ3バイトの色情報が並んでいる。
- データを配置する位置を今回のLEDマトリックスの仕様に合わせて計算して配置するようにした。
- 高速化のため、同じ計算の繰り返しを避けて変数に格納するようにした。
- データを WS2812B に送信するためのマシン語コードを追加し、呼び出すようにした。
動くことは動くが、実機 (IchigoJam Q、1.4.3) でビデオ出力なし (VIDEO 0
) のとき1フレームの描画に最大1.5秒弱かかる程度に低速である。
データの配置もマシン語で行う
10 'オオイシイズミ スキ(WS.LED マシンゴ)
20 CLV:LET[0],0,64,64,64,64,8190,192,448,352,800,560,1552,3096,6156,4102,0,0,0,0,8190,384,192,96,48,8184,4140,4134,4128,4128,8160,0,0,0,256,128,8188,4100,8188,4100,8188
30 LET[40],128,3324,960,416,664,1156,6336,0,0,128,128,4092,128,128,240,144,144,240,128,192,96,48,16,0,0,32,96,3904,508,128,7296,2044,256,256,256,388,12,24,8176,0,15,3860,5120,15,276,276,3845
40 POKE#700,240,180,114,160,6,138,135,139,54,178,61,165,160,53,173,25,173,25,173,25,0,36,58,160,113,1,64,24,0,25,0,25,0,35,54,66,0,212,3,136,0,34,4,46,0,218,2,140,18,4,155,24,251,64,15,34,1,33,11,66
50 POKE#73C,28,208,16,70,12,66,1,208,64,66,15,48,48,33,97,67,73,66,9,24,9,24,9,24,3,32,0,2,208,48,9,24,40,160,9,24,40,70,186,66,0,218,192,28,148,70,2,120,10,112,66,120,74,112,130,120,138,112,98,70
60 POKE#778,91,8,82,30,220,218,100,28,15,44,200,221,30,161,128,49,128,49,3,32,0,2,2,74,238,35,27,136,240,188,24,71,128,0,1,80
70 CLT:T=0:FORI=-1TO4:FORO=0TO15:CLS:Z=USR(#700)
80 T=T+10
90 X=T-TICK():?X:IFX>0WAITX:NEXT:NEXT:GOTO70
マシン語のソースコード
' R7 : O
' R6 : I
' R5 : 色データのアドレス (#8A0 + 3*I)
PUSH {R4,R5,R6,R7}
R0 = @VARS
R6 = [R0 + 8]W
R7 = [R0 + 14]W
R6 = SXTH(R6)
R5 = @ARRAY
R5 += #A0
R5 = R5 + R6
R5 = R5 + R6
R5 = R5 + R6
' R4 : Y
' R3 : 画像データ (≒M)
R4 = 0
@Y_LOOP
' 行の画像データを取得する
R0 = @ARRAY
R1 = R6 << 5
R0 = R0 + R1
R0 = R0 + R4
R0 = R0 + R4
R3 = 0
R6 & R6
IF MI GOTO @NO_FIRST_PART
R3 = [R0 + 0]W
@NO_FIRST_PART
R2 = 0
R6 - 4
IF GE GOTO @NO_SECOND_PART
R2 = [R0 + 16]W
@NO_SECOND_PART
R2 = R2 << 16
R3 = R3 + R2
R3 >>= R7
' 行の画像データをVRAMに反映する
' R2 : 15-X
R2 = 15
@X_LOOP
R1 = 1
R3 & R1
IF EQ GOTO @NO_PUT_DOT
' R0 : U
R0 = R2
R4 & R1
IF EQ GOTO @NO_REVERSE_X
R0 = -R0
R0 += 15
@NO_REVERSE_X
' R1 : #BD0-48*Y+3*U
R1 = 48
R1 *= R4
R1 = -R1
R1 = R1 + R0
R1 = R1 + R0
R1 = R1 + R0
R0 = #03
R0 = R0 << 8
R0 += #D0
R1 = R1 + R0
R0 = @ARRAY
R1 = R1 + R0
' R0 : C / C+3
R0 = R5
R2 - R7
IF GE GOTO @NO_SHIFT_COLOR
R0 = R0 + 3
@NO_SHIFT_COLOR
' 色データをコピーする
R12 = R2
R2 = [R0 + 0]
[R1 + 0] = R2
R2 = [R0 + 1]
[R1 + 1] = R2
R2 = [R0 + 2]
[R1 + 2] = R2
R2 = R12
@NO_PUT_DOT
R3 = R3 >> 1
R2 = R2 - 1
IF GE GOTO @X_LOOP
R4 = R4 + 1
R4 - 15
IF LE GOTO @Y_LOOP
' VRAM上の画像データをLEDに転送する
R1 = @ARRAY
R1 += #80
R1 += #80
R0 = 3
R0 = R0 << 8
R2 = [@GPIO_LED]L
R3 = #EE
R3 = [R3]W
POP {R4,R5,R6,R7}
GOTO R3
ALIGN 4,0,0
@GPIO_LED
DATAL #50010080
ORGR #100
@ARRAY
ORGR #1CC
@VARS
データを配置する処理もマシン語で行うようにし、BASICでは描画位置とタイミングの管理のみを行うようにした。
マシン語では、レジスタが32ビットあることを活かし、2文字分のデータをまとめて取得して処理するようにした。
更新間隔を調整しやすいよう、タイミングを決定する80行目 T=T+10
は独立した行にした。
実機 (IchigoJam Q、1.4.3) で1フレームの処理に1/60秒以下しかかからず、高速で処理できるようになった。
ライセンス
今回のプログラムは、CC BY 4.0 でライセンスする。
このプログラム (改造したものを含む) を公開の場で利用する際は、出典を示していただけると嬉しい。
これは、Qiitaの利用規約に基づくプログラムの利用を妨げるものではない。
結論
IchigoJam のマシン語APIを利用することで、BASICコマンドで制御できるより多くの WS2812B LED を制御し、マトリックスに「大石泉すき」を表示することができた。
大石泉すき。