SNSへはこちら

Intel 8051マイコンを使う(STCマイコン)(4) – バイナリ手書き

Lチカのバイナリを手書きしました。割り込みを使わないものと使うもので合計2種類の手書きです。このマイコンはリトルエンディアンなのでとても書きやすかったです。
例によって使ったコマンドは echoxxd です。

手書き時の注意

手書きを行った際にハマったことをお話します。とはいっても単純な構造なのであまりハマりどころは多くはありません。

ジャンプ命令は相対アドレスの扱いに注意

条件分岐時等、相対アドレスを利用することがあると思います。その際はプログラムカウンタを操作することになるのですが、相対値の値に注意しましょう。具体的には、PC は命令実行時には1つ先の命令を指しているので、それを考慮しなければなりません。例えば次の場合、相対値はいくつでしょうか??

0000: dec A
0001: jnz xxxx ; 0000に飛びたい
0003: ret

何も考えずに「命令実行時には PC は 0001 だから -1 すればいいでしょ」と思った人はハマります。今言ったように、この命令を実行している時はすでに次の命令を指しているのですから、答えは -3 です。8bit で2の補数を取ると 0xFDですね。
この相対値は l で始まらないジャンプ命令によくあります。アセンブラをつかう場合は何も気にしなくていいですが、バイナリを手書き(ハンドアセンブル)する際は要注意です。

補数は2の補数

これも高をくくって「どうせ昔だし1の補数でしょ」と思い込んでいたところハマりました。皆さんちゃんとビット反転をした後に1を足しましょう。

ディレイを用いたLチカ

それではまずアセンブリコードから。

.module main
.globl _start
.globl _ms_wait

.area HOME (CODE)
.area XSEG (XDATA)
.area PSEG (PAG,XDATA)
.area RSEG (ABS,DATA)

.area CSEG (ABS,CODE)

.org 0x0000
    ljmp _start

TCON = 0x88
TMOD = 0x89
TL0 = 0x8A
TH0 = 0x8C
P1 = 0x90


_ms_wait: ; param: A ==> wait for A * 100 msec
    orl TMOD, #0x02
    mov TL0, #(256 - 100)
    mov TH0, #(256 - 100)
    setb TCON.4

ms_wait_loop:
    lcall wait_100ms
    dec A
    jnz ms_wait_loop

    clr TCON.4
    ret

wait_1ms:
    push A
    mov A, #10
wait_1ms_loop:
    jnb TCON.5, .
    clr TCON.5
    dec A
    jnz wait_1ms_loop
    pop A
    ret

wait_100ms:
    push A
    mov A, #100
wait_100ms_loop:
    lcall wait_1ms
    dec A
    jnz wait_100ms_loop
    pop A
    ret

_start:
    mov A, #10
    lcall _ms_wait
    xrl P1, #1
    ljmp _start

ここからハンドアセンブルします。その結果をこちらに示します(PDF)
バイナリ生成は例によってシェル芸です。

echo 02 00 33 43 89 02 75 8A 9C 75 8C 9C D2 8C 12 00 26 14 70 FA C2 8C 22 C0 E0 74 0A 30 8D FD C2 8D 14 70 F8 D0 E0 22 C0 E0 74 64 12 00 17 14 70 FA D0 E0 22 74 0A 12 00 03 63 90 01 02 00 33 | xxd -r -p > delay.bin

割り込みを用いたLチカ

Timer0 を用いた割り込みです。

.module main
.globl _start

.area HOME (CODE)
.area XSEG (XDATA)
.area PSEG (PAG,XDATA)
.area RSEG (ABS,DATA)

.area CSEG (ABS,CODE)

.org 0x0000
    ljmp _start
.org 0x000B
    ljmp _timer0_isr

TCON = 0x88
TMOD = 0x89
TL0 = 0x8A
TH0 = 0x8C
P1 = 0x90
IE = 0xA8


_irq_init:
    setb IE.7
    setb IE.1
    orl TMOD, #0x02
    mov TL0, #(256 - 100)
    mov TH0, TL0
    setb TCON.4
    ret


_start:
    acall _irq_init
    mov A, #100
    mov r3, A
    ljmp .

_timer0_isr:
    dec A
    jnz _isr_end1
_isr_if_zero1:
    djnz r3, _isr_end2 ; decrement r3 and jump if not zero
_isr_if_zero2:
    mov r3, #100
    cpl P1.0 ; toggle LED
_isr_end2:
    mov A, #100
_isr_end1:
    reti

ちょっとコードが汚すぎますが、許してください。ハンドアセンブルの結果はこちら
そして、バイナリは以下のように生成されます。

echo 02 00 1E FF FF FF FF FF FF FF FF 02 00 26 D2 AF D2 A9 43 89 02 75 8A 9C 85 8A 8C D2 8C 22 11 0E 74 64 EB 02 00 23 14 70 08 DB 04 7B 64 B2 90 74 64 32 | xxd -r -p >irq.bin

以上です。これまでの経験から、比較的サクッとマイコンを攻略できて嬉しいです。しかもこれは 8bit マイコンなので、本当にバイナリ手書きが楽でいいです。Intel 8080 に比べてアキュミュレータに依存した命令が少なく、色々融通が効きました。