2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

Arduinoを卒業しavr-gccで電子オルゴールを作って遊んでいたが、機能を盛ろうとしたところ処理性能の壁にぶちあたったのでアセンブリを始めることにした

アセンブラの導入

アセンブラとはアセンブリ言語を機械語に翻訳するコンパイラのこと
avr-gccみたいなもん
avr-gccと同じくhexファイルを吐き出せればいつも通りavrdudeやmicronucleusで書き込むことができる

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

Microchip Studio

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

AVRA

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

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

アセンブリ

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

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

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

LEDの点灯

.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レジスタに即値を代入する動作を実現するためにldir16に即値を代入してoutr16の値を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に相当する

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

を使って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になります
無駄がないというのは気分がいいですね

続く?

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?