アセンブリの勉強  MCUはTiny2313です。                             TOPページへもどる
   C言語で書けないところまで進んでいるのではないのですが、アセンブリ言語も勉強してみようかと考えたところです。例によって勉強したことの備忘録となりますが、試行錯誤の記録でもありますので途中経過的に間違った解釈も多々あることになります。ここの記録は信用なさらないようにお願いします。

14 Cから呼び出す delay関数 08.12.13
13 SRAMに置くbyte型変数 08.09.08
12 PWM動作(LED点灯) 08.09.07
11 ROM領域に定数を置く 08.09.06
10 LED点灯移動方向の切り換え 2つの割り込み 08.09.05
9 タイマカウンタ割り込みのLED点滅 08.09.03
8 スイッチのトグル動作(割り込みによる) 08.09.01
7 スイッチのトグル動作(ポーリングによる) 08.08.31
6 スイッチ入力の処理 08.08.30
5 LED点滅プログラム 08.08.28
4 アセンブラ覚え書き 08.08.27
3 最初のプログラム 08.08.27
2 環境構成 08.08.27
1 はじめに 08.08.27






1 はじめに
アセンブリ言語では、AVRでは通常AVRstudioを使いますが、IDEでは多くのアイコンと窓操作を覚える必要がありますので新しいものを覚えることに苦労がある私には快適な環境とは思えません。しかし、AVRstudioにはavrasm2.exeというアセンブラが同梱されていて(これがご本尊化かもしれませんが)、コマンドプロンプト(DOS窓)でバッチファイルをドラッグアンドドロップで実行すると快適にアセンブルされてhexファイルが生成されます。hexの書込は常用のライタでWINAVRのときと同じ操作となります。

ドラッグアンドドロップでのアセンブルはWINAVRのドラッグアンドドロップによるコンパイルと同じ操作で行えますから新しい方法を覚えなくてもできることになります。
将来、複数のファイルをアセンブルする必要があるのかもしれませんがさし当たってはアセンブリの基本を理解するための勉強ですから簡単な方法を取ることにしました。複雑なファイルを取り扱う頃にはAVRstudioが使えるようになると思います。

また、アセンブラavrasm2.exeはマクロも扱えるようですが、最初はできるだけ単純な形で勉強していくつもりです。

                                              



2 環境構成
ソフトウエアについて:
アセンブラの実行ファイルと参照ファイルは次のとおりです。
@ AVRstudioを E:\0_programs\ ディレクトリにインストールしています。デフォルトの場所とは異なっています。
A その結果、アセンブラavrasm2.exeは E:\0_programs\AVRtools\AvrAssembler2\ ディレクトリに置かれます。
B tn2313def.inc などのチップ定義ファイルは E:\0_programs\AVRtools\AvrAssembler2\Appnotes\ にあります。

作成するプログラムファイルは次のとおりです。
@ H:\assemble_AVR\_tn2313\ のもとにプログラム毎にプログラム名と同じフォルダを作ります。
A そのフォルダの中にソースファイルとアセンブル実行のバッチファイル0.bat(0にすると上に来ます)を置きます。
B 0.batには次のように書きます。
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
#endif
SRAMに置くbyte型変数
SRAMにbyte型変数を置くことができる。 .dseg .org 0x60 に続いて ラベル名 .byte 個数 と書く。directivのドット と オフセットに注意。めんどうであるが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型変数 
.dseg0x60番地以降は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


                           


TOPページへもどる