PICマイコン アセンブリ入門 💡

プログラミング

このブログへようこそ!ここでは、組み込みシステムの世界で広く使われているPIC(ピック)マイクロコントローラ(マイコン)のアセンブリ言語プログラミングについて、初心者の方にも分かりやすく解説していきます。

「マイコンって難しそう…」「アセンブリ言語なんて、もっと分からない…」と思っている方もいるかもしれません。でも、心配はいりません!😊 PICマイコンは、比較的シンプルな構造を持ち、アセンブリ言語も命令数が少ない(RISCアーキテクチャ)ため、基本から学べば誰でも理解できるようになります。

なぜ今、アセンブリ言語なのでしょうか? C言語などの高級言語が主流の時代ですが、アセンブリ言語を学ぶことで、以下のようなメリットがあります。

  • マイコンの動作原理を深く理解できる: ハードウェアに近いレベルでプログラムを書くため、マイコンが内部でどのように動いているかを直接的に把握できます。
  • リソースを最大限に活用できる: メモリ使用量や実行速度を細かく制御できるため、リソースが限られた小さなマイコンでも効率的なプログラムを作成できます。
  • 他の言語学習の基礎となる: コンピュータの基本的な仕組みを理解することは、C言語や他のプログラミング言語を学ぶ上でも非常に役立ちます。

この入門ブログでは、PICマイコンの概要から始め、開発環境の準備、基本的なアセンブリ命令、そして簡単なプログラム(LEDチカチカなど)の作成までをステップバイステップで解説します。回路を組むのが難しい方でも、シミュレータを使ってPC上で動作を確認できるので安心してください。

さあ、PICアセンブリの世界を探求し、モノを動かす楽しさを体験しましょう!🚀

PIC(ピック)は、Microchip Technology社が開発・製造しているマイクロコントローラ(マイコン)のファミリー名です。元々はPeripheral Interface Controllerの略でしたが、現在は公式な略称ではありません。シンプルながら高機能で、ホビー用途から産業機器まで、世界中で非常に多くの組み込みシステムに採用されています。

PICマイコンの特徴

  • 種類が豊富: 8ピンの小さなものから100ピンを超える大規模なものまで、機能や性能に応じて様々なデバイスファミリーが存在します。(PIC10, PIC12, PIC16, PIC18, PIC24, dsPIC, PIC32など)
  • 低価格・省電力: 比較的手頃な価格で入手でき、低消費電力のモデルも多いです。
  • RISCアーキテクチャ: 命令セットが比較的少なく(例:PIC16F84Aは35命令)、シンプルな構造をしています。ただし、命令によっては動作が直感的でない部分もあります。
  • Harvardアーキテクチャ: プログラムメモリとデータメモリが物理的に分離されており、同時にアクセスできるため高速な処理が可能です。
  • 内蔵ペリフェラル: タイマー、A/Dコンバータ、シリアル通信(UART, I2C, SPI)、PWMなど、多くの周辺機能がチップに内蔵されており、外付け部品を少なくできます。
  • 開発環境: Microchip社から無償の統合開発環境(IDE)であるMPLAB X IDEが提供されており、開発を始めやすいです。

PICファミリーの概要

PICマイコンは、コアアーキテクチャや性能によっていくつかのファミリーに分類されます。

ファミリー コア データメモリ幅 命令長 特徴
PIC10, PIC12 (ベースライン) 8ビット 8ビット 12ビット 最もシンプル、低ピン数、低価格
PIC16 (ミッドレンジ) 8ビット 8ビット 14ビット 最も一般的、豊富なラインナップ、機能とコストのバランスが良い (例: PIC16F84A, PIC16F628A, PIC16F877A)
PIC18 (ハイエンド) 8ビット 8ビット 16ビット 高性能8ビット、命令セット拡張、C言語向き、USBやCAN搭載品あり (例: PIC18F4550)
PIC24, dsPIC 16ビット 16ビット 24ビット 16ビット処理、dsPICはDSP機能強化
PIC32 (MX, MZ, MM, MK, …) 32ビット (MIPS or Arm) 32ビット 32ビット (MIPS/Arm) 高性能32ビット、複雑なアプリケーション向け

この入門では、特に歴史が長く情報も豊富なミッドレンジのPIC16ファミリー(例えばPIC16F84AやPIC16F628Aなど)を中心に解説を進めますが、基本的な概念は他のファミリーにも応用できます。

アセンブリ言語は、コンピュータ(この場合はPICマイコン)が直接理解できる機械語(0と1の羅列)に非常に近い、低水準プログラミング言語です。機械語の命令一つ一つに、人間が理解しやすい「ニーモニック(Mnemonic)」と呼ばれる短い英単語(例: `MOV`, `ADD`, `GOTO`)を対応させたものです。


機械語   <---> アセンブリ言語 <---> C言語/BASICなど (高級言語)
(010110...)    (MOVWF, ADDLW)       (a = b + 5;)
      

アセンブラ(Assembler)と呼ばれるソフトウェアが、私たちが書いたアセンブリ言語のコード(ソースコード)を、PICマイコンが実行できる機械語のコード(オブジェクトコード、通常はHEXファイル形式)に変換(アセンブル)します。

PICアセンブリの構成要素

PICのアセンブリプログラムは、主に以下の要素で構成されます。

  • ラベル (Label): プログラム中の特定の場所(アドレス)や定数に名前を付けるために使います。通常、行の先頭に記述し、コロン(:)を付ける場合と付けない場合があります(アセンブラによる)。例: `MAIN:`, `LOOP`, `COUNTER`。
  • ニーモニック (Mnemonic): マイコンに実行させたい命令を表す短い英単語です。オペコード(Opcode)とも呼ばれます。例: `MOVLW` (リテラル値をWレジスタにロード), `MOVWF` (Wレジスタの値をファイルレジスタに書き込み), `GOTO` (指定したラベルへジャンプ)。
  • オペランド (Operand): 命令の対象となるデータやアドレスを指定します。命令によって必要なオペランドの数や種類は異なります。例: `MOVLW 0x0F` (0x0Fがオペランド), `MOVWF PORTB` (PORTBがオペランド)。
  • コメント (Comment): プログラムの動作を説明するための注釈です。通常、セミコロン(;)以降の文字列はコメントとして扱われ、アセンブル時には無視されます。プログラムを分かりやすくするために重要です。
  • ディレクティブ (Directive): アセンブラに対する指示です。疑似命令とも呼ばれます。PICマイコンの命令(機械語)に直接変換されるわけではありませんが、アセンブルの方法を制御します。例: `PROCESSOR` (使用するPICデバイスを指定), `INCLUDE` (他のファイルを読み込む), `EQU` (定数を定義), `ORG` (プログラムの開始アドレスを指定), `END` (ソースコードの終わりを示す)。

一般的な命令の書式は以下のようになります(空白の数は重要ではありませんが、見やすさのために揃えるのが一般的です)。


LABEL    MNEMONIC    OPERAND(s)    ; COMMENT
MAIN:    MOVLW       0x55          ; Wレジスタに55hをロードする
         MOVWF       PORTB         ; Wレジスタの内容をPORTBへ書き込む
LOOP     GOTO        LOOP          ; 無限ループ
      

PICの基本構成:レジスタとメモリ

PICマイコン(特にPIC16ファミリー)をアセンブリで扱う上で、以下の要素を理解することが重要です。

  • Wレジスタ (Working Register): ワーキングレジスタ、またはアキュムレータとも呼ばれます。演算の中心となる8ビットのレジスタです。多くの命令は、このWレジスタとファイルレジスタ(後述)の間、またはWレジスタとリテラル(即値)の間でデータをやり取りします。
  • ファイルレジスタ (File Register): PIC内部のデータメモリ領域(RAM)や、ポート、タイマーなどの周辺機能を制御するための特殊機能レジスタ(SFR: Special Function Register)を総称してファイルレジスタと呼びます。PIC16F84Aでは68バイトの汎用RAMとSFRがあります。
  • プログラムメモリ (Program Memory): 作成したプログラム(機械語)が格納されるメモリです。PIC16F84Aでは1Kワード(1ワード=14ビット)のフラッシュメモリが搭載されています。
  • データメモリ (Data Memory / RAM): プログラム実行中にデータを一時的に保存するためのメモリ(汎用レジスタ)と、マイコンの機能を設定・制御するためのSFRが含まれます。PIC16ファミリーでは、データメモリは複数の「バンク」に分かれており、アクセスしたいレジスタが存在するバンクを適切に切り替える必要があります(バンク切り替え)。
  • ステータスレジスタ (STATUS Register): 演算結果の状態(ゼロになったか、桁上げが発生したかなど)を示すフラグ(Zフラグ, Cフラグ, DCフラグ)や、データメモリのバンクを選択するためのビット(RP0, RP1)などが含まれる重要なSFRです。
  • プログラムカウンタ (PC: Program Counter): 次に実行する命令が格納されているプログラムメモリアドレスを指し示すレジスタです。GOTO命令やCALL命令などで内容が変更されます。
  • スタック (Stack): サブルーチン呼び出し(CALL命令)や割り込み発生時に、プログラムカウンタの値を一時的に退避させておくための領域です。PIC16ファミリーではハードウェアスタックで、階層数に制限があります(例:PIC16F84Aは8レベル)。
💡 ポイント: PIC16ファミリーでは、ファイルレジスタ間の直接データ転送はできません。多くの場合、一度Wレジスタを経由させる必要があります。 (例: PORTAの値をPORTBにコピーする場合、`MOVF PORTA, W` → `MOVWF PORTB` のようにします)

PICマイコンのアセンブリプログラミングを始めるには、いくつかのソフトウェアと、場合によってはハードウェアが必要です。ここでは、Microchip社が提供する標準的な開発環境「MPLAB X IDE」を中心に説明します。

ソフトウェア

  1. MPLAB X IDE (Integrated Development Environment):
    Microchip社が無料で提供している統合開発環境です。プログラムの編集、アセンブル(コンパイル)、デバッグ(シミュレーション)、PICへの書き込みなどを一貫して行えます。Windows, macOS, Linuxに対応しています。Microchip社のウェブサイトからダウンロードしてインストールします。
  2. アセンブラ (Assembler):
    アセンブリ言語のソースコードを機械語(HEXファイル)に変換するソフトウェアです。MPLAB X IDEには、現在主流の `pic-as` (XC8コンパイラに同梱) や、旧来の `MPASM` が含まれています。プロジェクト作成時にどちらを使用するか選択します。最近の開発では `pic-as` が推奨されています (2023年11月時点の情報)。`pic-as` を使う場合、MPLAB X IDEのインストール時にXC8コンパイラも一緒にインストールする必要があります(無料モードでOK)。
  3. テキストエディタ:
    MPLAB X IDEには内蔵エディタがありますが、使い慣れた別のテキストエディタでソースファイル(.asm)を作成し、MPLAB X IDEのプロジェクトに追加することも可能です。ただし、プレーンテキスト(ASCII)形式で保存する必要があります。
⚠️ 注意: 以前広く使われていたアセンブラ `MPASM` は、Microchip社によるサポートが終了しており、新しいデバイスには対応していません。新規開発では `pic-as` (MPLAB XC8 PIC Assembler) の使用が推奨されます。本書では両方のアプローチに触れる可能性がありますが、主に新しい `pic-as` を中心に説明します。

ハードウェア(オプション)

  • PICマイコン:
    実際にプログラムを動かすためのPICチップです。入門用としてはPIC16F84A, PIC16F628A, PIC16F1xxxシリーズなどがよく使われます。
  • プログラマ/デバッガ (Programmer/Debugger):
    PCで作成したプログラム(HEXファイル)をPICマイコンに書き込むための装置です。また、プログラムを実行しながらマイコン内部の状態を確認するデバッグ機能も持っています。Microchip純正の `PICkit` シリーズ(PICkit 4, PICkit 5 など)が手頃な価格で入手しやすく、広く使われています。古いものとしてはPICkit 2やPICkit 3もあります。秋月電子通商のPICプログラマなども有名です。
  • ブレッドボードと部品:
    PICマイコン、LED、抵抗、スイッチ、発振子(クリスタルやセラロック)、コンデンサなどを配線し、回路を組むためのブレッドボードやジャンパーワイヤが必要です。電源(通常5Vまたは3.3V)も必要になります。PICkitはターゲット回路に電源を供給できる場合もあります。

MPLAB X IDEの基本的な使い方

  1. プロジェクトの作成:
    • MPLAB X IDEを起動し、[File] -> [New Project] を選択します。
    • [Microchip Embedded] カテゴリの [Standalone Project] を選びます。
    • 使用するPICデバイス(例: PIC16F84A)を選択します。
    • 使用するツール(Hardware Tool)を選択します。最初はPC上でシミュレーションを行う [Simulator] を選ぶのが良いでしょう。実際に書き込む場合は [PICkit 4] などを選びます。
    • 使用するコンパイラ/アセンブラを選択します。アセンブリ言語の場合は `pic-as` (XC8に含まれる) または `mpasm` を選択します。
    • プロジェクト名と保存場所を指定します。文字コード(Encoding)は `UTF-8` を選択すると良いでしょう。
    • [Finish] をクリックしてプロジェクトを作成します。
  2. ソースファイルの作成・追加:
    • 左側の [Projects] ペインで、作成したプロジェクト内の [Source Files] フォルダを右クリックし、[New] -> [AssemblyFile.asm] (または [Other…] から Assembler -> AssemblyFile.asm) を選択します。
    • ファイル名(例: `main.asm`)を入力して [Finish] をクリックすると、エディタが開きます。
    • ここにアセンブリコードを記述します。
    • 既存のファイルをプロジェクトに追加する場合は、[Source Files] を右クリックして [Add Existing Item…] を選択します。
  3. アセンブル(ビルド):
    • コードを記述したら、ツールバーのハンマーとほうきのアイコン(Clean and Build Project)またはハンマーアイコン(Build Project)をクリックします。
    • 画面下部の [Output] ウィンドウにビルドの進行状況と結果が表示されます。エラーがあれば修正します。
    • 成功すると、プロジェクトフォルダ内の `dist/default/production` (または `debug`) フォルダに `プロジェクト名.X.production.hex` という名前のHEXファイルが生成されます。
  4. シミュレーション:
    • Hardware ToolでSimulatorを選択した場合、デバッグ実行(虫アイコンの付いた再生ボタン)を行うと、シミュレータが起動します。
    • [Window] -> [Simulator] メニューから [Stimulus] (入力信号の模擬) や [Analyzer] (信号波形の表示) などの機能を使えます。
    • [Window] -> [PIC Memory Views] から [File Registers] (データメモリ/SFRの内容表示) や [Program Memory] (プログラムメモリの内容表示) を確認できます。
    • ステップ実行(F7キー、F8キー)やブレークポイント設定などでプログラムの動作を追跡できます。
  5. PICへの書き込み:
    • Hardware ToolでPICkitなどを選択し、PICマイコンとPCを接続します。
    • ツールバーの「Make and Program Device Main Project」(下向き矢印の付いたICチップのアイコン)をクリックすると、ビルド後に自動的に書き込みが開始されます。

まずはMPLAB X IDEとアセンブラ(XC8コンパイラ)をインストールし、シミュレータを使ってプログラムを動かしてみることから始めるのがおすすめです。

PICマイコン(特にミッドレンジのPIC16ファミリー)の命令セットは比較的少ないですが、いくつか基本的な命令を覚えることで様々な動作を実現できます。ここでは、よく使われる命令をカテゴリ別に紹介します。

命令の説明中で使われる記号の意味は以下の通りです。

  • `f`: ファイルレジスタのアドレス (0〜127など、デバイスによる)
  • `W` or `w`: Wレジスタ (Working Register)
  • `d`: 結果の格納先 (Destination)。`d=0` または `d=W` ならWレジスタへ、`d=1` または `d=F` ならファイルレジスタ `f` へ格納。
  • `k`: リテラル値(即値、定数)。通常8ビット。
  • `b`: ビット番号 (0〜7)

1. データ転送命令 (Move Instructions)

データの移動や読み込み、書き込みを行います。

ニーモニック オペランド 機能 説明
MOVLW k Move Literal to W 8ビットのリテラル値 `k` をWレジスタにロード(コピー)します。
MOVWF f Move W to f Wレジスタの内容を指定されたファイルレジスタ `f` に書き込み(コピー)します。
MOVF f, d Move f ファイルレジスタ `f` の内容を、指定された格納先 `d`(Wレジスタまたはファイルレジスタ `f` 自身)にコピーします。d=W (または d=0) で `f` の内容をWレジスタに読み込みます。d=F (または d=1) で `f` の内容を `f` 自身に書き戻します(主にZフラグのチェックに使用)。
CLRW Clear W Wレジスタの内容をゼロ (0x00) にします。MOVLW 0 と同じ効果です。
CLRF f Clear f 指定されたファイルレジスタ `f` の内容をゼロ (0x00) にします。
SWAPF f, d Swap Nibbles in f ファイルレジスタ `f` の上位4ビットと下位4ビットを入れ替えて、結果を `d` に格納します。

2. 算術演算命令 (Arithmetic Instructions)

加算や減算を行います。

ニーモニック オペランド 機能 説明 影響を受けるフラグ
ADDLW k Add Literal and W リテラル値 `k` とWレジスタの内容を加算し、結果をWレジスタに格納します。 C, DC, Z
ADDWF f, d Add W and f Wレジスタの内容とファイルレジスタ `f` の内容を加算し、結果を `d` に格納します。 C, DC, Z
SUBLW k Subtract W from Literal リテラル値 `k` からWレジスタの内容を減算し、結果をWレジスタに格納します (k – W → W)。 C, DC, Z
SUBWF f, d Subtract W from f ファイルレジスタ `f` の内容からWレジスタの内容を減算し、結果を `d` に格納します (f – W → d)。 C, DC, Z
INCF f, d Increment f ファイルレジスタ `f` の内容を1増やし、結果を `d` に格納します。 Z
DECF f, d Decrement f ファイルレジスタ `f` の内容を1減らし、結果を `d` に格納します。 Z
💡 フラグについて:
  • C (Carry) フラグ: 演算結果が8ビットを超えて桁あふれした場合(加算)や、借りが発生した場合(減算)にセット(1)されます。
  • DC (Digit Carry) フラグ: 演算結果の下位4ビットから上位4ビットへの桁あふれ/借りが発生した場合にセットされます。BCD演算などで使われます。
  • Z (Zero) フラグ: 演算結果がゼロになった場合にセット(1)されます。
これらのフラグは、後述する条件分岐命令で利用されます。

3. 論理演算命令 (Logical Instructions)

AND, OR, XORなどの論理演算やビット操作を行います。

ニーモニック オペランド 機能 説明 影響を受けるフラグ
ANDLW k AND Literal with W リテラル値 `k` とWレジスタの内容のビットごとの論理積 (AND) をとり、結果をWレジスタに格納します。 Z
ANDWF f, d AND W with f Wレジスタの内容とファイルレジスタ `f` の内容のビットごとの論理積 (AND) をとり、結果を `d` に格納します。 Z
IORLW k Inclusive OR Literal with W リテラル値 `k` とWレジスタの内容のビットごとの論理和 (OR) をとり、結果をWレジスタに格納します。 Z
IORWF f, d Inclusive OR W with f Wレジスタの内容とファイルレジスタ `f` の内容のビットごとの論理和 (OR) をとり、結果を `d` に格納します。 Z
XORLW k Exclusive OR Literal with W リテラル値 `k` とWレジスタの内容のビットごとの排他的論理和 (XOR) をとり、結果をWレジスタに格納します。 Z
XORWF f, d Exclusive OR W with f Wレジスタの内容とファイルレジスタ `f` の内容のビットごとの排他的論理和 (XOR) をとり、結果を `d` に格納します。 Z
COMF f, d Complement f ファイルレジスタ `f` の内容の各ビットを反転(0→1, 1→0)し、結果を `d` に格納します。(1の補数) Z
BCF f, b Bit Clear f ファイルレジスタ `f` の指定されたビット `b` をクリア (0に) します。
BSF f, b Bit Set f ファイルレジスタ `f` の指定されたビット `b` をセット (1に) します。
RLF f, d Rotate Left f through Carry ファイルレジスタ `f` の内容をキャリーフラグ(C)を通して左に1ビット回転させ、結果を `d` に格納します。 C
RRF f, d Rotate Right f through Carry ファイルレジスタ `f` の内容をキャリーフラグ(C)を通して右に1ビット回転させ、結果を `d` に格納します。 C

4. プログラムフロー制御命令 (Control Flow Instructions)

プログラムの実行順序を変更します。

ニーモニック オペランド 機能 説明
GOTO k (label) Go To address 指定されたラベル `k` のアドレスへ無条件にジャンプします。
CALL k (label) Call Subroutine 指定されたラベル `k` のアドレスにあるサブルーチンを呼び出します。戻りアドレスがスタックに積まれます。
RETURN Return from Subroutine サブルーチンから呼び出し元の次の命令へ戻ります。スタックから戻りアドレスを取り出します。
RETLW k Return with Literal in W リテラル値 `k` をWレジスタにロードしてから、サブルーチンから戻ります。テーブル参照などでよく使われます。
RETFIE Return from Interrupt 割り込み処理ルーチンから戻ります。戻りアドレスをスタックから取り出し、割り込み許可フラグ(GIE)をセットします。
BTFSC f, b Bit Test f, Skip if Clear ファイルレジスタ `f` の指定されたビット `b` をテストし、もしビットがクリア (0) ならば、次の命令をスキップ(実行しない)します。ビットがセット (1) なら、次の命令を実行します。
BTFSS f, b Bit Test f, Skip if Set ファイルレジスタ `f` の指定されたビット `b` をテストし、もしビットがセット (1) ならば、次の命令をスキップします。ビットがクリア (0) なら、次の命令を実行します。
DECFSZ f, d Decrement f, Skip if 0 ファイルレジスタ `f` の内容を1減らし、結果を `d` に格納します。もし結果がゼロになったら、次の命令をスキップします。ループカウンタなどでよく使われます。
INCFSZ f, d Increment f, Skip if 0 ファイルレジスタ `f` の内容を1増やし、結果を `d` に格納します。もし結果がゼロになったら(オーバーフローして0に戻った場合)、次の命令をスキップします。
NOP No Operation 何もせず、1命令サイクルだけ時間を消費します。タイミング調整などに使われます。

これらの命令を組み合わせることで、様々な処理を実現できます。最初は簡単な命令から少しずつ使ってみましょう! 😊

理論ばかりでは面白くないので、実際に簡単なプログラムを作成してみましょう!マイコン入門の定番、「Lチカ」(LEDチカチカ)です。ここでは、PIC16F84Aを例に、ポートBの0番ピン(RB0)に接続されたLEDを約1秒間隔で点滅させるプログラムを作成します。

(回路図は省略しますが、PIC16F84AのRB0ピン(6番ピン)に抵抗(例: 330Ω)を介してLEDのアノードを接続し、LEDのカソードをGNDに接続する構成を想定します。また、適切な発振回路と電源(5V)、リセット回路が必要です。)

サンプルコード (MPASM/pic-as共通風)

以下は、MPASMとpic-asの両方で比較的動作しやすいように記述したサンプルコードです。(一部ディレクティブは使用するアセンブラに合わせて調整が必要な場合があります)


; PIC16F84A LED Blinking Example
; Target Device: PIC16F84A
; Clock: 4MHz (External Crystal or Resonator assumed)

; ***** Configuration Bits *****
; For MPASM (older style)
; __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC
; For pic-as (newer style, using CONFIG directive in source or Configuration Bits window in MPLAB X)
; CONFIG FOSC = XT        ; XT Oscillator
; CONFIG WDTE = OFF       ; Watchdog Timer disabled
; CONFIG PWRTE = ON       ; Power-up Timer enabled
; CONFIG CP = OFF         ; Code Protection disabled

; ***** Includes *****
; For MPASM
; #include <p16F84A.inc>
; For pic-as (included by default or via MPLAB X project settings)
#include <xc.inc> ; For pic-as standard SFR definitions

; ***** Variable Definition (using CBLOCK for MPASM, EQU for pic-as style) *****
; For MPASM style:
; CBLOCK 0x0C      ; Start of General Purpose RAM
;   COUNT1         ; Delay loop counter 1
;   COUNT2         ; Delay loop counter 2
; ENDC
; For pic-as style (define variables in RAM):
PSECT udata_shr     ; Define variables in shared RAM section
COUNT1: DS 1        ; Reserve 1 byte for COUNT1
COUNT2: DS 1        ; Reserve 1 byte for COUNT2

; ***** Reset Vector *****
PSECT resetVec,class=CODE,reloc=2 ; pic-as style reset vector at 0x00
ORG     0x000       ; Program start address (Reset Vector) for MPASM
    goto    Init    ; Jump to Initialization routine

; ***** Interrupt Vector (Not used in this example) *****
; ORG     0x004       ; Interrupt Vector for MPASM
;    retfie          ; Return from interrupt

; ***** Initialization Routine *****
PSECT code          ; Start of code section for pic-as
Init:
    ; Configure PORTB Pin 0 (RB0) as Output
    bsf     STATUS, RP0     ; Select Bank 1 (STATUS register, bit 5)
                            ; For pic-as, BANKSEL TRISB is preferred:
                            ; BANKSEL TRISB
    movlw   b'11111110'     ; Set RB0 as output (0), others as input (1)
    movwf   TRISB           ; Write to TRISB register
    bcf     STATUS, RP0     ; Select Bank 0 (STATUS register, bit 5)
                            ; For pic-as, BANKSEL PORTB is preferred:
                            ; BANKSEL PORTB

    ; Initial state: Turn LED OFF (set RB0 to 0)
    clrf    PORTB           ; Clear PORTB (all pins to 0)

    goto    MainLoop        ; Jump to the main loop

; ***** Main Loop *****
MainLoop:
    ; Turn LED ON (Set RB0 high)
    bsf     PORTB, 0        ; Set bit 0 of PORTB to 1

    ; Wait for approx. 0.5 second
    call    Delay_ms

    ; Turn LED OFF (Set RB0 low)
    bcf     PORTB, 0        ; Clear bit 0 of PORTB to 0

    ; Wait for approx. 0.5 second
    call    Delay_ms

    goto    MainLoop        ; Repeat the loop

; ***** Delay Subroutine (Approx. 0.5 sec for 4MHz clock) *****
Delay_ms:
    ; This delay is approximate and depends on clock frequency
    ; Delay = COUNT1 * COUNT2 * ~3 cycles per inner loop + overhead
    ; For 0.5 sec (500,000 cycles @ 1 MIPS), need ~166,667 loops
    ; Let COUNT1 = 250, COUNT2 = ~222 (approx)
    movlw   d'250'          ; Load COUNT1 with 250 (decimal)
    movwf   COUNT1
Delay_OuterLoop:
    movlw   d'222'          ; Load COUNT2 with 222 (decimal)
    movwf   COUNT2
Delay_InnerLoop:
    nop                     ; No operation (consumes 1 cycle)
    nop                     ; No operation (consumes 1 cycle)
    decfsz  COUNT2, F       ; Decrement COUNT2, skip next if zero
    goto    Delay_InnerLoop ; Repeat inner loop
    decfsz  COUNT1, F       ; Decrement COUNT1, skip next if zero
    goto    Delay_OuterLoop ; Repeat outer loop
    return                  ; Return from subroutine

; ***** End of Program *****
    END                     ; End directive for the assembler
      

コードの解説

  1. コメントと設定: 最初の数行はコメントで、プログラムの目的や対象デバイスを記述します。
  2. コンフィギュレーションビット: `__CONFIG` (MPASM) や `CONFIG` (pic-as) ディレクティブ、またはMPLAB X IDEの専用ウィンドウで、発振器の種類(XT=クリスタル)、ウォッチドッグタイマー(無効)、パワーアップタイマー(有効)、コード保護(無効)などを設定します。これはPICの基本的な動作モードを決める重要な設定です。
  3. インクルードファイル: `` (MPASM) や `` (pic-as) は、`PORTB`, `STATUS`, `TRISB` などのSFR名やビット名を定義しているヘッダファイルです。これを読み込むことで、アドレスの数字の代わりに分かりやすい名前を使えます。
  4. 変数定義: `CBLOCK`/`ENDC` (MPASM) や `PSECT udata_shr`/`DS` (pic-as) を使って、遅延処理で使用するカウンタ変数 `COUNT1`, `COUNT2` を汎用RAM領域(PIC16F84Aでは0x0C番地以降)に割り当てます。
  5. リセットベクタ (`ORG 0x000` / `PSECT resetVec`): PICは電源投入時やリセット時にプログラムメモリの0番地から実行を開始します。ここには通常、初期化処理へのジャンプ命令 (`goto Init`) を置きます。
  6. 初期化 (`Init`):
    • ポート設定:
      • `bsf STATUS, RP0` (または `BANKSEL TRISB`) でデータメモリのバンク1を選択します。ポートの方向を設定する `TRISB` レジスタはバンク1にあるためです。
      • `movlw b’11111110’` で、Wレジスタに `0b11111110` をロードします。TRISレジスタでは、ビットが `0` なら出力、`1` なら入力に対応します。これでRB0ピンのみが出力、他のポートBピンは入力になります。
      • `movwf TRISB` でWレジスタの内容を `TRISB` に書き込みます。
      • `bcf STATUS, RP0` (または `BANKSEL PORTB`) でバンク0に戻します。実際にポートに値を書き込む `PORTB` レジスタはバンク0にあるためです。
    • 初期状態: `clrf PORTB` でポートBの全ピンをLow (0V) にし、LEDを消灯状態から始めます。
    • `goto MainLoop` でメインループへジャンプします。
  7. メインループ (`MainLoop`):
    • `bsf PORTB, 0` で `PORTB` レジスタの0番目のビットをセット (1) します。これによりRB0ピンがHigh (5V) になり、LEDが点灯します。
    • `call Delay_ms` で約0.5秒の遅延サブルーチンを呼び出します。
    • `bcf PORTB, 0` で `PORTB` レジスタの0番目のビットをクリア (0) します。これによりRB0ピンがLow (0V) になり、LEDが消灯します。
    • `call Delay_ms` で再び約0.5秒待ちます。
    • `goto MainLoop` でループの最初に戻り、点灯・消灯を繰り返します。
  8. 遅延サブルーチン (`Delay_ms`):
    • `movlw`/`movwf` でカウンタ変数 `COUNT1`, `COUNT2` に初期値を設定します。
    • `decfsz COUNT2, F` / `goto Delay_InnerLoop`: `COUNT2` を1減らし、ゼロでなければ `Delay_InnerLoop` に戻る、という処理を繰り返す内部ループです。`decfsz` はデクリメント結果がゼロになったら次の `goto` をスキップします。
    • `decfsz COUNT1, F` / `goto Delay_OuterLoop`: 内部ループが終わるごとに `COUNT1` を1減らし、ゼロでなければ `Delay_OuterLoop` に戻って内部ループを再度実行する外部ループです。
    • `nop` は時間稼ぎのための命令です。
    • `return` でサブルーチンを終了し、呼び出し元に戻ります。
    • この2重ループによって、一定時間の遅延を作り出します。ループ回数や `nop` の数は、使用するクロック周波数に合わせて調整が必要です。
  9. プログラム終了 (`END`): アセンブラにソースコードの終わりを伝えます。

このコードをMPLAB X IDEでビルドし、シミュレータで実行したり、PICkitでPIC16F84Aに書き込んだりして動作を確認してみてください。LEDが点滅すれば成功です! 🎉

基本的な命令やプログラムの書き方が分かってきたところで、PICアセンブリプログラミングにおいて特に重要となるいくつかの概念について解説します。

1. バンク切り替え (Bank Switching)

PIC16ファミリーなど、一部のPICマイコンでは、データメモリ(RAMとSFR)が複数の「バンク」と呼ばれる領域に分割されています。例えばPIC16F84Aでは、バンク0とバンク1の2つのバンクがあります。

  • バンク0: 主にデータ操作に使われるレジスタ(PORTA, PORTBなど)や汎用RAMの一部が含まれます。
  • バンク1: 主にマイコンの設定に使われるレジスタ(TRISA, TRISB, OPTION_REGなど)や汎用RAMの残りなどが含まれます。

アクセスしたいファイルレジスタが存在するバンクを、プログラム中で明示的に選択する必要があります。これを行わないと、意図しないレジスタにアクセスしてしまい、プログラムが正しく動作しません。

バンクの切り替えは、`STATUS`レジスタ内のバンク選択ビット(PIC16F84AではRP0ビット、他のデバイスではRP0とRP1ビットなど)を操作して行います。

  • バンク1を選択: `bsf STATUS, RP0` (STATUSレジスタのビット5をセット)
  • バンク0を選択: `bcf STATUS, RP0` (STATUSレジスタのビット5をクリア)

最近の `pic-as` アセンブラでは、より直感的な `BANKSEL` ディレクティブが用意されています。これを使うと、アクセスしたいレジスタ名を指定するだけで、アセンブラが自動的に適切なバンク切り替え命令(`bsf`や`bcf`)を生成してくれます。


; pic-as で BANKSEL を使用する例
BANKSEL TRISB     ; TRISBレジスタがあるバンク (バンク1) を選択
movlw   b'11110000'
movwf   TRISB     ; TRISBに書き込み

BANKSEL PORTB     ; PORTBレジスタがあるバンク (バンク0) を選択
movlw   0xAA
movwf   PORTB     ; PORTBに書き込み
      

`BANKSEL` を使うことで、どのレジスタがどのバンクにあるかを常に意識する必要がなくなり、コードが読みやすくなります。可能な限り `BANKSEL` を使用することをお勧めします。

2. ステータスレジスタ (STATUS Register) とフラグ

`STATUS`レジスタは、PICマイコンの状態を示す重要なレジスタです。特に以下のフラグビットが演算命令や条件分岐命令と密接に関連しています。

ビット 名前 機能
0 C (Carry) 算術演算(加算、減算)の結果、キャリー(桁上げ)またはボロー(借り)が発生した場合に1になる。ローテート命令でも使用。
1 DC (Digit Carry) 算術演算の結果、下位4ビットから上位4ビットへのキャリー/ボローが発生した場合に1になる。BCD演算で使用。
2 Z (Zero) 算術演算または論理演算の結果がゼロになった場合に1になる。
3 PD (Power-down) SLEEP命令実行後に0になる。ウォッチドッグタイマーリセットなどで1に戻る。
4 TO (Time-out) ウォッチドッグタイマーがタイムアウトした場合に0になる。通常は1。
5 RP0 (Register Bank Select bit 0) データメモリのバンクを選択するビット(PIC16F84Aの場合)。RP1と合わせて使用するデバイスもある。
6 RP1 (Register Bank Select bit 1) データメモリのバンク選択ビット(PIC16F84Aでは未使用、PIC16F877Aなどで使用)。
7 IRP (Register Bank Select bit) 間接アドレッシング時のバンク選択ビット(PIC16F84Aでは未使用)。

特に C, DC, Z フラグは、`BTFSC` や `BTFSS` 命令と組み合わせて条件判断を行う際に頻繁に使用されます。例えば、「演算結果がゼロだったらジャンプする」という処理は、演算命令の直後に `BTFSS STATUS, Z` (Zフラグがセットされていたらスキップ) のような形で記述します。

3. 割り込み (Interrupts)

割り込みは、通常のプログラム実行中に、特定のイベント(例:タイマーがオーバーフローした、ピンの状態が変化した、データを受信した)が発生したことをマイコンに知らせ、現在実行中の処理を一時中断して、あらかじめ決められた別の処理(割り込みサービスルーチン, ISR)を実行させる仕組みです。

割り込みを使うことで、常に特定のイベントを監視する(ポーリング)必要がなくなり、効率的なプログラムを作成できます。例えば、ボタンが押されるのを待つ間、他の処理(LEDの点滅など)を進めることができます。

PICで割り込みを使用する際の主な手順は以下の通りです。

  1. 割り込みベクタの設定: PICマイコンは割り込みが発生すると、プログラムメモリの特定のアドレス(通常は0x004)にジャンプします。このアドレスにISRへのジャンプ命令を配置します。(`ORG 0x004` / `PSECT Isr_Vec, base=4`)
  2. 割り込み要因の設定: どのイベントで割り込みを発生させるか、関連するSFR(例: `INTCON`, `PIE1`, `PIR1`など)を設定します。
  3. 割り込みの許可:
    • 個別の割り込み要因に対する許可ビット(例: `INTCON`レジスタのT0IEビットでタイマー0割り込みを許可)をセットします。
    • 全体の割り込み許可ビット(`INTCON`レジスタのGIEビット: Global Interrupt Enable)をセットします。
  4. 割り込みサービスルーチン (ISR) の作成:
    • 割り込み発生時に実行される処理を記述します。
    • ISRの最初で、WレジスタやSTATUSレジスタなど、ISR内で使用するレジスタの内容を一時的にRAMに退避させます(コンテキストセーブ)。これは、中断したメインプログラムの処理に影響を与えないためです。
    • どの割り込み要因が発生したかを示すフラグ(例: `INTCON`レジスタのT0IFビット)をチェックし、対応する処理を行います。
    • 処理が完了したら、割り込みフラグをソフトウェアでクリアします。(クリアしないと、ISR終了後に再び同じ割り込みが発生してしまいます)。
    • ISRの最後に、退避しておいたレジスタの内容を復元します(コンテキストリストア)。
    • `retfie` 命令でISRを終了し、中断したメインプログラムの処理を再開します。`retfie`命令はGIEビットを自動的に再セットします。

割り込み処理は少し複雑ですが、マスターするとPICプログラミングの幅が大きく広がります。詳細な設定方法は、使用するPICマイコンのデータシートを参照してください。`pic-as` を使用する場合、リンカオプションでISRの配置アドレスを設定する必要があります。

PICアセンブリの学習は、最初は少し戸惑うかもしれませんが、以下のヒントを参考に、焦らず着実に進めていきましょう。

学習のヒント ✨

  • データシートを読む習慣をつける:
    これが最も重要です!Microchip社のウェブサイトから、使用するPICマイコンのデータシート(日本語版がある場合もあります)をダウンロードしましょう。データシートには、ピン配置、メモリマップ、SFRの詳細、各命令の動作、電気的特性など、必要な情報がすべて詰まっています。最初は分からなくても、必要に応じて参照する癖をつけましょう。特に「Instruction Set Summary」の章は必読です。
  • シミュレータを活用する:
    MPLAB X IDEのシミュレータは非常に強力な学習ツールです。ハードウェアがなくても、レジスタの内容やプログラムの実行フローを1ステップずつ追いながら、命令の動作を視覚的に確認できます。特に、バンク切り替えやフラグの変化などを理解するのに役立ちます。
  • 簡単なプログラムから始める:
    最初から複雑なプログラムに挑戦するのではなく、Lチカ、スイッチ入力、簡単な演算、タイマーの基本動作など、小さな機能ごとにプログラムを作成し、確実に動作させる経験を積み重ねましょう。
  • 実際に動かしてみる:
    可能であれば、ブレッドボード上に回路を組み、PICkitなどでプログラムを書き込んで実際に動作させてみましょう。シミュレータだけでは分からない、タイミングの問題やハードウェア固有の挙動などを体験できます。
  • サンプルコードを参考にする:
    Microchip社のウェブサイト、技術書籍、インターネット上には多くのサンプルコードが存在します。これらを読んで、他の人がどのようにプログラムを書いているかを学ぶのは非常に有効です。ただし、丸写しするだけでなく、なぜそのように書かれているのかを理解しようと努めることが大切です。特に、使用しているアセンブラ(MPASMかpic-asか)や対象デバイスが異なる場合があるので注意が必要です。
  • コメントをしっかり書く:
    アセンブリ言語は、少し時間が経つと自分でも何をやっているのか分からなくなりがちです。各命令や処理ブロックの目的をコメントとして残す習慣をつけましょう。
  • コミュニティを活用する:
    インターネット上には、PICマイコンに関するフォーラムやコミュニティが存在します。分からないことがあれば、質問してみるのも良いでしょう。(質問する際は、試したことやエラーメッセージなどを具体的に記述しましょう)

次のステップ 👣

基本的なアセンブリプログラミングに慣れてきたら、以下のようなトピックに挑戦してみましょう。

  • タイマー機能の活用: より正確な時間制御、PWM(パルス幅変調)によるモーター制御やLEDの明るさ調整。
  • A/Dコンバータ: センサーなどからのアナログ信号を読み取る。
  • シリアル通信 (UART, I2C, SPI): PCや他のICとデータをやり取りする。
  • LCD(液晶ディスプレイ)の制御: 文字や簡単なグラフィックを表示する。
  • EEPROM: 電源を切っても消えないデータメモリに設定値などを保存する。
  • 割り込み処理の応用: 複数のイベントを効率的に処理する。
  • より高度なPICデバイス: PIC18やPIC24/dsPICなど、より高機能なデバイスを使ってみる。
  • C言語への移行: アセンブリでマイコンの基本を理解した後、より複雑なプログラムを効率的に開発するためにC言語を学んでみる。(MPLAB X IDEではXC8などのCコンパイラも利用できます)

PICアセンブリ入門ブログをお読みいただき、ありがとうございました。アセンブリ言語は、一見すると難解に見えるかもしれませんが、マイコンの動作原理を深く理解するための最良の方法の一つです。

このブログが、皆さんのPICマイコン学習の第一歩となり、プログラミングの楽しさを発見するきっかけとなれば幸いです。最初は小さな成功体験を積み重ねることが大切です。焦らず、楽しみながら学習を進めてください。

Let’s enjoy embedded programming! 😊💻🔌

参考情報

コメント

タイトルとURLをコピーしました