h: cd H:\assemble_AVR\_tn2313\led1 E:\0_programs\AVRtools\AvrAssembler2\avrasm2.exe -fI led1.asm
1行目は、カレントドライブをアセンブルソースのあるH:に移します。
2行目は、カレントディレクトリをソースのあるフォルダに移します。H:以下はフォルダパスのコピペで書けます。
3行目は、アセンブルの実行です。-fI はインテルhexの指定です。最後はソース名をコピペします。
このように準備しておくとソースファイルのあるフォルダを小さく表示しておき、DOS窓を開いて 0.bat をドラッグアンドドロップとenterキーでアセンブル結果のhexファイルがソースのあるフォルダに生成されます。
通常使用中の画面です。
少しずつプログラムを書いて、実行して確認後に次の段階へ進むのが楽しみ方ですから、
プログラムの追加・修正 → アセンブル → 書き込み・実行
を繰り返すことになります。DOS窓を2つあけておけば、移動して F3キーだけで作業を続けることができます。(WINAVRでまったく同じ操作をしています。) なお、ソースエディタは自由ですから自分が読みやすいようにカスタマイズしたエディタを使用できます。
ハードウエアについて:
とりあえずATTiny2313を内蔵RC8MHzで使用することとして専用のブレッドボードを準備しました。現在はPB0〜PB7にLEDアレイ(330ΩでGNDへ)を接続しています。出力Hで点灯します。

あとは必要に応じて入力用のスイッチやセンサを増設する予定です。
↑
3 最初のプログラム
構想のアセンブルシステムが正常に動くかどうかをテストするために、単にLEDを2個点灯するだけのプログラムです。
結果として思い通りになっていました。
注)半角の<>は全角の<>で書いています。半角に戻す必要があります。
;*****************************************************************
; led1.asm 最も簡単なプログラム
;
;mcu=Tn23123 RC8MHz
;ハードウエアはPB0〜7にLEDアレイを接続。
;PB2とPB5のLED(Hで点灯)を点灯する。
;
;*****************************************************************
.INCLUDE <tn2313def.inc>
;リセット・ベクタの設定
.CSEG
rjmp reset ;RESET 割り込みベクタはresetだけ指定
;プログラム本体
reset:
; PORTBの設定
ldi R18, 0b11111111 ;LEDポートのみ出力ポートに設定
out DDRB, R18 ;DDRBに直接書けない
main:
ldi R18, 0 ;次行とセットでPORTBに 0x00を代入する
out PORTB, R18 ;デフォルトで0らしい 書かなくてもよい
sbi PORTB,1 ;PB1=H
sbi PORTB,5 ;PB5=H
rjmp main ;ラベルmainに戻ってループ
↑
4 アセンブラ覚え書き
.CSEG Codeセグメント=ここからコードが始まる。 DSEG=data ESEG=eeprom
def.incの参照 アセンブラを他のフォルダに置いて実行しても ちゃんと探してインクルードしている。不思議。
0b10101010の分割記述 0b1010_1010 や 0b\10_10_10_10のようにアンダースコアを挿入してもよい。
コメント 「;」 「//」 「/*…*/」 いずれも使えます。
サブルーチン rcallで呼び出し retで復帰 レジスタ退避に注意
スキップ命令 感覚的にはyesを先に書きたいのですが…
sbrs r4,0 ;スキップ命令 setされていたら
aaaa ;NOのとき(rjmp 又は 他の命令)
bbbb ;YESのとき(rjmp 又は 他の命令)
Tiny2313割り込みベクタテーブル
.org 0 rjmp reset ;各種リセット 下の reset:へ
.org 1 rjmp ext_int0 ;外部割り込み要求0
.org 2 rjmp ext_int1 ;外部割り込み要求1
.org 3 rjmp tim1_capt ;タイマ/カウンタ1捕獲(キャプチャ)発生
.org 4 rjmp tim1_compa ;タイマ/カウンタ1比較A一致
.org 5 rjmp tim1_ovf ;タイマ/カウンタ1オーバーフロー
.org 6 rjmp tim0_ovf ;タイマ/カウンタ0オーバーフロー
.org 7 rjmp usart_rxc ;USART受信完了
.org 8 rjmp usart_udre ;USART送信バッファ空き
.org 9 rjmp usart_tx ;USART送信完了
.org 10 rjmp ana_comp ;アナログ比較器出力遷移
.org 11 rjmp pcint ;ピン変化割り込み要求
.org 12 rjmp tim1_compb ;タイマ/カウンタ1比較B一致
.org 13 rjmp tim0_compa ;タイマ/カウンタ0比較A一致
.org 14 rjmp tim0_compb ;タイマ/カウンタ0比較B一致
.org 15 rjmp usi_strt ;USI開始条件検出
.org 16 rjmp usi_ovf ;USIカウンタオーバーフロー
.org 17 rjmp ee_rdy ;EEPROM操作可
.org 18 rjmp wdt_ovf ;ウォッチドッグ計時完了
タイマ0オーバーフローだけを使うときは(resetは必要)
.cseg
,org 0
rjmp reset ;各種リセット 下の reset:へ
.org 6
rjmp tim0_ovf ;タイマ/カウンタ0オーバーフロー
.org 20
と書けばよい
チャタリング対策
タクトスイッチのチャタリングを除去するために、スイッチをPD2にプルアップで繋ぎ、レジスタr3のbit1〜0で判定します。スイッチはポーリングで調べ、読み込み後は20msのディレイを取っています。これと0.1uF並列でチャタリングが防止できます。
;初期設定で
ldi r16, 0 ;PORTDは全bit入力設定
out DDRD, r16 ;↑
sbi PORTD, 2 ;PD2をプルアップ
clr r3 ;スイッチ判定レジスタ初期設定
;loop内のスイッチを調べる場所で
;スイッチチェック
rcall pin20ms ;swポーリングルーチン 内部使用は r0,r1,r2,r22(必要ならpush)
sbrc r3, 0 ;bit0=0で
rjmp foo ;処理しない 次のジャンプ先へ
sbrs r3, 1 ;bit1=1なら押されたから下へ
rjmp foo: ;処理しない 次のジャンプ先へ
(swが押されたときの処理を書く)
foo:
(次の処理)
;20ミリ秒待ちswチェックサブルーチン
pin20ms: ;////////// 20ミリ秒待ちswチェックサブルーチン //////////
;mov r2, r22 ;有効にするとr22から引数を得る r22=1の時20ms
clr r2 ;
inc r2 ;r2=1
lsl r3 ;左へシフトして
sbic PIND, 2 ;スイッチが押されたらskip
inc r3 ;押されていないとき bit10が10のとき押されたと判断
dly2: ;
ldi r22, 200 ;
mov r1, r22 ;
dly1: ;
ldi r22, 200 ;
mov r0, r22 ;
dly0: ;
nop ;1クロック
dec r0 ;1クロック
brne dly0 ;2クロック 合計4クロック
dec r1 ;
brne dly1 ;
dec r2 ;
brne dly2 ;
ret ;サブルーチンの終了=呼び出しルーチンへ復帰
10ms waitルーチン
r22に引数(1で10ms)を書いて呼び出す。r22は内部で変更される。r0,r1,r2を使用。
delay10ms: ;////////// 10ミリ秒サブルーチン //////////
mov r2, r22 ;r22から引数を得る r22=1の時20ms
dly2: ;
ldi r22, 100 ;
mov r1, r22 ;
dly1: ;
ldi r22, 200 ;
mov r0, r22 ;
dly0: ;
nop ;1クロック
dec r0 ;1クロック
brne dly0 ;2クロック 合計4クロック
dec r1 ;
brne dly1 ;
dec r2 ;
brne dly2 ;
ret ;サブルーチンの終了=呼び出しルーチンへ復帰
ROM領域に定数を置く
データはラベルを付けて,csegに置く。読み取りはZレジスタにアドレスを書いて、間接アドレスで読みとる。rレジスタの指定がなければr0に。
data:
.db 1,2,3,4,5,6,7,8,9,10
;読み取りルーチンで
ldi zl, low(data+data) ;zレジスタ初期設定
ldi zh, high(data+data) ;ROMのアドレスはバイトではなくワード単位である。
lpm r16, z+ ;r16に読み取り後インクリメントされる
アセンブルしたくない部分
#if 0 でアセンブルされない
#if 0 // code here is never included #endifSRAMに置くbyte型変数
.dseg
.org 0x60 ;SRAMはこの番地から
data:
.byte 2 ;byte型2個
読み取り
.cseg
ldi zl,low(data) ;
ldi zh,high(data) ;
ld r16,z ;z+,-zも使える
書き込み
ld zh,high(data) ;
ld zl,low(data) ;
st z, r16 ;メモリへはストア命令
↑
5 LED点滅プログラム
ポイント: サブルーチン(rcallで呼び出し retで復帰 レジスタ退避に注意)
ポートB5のLEDを2秒周期で点滅します。点灯→1秒待ち→消灯→1秒待ち を繰り返します。ポートのLEDはGNDに繋いでいますからHで点灯します。
1秒のウエイトルーチン(delay)はサブルーチンをコールします。1秒のサブルーチンは 1ms か 10ms の孫サブルーチンを使うのが適当だと思いますが、ここではサブルーチンの構造を簡単にするために一つにまとめています。
ウエイト(ディレー)はよく使われますが、MCUはひたすら時間の計算をしていますので同時に他の処理はできません。能率の悪い方法です。
割り込みテーブルを書いています。tiny2313はresetを含めて19種の割り込みがありますが、これらは0番地〜37番地(?)の決まった場所にアクセスするようです。割り込みを使うならその場所には勝手なプログラムは書けませんから予約の意味でテーブルを書いてあります。今回は使いません。したがって、すべての割り込み先はresetにしています。今後割り込みを使うときは該当する割り込みだけジャンプ先を書くことになります。使わない割り込みは rjmp reset でなくて reti(割り込みからの復帰)を書くべきかもしれません、要勉強です。
注)半角の<>は全角の<>で書いています。半角に戻す必要があります。
;*****************************************************************
; led2.asm LED点滅 im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続。
;PB2とPB5のLED(プルアップ、Hで点灯)を点灯する。
;1秒のディレーサブルーチンを作り、2秒周期で点滅する。
;
;*****************************************************************
.include <tn2313def.inc> ;<>は半角です
;リセット・ベクタの設定 全割り込みを列記するが使用しないものはresetラベルへ
.cseg
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
rjmp reset ;ext_int0 ;外部割り込み要求0
rjmp reset ;ext_int1 ;外部割り込み要求1
rjmp reset ;tim1_capt ;タイマ/カウンタ1捕獲(キャプチャ)発生
rjmp reset ;tim1_compa ;タイマ/カウンタ1比較A一致
rjmp reset ;tim1_ovf ;タイマ/カウンタ1オーバーフロー
rjmp reset ;tim0_ovf ;タイマ/カウンタ0オーバーフロー
rjmp reset ;usart_rxc ;USART受信完了
rjmp reset ;usart_udre ;USART送信バッファ空き
rjmp reset ;usart_tx ;USART送信完了
rjmp reset ;ana_comp ;アナログ比較器出力遷移
rjmp reset ;pcint ;ピン変化割り込み要求
rjmp reset ;tim1_compb ;タイマ/カウンタ1比較B一致
rjmp reset ;tim0_compa ;タイマ/カウンタ0比較A一致
rjmp reset ;tim0_compb ;タイマ/カウンタ0比較B一致
rjmp reset ;usi_strt ;USI開始条件検出
rjmp reset ;usi_ovf ;USIカウンタオーバーフロー
rjmp reset ;ee_rdy ;EEPROM操作可
rjmp reset ;wdt_ovf ;ウォッチドッグ計時完了
;プログラム本体
.org $100 ;100番地から次のプログラムが始まる(今は意味がない)
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
;以下、I/O初期化など
; PORTBの設定
ldi r18, 0b11111111 ;LEDポートのみ出力ポートに設定
out DDRB, r18 ;DDRBに直接書けない
main:
sbi PORTB,1 ;PB1=H
sbi PORTB,5 ;PB5=H点灯
rcall delay1s ;1秒待ち(サブルーチン呼び出し)
cbi PORTB,5 ;消灯
rcall delay1s ;1秒待ち(サブルーチン呼び出し)
rjmp main ;ラベルmainに戻ってループ
delay1s: ;1秒待ちサブルーチン
ldi r16, 100
mov r2, r16
dly2:
ldi r16, 100
mov r1, r16
dly1:
ldi r16, 200
mov r0, r16
dly0:
nop ;1クロック
dec r0 ;1クロック
brne dly0 ;2クロック 合計4クロック
dec r1 ;
brne dly1 ;
dec r2 ;
brne dly2 ;
ret ;サブルーチンの終了=呼び出しルーチンへ復帰
サブルーチンは3重のループになっています。内側のループは4クロックでできていて、ループ回数は内側が200回、中間が100回、外側が100回ですから、途中のループにかかる処理を無視すると、 4クロック*200*100*100=8000000クロックとなって、8MHzクロックから1秒かかることになります。尤も、内側のループを回すために5クロックかかり、これが100*100=10000回ありますから50000クロックは余分にかかります。1%未満ですが、およそ1秒ということにしておきます。
今回はサブルーチン内で使うレジスタはmainでは使っていないので退避はしていません。
↑
6 スイッチ入力の処理
ポイント:
●I/Oレジスタのビットを判定する(sbis PINx,2 ;I/Oポートのビットがセットならスキップ sbicもある)
●入力ポートのプルアップ(入力指定のPORTxに1を書くとプルアップされる)
PORTD2-GND間にタクトスイッチを接続して、onの時にPORTB2のLEDが点灯する回路です。他の7個のLEDは常時点灯しています。スイッチ入力の判定を調べるために作ったプログラムです。
;*****************************************************************
; led3.asm sw入力でLED制御 im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
; PD2(INT0)、PD3(INT1)にタクトswを接続(ソフトウエアプルアップ)
;
;PD2スイッチonでPB2のLED点灯(他のLEDは点きっぱなし)
;
;*****************************************************************
.include <tn2313def.inc> ;<>は半角です
;リセット・ベクタの設定 全割り込みを列記するが使用しないものはresetラベルへ
.cseg
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
rjmp reset ;ext_int0 ;外部割り込み要求0
rjmp reset ;ext_int1 ;外部割り込み要求1
rjmp reset ;tim1_capt ;タイマ/カウンタ1捕獲(キャプチャ)発生
rjmp reset ;tim1_compa ;タイマ/カウンタ1比較A一致
rjmp reset ;tim1_ovf ;タイマ/カウンタ1オーバーフロー
rjmp reset ;tim0_ovf ;タイマ/カウンタ0オーバーフロー
rjmp reset ;usart_rxc ;USART受信完了
rjmp reset ;usart_udre ;USART送信バッファ空き
rjmp reset ;usart_tx ;USART送信完了
rjmp reset ;ana_comp ;アナログ比較器出力遷移
rjmp reset ;pcint ;ピン変化割り込み要求
rjmp reset ;tim1_compb ;タイマ/カウンタ1比較B一致
rjmp reset ;tim0_compa ;タイマ/カウンタ0比較A一致
rjmp reset ;tim0_compb ;タイマ/カウンタ0比較B一致
rjmp reset ;usi_strt ;USI開始条件検出
rjmp reset ;usi_ovf ;USIカウンタオーバーフロー
rjmp reset ;ee_rdy ;EEPROM操作可
rjmp reset ;wdt_ovf ;ウォッチドッグ計時完了
;プログラム本体
.org $100 ;100番地から次のプログラムが始まる(今は意味がない)
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
;以下、I/O初期化など
; PORTBの設定
ldi r16, 0b11111111 ;LEDポートのみ出力ポートに設定 sed r16 も同じ
out DDRB, r16 ;DDRBに直接書けない
ldi r16, 0 ;clr r16 も同じ
out DDRD, r16 ;PORTDの全ビット入力設定
ldi r16, 0b0000_0100 ;
out PORTD, r16 ;PD2プルアップ 入力のPORTxに1を書くとプルアップ
main:
ldi r16, 0b1111_1011 ;
out PORTB, r16 ;PB2以外は点灯
loop:
sbis PIND, 2 ;I/Oポートのビットがセットならスキップ
rjmp led_on ;セットでない時
ldi r16, 0b1111_1011 ;セットの時
out PORTB, r16 ;PB2以外は点灯
rjmp loop ;
led_on: ;swがonのときのジャンプ先
ldi r16, 0xff ;
out PORTB, r16 ;
rjmp loop ;
この項ではスイッチのチャタリングは考慮していません。また、I/Oポートの処理が最適ではないかと思いますが今後の課題です。
↑
7 スイッチのトグル動作(ポーリングによる)
ポイント:
●ONとOFFを調べるだけではトグル動作はできません。数十msでスイッチを読みますから手押しでは何回も連続してONを読むことになります。前回の結果を記憶して、前回はOFFで、今回がONのときだけ押されたと判断します。いくつかのレジスタが必要です。
●チャタリング防止のため10ms以上の読み取り間隔を作りました。(10msのウエイトルーチンを入れています)
●sbrs r4, 1 ;r4のbit1がset(H)ならスキップ(skip bit register set?)でレジスタのbitを判断できます。
●sbrc(skip bit register clear?)もあります。全汎用レジスタに使えるようです。
●r0〜r15は clr r0 が使えます。0を代入できます。ただし、ser 0xff代入はできません。したがって、1を代入するときに clr r4 と inc r4 の2命令で書きました。
;*****************************************************************
; sw1.asm sw入力 トグル動作 im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
; PD2(INT0)、PD3(INT1)にタクトswを接続(ソフトウエアプルアップ)
;
;スイッチでLEDの連続点灯と点滅をトグルで切り換える
;PD2はポーリングでスイッチ入力を調べる チャタリング防止のため10ms待つ
;
;r0,r1,r2=ディレイルーチン
;r3=PD2スイッチフラッグ 10:押された
;
;スイッチonだけ調べると、長押し(10msくらい)でトグルを繰り返す
;前回のsw状態を記憶していて、前回が1で今回が0の場合だけ押されたと判断する
;長押しのときは前回が0で今回も0になるからトグル動作はしない
;
;*****************************************************************
.include <tn2313def.inc> ;<>は半角です
;リセット・ベクタの設定 全割り込みを列記するが使用しないものはresetラベルへ
.cseg
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
rjmp reset ;ext_int0 ;外部割り込み要求0
rjmp reset ;ext_int1 ;外部割り込み要求1
rjmp reset ;tim1_capt ;タイマ/カウンタ1捕獲(キャプチャ)発生
rjmp reset ;tim1_compa ;タイマ/カウンタ1比較A一致
rjmp reset ;tim1_ovf ;タイマ/カウンタ1オーバーフロー
rjmp reset ;tim0_ovf ;タイマ/カウンタ0オーバーフロー
rjmp reset ;usart_rxc ;USART受信完了
rjmp reset ;usart_udre ;USART送信バッファ空き
rjmp reset ;usart_tx ;USART送信完了
rjmp reset ;ana_comp ;アナログ比較器出力遷移
rjmp reset ;pcint ;ピン変化割り込み要求
rjmp reset ;tim1_compb ;タイマ/カウンタ1比較B一致
rjmp reset ;tim0_compa ;タイマ/カウンタ0比較A一致
rjmp reset ;tim0_compb ;タイマ/カウンタ0比較B一致
rjmp reset ;usi_strt ;USI開始条件検出
rjmp reset ;usi_ovf ;USIカウンタオーバーフロー
rjmp reset ;ee_rdy ;EEPROM操作可
rjmp reset ;wdt_ovf ;ウォッチドッグ計時完了
;プログラム本体
.org $100 ;100番地から次のプログラムが始まる(今は意味がない)
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
;以下、I/O初期化など
; PORTBの設定
ldi r16, 0b11111111 ;LEDポートのみ出力ポートに設定 sed r16 も同じ
out DDRB, r16 ;DDRBに直接書けない
ldi r16, 0 ;clr r16 も同じ
out DDRD, r16 ;PORTDの全ビット入力設定
ldi r16, 0b0000_0100 ;
out PORTD, r16 ;PD2プルアップ 入力のPORTxに1を書くとプルアップ
main:
ldi r16, 0b1111_1011 ;
out PORTB, r16 ;PB2以外は点灯
clr r3 ;フラッグ初期化
loop:
ldi r16, 1 ;チャタリング防止の10msルーチン
rcall delay10ms ;
;スイッチの判定
lsl r3 ;logical shift left
sbic PIND, 2 ;押されていたらスキップ skip bit I/O clear?
inc r3 ;押されたらINCされない
sbrs r3, 1 ;r3が0bxxxx_xx10のときswが押されたと判断 skip bit register set?
rjmp conti ;bit1が1でなければcontiへ跳ぶ
sbrc r3, 0 ;bit0が0でなければcontiへ跳ぶ
rjmp conti ;
inc r4 ;r4のbit0が反転するのでトグルになる
conti:
sbrc r4, 0 ;r4=0ならスキップ
rjmp led7654on_off ;
rjmp led7654on ;
led7654on: ;連続点灯
ldi r16, 0xff ;
out PORTB, r16 ;
clr r4 ;
rjmp loop ;
led7654on_off: ;上位4ビット交互点滅 0b0101_1111と0b1010_1111
ldi r16, 0x5f ;
out PORTB, r16 ;
ldi r16, 10 ;100msウエイト
rcall delay10ms ;
ldi r16, 0xaf ;
out PORTB, r16 ;
ldi r16, 10 ;100msウエイト
rcall delay10ms ;
clr r4 ;r4はイミディエイトを扱えないので
inc r4 ;clrとincでbit1をHにしている
rjmp loop ;
delay10ms: ;10ミリ秒待ちサブルーチン
mov r2, r16 ;r16から引数を得る r16=1の時10ms
dly2:
ldi r16, 100
mov r1, r16
dly1:
ldi r16, 200
mov r0, r16
dly0:
nop ;1クロック
dec r0 ;1クロック
brne dly0 ;2クロック 合計4クロック
dec r1 ;
brne dly1 ;
dec r2 ;
brne dly2 ;
ret ;サブルーチンの終了=呼び出しルーチンへ復帰
点滅の1周期(スイッチのディレイを含めて約0.3秒)の期間はスイッチを読みません。点滅動作中に瞬間的にスイッチを押すとほとんど読みとれません。やや長押しのスイッチングが必要です。
Cで同様のルーチンを書いたことがあるのですが、命令を確認しながら(探しながら)のプログラムにかなり苦労しました。試行錯誤の結果できたようなもので、最適なプログラムではないと思います。
勉強の一里塚として記録しておきます。次は割り込み処理で考えてみようと思いますが、割り込みの方が簡単ではないかと思います。
今回は使いませんでしたが、Cにおける変数をどのように data segment に置けばいいのか、どのようにアクセスするのかを今後は調べる必要があります。探しても以外にサンプルはないような感じです。
↑
8 スイッチのトグル動作(割り込みによる)
ポイント:
●INT1割り込みを使いました。MCUCR=0b0000_1000 (INT0なら 0b0000_0010)いずれも立ち下がりで割り込み。
GIMSK=0b1000_0000(INT0なら0b0100_0000)他にSREGで 割り込みの元締めを許可する sei が必要です。
割り込みベクタに跳んでくるので処理ルーチンのラベルへジャンプ処理します。
●割り込みルーチンではフラグr3を立てるだけで、分岐はメインループで行っています。
●割り込み許可を常に可としていたら、点滅のウエイトルーチンのあるところで受け付けると不可解な挙動をしましたのでウエイトループのないところだけで許可しています。
;*****************************************************************
; sw2.asm sw入力 トグル動作 im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
; PD2(INT0)、PD3(INT1)にタクトswを接続(ソフトウエアプルアップ)
;
;スイッチでLEDの連続点灯と点滅をトグルで切り換える
;PD3は割り込みでスイッチ入力を調べる。立ち下がり割り込み使用。
;
;r0,r1,r2=ディレイルーチン
;r3=PD2スイッチフラッグ 押されたら割り込みルーチン内で0x01にする。
;
;
;*****************************************************************
.include <tn2313def.inc> ;<>は半角です
;リセット・ベクタの設定 全割り込みを列記するが使用しないものはresetラベルへ
.cseg
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
rjmp reset ;ext_int0 ;外部割り込み要求0
rjmp ext_int1 ;ext_int1 ;外部割り込み要求1
rjmp reset ;tim1_capt ;タイマ/カウンタ1捕獲(キャプチャ)発生
rjmp reset ;tim1_compa ;タイマ/カウンタ1比較A一致
rjmp reset ;tim1_ovf ;タイマ/カウンタ1オーバーフロー
rjmp reset ;tim0_ovf ;タイマ/カウンタ0オーバーフロー
rjmp reset ;usart_rxc ;USART受信完了
rjmp reset ;usart_udre ;USART送信バッファ空き
rjmp reset ;usart_tx ;USART送信完了
rjmp reset ;ana_comp ;アナログ比較器出力遷移
rjmp reset ;pcint ;ピン変化割り込み要求
rjmp reset ;tim1_compb ;タイマ/カウンタ1比較B一致
rjmp reset ;tim0_compa ;タイマ/カウンタ0比較A一致
rjmp reset ;tim0_compb ;タイマ/カウンタ0比較B一致
rjmp reset ;usi_strt ;USI開始条件検出
rjmp reset ;usi_ovf ;USIカウンタオーバーフロー
rjmp reset ;ee_rdy ;EEPROM操作可
rjmp reset ;wdt_ovf ;ウォッチドッグ計時完了
;プログラム本体
.org $100 ;100番地から次のプログラムが始まる(今は意味がない)
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
;以下、I/O初期化など
; PORTBの設定
ldi r16, 0b11111111 ;LEDポートのみ出力ポートに設定 sed r16 も同じ
out DDRB, r16 ;DDRBに直接書けない
ldi r16, 0 ;clr r16 も同じ
out DDRD, r16 ;PORTDの全ビット入力設定
ldi r16, 0b0000_1100 ;
out PORTD, r16 ;PD2,3プルアップ 入力のPORTxに1を書くとプルアップ
ldi r16, 0b0000_1000 ;
out MCUCR, r16 ;mcu制御レジスタの設定 INT1立ち下がりを指定
ldi r16, 0b1000_0000 ;
out GIMSK, r16 ;一般割り込みマスクレジスタ(GeneralInterruptMaskRegister)設定 INT1許可
main:
clr r3 ;フラッグ初期化
loop:
sei ;
;スイッチの判定
sbrs r3, 0 ;r3のbit0がHならスキップする(割り込みがあったとき)skip bit register set?
rjmp conti ;bit1が1でなければcontiへ跳ぶ
inc r4 ;r4のbit0が反転するのでトグルになる
clr r3 ;フラッグを初期化する
conti:
cli ;
sbrc r4, 0 ;r4=0ならスキップ
rjmp led7654on_off ;
rjmp led7654on ;
led7654on: ;連続点灯
ldi r16, 0xff ;
out PORTB, r16 ;
clr r4 ;
rjmp loop ;
led7654on_off: ;上位4ビット交互点滅 0b0101_1111と0b1010_1111
ldi r16, 0x5f ;
out PORTB, r16 ;
ldi r16, 10 ;100msウエイト
rcall delay10ms ;
ldi r16, 0xaf ;
out PORTB, r16 ;
ldi r16, 10 ;100msウエイト
rcall delay10ms ;
clr r4 ;r4はイミディエイトを扱えないので
inc r4 ;clrとincでbit1をHにしている
rjmp loop ;
delay10ms: ;10ミリ秒待ちサブルーチン
mov r2, r16 ;r16から引数を得る r16=1の時10ms
dly2:
ldi r16, 100
mov r1, r16
dly1:
ldi r16, 200
mov r0, r16
dly0:
nop ;1クロック
dec r0 ;1クロック
brne dly0 ;2クロック 合計4クロック
dec r1 ;
brne dly1 ;
dec r2 ;
brne dly2 ;
ret ;サブルーチンの終了=呼び出しルーチンへ復帰
ext_int1: ;割り込みルーチン
inc r3 ;割り込みフラグH
reti ;割り込みから復帰
前回と同じでウエイトルーチンがありますので極短く押したときは読まれないときがあります。
コードサイズは偶然に前回とまったく同じでした。
スイッチoffのときにチャタリングがあるのか、時々不安定な挙動を示します。プルアップしているスイッチ入力端子とGND間に0.1μFを入れると簡単に解決しました。ソフトで解決するには前回と比べねばなりませんのでやっかいになりそうです(やがては必要でしょうが)。
Cのif文を書くのにずいぶん考え込みます。
↑
9 タイマカウンタ割り込みのLED点滅
ウエイト(ディレイ)ルーチンによる点滅でなく、タイマーを使って1秒ごとにLEDを点滅するプログラムを意図しました。
timer1(16bit)を使いますが、スタートと同時にタイマーが動き始めて、1秒経ったら割り込みが起こってLEDの点灯が変わるものです。「特定の操作から1秒間」にはなっていません。あなたまかせの1秒です。
一つのLEDの点滅では芸がないので1秒ごとにシフト命令でPORTB0からPORETB7のLEDへ点灯を移動させます。
プログラムの目的はtimer1のオーバーフローによる割り込みの実験です。
AVRの機能には多くのレジスタが関係しています。使用言語に関係なく、レジスタ機能をデータシートから紙に書き出してプログラムを書いてきましたが、そのメモは再利用できるものではなく、次回にはまたまたデータシートと格闘になります。今回は少しの改善策として、プログラム冒頭のコメント欄にデータシートから取り出したレジスタ情報を少ないながらも書いてみることにしました。
関係のレジスタであっても意図する用途では不要なものもありますが、それも不要と書いてメモすることにします。
ポイント:
●タイマ・カウンタ1に次のレジスタが関係します。使い方によっては設定不要のものもあります。
TCCR1A (Timer/Counter1ControlRegisterA)
TCCR1B (Timer/Counter1ControlRegisterB) タイマ/カウンタ1制御レジスタB
TCNT1H,TCNT1L(TCNT1)(Timer/Counter1) タイマ/カウンタ1
OCR1AH,OCR1AL(OCR1A) (Timer/Counter1OutputCompareRegisterA)タイマ/カウンタ1比較Aレジスタ
OCR1BH,OCR1BL(OCR1B) (Timer/Counter1OutputCompareRegisterB)タイマ/カウンタ1比較Bレジスタ
ICR1H,ICR1L(ICR1) (Timer/Counter1InputCaptureRegister)タイマ/カウンタ1捕獲(キャプチャ)レジスタ
TIMSK (Timer/CounterInterruptMaskRegister)タイマ/カウンタ割り込みマスクレジスタ
TIFR (Timer/CounterInterruptFlagRegister) タイマ/カウンタ割り込み要求フラグレジスタ
●16bitレジスタをアクセスするときは順序があります。書き込みは上位バイトから、読み取りは下位バイトからです。アクセス時は割り込みを禁止する必要があります。この例では sei の前の初期設定と、割り込みルーチン中で割り込みが禁止されているときに書き込んでいますから割り込み禁止を発行していません。
●タイマーはカウントアップして$0000になるときにオーバーフロー割り込みを発生しますから 65536-カウント数 をタイマー値として代入しておきます。もちろん、オーバーフローした直後に繰り返し設定する必要があります。
;*****************************************************************
; led_timer1.asm LEDをtimer1(16bit)で点滅する im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
;
;16ビットタイマで割り込みをかけて、LEDを点滅する。
;
;タイマ設定のためのレジスタ
; TCCR1A (Timer/Counter1ControlRegisterA)
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 標準動作 comp出力なし、波形関係なし。デフォルト
; TCCR1B (Timer/Counter1ControlRegisterB) タイマ/カウンタ1制御レジスタB
; 7:0 6:0 5:0 4:0 3:0 2:1 1:0 0:1 キャプチャ・波形関係なし 101=分周比1024
; TCNT1H,TCNT1L(TCNT1)(Timer/Counter1) タイマ/カウンタ1
; TCNT1H, TCNT1L に値をセットする。割り込み禁止にして、Hを先に書く。
; OCR1AH,OCR1AL(OCR1A) (Timer/Counter1OutputCompareRegisterA)タイマ/カウンタ1比較Aレジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 比較しないのですべて0。デフォルト
; OCR1BH,OCR1BL(OCR1B) (Timer/Counter1OutputCompareRegisterB)タイマ/カウンタ1比較Bレジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 比較しないのですべて0。デフォルト
; ICR1H,ICR1L(ICR1) (Timer/Counter1InputCaptureRegister)タイマ/カウンタ1捕獲(キャプチャ)レジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 キャプチャしないのですべて0。デフォルト
; TIMSK (Timer/CounterInterruptMaskRegister)タイマ/カウンタ割り込みマスクレジスタ
; 7:1 ovf可 6:0 比較A不 5:0比較B不 4:0捕獲不 3:0 2:0 1:0 0:0 1000_0000
; TIFR (Timer/CounterInterruptFlagRegister) タイマ/カウンタ割り込み要求フラグレジスタ
; 書かなくてよい
;
; 今回書く必要があるのは、
; TCCR1B=0b0000_0101
; TCNT1H=設定値 $e1 8MHz/1024=7812.5Hz 7812カウントで1秒 65536-7812=57724→0xe177
; TCNT1L=設定値 $77
; TIMSK =0b1000_0000
;
;16bitレジスタのアクセス順に注意:上(H)から書いて、下(L)から読む。
; アクセス時割り込み禁止 in r18,SREG/ cli/ .../ out SREG,r18/
;
;timer1のオーバーフローで割り込みをかけるから割り込みベクタの設定が必要
;
;r3:フラッグで使用
;r17:出力データ保持
;
;*****************************************************************
.include <n2313def.inc> ;<>は半角です
;リセット・ベクタの設定 全割り込みを列記するが使用しないものはresetラベルへ
.cseg
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
rjmp reset ;ext_int0 ;外部割り込み要求0
rjmp reset ;ext_int1 ;外部割り込み要求1
rjmp reset ;tim1_capt ;タイマ/カウンタ1捕獲(キャプチャ)発生
rjmp reset ;tim1_compa ;タイマ/カウンタ1比較A一致
rjmp tim1_ovf ;tim1_ovf ;タイマ/カウンタ1オーバーフロー
rjmp reset ;tim0_ovf ;タイマ/カウンタ0オーバーフロー
rjmp reset ;usart_rxc ;USART受信完了
rjmp reset ;usart_udre ;USART送信バッファ空き
rjmp reset ;usart_tx ;USART送信完了
rjmp reset ;ana_comp ;アナログ比較器出力遷移
rjmp reset ;pcint ;ピン変化割り込み要求
rjmp reset ;tim1_compb ;タイマ/カウンタ1比較B一致
rjmp reset ;tim0_compa ;タイマ/カウンタ0比較A一致
rjmp reset ;tim0_compb ;タイマ/カウンタ0比較B一致
rjmp reset ;usi_strt ;USI開始条件検出
rjmp reset ;usi_ovf ;USIカウンタオーバーフロー
rjmp reset ;ee_rdy ;EEPROM操作可
rjmp reset ;wdt_ovf ;ウォッチドッグ計時完了
;プログラム本体
.org $100 ;100番地から次のプログラムが始まる(今は意味がない)
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
;以下、I/O初期化など
; PORTBの設定
ldi r16, 0b11111111 ;LEDポートのみ出力ポートに設定 sed r16 も同じ
out DDRB, r16 ;DDRBに直接書けない
ldi r16, 0b0000_0101 ;
out TCCR1B, r16 ;クロック分周比1024
ldi r16, 0b1000_0000 ;
out TIMSK, r16 ;オーバーフローで割り込み許可
main:
ldi r16, $e1 ;timer1に 65536-7812=57724→0xe177 を設定 上位=e1 下位=77
out TCNT1H, r16 ;↑
ldi r16, $77 ;↑
out TCNT1L, r16 ;↑
clr r3 ;割り込み判定フラッグ初期化
sei ;割り込み許可
ldi r17,0b0000_0001 ;led点灯スイッチ
loop:
;割り込みの判定
sbrs r3, 0 ;r3のbit0がHならスキップする(割り込みがあったとき)skip bit register set?
rjmp loop ;割り込みがなければloop:へ戻る
clr r3 ;フラッグを倒して
out PORTB, r17 ;ledへ出力
lsl r17 ;左シフト
brne loop ;0でなければloop:へ
inc r17 ;0ならば1にして
rjmp loop ;loop:へ戻る
tim1_ovf: ;オーバーフロー割り込みルーチン
ldi r16, $e1 ;タイマー設定
out TCNT1H, r16 ;↑
ldi r16, $76 ;↑
out TCNT1L, r16 ;↑
inc r3 ;フラッグ立てる
reti ;
タイマー割り込みで周期的な動作をさせる実験でした。考えていたとおりの結果が得られましたが、LED点滅だけの目的ならディレイルーチンを使うものと違いはないでしょう。他の仕事、例えばデータの取り込みなどをしながらLEDの点滅が必要なときには使えると思います。
↑
10 LED点灯移動方向の切り換え 2つの割り込み
前回のLED点灯でスイッチを付加してトグル動作で点灯方向を切り換える動作を考えました。
点灯を移動するインターバルはタイマー割り込みで作り、さらにスイッチの読み込みはINT0の外部割り込みで行います。2つの割り込みを使うとどのように動くのかを実証したいと考えてこのプログラムをつくりました。なお、PORTD2(INT0)に付けたタクトスイッチにはチャタリング防止のためスイッチと並列に0.1μFを入れてあります。
入力スイッチではプルアップが必要です。これを忘れて割り込みが入らないのでしばらく悩みました。
;*****************************************************************
; led_timer1.asm LEDをtimer1(16bit)で点滅する その1 im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
; PD2(INT0)にタクトsw(GNDへ)
;
;16ビットタイマで割り込みをかけて、LEDを点滅する。(タイマ割り込みで点灯が移動する)
;タクトswを押すとトグルで点灯移動が逆方向になる。
; (割り込みルーチン内でフラグレジスタをincする。bit0はトグルとなる)
;
;タイマ設定のためのレジスタ
; TCCR1A (Timer/Counter1ControlRegisterA)
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 標準動作 comp出力なし、波形関係なし。デフォルト
; TCCR1B (Timer/Counter1ControlRegisterB) タイマ/カウンタ1制御レジスタB
; 7:0 6:0 5:0 4:0 3:0 2:1 1:0 0:1 キャプチャ・波形関係なし 101=分周比1024
; TCNT1H,TCNT1L(TCNT1)(Timer/Counter1) タイマ/カウンタ1
; TCNT1H, TCNT1L に値をセットする。割り込み禁止にして、Hを先に書く。
; OCR1AH,OCR1AL(OCR1A) (Timer/Counter1OutputCompareRegisterA)タイマ/カウンタ1比較Aレジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 比較しないのですべて0。デフォルト
; OCR1BH,OCR1BL(OCR1B) (Timer/Counter1OutputCompareRegisterB)タイマ/カウンタ1比較Bレジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 比較しないのですべて0。デフォルト
; ICR1H,ICR1L(ICR1) (Timer/Counter1InputCaptureRegister)タイマ/カウンタ1捕獲(キャプチャ)レジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 キャプチャしないのですべて0。デフォルト
; TIMSK (Timer/CounterInterruptMaskRegister)タイマ/カウンタ割り込みマスクレジスタ
; 7:1 ovf可 6:0 比較A不 5:0比較B不 4:0捕獲不 3:0 2:0 1:0 0:0 1000_0000
; TIFR (Timer/CounterInterruptFlagRegister) タイマ/カウンタ割り込み要求フラグレジスタ
; 書かなくてよい
;
; 今回書く必要があるのは、
; TCCR1B=0b0000_0101
; TCNT1H=設定値 $e1 8MHz/1024=7812.5Hz 7812カウントで1秒 65536-7812=57724→
; TCNT1L=設定値 $77 0.1秒なら $fcf3
; TIMSK =0b1000_0000
;
;INT0割り込み設定のためのレジスタ
; MCUCR (MCUControlRegister)MCU制御レジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:1 0:0 B10=10はINT0は立ち下がりで割り込み B32はINT1
; GIMSK (GeneralInterruptMaskRegister)一般割り込みマスクレジスタ
; 7:0 6:1 5:0 4:0 3:0 2:0 1:0 0:0 B6=1はINT0許可 B7はINT1
; EIFR (ExternalInterruptFlagRegister)外部割り込み要求フラグレジスタ
; 書かなくてよい
;
; 今回書く必要があるのは
; MCUCR=0b0000_0010
; GIMSK=0b0100_0000
;
;
;16bitレジスタのアクセス順に注意:上(H)から書いて、下(L)から読む。
; アクセス時割り込み禁止 in r18,SREG/ cli/ .../ out SREG,r18/
;
;timer1のオーバーフローで割り込みをかけるから割り込みベクタの設定が必要
;INT0 の外部割り込みで割り込みをかけるから割り込みベクタの設定が必要
;
;r3:タイマー割り込みフラッグで使用
;r4:外部sw割り込みで使用、移動方向トグルレジスタ
;r17:出力データ保持
;
;*****************************************************************
.include <tn2313def.inc> ;<>は半角です
;リセット・ベクタの設定 全割り込みを列記するが使用しないものはresetラベルへ
.cseg
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
rjmp ext_int0 ;ext_int0 ;外部割り込み要求0
rjmp reset ;ext_int1 ;外部割り込み要求1
rjmp reset ;tim1_capt ;タイマ/カウンタ1捕獲(キャプチャ)発生
rjmp reset ;tim1_compa ;タイマ/カウンタ1比較A一致
rjmp tim1_ovf ;tim1_ovf ;タイマ/カウンタ1オーバーフロー
rjmp reset ;tim0_ovf ;タイマ/カウンタ0オーバーフロー
rjmp reset ;usart_rxc ;USART受信完了
rjmp reset ;usart_udre ;USART送信バッファ空き
rjmp reset ;usart_tx ;USART送信完了
rjmp reset ;ana_comp ;アナログ比較器出力遷移
rjmp reset ;pcint ;ピン変化割り込み要求
rjmp reset ;tim1_compb ;タイマ/カウンタ1比較B一致
rjmp reset ;tim0_compa ;タイマ/カウンタ0比較A一致
rjmp reset ;tim0_compb ;タイマ/カウンタ0比較B一致
rjmp reset ;usi_strt ;USI開始条件検出
rjmp reset ;usi_ovf ;USIカウンタオーバーフロー
rjmp reset ;ee_rdy ;EEPROM操作可
rjmp reset ;wdt_ovf ;ウォッチドッグ計時完了
;プログラム本体
.org $100 ;100番地から次のプログラムが始まる(今は意味がない)
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
;以下、I/O初期化など
; PORTBの設定
ldi r16, 0b11111111 ;LEDポートのみ出力ポートに設定 sed r16 も同じ
out DDRB, r16 ;DDRBに直接書けない
ldi r16, 0 ;PORTDは全bit入力設定
out DDRD, r16 ;↑
sbi PORTD, 2 ;PD2をプルアップ
ldi r16, 0b0000_0101 ;クロック分周比1024
out TCCR1B, r16 ;↑
ldi r16, 0b1000_0000 ;オーバーフローで割り込み許可
out TIMSK, r16 ;↑
ldi r16, 0b0000_0010 ;INT0は立ち下がりで割り込み発生
out MCUCR, r16 ;↑
ldi r16, 0b0100_0000 ;INT0割り込み許可
out GIMSK, r16 ;↑
main:
ldi r16, $fc ;タイマ初期設定
out TCNT1H, r16 ;
ldi r16, $f3 ;
out TCNT1L, r16 ;timer1に $1e84=7812 を設定
clr r3 ;タイマ割り込み判定フラッグ初期化
clr r4 ;スイッチ割り込み判定フラッグ初期化
sei ;割り込み許可
ldi r17,0b0000_0001 ;led点灯スイッチ
loop:
;割り込みの判定
sbrs r3, 0 ;r3のbit0がHならスキップする(割り込みがあったとき)skip bit register set?
rjmp loop ;割り込みがなければloop:へ戻る
clr r3 ;フラッグを倒して=割り込みがあった
sbrs r4,0 ;bit0がH?
rjmp lefts ;Lならlefts:へ
rjmp rights ;Hならrights:へ
lefts: ;左へ移動
out PORTB, r17 ;ledへ出力
lsl r17 ;左シフト
brne loop ;0でなければloop:へ
inc r17 ;0ならば1にして
rjmp loop ;loop:へ戻る
rights: ;右へ移動
out PORTB, r17 ;ledへ出力
lsr r17 ;右シフト
brne loop ;0でなければloop:へ
ldi r17,0b1000_0000 ;0ならばbit7を1にして
rjmp loop ;loop:へ戻る
tim1_ovf: ;オーバーフロー割り込みルーチン
ldi r16, $fc ;タイマー設定
out TCNT1H, r16 ;↑
ldi r16, $f3 ;↑
out TCNT1L, r16 ;↑
inc r3 ;フラッグ立てる
reti ;
ext_int0: ;INT0割り込みルーチン
inc r4 ;移動方向レジスタトグル
reti ;
稼働してスイッチを押すと確かに左向きの流れから右向きの流れに変わります。気をよくして何回もスイッチを押していると向きが変わらないときがでてきます。ときには数回続けて無視される場合があります。どうやら2つの割り込みが競合しているようです。スイッチの割り込みは信号の立ち下がりで起こりますからスイッチを押した瞬間だけしか実現しません。この瞬間がタイマー割り込みの最中で割り込み禁止になっていると無視状態になるようです。
タイマー割り込み処理ルーチンに sei を書いてもやはり起こります。タイマー割り込みが起こってsei実行までの間にスイッチ信号が入ると起こるのだと思います。ある瞬間だけに発生する割り込みを2つ設定するとこのようになることが避けられないように思います。どのように解決しているのでしょうか。
とにかく考えた結果、立ち下がりの割り込みをやめてローレベル割り込みに変更します。このままだとスイッチを押している間中割り込みが連続して発生しますから複数回押したのと同じ状態になってしまいます。そこで、フラッグを用意して最初の割り込みはレジスタの処理incをしますが、2回目以降はなにもせずに割り込みを抜けます。押し続けのときは最初の1回だけが処理され、あとは空回りで割り込みルーチンから抜けることができません。スイッチを放すとメインルーチンに戻り割り込み処理フラグがクリアされます。
これでスイッチが無視されることなく方向反転ができますが、反対にスイッチを押している間はタイマー割り込みができませんので正確なタイマーが必要なときはこの方法も取れなくなります。スイッチをポーリングにする以外に方法は無いのでしょうか。尤もこれは使用言語によらず同じ問題になると思います。
<重大な訂正>後になって使用したタクトスイッチが正常な動作をしないことがあるのを発見しました。問題のスイッチを正常なものに取り変えると衝突も起きずに動作しました。2つの瞬間が同時に起こるのは確率的にずいぶん低いのではないかと思ってはいたのですが。しかし、せっかく作りましたからもう一つのプログラムも書いておきます。
;*****************************************************************
; led_timer1.asm LEDをtimer1(16bit)で点滅する その2 im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
; PD2(INT0)にタクトsw(GNDへ)
;
;16ビットタイマで割り込みをかけて、LEDを点滅する。(タイマ割り込みで点灯が移動する)
;タクトswを押すとトグルで点灯移動が逆方向になる。
; (割り込みルーチン内でフラグレジスタをincする。bit0はトグルとなる)
;
;タイマ設定のためのレジスタ
; TCCR1A (Timer/Counter1ControlRegisterA)
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 標準動作 comp出力なし、波形関係なし。デフォルト
; TCCR1B (Timer/Counter1ControlRegisterB) タイマ/カウンタ1制御レジスタB
; 7:0 6:0 5:0 4:0 3:0 2:1 1:0 0:1 キャプチャ・波形関係なし 101=分周比1024
; TCNT1H,TCNT1L(TCNT1)(Timer/Counter1) タイマ/カウンタ1
; TCNT1H, TCNT1L に値をセットする。割り込み禁止にして、Hを先に書く。
; OCR1AH,OCR1AL(OCR1A) (Timer/Counter1OutputCompareRegisterA)タイマ/カウンタ1比較Aレジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 比較しないのですべて0。デフォルト
; OCR1BH,OCR1BL(OCR1B) (Timer/Counter1OutputCompareRegisterB)タイマ/カウンタ1比較Bレジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 比較しないのですべて0。デフォルト
; ICR1H,ICR1L(ICR1) (Timer/Counter1InputCaptureRegister)タイマ/カウンタ1捕獲(キャプチャ)レジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 キャプチャしないのですべて0。デフォルト
; TIMSK (Timer/CounterInterruptMaskRegister)タイマ/カウンタ割り込みマスクレジスタ
; 7:1 ovf可 6:0 比較A不 5:0比較B不 4:0捕獲不 3:0 2:0 1:0 0:0 1000_0000
; TIFR (Timer/CounterInterruptFlagRegister) タイマ/カウンタ割り込み要求フラグレジスタ
; 書かなくてよい
;
; 今回書く必要があるのは、
; TCCR1B=0b0000_0101
; TCNT1H=設定値 $e1 8MHz/1024=7812.5Hz 7812カウントで1秒 65536-7812=57724→
; TCNT1L=設定値 $77 0.1秒なら $fcf3
; TIMSK =0b1000_0000
;
;INT0割り込み設定のためのレジスタ
; MCUCR (MCUControlRegister)MCU制御レジスタ
; 7:0 6:0 5:0 4:0 3:0 2:0 1:0 0:0 B10=00はINT0のローレベル割り込み B32はINT1
; GIMSK (GeneralInterruptMaskRegister)一般割り込みマスクレジスタ
; 7:0 6:1 5:0 4:0 3:0 2:0 1:0 0:0 B6=1はINT0許可 B7はINT1
; EIFR (ExternalInterruptFlagRegister)外部割り込み要求フラグレジスタ
; 書かなくてよい
;
; 今回書く必要があるのは
; MCUCR=0b0000_0000
; GIMSK=0b0100_0000
;
;
;16bitレジスタのアクセス順に注意:上(H)から書いて、下(L)から読む。
; アクセス時割り込み禁止 in r18,SREG/ cli/ .../ out SREG,r18/
;
;timer1のオーバーフローで割り込みをかけるから割り込みベクタの設定が必要
;INT0 のオーバーフローで割り込みをかけるから割り込みベクタの設定が必要
;
;r3:タイマー割り込みフラッグで使用
;r4:外部sw割り込みで使用、移動方向トグルレジスタ
;r17:出力データ保持
;
;*****************************************************************
.include <n2313def.inc> ;<>は半角です
;リセット・ベクタの設定 全割り込みを列記するが使用しないものはresetラベルへ
.cseg
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
rjmp ext_int0 ;ext_int0 ;外部割り込み要求0
rjmp reset ;ext_int1 ;外部割り込み要求1
rjmp reset ;tim1_capt ;タイマ/カウンタ1捕獲(キャプチャ)発生
rjmp reset ;tim1_compa ;タイマ/カウンタ1比較A一致
rjmp tim1_ovf ;tim1_ovf ;タイマ/カウンタ1オーバーフロー
rjmp reset ;tim0_ovf ;タイマ/カウンタ0オーバーフロー
rjmp reset ;usart_rxc ;USART受信完了
rjmp reset ;usart_udre ;USART送信バッファ空き
rjmp reset ;usart_tx ;USART送信完了
rjmp reset ;ana_comp ;アナログ比較器出力遷移
rjmp reset ;pcint ;ピン変化割り込み要求
rjmp reset ;tim1_compb ;タイマ/カウンタ1比較B一致
rjmp reset ;tim0_compa ;タイマ/カウンタ0比較A一致
rjmp reset ;tim0_compb ;タイマ/カウンタ0比較B一致
rjmp reset ;usi_strt ;USI開始条件検出
rjmp reset ;usi_ovf ;USIカウンタオーバーフロー
rjmp reset ;ee_rdy ;EEPROM操作可
rjmp reset ;wdt_ovf ;ウォッチドッグ計時完了
;プログラム本体
.org $100 ;100番地から次のプログラムが始まる(今は意味がない)
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
;以下、I/O初期化など
; PORTBの設定
ldi r16, 0b11111111 ;LEDポートのみ出力ポートに設定 sed r16 も同じ
out DDRB, r16 ;DDRBに直接書けない
ldi r16, 0 ;PORTDは全bit入力設定
out DDRD, r16 ;↑
sbi PORTD, 2 ;PD2をプルアップ
ldi r16, 0b0000_0101 ;クロック分周比1024
out TCCR1B, r16 ;↑
ldi r16, 0b1000_0000 ;オーバーフローで割り込み許可
out TIMSK, r16 ;↑
ldi r16, 0b0000_0000 ;INT0はローレベルで割り込み発生 Lレベル割り込み
out MCUCR, r16 ;↑
ldi r16, 0b0100_0000 ;INT0割り込み許可
out GIMSK, r16 ;
main:
ldi r16, $fc ;タイマ初期設定
out TCNT1H, r16 ;
ldi r16, $f3 ;
out TCNT1L, r16 ;timer1に $1e84=7812 を設定
clr r3 ;タイマ割り込み判定フラッグ初期化
clr r4 ;スイッチ割り込み判定フラッグ初期化
clr r5 ;割り込み重複禁止フラグ
sei ;割り込み許可
ldi r17,0b0000_0001 ;led点灯スイッチ
loop:
;割り込みの判定
sbrs r3, 0 ;r3のbit0がHならスキップする(割り込みがあったとき)skip bit register set?
rjmp loop ;割り込みがなければloop:へ戻る
clr r5 ;割り込み回数フラグを倒す
clr r3 ;フラッグを倒して=割り込みがあった
sbrs r4,0 ;bit0がH?
rjmp lefts ;Lならlefts:へ
rjmp rights ;Hならrights:へ
lefts: ;左へ移動
; out PORTB, r17 ;ledへ出力
lsl r17 ;左シフト
brne to_led ;0でなければled点灯へ
inc r17 ;0ならば1にして
rjmp to_led ;led点灯へ
rights: ;右へ移動
; out PORTB, r17 ;ledへ出力
lsr r17 ;右シフト
brne to_led ;0でなければled点灯へ
ldi r17,0b1000_0000 ;0ならばbit7を1にして
rjmp to_led ;led点灯へ
to_led:
out PORTB, r17 ;ledへ出力
rjmp loop ;
tim1_ovf: ;オーバーフロー割り込みルーチン
ldi r16, $fc ;タイマー設定
out TCNT1H, r16 ;↑
ldi r16, $f3 ;↑
out TCNT1L, r16 ;↑
inc r3 ;フラッグ立てる
reti ;
ext_int0: ;INT0割り込みルーチン
sbrc r5,0 ;r5のbit0がclearされていたらスキップ
reti ;r5のbit0がsetされていたら、2回目以降だから割り込み処理はなにもしない
inc r5 ;↑
inc r4 ;移動方向レジスタトグル
reti ;
点灯方向の切り替えは確実になりました。当然の事ながらスイッチを押している間はLEDは移動しません。INT0の割り込みルーチンを繰り返すために他のルーチンは停止してしまうからです。
(追記) 2つの割り込みを使ってもこのケースでは問題ないようです。常に「問題がない」と言い切れないのはCで書いた周波数カウンタがtimer0とtimer1が他の割り込み処理中に割り込みが起こって無視されるケースがあったからです。仮に無視されても重大なエラーにならない回路だと使えると思います。
↑
11 ROM領域に定数を置く
ROM領域(プログラム領域)にデータを置くことができます。今はまだbiteデータだけですが、そのデータの書き方と読み取り方を実験しました。
意図したとおりに書けているか、読み出せているかを判断するためにLEDを2進8桁表示器をして使っています。スイッチを押すと順次データを読み出してLEDに表示します。読み出しにカウンタを付けて、データを読み出しきると元にもどるようにしました。
;*****************************************************************
; 0db_read1.asm ROM域のバイトデータを読みLEDに表示する im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
; PD2(INT0)にタクトsw(GNDへ)
;
; ROM域に .db aa,bb,cc,dd…のバイトデータを置き、
; swで順に読みとってLEDにバイナリ表示する。
;
; ROM域に置いたバイト定数読み取りの実験用。
;
;
;16bitレジスタのアクセス順に注意:上(H)から書いて、下(L)から読む。
; アクセス時割り込み禁止 in r18,SREG/ cli/ .../ out SREG,r18/
;
;使用レジスタ
; z データアドレス設定用
; r16 汎用
; r17 表示数カウント用
; r22 ディレイルーチン設定用
; r0,r1,r2 ディレイルーチン用
; r3 スイッチ判定用
;
;データの読み出し方
; データ領域topのアドレスラベルを data: とするとき( .data 1,2,3,4,5,6,7,8)
; ldi zl, low(data+data) ;ROMのアドレスはバイトではなくワード単位である。
; ldi zh, high(data+data) ;
; lpm r16, z+ ;読み取り後インクリメントされる
;
;チャタリング対策:
; 20msのディレイルーチンの中でPINDで読み取りをしている。一度読むとあと20msは
; スイッチを読まないのでチャタリングをキャンセルできる。
; PINDを読むたびにr3レジスタに、押していないときはinc命令でbit0に1を書き、押し
; ていないときはlslでシフトと同時にbit0に0を書き込む。
; スイッチを押されたときはbit10が 10 になるからこれで判定する。
; なお、それでもチャタリングが稀に起こるからスイッチに0.1uFを抱かせている。
;
;
;*****************************************************************
.include "tn2313def.inc" ;半角の<>が本来です。
;割り込みベクタの設定
.cseg
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
;プログラム本体
.org 20 ;20番地から次のプログラムが始まる
data:
.db 1,2,3,4,5,6,7,8,9,10
.db 11,12,13,14,15,16,17,18,19,20 ;
.db 21,22,23,24,25,26,27,28,29,30 ;
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
;以下、I/O初期化など
; PORTBの設定
ldi r16, 0b11111111 ;LEDポートのみ出力ポートに設定 ser r16 も同じ
out DDRB, r16 ;DDRBに直接書けない
ldi r16, 0 ;PORTDは全bit入力設定
out DDRD, r16 ;↑
sbi PORTD, 2 ;PD2をプルアップ
main:
ldi zl, low(data+data) ;zレジスタ初期設定
ldi zh, high(data+data) ;ROMのアドレスはバイトではなくワード単位である。
ldi r17, 30 ;ループカウンタ初期設定
ser r16 ;$ff
clr r3 ;
loop:
out PORTB, r16 ;ledへ出力
;スイッチチェック
rcall pin20ms ;swポーリングルーチン r22は要push
sbrc r3, 0 ;bit0=0で
rjmp loop ;
sbrs r3, 1 ;bit1=1なら押されたから下へ
rjmp loop ;
;swが押されたときの処理
lpm r16, z+ ;読み取り後インクリメントされる
dec r17 ;読み出し数カウンタdec
brne cont2 ;カウンタ=0なら↓レジスタ再設定
ldi zl, low(data+data) ;zレジスタ初期設定
ldi zh, high(data+data) ;ROMのアドレスはバイトではなくワード単位である。
ldi r17, 30 ;ループカウンタ再設定
cont2:
rjmp loop ;
pin20ms: ;////////// 20ミリ秒待ちswチェックサブルーチン //////////
;mov r2, r22 ;有効にするとr22から引数を得る r22=1の時20ms
clr r2 ;
inc r2 ;r2=1
lsl r3 ;左へシフトして
sbic PIND, 2 ;スイッチが押されたらskip
inc r3 ;押されていないとき bit10が10のとき押されたと判断
dly2: ;
ldi r22, 200 ;
mov r1, r22 ;
dly1: ;
ldi r22, 200 ;
mov r0, r22 ;
dly0: ;
nop ;1クロック
dec r0 ;1クロック
brne dly0 ;2クロック 合計4クロック
dec r1 ;
brne dly1 ;
dec r2 ;
brne dly2 ;
ret ;サブルーチンの終了=呼び出しルーチンへ復帰
↑
12 PWM動作(LED点灯)
高速PWNといわれる動作で、8ビットカウンタは0〜255のカウントを繰り返しますが、このときでOCR0Aレジスタに設定した値(0〜255)と一致したときにOC0A端子(PB2端子)がLになり、カウントが255になるとこの端子がHになります。OCR0Aレジスタの数値が小さいときはLの期間が長くなり、デューティ比が小さい矩形波となります。
このレジスタの値を連続的に変えるとデューティ比が連続して変わる矩形波が生じますから、これでLEDを点灯すると明るさが連続的に変化することになります。
等比数列的に明るさを変えるためにROM域に定数を設定してその値でデューティ比を決めています。
;*************************************************************************************************************
; 01pwm1.asm PWMのテスト PB0のLEDの明るさを周期的に変える im
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
; PD2(INT0)にタクトsw(GNDへ)
;
;高速PWNでは、関係レジスタをセットして、OCR0Aにデューティ値1〜255を書き込めば
;OC0A(PB2端子)に結果が出力される。
;
;人の目には明るさは等比数列的に見えるらしく、等差数列で変化するとデューティが小さいところ
;だけしか変化がわからない。 そこで .dbを使って等比数列的な数値列を記録して、その読み出しで
;デューティを変えている。一様な全範囲で明るさの変化が見える。
;
;
;高速PWMを使う。(WGM02〜0=011または111)
; カウンタはBOTTOMからTOPまで計数。TOPはWGM02〜0=011時に$FF、WGM02〜0=111時にOCR0A。
; 非反転比較出力動作(COM0x1〜0=10)では比較出力(OC0x)は TCNT0とOCR0xが一致したときクリア(0)、BOTTOMでセット(1)。
; 反転出力動作(COM0x1〜0=11)は一致したときセット(1)され、BOTTOMでクリア(0)。
;
;タイマ/カウンタ0制御レジスタA(Timer/Counter0ControlRegisterA)TCCR0A
; FCOM0A1 ECOM0A0 DCOM0B1 CCOM0B0 B - A - @WGM01 ◎WGM00
; 1 0 1 1
;タイマ/カウンタ0制御レジスタB(Timer/Counter0ControlRegisterB)TCCR0B
; FFOC0A EFOC0B D- C- BWGM02 ACS02 @CS01 ◎CS00
; 0 0 1 0 I/O分周 8
;タイマ/カウンタ0(Timer/Counter0)TCNT0
; F(MSB) E D C B A @ ◎(LSB)
;
;タイマ/カウンタ0比較Aレジスタ(Timer/Counter0OutputCompareARegister)OCR0A
; F(MSB) E D C B A @ ◎(LSB)
;
;タイマ/カウンタ0比較Bレジスタ(Timer/Counter0OutputCompareBRegister)OCR0B
; F(MSB) E D C B A @ ◎(LSB)
;
;タイマ/カウンタ割り込みマスクレジスタ(Timer/CounterInterruptMaskRegister)TIMSK
; FTOIE1 EOCIE1A DOCIE1B C- BICIE1 AOCIE0B @TOIE0 ◎OCIE0A
; 1 一致割り込みA許可
;タイマ/カウンタ割り込み要求フラグレジスタ(Timer/CounterInterruptFlagRegister)TIFR
; FTOV1 EOCF1A DOCF1B C- BICF1 AOCF0B @TOV0 ◎OCF0A
; 設定しない
;今回必要な設定は
; sei
; TCCR0A=0b1000_0011
; TCCR0B=0b0000_0010
; OCR0A =デューティ設定値
; TIMSK =0b0000_0001
;
;
;
;使用レジスタ
; z データアドレス設定用
; r16 汎用
; r17 .db読み取り回数カウント用
; r22 ディレイルーチン設定用
; r0,r1,r2 ディレイルーチン用
;
;***************************************************************************************************************
.include "tn2313def.inc" ;" "は半角の<>が本来なのですがhtmlのため""を使っています。
;===== 割り込みベクタの設定 ==========================================
.cseg
.org 0
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
.org 13
rjmp tim0_compa ;タイマ/カウンタ0比較A一致
;===== プログラム本体 =====================================================
.org 20 ;20番地から次のプログラムが始まる
data:
.db 1, 2, 4, 4, 5, 6, 8, 10, 12, 14
.db 17, 21, 25, 31, 37, 45, 55, 67, 81, 98
.db 119,145,177,214,255,255,214,177,145,119
.db 98, 81, 67, 55, 45, 37, 31, 25, 21, 17
.db 14, 12, 10, 8, 6, 5, 4, 4, 2, 1
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
ldi r16,0b1000_0011 ;TCCR0A=0b1000_0011
out TCCR0A,r16 ;
ldi r16,0b0000_0010 ;TCCR0B=0b0000_0010
out TCCR0B,r16 ;
ldi r16,128 ;OCR0A =デューティ設定値
out OCR0A,r16 ;必要ない。mainですぐに再設定
ldi r16,0b0000_0001 ;TIMSK =0b0000_0001
out TIMSK,r16 ;割り込みマスク
ldi r16, 0b11111111 ;PORTBの設定(出力)
out DDRB, r16 ;DDRBに直接書けない
main:
ldi r17,50 ;デューティデータ読み取り回数
sei ;全割り込み許可
ldi zl, low(data+data) ;zレジスタ初期設定
ldi zh, high(data+data) ;ROMのアドレスはバイトではなくワード単位である。
loop:
;main_loopではなにもしない。割り込みルーチンで。
rjmp loop ;
tim0_compa: ;タイマカウンタ比較割り込み
lpm r16, z+ ;読み取り後インクリメントされる
out OCR0A,r16 ;デューティ値を OCR0Aに設定
ldi r22,5 ;20msに設定 この値が光度変化の速さを決める
rcall delay10ms ;ウエイト呼び出し
;
dec r17 ;データ読み出しカウンタdec
breq restart ;0か?
reti ;0でなければ抜ける
restart: ;0のときはデータ読み出し再設定
ldi r17, 50 ;
ldi zl, low(data+data) ;zレジスタ初期設定
ldi zh, high(data+data) ;ROMのアドレスはバイトではなくワード単位である。
reti ;
delay10ms: ;////////// 10ミリ秒サブルーチン //////////
mov r2, r22 ;r22から引数を得る r22=1の時20ms
dly2: ;
ldi r22, 100 ;
mov r1, r22 ;
dly1: ;
ldi r22, 200 ;
mov r0, r22 ;
dly0: ;
nop ;1クロック
dec r0 ;1クロック
brne dly0 ;2クロック 合計4クロック
dec r1 ;
brne dly1 ;
dec r2 ;
brne dly2 ;
ret ;サブルーチンの終了=呼び出しルーチンへ復帰
↑
13 SRAMに置くbyte型変数
.dseg の 0x60番地以降はSRAM領域です。 ここに ラベル: .byte 個数 を書くとbyte型変数の領域が確保されます。初期化は行われません。プログラム中の書き出し(代入)はラベルの番地をzレジスタにldiして、レジスタからstでします。読み出し(参照)は同じくzレジスタにラベルの番地を入れて、レジスタに ld で行います。zには Z+ や -Z も使えます。
この実験プログラムでは2つの数値を2つの変数に代入し、その後、1番目の変数を読み出してLEDに表示して、1秒のインターバルで、標識の0xaaを表示、続いて2つ目の変数をLEDに表示するものです(表示は無限ループになっています)。
;*****************************************************************
; 02dseg1.asm SRAMにBYTE型変数を置くim
;
;目的:SRAM上の変数を定義して、読み書きを行う。
; 2byteのbyte型変数を置く
;
;mcu=Tn2313 RC 8MHz
;ハードウエアはPB0〜7にLEDアレイを接続(Hで点灯)
; PD2(INT0)にタクトsw(GNDへ)
;
;変数は次のように定義する(置く)。
; .dseg
; .org 0x60 ;SRAMはこの番地から
; data:
; .byte 2 ;byte型2個
; 読み取り
; .cseg
; ldi zl,low(data) ;
; ldi zh,high(data) ;
; ld r16,z ;z+,-zも使える
; 書き込み
; ld zh,high(data) ;
; ld zl,low(data) ;
; st z, r16 ;メモリへはストア命令
;
;実験プログラムは (data)に65を書き、(data+1)に127を書く。
;続いて、(data)を読み出してLEDに1秒間表示し、中間に$aaを表示した後に
;(data+1)を表示する。
;
;
;使用レジスタ
; z データアドレス設定用
; r16 汎用
; r22 ディレイルーチン設定用
; r0,r1,r2 ディレイルーチン用
;
;*****************************************************************
.include "tn2313def.inc" ;" "は半角の<>が本来なのですがhtmlのため""を使っています。
;===== 割り込みベクタの設定 ==========================================
.cseg
.org 0
rjmp reset ;各種リセット 下の reset: ラベルへジャンプする
;===== プログラム本体 =====================================================
.dseg
.org 0x60
data:
.byte 2 ;ラベルdataにbyte型変数を2個置く
.cseg
.org 20 ;20番地から次のプログラムが始まる
reset:
ldi r16,low(ramend) ;RAM最終アドレス下位を取得
out spl,r16 ;スタックポインタ(下位)を初期化
ldi r16, 0b11111111 ;PORTBの設定(出力)
out DDRB, r16 ;DDRBに直接書けない
main:
ldi r16, 67 ;
;書き込み
ldi zh,high(data) ;変数の番地を2回に分けて
ldi zl,low(data) ;zレジスタに読む
st z, r16 ;zレジスタ間接でr16を書き出す
ldi r16, 127 ;
;書き込み
ldi zh,high(data+1) ;変数の2番目を指定
ldi zl,low(data+1) ;
st z, r16 ;
;読み取り
ldi zl,low(data) ;変数の番地を2回に分けて
ldi zh,high(data) ;zレジスタに読む
ld r16,z ;z間接指定で読みとる
out PORTB, r16 ;LEDに表示
ldi r22,100 ;
rcall delay10ms ;
ldi r16,0xaa ;
out PORTB, r16 ;
ldi r22,100 ;
rcall delay10ms ;
;読み取り
ldi zl,low(data+1) ;
ldi zh,high(data+1) ;
ld r16, z ;
out PORTB, r16 ;
ldi r22,100 ;
rcall delay10ms ;
rjmp main ;
delay10ms: ;////////// 10ミリ秒サブルーチン //////////
mov r2, r22 ;r22から引数を得る r22=1の時20ms
dly2: ;
ldi r22, 100 ;
mov r1, r22 ;
dly1: ;
ldi r22, 200 ;
mov r0, r22 ;
dly0: ;
nop ;1クロック
dec r0 ;1クロック
brne dly0 ;2クロック 合計4クロック
dec r1 ;
brne dly1 ;
dec r2 ;
brne dly2 ;
ret ;サブルーチンの終了=呼び出しルーチンへ復帰
↑
14 Cから呼び出す delay関数
1ms単位のdelay関数をアセンブラで書いてみました。AVRwiki と ChaNさんの記事を参考に書いてみたところうまく動きました。
まねをして書いてみたものですから、書き方におかしなところがあるかもしれません。
1MHzを基準にしていますから、12MHzクロックで 8ms 必要なときは delay_ms1(8*12) の様に12倍して呼び出します。引数は uint16_t ですから掛け算した後の数値が56635まで可能です。コンパイラの最適化には関係しないと思います。
Cのプログラムでは void delay_ms1(uint16_t); とプロトタイプ宣言しておく方がいいようです。
コンパイルは Cプログラムと同じディレクトリに置いて、makefileの ASRC = に delay_ms1.S と書くとmakeでアセンブルとリンクが行われます。
;******************************************************************************* ;delay_ms1.S クロック1MHzの時1msのdelayルーチン 2008.12.13 im ;呼び出しは delay_ms1(15*8); 8MHzクロックで15msのとき。引数はuint16_t。 ;コンパイル時にmakefileの ASRC =に delay_ms1.S と書く。 ;Cプログラムに void delay_ms1(uint16_t); と宣言する。なくても可。 ;******************************************************************************* //拡張子を.SにすればCプリプロセッサにも掛けられるので、 //C++方式のコメントが利用できる .global delay_ms1 //delay_ms1を外部から利用可能にする .func delay_ms1 // 関数名の宣言。この後に書かれるアセンブラ命令が関数 //の中身になる delay_ms1: //関数の開始 2: ldi r18,250 //r18--r25は自由に使える 1: nop //時間合わせ dec r18 brne 1b //0でなければ後方の1:へ sbiw r24,1 //r25,r24ペアに引数が入っている brne 2b //0でなければ後方の2:へ ret .endfunc