第 5 回 アセンブリ言語

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

5-1. アセンブリ言語

基本書式

アセンブリ言語はテキストファイルに記述します。 「ラベル: ニモニック オペランド」 を空白で区切って記述します ( AVR Assembler p. 11 3. Assembler source )。 ラベルの前に空白があってはいけません。 また、セミコロン(;)の後ろはコメントとしてアセンブル時に無視されます。 ニモニックの位置には他にディレクティヴ、マクロなどを置くことができます。


ラベル  ニモニック オペランド コメント
.org    0x0013
reset:
        ldi        r16,low(RAMEND)
        out        SPL,r16

演算

アセンブリ言語中で定数の演算式を書くことが出来ます。 C 言語で許されて いるような演算子がそのまま使えます (AVR Assembler p.33 7. Expressions )。 但し、レジスタの内容は演算の対象ではありません。

ディレクティヴ

アセンブラとはニモニックとオペランドから機械語を生成するものですが、 利便性を向上するためのアセンブラの機能があります。 ディレクティヴとはアセンブラに対する指示をする命令です。 ディレクティヴはピリオド(.)で始めます。

良く使うディレクティヴを紹介します。

cseg, dseg, eseg
それぞれ、 Code, Data, EEPROM の内容を定義するという意味です。
equ
任意の値を持つラベルを定義します。再定義できません。 計算値も定義できます。
def
レジスタにラベルを定義します。再定義できません。 これを使用すると、レジスタの利用を論理的に表示できるので、 必ず使うことを推奨します。
org
実際のプログラムやデータを置く始点となる番地を指定します。
db, dw, dd, dq
データをじかに書きます。 db は Byte, dw は Word(16bit)の定義に使います。カンマ(,)で区切るといく つも定義できます。 なお、プログラム領域(cseg で定義される領域)は 16bit です。 lpm 命令で 8bit ずつ取り出す場合、下位 8bit がアドレスの偶数番地、上位 8bit が奇数番地に対応します。さらに、奇数個のデータを入れると、上位 8bit が $00 で埋められます。
byte
dseg で SRAM の領域にラベル付きの領域を定義する際に使用します。
exit
アセンブリのリストの最後を示します。
macro, endmacro

マクロを定義します。 macro のオペランドにマクロ名を指定します。 マクロ定義の中で引数として @0 から @9 の 10 個の仮引数が使えます。 なお、AVR のアセンブラのマクロ中のラベルはローカルにしか使用できません ので、マクロ中に定義したラベルに外部からアクセスすることはできません。 なお、グローバルなラベルを使用するには #define 疑似命令を使用する手が ありますが、こちらこちらで基本的に一行で記述しなければならず、複数行を 書くには(\)を使用した継続行を使用する必要があります。

5-2. 演算子

アセンブリ言語で式を書くことができます。 値はリテラルを使用でき、レジスタやメモリの内容を使うことはできませ ん

なお、これらは一部なので、詳しくは アセンブラマニュアル p.34 7.3 Operator を参照してください。

!
論理否定: 0以外なら0を、0は1を返します
&&
論理積
||
論理和
~
ビット反転
&
ビット積
|
ビット和

5-3. 関数

EQU などで使用できる関数を下記に示します。 なお、これらは一部なので、詳しくは AVR Assembler p.33 7. Expressions を参照してください。

LOW(式)
式の値の下位8bitを取り出します
HIGH(式)
式の値の上位8bitを取り出します
EXP2(式)
式の値の2のべき乗を返します。 (1<<式)と同等です
LOG2(式)
式の値の 2 を底とした対数の整数部分を返します
INT(式)
式の値を切り捨てます
FRAC(式)
式の値の小数部分を取り出します
ABS(式)
式の値の絶対値を返します

5-4. アセンブル

さて、この節では実際にプログラムを作成する手順を説明します。 既に、Microsoft VisualStudio Code に MPLab Extension がインストールされているものとします。

  1. MPLABのExtensionがインストールされた VisualStudio Codeを起動する。
  2. 左のバーから MPLAB ボタンを押す
    1. project のバーの New Project ボタンを押す。
      1. Enter project name でプロジェクト名を入れる
      2. Select project locationで保存場所を指定する(予めフォルダを作っておく)
      3. Select Device でマイコンの機種名 を入れる
      4. Select Project Template では Application Standard application projectを選ぶ
    2. 画面が整理されてプロジェクトが作られる。 ここで、左下の Trust ボタンを押し、このプロジェクトを Trust モードに 移行する
    3. 初回のみダウンロードの選択画面が出るので、avr-asm2 を選ぶ
    4. Emission failed. Error: Project contains no suitable source files: Supported extensions: [(asm|ASM|Asm)]. Available files: [main.c] と、 asm ファイルがプロジェクトに含まれていないというエラーが出る。
  3. 左側のバーの一番上の Explorer ボタンを押すと 作成したプロジェクトのファイル構成を見ることができる
    1. プロジェクトに main.asm というファイルが入っているか確認する。 もし無ければ、プロジェクトのフォルダを右クリックして new File で作 成する。 更に、代わりに main.c というファイルがあったら、右クリックして Delete する。
    2. 2026年9月時点では、システムファイルを読むことができないため、 このプロジェクト内に必要なシステムファイルをコピーする必要がある
      1. Windows のエクスプローラーで「表示→表示」で「隠しファイル」に チェックが付いていることを確認する(付いてなければチェックする)
      2. c:\User\ログイン名\.mchp_packs\Microchip\ATmega_DFP\3.6.299\avrasm\inc というフォルダを開く
      3. ファイル を探し、VisualStudio Code の Explorer の作成したプ ロジェクトにドラッグ & ドロップする(インポート)。
  4. main.asm にプログラムを入力します。 インポートしたファイルに含まれる マクロ、名前などが使用できます。
  5. MPLABボタンをおして、作成したプロジェクトの Buildボタンを押して、 プロジェクトを Build します。

5-5. 実行

プログラムの書き込み

  1. Arduino をコンピュータに接続します
  2. デバイスマネージャーで、ポート(COMとLPT) の中のArduino のデバイス識別子を調べます。 (例えば COM3 だとします)
  3. VisualStudio Code の Explorer 画面で、 作成したプロジェクトの中の out/プロジェクト名/ の中に default.hex というファイルがあることを確認します。
  4. そのフォルダでnew file で program328.batというファイルを作ります。
  5. 下記のprogram328.batをそのファイルに入れて保存します。
  6. Explorer 画面の program328.bat を右クリックして 「Open in Integrated Terminal」を選ぶ と Terminal タブのウィンドウが開く
  7. .\program328.bat COM3 とするとプログラムがArduino に書き込めます。

program328.bat(Windows 用)

以下のプログラムを program.bat というテキストファイルに収めます。


set root=C:\Users\sakamoto\Downloads\avrdude-v8.3-windows-x64
set dude=%root%\avrdude.exe
set conf=%root%\avrdude.conf
%dude%  -C %conf% -p m328p -c arduino -P %1  -D -U flash:w:"default.hex":i

なお、上記にsakamotoという名前が入っています。これを自分のパソコン のアカウント名などに置き換える必要があります。

  1. Arduino をコンピュータに接続します
  2. デバイスマネージャーで、Arduino のデバイス識別子を調べます。 デバイスマネージャーで、ポート(COMとLPT) を開きます。 Arduino Leonardo は特殊で、通常はUSBはシリアル回線として接続してい て、 リセットがかかるとブートローダー用に別のシリアルポートが開かれます。 そこで、リセットをかけたときに出るデバイスの識別子を調べます。 (例えば COM5 だとします)
  3. そのフォルダでnew file で program32u4.batというファイルを作ります。
  4. 下記のprogram32u4.batをそのファイルに入れて保存します。
  5. Explorer 画面の program32u4.bat を右クリックして 「Open in Integrated Terminal」を選ぶ と Terminal タブのウィンドウが開く
  6. リセットしたときに出るデバイス識別子が例えばCOM5だったら、 .\program32u4.bat COM5 と タイプし、Arduino Leonardo の基板上にあるリセットボタンを押してから上 記のコマンドをReturnで起動するとプログラムがArduino に書き込めます。

program32u4.bat(Windows 用)

以下のプログラムを program32u4.bat というテキストファイルに収めます。


set root=C:\Users\sakamoto\Downloads\avrdude-v8.3-windows-x64
set dude=%root%\avrdude.exe
set conf=%root%\avrdude.conf
%dude%  -C %conf% -p m32u4 -c avr109 -P %1  -D -U flash:w:"default.hex":i

なお、上記にsakamotoという名前が入っています。これを自分のパソコン のアカウント名などに置き換える必要があります。

  1. Arduino をコンピュータに接続します
  2. デバイスマネージャーで、ポート(COMとLPT) の中のArduino のデバイス識別子を調べます。 (例えば COM3 だとします)
  3. VisualStudio Code の Explorer 画面で、 作成したプロジェクトの中の out/プロジェクト名/ の中に default.hex というファイルがあることを確認します。
  4. そのフォルダでnew file で program4809.ps1というファイルを作ります。
  5. 下記のprogram4809.ps1をそのファイルに入れて保存します。
  6. Explorer 画面の program4809.ps1 を右クリックして 「Open in Integrated Terminal」を選ぶ と Terminal タブのウィンドウが開く
  7. Set-ExecutionPolicy RemoteSigned -Scope CurrentUserを 打って、Power Shell のスクリプトを実行できるようにします。
  8. .\program4809.ps1 COM3 とするとプログラムがArduino に書き込めます。

program4809.ps1(Windows 用)

以下のプログラムを program4809.ps1 というテキストファイルに収めます。


param(
[string]$COMPORT
)
$root="C:\Users\sakamoto\Downloads\avrdude-v8.3-windows-x64"
$dude = Join-Path $root "avrdude.exe"
$conf = Join-Path $root "avrdude.conf"
$port = new-Object System.IO.Ports.SerialPort $COMPORT,1200,'None',8,'One'
$port.DtrEnable = $true
$port.Open()
Start-Sleep -Milliseconds 100
$port.DtrEnable = $false
$port.Close()
Start-Sleep -Seconds 2
& $dude  -C $conf -patmega4809 -cjtag2updi -v -V -P $COMPORT -e -D -U flash:w:"default.hex":i

なお、上記にsakamotoという名前が入っています。これを自分のパソコン のアカウント名などに置き換える必要があります。

5-6. 演習問題

演習5-1

サンプルプログラムflashuno.asm の各ディレクティヴがどのような意味か、全て調べなさい。

演習5-2

サンプルプログラムflashuno.asm を機能毎に区分し、各機能を説明しなさい。

演習5-3

サンプルプログラムswitchflasher.asm を動作させなさい。

また、プログラムを読み、どのような動きをするのか、説明しなさい。

演習5-4

サンプルプログラムswitchflasher.asm の各ディレクティヴがどのような意味か、全て調べなさい。

演習5-5

サンプルプログラムswitchflasher.asm を機能毎に区分し、各機能を説明しなさい。

switchflasher.asm


.include        "m328Pdef.inc"
.cseg
.org    0x0000
        rjmp        reset
.org    INT_VECTORS_SIZE
reset:
        ldi     r16,high(RAMEND)
        out     SPH,r16 
        ldi     r16,low(RAMEND)
        out     SPL,r16
        sbi     ddrb,pb5
        cbi     ddrd,pd4
        sbi     portd,pd4
main:
        sbi     portb,pb5
        sbis    pind,pd4
        rjmp    onl
        rcall   shortwait
        rjmp    off
onl:
        rcall   longwait
off:
        cbi     portb,pb5
        sbic    pind,pd4
        rjmp    offl
        rcall   shortwait
        rjmp    main
offl:
        rcall   longwait
        rjmp    main
  
.equ    shorttime = 4
.equ    longtime = 40
.def    wreg0 = r20
.def    wreg1 = r21
.def    wreg2 = r22
shortwait:
        ldi     wreg0,shorttime
        rjmp    wait0
longwait:
        ldi     wreg0,longtime
wait0:
        ldi     wreg1,0
wait1:
        ldi     wreg2,0
wait2:
        nop
        dec     wreg2
        brne    wait2
        dec     wreg1
        brne    wait1
        dec     wreg0
        brne    wait0
        ret
.exit

.include        "m32U4def.inc"
.cseg
.org    0x0000
        rjmp        reset
.org    INT_VECTORS_SIZE
reset:
        ldi     r16,high(RAMEND)
        out     SPH,r16 
        ldi     r16,low(RAMEND)
        out     SPL,r16
        sbi     ddrc,pc7
        cbi     ddrd,pd4
        sbi     portd,pd4
main:
        sbi     portc,pc7
        sbis    pind,pd4
        rjmp    onl
        rcall   shortwait
        rjmp    off
onl:
        rcall   longwait
off:
        cbi     portc,pc7
        sbic    pind,pd4
        rjmp    offl
        rcall   shortwait
        rjmp    main
offl:
        rcall   longwait
        rjmp    main
  
.equ    shorttime = 4
.equ    longtime = 40
.def    wreg0 = r20
.def    wreg1 = r21
.def    wreg2 = r22
shortwait:
        ldi     wreg0,shorttime
        rjmp    wait0
longwait:
        ldi     wreg0,longtime
wait0:
        ldi     wreg1,0
wait1:
        ldi     wreg2,0
wait2:
        nop
        dec     wreg2
        brne    wait2
        dec     wreg1
        brne    wait1
        dec     wreg0
        brne    wait0
        ret
.exit

.include        "m4809def.inc"
.cseg
.org    0x0000
        rjmp        reset
.org    INT_VECTORS_SIZE
reset:
        ldi     r16,low(RAMEND)
        out     CPU_SPL,r16
        ldi     r16,high(RAMEND)
        out     CPU_SPH,r16 
        ; PE2
        lds     r16,PORTE_DIR
        ori     r16,1<<2 
        sts     PORTE_DIR,r16
        ; PC6
        lds     r16,PORTC_PIN6CTRL
        ori     r16,PORT_PULLUPEN_bm
        sts     PORTC_PIN6CTRL,r16

main:
        lds     r17,PORTE_OUT
        ori     r17,1<<2
        sts     PORTE_OUT,r17
        lds     r17,PORTC_IN
        sbrs    r17,6
        rjmp    onl
        rcall   shortwait
        rjmp    off
onl:
        rcall   longwait
off:
        lds     r17,PORTE_OUT
        andi    r17,~(1<<2)
        sts     PORTE_OUT,r17
        lds     r17,PORTC_IN
        sbrc    r17,6
        rjmp    offl
        rcall   shortwait
        rjmp    main
offl:
        rcall   longwait
        rjmp    main
  
.equ    shorttime = 1
.equ    longtime = 10
.def    wreg0 = r20
.def    wreg1 = r21
.def    wreg2 = r22
shortwait:
        ldi     wreg0,shorttime
        rjmp    wait0
longwait:
        ldi     wreg0,longtime
wait0:
        ldi     wreg1,0
wait1:
        ldi     wreg2,0
wait2:
        nop
        dec     wreg2
        brne    wait2
        dec     wreg1
        brne    wait1
        dec     wreg0
        brne    wait0
        ret
.exit

演習5-6

次の操作をする命令の組を書きなさい

  1. レジスタ r16 に 0xA0 を入れる
  2. 出力ポート PORTB5を HIGHにする
  3. r16 の値が 0x32 かどうか調べ、等しければ label0 に飛び、等しくなけ れば label1 に飛ぶ
  4. r16 の値が 0x45 と比較し、大きければ label0 に飛び、それ以下なら label1 に飛ぶ
  5. rcall sub0 を 100回呼ぶ

演習5-7

flashuno.asm のプログラムを改造して、点滅の速さが2倍になるようにしよ う

演習5-8

flashuno.asm のプログラムを改造して、点滅の速さが1/2倍になるようにし よう

演習5-9

r16に入れた数だけ、0.1秒ずつLEDを点滅した後、1秒消灯する nblink というサブルーチンを作りなさい。 さらに、 次のプログラムと結合して正常に動作することを確かめなさい。


.include        "m328Pdef.inc"
.cseg
.org    0x0000
        rjmp        reset
.org    INT_VECTORS_SIZE
reset:
        ldi     r16,high(RAMEND)
        out     SPH,r16 
        ldi     r16,low(RAMEND)
        out     SPL,r16
    
        sbi     ddrb,pb5
main:
        ldi     r16,0
loop:
        inc     r16
        push    r16
        rcall   nblink
        pop     r16
        cpi     r16,4
        brne    loop
        rjmp    main

.include        "m32U4def.inc"
.cseg
.org    0x0000
        rjmp        reset
.org    INT_VECTORS_SIZE
reset:
        ldi     r16,high(RAMEND)
        out     SPH,r16 
        ldi     r16,low(RAMEND)
        out     SPL,r16
        sbi     ddrc,pc7
main:
        ldi     r16,0
loop:
        inc     r16
        push    r16
        rcall   nblink
        pop     r16
        cpi     r16,4
        brne    loop
        rjmp    main

.include        "m4809def.inc"
.cseg
.org    0x0000
        rjmp        reset
.org    INT_VECTORS_SIZE
reset:
        ldi     r16,low(RAMEND)
        out     CPU_SPL,r16
        ldi     r16,high(RAMEND)
        out     CPU_SPH,r16 
        ; PE2
        lds     r16,PORTE_DIR
        ori     r16,1<<2 
        sts     PORTE_DIR,r16
main:
        ldi     r16,0
loop:
        inc     r16
        push    r16
        rcall   nblink
        pop     r16
        cpi     r16,4
        brne    loop
        rjmp    main

演習5-10

番兵が0の数列をdbで確保し、各数列の値でblink()メソッドを呼ぶプログラム を書きなさい。


sample:
.db     3,3,7,0
.equ    sampleadr = sample<<1

演習5-11

4番とGNDをつないだときだけ、LED が点灯するプログラムを作りなさい。

演習5-12

4番とGNDをつないだときは、1秒おきに2回LEDが点滅、つないでない時は 1秒おきに短くLEDが点滅するプログラムを作りなさい。

演習5-13

4番とGNDをつないだときは、1秒おきにLEDが1回点滅、2回点滅、3回点滅、 1回点滅,2回点滅、3回点滅と繰り返すが、 つないでない時は同じ回数の点滅を繰り返すプログラムを作りなさい。 つまり、3回点滅の時に線を外せば、そのまま3回点滅を繰り返し、 1回点滅の時に線を外すと1回点滅を繰り返すようにしなさい。


坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科