AVRアセンブリ日記-1-導入とLチカ

Arduinoを卒業しavr-gccで電子オルゴールを作って遊んでいたが、機能を盛ろうとしたところ処理性能の壁にぶちあたったのでアセンブリを始めることにした
# アセンブラの導入
アセンブラとはアセンブリ言語を機械語に翻訳するコンパイラのこと
avr-gccみたいなもん
avr-gccと同じくhexファイルを吐き出せればいつも通りavrdudeやmicronucleusで書き込むことができる

ところで今回のハードウェアはmicronucleusを導入したATTiny85である
詳しくはこちら

https://qiita.com/McbeEringi/items/1f49840dca741acd2516

## Microchip Studio
WindowsではMicrochip純正のIDEがあるようでこれを使うのが一番という話ではある
試しにインストールしたところVisualStudioやPICのコンパイラのインストールが始まった
流石に余分なものばかり入れられると困るしそもそも私はアンチIDE教信者なので、avr-gccと同じように純粋なコマンドラインツールを探すことにした
もちろんこの後徹底的にアンインストールした

## AVRA
https://github.com/Ro5bert/avra

Microchip Studioの次にメジャーなアセンブラ
Microchip Studioでのアセンブリに対応しているとのこと
Windows以外にもMac、Linuxにも対応している模様
ただし実行ファイルは配布されていないので自分でmakeする必要がある
Windows環境でのmakeにつまずいたので別記事にまとめた

https://qiita.com/McbeEringi/items/a4dc577cc163442c43f7

好みに合っていそうなのでひとまずこれを使う

# アセンブリ
アセンブリはコマンド本体(二―モック)とその引数を書き連ねることでプログラムを作成する
二―モックについては本体のデータシートとavr命令一覧を見ればだいたい理解できる
細かな仕様はavraがエラーを吐いてくれたので言われたら直す程度でも良いかも
どちらも調べればpdfで出てくる

ところでレジスタの操作が必須になるわけだがこれはC言語も同じなので詳しい説明は省略

例がないとどうにも説明し辛いのでとりあえず進む

## LEDの点灯
```asm
.include "tn85def.inc"

.cseg
	rjmp main

main:
	ldi r16,0b00000010
	out DDRB,r16
	ldi r16,0b00000010
	out PORTB,r16
```
命令は基本的に上から順に実行される

`.~~`は疑似命令と呼ばれるもので二―モックと違って直接機械語に翻訳されるものではない
C言語のマクロみたいな感覚

`~~:`はラベルと呼ばれるものでこれが書かれた位置のメモリの番地を表す
メモリの相対座標が必要な命令などに数字の代わりにラベル名を入れるとコンパイル時に置き換えてくれる
C言語の関数と違って飽くまでもプログラム上の場所のみを表していることに注意

`.include`はファイル読み込みの疑似命令
アセンブリではincludeの略で`.inc`が定番らしい
`tn85def.inc`はATTiny85のレジスタの番地などが書かれているようでこれはavraに付属している

`.cseg`はプログラム領域の開始の疑似命令
いまいちよく分かっていないがこれは書かなきゃいけないようなので書いた
ここからプログラム本体を書き始める

`rjmp`は実行場所を指定した場所に移動する命令
`rjmp main`はmainラベルの場所まで移動する
つまりこのプログラムではまったく意味をなしていないが(ラベルは処理自体には影響しない)
これはmicronucleusが先頭に`rjmp`が見つからないとエラーを吐いたのでつけたもの
電源を投入すると0番地から実行されるが、割り込みを有効にすると実行が先頭にまとまった対応した番地に勝手に移動する
つまり先頭の数行は割り込みが飛んでくる可能性があるわけで、基本的にすべて`rjmp`にしておくのが普通ということだろう

`ldi`はレジスタへの指定した数(即値)の代入
`out`はIOレジスタへの代入
本当はoutiのようなIOレジスタに即値代入できる命令があれば嬉しいのだがそんなものはなかった
`r16`は16番汎用レジスタのこと
レジスタはCPU内にある「メモリ」より高速なメモリであり数値の計算や受け渡しに使う
このうち汎用レジスタはユーザーが勝手に使えるレジスタで今回使うATTiny85の場合8bit*32個用意されていて`r0`~`r31`の名前が振られている
このうち最後の6個はメモリの番地指定に特化した用途で最初の16個は即値の代入ができない
つまり何も考えずに使えるのは`r16`~`r25`の10個のみである
ということでIOレジスタに即値を代入する動作を実現するために`ldi`で`r16`に即値を代入して`out`で`r16`の値をIOレジスタに代入している

これでPB1に繋がっているLEDが点灯する

## Lチカ
Lチカには指定時間待つ動作が必要
Arduinoでは指定した時間だけ待つ関数が存在するが、アセンブリにそんな便利なものはない
方法としてはタイマー使う方法と使わない方法で大きく二通り、タイマーを使う方法では割り込みを使う方法と使わない方法で二通りありそうだが、今回はタイマーを使わない方法で行く

各命令はそれぞれ決まったクロックだけ時間を消費する
つまり上手く命令を組み合わせることで時間を消費することができる

アセンブリにも関数のような機能は存在するのでそれを使う
```
.include "tn85def.inc"
.cseg
rcall delay10us ;3
delay10us:;3+1+1+1+((1+2)*52-1)+4=165clk=10us@16.5MHz
    nop ;1
    nop ;1
    ldi  r20,52 ; 1
    _l:
        dec  r20 ;1
        brne _l ;!Z?2:1
    ret ;4
```
`;~~`はコメントで書いてある数字は消費するクロック数
`rcall`でサブルーチン(関数)に入って`ret`で戻る
`nop`は何もしないをする命令
`dec`はデクリメント
`brne`は不一致で条件分岐
ステータスレジスタという特殊なレジスタがあり、これは前の命令の結果を持っている
各ビットに真偽値で保持しているわけだが、ここにゼロフラグ(`Z`)というものがある
これは直前の命令の結果が0だったか否かを保持していて`brne`はここを見て分岐するか否かを判断する
つまり今回は`dec`を実行したときに結果が0でなければ戻るということである

これらの処理のクロック数を合計すると165クロックとなり、これは16.5MHzで10usに相当する

同じ要領でこれを繰り返せば任意の遅延が作れるが面倒なので

http://darcy.rsgc.on.ca/ACES/TEI4M/AVRdelay.html

を使ってLチカを作る
```
.include "tn85def.inc"

.cseg
	rjmp setup

setup:
	ldi r16,0b00000010
	out DDRB,r16

loop:
	ldi r16,0b00000010
	out PORTB,r16
	rcall delay500
	ldi r16,0b00000000
	out PORTB,r16
	rcall delay500
	rjmp loop

delay500:
	ldi r20,42
	ldi r21,219
	ldi r22,41
	_delay500: dec r22
		brne _delay500
		dec r21
		brne _delay500
		dec r20
		brne _delay500
		rjmp PC+1
		ret
```
# おしまい
このLチカをコンパイルすると42Bになります
無駄がないというのは気分がいいですね

続く？