はじめに
AVRマイコンは、Atmel社(現在はMicrochip Technology社)によって1996年頃に開発された、8ビットRISCアーキテクチャのマイクロコントローラファミリです。シンプルながらも高性能で、特にホビー用途や組み込みシステムのプロトタイピングで広く利用されてきました。Arduinoの初期モデルに搭載されていたことでも有名です。
では、なぜ今、C言語や他の高水準言語が主流の中で、アセンブリ言語を学ぶのでしょうか? いくつかの理由があります:
- ハードウェアの深い理解: アセンブリ言語は、マイコンのアーキテクチャ(レジスタ、メモリ、命令セットなど)と直接対話します。これにより、ハードウェアがどのように動作するのかを根本的に理解することができます。
- パフォーマンスの最適化: アセンブリ言語を使うことで、コードの実行速度やメモリ使用量を極限まで最適化できます。特にリソースが限られたマイコンでは、この点が重要になることがあります。
- 高水準言語の限界を超える: コンパイラが対応していない特定のハードウェア機能を利用したい場合や、非常に精密なタイミング制御が必要な場合に、アセンブリ言語が役立ちます。
- デバッグ能力の向上: 高水準言語で書かれたコードが期待通りに動作しない場合、コンパイラが生成したアセンブリコードを理解することで、問題の原因を特定しやすくなります。
この記事では、AVRアセンブリ言語の基本的な概念から、簡単なプログラムを作成して動かすまでを解説します。AVRマイコンの内部構造、主要な命令、開発環境のセットアップ、そして実際にLEDを点滅させるプログラム(Lチカ)の作成を通じて、アセンブリプログラミングの世界への第一歩を踏み出しましょう。
AVRアーキテクチャの基本
AVRアセンブリを理解するためには、まずAVRマイコンの基本的な構造(アーキテクチャ)を知る必要があります。AVRは「修正ハーバード・アーキテクチャ」を採用しており、プログラムメモリとデータメモリが物理的に分離されています。
レジスタ (Registers)
レジスタは、CPU内部にある高速な記憶領域で、演算やデータの一時的な格納に使用されます。AVRマイコンにはいくつかの種類のレジスタがあります。
- 汎用レジスタ (General Purpose Registers): R0からR31までの32個の8ビットレジスタです。これらのレジスタは、データ操作の中心となります。多くの命令は、これらのレジスタ間で直接データをやり取りできます。特にR16からR31は、即値(定数)を直接ロードする命令 (`LDI`) などでよく使われます。R26:R27、R28:R29、R30:R31のペアは、それぞれX, Y, Zポインタレジスタとして、間接アドレッシングに使用できます。
- ステータスレジスタ (Status Register – SREG): 演算結果の状態(ゼロ、負、キャリーなど)を示すフラグが格納される8ビットのレジスタです。条件分岐命令は、このSREGのフラグを参照して動作を決定します。主なフラグには、C(キャリー)、Z(ゼロ)、N(ネガティブ)、V(オーバーフロー)、S(符号)、H(ハーフキャリー)、T(ビットコピー用)、I(グローバル割り込み許可)があります。
- プログラムカウンタ (Program Counter – PC): 次に実行される命令が格納されているプログラムメモリのアドレスを指し示すレジスタです。通常は命令が実行されるごとに自動的にインクリメントされますが、ジャンプ命令やコール命令によって任意のアドレスに変更することもできます。
- スタックポインタ (Stack Pointer – SP): スタックと呼ばれるデータメモリ上の一時的なデータ格納領域の、現在使用中の最上位アドレスを指し示すレジスタです。サブルーチン呼び出し時の戻りアドレスの保存や、一時的なデータの退避・復帰(プッシュ/ポップ)に使用されます。AVRでは、SPはデータメモリ空間(SRAM)を指します。多くのAVRデバイスでは、SPHとSPLの2つの8ビットI/Oレジスタで構成され、16ビットのアドレスを扱います。
メモリ空間 (Memory Space)
AVRマイコンは、主に3種類のメモリ空間を持っています。
- Flashメモリ (プログラムメモリ): プログラムコードを格納するための不揮発性メモリ(電源を切っても内容が消えない)です。命令はここから読み出されて実行されます。AVRは、チップ上にFlashメモリを搭載した初期のマイコンファミリの一つです。サイズはデバイスによって異なります(例: ATtiny2313は2KB, ATmega328Pは32KB)。
- SRAM (データメモリ): プログラム実行中に変数やデータを一時的に格納するための揮発性メモリ(電源を切ると内容が消える)です。汎用レジスタやI/Oレジスタも、このデータメモリ空間にマッピングされています。スタックもこのSRAM上に確保されます。サイズはデバイスによって異なり、Flashメモリよりも小さいのが一般的です(例: ATtiny2313は128バイト, ATmega328Pは2KB)。
- EEPROM (データEEPROM): 設定値など、電源を切っても保持したい少量のデータを格納するための不揮発性メモリです。Flashメモリと同様に書き換え回数に制限がありますが、プログラムメモリとは独立して読み書きできます。SRAMやFlashメモリとは別のアドレス空間を持ち、通常は専用のI/Oレジスタ経由でアクセスします。サイズはデバイスによります(例: ATtiny2313は128バイト, ATmega328Pは1KB)。
I/Oポート (Input/Output Ports)
マイコンが外部の世界とやり取りするための窓口がI/Oポートです。AVRマイコンの各ピンは、通常いくつかのポート(例: PORTA, PORTB, PORTC, PORTD)にグループ化されています。各ポートには、関連する3つのI/Oレジスタがあります(xはポート名A, B, C, D…)。
- DDRx (Data Direction Register): 各ピンを入力(0)にするか出力(1)にするかを設定します。
- PORTx (Port Data Register): ピンが出力に設定されている場合、そのピンから出力する値(High=1, Low=0)を書き込みます。ピンが入力に設定されている場合、内蔵プルアップ抵抗を有効(1)にするか無効(0)にするかを設定します。
- PINx (Port Input Pins Register): ピンが入力に設定されている場合、そのピンの現在の状態(High/Low)を読み取ります。このレジスタは読み取り専用です(書き込んでも影響はありません)。
これらのレジスタはデータメモリ空間内の特定のI/Oアドレスに割り当てられており、`IN`, `OUT`命令や、特定のI/Oアドレス範囲(0x00-0x1F)に対しては `SBI`, `CBI` などのビット操作命令でアクセスできます。また、`LDS`, `STS`命令を使ってSRAMアドレス経由でもアクセス可能です。
レジスタ | 役割 | 備考 |
---|---|---|
汎用レジスタ (R0-R31) | データ演算・一時格納 | 8ビット x 32本 |
SREG | 演算結果の状態フラグ格納 | 条件分岐で使用 |
PC | 次に実行する命令のアドレス | 分岐命令で変更可能 |
SP | スタックのトップアドレス | SRAM上を指す |
DDRx | ポートの入出力方向設定 | 0:入力, 1:出力 |
PORTx | 出力値設定 / プルアップ設定 | 出力時: H/L設定, 入力時: プルアップON/OFF |
PINx | 入力ピンの状態読み取り | 読み取り専用 |
開発環境の準備
AVRアセンブリプログラムを作成し、マイコンで実行するには、いくつかのツールが必要です。幸いなことに、多くのツールは無料で利用可能です。
必要なツール
-
アセンブラ (Assembler): アセンブリ言語のコード(.asmファイル)を、マイコンが理解できる機械語(オブジェクトファイルやHEXファイル)に変換するプログラムです。
- avr-as: GNU Binutilsに含まれるアセンブラで、AVR-GCCツールチェーンの一部です。Linux, macOS, Windowsで利用可能です。
- Microchip Studio (旧Atmel Studio): Microchip社が提供するWindows向けの統合開発環境(IDE)で、アセンブラ、C/C++コンパイラ、シミュレータ、デバッガ機能が含まれています。内部的にはAVR-GCCツールチェーンを使用しています。
- avra: 独立したAVRアセンブラで、マクロ機能などが強化されています。クロスプラットフォームで利用可能です。
- リンカ (Linker): アセンブラが生成したオブジェクトファイルを結合し、最終的な実行可能ファイル(通常はELF形式)を作成します。(avr-gccツールチェーンに含まれる `avr-ld`)
- HEXファイル生成ツール: リンカが生成したELFファイルから、マイコンへの書き込みに使用されるIntel HEX形式のファイルを生成します。(avr-gccツールチェーンに含まれる `avr-objcopy`)
-
書き込みツール (Programmer Software): 生成されたHEXファイルを、ハードウェアプログラマ(書き込み器)経由でマイコンのFlashメモリに書き込むためのソフトウェアです。
- avrdude: 様々な書き込み器に対応した、広く使われているコマンドラインツールです。Linux, macOS, Windowsで利用可能です。
- Microchip Studio: 純正の書き込み器(AVRISP mkII, PICkit など)に対応した書き込み機能を持っています。
- ハードウェアプログラマ (書き込み器): PCとAVRマイコンを接続し、プログラムを書き込むための物理的なデバイスです。USBasp, AVRISP mkII (および互換機), PICkit 4/5 など、様々な種類があります。Arduinoボード自体をISPプログラマとして使用することも可能です。
- テキストエディタ または IDE: アセンブリコードを記述するためのエディタ。シンプルなテキストエディタでも構いませんが、シンタックスハイライト機能などがあると便利です。VSCodeにAVR関連の拡張機能を入れるのも良い選択肢です。Microchip Studioは高機能なIDEです。
-
シミュレータ (Simulator): 実際のハードウェアなしに、PC上でプログラムの動作をシミュレーションするツールです。デバッグに非常に役立ちます。
- Microchip Studio Simulator: IDEに内蔵されています。
- simavr: コマンドラインベースのオープンソースシミュレータです。
簡単なセットアップ例 (AVR-GCC + avrdude)
ここでは、クロスプラットフォームで利用可能なコマンドラインツールを使った基本的なセットアップの流れを示します。
-
AVR-GCC ツールチェーンのインストール:
- Windows: Microchip Studioをインストールするのが簡単です。あるいは、独立したAVR-GCCツールチェーン (例: Zak Kemble氏のビルドなど) を探してインストールします。パスを通す必要があるかもしれません。
- macOS: Homebrew を使って `brew install avr-gcc avrdude` を実行するのが簡単です。または CrossPack を利用する方法もあります。
- Linux (Debian/Ubuntu): `sudo apt-get update && sudo apt-get install gcc-avr binutils-avr avr-libc avrdude` を実行します。
- テキストエディタの準備: お好みのテキストエディタ(VSCode, Sublime Text, Atom, Vim, Emacsなど)を用意します。
- ハードウェアプログラマと接続: 使用する書き込み器(例: USBasp)をPCに接続し、必要であればドライバをインストールします。書き込み器とターゲットのAVRマイコンをISPケーブルで接続します(MOSI, MISO, SCK, RESET, VCC, GND)。
- 動作確認: ターミナル(コマンドプロンプト)を開き、`avr-gcc –version` や `avrdude -c usbasp -p m328p` (マイコンに合わせて `-p` オプションを変更) などのコマンドを実行して、ツールが認識されているか、書き込み器とマイコンが通信できるかを確認します。
これで、アセンブリコードを書いて、アセンブルし、マイコンに書き込む準備が整いました!
基本的なAVRアセンブリ命令
AVRアセンブリ言語は、マイコンに特定の操作を行わせるための命令(インストラクション)の集まりです。各命令は、通常「ニーモニック (Mnemonic)」と呼ばれる短い英単語(例: `LDI`, `ADD`)と、操作対象を示す「オペランド (Operand)」で構成されます。AVRはRISCアーキテクチャを採用しており、多くの命令は1クロックサイクルで実行されます。
以下に、よく使われる基本的な命令をカテゴリ別に紹介します。ここではATmega328Pなどで使われる一般的な命令を例示しますが、デバイスによってはサポートされていない命令もあります。詳細は各デバイスのデータシートや AVR Instruction Set Manual を参照してください。
データ転送命令 (Data Transfer Instructions)
レジスタ、メモリ、I/Oポート間でデータを移動させる命令です。
- `LDI Rd, K` (Load Immediate): 8ビットの即値(定数)Kを、汎用レジスタRd(R16~R31のみ)にロードします。
- `MOV Rd, Rr` (Move Register): 汎用レジスタRrの内容を、汎用レジスタRdにコピーします。
- `LDS Rd, address` (Load Direct from Data Space): データ空間(SRAM)の指定された16ビットアドレス `address` から8ビットデータを読み込み、汎用レジスタRd(R0~R31)にロードします。
- `STS address, Rr` (Store Direct to Data Space): 汎用レジスタRrの内容を、データ空間(SRAM)の指定された16ビットアドレス `address` に書き込みます。
- `IN Rd, P` (Input from I/O Location): I/Oアドレス空間のポートアドレスP(0x00~0x3F)からデータを読み込み、汎用レジスタRd(R0~R31)にロードします。
- `OUT P, Rr` (Output to I/O Location): 汎用レジスタRrの内容を、I/Oアドレス空間のポートアドレスP(0x00~0x3F)に書き込みます。
- `PUSH Rr` / `POP Rd` (Push/Pop Register on Stack): レジスタRrの内容をスタックに退避 (PUSH) / スタックからレジスタRdに復帰 (POP) します。サブルーチン呼び出し時のレジスタ内容保護などに使います。
算術演算命令 (Arithmetic Instructions)
加算、減算などの算術演算を行います。
- `ADD Rd, Rr` (Add without Carry): Rd = Rd + Rr の加算を行います。SREGのフラグが変化します。
- `ADC Rd, Rr` (Add with Carry): Rd = Rd + Rr + C (キャリーフラグ) の加算を行います。多バイト長の加算に使用します。
- `SUB Rd, Rr` (Subtract without Carry): Rd = Rd – Rr の減算を行います。
- `SBC Rd, Rr` (Subtract with Carry): Rd = Rd – Rr – C (キャリーフラグ、減算ではボローとして扱われる) の減算を行います。多バイト長の減算に使用します。
- `SUBI Rd, K` (Subtract Immediate): Rd = Rd – K (即値) の減算を行います。(R16~R31のみ)
- `INC Rd` (Increment): Rd = Rd + 1 のインクリメントを行います。キャリーフラグは変化しません。
- `DEC Rd` (Decrement): Rd = Rd – 1 のデクリメントを行います。
論理演算命令 (Logical Instructions)
AND, OR, XOR などのビット単位の論理演算を行います。
- `AND Rd, Rr` (Logical AND): Rd = Rd & Rr (ビットごとの論理積) を行います。特定のビットをクリアするのに使えます。
- `ANDI Rd, K` (Logical AND with Immediate): Rd = Rd & K (即値) を行います。(R16~R31のみ)
- `OR Rd, Rr` (Logical OR): Rd = Rd | Rr (ビットごとの論理和) を行います。特定のビットをセットするのに使えます。
- `ORI Rd, K` (Logical OR with Immediate): Rd = Rd | K (即値) を行います。(R16~R31のみ)
- `EOR Rd, Rr` (Exclusive OR): Rd = Rd ^ Rr (ビットごとの排他的論理和) を行います。特定のビットを反転するのに使えます。また、`EOR R1, R1` は `R1` をゼロクリアする常套手段です。
- `COM Rd` (One’s Complement): Rd の全ビットを反転します (Rd = ~Rd)。
- `NEG Rd` (Two’s Complement): Rd の2の補数を計算します (Rd = 0 – Rd)。
分岐命令 (Branch Instructions)
プログラムの実行フローを制御します。ジャンプや条件分岐、サブルーチン呼び出しなどがあります。
- `RJMP k` (Relative Jump): 無条件に、現在のPCからの相対位置kへジャンプします。近距離のジャンプに使われます。
- `JMP k` (Jump): 無条件に、指定された絶対アドレスkへジャンプします。より広範囲のジャンプが可能ですが、命令長が長くなります。(一部のデバイスのみ)
- `RCALL k` (Relative Call): 現在のPCからの相対位置kにあるサブルーチンを呼び出します。戻りアドレスがスタックに積まれます。
- `CALL k` (Call): 指定された絶対アドレスkにあるサブルーチンを呼び出します。(一部のデバイスのみ)
- `RET` (Return from Subroutine): サブルーチンから呼び出し元に戻ります。スタックから戻りアドレスをPCにポップします。
- `BREQ k` (Branch if Equal): SREGのZフラグ(ゼロフラグ)が1の場合、相対位置kへ分岐します。直前の比較(`CP`, `CPI`)や演算(`SUB`, `DEC`など)の結果がゼロだった場合に分岐します。
- `BRNE k` (Branch if Not Equal): SREGのZフラグが0の場合、相対位置kへ分岐します。直前の比較や演算の結果がゼロでなかった場合に分岐します。
- その他条件分岐: `BRLO`(未満), `BRSH`(以上), `BRLT`(負), `BRGE`(非負), `BRMI`(マイナス), `BRPL`(プラス), `BRVS`(オーバーフローセット), `BRVC`(オーバーフロークリア) など、SREGの各フラグに対応した多くの条件分岐命令があります。
- `CP Rd, Rr` (Compare): Rd と Rr を比較します (内部的に Rd – Rr を実行)。結果はレジスタには格納されず、SREGのフラグのみが変化します。条件分岐命令の直前で使われます。
- `CPI Rd, K` (Compare with Immediate): Rd と 即値K を比較します。(R16~R31のみ)
ビット操作命令 (Bit Manipulation Instructions)
レジスタやI/O空間の特定のビットを操作します。
- `SBI P, b` (Set Bit in I/O Register): I/Oアドレス空間P(0x00~0x1F)のビットb(0~7)を1にセットします。ポートの特定のピンをHighにするのによく使われます。
- `CBI P, b` (Clear Bit in I/O Register): I/Oアドレス空間P(0x00~0x1F)のビットbを0にクリアします。ポートの特定のピンをLowにするのによく使われます。
- `SBRC Rr, b` (Skip if Bit in Register is Clear): 汎用レジスタRrのビットbが0の場合、次の1命令をスキップします。
- `SBRS Rr, b` (Skip if Bit in Register is Set): 汎用レジスタRrのビットbが1の場合、次の1命令をスキップします。入力ピンの状態確認などに使われます。
- `BST Rd, b` / `BLD Rd, b` (Bit Store/Load): RdのビットbをSREGのTフラグにコピー(BST) / Tフラグの内容をRdのビットbにコピー(BLD)します。ビット単位のデータ操作に使えます。
これらの命令はAVRアセンブリのほんの一部です。しかし、これらを組み合わせることで、多くの基本的な処理を実装できます。命令の正確な動作、影響を受けるフラグ、実行に必要なクロックサイクル数などを知るためには、公式の命令セットマニュアルを参照することが不可欠です。
最初のプログラム:LED点滅 (Lチカ)
理論を学んだら、次は実践です! マイコンプログラミングの「Hello, World!」とも言える、LED点滅(Lチカ)プログラムをAVRアセンブリで作成してみましょう。
目標
AVRマイコン(ここでは例としてATmega328Pを想定)の特定のピンに接続されたLEDを、一定の間隔で点滅させます。
ハードウェア設定 (概念)
物理的な接続が必要です(ここでは詳細な回路図は省略します)。
- ATmega328Pを用意します(例: Arduino Unoボード上のマイコン)。
- 例えば、デジタルピン13 (マイコンのPB5ピンに対応) にLEDを接続します。通常、電流制限抵抗(例: 220Ω~1kΩ)を直列に挿入します。LEDのアノード(長い足)をPB5ピンに、カソード(短い足)を抵抗経由でGND(グラウンド)に接続します。
- マイコンに電源(通常5V)とGNDを接続します。
- ISPプログラマを接続するためのピン(MOSI, MISO, SCK, RESET, VCC, GND)にアクセスできるようにします。
コード解説
以下は、ATmega328PのPB5ピン(Arduino Unoのデジタルピン13)に接続されたLEDを約0.5秒間隔で点滅させるアセンブリコードの例です。
コードのポイント:
- `.include “m328pdef.inc”`: マイコン固有のレジスタ名やビット名を定義したファイルを取り込みます。これにより、`PORTB` や `DDB5` のようなシンボル名が使えます。このファイルは通常、AVR-GCCツールチェーンに含まれています。
- `.def`: レジスタに別名(エイリアス)を付けます。コードが読みやすくなります。
- `.cseg`, `.org 0x0000`: コードセグメント(プログラムを配置するメモリ領域)を指定し、開始アドレスをリセットベクタである0番地に設定します。
- `rjmp main`: リセット後、すぐに`main`ラベルの処理にジャンプします。
- スタックポインタ初期化: `SPH`, `SPL`レジスタにSRAMの最終アドレスを設定します。`RCALL`や`PUSH`/`POP`命令を使う場合は必須です。`RAMEND`は定義ファイルで定義されている定数です。
- I/Oポート初期化 (`sbi DDRB, DDB5`): `DDRB`レジスタの`DDB5`ビット(5番目のビット)を1にセットします。これにより、PB5ピンが出力モードになります。`sbi`命令はI/Oアドレス0x00-0x1Fのレジスタに対して使えます (ATmega328PではDDRBは該当)。
- メインループ (`loop`):
- `sbi PORTB, PORTB5`: PB5ピンの出力をHigh (1) にします。LEDが点灯します。
- `rcall delay_ms_500`: 遅延サブルーチンを呼び出します。
- `cbi PORTB, PORTB5`: PB5ピンの出力をLow (0) にします。LEDが消灯します。
- `rcall delay_ms_500`: 再び遅延サブルーチンを呼び出します。
- `rjmp loop`: `loop`ラベルに戻り、点滅を繰り返します。
- 遅延サブルーチン (`delay_ms_500`):
- 3重のループ (`ldi`, `dec`, `brne`) を使って時間を稼ぎます。`nop` (No Operation) 命令は、何もしませんが1クロックサイクルを消費するため、微調整に使われます。
- この遅延方法は、クロック周波数に依存し、精度も高くありません。正確な時間制御にはタイマー割り込みを使うのが一般的です。
- `ret`: サブルーチンの最後に記述し、呼び出し元に戻ります。
アセンブルと書き込み (コマンドライン例)
上記コードを `blink.asm` という名前で保存した場合、avr-gccツールチェーンとavrdudeを使って以下のようにアセンブル・書き込みできます。
- アセンブル (.asm -> .o): `-mmcu`で使用するマイコンを指定します。
- リンク (.o -> .elf): (単純なプログラムならリンカは不要な場合もありますが、通常はこのステップを含めます)
- HEXファイル生成 (.elf -> .hex): `-O ihex`でIntel HEX形式を指定します。`-R .eeprom`でEEPROMセクションを除外します。
- マイコンへの書き込み (.hex -> Flash): `-c`で書き込み器の種類(例: `usbasp`, `avrispmkII`, `arduino`)、`-p`でマイコンの種類(例: `m328p`, `t2313`)、`-U flash:w:blink.hex:i`でHEXファイルをFlashメモリに書き込むことを指定します。
書き込みが成功すれば、PB5ピンに接続されたLEDがチカチカと点滅し始めるはずです! これが、AVRアセンブリプログラミングの第一歩です。
デバッグとシミュレーション
プログラムが期待通りに動作しないことはよくあります。特にアセンブリ言語のように低レベルなプログラミングでは、小さなミスが大きな問題につながることもあります。そこで重要になるのが「デバッグ」です。デバッグとは、プログラム中の誤り(バグ)を見つけて修正する作業のことです。
AVR開発では、主にシミュレータを使ったデバッグと、実機を使ったデバッグ(インサーキット・エミュレーション)があります。ここでは、特にシミュレータを用いたデバッグの基本的な手法について説明します。
デバッグの重要性
- 問題の早期発見: シミュレータを使えば、実際のハードウェアに書き込む前に、プログラムの論理的な誤りを発見できます。
- 内部状態の可視化: レジスタの値、メモリの内容、I/Oポートの状態などをステップごとに確認できるため、プログラムがどのように動作しているかを詳細に追跡できます。
- 効率的な修正: 問題箇所を特定しやすいため、修正作業を効率的に進められます。
シミュレータの使い方
AVR開発でよく使われるシミュレータには、Microchip Studioに内蔵されているものや、オープンソースのsimavrなどがあります。
Microchip Studio Simulator:
- Microchip Studioでプロジェクトを開き、ビルドしてエラーがないことを確認します。
- デバッグメニューから「Start Debugging and Break」(F5) を選択するか、ツールバーの緑色の再生ボタンの隣にあるドロップダウンから「Simulator」を選択してデバッグを開始します。
- デバッグが開始されると、コードエディタ上で現在の実行行がハイライトされ、各種デバッグウィンドウ(レジスタ、メモリ、I/Oビュー、ウォッチ式など)が表示されます。
simavr (コマンドライン):
simavrはコマンドラインツールですが、GDB(GNUデバッガ)と連携して強力なデバッグ機能を提供します。
- まず、デバッグ情報付きでELFファイルをビルドする必要があります。avr-gcc (avr-asを含む) に `-g` オプションを付けてアセンブル・リンクします。 (avr-gcc をリンカとして使うと C ライブラリ等がリンクされる場合があるので注意。単純なアセンブリなら `avr-ld -g` でも良いかもしれません。)
- 別のターミナルでsimavrをGDBサーバモードで起動します。 `-m`でマイコン、`-f`でクロック周波数、`–gdb`でGDBサーバを有効にします。ポート1234で接続を待ち受けます。
- 別のターミナルでAVR用のGDB (`avr-gdb`) を起動し、simavrに接続します。
- これでGDBのコマンドを使ってデバッグできます。
基本的なデバッグ手法
シミュレータやデバッガを使って、以下の基本的な操作を行うことができます。
-
ステップ実行 (Step Over/Step Into):
- Step Over (F10): 現在の行を実行し、次の行で停止します。サブルーチン呼び出し (`RCALL`, `CALL`) がある場合、サブルーチン全体を実行して戻ってきた次の行で停止します。
- Step Into (F11): 現在の行を実行します。サブルーチン呼び出しがある場合、そのサブルーチンの最初の行に移動して停止します。サブルーチンの内部動作を確認したい場合に使います。
- Step Out (Shift+F11): 現在実行中のサブルーチンを最後まで実行し、呼び出し元に戻った次の行で停止します。
- ブレークポイント (Breakpoints): コード中の特定の行にブレークポイントを設定すると、プログラムの実行がその行に到達した時点で一時停止します。特定の処理の前後の状態を確認したい場合に便利です。Microchip Studioでは行番号の左側の余白をクリック、GDBでは `break <ラベル名 or 行番号>` コマンドで設定できます。 (例: `break main`, `break loop`)
-
レジスタ/メモリ監視 (Watch/Memory View):
- レジスタウィンドウ: 汎用レジスタ (R0-R31)、SREG、PC、SPなどの現在の値をリアルタイムで確認できます。値が期待通りに変化しているかを確認します。
- メモリウィンドウ: SRAMやプログラムメモリ(Flash)、EEPROMの内容を指定したアドレス範囲で表示します。変数の値やスタックの状態を確認するのに役立ちます。
- I/Oビュー: DDRx, PORTx, PINx などのI/Oレジスタの状態を専用のウィンドウで確認できます。ポートの設定や入出力が正しく行われているかを確認します。
- ウォッチウィンドウ: 特定のレジスタやメモリアドレス、式を登録しておき、その値の変化を常に監視できます。
-
実行制御 (Continue/Run):
- Continue/Run (F5): プログラムの実行を再開し、次のブレークポイントに到達するかプログラムが終了するまで実行を続けます。GDBでは `continue` または `c`。
デバッグは試行錯誤のプロセスです。これらの基本的な手法を駆使して、プログラムの動作を注意深く観察し、期待とのずれを見つけることがバグ修正への近道です。焦らず、一つずつ確認していきましょう。
さらに学ぶために
AVRアセンブリの基本的な概念と「Lチカ」プログラムの作成方法を学びました。しかし、これは広大なAVRの世界の入り口に過ぎません。さらに深く学び、より複雑なプロジェクトに挑戦するためには、以下のリソースや学習ステップが役立つでしょう。
AVR 命令セットマニュアル (Instruction Set Manual)
AVRアセンブリプログラミングにおける最も重要で信頼できる情報源です。Microchip社のウェブサイトからダウンロードできます。
- 内容: 全てのAVR命令について、ニーモニック、オペランド、動作内容、影響を受けるSREGフラグ、実行に必要なクロックサイクル数、オペコード(機械語表現)などが詳細に記述されています。
- 重要性: 特定の命令の正確な動作や副作用を理解するため、また、効率的なコードを書くために不可欠です。異なるAVRデバイス間で命令セットに差異がある場合もあるため、ターゲットデバイスのマニュアルを確認することが重要です。
- 入手先: Microchip Technology公式サイト で “AVR Instruction Set Manual” を検索してください。例えば、DS40002198のようなドキュメント番号で見つかります。(直接リンク: AVR Instruction Set Manual PDF – リンク切れの可能性あり)
データシート (Datasheet)
使用する特定のAVRマイコン(例: ATmega328P, ATtiny85)のデータシートも必読です。
- 内容: ピン配置、電気的特性、メモリマップ、各内蔵ペリフェラル(タイマー、ADC、UART、SPI、I2Cなど)の詳細な機能、関連するI/Oレジスタとその設定方法などが記載されています。
- 重要性: アセンブリから特定のハードウェア機能を制御するためには、データシートのレジスタ情報が不可欠です。
- 入手先: Microchip Technology公式サイトで、使用するデバイス名(例: “ATmega328P”)で検索します。
オンラインリソースとコミュニティ
- チュートリアルサイト:
- AVR Freaks: AVRに関するフォーラム、プロジェクト、チュートリアルなどが豊富なコミュニティサイト(英語)。
- Instructables AVR Assembler Tutorials: コマンドラインツールを使った実践的なチュートリアル集(英語)。
- 国内の個人ブログや技術サイトにも、AVRアセンブリに関する記事が多数存在します(例: 「AVR アセンブリ チュートリアル」などで検索)。
- 書籍: AVRマイコンやアセンブリ言語に関する入門書や解説書も参考になります(ただし、情報は最新か確認が必要です)。
実践的なプロジェクト
知識を定着させる最良の方法は、実際に手を動かして何かを作ってみることです。
- タイマーを使った正確なLED点滅: 遅延ループではなく、内蔵タイマーを使ってより正確な周期でLEDを点滅させてみましょう。タイマー割り込みを使うと、点滅させながら他の処理も行えます。
- スイッチ入力の読み取り: スイッチを接続し、その状態を読み取ってLEDの点灯・消灯を制御します。チャタリング対策(ソフトウェアデバウンス)も実装してみましょう。
- 7セグメントLEDの制御: 数字を表示できる7セグメントLEDを制御してみます。ダイナミック点灯方式にも挑戦してみましょう。
- UART通信: PCとシリアル通信を行い、マイコンからメッセージを送信したり、PCからのコマンドを受信したりします。
- ADC (アナログ-デジタル変換): 可変抵抗(ポテンショメータ)やセンサーからのアナログ電圧を読み取り、その値に応じてLEDの明るさを変える(PWM制御)などの処理をします。
小さなプロジェクトから始めて、徐々に複雑なものに挑戦していくことで、AVRアセンブリのスキルを着実に向上させることができます。
まとめ
この記事では、AVRアセンブリ言語の入門として、その基礎となるAVRマイコンのアーキテクチャ、開発環境の準備、基本的な命令セット、そして簡単な「Lチカ」プログラムの作成とデバッグ手法について解説しました。
アセンブリ言語の学習は、一見すると難しく、現代のソフトウェア開発の主流からは外れているように見えるかもしれません。しかし、その学習を通じて得られるハードウェアへの深い理解、コード最適化の技術、そして問題解決能力は、どのようなレベルのプログラミングにおいても必ず役立つ貴重なスキルとなります。特にリソースが限られた組み込みシステムの世界では、アセンブリの知識が決定的な差を生むこともあります。
最初は戸惑うことも多いかもしれませんが、命令セットマニュアルやデータシートを片手に、実際にコードを書き、シミュレータや実機で動かしてみる経験を重ねることが重要です。小さな成功体験を積み重ねながら、AVRアセンブリの世界を探求してみてください。きっと、コンピュータがどのように動いているのか、その本質に触れる面白さを発見できるはずです。
Happy Assembling!
参考情報
より詳細な情報については、以下の公式ドキュメント等を参照してください。
- Microchip Technology 公式サイト: https://www.microchip.com/ (各種データシート、命令セットマニュアル、開発ツールなどが入手可能)
- AVR Instruction Set Manual (例): https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-Instruction-Set-Manual-DS40002198.pdf (リンクは変更される可能性があります)
- AVR Freaks Community: https://www.avrfreaks.net/ (フォーラムやチュートリアル)