開発日記

複数の似た処理の統合

かなり手間取ったが統合できた. 入力命令の解釈が不適切で DMA 2ch で動かせる内容を DMA 4ch で動いていて結果としてはあっているという問題が直せた.
data を increment-pattern でいれると MCU が止まってしまうバグがあったが、統合時に increment-pattern と無限ループに対する定義が仕様書になかったので明示して、それに準拠した.

DMAC が RAM に結果を書くタイプの設定不具合

ソースがきれいになったのでいままでやれなかった電源投入後のDMA開始の調整と DMA descriptor と EVSYS を再設定無しに再実行を実装.

ADC が動かない. これまで3度の改訂で処理を少しずつ変えているのだが毎回理由不明で苦しめられる. 今回に関しては再実行で read した data (DR) の先頭にゴミデータが入るという不具合も発生.

今回の傾向から判断するに...

  • DR と ADC は EVSYS 経由ではなく CHCTRLB.TRIGSRC から取って来るものである.
  • CHCTRLA.ENABLE = 0, CHCTRL.SWRST = 1 にしても改善しない.
  • DMA 完了割り込みのときのみに CHCTRL.SWRST = 1 するとなぜか改善する.

以上から CHCTRLA.ENABLE = 0 であっても TRIGSRC を書き換えていないと、DMA ch は TRIGGER を認識しているが ENABLE = 0 なので pending になってしまって DMA がつまるのだろうという仮説ができた. 今回の実装では DMA 完了割り込みのあとに SPI を動かしたりすること、 TIMER が止まってないと ADC が再度動いてしまうことが予想できたからだ.

ということで CHCTRLB = 0 (全ビット)としたら改善した. DR と ADC の channel の TRIGSRC のみ = 0 としたがこれは改善せず、全ての channel の CHCTRLB = 0 としたら期待通りになった. 他の要因は EVSYS につなぐことなんだろうけどここらへんは直接レジスタを見づらく対処に困る.

TRIG がつまってしまった場合にそれを明確に止める方法がないのが対策に時間がかかった.

開発日記

前振り(ながい)

昨年8月まで設計は 74595 を 3 つにして、 CPU PHI2, CPU ROMSEL といった制御線は GPIO で制御していたが、 flash programming の性能がよくない(具体的にはソースが複雑になりすぎる上に遅い)ので制御線も 74595 を 1つ追加して DMA で動かすことにした.

ROM dump では address が increment, data 出力は不要. この場合は動かす address 16 bit で 74595 は 2 個ですむため追加した制御線の DMA を利用するかは条件を設定して切り替えるようにした. 一方 flash programming は制御線を DMA で激しく替える. ROM dump で制御線もいれて動かすこともできるが処理速度は半分になる.

複数の似た処理の統合

このため制御線の DMA を利用するか否かはソースでは処理の途中で新旧の実装でかなり分岐していて、最後にハードウェアを動かすところで1つになる. 途中の分岐は無駄が多いのでできるだけ減らすのが今回の目的.

ハードウェアに近いレイヤの統合は予想外に早く終わったのでその上位のレイヤの統合をし始めた. こちらは難易度が高く、統合ができていない. 統合の処理は使い回す関数をコピーして flash programming のコードは一旦削除して、簡単に構造に戻している.

転送の限界調査

ハードウェアに近いレイヤの統合の時点で ADC の Sampling Freq を可変値にすることができた. 音質は重要ではなく sampling freq を上げていって、どの速さまで限界を知りたい. できれば ROM dump の DMA が動いているときに USB の転送も ROM dump より早く動いてくれているとわかればもっと早くできるし、DMA を頻繁に止める処理もいらなくなる.

sampling freq は前回は定数 16 kHz だったので、20 kHz, 30 kHz と上げていったが 21 kHz で止まった. 一番長い音声が約3秒で、21 kHz の時点でデータ量が約 65,000 bytes になっていた. 止まった理由は一度に転送できるデータを 16 bit に指定していてそれを超える量を想定した設計になってない. つまりプロトコルの再設計が必要.

Cにおけるラベル直後の変数宣言とswitch

void hoge0(int v)
{
	//{} がない switch, 分岐が1つだけで使い物にならない
	switch(v)
	label: break;
	
	//{} がないので label は有効
	goto label;
}

void hoge1(int v)
{
	//{} がある switch 
	switch(v){
	case 1: break;
	label: break;
	}
	
	//label: は switch{} のそとでも使える (なんで?)
	goto label;
}

int hoge2(int v)
{
	switch(v){
	case 1: //error, case x:, label: の直後に変数宣言はできない
		int i = 0;
		break;
	label: break;
	}
	return i;
}

int hoge3(int v)
{
	switch(v){
	case 1: ; //ダミーの ; をいれれば変数宣言は通る
		int i = 0;
		break;
	label: 
		i = 2; //i は有効
		break;
	}
	//error, i の有効範囲は switch の {} に制限されている
	return i;
}

int hoge4(int v)
{
	switch(v){
	case 1: { //ダミーの {} をいれれば変数宣言は通るが
		int i = 0;
		}break;
	label: 
		i = 2; //error, 上の {} の外では i は 無効
		break;
	}
	//error, この i も無効
	return i;
}
int hoge5(int v)
{
	switch(v){
	int i = 0; //ラベルをいれなかったらこの変数宣言は有効
	case 1:
		i = 2;
		break;
	label:
		i += 3; //この場合, i の宣言だけが有効で代入(i=0)は無効
		//case の前か後かは関係ないようだ
		//コンパイラが未初期化の値を利用していると警告した
		break;
	}
	if(i < 4){ //error, i の有効期間は {} の中まで
		goto label;
	}
	return i;
}

開発日記

カードエッジの Sound Out 端子から MCU に配線し ADC を使う. これによって燃えプロ系のボイスをアナログ経路で録音することに成功. *1

録音手法

  • TCx->EVSYS->ADC, ADC->DMA->EVSYS をつなぐ.
  • TC から ADC はタイマの OVERFLOW イベントを ADC につなぐ.
  • ADC から DMA は EVSYS でもいけると思うが、TRIGSRC からでもいける.
  • DMA は block 単位転送完了を EVSYS でつなぎ、割り込みをおこす.

この実装に関して EVSYS の仕様書を見たらバスのつなぎ方に非同期、同期、再同期がありデバイスが求めるバスを適切に接続する必要がある. それに気づいていなかったので全部非同期にしていた. それを直すのに手間取った. 直す過程でバスサイクルが少し悪くなったので後日要調整.

音質に関して

人の声レベルなので元々の音がサンプリングレートが低くく、 MCU の汎用の ADC で取るには結構マシな音質であった. Youtube でのプレイ動画の音質と比べても素人の私には大体同じに聞こえた.

DMA に関して

block 単位の転送完了の割り込みは DMA 単体からは suspend 動作がついてしまうが、 EVSYS 経由で割り込みをおこすと suspend がつかない.

voice の場合は音声再生時間が長くなると MCU の RAM の容量を超えてしまうので、録音途中にPCへ随時送り続けるようにしたい. バッファRAMを半分で2つに分ける. 1つの block を DMA の書き込み、もう1つの block を読み込み(PC へ送信)にする.

この場合、DMA descriptor の使用数を転送サイズ/バッファサイズ/2 にすることも可能だが、 2つの descriptor で無限ループを作ることも可能. 実は以前から address の increment pattern の bit7:0 は 1 つの descriptor で無限ループにしている. address 出力は MCU からは write (送信) なので割り込みはいらない.
この転送途中に割り込みをかけて随時送る方式は今後他の目的でも使いたいので ADC をネタにちゃんと実装したいので実証実験も兼ねている. 実のところ MCU 用のソースコードは早い段階でできているのだが、シミュレータでは無限ループ型の DMA は先述の1つのパターンのみ対応でそれの対応に手間取っている. だから動作確認がとれてないので MCU で動かすのは意味がない.

基本MCU用のソースだけ書いても1発でちゃんと動くわけがないし、デバッグ情報がとれないので MCU 用のソースは依存部分をできるだけ排除して PC でもほぼ同じコードを動かしてデバッグ情報を得ることで直したほうが早い...早いはウソでこれがないと直せない.

*1:注意: 音源ICへカートリッジ内部からクロックを供給しているから録音ができる. 燃えプロ系以外は内部でクロックを供給していないことが多い.

開発日記

休止期間も含めて3か月もまともに動作しなかった LZ93D50 とその周辺の対応の大半がようやく終わった. variant が2つあってそれはまだ対応してない.

問題その1/未完: write cycle で MCU が死ぬ

CPU_RW の配線が不適切だったので OR gate をいれて修正したことで発生頻度がとても下がったが、まれに起きるので完全に直ってないと思う.

問題その2/完了: register への write cycle を認識しない

register への write cycle の前には必ず read cycle がいる.

問題その3/完了: read cycle と write cycle を1度に含めると通信がおかしくなる

長らく動作不安定の原因として悩まされたもの. 原因究明が問題その1と問題その3の切り分けに相当時間がかかり、カートリッジに関係なく MCU 単体で問題が発生する.

MCU のソフトの実装が悪いまではわかったものの、明確におかしい場所の特定ができなかった. おかしい場所は3つの送信処理を3つにわけていたのでうち2つを1つにまとめて送ることで直りそうな気がした. その後も原因特定ができなかったので直る保証がないまま実装したところ問題その3は起きなくなった.

問題その4/完了: 静的なバッファ管理の限界

問題その2とその3の対策として複合的な問題. MCU へ送る命令は複数記載できるものの容量は 0xc0 bytes, ROM dump での容量は 0x400 bytes と固定になっていた. これで問題その2の dummy read を実装した上で EEPROM を効率的に制御しようものなら 0xc0 bytes は少なすぎる. そして ROM dump の buffer はこのときはそんなにいらない.

また backup RAM (パラレルのSRAM) write でも同じ問題があり、その場しのぎに無茶castしてたが限界を感じたので動的な確保をすることにした. これに関して MCU 内部に各種 alignment 制限があるので動的管理領域を100%使い切れることはないが、柔軟性が増えた.

問題その5/新規: MCU の ROM 容量の限界

firmware を修正したり効率的な機能を実装していったら残り 0x1000 bytes を切ってしまった. RAM では場合によって使わない部分は動的管理すれば資源はわりと使い回せるが、 ROM ではそういうことができない.

もうちょっと機能を追加したいのでそれができた場合に使用率95%では将来性が全くない...

問題その6/新規: flash programming の再実装

問題その4やその他の過程で flash programming に関する RAM 管理や今見ると不必要に複雑で何をやっているかわからないコードをいったん捨てて実装したのでちゃんと動くように戻す必要がある.

古いPCゲームソフトとMIDI ファイルの読み込み

ソフト開発としてつまっていることはないがスランプになってしまってコードが見たくなり別件が入ったことで停滞中... 作業量が少ない対応として Windows 98 時代のゲームソフトの修正をしてみた.

BGM として MIDI (.mid) ファイルを利用するのだが、ファイル再生前に5秒間ぐらいソフトが止まる. 原因を探った. (Windows ソフトの hack はほとんど経験無し)

Win32 API として当時の midi の再生手段は mci を利用して mciSendCommand() か mciSendString() を利用するらしい. 該当のゲームソフトを逆アセンブルしてみた、 UsaMimi32.exe から実行中のプログラムを逆アセンブルしたり、ブレークをかけることができる. (すごい)

解析手順

  • モジュールを逆アセンブル → 逆アセンブル実行 → 検索:APIコールで mci を入れる
  • 検索結果で mciSendCommand() を利用した数か所でてくる
  • mciSendCommand() の実装前に引数の確認. ABI はちゃんとよんでないが引数を逆から push するらしい.
    • 第2引数の uMSG は immediate を push していたのでその値の意味を調べる. mingw では /mingw32/include/mmsystem.h に定義があった.
    • mciSendCommand() を呼び出してる箇所で引数が MCI_OPEN の場所は2個に絞り込めた
  • ブレークポイントの挿入/削除でコード実行ブレークポイント(INT3) で該当の2つの PC (=EIP) をいれる (ここで書き写しが発生するのがちょっと...)
    • ゲーム側で音を鳴らしてみて2つの PC は midi と wave で別れていることが判明した.

API をみる

midi を利用するための関数の逆アセンブルされた命令*1を眺めてみる.
http://www13.plala.or.jp/kymats/study/MULTIMEDIA/mciCommand_play.html このサイトの説明通りの手順らしい. そこで第3引数の lpOpen のもととなる変数をみたところ, ここの説明通りで下記のようだった.

static MCI_OPEN_PARMS mop;
mop.lpstrDeviceType= "sequencer"; //.exe ファイルの(たぶん) rodata section にある
mop.lpstrElementName= xx; //ini ファイルから読んだ文字列をわたしていたので動的確保された領域のようだ

これの原因は上記で言う変数 lpstrDeviceType が "sequencer" の場合は Windows NT 系(2000,xp,それ以降) だと遅く "MPEGVideo" だと早いらしい. ただしなぜそうなるかはちゃんとした文書が見当たらなかった. そしてこの議論は2005年にされているがおそらく 2000 年の時点で明らかになり20年間以上放置されているらしい.
https://www.activebasic.com/forum/viewtopic.php?t=99

以上より .exe ファイルのなかをバイナリエディタで探り "sequencer" を "MPEGVideo" に書き換えることで問題は解消された*2. あとは ElementName の参照先が記載された ini ファイルのファイル名を書き換えると MPEGVideo で開けるファイルの種類がたくさんあるので mp3 も使える.

レジストリでは aiff も "MPEGVideo" で使えるらしいのでやってみたい. 噂で aiff はループ再生ができると聞いており、仕様書では loop の記述を確認したが、編集ソフトでそれに対応しているものは今はないだろうし、 mci でそれが動くかはわからない.

他のソフト

同様の現象があったがこのソフトでは関数は mciWndCreate() で動いているようでそこには DeviceType を明示する方法が分からなかった.

*1:私は 80386 はほとんど知識なしなので addressing mode がよくわからない

*2:注: "" で囲んでいるのは \0 終端文字列である

開発日記

現在は協力者にファミコンソフトを大量に借りて、 dump 機能の動作確認をしている. 意外とやることがおおく flash programming の機能修正ができていない.

設計初期から構想があった nametable control register によるカートリッジハードの検出を実装した. 個人的には優秀な機能だと思っているが欠点もいくつかある.

利点:

  • わりと早い.
  • nametable control で PPU A10,A11 の MUX を介さない TKSROM のような CHR ROM Address MSB を VRAM A10 に配線する variant の検出の信頼度がとても高い.
  • ROM data を参照しないのでバージョン違いを誤認しない.

欠点:

  • nametable control register がない CNROM, UNROM その他単純なハードの検出ができない.
  • MMC1A を利用したハードでは backup RAM に write protect register がついていないので、この領域に nametable control register があるハード(Taito-Xxx ほか数種類)の検出でバックアップデータを消してしまう.
  • VRC1-VRC2, X1-005-X1-017 のようにレジスタ仕様が全く同じだと別の異なる仕様を確認するがそれは ROM bank の違いぐらいなので、ROM data の内容によっては誤認する.

variant hardware の発生原因は大まかに2種類あり上記の nametable 制御と work RAM の容量の違いである. nametable 制御は先述のようにほぼ完璧に検出できる反面、 work RAM の容量とそれに派生する電池の有無の検出は実装しづらい. その理由は30年以上経過しているであろう電池が死んでいると検出を誤認する点である.

今回 +5V の電源スイッチは MCU の GPIO から操作できるので、一旦電源を止めてしばらく待って再度電源をいれてデータが残っていれば電池があると判別の実装は可能. また電池が生きていたとしてもセーブデータを誤って消すリスクがあるのでそれは問題となる.

以上の点からハードウェアの検出は ROM hash (dump 前例)を起因とするデータベースなんかつかわないと豪語しておきながら、電池の有無はデータベースを利用する実装にしてしまった.

開発日記

MCU からの送信が不安定になる問題を解決して安定した programming を確認できた. 長かった... 細かい部分で直すところはたくさんあるが、基礎部分の構築ができたことがとてもうれしい.

送信の不安定は前回の通りでここから改善に苦労した.

  • bootloader 部に送信は 2 段の request 切り替えと 0x200 bytes のバッファを持っていた
  • bootloader に置いていた 0x200 bytes のバッファの扱いに無理があったので作り直した
  • 2 段の request なんかいらないから 1 段でいいや

1段にしたところ不安定だらけになったり送信済みの割り込みが正しく取れない、task の同期がやたらといるようになるなど迷走し始める.

  • requst を 1 段から 5 段の queue 方式に変更
  • 送信用バッファをリングバッファに変更

... とここまでやる必要にようやく気づき安定動作となった. こんなのMCUベンダ提供のライブラリ使えば最初からついてるだろうから圧倒的な開発時間の浪費を実感した.

開発日記

前回書いたシミュレータと本物のソースコードの統合はほぼ完了.

約2週間前に programming も片側の領域ずつ動かすのは安定動作を確認. 疑似並列 programming は対策をいれてはいるが安定動作していない. 最初の疑似並列動作の波形を確認してから、 WBC (野球)が始まってしまって、序盤の1試合をみたらほぼ毎日見るようになってしまい開発効率がとても落ちた.

WBC が終わって programming 以外もいくつか問題がありそれを先に対応. programming は対応途中. programming は PC 側の待ち時間が多いので, MCU 側のデータがたりなくなると PC に状態を送信するのだが、疑似並列ではその送信が頻度があがり不定期になるためそのやり取りに苦労している状態.

処理高速化のためのアイディアはあるものの、安定動作してから入れるものなのでいまはできなくもどかしい.

開発日記

SWD 切り替えの回路に不具合があったものの放置していたのをいまさら対応. 原因はICの単なるハンダづけの不良.

MCU 用の build で text mode で DMA のあとに文字を打つと1文字無視されることがあるバグの原因調査. 原因はおそらく2つ.

1つ目が5Vの電源投入後に待ち時間があるときに timercallback をちゃんと実装せずに WFI() で止めていたのを修正. これは各種オブジェクトもどきの構造の変更が非常に多く、非常に手間がかかった. ただしバグは直らなかった. 1次的に WFI() を止めたら改善したので本腰をいれて直したが、頻度は下がった気がするので不具合の原因は複数あった気がする(自信なし).

2つめが文字入力の割り込みはちゃんと動いているのに文字として認識されていないところまで判明し、escape sequence の変数がプログラム上ありえない値になっていた. escape sequence の変数のメモリ配置を確認したところ、メモリ節約のため自前で動的に配置していた部分でそこのポインタ算出がものすごく間違っていた.
binary mode のメモリ使用量が増えたことでスタック領域が浅くなり、そこにその変数を配置していた. よって DMA の呼び出しでスタックが深く積まれるとその変数を書き換えてしまうということであった. text mode はシミュレータで確認していないため、もしシミュレータで動かしたら即 segmentation fault でわかったバグである.

1つめの修正でシミュレータも関数の引数がかなり変わってしまったのでそれの対応. こちらは MCU に比べて修正箇所が少なく、1発で対応完了.

以上によりファームウェアの開発は1つの山を越えた. 次回はシミュレータにつくった mruby のスクリプトMCU でも動くように改善する. 半年前に作ったものはシミュレータとMCUと通信するために似たようなソースコードを作っていたものの別々になっていた.

シミュレータ用の flash programmer の構成が異常に複雑になってしまったために、似たものを作るととんでもない手間になる. これをさけるためにシミュレータで作った使い回せるソースコードを取り込む方針で、MCU の通信するソフトを作り直す.