mruby - mpsse 経由で SPI Flash/ I2C EEPROM programmer を作るその1

ICE40 専用ではなく汎用の 8 pin memory programmer を作ることにしました.

24 系 I2C EEPROM と 25 系 SPI flash のピン配置

1つの socket で対応ということで下記にしました.

socket         SPI flash    I2C EEPROM
1 AD4 8 +3.3V |1 CS# 8 Vcc |1 A0  8 Vcc
2 DO  7 AD5   |2 DO  7 HOLD|2 A1  7 WP
3 PU  6 AD0   |3 WP  6 CLK |3 A2  6 SCL
4 GND 5 AD1+PU|4 GND 5 DI  |4 GND 5 SDA

74157
AD2 = mode == 0 ? I2C.SDA : SPI.DO

AD
0 SPI.CLK, I2C.SCL
1 SPI.DI, I2C.SDA(input)
2 SPI.DO, I2C.SDA(output)
3 未使用
4 SPI.CS
5 mode (0:I2C, 1:SDA)
* PU はプルアップ

I2C では SDA を AD1 と AD2 につなぐようになる回路図が多いですが SPI と共用する場合は multiplexer を利用します. I2C では WP = 0 で書き込み可能ですが、SPI の WP は逆ですのでここも注意が必要です.

93 系の EEPROM は配線が全く違うので共用は不可能としてソケットのあまりに別途 8 pin の場所を用意して, CS# を個別出力にして切り替えようと思います. Vcc ラインは P-MOS FET で電源を切り替えるようにするつもりです.

mpsse の driver

前回の ICE40 の programmer のソースに書き足して対応しました. I2C は野良ソースコードと FTDI の技術文書を見て作りました. 野良ソースコードは SDA の出力が wired or ではなく high と low を出すようでしたが、こちらの実装では hiz と low の2通りとしました.

mpsse driver とグルー言語 mruby をつなぐ

  • 毎度ながら Squirrel でもいいのですが新規開拓として mruby にしました.
  • mruby の取得は msys2 なら pacman -S mruby でできます.
  • mruby のリンクは -lmruby -lws2_32 でできました.

一番の問題は C の関数と mruby の API で、公式がまともなドキュメントを出していないのでインターネット上の野良情報なり自分でヘッダやソースを読むことが求められます. 一応いままでこの手のグルー言語なりなんらかのライブラリの API を見ましたがここまで雑なのはとても不満です.

公式のドキュメント: http://mruby.org/docs/api/header_list.html

2013 年 3 月に書かれた下記の野良情報が便利というのは 8 年間放置している証拠だと思います.
https://tyfkda.github.io/blog/2013/03/11/mruby-api.html

話がそれました. 私の実装は ruby から呼び出された method の引数を配列とみなし、 uint8_t * の配列を作って mpsse の driver と渡すものです. mruby の配列長を得る方法は信じられないことにどこにも書いてませんでした.

自分が書いたソースは下記です.

struct array{
	mrb_value mrb_array;
	mrb_int size;
	uint8_t *data;
};

static int array_into_uint8_t(mrb_state *m, struct array *t)
{
	t->size = ARY_LEN(RARRAY(t->mrb_array));
	if(t->size == 0){
		t->data = NULL;
		return 1;
	}
	t->data = malloc(t->size);
	for(mrb_int i = 0; i < t->size; i++){
		mrb_int v = mrb_fixnum(mrb_ary_ref(m, t->mrb_array, i));
		if(v < 0 || v >= 0x100){
			free(t->data);
			t->data = NULL;
			return 0;
		}
		t->data[i] = v;
	}
	return 1;
}

<mruby/array.h> にあったそれっぽい ARY_LEN(RARRAY()) を組み合わせたらたまたま動きました. この2つは C のマクロでキャストしていて型保証もないのでうさんくさいのですが動きました. だから、もしこれをみた mruby の関係者はまともな解説を目に付く場所に書いてほしいです.

あとは慣れなのでしょうが C->mruby, mruby->C での API の名称も解説が不十分かつ関数名が推測しづらいのはいい印象ではありません.

mruby の組み込み後

今の所、 mruby->C の method は引数1個、型は整数か上記のuint8_tにいれるような配列だけです. これだけわかればあとは登録した C の関数を rubyスクリプトで組むことになります.

ここに達すると C で考えるべき型やタグやマクロや固定配列長やstaticとか面倒なことを考えなくてよくなり急にスピードアップを感じられます. (たぶん string.h にある memcpy, memcmp, memset がいらなくなるのが大きい) そして先程まで不満だった mruby の API ドキュメントの雑さも忘れられてしまいます. *1

こうなると Squirrel の言語との比較が多くなりますが、書き慣れている ruby script のほうが簡単にやれることが多いのでさらに楽になれます. 例としては下記です.

  • g++, clang++ でリンクしなくて良い
  • &=, <<=, |= 演算子が使える
  • Array.push だけなく Array << が使える. shift が使えるのもうれしい.
  • hash がある
  • File の扱いが楽で blob とかがない

今のところでの不満はエラーメッセージが標準ででてこないので自前でAPIからとってくることです.

もうちょっと mruby での調べ事なり雑なエラーチェックをまともにして満足できたならグルー言語として mruby を使うのは結構いい気がしています.

*1:だからといってそれでやってみようとしたのに諦めた無数の無名の挑戦者がいるのは忘れないでほしいです