フタなしカンヅメ

徒然なるままに @happytar0

アセンブリでLEDを点灯させる【後編】

後編ということで、前回の続きをだらだらと書きたいと思う。
2つのLEDを交互に点灯させたい、ということなので一定間隔で点灯させるために、ディレイ処理を行う必要がある。
C言語であれば_delay_ms関数が使えるのでかなり簡単に実装できるが、アセンブリとなるとクロック周期をカウントしていく必要があるようだ。

指定秒数待機するルーチンを実装する

いろいろと参考サイトを見ていくと、下記のような実装が一番シンプルなようだ。
AVRŽŽ—p‹L-assembly

delay1s:
    ldi  r16, 100
    mov  r2,  r16
dly2:
    ldi  r16, 100
    mov  r1,  r16
dly1:
    ldi  r16, 200
    mov  r0,  r16
dly0:
    nop
    dec  r0
    brne dly0
    dec  r1
    brne dly1
    dec  r2
    brne dly2
    ret

最初にこれを見せられたらちんぷんかんぷんである。
まず上から順に実行されていくので、r2に100、r1に100、r0に200とレジスタにそれぞれの値が入っていく模様。

NOP命令「無操作」と書いてある。何もせずに1クロック消費するということだ。

DEC命令「汎用レジスタを減少」と書いてある。要はデクリメント(-1減算)ということみたいだ。

BRNE命令「不一致で分岐」と書いてある。0の時に実行されるラベルを指定すると、そこにジャンプするようだ。

RET命令「サブルーチンからの復帰」と書いてある。サブルーチンとして呼び出された場合に、呼び出し元にジャンプするようだ。

これでだいたい分かると思うが、r2で100ループ、r1で100ループ、r0で200ループと、子ルーチンが呼び出されていく。
nop、dec r0、で2クロック、brne dly0の呼び出しは、条件成立時は2、不成立時は1と変化するようだ。ほとんど成立して動作するので、2クロックとカウントしていいと思う。

4 * 100 * 100 * 200 = 8000000 = 8Mhz となる。

解説しているサイトによると、内側のループ処理に5クロックかかるので、

5 * 100 * 100 = 50000 = 50Khz となるようだ。1%未満の誤差ようなのでおよそ1秒となる。

また、r0〜r15までは、使える命令が限定されている。r16~r31については何でも使えるようで使い分けが重要みたいだ。
例えば、LDI命令はr16以上でしか使えないので、一度r16を経由させたあとにMOV命令でr0に値をコピーしている。

待機処理を入れてLEDを交互に点灯させる

先ほどの待機ルーチンを使ってみて以下のように書いてみた。

.include "tn2313def.inc"

main:
    ldi   r16, 0b00011000
    out   DDRD, r16
    ldi   r16, 0b00010000
    out   PORTD, r16
    rcall delay1s
    ldi   r16, 0b00001000
    out   PORTD, r16
    rcall delay1s
    rjmp  main

delay1s:
    ldi  r16, 100
    mov  r2,  r16
dly2:
    ldi  r16, 100
    mov  r1,  r16
dly1:
    ldi  r16, 200
    mov  r0,  r16
dly0:
    nop
    dec  r0
    brne dly0
    dec  r1
    brne dly1
    dec  r2
    brne dly2
    ret

RCALL命令「PC相対サブルーチン呼び出し」と書いてある。そのままの意味でサブルーチンの呼び出しである。
さっそくマイコンに転送してみたところ、非常にゆっくり点灯しているようだ…。
なぜ…という感じだが、クロック周波数がおかしいのかもしれない。

マイコンに設定されいてるクロック周波数を確認してみることにした。

マイコンのクロック周波数を確認する

「CKDIV8」というワードが重要なようだ。
調べてみると、CPUクロックの分周比を設定します、と書いてある。

マイコンに設定されているヒューズ情報を確認する必要があるので、以下のコマンドを実行してみた。

# 対話モードに入る
$ avrdude -c avrispmkii -P usb -p attiny2313 -t
$ read lfuse
0000   64
$ read hfuse
0000   df

これだけでは意味がさっぱりである。
ヒューズビットの意味を調べる必要があるので、以下のデータシートを確認してみた。
http://www.avr.jp/user/DS/PDF/tiny2313.pdf

lfuseは、ヒューズの下位ビット。hfuseは、上位ビットとなる。
「CKDIV8」が設定されているのは、下位ビットのほうになる。

ヒューズビットについては以下のサイトが参考になるようだ。
◆ヒューズビット

要は、7ビット目が「0」になっていると、クロックが1/8になってしまうらしい…なんてこったい。
初期設定では全てこのようになっているとのことだ。
設定されている値をビット値に変えてみると…

64 = 0110 0100

7ビット目が0である…そういうことか。こいつを1に書き換えてやる必要がある。

1110 0100 = e4

16進数でe4という値に書き換えることで、1/8動作を変更できる。
以下のコマンドを実行して書き換えてみたところ無事に正常な動作となった。
なお、ヒューズの書き換えは失敗すると、動作しなくなってしまう場合もあるようなので注意して欲しい。

$ avrdude -c avrispmkII -P usb -p t2313 -U lfuse:w:0xe4:m

あと、こんなサイトも見つけた。
マイコンの種類を選択するとWeb上で適切な値を表示してくれるようだ。便利そう。
Engbedded AVR Fuse Calculator

今回利用したコードもGitHubにアップしてみた。
pontago/avr-LedTest-asm · GitHub