(続)IchigoJamで32x16ドットマトリクスLEDを制御する

はじめに

前回の記事では結局BASICでは、

IchigoJamのBASICからでは速度的につらかった。本気でやるときは機械語でよろしくすべし。

ということになったので、今回、機械語でよろしくすべく色々やってみたことをここに記す。

rohi.hatenablog.com

IchigoJam-FANに投稿した動画は、以下のプログラムを実行する前にvideo 0も実行している。

www.facebook.com

どこをマシン語で?

前回記事のBASICソースで言うところの内側16回ループのところ。ここは16クロック分のデータを出力するところ。ここをIchigiJam BASICで言うところの仮想アドレス#700からのマシン語ルーチンで実現するようにした。

外側の16回ループは16ライン分のループで、こちらはBASIC側で回している。

マシン語ルーチンへは前回記事で言うところの変数c, d, eに相当する3つの値を渡したいところ。ちょうど1.1beta6で追加された第2パラメータを使って、配列の特定位置([33], [34], [35])をマシン語ルーチン側から参照するようにした。

プログラム

BASIC側のプログラム。省メモリ対策として空白なし、マルチステートメント化、16進は短く10進表現にといったことをしているので見にくくなっている。

10 fori=0to5:outi,0:next
20 poke#700,132,35,27,1,202,90,128,35
30 poke#708,91,2,147,64,16,32,26,74
40 poke#710,112,181,140,90,25,74,137,90
50 poke#718,27,12,26,4,18,20,24,77
60 poke#720,210,23,24,78,42,64,50,128
70 poke#728,34,4,18,20,1,212,0,34
80 poke#730,0,224,19,74,20,78,50,128
90 poke#738,10,4,18,20,1,212,0,34
100 poke#740,0,224,15,74,17,78,50,128
110 poke#748,0,38,17,74,91,4,100,4
120 poke#750,73,4,1,56,22,128,27,12
130 poke#758,36,12,9,12,8,78,21,128
140 poke#760,0,40,218,209,11,75,12,74
150 poke#768,30,128,22,128,16,128,24,128
160 poke#770,112,188,2,188,8,71,192,70
170 poke#778,66,8,0,0,68,8,0,0
180 poke#780,255,15,0,0,4,0,1,80
190 poke#788,8,0,1,80,16,0,1,80
200 poke#790,32,0,1,80,0,4,1,80
210 poke#798,0,8,1,80
220 let[0],0,#6300,#9480,#9480,#7480,#1300,#6000,0
230 let[8],0,0,#8000,#4000,#4000,#4000,0,0
240 let[16],#e040,#4642,#4970,#484a,#484a,#e64a,0,0
250 let[24],#1c00,#0800,#08c6,#0925,#0925,#30d5,0,0
260 fori=0to15:[32]=i:[33]=[i]:[34]=[i+16]:u=usr(#700,0):next:goto260

10行目、このプログラムで使用するポート0〜5にとりあえず0出力させている。端子が他の用途に使われていたかもしれないので、念のための初期化のつもり有効性は未確認。 -->「先頭のOUTn,0、有効です」との見解をIchigoJam作者福野さんからいただきました。

20〜210行目、16クロック分を出力するためのマシン語ルーチン。アドレス#700からの配置。

220〜230行目、前回記事の110〜120行目に相当。

240〜250行目、同じく前回記事の130〜140行目に相当。

260行目、16ライン分のループ。マシン語ルーチンへは3つの16ビット値を渡す必要がありBASICの配列[32]、[33]、[34]を使用している。16行表示したら先頭行に戻るべくgotoしている。

マシン語ルーチン側のプログラム。これをbin2pokeしたものが、上記BASICソースの20〜210行めのpokeとなる。

#include <stdint.h>

#define GPIO1_BASE 0x50010000
#define GPIO1(pin) *(volatile uint16_t *)(GPIO1_BASE | (1 << ((pin) + 2)))
#define SET 0xfff
#define RESET 0

int16_t usr_calc(int16_t val, void *mem)
{
  uint16_t *array = (uint16_t *)(mem + 0x800 + sizeof(int16_t) * 32);
  uint16_t row = 1 << *array;
  uint16_t col1 = *++array;
  uint16_t col2 = *++array;
  int i;
  
  for (i = 0; i < 16; i++) {
    GPIO1(0) = (row & 0x8000) ? SET : RESET;
    GPIO1(1) = (col1 & 0x8000) ? SET : RESET;
    GPIO1(2) = (col2 & 0x8000) ? SET : RESET;
    row <<= 1;
    col1 <<= 1;
    col2 <<= 1;
    GPIO1(3) = RESET;
    GPIO1(3) = SET;
  }
  GPIO1(8) = SET;
  GPIO1(9) = SET;
  GPIO1(9) = RESET;
  GPIO1(8) = RESET;

  return 0;  /* success */
}

GPIO1のポート0〜3、8〜9を使う。BASICの配列[32]、[33]、[34]へは、関数の第2パラメータをベースに、配列格納領域の先頭0x800を加算し、さらに[0]〜[31]を飛ばすべくちょっと足し算している。結果、変数arrayが[32]を指すところから始まる。

マクロGPIO1()はポート1_nのnをパラメータとして、アドレス部でマスクを実現する方法を使ってアクセスしている。書き込み値を0xfffとしているがマスクとANDするのでどれか1ビットしか操作されない。

関連

今回の「機械語でよろしくすべし」を実現するために、CソースコードからBASICのpoke文に変換するスクリプトとドライブするMakefileを以下で公開している。

github.com