ポケットプリンタ制御2018年12月22日 17:40
この記事はGame Boy Advent Calendar 2018の22日めです。
最近ゲームボーイのプログラミングがマイブームだ。
ところでポケットプリンタってあるよね。
動く機械って萌えるよね。
というわけでやってみた。
使うのはもちろん使い慣れたPICマイコン。
ちょうど最近電子ペーパーを試すのに使っていたPIC16F1508が手頃だったのでこれを使うことにした。(電子ペーパーについてもブログに書こうと思っているのだが…)
なお固有の機能は特に使っていないのでEnhancedミッドレンジならどれでも容易に移植できるはず。SPI送信のみとかわざわざ機能調べるほうがめんどい。
ポケットプリンタ(海外名はGameboy Printer)の制御方法の情報は下記2サイトによくまとまっている。
In Depth: The Game Boy Printer
https://shonumi.github.io/articles/art2.html
Furrtek.org : Reversing GameBoy Printer
http://furrtek.free.fr/?a=gbprinter (原文フランス語)
http://furrtek.free.fr/?a=gbprinter&i=2 (英語; 未翻訳部あり)
さらに実際の送受信内容のダンプが公開されているのが嬉しい。
http://furrtek.free.fr/noclass/gbprinter/hexdump.txt
チェックサムが「1バイトごとの和を2バイトで」という珍しい形式なのでこれが無ければ理解は困難だっただろう。
これらサイトの情報を元に、「最低限動くプログラム」を作った。
最初のプログラムというものは単純なら単純なほどよい。バグも入りにくいし、他人も参考にしやすい。
具体的には、
・印刷する画像サイズは最小の160×16(ヘッドの1行分)
・上下のマージンは手動でフィードすればよいので0
・データの圧縮はしない(そもそも白黒でないとほとんど圧縮されなそうだし、圧縮しなくても送信に1秒掛からないし)
・データは送信のみ、プリンタからの返答は見ない
・statusコマンドのポーリングによる印刷終了判定もしない
・各種データはプログラムに直書き
・EnhancedミッドレンジPICのデータは14bit中8bitしか使えない簡易な読み方と全部読める読み方があるが、楽な前者
・電源ONで自動で1回だけ送信
といった感じ。
用紙は感熱紙自体は容易に手に入る。サイズが合わないのでノコギリで切って…
http://furrtek.free.fr/?a=gbpcable&i=2
「No paper ? Take a used receipt」
なるほどその手があったか!
不要なレシートならそのへんに落ちている。
電池を用意し(多いなあ…)

以前GBAと通信を試したときに通信ケーブル変換コネクタをばらして作った線でつないで(コネクタが入手できなければ本体をばらして線をつなぐのが早いと思う)

…動かない。
色々チェックし見つけたバグを修正したがやはり動かない。
出力は確かに出ている。クロック線とデータ線も間違っていない。
簡略化のためにプリンタからの応答を見なかったのはまずかったか…とも思ったがそれ以前にプリンタからのSO(Serial Out; 分かりやすくいえばMISO)線に何も出ていない。
何も出ていないというのはつまり、常にHigh-Zになっている。
出力していない時の信号が不定でHigh-Zにするのはまだ分かるとして、[furrtek]によれば何か信号を入れたら00が返ってくるように書かれているのだが。
線にLEDをつないでのデバッグではこのくらいが限度だ。
仕方ない。重い腰を上げオシロスコープを引っ張り出してきて(先日のPCのクラッシュで制御ソフトが消えていたのでインストールもして)確認してみる。

想定どおりである。(電源の都合上この時は電圧が違うが本番は5Vで動かしている)
ポケモンカードGBから印刷して実機の信号も見てみる。

こちらも想定どおりである。困った。
実機と違うところといえば、以下2点。
速度は[furrtek]には「1kHz以下(もっといけるかも)」くらいに書いてあったので、1kHz以下でプログラムが簡単なところとして約650Hz。
実機の速度は約8kHzだが、ふつう同期シリアル信号というものは遅い分には問題が無いものだ。
怪しいのは電源投入時だ。線をつないでから
プリンタ電源ON→PIC電源ON→300msほど待つ→送信開始
というシーケンスで操作しているが、このPIC側電源投入時にクロック線が暴れて不正なデータが入っているのかもしれない。
しかし、先頭のマジックバイト8833というのはこの辺の同期ズレをリセットするためのものではないのか?
つまり、電源投入時から(バイト単位でなく)ビット単位で信号を見て「1000100000110011」のパターンがあったらそこで同期するという仕組みではないのか。
まあ他に打つ手も無いのでこの2つを修正してみることにした。
通信速度は8kHz弱に。念のため上の画像でも見える1バイトごとに半bit分くらい止まる部分も実機に合わせた。
PIC電源投入時の待ち時間を5秒ほどに伸ばし、電源投入シーケンスを
PIC電源ON→(5秒の間に)プリンタ電源ON→送信開始
とした。
すると、

成功!
どちらが効いたのか片方づつ試すと、なんと両方とも必要だった。
650Hzの速度では、全く反応しない。
電源投入時のシーケンスを最初のものに戻すと、まれに成功するもののほとんど反応しない。(20回ほど試し2回成功)
再び両方修正後のコードで何度か試すと、ほぼ確実に成功する。(失敗は接触不良か?)
これは不思議である。
遅い信号を弾くのはノイズを信号と判断しないためではないのだろうか。そうであれば300ms空けたところでリセットしていてほしい。
一方そうでないなら、挿抜時のノイズを無視するためにマジックバイトがあるのではないのだろうか。そうであれば挿抜時にどんなノイズが乗っても正しく信号を受けてほしい。
何か間違っているかもしれないが、おそらく動くようにはなった。
もっとちゃんとした画像表示もそのうちやりたい。
[余談]
・折角買った感熱紙だし切った。

…55分掛かった。間に1時間の休憩を入れて。
学研のふろくじゃなくまともなノコギリを持っておいたほうがいいな。
・画像データは以下のようにして作った。
GIMPで描く

ペイントで8×8ドットごとに並び替える(背景色を変えるとやりやすい)

上下反転・色反転、GIMPでトーンカーブを使って上位bitと下位bitのデータを作る

モノクロビットマップで保存
バイナリエディタで開く
テキストエディタで適宜置換
・何度か試したの図↓

コード:
list p=16F1508
#include p16F1508.inc
radix dec
; Vdd +-v-+ Vss
; RA5 | | RA0/DAT
; RA4 | | RA1/CLK 4_1
;1_0 (RA3)| | RA2 CLC1
; RC5 | | RC0 CLC2
;21/4 RC4 | | RC1
;2_0 RC3 | | RC2
;3_1 RC6 | | RB4 3_0
;1_1 RC7 | | RB5 4_0
;CLC3 RB7 +---+ RB6
__CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF
__CONFIG _CONFIG2, _WRT_OFF & _STVREN_OFF & _BORV_LO & _LPBOR_OFF & _LVP_OFF
#define DATPIN 4
#define CLKPIN 5
cblock 0x20
endc
cblock 0x70
cntl, cnth, suml, sumh
dat, bitcnt, waitcnt, portbuf
; temp, temp2
endc
org 0
goto init
org 4
init:
banksel OSCCON
movlw b'01101010'
; ^^^^ || IRCF 1101=4MHz (※クロックを4MHzにすると1命令1μsで計算しやすい)
; ^^ SCS SystemClockSelect 1x = internal
movwf OSCCON
banksel 0
clrf PORTC
clrf PORTB
movlw b'00110000'
movwf PORTA
banksel TRISC
clrf TRISA
clrf TRISB
clrf TRISC
banksel ANSELC
clrf ANSELC
clrf ANSELB
clrf ANSELA
banksel 0
clrf cntl
clrf cnth
clrf suml
clrf sumh
clrf dat
clrf bitcnt
clrf waitcnt
clrf portbuf
;少し待つ
;//65536*5=327 680
;11*65536*7=5046272
movlw .11
movwf waitcnt
decfsz cntl,F
goto $+2
decfsz cnth,F
goto $+2
decfsz waitcnt,F
goto $-5
;init
movlw .4
movwf cntl
movlw .1 ;プログラムの都合で+1
movwf cnth
movlw high(cmd_init)
movwf FSR0H
movlw low(cmd_init)
movwf FSR0L
call sendcmd
;データ
movlw 0x84
movwf cntl
movlw 0x02+1
movwf cnth
movlw high(cmd_data)
movwf FSR0H
movlw low(cmd_data)
movwf FSR0L
call sendcmd
;空データ
movlw .4
movwf cntl
movlw .1
movwf cnth
movlw high(cmd_data0)
movwf FSR0H
movlw low(cmd_data0)
movwf FSR0L
call sendcmd
movlw .8
movwf cntl
movlw .1
movwf cnth
movlw high(cmd_print)
movwf FSR0H
movlw low(cmd_print)
movwf FSR0L
call sendcmd
goto $ ;終了
;コマンド送信
;具体的には、「88,33,本体,チェックサム,ダミー×2」を送信
sendcmd:
clrf suml
clrf sumh
;magic byte 88,33
movlw 0x88
call sendbyte
movlw 0x33
call sendbyte
;本体
sendcmdloop:
moviw FSR0++
addwf suml,F
btfsc STATUS,C
incf sumh,F
call sendbyte
decfsz cntl,F
goto $+2
decfsz cnth,F
goto sendcmdloop
;チェックサム
movf suml,W
call sendbyte
movf sumh,W
call sendbyte
movlw 0
call sendbyte
movlw 0
call sendbyte
return
;1バイト送信
;相手はクロック立ち上がりで読む
sendbyte:
movwf dat
movlw .18 ;タイミングを現物合わせ
movwf waitcnt
decfsz waitcnt,F
goto $-1
movlw .8
movwf bitcnt
clrf waitcnt
;DAT,CLKをlowにした値を用意
movf PORTA,W
andlw ~((1<<DATPIN)|(1<<CLKPIN)) ;b'11001111'
movwf portbuf
sendbyteloop:
movlw .18 ;タイミングを現物合わせ
movwf waitcnt
decfsz waitcnt,F
goto $-1
movf portbuf,W ;読み込み
rlf dat,F
btfsc STATUS,C
iorlw 1<<DATPIN ;データが1ならDATをhigh
movwf PORTA ;書き込み
movlw .20 ;タイミングを現物合わせ
movwf waitcnt
decfsz waitcnt,F
goto $-1
bsf LATA,CLKPIN ;クロック操作
decfsz bitcnt,F
goto sendbyteloop
return
; magic 8833
; cmd comp len L/H sum L/H dummy
cmd_init:
;dt 0x88, 0x33,
dt 0x01, 0x00, 0x00, 0x00; 0x01, 0x00, 0x00, 0x00
cmd_init_len equ $-cmd_init ;やっぱりめんどいので直書きにする
cmd_print:
dt 0x02, 0x00, 0x04, 0x00
dt 0x01, 0x00, 0xE4, 0x40
; ???? margin palt exposure
cmd_status:
dt 0x0F, 0x00, 0x00, 0x00
cmd_data0:
dt 0x04, 0x00, 0x00, 0x00
cmd_data:
dt 0x04, 0x00, 0x80, 0x02
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x01,0x03,0x03,0x03,0x07,0x07,0x07
dt 0x28,0x70,0xFC,0x78,0xFC,0xFE,0xFC,0xFE,0xFC,0xFE,0xFC,0xFE,0xFE,0xFC,0xF8,0xFC
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x3E,0x7C,0x7F,0xFE,0x7F,0x7F
dt 0x00,0x00,0x03,0x00,0x2F,0x1F,0x1F,0x3F,0x1F,0x3F,0x1F,0x1F,0x0F,0x1F,0x07,0x0F
dt 0x03,0x00,0x87,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xEC,0xF0
dt 0x1E,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0x7E,0x7E,0x7E
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x01,0x7F,0x3F,0x7F,0x3F
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x0F,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF
dt 0x03,0x01,0x07,0x0F,0x0F,0x0F,0x8F,0x47,0xCF,0xE7,0xC7,0xE7,0xE7,0xE3,0xE3,0xC3
dt 0xDF,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xF8,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0
dt 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,0x0F,0x1F,0x0F,0x1F,0x0F,0x1F,0x1F
dt 0xC0,0xE0,0xC0,0xE0,0xC0,0xE0,0xE0,0xC0,0xE0,0xC0,0xCE,0xC1,0xFF,0xDF,0x9F,0xDF
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x1F,0xFF,0xFF,0xFF,0xFF,0xFF
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0xF8,0xF8,0xF0,0xF8,0xF0,0xF0,0xF0
dt 0x01,0x03,0x01,0x03,0x01,0x03,0x01,0x7B,0x79,0x7B,0x79,0x7B,0x79,0x7B,0x79,0x7B
dt 0xF8,0xF8,0xF8,0xF8,0xF0,0xF8,0xF8,0xF0,0xF8,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0
dt 0x03,0x01,0x03,0x01,0x0B,0x01,0x11,0x09,0x39,0x19,0x18,0x39,0x78,0x39,0x39,0x78
dt 0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFF,0xFF
dt 0x00,0x01,0x00,0x01,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x0F,0xFF,0xFF,0xFF
dt 0xE7,0xEF,0xE7,0xEF,0xEF,0xE7,0xEF,0xE7,0x00,0x00,0x00,0x00,0x80,0x00,0x80,0x80
dt 0x07,0x0F,0x0F,0x0F,0x0F,0x1F,0x3F,0x1F,0x3F,0x3F,0x3F,0x7F,0xFF,0x7F,0x7F,0xFF
dt 0xFC,0xF8,0xFC,0xF8,0xF0,0xF8,0xF0,0xF8,0xF8,0xF0,0xE0,0xF0,0xE0,0xE0,0xC0,0xE0
dt 0x3F,0x7F,0x7F,0x3F,0x3F,0x3F,0x3F,0x1F,0x0F,0x1F,0x0F,0x0F,0x07,0x0F,0x07,0x07
dt 0x07,0x8F,0xC7,0x87,0xA7,0xC3,0xE3,0xC3,0xE1,0xE3,0xF1,0xF1,0xF9,0xF0,0xF0,0xF8
dt 0xF0,0xE0,0xE0,0xF0,0xF0,0xF0,0xF8,0xF1,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xF8,0x70
dt 0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x7C,0xFE,0xFC,0xFE,0xFC,0xFC,0x60,0x80,0x00,0x00
dt 0x3F,0x3F,0x1F,0x3F,0x3F,0x1F,0x14,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0xFF,0xFF,0xFC,0xFF,0xE8,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0xC1,0xC3,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0x80,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x01,0x0F,0x07,0x1F,0x1F
dt 0x3F,0x1F,0x1F,0x3F,0x3F,0x7F,0xFE,0x7F,0xFE,0xFE,0xFE,0xFC,0xFA,0xFC,0xFC,0xF8
dt 0xDF,0x8F,0x07,0x8F,0x8F,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0xFF,0xFF,0xFB,0xFC,0x90,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0xE0,0xF0,0x01,0x00,0x01,0x01,0x01,0x03,0x03,0x07,0x0F,0x07,0x0F,0x3F,0x7F,0x7F
dt 0x79,0xFB,0xF1,0xFB,0xF1,0xFB,0xF9,0xF3,0xF9,0xF3,0xE1,0xF3,0xF1,0xE3,0xE1,0xE3
dt 0xE0,0xF0,0xE1,0xF0,0xE3,0xF1,0xE7,0xF3,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
dt 0x79,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF0,0xC0,0xE0,0x40,0x80
dt 0xFF,0xFF,0xFF,0xFF,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xF8,0xFC,0x78,0xFC
dt 0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x03,0x07,0x02,0x03,0x00,0x00,0x00,0x00,0x00,0x00
dt 0x80,0x80,0x00,0x80,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x55,0xAA ;※末尾の55AAはデバッグ時に使ったものの消し忘れ
end
(2019/09/03 修正) コード中のシフト演算子がHTMLタグ扱いで消えていたのを修正しました。
PIC16のDhrystone MIPSを測ろうとしてみた
I2C液晶が動かない理由が分かった
ゲームボーイの吸い出し機を作った (後編)
ARMマイコンはじめました。
SDカードから1セクタ読み取るまでの手順解説
初代PIC解説
I2C液晶が動かない理由が分かった
ゲームボーイの吸い出し機を作った (後編)
ARMマイコンはじめました。
SDカードから1セクタ読み取るまでの手順解説
初代PIC解説
この記事へのコメント
なるほど、一般的な感熱紙プリンタにつなげるように変換する機械ですか。
面白い情報をありがとうございます。
面白い情報をありがとうございます。
Posted by いかづちSqueak
at 2021年01月02日 22:53

通信ケーブルのブレイクアウト基板を見つけました。
研究が捗りそうですね。
ttps://www.tindie.com/products/vaguilar/gameboy-coloradvancesp-link-cable-breakout-board/
研究が捗りそうですね。
ttps://www.tindie.com/products/vaguilar/gameboy-coloradvancesp-link-cable-breakout-board/
Posted by daiki at 2021年10月22日 15:38
ブレイクアウト基板はありがたいですね。
(おや、上の「一般的な感熱紙プリンタにつなげるように変換する機械」の言及元のコメントが消えている…?)
(おや、上の「一般的な感熱紙プリンタにつなげるように変換する機械」の言及元のコメントが消えている…?)
Posted by いかづちSqueak
at 2023年03月20日 00:45
