たまりば

パソコン・インターネット パソコン・インターネット三鷹市 三鷹市

ポケットプリンタ制御
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」
なるほどその手があったか!
不要なレシートならそのへんに落ちている。

電池を用意し(多いなあ…)
電池6本

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

…動かない。
色々チェックし見つけたバグを修正したがやはり動かない。

出力は確かに出ている。クロック線とデータ線も間違っていない。
簡略化のためにプリンタからの応答を見なかったのはまずかったか…とも思ったがそれ以前にプリンタからのSO(Serial Out; 分かりやすくいえばMISO)線に何も出ていない。
何も出ていないというのはつまり、常にHigh-Zになっている。
出力していない時の信号が不定でHigh-Zにするのはまだ分かるとして、[furrtek]によれば何か信号を入れたら00が返ってくるように書かれているのだが。

線にLEDをつないでのデバッグではこのくらいが限度だ。
仕方ない。重い腰を上げオシロスコープを引っ張り出してきて(先日のPCのクラッシュで制御ソフトが消えていたのでインストールもして)確認してみる。
PICからポケットプリンタへの信号
想定どおりである。(電源の都合上この時は電圧が違うが本番は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のデータを作る
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

    ;print
    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タグ扱いで消えていたのを修正しました。

  • 同じカテゴリー(マイコン)の記事画像
    I2C液晶が動かない理由が分かった
    ゲームボーイの吸い出し機を作った (後編)
    ARMマイコンはじめました。
    SDカードから1セクタ読み取るまでの手順解説
    PICで浮動小数点ウェイトルーチン
    ベースラインPICの注意点
    同じカテゴリー(マイコン)の記事
     PIC16のDhrystone MIPSを測ろうとしてみた (2020-04-29 02:23)
     I2C液晶が動かない理由が分かった (2017-01-31 01:54)
     ゲームボーイの吸い出し機を作った (後編) (2017-01-16 22:44)
     ARMマイコンはじめました。 (2016-05-28 14:43)
     SDカードから1セクタ読み取るまでの手順解説 (2015-10-05 01:09)
     初代PIC解説 (2015-09-22 05:27)
    Post time : 2018年12月22日 17:40│Comments(3)マイコンゲーム機
    この記事へのコメント
    なるほど、一般的な感熱紙プリンタにつなげるように変換する機械ですか。
    面白い情報をありがとうございます。
    Posted by いかづちSqueakいかづちSqueak at 2021年01月02日 22:53
    通信ケーブルのブレイクアウト基板を見つけました。
    研究が捗りそうですね。
    ttps://www.tindie.com/products/vaguilar/gameboy-coloradvancesp-link-cable-breakout-board/
    Posted by daiki at 2021年10月22日 15:38
    ブレイクアウト基板はありがたいですね。

    (おや、上の「一般的な感熱紙プリンタにつなげるように変換する機械」の言及元のコメントが消えている…?)
    Posted by いかづちSqueakいかづちSqueak at 2023年03月20日 00:45
    URL欄を実験的に消してる間に廃止されてしまいました。まあいいか。
     
    <ご注意>
    書き込まれた内容は公開され、ブログの持ち主だけが削除できます。
    削除
    ポケットプリンタ制御
      コメント(3)