(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

SH2で動作したので,再びH8をいじってみよう.というのはH8/3069Fボードはおまけで2MBのDRAMがついているのと,LANコントローラが載っていてLANコネクタも実装済みなので,いじりがいがあるからだ.(この点,SH2のボードだと内蔵RAMも少ないし,外部デバイスもたいしたものは載ってない(LEDとスイッチくらい)し,いったん動いてしまうとあんましいじりがいが無い)

で,LANコントローラを動かすのも面白そうと思ったのだけど,とりあえずDRAMを動かしてみよう.というのは,ethernetのフレーム送受信とかしたときに,やはりフレーム保存のためには内蔵RAMだと容量がちょっと足りないと思うので,その前にDRAMを動くようにしておきたいからだ.

このDRAMなのだけど,H8/3069Fボードのマニュアルを見ると「もしも買ったボードに付いてたらラッキーくらいのおまけ」というようなことが書いてある.僕はこのボードはすでに2枚買ったのだけど(1枚は予備),とりあえず両方ともDRAMは載っているみたい.秋月電子のホームページを見る限りでは,DRAM付属とはっきり書いてあって,標準でついているのかおまけなのかちょっとよくわからんが,まあだいたいついているものと考えてもいいようだ.

ちなみにDRAMは,基板の裏側に実装されています.確かめたい人は基板裏の電源アダプタの下付近ちょっと中央よりに1cm×2cmくらいのICチップがあるか見てみてください(たぶんあるとおもうけど).

回路図を見ると,DRAMはCS2に接続されている.ということで0x400000~0x5fffff の2MBの空間が使えるようだ.ちなみに注意だけど,このDRAM,16Mと書いてあるのだけどこれって16Mbyteでなくて16Mbitなので,byteだと2MBということになる.CS2の空間は2MBなので実はちょうどなのだけど,最初,これを誤解しててちょっと混乱した(こんなの勘違いするのって私だけか?).

ちなみにボード付属のマニュアルを読むと「CS1に接続されている」って書いてあるのだけど,回路図見るとCS2に繋がっている.回路図はたぶんパターン引きしたときの電子データをそのまま持ってきているのだろうし,回路図のほうがあっているのだろうと思ってはいたのだが,やってみたところ,やっぱしそうだったようだ.なのでマニュアル読んでいるひとは気をつけてください.

で,なーんだCS2に繋がっているのならなにもしなくても0x400000以降を読み書きすればもう使えるんじゃーないの? とか気楽に思っていたのだけど(動くようになったあとで冷静に考えてみると,そんなわけないよね.でもハードに無知なソフトウエアプログラマの感覚ってこんなもんなのよ),全然動かなかった.以下,動くようになるまでをちょっと説明.

まずCPUのモード設定なのだけど,今までディップスイッチの設定はボード付属のマニュアルに載っているままに使っているのだけど,これはCPUの「モード5」というやつで使っていることになる.

このモード5ってやつがくせもので,3052Fとかだとモード5って1MBアクセスのモードなのよ.ところが3069Fだと16MBアクセスモードなのだな.最初これを知らなくて,H8の書籍(3048や3052を使っている場合が多い)を読むとモード5だと1MBまでって書いてあるのでなんでこれで動いてるんだろうとか別モードにする必要があるのかもとか思っていたのだけど,付属CD-ROMの3069Fのマニュアル見たらモード5は16MBになっていた.同じシリーズのCPUなのでモードの意味は同じだと思っていたのだけど,うーんこういうことってあるんだなあ.要注意だ.

で,3069FのマニュアルのCPUのモードの説明のところを読むとわかるのだけど,内蔵ROM有効でアドレスは16MB空間で,さらに外部メモリを使うにはモード5でないとならない.ということでCPUモードは従来通り,モード5のままでいい.

あとチップセレクトであるCS2がI/Oポートとピンが共通になっているので,たぶんピン指定をしないといけないだろーなと思って調べたら,P8DDRというので指定するようだ.

で,このへんを指定して0x400000以降に適当な値を書き込んで読んで比較するようなメモリチェックを実装してためしてみたんだけど,うまく読み書きできてない.

いろいろマニュアル調べているうちに,設定するのはP8DDRだけでは不十分で,DRCRAとかDRCRBとかRTMCSRといったレジスタをいろいろ設定しないとならないようだ.DRAMのウエイトとかリフレッシュ関連とか設定しないとならんらしい(まあそりゃそうだよね).このへんはマニュアルの「I/Oポート」の章と「バスコントローラ」の章(とくに,「DRAMインタフェース」の節)とあと回路図をよく見て,設定値を考えなければならない.

でもこのへんってちょっとよくわからなくて,とりあえず上記の章で目につくレジスタをひととおり適当に設定してみたのだけどダメ.ほんとならDRAMのデータシート見てちゃんと調べるべきで,付属CD-ROMにデータシートがあったのでちょろっと見てみたのだけど,よくわからんしこれを真剣に読みだしたらかなり先の長い話になってしまうのでとりあえずこれは保留.

で,ネットで検索してみた.もっともこのへんはボードや搭載するDRAMや回路接続の方法によっても設定値が変わってくるだろうから,まさに同じボードでDRAM使っているという例でないとダメなのだけど,運良く以下のサイトがサクッと見つかった.

「soukz理研 AKI-H8 > 3069F LANボードのDRAM増設あまり」

こちらはDRAMをさらに増設して使っているようなのだけど,とりあえず同じ秋月のボードでDRAMを使えるようにしていて,上記レジスタの設定内容とその理由がひととおり書いてある.とりあえず上記サイトの設定例を参考に,CS2(エリア2)のみを使うようにしてレジスタの設定をしてみた.まあ設定値とその理由については上記サイトを参考にしてほしい.いやー,ありがたいありがたい.

で,動かしてみたらちゃんと読み書きできた.ほんとは設定値をもっとちゃんと調べて細かくチューニングすべきかもしれないけど,まあ動いたしべつに速度を追求する必要もないので,気にしない.

ということで以下,修正したソース.

OSにDRAM関連の処理として dram.c を追加.さらにOSのinit()で,dram_init()を呼ぶことでDRAM関連の初期設定を行っている.これ以降,0x400000~0x5fffffにDRAMが見えるようになる.

さらに command.c にメモリチェックコマンドを追加した.OS起動して ramchk を実行することで,メモリチェックを行う.メモリチェックはそのアドレスの値と0x00000000 と 0xffffffff を,1バイト単位,2バイト単位,4バイト単位で書き込んで読み出して,値の比較を行うというもの.16ビットCPUなので4バイト単位は実際には意味無いような気がするが,とりあえず最初に全領域全パターンチェックをしておきたいので,なんとなくいちおう4バイトでのチェックも入れといた.まあ修正点に関しては,前回との差分をみてちょうだい.今回はブートローダーの修正は無し.(将来的にはDRAMの初期化はブートローダーで行って,OSはDRAM上にロードするようにしてもいいかもしれない)

実際には上記チェック処理を2MBの全アドレスに渡って行うので,それなりに時間がかかる.僕のボードでやってみたところ,1時間くらいだったかなあ.

以下,チェックの実行結果.

kzboot> run
starting from entry point: ffc120
kozos boot succeed!
command> echo

OK
command> echo aaa
aaa
OK
command> ramchk
DRAM checking...
005ffffc
all check OK.
OK
command>

ramchk実行でチェック開始し,正常終了している.問題無いようだ.

今回わかったことだが,たとえCS2に繋がっているからといってなにも考えずにそのアドレスにアクセスすれば読み書きできるというものではない.これはCS1に接続されているLANコントローラでも同じことで,いきなりそのコントローラのレジスタを読み書きできるわけではなく,まずは最低限,CS1のピン設定は(たぶん他のピンと共用になっているので)絶対に必要だと思われる.

ほかにもアドレスピンとかもI/Oとピンが共通化されているので,このへんもアドレス信号を出すようにピン設定しなければならない(これは後になって考えると当り前なのだけど,上記サイトでそのような設定を行っているのを見たときは非常に意外でびっくりした.アドレス線はなにもしないでほっといてもアドレス信号が出ているもんだと思っていた).で,それらの設定をして,ようやくLANコントローラのレジスタが見えるようになる.使うためにはさらにその後,LANコントローラのレジスタ設定を行わなければならないわけだ.

今回は運良く上記のサイトが見つかって,参考にして設定したらあっさり動いたけど,これってなにも参考にできずにマニュアルとデータシートだけ見て自分一人でやってたら,ハマリにハマッただろうな~としみじみ思う.だってアドレス線までピン設定しなきゃならんとは思わなかったもん.いやー,ありがたや,ありがたや.

それにしても,DRAM動くようにするだけでもこんなにいろんなレジスタ設定しなきゃならんのね.H8ってすげー簡単と思っていたけど,それは今まで内蔵デバイスだけを使っていたからで,外部デバイス使うとなるとそれなりに設定は必要だっつーことだ.

ソフトウエア・プログラマの視点だとDRAMなんて使えて当り前で最初っから必ず使えるものっつーイメージがあるけど,そんなことはなくて,使うためにはメモリコントローラの設定がいろいろ必要だということだ(それも,接続されているハードや回路接続のしかたに合わせた設定が必要なので,本に載っているような値をそのまま設定すればいいというわけではなく,ボードに合わせて考えなければならない).何もしなくても動くのでは?なーんて考えていた自分が恥ずかしい.

今回の僕の場合はたまたま同じボードでのサンプルがあったのでよかったけど,新規ハードでまったくサンプルの無い状態だと,回路図とかデバイスのデータシートを見ながら自分で設定値を選ぶわけだから,そりゃードライバ作る人が苦労するわけだよねえ.
AD
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

H8への移植がひと段落したけれど,実はH8のボードを買ったときについでにSH2のボードも買っていて,なんとなくSH2のマニュアルを見ていたのだけど,なんか割り込み周りとかSCIのレジスタとか,H8とほとんど同じなので,実はKOZOS/H8をベースにしてSH2にもサクッと移植できてしまうのでは...と思ってSH2への移植をやってみた.

ターゲットボードは秋月電子の「AKI-7125マイコンボード」というもの.やはり完成品で,シリアルコネクタも実装済みなので半田づけは一切不要で買ってきたその日からすぐ遊べる.値段も4800円なので個人レベルで買える金額だ.シリアル経由でフラッシュROMを書き換えできるのもH8と同じ.ついでに,電源にH8/3069Fで使った電源アダプタがそのまま使えてさらに嬉しい.(SH/7144のボードは電源電圧が3.3Vなので,H8/3069Fボードと電源アダプタを共用できない)

画像はこちら

画像はこちら

このボードなのだけど,SH2の7125というCPUが載っている.SH2の中でも「SH/Tiny」とか呼ばれているやつで,SH2の中では比較的新しい,廉価版のSH2のようだ.

で,SH2への移植なのだけど,まず開発環境から.クロスコンパイラはH8のときとほとんど同じで,以下で作成できた.環境はいつもどおりFreeBSDね.

(binutils)

% ./configure --target=sh-elf --prefix=/usr/local --disable-nls
% gmake
# gmake install

(gcc)

% setenv SHELL /usr/local/bin/bash
% ./configure --target=sh-elf --prefix=/usr/local --disable-nls --disable-thread
s --disable-shared --enable-languages=c
% gmake
# setenv SHELL /usr/local/bin/bash
# gmake install

次にフラッシュROMの書き込みソフトなのだけど,ちょっといろいろ探してみたのだけどどうもFreeBSDから使えるもので,SH/7125に対応してるものが無い.実はSH/7144のボードも買ってあって,こっちだとfw(だったかな?)というシェアウエアがFreeBSD上で使えるみたいなのだけど,まあ最初だしよくわからんところではまってもしょうがないので,とりあえず添付CD-ROMに付属のWindows用の書き込みソフトを使うことにした.割と有名な「FDT」ってやつね.

移植はH8と同様に,ブートローダーとKOZOS本体の両方を移植する.理由はH8のときと同じ.ブートローダーはH8用に作ったものをそのまま移植.

ただしOSに関しては,SH/7125はRAMが8KBしかなくて,これはかなりきつい(H8/3069Fは16KB).OSが入りきるか心配だったのだけど,実際にH8用のソースコードを持ってきてSH2用にビルドして見ると,RAMのサイズが全然足りなくて入りきらない.ということでKOZOS/H8をベースにするのはあきらめて,Interface誌にPowerPC用に移植したときのかなりシェイプアップしたソースコードをベースにして移植を行った.

で,移植したソースコードは以下.

移植時に注意したこととかはまったことは以下.
  • RAMのサイズが足りないので,ELF形式でのダウンロードはやめてモトローラSフォーマットでの直接展開を利用する.
  • SHはアセンブラがちょっと特殊というか,癖があって慣れるまでちょっとひと苦労.意外なことに,アセンブラを書くための資料とか書籍が少なかった.(割り込み周りとか説明している書籍はあるが,メーカー純正コンパイラ(HEW)の#pragma 機能とか使ってアセンブラ書かずに実装している場合が多く,まったく参考にならないのが多い.がっくし)
  • SH2はジャンプ命令が遅延スロットを持つので,ジャンプ命令の後にNOPを入れておくとか考慮する必要有り.これは忘れがちなので要注意.
  • 50MHzと書いてあるのだけど起動時は実は25MHzで,50MHzで動作させるには分周比を設定しなおさないとならないみたい.SCIに供給するクロックはCPUとは別の分周比設定だが,これも25MHzになっているので,SCIのBCCの値を25MHzで計算する必要有り.
  • SCIが利用するCPUのピンが標準I/O入出力ポートとして割り当てられているので,SCIで使うという設定をする必要有り.これはブートローダーの serial_init() で行っている.(ブートローダーで設定しているので,OS側では現状でとくに設定はしていない)
  • SCIがデフォルトで省電力状態になっているので,起動後にシリアルの初期設定をしただけではシリアルが使えない(クロックが止まっている).STBCR3というレジスタを操作してクロック供給するようにする必要有り.(ブートローダーの serial_init() を参照.これはわりとハマッた.このへんのために,シリアル出力するだけでちょっとひと苦労)
  • RAMがとにかく少ないので,スレッドのスタックとかをさらに節約してギリギリまでメモリチューニングした.(スタック不足とかでたまに動かなくなったりしてハマることが数回あった)
  • シリアルの割り込みを有効にする際に,IPRLというレジスタを操作して,シリアル割り込みの優先度を設定しないといけない.これが起動時のデフォルトでは優先度最低になってしまっているため,割り込み有効化しても割り込みが入らない.これはかなりハマッたところだった...
  • ブートローダーの割り込みハンドラが

    void interrupt(int vec);

    のようになっていて,割り込み発生時にはintr.Sで割り込み番号を引数にしてinterrupt()を呼び出すのだけど,割り込み番号をアセンブラで設定する際に即値としてレジスタに設定するため,int型の値でなく1バイトの数値として符号拡張されてレジスタに値が入る.このためシリアル受信割り込み(221)がcharの上限を越えてしまい符号拡張されて,負の値になって interrupt() が呼ばれてしまうため,シリアル受信時に interrupt() がおかしなソフトウエア割り込みベクタを実行してしまい,誤動作していた.(割り込みが入ってもハンドラが呼ばれず,これはかなりハマッた)
  • 割り込みベクタ数がH8に比べて多く(H8は64個だが,SH2は256個),またRAMも少ないので,ソフトウエア・割り込みベクタを割り込みごとに用意するのはやめにしてひとつのハンドラですべてまかなうように変更(ハンドラ呼出し時に割り込みベクタ番号を引数にして関数コールするので,ハンドラでは番号見て処理を行う)
まあこんなところかな.最初のシリアル送信できるようになるまでと,OS起動後のSCI割り込み受け付けでハマッた.まあ赤と白のLEDがあったので,LEDデバッグでなんとかなりはしたのだけど,思いのほかハマッて苦戦した.やっぱし32ビットCPUは複雑さが全然違うね.H8よりずっと高機能で,動かすのにいろいろ設定も必要な感じ.

以下,実際に動かしてみたところ.動作方法はH8/3069Fとほとんど同じ.1点だけ,フラッシュROMの書き込みに h8write でなく FDT を使うので,このときだけはWindowsで作業する必要がある.

kzboot (kozos boot loader) started.
kzboot> load
~CLocal command? lsx kozos.mot
Sending kozos.mot, 94 blocks: Give your local XMODEM receive command now.
Bytes Sent: 12160 BPS:865

Transfer complete
eceive succeeded.
kzboot> run
starting from entry point: ffffa010
kozos boot succeed!

start command thread.
> echo test
test
OK
> threads
extintr
idle
command
OK
>

ブートローダーを起動してOSをダウンロードし,起動してコマンド受け付けできている.いやー,なんとか動いたね.よかったよかった.

雑感として,tinyといえどやはりSH2は32ビットRISCプロセッサだなあ,と思った.アセンブラとか,割り込み処理とか,レジスタ周りとか,I/Oポートまわりの設定の必要性とか,H8よりもはるかに複雑で,パッと見は似てるけど,路線というかポリシーはまったく違うCPUなのだなあ,と思った.対してH8は,やっぱしマイクロコントローラだなあ,という感じ.

そう思うとH8は移植が簡単だったなあ.シリアルとかタイマとか,たいして設定せずにあっさり動いたし.H8移植を先にやっといてよかったかも.

あとやっぱし8KBというのはOS動かすには少なくて,ROM上で動かすか,ライブラリ周りをROMに持っていってBIOSみたいにするか,なんか考えないとこれ以上の機能追加は現実,厳しい.H8/3069Fのボードは16KBある上におまけで2MBのDRAMがついている(マニュアルでは「あったらラッキーなおまけ」と書いてあるのだけど,web上のカタログでは必ずついているように書かれている.よくわからんけどまあだいたいついているもんだと思ってしまっていいみたいな気がする)ので,機能拡張していく楽しみがある.

H8はCISCなので最初はちょっとうーんと思っていたのだけど,逆にSH2はRISCなので32ビット即値をレジスタに直接代入することができず,値を近くのてきとうな場所に置いとく必要がある(このへんは,startup.s でのスタックポインタ設定とかmain()へのジャンプとかを参照してほしい).これがけっこう面倒で,32ビット即値を一撃で代入できるH8は楽で初心者にはいいなあ,と思う.初心者はH8ボードからはじめた方がいいかも.
AD

SH2への移植

テーマ:

H8の移植がだいたいいいかんじで動いたので次なのだけど、まあほんとはH8版のソースコードをきれいにしていくべきなのだけど、こないだSH2のマニュアルをちょっとみたらペリフェラル周りとか割り込みまわりがH8にかなり似ていて、もしかしたらH8をベースに移植すれば、SH2でもあっさり動くのでは。。。と思って現在SH2に移植中。


ターゲットはまた秋月のボード。今度はSH7125Fってやつ。


SH2なのだけど、アセンブラまわりとかI/Oまわりも、さすがにH8よりは格段に32ビットCPUっぽいかんじで、H8ほど単純ではなく、あっさり簡単にはいかないようだ。ちょっと甘く見ていたか。。。


とりあえずブートローダーは動くようになった。で、OSが起動するとこまでは動いたのだけど、そのあとコマンド応答が無い。うーん、ブートメッセージは出ていてスレッドに切り替わるあたりで固まっているように思えるので、スレッドのディスパッチ周りか割り込み周りに問題がありそうだ。


SHもおもしろいね。さすがに32ビットCPUってかんじで、いろいろできそうだ。

ただ、内蔵RAMが少なすぎ!せめて16KBはほしかった。。。8KBしかないので、H8用のKOZOSだとあふれてしまうので、Interface誌に掲載したときの極限までサイズを削ったやつをベースにして移植を進めている。。。

AD
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

昨日からアホみたいに更新しているが,久々に時間がとれて一気に書き進めるいい機会なので,いっきにやってしまおう.

ということでちょっと見直しして,細かいところというかイマイチな部分を直してみた.

以下,修正したソース.

■ ブートローダーの修正

まず大きな修正として,割り込み発生時にスタックにレジスタを退避する際に,レジスタの退避順番を逆にするように仕様変更した.これは,従来の順番だとアドレスの低位から ER6, ER5, ER4, ... のように格納されてしまっていて,なんか順番が逆になっていたので,逆にするようにした.これによりOSのthread.c のレジスタ値保存部分で,memcpy() で一撃でレジスタ値を退避できるようになるメリットがある.

実際に修正したのは intr.S で,以下のように手を入れている.

diff -ruN h8_10/kzload/intr.S h8_11/kzload/intr.S
--- h8_10/kzload/intr.S Sun Sep 20 13:39:14 2009
+++ h8_11/kzload/intr.S Sun Sep 20 18:42:36 2009
@@ -8,20 +8,20 @@
mov.l er7,er0
mov.l #_stack,sp
mov.l er0,@-er7
- mov.l er1,@-er7
- mov.l er2,@-er7
- mov.l er3,@-er7
- mov.l er4,@-er7
- mov.l er5,@-er7
mov.l er6,@-er7
+ mov.l er5,@-er7
+ mov.l er4,@-er7
+ mov.l er3,@-er7
+ mov.l er2,@-er7
+ mov.l er1,@-er7
mov.w #1,r0
jsr @_interrupt
- mov.l @er7+,er6
- mov.l @er7+,er5
- mov.l @er7+,er4
- mov.l @er7+,er3
- mov.l @er7+,er2
mov.l @er7+,er1
+ mov.l @er7+,er2
+ mov.l @er7+,er3
+ mov.l @er7+,er4
+ mov.l @er7+,er5
+ mov.l @er7+,er6
mov.l @er7+,er0
mov.l er0,er7
mov.l @er7+,er0
...



次に,シリアルの速度を上げる修正.モトローラSフォーマットにしたためにOSのダウンロードにやたら時間がかかるようになってしまったので,とりあえずシリアルの転送レートを上げる.いろいろ試した結果,19200bpsよりも上げると通信がうまくいかないので,19200bpsとする.

diff -ruN h8_10/kzload/serial.c h8_11/kzload/serial.c
--- h8_10/kzload/serial.c Sun Sep 20 13:39:14 2009
+++ h8_11/kzload/serial.c Sun Sep 20 18:42:36 2009
@@ -15,8 +15,10 @@
volatile uint8 scmr;
};

-#define H8_3069F_SCI_SMR_CKS0 (1<<0)
-#define H8_3069F_SCI_SMR_CKS1 (1<<1)
+#define H8_3069F_SCI_SMR_CKS_PER1 (0<<0)
+#define H8_3069F_SCI_SMR_CKS_PER4 (1<<0)
+#define H8_3069F_SCI_SMR_CKS_PER16 (2<<0)
+#define H8_3069F_SCI_SMR_CKS_PER64 (3<<0)
#define H8_3069F_SCI_SMR_MP (1<<2)
#define H8_3069F_SCI_SMR_STOP (1<<3)
#define H8_3069F_SCI_SMR_OE (1<<4)
@@ -48,7 +50,17 @@

sci->scr = 0;
sci->smr = 0;
+#if 0
sci->brr = 64; /* 20MHz 9600bps */
+#elif 1
+ sci->brr = 32; /* 20MHz 19200bps */
+#elif 0
+ sci->brr = 15; /* 20MHz 38400bps */
+#elif 0
+ sci->brr = 9; /* 20MHz 57600bps */
+#else
+ sci->brr = 4; /* 20MHz 115200bps */
+#endif
sci->scr = H8_3069F_SCI_SCR_RE | H8_3069F_SCI_SCR_TE;
sci->ssr = 0;

同様の修正はOS側にも入れる.このため,今後はシリアル接続の際には9600bpsでなく19200bpsを選択する必要あり.これは cu で接続する場合には

# cu -s 19200 -l /dev/cuad0

のようにして,-sオプションで転送レートを指定できる.

次に,XMODEMでのモトローラSフォーマット転送時の処理をちょろっと修正.エラー発生時にエラー検出できていなかったので,戻り値を int としてエラーを返せるように修正した.

diff -ruN h8_10/kzload/xmodem.c h8_11/kzload/xmodem.c
--- h8_10/kzload/xmodem.c Sun Sep 20 13:39:14 2009
+++ h8_11/kzload/xmodem.c Sun Sep 20 18:42:36 2009
@@ -33,7 +33,7 @@
return 0;
}

-static char *xmodem_read_block(char *buf)
+static int xmodem_read_block(char **buf)
{
unsigned char c, block_num, check_sum;
int i;
@@ -42,19 +42,20 @@
block_num ^= serial_getb();

if (block_num != 0xff)
- return NULL;
+ return -1;

check_sum = 0;
for (i = 0; i < XMODEM_BLOCK_SIZE; i++) {
c = serial_getb();
- if (!buf) {
+ if (*buf == NULL) {
if (srec_decode(c) < 0)
- return NULL;
+ return -1;
} else {
#ifdef USE_UUENCODE
- buf = uu_decode(buf, c);
+ *buf = uu_decode(*buf, c);
#else
- *(buf++) = c;
+ **buf = c;
+ (*buf)++;
#endif
}
check_sum += c;
@@ -62,16 +63,15 @@

check_sum ^= serial_getb();
if (check_sum)
- return NULL;
+ return -1;

- return buf;
+ return 0;
}

int xmodem_recv(char *buf)
{
int receiving = 0, size = 0;
unsigned char c;
- unsigned char *p;

srec_init();

@@ -86,12 +86,9 @@
break;
} else if (c == XMODEM_SOH) {
receiving++;
- p = xmodem_read_block(buf);
- if (buf && !p) {
+ if (xmodem_read_block(&buf) < 0) {
serial_putc(XMODEM_NAK);
} else {
- if (p)
- buf = p;
size += XMODEM_BLOCK_SIZE;
serial_putc(XMODEM_ACK);
}

ブートローダーの修正はこんだけ.次はOSの修正点.

■ OSの修正

まず,シリアルの転送レートを19200bpsにする修正.これはブートローダーの修正と同じもの.

diff -ruN h8_10/os/serial.c h8_11/os/serial.c
--- h8_10/os/serial.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/serial.c Sun Sep 20 18:43:09 2009
@@ -18,8 +18,10 @@
volatile uint8 scmr;
};

-#define H8_3069F_SCI_SMR_CKS0 (1<<0)
-#define H8_3069F_SCI_SMR_CKS1 (1<<1)
+#define H8_3069F_SCI_SMR_CKS_PER1 (0<<0)
+#define H8_3069F_SCI_SMR_CKS_PER4 (1<<0)
+#define H8_3069F_SCI_SMR_CKS_PER16 (2<<0)
+#define H8_3069F_SCI_SMR_CKS_PER64 (3<<0)
#define H8_3069F_SCI_SMR_MP (1<<2)
#define H8_3069F_SCI_SMR_STOP (1<<3)
#define H8_3069F_SCI_SMR_OE (1<<4)
@@ -64,7 +66,17 @@

sci->scr = 0;
sci->smr = 0;
+#if 0
sci->brr = 64; /* 20MHz 9600bps */
+#elif 1
+ sci->brr = 32; /* 20MHz 19200bps */
+#elif 0
+ sci->brr = 15; /* 20MHz 38400bps */
+#elif 0
+ sci->brr = 9; /* 20MHz 57600bps */
+#else
+ sci->brr = 4; /* 20MHz 115200bps */
+#endif
sci->scr = H8_3069F_SCI_SCR_RE | H8_3069F_SCI_SCR_TE;
sci->ssr = 0;

次に,消費電力低減のためにアイドル時にスリープモードに入るようにする修正.

diff -ruN h8_10/os/idle.c h8_11/os/idle.c
--- h8_10/os/idle.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/idle.c Sun Sep 20 18:43:09 2009
@@ -5,6 +5,6 @@
int idle_main(int argc, char *argv[])
{
while (1) {
- /* none */
+ asm volatile ("sleep");
}
}

スリープ命令を実行すればいいだけのようなので,そのように修正した.idleスレッド中にシリアル出力するような処理を入れて実際に試してみたところ,sleep実行するとシリアル出力されなくなるので,たしかにスリープしているようだ.

次に,スタックサイズ指定の修正.これにより,idleスレッドのようなたいしてスタックを利用しないスレッドはスタックサイズを少なく指定して起動できるので,メモリの節約になる.

具体的には,kz_start()とkz_run()に stacksize というパラメータを追加して,スレッド単位でスタックサイズを指定できるように修正している.ちょっとソースコード全体に渡って修正が入っているのだけど,まあパラメータ追加とか引数追加のちょろっとした修正なので,全部ここで説明するのはやめておく.興味のある人はdiffをとって見てみて.とりあえず kozos.h と syscall.[ch] の修正内容を以下に添付しておく.

diff -ruN h8_10/os/kozos.h h8_11/os/kozos.h
--- h8_10/os/kozos.h Sun Sep 20 13:39:30 2009
+++ h8_11/os/kozos.h Sun Sep 20 18:43:09 2009
@@ -11,7 +11,7 @@
typedef int (*kz_dbgfunc)(struct _kz_thread *thp, int signo);

/* syscall */
-uint32 kz_run(kz_func func, char *name, int pri, int argc, char *argv[]);
+uint32 kz_run(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[]);
void kz_exit();
int kz_wait();
int kz_sleep();
@@ -35,7 +35,7 @@
int kx_kmfree(void *p);

/* library */
-void kz_start(kz_func func, char *name, int pri, int argc, char *argv[]);
+void kz_start(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[]);
void kz_sysdown();
void kz_trap();
void kz_break();


diff -ruN h8_10/os/syscall.c h8_11/os/syscall.c
--- h8_10/os/syscall.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/syscall.c Sun Sep 20 18:43:09 2009
@@ -12,12 +12,13 @@

/* System Call */

-uint32 kz_run(kz_func func, char *name, int pri, int argc, char *argv[])
+uint32 kz_run(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[])
{
kz_syscall_param_t param;
param.un.run.func = func;
param.un.run.name = name;
param.un.run.pri = pri;
+ param.un.run.stacksize = stacksize;
param.un.run.argc = argc;
param.un.run.argv = argv;
kz_syscall(KZ_SYSCALL_TYPE_RUN, ¶m);


diff -ruN h8_10/os/syscall.h h8_11/os/syscall.h
--- h8_10/os/syscall.h Sun Sep 20 13:39:30 2009
+++ h8_11/os/syscall.h Sun Sep 20 18:43:09 2009
@@ -28,6 +28,7 @@
kz_func func;
char *name;
int pri;
+ int stacksize;
int argc;
char **argv;
int ret;

次に,extintr でメッセージ使用するための修正.

diff -ruN h8_10/os/extintr.c h8_11/os/extintr.c
--- h8_10/os/extintr.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/extintr.c Sun Sep 20 18:43:09 2009
@@ -5,7 +5,7 @@

#include "lib.h"

-/* #define USE_MESSAGE */
+#define USE_MESSAGE

#define BUFFER_SIZE 16

これを有効にする場合には,extintr が優先度ゼロで起動して,さらに優先度ゼロのスレッドは割り込み禁止で動作するという対処を入れる必要がある(でないと割り込みフラグが落ちないままにスレッドのディスパッチが行われ,割り込み有効になって再度割り込みが入って無限ループになってしまう).割り込み禁止にする処理はすでに thread.c の thread_run() に入っているので,extintr の起動時に優先度ゼロで起動するように kozos.c に対処を入れる.

diff -ruN h8_10/os/kozos.c h8_11/os/kozos.c
--- h8_10/os/kozos.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/kozos.c Sun Sep 20 18:43:09 2009
@@ -10,18 +10,18 @@
kz_debug(stub_proc);
#endif

- extintr_id = kz_run(extintr_main, "extintr", 1, 0, NULL);
- idle_id = kz_run(idle_main, "idle", 31, 0, NULL);
+ extintr_id = kz_run(extintr_main, "extintr", 0, 0x200, 0, NULL);
+ idle_id = kz_run(idle_main, "idle", 31, 0x100, 0, NULL);
#if 0
- command0_id = kz_run(command_main, "command0", 11, 0, NULL);
+ command0_id = kz_run(command_main, "command0", 11, 0x200, 0, NULL);
#endif
- command1_id = kz_run(command_main, "command1", 11, 1, NULL);
+ command1_id = kz_run(command_main, "command1", 11, 0x200, 1, NULL);

return 0;
}

int kozos_start(int argc, char *argv[])
{
- kz_start(mainfunc, "main", 0, argc, argv);
+ kz_start(mainfunc, "main", 0, 0x100, argc, argv);
return 0;
}

上の差分だとスレッドのスタック指定の差分がいっしょになっていてちょっとわかりづらいのだけど,extintr の優先度が1→0に変更されている点に注意.

次に thread.c の修正.以下の点を修正してある.
  • thread_run()にstacksizeの引数を追加し,スタックをスレッドごとに指定されたサイズで獲得するように処理を変更.
  • schedule()の処理をデバッグ用にベタ書きで書いてそのまま忘れていたので,もとに戻す.
  • 割り込み発生時のレジスタ退避順序が変更されたので,それに合わせて退避処理を修正.ついでにmemcpy()で簡略化する.

diff -ruN h8_10/os/thread.c h8_11/os/thread.c
--- h8_10/os/thread.c Sun Sep 20 13:39:30 2009
+++ h8_11/os/thread.c Sun Sep 20 18:43:09 2009
@@ -80,7 +80,7 @@
thread_end();
}

-static uint32 thread_run(kz_func func, char *name, int pri,
+static uint32 thread_run(kz_func func, char *name, int pri, int stacksize,
int argc, char *argv[])
{
int i;
@@ -100,9 +100,9 @@
thp->func = func;
thp->pri = pri;

- memset(thread_stack, 0, SIGSTKSZ);
+ memset(thread_stack, 0, stacksize);

- thread_stack += THREAD_STACK_SIZE;
+ thread_stack += stacksize;
thp->stack = thread_stack;

thp->context.pc = (uint32)thread_init | ((uint32)(pri ? 0 : 0xc0) << 24);
@@ -306,7 +306,8 @@
/* システムコールの実行中にcurrentが書き換わるので注意 */
switch (type) {
case KZ_SYSCALL_TYPE_RUN:
- p->un.run.ret = thread_run(p->un.run.func, p->un.run.name, p->un.run.pri,
+ p->un.run.ret = thread_run(p->un.run.func, p->un.run.name,
+ p->un.run.pri, p->un.run.stacksize,
p->un.run.argc, p->un.run.argv);
break;
case KZ_SYSCALL_TYPE_EXIT:
@@ -384,7 +385,6 @@

static void schedule()
{
-#if 0
#if PRI_NUM > 32
#error ビットマップを配列化する必要あり
#endif
@@ -412,14 +412,6 @@
while (1)
;
}
-#else
- int n;
- for (n = 0; n < PRI_NUM; n++)
- if (readyque[n].head) break;
- if (n == PRI_NUM)
- while (1)
- ;
-#endif

current = readyque[n].head;
}
@@ -505,14 +497,8 @@
break;
}

- p = (uint32 *)INTR_STACK_START;
- current->context.er[7] = *(--p);
- current->context.er[1] = *(--p);
- current->context.er[2] = *(--p);
- current->context.er[3] = *(--p);
- current->context.er[4] = *(--p);
- current->context.er[5] = *(--p);
- current->context.er[6] = *(--p);
+ p = (uint32 *)INTR_STACK_START - 7;
+ memcpy(¤t->context.er[1], p, sizeof(*p) * 7);
current->context.er[0] = *(uint32 *)(current->context.er[7]);

current->context.pc = *(uint32 *)(current->context.er[7] + 4);
@@ -521,7 +507,7 @@
current = thp;
}

-static void thread_start(kz_func func, char *name, int pri, int argc, char *argv[])
+static void thread_start(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[])
{
memset(threads, 0, sizeof(threads));
memset(readyque, 0, sizeof(readyque));
@@ -535,16 +521,16 @@
* 直接関数を呼び出してスレッド作成する.
*/
current = NULL;
- current = (kz_thread *)thread_run(func, name, pri, argc, argv);
+ current = (kz_thread *)thread_run(func, name, pri, stacksize, argc, argv);

dispatch(¤t->context);
}

-void kz_start(kz_func func, char *name, int pri, int argc, char *argv[])
+void kz_start(kz_func func, char *name, int pri, int stacksize, int argc, char *argv[])
{
kzmem_init();

- thread_start(func, name, pri, argc, argv);
+ thread_start(func, name, pri, stacksize, argc, argv);

/* ここには返ってこない */
while (1)

修正は以上.いちおう,これできちんと動作することは確認できた.

これでOSもばっちり動いて,だいたいソースもすっきりした.さて次は何をやろうか.
(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

前回の課題というか今までの課題として,第4回でちょろっと書いたのだけど,ブートローダーをモトローラSレコードフォーマットに対応させよう.理由は以下.
  • 現状で loadbuf[] というバッファにいったんロードしてから実メモリ上に展開しているので,RAM16KBの半分の8KBまでのサイズしか原理的にロードできない.
  • ELFフォーマットのプログラムヘッダを読みながら解析して実メモリ上に直接展開,ということはできなくはないが,なんかめんどい.
  • モトローラSレコードフォーマットだと,読み込みながら実メモリ上に直接展開が非常にやりやすい.
  • 現状でKOZOSのバイナリサイズとロード用のバッファとかがギリギリの感じで,これ以上機能拡張するのはちょっと難しい.ロード用バッファが不要な構成にしたい.
  • 他にもテキストのみのフォーマットなのでなにかと便利だし,安全安心.(バイナリコードだと7bit回線は通らないとか,そーいうことも気にしなくて済む)
  • いろんなところで使われていて,組み込み分野ではかなり普及度が高いフォーマットである.


で,以下の方針で実装してみた.
  • ELFとモトローラSフォーマットの両方に対応.
  • ELFの場合は loadelf, runelf コマンドでロードと起動.動作的には従来と同様で,loadelf でバッファにいったん読み込んで,runelf で実メモリに展開して実行する.
  • モトローラSフォーマットの場合は,load,run コマンドでロードと起動.
  • つまり従来の load, run コマンドは,ELFでなくモトローラSフォーマットを解釈するように仕様変更し,ELF用には loadelf, runelf を新設するということだ.
で,実装したのが以下.モトローラSフォーマットの解釈のために,ブートローダーに srec.c というファイルが追加されている.モトローラSフォーマットについてはネットで検索するといっぱい出てくるのでここではいちいち説明しないけど,まあ解析はたいしてたいへんではない.

修正内容について,以下に説明しよう.まずはブートローダーの修正箇所.

diff -ruN h8_08/kzload/ld.scr h8_10/kzload/ld.scr
--- h8_08/kzload/ld.scr Sun Sep 20 00:33:51 2009
+++ h8_10/kzload/ld.scr Sun Sep 20 13:39:14 2009
@@ -8,6 +8,7 @@
rom(rx) : o = 0x000100, l = 0x07ff00 /* 512kb */
ram(rwx) : o = 0xffbf20, l = 0x004000 /* 16kb */
buffer(rwx) : o = 0xffdf20, l = 0x002000 /* 8kb */
+ data(rwx) : o = 0xffdf20+7400, l = 0x002000-7400 /* -BUFSIZE */
stack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
}

@@ -30,18 +31,22 @@
_erodata = . ;
} > rom

+ .buffer : {
+ _buffer_start = . ;
+ } > buffer
+
.data : AT(_erodata) {
_data_start = . ;
*(.data)
_edata = . ;
- } > buffer
+ } > data

.bss : {
_bss_start = . ;
*(.bss)
*(COMMON)
_ebss = . ;
- } > buffer
+ } > data

_end = . ;

まずリンカスクリプトなのだけど,従来はグローバル変数も loadbuf[] も両方ともバッファ領域に配置されていて,BSSの展開時にグローバル変数も破壊される恐れがあった(ていうか,破壊されていた).

対策として,ロード用のバッファ領域とデータ領域を別セクションとして,データ領域はバッファ領域の後ろのほうに配置してグローバル変数などのデータはそっちに持っていくように修正した.

同様に,main.c の修正が以下.

diff -ruN h8_08/kzload/main.c h8_10/kzload/main.c
--- h8_08/kzload/main.c Sun Sep 20 00:33:51 2009
+++ h8_10/kzload/main.c Sun Sep 20 13:39:14 2009
@@ -1,12 +1,10 @@
#include "lib.h"
#include "serial.h"
#include "xmodem.h"
+#include "srec.h"
#include "elf.h"
#include "interrupt.h"

-#define BUFSIZE 7400
-static unsigned char loadbuf[BUFSIZE];
-
static int init()
{
extern int erodata, data_start, edata, bss_start, ebss;
@@ -33,7 +31,8 @@
return 0;
}

-static int putval(unsigned int value, int column)
+#if 0
+static int putval(unsigned long value, int column)
{
char buf[12];
char *p;
@@ -54,8 +53,9 @@

return 0;
}
+#endif

-static int putxval(unsigned int value, int column)
+static int putxval(unsigned long value, int column)
{
char buf[9];
char *p;
@@ -118,6 +118,8 @@
int size = -1;
char *entry_point;
void (*f)();
+ unsigned char *loadbuf;
+ extern int buffer_start;

init();

@@ -127,7 +129,11 @@
puts("kzboot> ");
gets(buf);

- if (!strcmp(buf, "load")) {
+ if (!strcmp(buf, "load") || !strcmp(buf, "loadelf")) {
+ if (!strcmp(buf, "load"))
+ loadbuf = NULL;
+ else
+ loadbuf = (char *)(&buffer_start);
size = xmodem_recv(loadbuf);
if (size < 0) {
puts("XMODEM receive error!\n");
@@ -136,15 +142,20 @@
}
} else if (!strcmp(buf, "dump")) {
puts("size: ");
- putval(size, 0);
+ putxval(size, 0);
puts("\n");
dump(loadbuf, size);
- } else if (!strcmp(buf, "run")) {
- entry_point = elf_load(loadbuf);
+ } else if (!strcmp(buf, "run") || !strcmp(buf, "runelf")) {
+ if (!strcmp(buf, "run"))
+ entry_point = srec_startaddr();
+ else
+ entry_point = elf_load(loadbuf);
if (!entry_point) {
puts("run error!\n");
} else {
- puts("starting from entry point.\n");
+ puts("starting from entry point: ");
+ putxval((unsigned long)entry_point, 0);
+ puts("\n");
f = (void (*)())entry_point;
f();
}

従来はロード用のバッファ領域を loadbuf[] として静的に獲得していたが,リンカスクリプト内で定義されている &buffer_start を見て,バッファ領域を利用するように修正.

あと loadelf とか runelf とかのコマンド対応がされている.load が実行されたときにはバッファ領域は必要無いのでバッファ未指定でxmodem_recv()が呼ばれ,この場合にはモトローラSフォーマットを受信して直接展開する,という動作になる.

次に xmodem.c の修正.

diff -ruN h8_08/kzload/xmodem.c h8_10/kzload/xmodem.c
--- h8_08/kzload/xmodem.c Sun Sep 20 00:33:51 2009
+++ h8_10/kzload/xmodem.c Sun Sep 20 13:39:14 2009
@@ -1,5 +1,6 @@
#include "lib.h"
#include "serial.h"
+#include "srec.h"
#include "uudecode.h"
#include "xmodem.h"

@@ -46,11 +47,16 @@
check_sum = 0;
for (i = 0; i < XMODEM_BLOCK_SIZE; i++) {
c = serial_getb();
+ if (!buf) {
+ if (srec_decode(c) < 0)
+ return NULL;
+ } else {
#ifdef USE_UUENCODE
- buf = uu_decode(buf, c);
+ buf = uu_decode(buf, c);
#else
- *(buf++) = c;
+ *(buf++) = c;
#endif
+ }
check_sum += c;
}

@@ -67,6 +73,8 @@
unsigned char c;
unsigned char *p;

+ srec_init();
+
while (1) {
if (!receiving)
xmodem_wait();
@@ -79,10 +87,11 @@
} else if (c == XMODEM_SOH) {
receiving++;
p = xmodem_read_block(buf);
- if (!p) {
+ if (buf && !p) {
serial_putc(XMODEM_NAK);
} else {
- buf = p;
+ if (p)
+ buf = p;
size += XMODEM_BLOCK_SIZE;
serial_putc(XMODEM_ACK);
}

バッファ未指定の場合の対処が追加されている.

ブートローダーの修正のおおまかな内容は以上.次にOS側の修正点.

まず make image でモトローラSフォーマットのファイル「kozos.mot」を作成するように Makefile にターゲットを追加.

diff -ruN h8_08/os/Makefile h8_10/os/Makefile
--- h8_08/os/Makefile Sun Sep 20 00:34:04 2009
+++ h8_10/os/Makefile Sun Sep 20 13:39:30 2009
@@ -42,11 +42,14 @@
#$(LIB) : $(LIBOBJS)
# $(AR) ruc $(LIB) $(LIBOBJS)

-$(TARGET).uu : $(TARGET)
+$(TARGET).mot : $(TARGET)
+ $(OBJCOPY) -O srec $(TARGET) $(TARGET).mot
+
+$(TARGET).uu : $(TARGET)
uuencode -o $(TARGET).uu $(TARGET) $(TARGET)

-image : $(TARGET).uu
+image : $(TARGET).mot $(TARGET).uu

clean :
rm -f $(OBJS) $(LIBOBJS) $(LIB) \
- $(TARGET) $(TARGET).elf $(TARGET).uu
+ $(TARGET) $(TARGET).elf $(TARGET).mot $(TARGET).uu

次に,BSSの初期化処理を追加する.モトローラSフォーマットだとBSSの情報が来ないみたいなので,OS側でゼロクリアする必要がある.これをやらないとなんかうまく動かなかった.

diff -ruN h8_08/os/main.c h8_10/os/main.c
--- h8_08/os/main.c Sun Sep 20 00:34:04 2009
+++ h8_10/os/main.c Sun Sep 20 13:39:30 2009
@@ -9,6 +9,12 @@

static int init()
{
+ extern int bss_start;
+ extern int ebss;
+
+ /* clear BSS */
+ memset(&bss_start, 0, (uint32)&ebss - (uint32)bss_start);
+
serial_initialize(0, 0);
return 0;
}

OS側の修正はこれだけ.

では実際に試してみよう.ブートローダーをビルドしてフラッシュROMに書き込んで起動し,従来通りの動作として,loadelf; runelf でELFフォーマットをダウンロードさせてOS起動してみる.

kzboot> loadelf
~CLocal command? lsx kozos
Sending kozos, 56 blocks: Give your local XMODEM receive command now.
Bytes Sent: 7296 BPS:777

Transfer complete
eceive succeeded.
kzboot> runelf
starting from entry point: ffc120
kozos boot succeed!
command> echo aaa
aaa
OK
command> threads
extintr
idle
command1
OK
command>

とりあえず問題なさそう.

次に,リセットボタンを押してブートローダーを起動しなおして,load; run でモトローラSフォーマットでダウンロードしてOS起動してみる.

kzboot> load
~CLocal command? lsx kozos.mot
Sending kozos.mot, 153 blocks: Give your local XMODEM receive command now.
Bytes Sent: 19712 BPS:827

Transfer complete
eceive succeeded.
kzboot> run
starting from entry point: ffc120
kozos boot succeed!
command> echo aaaa
aaaa
OK
command> threads
extintr
idle
command1
OK
command>

おー問題無さそうだ.ちゃんと動いている.

ただ,当り前だけどモトローラSフォーマットはテキスト形式なので,ダウンロード時間がやたら長くなる.修正しては試してまた修正してをガンガン繰り返すような感じでやりたいときにはちょっとうっとうしいかも.まあシリアルの速度が9600bpsなので,115200bpsとかにすれば解決できるとは思うが.これはそのうち考えよう.