最弱のPICでTV出力

いかづちSqueak

2012年11月26日 01:38

最弱のPICマイコン、PIC10F200でTV出力をしてみた。
PIC10F200はPICの中で最弱なだけでなく、今普通に店で買えるマイコンの中でも最弱ではないかと思う。

【ニコニコ動画】最弱のPICマイコンでTV出力してみた

TV出力は以前も16F628Aや10F222でやったことがあった。


16F628A、「イ」16F628A、猫耳

10F222、「イ」10F222、猫耳

それらと今回の10f200の性能を比べるとこんな感じ。

16F628A10F22210F200
I/Oピン数1644
処理速度1MIPS2MIPS1MIPS
FlashROM2kワード512ワード256ワード
メモリ224バイト23バイト16バイト
周辺機能色々ADCとタイマタイマのみ
色々違いはあるが、今回問題になるのは処理速度とFlashROMのみである。
16F628AはFlashが2Kワードと(10F2xxに比べれば)潤沢であったのでXORWFとNOPをベタ書きして1サイクル1ドットを出力する力技を使っていたのだが、10F222ではそうはいかないので1ドットあたりデータの読み取りと書き換えの2サイクルを使う方式をとった。
(なお後で知ったが、16F628Aの同期シリアル出力を流用して1ドット1サイクルでメモリから出力する技がある)

ベタ書きバージョン
BCF PORTA,1 ;まず黒にしておく
MOVLW 0x01 ;XOR用のマスクを読み込む
XORLW PORTA ;ここで白になる
XORLW PORTA ;ここで黒になる
NOP ;ここで黒のまま
NOP ;ここで黒のまま
XORLW PORTA ;ここで白になる
NOP ;ここで白のまま
これを表示する画像のドット数だけ並べる。例えば50×50ドットなら2500ワード。一方16F628AのFlashは2048ワード。
実際にはNOP2つをGOTO $+1にするなど多少の圧縮は効くので52×52くらいいけたりする。

メモリ読み込みバージョン
BCF GPIO,1 ;ここと
MOVLW 0x01 ;ここは同じ
btfsc DATA0, 0 ;メモリを見る
xorlw GPIO ;メモリが1ならばここで色が変わる
btfsc DATA0, 1 ;以下同様
xorlw GPIO
btfsc DATA0, 2
xorlw GPIO
これを1ライン分用意しておき、必要に応じてデータを書き換える。
解像度は半分になるが、メモリを1ワード8ビットとして使えるので効率的。
今回はFlashが少ないので当然メモリ読み込みバージョンを使ったのだが、10F222は16F628Aの2倍速で動くので都合16F628Aと同じ解像度が出せるのだが、10F200では解像度は半分になってしまった。

なおどちらの場合も、白と黒の切り替わるところで出力をXORで切り替えるので、画像データの持ち方が面倒になる。
最初は読み取るときに変換しようとしていたのだが、その分のコード(6ワード)が惜しかったのでXORした状態のデータを持つようにした。
そのかわりマクロを使ったのでソースでは一応見やすくなっている。
(ちなみにXORで出力を切り替えるのは、出力先のインピーダンス(抵抗)が十分に高い場合でないと正常に働かないらしいが、まあなんか動いてるので大丈夫だったようだ)

コードの流れは次のようになっている。
・画像の1行(走査線12ライン相当)ごとに、
 ROMの画像データテーブルを読み込み、メモリに書きだしておく
・走査線1ラインごとに、
 メモリを読んでI/Oピンに出力×24回
 水平同期信号を出力
・1画面ごとに、
 垂直同期信号を出力する
 画像データのポインタをリセットする
・15画面ごとに
 アニメーションのため画像データのポインタを1画面分ずらす

ROMから直接出力せずにメモリを経由する理由だが、ベースラインのPICはROMからの読み出しに時間がかかるためである。
メモリからワーキングレジスタにデータを読むには
movf MEM, W
の1命令1サイクルで済むのだが、
ROMからの読み出しは
(Wレジスタに対象のアドレスが入っているとして)
call READ_TABLE ;サブルーチンコール
READ_TABLE: ↓サブルーチンの場所
movwf PCL ;対象のアドレスへジャンプ
;↓PCLに書き込まれたアドレス
retlw 0xNN ;定数値を返すリターン命令
という3命令6サイクルが必要になる。

10F222では水平帰線期間を使ってROMからの読み出しをしていたのだが、速度が半分の10F200ではかなり無理があったので、1ラインを捨てて読み出しに専念している。画像の1pxごとに1ラインの黒線が入っているのがこれだ。

また、コード短縮に効果があったのが、同期信号の簡略化だ。
同期信号は前回は律儀に出していたのだが、ファミコンなど結構いい加減な同期信号を出していてそれでも大丈夫という情報を得たので、今回は垂直同期と水平同期のみにしてみた。これは難なく成功した。これによりコードサイズの大幅な削減ができた。今回もっともきつかった制限がコードサイズなので、これが無かったら1枚絵になっていただろう。

最後に回路とソースを公開する。

・回路

2本の抵抗で4値を出す一種の抵抗ラダーにPICの0番ピンと2番ピンをつないでいる。
ちなみになんで0と1でないかというと、この石の2番ピンはプルアップ抵抗がついておらず入力に使いづらい仕様のため優先的に出力に使いたいからである。
同軸ケーブルの右はTV内部を示す。映像信号の終端抵抗は75Ωのはず(というか実際測ったらそうだった)。電源電圧3Vの時にこれと分圧して1Vp-pが出るように抵抗値は定めたつもり。ただ、実はこれで出力したところやけに暗かったので何か間違っているかもしれない。
コンデンサはちょうど手元にあった470uFを使っているが、値は適当でよい。

・ソース
HTMLに書くにあたってタブを全角スペース2つに、半角スペース2つを全角スペース1つに置換しているので直さないと動かない。
  list  p=10F200
  #include p10f200.inc
  radix dec ;数値のデフォルトが16進だが、10進に変えておくのが好み

  ;  N/C+-v-+(GP3) ;ここに図を書いておくとブレッドボードに組むとき便利
  ;  Vdd|  |Vss
  ;  GP2|  |N/C
  ;  GP1+---+GP0

  __CONFIG _MCLRE_OFF & _CP_OFF & _WDT_OFF ;MCLRオフ、コードプロテクトオフ、WDTオフ。WDT以外はどうでもいい。

  cblock 0x10 ;メモリの宣言
    linebuf1 ;ラインバッファ×3バイト
    linebuf2
    linebuf3
    linecount ;画像1行ごとの走査線ライン数のカウンタ
    imgcount ;画像データ読み出し用のポインタ
    wcount ;ウェイト用のカウンタ
    blankloopcount ;垂直同期中の走査線ライン数カウンタ
    framecount ;アニメーション用にフレーム数(正しくはフィールド数)をカウント
  endc

#define lineperrow 11 ;絵の1行あたりの走査線数-1

waitx macro cycle ;指定サイクル待つ; 2ワード
  movlw ((cycle)-5)/3
  call wait3w4 - (((cycle)-5)%3)
  endm

imgdat macro dat1, dat2, dat3 ;画像データをXORされた形式で保存するためのマクロ
  retlw low(dat1 ^ ((dat17)))
  retlw low(dat2 ^ ((dat27)))
  retlw low(dat3 ^ (dat3