PHPプログラムで学ぶアセンブラのしくみ

第4回バイナリをマイコンにダウンロードするしくみを見てみよう

前回は、PICがサーバからバイナリをダウンロードし、自分自身を書き換えるプログラムを紹介しました。ネットワークアップデートに頼るのはいろいろ考えさせられなくもないですが、PHP版のアセンブラで出力したバイナリをPICに書き込むことができるようになりました。

今回は、このダウンローダの動作を詳しく見てみましょう。プログラムの全体は、前回圧縮して用意したアセンブラプログラム「download.asm」となります。

ダウンローダの仕組み

PICは、EEPROMから命令コードを直接読み出して実行しています。このため、メモリ書き換え中は命令コードが読み出せなくなり、実行も停止してしまうという問題があります。HTTPでは全データが一度に送られてきますので、書き込み中に実行が止まると、データを取り落してしまいます。もちろんLANモジュールとのあいだでフロー制御をおこなう方法はありますが、今回はソフトで逃げることにしました。

具体的には、HTTPリクエストを出して、書き込む必要がある部分だけをRAMに保存します。RAMは容量があまりありませんが、あふれた部分はとりあえず捨ててしまいます。そして、RAMのデータを使ってプログラムメモリに書き込みます。次に、もう一度HTTPリクエストを出します。先ほど書き込んだところは、コンペアによって飛ばされます。これを繰り返すことで、最後まで書き込むことができます。

では、全体の流れを見てみましょう。

最初に、LANモジュールからの信号線をチェックします。Hならスイッチが押されていないと判断して、ダウンロード済みのプログラムを実行します。Lが一定時間続いた場合は、ダウンロードモードに入ります。LANモジュールの準備ができるのを待って、HTTPリクエストを発行します。

リスト1 download.asmの一部(その1)
recvline
    call    recvrs
    movf    rxdata, 0
    xorlw    0x3a            ; ":"
    btfss    STATUS, Z
    goto    recvline
    
    clrf    sum
recvline_len
    call    recvhex
    movf    rxdata, 0
    movwf    len
    addlw    0xef
    
    movlw    0x10            ; error: len > 0x10
    btfsc    STATUS, C
    goto    disperror
....

HTTPレスポンスを解析し、バイナリ部分を取り出します。プログラム用のEEPROMは16ワード単位で消去しないといけないので、16ワード単位でRAMに取り込むようにします。

リスト2 download.asmの一部(その2)
setdatah
    btfsc    mode, MODE_FULL
    retlw    0
    
    movwf    INDF
    incf    FSR, 1
    
    bsf    STATUS, RP1
    xorwf    EEDATH & 0x7f, 0
    bcf    STATUS, RP1
    
    btfss    STATUS, Z
    bsf    mode, MODE_CHANGEBUF
    btfss    STATUS, Z
    bsf    mode, MODE_CHANGE
    
    bsf    STATUS, RP1
    incf    EEADR & 0x7f, 1
    bsf    STATUS, RP0
    bsf    EECON1 & 0x7f, EEPGD
    bsf    EECON1 & 0x7f, RD
    nop
    nop
    bcf    STATUS, RP0
    bcf    STATUS, RP1
    
    retlw    0

受信したバイナリと、すでにEEPROMにあるプログラムを比較します。一致していれば書き込みの必要はないので、飛ばします。ついでに、1行のチェックサムの計算も一緒にやってしまいます。もしチェックサムが合わなければ、エラーとします(ここではledにコードを表示しています⁠⁠。

今回はHTTPのリクエストでしたが、PICでHTTPリクエストを扱う方法については、『組込みプレスVol.19』に詳しい解説があります。
リスト3 download.asmの一部(その3)
writebuf
    movwf    FSR
    movf    INDF, 0
    movwf    addrh
    incf    FSR, 1
    movf    INDF, 0
    movwf    addrl
    incf    FSR, 1
    
    bcf    STATUS, C
    rrf    addrh, 0
    bsf    STATUS, RP1
    movwf    EEADRH & 0x7f
    bcf    STATUS, RP1
    rrf    addrl, 0
    bsf    STATUS, RP1
    movwf    EEADR & 0x7f
    bcf    STATUS, RP1
    
    movlw    0x10
    movwf    work0
writebuf0
    bsf    STATUS, RP1
    movf    INDF, 0
    movwf    EEDAT & 0x7f
    incf    FSR, 1
    movf    INDF, 0
    movwf    EEDATH & 0x7f
    incf    FSR, 1
    
    bsf    STATUS, RP0
    bsf    EECON1 & 0x7f, EEPGD
    bsf    EECON1 & 0x7f, WREN
    movlw    0x55
    movwf    EECON2 & 0x7f
    movlw    0xaa
    movwf    EECON2 & 0x7f
    bsf    EECON1 & 0x7f, WR
    nop
    nop
    bcf    EECON1 & 0x7f, WREN
    bcf    STATUS, RP0
    
    incf    EEADR & 0x7f, 1
    
    bcf    STATUS, RP1
    
    decfsz    work0, 1
    goto    writebuf0
    
    retlw    0

RAMがいっぱいになったら、書き込みに入ります。完了したら、再度HTTPリクエストを発行して、データの取り込みとコンペアをおこないます。全データのコンペアが成功したら、ダウンロードしたプログラムを実行します。

書き込みの方法は、PICの種類によって異なります。PIC16F88では、最初にイレースが必要で、一度に32ワードが消去されます。16F883では、最初の4ワードを書き込むと、16ワードがイレースされますので、続けて12ワードを書き込みます。16F886では、最初の8ワードを書き込むと、16ワードがイレースされますので、続けて8ワードを書き込みます。

説明はここまでなのですが、もう少し詳しく知りたいという読者の方もいらっしゃると思います。そこで、PICマイコンの内部の動作を、ロジックレベルで推測してみましょう。

CPUの中を読む

マイコンをはじめとして、コンピュータというのは電子回路を組み合わせたものです。極論すれば、たくさんのスイッチです。命令コードを2進数であらわした場合に、それぞれのビットがスイッチに対応します。そして、そのスイッチによって回路が切り替えられます。

PIC16Fシリーズの命令コードは14ビットですので、14個のスイッチがあります。このうち、上位(左側)の2つのスイッチにより、4つのタイプの命令にわかれます。

「10」のグループは、callとgotoです。2つしかありませんので、3番目のビットが0ならcall、1ならgotoです。そして残りの11ビットがジャンプ先のプログラムカウンタになります。

今度は、これをスイッチの視点から見てみましょう。下位11ビットはプログラムカウンタで、上位2ビットが「10」のときにプログラムカウンタに取り込まれます。このとき、3ビット目が0なら、今の命令の次のアドレスがスタックに積まれます。しかし、上位2ビットが「10」でなければ、プログラムカウンタには取り込まれません。たとえば、⁠sleep」の下位11ビットは0x63ですが、上位2ビットは「10」でないので、0x63のアドレスにジャンプすることはありません。

これだけだと、スイッチの視点でもあまり違いませんが、ではmovwfを見てみましょう。命令コードは0x0080で、下位7ビットがレジスタアドレスです。ところで、上位2ビットが「00」のグループを見ると、0x80のビットが1だとレジスタに、0だとWに書き込みになっています。movwfはこのビットが1なので、W→fになるわけです。では、このビットを0にしたらW→Wになるのでしょうか。0x0000を調べるとnopになっていますから、nopの命令は、実際にはW→Wとして実現されていると想像できます。レジスタは使われないので、下位7ビットは何でもいいわけですが、実際にはsleepやclrwdtやreturnはnopのバリエーションとして作られていることがわかります。ということは、この辺の命令コードに、公開されていない命令があるかも知れない、いうことがわがります。

筆者は基本的に、公開されている範囲でプログラムを作ります。しかし、こういう知識を持っていれば、ドキュメントの間違いを見つけるのに役立ったりします。興味があれば、みなさんもいろいろ調べてみてください。

おすすめ記事

記事・ニュース一覧