16.78MHz -3ページ目

Stellaris LM3S811で日本語

k6x8でテキストを画像にしてStellarisの開発環境を整えてからずいぶん経ってしまったが、Stellarisで日本語を表示しておく。まずテキストの画像化で使ったk6x8描画関数をStellaris用に少し変更したものStellarisWareディレクトリの直下に展開する。(この場所に展開する理由はサンプルプログラムのMakefileから最小限の変更で済むという理由しかないので、あなたがMakefileの書き方をよく知っているのならどこに展開しても構わない)
続いてサンプルプログラムのhelloのなかから必要なものをコピーする
$ cd /path/to/StellarisWare/
$ cd boards/ek-lm3s811/
$ mkidr japanese
$ cp hello/{Makefile,hello.c,hello.ld,startup_gcc.c} japanese/
$ cd japanese/

そしてこのパッチを当ててビルドする。
$ patch -p1 <./japanese.diff
$ make

問題がなければgcc/hello.binが出来上がっているので、これをOpenOCDでボードに書き込む。ボードをリセットするとOLEDディスプレイにこんなのが出てくる。
16.78MHz-アレゲ
中でやってることについて軽く説明しておくと、まず1行分のテキストを画像にして、それをStellarisWareディレクトリ以下に入っているOLEDドライバの画像表示関数Display96x16x1ImageDrawを使って表示している。Stellaris LM3S811のOLEDディスプレイは96x8のディスプレイが2段重なって縦16ドットになっており1バイトが縦1列に対応する。OLEDドライバはこのフォーマットで画像データを渡すことを要求しているが、元々このk6x8でテキストを画像にする関数はStellarisで使うために作ったので、グリフデータは最初からこのフォーマットになっている。というわけで、drawLine関数ひたすら必要な部分を切り貼りする作業を行っている。ソースを書いてから思ったのだがこの関数名はベクタ画像でも描き始めたら絶対に同じ名前の関数を別の場所で作りそうだから別の名前に変えた方がいいかもしれない。改行やオフスクリーンバッファに描画してスクロールみたいなややこしいことは今日のところはしていないが、そんなに面倒ではないので近いうちに実装する予定。

Linux上でStellaris LM3S811開発環境を整える その2

Stellarisが届いたのでさっそくLinuxマシンに繋いでみた。
バイナリのビルドと実行は既にエミュレータで試しているのでJTAGからの書き込みを行うわけだが、その前に前回作ったOpenOCDの設定ファイルopenocd.cfgを修正
source [find interface/luminary-lm3s811.cfg]
source [find board/ek-lm3s811.cfg]
source [find target/lm3s811.cfg]

どうもlm3s811評価ボード用の設定ファイルはlm3s811自体の設定を含んでいるらしく、target/lm3s811.cfgまで読み込むとOpenOCDは定義が二重に行われているといった旨のエラーを吐いて終了してしまうので、この行を削除する
次にボードを付属のUSBケーブルでLinuxマシンに接続する。USBバスパワーで動くようになっているので接続するとすぐにボードが起動する。ボードには最初から横スクロールシューティングゲームが書き込まれており、OLEDディスプレイとサムホイールで遊ぶことが出来る。
$ dmesg
...
ftdi_sio 7-1:1.1: FTDI USB Serial Device converter detected
ftdi_sio: Detected FT2232C
usb 7-1: FTDI USB Serial Device converter now attached to ttyUSB0
...

ボードが接続されたLinuxマシン側からはデバイスはFTDI USBシリアルコンバータとして認識される。Arduinoと同じチップなのでArduino開発環境が動いている環境なら問題なく認識するはず。認識されなかった場合カーネルドライバが無い可能性があるのでここの最後あたりを参考にカーネルを再構築する。
ボードが認識されたらOpenOCDを起動する
$ cd openocd-0.4.0
$ openocd -f openocd.cfg
Open On-Chip Debugger 0.4.0 (2010-04-04-15:28)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.berlios.de/doc/doxygen/bugs.html
Warn : Interface already configured, ignoring
500 kHz
jtag_nsrst_delay: 100
srst_only separate srst_gates_jtag srst_open_drain
Info : clock speed 500 kHz
Info : JTAG tap: lm3s811.cpu tap/device found: 0x2ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x2)
Warn : JTAG tap: lm3s811.cpu UNEXPECTED: 0x2ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x2)
Error: JTAG tap: lm3s811.cpu expected 1 of 1: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
Error: Trying to use configured scan chain anyway...
Warn : Bypassing JTAG setup events due to errors
Info : lm3s811.cpu: hardware has 6 breakpoints, 4 watchpoints

いきなりまずそうなログが流れるが取り合えず見なかったことにする。OpenOCDは以後4444番ポートからのtelnet接続を待ち受けているので、まずはボードにリセットをかけてみる。
$ telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> reset
JTAG tap: lm3s811.cpu tap/device found: 0x2ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x2)
JTAG tap: lm3s811.cpu UNEXPECTED: 0x2ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x2)
JTAG tap: lm3s811.cpu expected 1 of 1: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
Trying to use configured scan chain anyway...
Bypassing JTAG setup events due to errors
>

ここでもやっぱりまずそうなログが流れるがボードはきちんとリセットされて、TEXAS INSTRUMENTSのロゴが流れてくる。続いて内蔵フラッシュメモリにアクセスしてみる。
> mdw 0 16
0x00000000: 2000017c 0000168d 00001681 00001685 00001689 00001689 00001689 00000000
0x00000020: 00000000 00000000 00000000 00001689 00001689 00000000 00001689 00001689
>

mdwはメモリの指定したアドレスから指定した長さの範囲の値をワード(32bit)単位で表示する。LM3S811はアドレスの先頭に内蔵フラッシュメモリがマップされているため、今表示されたのは内蔵フラッシュメモリの先頭64バイトということになる。正常にフラッシュメモリにアクセス出来ることが確認できたら、さっそく前回ビルドしたバイナリを書き込んでみよう。
> halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x61000000 pc: 0x000006ba msp: 0x2000011c
> flash write_image erase /path/to/main.bin
auto erase enabled
wrote 3072 bytes from file /path/to/main.bin in 0.385969s (7.773 kb/s)
> reset
JTAG tap: lm3s811.cpu tap/device found: 0x2ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x2)
JTAG tap: lm3s811.cpu UNEXPECTED: 0x2ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x2)
JTAG tap: lm3s811.cpu expected 1 of 1: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
Trying to use configured scan chain anyway...
Bypassing JTAG setup events due to errors

結果
16.78MHz-挨拶失敗
これはまたずいぶんと理解に苦しむイカレっぷりで。とはいえ、先ほどまで横シューティングが正常に動いていたのでハードウェアの問題とは思えない。そこで、付属のCDに入っている横シューティングで使われていたOLEDドライバと前回ここから拾ってきたサンプルプログラムに含まれていたOLEDドライバを見比べてみた。付属CDに含まれているサンプルプログラムはご丁寧にWindowsインストーラ形式で固められているので、WINEを使ってインストールする。
$ wine /media/cdrom/Tools/StellarisWare/SW-EK-LM3S811-5125.exe

インストールされたディレクトリの中のサンプルプログラムと、前回コンパイルしたサンプルプログラムのOLEDドライバを比較したところ、前者にはSSD0303用ドライバとSSD1300用ドライバが含まれていたのに対して、後者にはSSD0303用ドライバのみが含まれていた。どうやら、前回のサンプルプログラムやQEMUのLM3S811サポートが作られた後でOLEDのコントローラチップが変更されたらしい。
というわけでCDに含まれていたHello Worldを前回用意したgccでコンパイルして転送。
$ cd StellarisWare
$ make
$ cd boards/ek-lm3s811/hello/gcc
$ telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x61000000 pc: 0x000003e0 msp: 0x200000f8
> flash write_image erase /path/to/StellarisWare/boards/ek-lm3s811/hello/gcc/hello.bin
auto erase enabled
wrote 4096 bytes from file /path/to/StellarisWare/boards/ek-lm3s811/hello/gcc/hello.bin in 0.469964s (8.511 kb/s)
> reset
JTAG tap: lm3s811.cpu tap/device found: 0x2ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x2)
JTAG tap: lm3s811.cpu UNEXPECTED: 0x2ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x2)
JTAG tap: lm3s811.cpu expected 1 of 1: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3)
Trying to use configured scan chain anyway...
Bypassing JTAG setup events due to errors
>

16.78MHz-挨拶成功
Hello World!
ちなみに今回買ったのはCodeSourcery30日間お試し版同梱の評価ボードだが、CodeSourceryが無くてもビルド出来る環境は既に前回整えたので日数制限を気にする必要は無い

k6x8でテキストを画像にする

Stellarisの付属ライブラリにはASCIIコードの文字列をOLEDディスプレイに表示する関数が用意されているが、せっかくなので日本語を表示したい。
Stellaris LM3S811のOLEDディスプレイの解像度96x16しかないので、できるだけ小さい日本語ビットマップフォントを組み込んで表示する必要がある。小さい日本語フォントは8x8ピクセルの恵梨沙フォントが有名だが、世の中にはさらに小さいk6x8フォントなんてものが存在する。k6x8フォントは上下左右の文字とくっつかないための1ピクセルの余白を含めて6x8ピクセルの極小日本語ビットマップフォントで、ライセンスによると自由に改変して再配布出来る。そこでこのk6x8フォントを組み込んだ日本語文字列表示関数を作ろうと思うわけだが、いきなりマイコン向けに開発するとデバッグが面倒なので、まずはPC上でテキストを画像化するコマンドに仕上げてみた。
ソースのダウンロードはこちら。PNG画像で出力するためにDevILを使っているので事前にインストールしていく必要がある。ビルドは一般的なautotoolsのビルド手順だが念のため。
# cd euc2png
# ./configure
# make
# make install

euc2pngコマンドは-iと-oの2つのオプションがあり、それぞれ入力ファイル名と出力ファイル名を指定する。
$ euc2png -i テキストファイル -o 出力ファイル名

実行するとファイル名が出力ファイル名_ページ番号.pngのpng画像がずらずら生成される。日本語文字コードは最近のLinuxディストリビューションではあまり使われていないeucjpなので、utf8なイマドキのディストリビューション上で作ったテキストファイルは文字コードを変換する必要がある。
$ cat UTF8なテキストファイル | iconv -f utf8 -t eucjp >eucjpなテキストファイル

Arduinoの記事もこの通り。
16.78MHz-euc2png
半角文字のグリフは全角文字のものを流用しているため全く同じ文字が出てくる。加えて字形が同じ文字はグリフを共有している。この状態でグリフのサイズが50KB前後で、内蔵フラッシュメモリ64KBのLM3S811に組み込むには大きすぎたので罫線と第二水準漢字を捨てて30KB前後にした。

前計算の分解

ある関数f(x)の計算が高価だがコード中で頻繁に必要とされている時、十分細かいと考えられる間隔の離散点でのf(x)の値を前計算してメモリに置いておき、与えられたxに最も近い点での結果を返すなり、与えられたxに近い2点を使った線形補間を結果として返すなりすることでパフォーマンスを改善するのは物理シミュレーションやコンピュータグラフィクス、シンセサイザーといった複雑な数値計算を伴うプログラムでは常套手段だ。

が、

引数の数が増えてくると全ての引数の値の範囲をカバーするだけのメモリを確保するのが非現実的になってくる。例えばx,y,zがそれぞれ0から9999までの整数の場合、関数f(x)の引数は1万通りだが、関数f(x,y)の引数の組み合わせは1億通り、関数f(x,y,z)の引数の組み合わせは1兆通りになり、いかに今日のPCが大量のメモリを積んでいるといっても収まらなくなる。

このような場合は関数の中身を見て計算に時間がかかる部分だけピンポイントに前計算できないか試す価値がある。例えば今ある関数f(x,y,z)の中身が以下のようなものだったとする。ここで、値は4バイトでx,y,zはそれぞれ0から9999までの範囲の整数とする。
f(x,y,z) = y0.5 + x0.3z + 5y0.25z + sin( 0.04xy ) + z

根と三角関数の計算を回避するためにこの関数を前計算したいが、全ての引数の組み合わせは1兆通りになり、4TBものメモリが必要になる。ここでzに注目すると、この式にはzの冪乗の項は存在せず全ての項はzを含まない項zに比例する項のどちらかに分類することができる。
f(x,y,z) = ( x0.3 + 5y0.25 + 1 )z + ( y0.5 + sin( 0.04xy ) )

g(x,y) = x0.3 + 5y0.25 + 1
h(x,y) = y0.5 + sin( 0.04xy )
f(x,y,z) = g(x,y)z + h(x,y)

この関数gと関数hはxとyにしか依存していないため、この2つの関数を前計算すれば引数の組み合わせは1億通りになり、両方合わせて800MBのメモリで済ますことが出来る。
続いて、関数gはxとyを両方とも含む項が存在しないので、以下のようにさらに分割する。
i(x) = x0.3
j(y) = 5y0.25
g(x,y) = i(x) + j(y) + 1
k(y) = y0.5
h(x,y) = k(y) + sin( 0.04xy )
f(x,y,z) = g(x,y)z + h(x,y)

関数gはタダの足し算になったので、関数i,j,hを前計算する。この時必要なメモリは400.08MBになる。
最後に関数hの中の根を含む項を括り出す。残ったsinは周期関数なので、xとyの値域全体をカバーせずとも1周期分前計算すれば十分である。(いわゆるサインテーブル)。ここでは512要素のサインテーブルを使うことにする。これで関数hは加算と乗算だけになったので関数i,j,kとサインテーブルを前計算する。この時必要なメモリは122.048KB
PCのようなプロセッサの速いアーキテクチャでは根の計算のような単純な処理を前計算してもメモリのアクセスパターンの分散によるパフォーマンスの低下が相対的に大きく出て効果は微々たるものだが、ややこしい物理シミュレーション等で、高価な関数を前計算したいが値域が広すぎて全てをメモリに保持出来ないとき、式を調べて一部だけ前計算を行うことで前計算の結果を保持するために必要となるメモリは劇的に減らせる可能性がある。

組み込みシンセサイザ

OSの走っていない小規模なマイコンに移植することを前提とした簡素なシンセサイザを作ってみた。
名付けてPICOSYNTH
7.8bit固定小数点演算で実装された波形テーブルシンセサイザで、すべてC言語で書かれている。ALSA出力部を除いて他のライブラリには依存していないので、この部分をマイコンに接続されたDACに値を書き出すコードに書き換えるだけで簡単に移植出来る。AVRのような8bitマイコンは一般に8bit単位でしかプッシュ/ポップが出来ずスタック操作が高価なので、関数呼び出し時のスタック操作を極力回避するために全ての関数はインライン展開されるようになっている。ダウンロードはここから。
ビルドにはソースパッケージとは別に楽譜データが必要になる。東方星蓮船の夜空のユーフォーロマンスを気合いの12音+ノイズドラムで演奏する楽譜サンプルはここからダウンロード。
$ cd picosynth
$ CFLAGS="-O3 -march=native -mfpmath=sse -fno-math-errno -ffast-math -fomit-frame-pointer" ./configure --with-score=`pwd`/sample_score.h
$ make
$ ./src/picosynth
(ガガガガガ)

ビルドして実行するとこんなのが流れてくる。ちなみに--with-target=pcmとか付けてconfigureするとWAVにダンプすることが出来るようになっている。
8bit 4000Hzでダイナミックレンジ圧縮が場当たり的なので、音質はお世辞にも良くないが、
$ time ./src/picosynth

real 0m36.780s
user 0m0.201s
sys 0m4.509s

とてつもなく軽量なのが特徴