福野泰介の一日一創 - create every day

Armマシン語でWS2812Bマトリクスの表示ドライバのようにちょっと大きめのプログラムをつくると陥るレジスタ不足。 この時の、対処方法は2つ。上位レジスタとスタックです。

1. 上位レジスタを活用する
Arm Cortex-M0には、R0〜R15まで16コのレジスタ。上位レジスタR8〜R15は使える命令が5コに制限されますが、カウンタの回数チェックや、割り算ルーチンのポインタを格納しておくなど用途を選べば活用できます。下記、Cortex-M0 Armマシン語表より、上位レジスタで使えるコマンド。

Rd=Rm Rd+=Rm Rn-Rm GOTO Rm GOSUB Rm

R8〜R11: 呼び出し元に帰る前に際には元に戻す必要がありますが自由に使えます。
R12: 破壊してOK。自由に使えます。
R13: スタックレジスタで、スタックに積んである一番上を指すポインタです。
R14: 呼び出し元のアドレスが入ったリンクレジスタ、GOSUBで呼び出す際に更新されます。
R15: プログラムカウンタ、実行中のアドレスの2つ先(4byte先)を指すポインタです。

上位レジスタをカウンタの回数チェックに使う(R0に指定した数までカウント)

R12=R0 R0=0 R0+=1 R0-R12 IF !0 GOTO -2 RET

上位レジスタを割り算に使うと、R3を他の用途使えて便利です。
パラメータなどで渡されたR0の値を7で割って返すプログラム例。
※IchigoJam BASIC ver1.2からR3に符号なし除算ポインタがセットされています(R0=R0/R1、R1に余り)。飛び先でRETしてくれるのでGOTOを使って省メモリ化しています。

R12=R3 R1=7 GOTO R12

上位レジスタを使った割り算の余り(R0を7で割った余りを返す)

PUSH {LR} R12=R3 ' R3も使ったいろいろ処理してOK! R1=7 GOSUB R12 R0=R1 POP {PC}

R8〜R11までをフルに使うためにスタックに保存する

PUSH {R4-R7,LR} R4=R8 R5=R9 R6=R10 R7=R11 PUSH {R4-R7} ' R0-R12まで使い放題 POP {R4-R7} R8=R4 R9=R5 R10=R6 R11=R7 POP {R4-R7,PC}

参考、フル活用例「IchigoJam で BASIC と C とアセンブラ速度比較と最適化あそび - Qiita
マシン語で書くと、BASICの1万6千倍も速い!

1. スタックを活用する
その5で解説したスタック、POPしなくても参照できる便利な命令があります。

※u7/u8:4byte単位
※PUSH:regsの大きいレジスタから順に、SPを減らしSPへ積む 例)PUSH {R1,R2}
※POP:regsの小さいレジスタから順に、SPから読み込みSPを増やす 例)POP {R1,R2}
※N:指定したレジスタの数、PCへPOPした場合4+Ncycles(それ以外は1+Ncycles)
Cortex-M0 Armマシン語表 (asm15表記、抜粋)

SPは、R13の別名です。SPのアドレスには最後に積んだ値が入っています。+1で2番目、+2で3番目とアクセス可能です。 WS2812Bのパレットドライバをこれを利用して作っています。

' 配列の物理アドレスをPUSHしておく R2=8 R2=R2<<8 R2+=R1 PUSH {R2-R7,LR} ' 中略 ' R1をスタックに退避し、スタック2番目、配列のアドレスを取得 PUSH {R1} R1=[SP+1]L R1+=R3 ' 中略 POP {R1} ' 中略 POP {R2-R7,PC}

スタックへの書き込みも移動も自由自在ですが、ちょっと間違うとすぐ暴走しちゃいます。


Cortex-M0 Armマシン語表 (asm15表記、抜粋)」(PDF)

参考に、WS2812Bの32x8マトリクスのパレット対応ドライバのArmマシン語 asm15表記プログラムの全文です。

' OUT1用 WS2812B 32x8 @OUTを変更でOUT1-4で変更可能、#800に3byteパレット最大64色パレット、R, G, B(キャラクターコード、"0"=48から順番) ' R0 - data count ' R1 - data address #900(VRAM)から1byte 1pixel = 1color palette #800 ' R2 - temp OUT value 0/#FF, & 15 ' R3 - data value, temp ' R4 - GPIO address ' R5 - bit count ' R6 - wait count buf ' R7 - wait count ' R12 - increment or decrement address @WS2812B R2=8 R2=R2<<8 R2+=R1 PUSH {LR,R2,R4,R5,R6,R7} CPSID R0=0 R4=9 R4=R4<<8 R1=R1+R4 R4=1 R12=R4 R4=[@OUT]L @LOOP_DATA R3=[R1] ' palette R3-=48 R2=63 R3&=R2 R3=R3<<1 PUSH {R1} R1=[SP+1]L R1=R1+R3 R3=[R1] ' G R3=R3>>4 GOSUB @SEND_BYTE R3=[R1]W ' R R3=R3>>8 GOSUB @SEND_BYTE R3=[R1] ' B R2=15 R3&=R2 GOSUB @SEND_BYTE POP {R1} R1+=R12 R0+=1 R2=31 R0&R2 IF !0 GOTO @LOOP_DATA R2=R0>>5 R2-8 IF 0 GOTO @END R1+=31 R3=1 R5=-R3 R12=R5 R2&R3 IF !0 GOTO @LOOP_DATA R1+=2 R12=R3 GOTO @LOOP_DATA @END CPSIE POP {PC,R1,R4,R5,R6,R7} @SEND_BYTE R5=#80 ' R3+=2 ' offset @LOOP_BIT R2=#FF [R4]L=R2 R7=3 '(3*4+1)=13 <- 16.8+-7.2 R6=9 '(9*4+1)=37 <- 43.2+-7.2 R3&R5 IF 0 GOTO @SKIP_LOOP_BIT R7=9 '(9*4+1)=37 <- 43.2+-7.2 R6=3 '(3*4+1)=13 <- 16.8+-7.2 @SKIP_LOOP_BIT R7-=1 'wait R7*4+1 clock IF !0 GOTO -1 R2=0 [R4]L=R2 R6-=1 'wait R6*4+1 clock IF !0 GOTO -1 R5=R5>>1 IF !0 GOTO @LOOP_BIT RET @OUT DATA L #50010004

@LOOP_DATAで、#900の画面を読み取り、48=ASC("0")を引き、63でアンドをとって64色パレットとしています。[0]から[63]までの配列に入った、RGB、各4bitを@SEND_BYTEを呼び出し送信しています。

@SEND_BYTE内のR7とR6を使ってシリアル信号のタイミングは仕様に収まるようにしていますが、短い方を1、長い方を5まで短縮が可能でした。(個体によるかもしれないので要確認)

対応強化したasm15用Armアセンブラ「asm15 assembler」を使ってIchigoJamで動かしましょう。
Armマシン語表を使ってハンドアセンブルしてもOKです!

- 連載、IchigoJamではじめる、Armマシン語入門
1. はじめてのマシン語
2. ハンドアセンブルで超速計算!
3. マシン語メモリアクセスで画面超速表示!
4. マシン語でLEDを光らせよう!
5. 楽しさ広がるマルチバイトメモリアクセスとスタック
6. マシン語使いこなしTIPS
7. カジュアルに使うインラインマシン語
8. アセンブラを使って楽しよう
9. マシン語で高速SPI
10. マシン語を制するもの時間を制す
11. 画面をイチゴで埋め尽くす12の方法
12. レジスタ不足に上位レジスタとスタック操作
13. コンパイラはじめのいっぽ、EVAL実現法とマシン語生成
14. サイズを取るかスピードを取るか、割り算のアルゴリズムとマシン語実装
15. マシン語化で1万倍速!? セットで学ぶアルゴリズムとコンピューター

Tweet
クリエイティブ・コモンズ・ライセンス
この作品は「Creative Commons — CC BY 4.0」の下に提供されています。
CC BY 福野泰介 - Taisuke Fukuno / @taisukef / アイコン画像 / プロフィール画像