アセンブラー命令について


 PICは機械語で動作します(でしか動作しません)。PICに限らず、すべてのCPUは、最終的には機械語でしか動作しません。ですから、パソコンのプ ログラミングで使うC言語などの高級言語は、最終的には機械語に直す必要があります。

 今回作るような簡単なプログラムの場合は、C言語のような高級言語を使わず、直接機械語でプログラムを作成します(ただし、PIC用のC 言語も存在することは存在します)。ただし、機械語そのものは2進数の羅列ですので、人間には非常に扱いにくいものです。そこで、機械語の2進数と1対1 に対応するけれども、人間に分かりやすいようにアルファベットの記号で表した命令を使ってプログラムを作成します。これがアセンブリ言語で、 また、機械語の2進数と1対1に対応したアルファベットの記号をニーモニックと言います。さらに、ニーモニックで表されたアセンブリ言語を 2進数で表された機械語に変換することを、アセンブルと言います。アセンブルするためのプログラムをアセンブラーと言います が、アセンブリ言語そのものを指してアセンブラーということもあります。また、アセンブラーのニーモニックは機械語と1対1対応がありますから、機械語の ことをアセンブラーと言ってしまうこともあります。

 機械語は、そのCPU固有の言葉であり、また、CPUは機械語だけしか分かりません。従って、CPUの種類が違えば、機械語も異なります。言い換える と、使うCPUに応じて、機械語プログラムは違ってきます。これが、アセンブラープログラミングの敷居を高くしている一因であることは間違いありません。 また、高級言語では1行で書ける命令も、アセンブラーでは、何行にもわたって書かなければならないのが普通です。

 ただ、PIC12F629のアセンブラー命令の場合、命令の種類が全部で35種類しかありません。さらに、これらのすべてを使わなくとも書けるプログラ ムは 数多くあります。ですから、まず、よく使う命令を覚えて、次第にその他の命令も覚えていくようにすればよいと思います。

 また、アセンブラーで書かれたプログラムを理解するためには、PICの内部構造、特に、メモリーマップについて理解する必要があります。以下に、 PIC12F629のCPUまわりの簡単なブロックダイヤグラムを示します。


図1 PIC12F629のCPUとメモリー

 PIC12F629の場合、メモリーは、プログラムメモリーデータメモリーに分かれます。データメモリーはさらに、レ ジスタ・ファイルと、データEEPROMとに分かれます。さらに、レジスタ・ファイルは、特殊機能レジスタ汎 用レジスタとに分かれます。また、レジスタ・ファイルはバンク0バンク1という、2つのバンクに分 かれます。さらに、CPUはWレジスタと 呼ばれる8ビットのレジスタ1つを持っています。プログラムメモリーはROMの一種で約10万回書き換えができるFLASHメモリー、レジスタ・ファイル は SRAM、データEEPROMは電気的に消去可能なROMであるEEPROMでできています。これが、PIC12F629のメモリー構造であり、他の PICではもちろん、また違うメモリー構造を持っています。

 これらのメモリーには、アドレスが振られています。プログラムメモ リーは1kバイトの大きさを持ちますので、アドレス0Hから3FFHまでが割り振られています。ただし、何々Hは16進数を表します。これを 図示すると、以下のようになります。

表1 プログラムメモリーのメモリーマップ
アドレス
メモリー
0H
リセット直後の実行開始アドレス
1H

2H

3H

4H
割り込み時の実行開始アドレス
5H

6H





3FCH

3FDH

3FEH

3FFH
クロックパラメータ


 一方、レジスター・ファイルは特殊機能レジスタ64バイト(注:アドレスの欠番や 重複があるので、実際に使えるのは24バイトのみ)と汎用レジスタ64バイトの合計 128バイト、また、データEEPROMは128バイトの大きさを持 ちます。

 レジスターファイルのメモリー割り当ては独特で、特殊機能レジスタの半分の32バイトがアドレス0Hから1FHまでに、続いて汎用レジスタの64バイト がアドレス20Hから5FHまでに、アドレス60Hから7FHまでは欠番、アドレス80Hから9FHまでに特殊機能レジスタの残りの32バイトが、続くア ドレスA0HからDFHまでには、汎用レジスタが繰り返して、再度割り当てられています。つまり、例えば、アドレス20HとアドレスA0Hは同じ汎 用レジスタを指します。このうち、アド レス0Hから5FHまでをバンク0、アドレス80HからDFHまでをバンク1と呼びます。その様子を図示したものが、下図に なります。

表2 レジスタ・ファイルのメモリーマップ
バンク
アドレス
レジスタ
バンク
アドレス
レジスタ

0H
INDF

80H
バンク0に同じ
1H
TMR0
81H
バンク0に同じ
2H
PCL
82H
バンク0に同じ
3H
STATUS
83H
バンク0に同じ
4H
FSR
84H
バンク0に同じ
5H
GPIO
85H
TRISIO
6H
欠番
86H
欠番
7H
欠番 87H
欠番
8H
欠番 88H
欠番
9H
欠番 89H
欠番
AH
PCLATH
8AH
バンク0に同じ
BH
INTCON
8BH
バンク0に同じ
CH
PIR1
8CH
PIE1
DH
欠番 8DH
欠番
EH
TMR1L
8EH
PCON
FH
TMR1H
8FH
欠番
10H
T1CON
90H
OSCCAL
11H
欠番 91H
欠番
12H
欠番 92H
欠番
13H
欠番 93H
欠番
14H
欠番 94H
欠番
15H
欠番 95H
WPU
16H
欠番 96H
IOC
17H
欠番 97H
欠番
18H
欠番 98H
欠番
19H
CMCON
99H
VRCON
1AH
欠番 9AH
EEDATA
1BH
欠番 9BH
EEADR
1CH
欠番 9CH
EECON1
1DH
欠番 9DH
EECON2
1EH
欠番 9EH
欠番
1FH
欠番 9FH
欠番
20H
汎用レジスタ
A0H
バンク0に同じ
21H
汎用レジスタ A1H
バンク0に同じ
22H
汎用レジスタ A2H
バンク0に同じ




5DH
汎用レジスタ DDH
バンク0に同じ
5EF
汎用レジスタ DEH
バンク0に同じ
5FH
汎用レジスタ DFH
バンク0に同じ


 レジスター・ファイルのアドレスは7ビット幅ですので、0Hから7FHまでしか表せません。しかし、アドレス60Hから7FHまでは欠番ですから、結 局、アドレス0Hから5FHまでしか表せません。これでは、アドレス80HからDFHまでにアクセスすることができないことになります。そこで、バンクと いう考え方が出てきます。アドレス80Hはバンク1のアドレス0H、アドレス81Hはバンク1のアドレス1H、と順に考えて行き、アドレスDFHをバンク 1のアドレス5FHとします。一方、本当のアドレス0Hから5FHまでは、バンク0のアドレス0Hから5FHと考えます。これが、バンク切り替え方 式で、届かないアドレスにアクセスするための一つの方法です。

 汎用レジスタは、本来はアドレス20Hから5FHに割り当てられていますが、アドレスA0HからDFHにも再割り当てされていると述べましたが、アドレ スA0HからDFHはすなわちバンク1のアドレス20Hから5FHですから、結局、汎 用レジスタは、バンクに関係なく、いつも、アドレス20Hから5FHでアクセスできるということになります。PIC12F629の設計者 は、そうなるように、わざわざ汎用レジスタをアドレスA0HからDFHにも再割り当てしたのです。

 表を見れば分かるように、特殊機能レジスタの一部(特に、よく使う特殊機能レジスタ)にも、両方のバンクでアドレスが共通になるように割り当てされてい るものがあります。

 では、実際のプログラムを見ていくことにしましょう。今回のLED点灯プログラムは比較的単純なので、それほど多くの命令は使 いません。まずはじめに、プログラムの全リストを以下に示します(下のリストでは、全角スペースが 入っているかもしれませんので、コピーアンドペーストする場合は注意してください)。

    LIST    P=12F629
    INCLUDE    P12F629.INC
    ERRORLEVEL -302

CB = _CPD_OFF
CB &= _CP_OFF
CB &= _BODEN_ON
CB &= _MCLRE_OFF
CB &= _PWRTE_ON
CB &= _WDT_OFF
CB &= _EXTRC_OSC_CLKOUT

    __CONFIG    CB
    __IDLOCS    H'0100'

    CBLOCK        H'20'
      CNT1
    ENDC
;---------------------------------
    ORG            H'0'
    GOTO        L1
;---------------------------------
    ORG            H'4'
    RETFIE
;---------------------------------
L1:
    BANKSEL GPIO
    MOVLW        B'00000000'
    MOVWF        GPIO

    BANKSEL        CMCON
    MOVLW        B'00000111'    ;SETUP DIGITAL I/O MODE
    MOVWF        CMCON

    BANKSEL        TRISIO
    MOVLW        B'00001000'    ;PORT 0,1,2 = OUT, PORT 3 = IN
    MOVWF        TRISIO

    BANKSEL        GPIO
L2:
    MOVLW        B'00000001'
    MOVWF        GPIO
    CALL           W200
    MOVLW        B'00000010'
    MOVWF        GPIO
    CALL           W200
    MOVLW        B'00000100'
    MOVWF        GPIO
    CALL           W200
    GOTO           L2
;---------------------------------
W200:
    MOVLW        D'200'
    MOVWF        CNT1
L3:                ;5 cycles = 20 clocks Loop
    GOTO        $+1
    DECFSZ        CNT1,F
    GOTO        L3
    RETURN
;---------------------------------
    END
   




 上記のプログラムのうち、黄色になっているところ以外は、いつも書く決まり文句と考えてかまいま せん。従って、プログラムを変更するときは、黄色の部分をいろいろと変更することになります。

 まずはじめに、決まりきった部分について、解説します。この部分はいつもこのように書くので、とにかくこの通りに入力すればいいと言ってしまえばそれま でなのですが、 それでは理解したことになりません。決まり文句ではありますが、その意味を知っておく方が良いに決まっています。

 最初の、「LIST   P=12F629」は、PICのタイプを指定します。

 次の、「INCLUDE    P12F629.INC」は、インクルードするファイルを指定します。「P12F629.INC」を指定します。これは、MPLABが持っており、プログ ラム中で使う定数の定義その他が書いてあります(例えば、すぐ下の、_CPD_OFF という定数の定義など)。自分で作成したファイルをインクルードする必要があるときは、INCLUDE文を複数書くことができま す。

 「ERRORLEVEL -302」は、アセンブル時に、302番のエラーメッセージを出さないようにします。これは、下のプログラム中で、 「BANKSEL」という命令を使っているのですが、これに関連して出る警告を無視するための設定です。仮にその警告が出ても正常にアセンブルできますの で、無視してかまいません。

 次に、
  CB = _CPD_OFF
  CB &= _CP_OFF
  CB &= _BODEN_ON
  CB &= _MCLRE_OFF
  CB &= _PWRTE_ON
  CB &= _WDT_OFF
  CB &= _EXTRC_OSC_CLKOUT

があります。これは、コンフィギュレーションビットと呼ばれるものの設定で、それぞれ、「_CPD_OFF」はデータメモリーを保護しない(誰でも読み出 せるようにする)、「_CP_OFF」はプログラムメモリーを保護しない(誰でも読み出せるようにする)、「_BODEN_ON」はブラウンアウトリセッ トを使う(電源電圧が2Vを切った時に自動的にリセットがかかるようにする)、「_MCLRE_OFF」は、3番ピンを外部リセット端子として使わない (その場合、3番ピンは普通の入力ピンとなる)、「_PWRTE_ON」はパワーアップタイマーを使う(電源投入後、すぐに電圧が安定しない場合があるの で、電源電圧の上昇開始を検知してから72ミリ秒だけ待つ)、「_WDT_OFF」はワッチドッグタイマーを使わない(ワッチドッグタイマーとは、不意の 暴走を防ぐため、定期的にか けるリセットのこと。野外観測など、主に無人の状況下でプログラムを走らせるときに使う。今のような時は使う必要はない)、 「_EXTRC_OSC_CLKOUT」はクロック発振モード をRCモードとするという意味があります。

 CB文は、
「CB = _CPD_OFF  &  _CP_OFF  &  ……  &  _WDT_OFF  &  _EXTRC_OSC_CLKOUT」
のように、一行で書いてもかまいません。

 次の、「__CONFIG    CB」は、上で定義したコンフィギュレーションビットを適用するということを表します。「CONFIG」の前には、アンダースコア「 _ 」が2つ要ることに注意してください。

 その次の、「__IDLOCS    H'0100'」は、プログラムのバージョンを4桁の16進数で指定します。「H'****'」は16進数を表します。同様に、 「B'********'」は2進数を、「D'****'」は10進数を表します。

 次に、「CBLOCK」から「ENDC」までの間で、プログラム中で使う変数を定義します。PIC12F629の場合、各変数は1バイト(8ビット)に 固定されてお り、また、変数は最大64個まで定義することができます(汎用レジスタのサイズが、アドレス20Hバイトから5FHバイトまでの64バイトであるた め)。サンプルプログラムでは、以下のように書かれています。

    CBLOCK        H'20'
      CNT1
    ENDC

 この部分を理解するためには、先に掲げたPIC12F629のメモリーマップを参照する必要があります。ユーザーが自分で定義する変数は、すべて汎用レ ジスタにその領域が確保されます。汎用レジスタはバンク0、バンク1ともに、アドレス20Hから5FHの部分となっています。このプログラムでは、変数を 1つだけ定義していますので、その範囲内ならば、どこに領 域を確保してもかまわないのですが、アドレスの低い方から確保していくのが自然でしょう。そこで、アドレ ス20Hに変数を確保します。先の「CBLOCK」の後に続けて16進数を指定すると、そのアドレ スから順に変数を確保してくれます。従って、「CBLOCK H'20'」は、アドレス20Hから順に変数を確保していく、という意味になります。変数名は任意ですが、どういう意味を持つ変数なのかが見て分かるよう な名前のほうが良いです。このプログラムでは、定義した変数は、LEDの点滅速度を指定するため のカウント数を格納する領域として使いますので、CNT1という名前にしておきました。いくつもの変数を必要とする場合は、「CBLOCK」と 「ENDC」の間に、いくつか変数を並べて書くことができます。

 その次から、いよいよプログラム本体が始まります。このPICはリセットすると、プログラムアドレス0Hから実行が始まるようになっていますので、プロ グラムの先頭をアドレス0Hに書き込みます。それが、「ORG H'0'」という命令で、次に続く「GOTO L1」のアドレスが0Hとなるようにする、という意味があります。
 ここで、アドレス0Hというけれども、バンクは0、1のどちらなのだろうか、とか、アドレス0HはINDFレジスタのはずではなかったのかな、とか思って はいけません。図1に示したように、PIC12F629では、プログラムメモリーとデータメモリーが別々になっています。バンクがあるのは、データメモ リーの一種であるレジスタ・ファイルの話です。ですから、プログラムメモリーには、バンクという概念はありません。プログラムの場合は表1を、データの場 合は表2を見なければなりません。

 さて、このプログラムでは割り込みを使いませんので、アドレス0Hから順に命令を書いていっても良いのですが、万一、何らかの理由で割り 込みがかかった場合を想定して(実際にはありえないはずですが)、割り込み処理プログラム(割り込みハンドラー)を念のために用意しておきます。と言って も、割り込みがかかっても何もしない、というプログラムを書いておけばいいわけです。表1にあるように、割り込みがかかると、プログラムのどこを実行中で あってもそれが一時中断されて、アドレス4Hが呼び出されます。割り込みから元に戻るには、「RETFIE」命令を使います(それに対し、 通常のサブルーチンコールから戻る場合は、「RETURN」命令を使う)。割り込みがかかっても何もしない、と言っても、元の位置に戻るこ とだけはしなければなりませんから、つまりは、アドレス4Hに「RETFIE」命令だけを書いておけばよいことになります。そうするためには、「ORG H'4'」に続けて「RETFIE」と書けばよいことになります。

 そうすると、リセット直後、アドレス0Hから実行開始したメインプログラムは、この割り込み処理プログラムの部分をよけて実行しなければなりません。さ もないと、リセットするといつも、割り込みはかかっていないのに割り込み処理プログラムが実行されてしまいます。それを避けるために、割り込み処理プログ ラムが終了した直後に、ラベルを設定し、メインプログラムを強制的にそこにジャンプさせてしまいます。ラベルは、ラベル名に 続けてコロン「:」を付けて表します。ここでは、「L1」というラベルを設定しました。「GOTO L1」は、ラベルL1にジャンプすることを示します。GOTO 文でラベルを引用するときは、コロンはいらないことに注意してください。
 
;---------------------------------
    ORG            H'0'
    GOTO        L1
;---------------------------------
    ORG            H'4'
    RETFIE
;---------------------------------
L1:

 なお、MPLABのアセンブラーでは、ある行内でセミコロン「;」以降はコメントとしてアセンブル時に無視されます。そこで、「;--- ------------------------------」のような区切り線を入れて、人間がプログラムを見たときに分かりやすいようにしました。

 さて、その次の部分です。ここからが一番重要な部分です。まず、

L1:
  BANKSEL        GPIO
    MOVLW        B'00000000'
    MOVWF        GPIO

という部分があります。今から、特殊レジスタの一つである、GPIOレジスタにデータを書き込みます。そのためには、まず、GPIOレジス タの存在するバンクに切り替える必要があります。表2を見て何々レジスタはバンクどちら、と確認して、手動でバンクを切り替えてもいいのですが、自動的に バンクを切り替えてくれる命令があります。これが「BANKSEL」命令です。従って、「BANKSEL GPIO」と書くと、自動的に、GPIOレジスタの存在するバンクである、バンク0に切り替えてくれます。ただし、「BANKSEL」命令を使うと、アセ ンブル時に、選択したバンクが正しいかどうかを確認する警告が出ます。この警告は無視してかまわないのですが、たくさん出るとうっとうしいので、先に説明 した「ERRORLEVEL -302」で、この警告を出さないようにしたわけです。

 バンクを正しく選択したら、あとはそのレジスタに値を書き込むだけなのですが、実は、PIC12F629のアセンブラでは、特殊レジスタであろう と汎用レジスタであろうと、レジスタ・ファイルに値を直接書き込むことができません。そこで、書き込みたい値は、一度Wレジスタにロードして、続いてWレ ジスタの内容をレジスタ・ファイルにコピーしなければなりません。

 PIC12F629では、Wレジスタに値をロードする命令として、「MOVLW」命令が用意されています。値(リテラルと言 われるので、Lで表す)を、WレジスタにMOV(移す)という意味です。ここでは、「MOVLW B'00000000'」なので、Wレジスタに0を入れる、という意味になります。0を特に2進数で表していますが、これは、後々、2進数の方が分かりや すい表記が出てきますので、それに合わせただけです。

 次に、Wレジスタの内容をレジスタ・ファイルにコピーする命令として、「MOVWF」命令があります。今の場合は、「MOVWF GPIO」なので、Wレジスタの内容をGPIOレジスタにコピーする、という意味になります。「MOV」と書くと移動のように思えますが、移動ではなくて コピーです。つまり、この命令の実行後も、Wレジスタの内容は消えずに元のままです。

 このような2段階の手順を経て、値を特殊レジスタに書き込むことができるのです。

 では、なぜ、まず、GPIOレジスタに0という値を書き込んだのでしょうか。それを理解するためには、そもそも、GPIOレジスタに値を書き込む、とい う行為の意味するところから説明しなければなりません。

 コンピュータが他のコンピュータや周辺機器とハードウェア的に直接通信する場合、ポートというものが使われます。ポートには、ハードウェ ア、ソフトウェアの両方の意味があります。パソコンの場合、シリアルポート(RS-232Cポートとも言う)とか、プリンタポート(パラレルポートとも言 う)とか、USBポートとかいう言葉が使われます。これらはすべて、ハードウェアの名前です。ポートはすべて、ポートアドレスという番号で管理され、例え ばシリアルポートのアドレスが10H番であったとすると、ソフトウェアでポートアドレス10H番になにがしかのデータを書き込むと、そのデータがRS- 232Cに出力されるという寸法になっています。また、10番のポートアドレスを読み出すと、RS-232Cで受信したデータが読み出せます。したがっ て、ポートはもともとハードウェアの概念ですが、ソフトウェア的な意味で用いる場合も多々あります。

 86系CPUを用いているWindowsパソコンでは、ポートはメモリーとは別の独自のアドレス体系になっており、「IN」命令や「OUT」命令など、 ポートを読み出したり書き込んだりするための専用の命令が用意されています。それに対し、68系CPUを用いているマッキントシュや、この実習で使う PIC12F629などでは、ポートアドレスは、表2にもあるように、あたかもメモリーアドレスの一部分のようになっており、通常の「MOV」命令を用い て、メモリの読み書きと同じように、ポートの読み書きもできるようになっています。このような方式を、メモリーマップドI/Oと言います。

 説明が長くなりましたが、結局、GPIOレジスタに0を書き込むというのは、電源投入直後にはすべてのLEDが消えた状態から始まるようにする、 という意味を持つのです。これについては、すぐ下で、もう少し補足説明します。

 その次に、

    BANKSEL        CMCON
    MOVLW        B'00000111'    ;SETUP DIGITAL I/O MODE
    MOVWF        CMCON

が続いています。上の説明が分かっておれば、これは何をしたのかお分かりでしょう。特殊レジスタの一つであるCMCONレジスタに、2進数の 00000111(つまり、10進数の7)を書き込んでいます。2行目の「;SETUP DIGITAL I/O MODE」の部分はコメントです。

 CMCONレジスタもバンク0にある特殊レジスタで、コンパレータを制御するための各種設定をするレジスタです。コンパ レータというのは、ある決められた電圧(リファレンス電圧)と入力電圧を比較し、それらの大小関係を判定する機能を指します。例えば、充電器などで、あら かじめ設定した電圧を超えたら充電を中止する、と言うような用途に使うことができます。PIC12F629はこのような機能も持っているのです。しかし、 この実習では、コンパレータは使いません。コンパレータはその性質上、入出力電圧をアナログ電圧として処理します。従って、その場合は、 PIC12F629の入出力用のピンは、アナログモードで動作します。一方、コンパレータを使わない場合は、入出力ピンは、デジタル モードで動作します。今の場合は、LEDを点けるか点けないかしかありませんから、デジタルモードで動作させる必要があります。そのためには、コ ンパレータを動作させない設定にしなければなりません。CMCONレジスタの下3桁はコンパレータのモードを指定するビットで、「000」から「111」 まで、8種類のモード指定ができるようになっています。詳しくは述べませんが、これを「111」にするとコンパレータはOFFとなり、従って入出力ピンは デジタルモードで動作するようになるのです。従って、CMCONレジスタに00000111を書き込むと、それ以降、デジタル入出力を行うことができます (電源投入直後やリセット直後は、自動的にアナログモードになっている)。言い換えれば、デジタル入出力を行うためには、それに先立って、 CMCONレジスタに 00000111という値を書き込んでやらなければなりません。

 その次に、

    BANKSEL        TRISIO
    MOVLW        B'00001000'    ;PORT 0,1,2 = OUT, PORT 3 = IN
    MOVWF        TRISIO

があります。CMCONレジスタに00000111という値を書き込んで、無事アナログモードからデジタルモードに切り替えたのですが、ポート0(7ピ ン)、ポート1(6ピン)、ポート 2(5ピン)は入力にも出力にも使えますから、どちらに使うのか指定しなければなりません(ポート3だけは入力専用)。これを指定するのがバ ンク1にあるTRISIOレジスタで、2進数で表したときに、右から順に、ポート0、ポート1、ポート2、ポート3の入出力モードを指定し ます。0 なら出力、1なら入力です。ポート3は入力専用ですので、右から4番目のビットは1しかありえません。この例では、ポート0、ポート1、ポート2をすべて 出力設定 にしました。ハードウェアでは、ここに抵抗を介してLEDをつなぎます(回路図参照)。

 これらの設定を経てやっと、7ピン、6ピン、5ピンに電圧を出力できるようになります。

 では、7ピン、6ピン、5ピンにどのような電圧を出力するか、どうやって設定するのでしょうか。それが、先ほども出てきた、GPIOレジスタです。 GPIOレジスタを8桁の2進数と見たとき、各ビットとポート(ピン)の対応は以下の通りです。



入力、出力とも、ビットの内容が0の場合は0V、同じく1の場合は電源電圧に等しい電圧であることを示します。従って、GPIOレジスタに 00000000を書き込むと、今の場合出力設定にした7ピン、6ピン、5ピンすべてに0が出力される、すなわち、これらのピンの電圧は0Vとなり、これ らのピンにつながれたLEDは光りません(ポート3、すなわち4ピンにも0が書き込まれますが、4ピンは入力専用ですので、書き込んだ値は無視されます。 また、ポート4、ポート5も、今回の場合はクロック発振がRCモードですので、やはり、書き込んだ値は無視されま す)。

 CMCONレジスタとTRISIOレジスタに先にモードをセットしてから、GPIOレジスタに出力すべきデータをセットしたらいいのにと思う人もいるか もしれませんが、そうしないのには訳があります。PICの電源投入直後やリセット直後には、GPIOレジスタの値は不定になっています。先にモードをセッ トしてからデータを書き込むと、モードセットが完了してからデータを書き込むまでの間、ほんのわずかな時間ですが、予期しない電圧がポートに出力される場 合がありえます。この回路のようにLEDを光らせるだけならば、予期しない電圧が出ても大したことにはなりませんが、回路によっては、ほんの一瞬でも予期 しない電圧が出ると、ハードウェアに損傷を与えるものもありえます。それを避けるために、まず先にGPIOレジスタに値だけは書き込んでおき(この状態で はまだ電圧は出力されていない)、それからモード設定を行います。

 あとは、GPIOレジスタに好きな値を書き込んで行けば、3つあるLEDをどのようにでも光らせることができます。

 例えば、7ピンのLEDだけ光らせるときは、GPIOレジスタに00000001を、6ピンのLEDだけ光らせるときは、GPIOレジスタに 00000010を、5ピンのLEDだけ光らせるときは、GPIOレジスタに00000100をそれぞれ書き込めばよいことになります。以下はそのプログ ラムです。

    BANKSEL        GPIO
L2:
    MOVLW        B'00000001'
    MOVWF        GPIO
    CALL        W200
    MOVLW        B'00000010'
    MOVWF        GPIO
    CALL        W200
    MOVLW        B'00000100'
    MOVWF        GPIO
    CALL        W200
    GOTO        L2
;---------------------------------

 LEDを一つずつ順に光らせ、それを繰り返します。

 ただ、何もしないとLEDの点滅速度が速すぎて、人間の目では順番に光っているのかどうかわからなってしまいます。それで、次のLEDに移る前に、ウエ イトを置くことにしました。それが、「CALL W200」の部分です。「CALL」命令はサブルーチンを呼ぶ命令で、呼ばれたサブルーチ ンからは、「RETURN」命令で復帰します。

 実は、アセンブラーでは、サブルーチンの入り口と、普通のラベルとは、まったく同一のものです。一つのラベルを、GOTO文のジャンプ先に使ったり、サ ブルーチンコールに使ったりできます。

 ただし、PIC12F629では、スタック領域(サブルーチンの戻りアドレスなどが自動的に保存される)の大きさの制限から、サブルーチンは、割り込み も含めて、最大9層までしかネストできませんので、注意が必要です。

;---------------------------------
W200:
    MOVLW        D'200'
    MOVWF        CNT1
L3:                ;5 cycles = 20 clocks Loop
    GOTO        $+1
    DECFSZ        CNT1,F
    GOTO        L3
    RETURN

ここでは、「W200」というラベルを設け、それをサブルーチンの入り口としました。このサブルーチンでは、指定回数(サンプルプログラムの例では、 200回)だけ空ループを回し、それで時間稼ぎをすることにします。まず、回すループの回数を、Wレジスタに設定します。それが、「MOVLW D'200'」です。D'200'ですので、10進数で200回を指定しました。この数字を変えれば、ループ回数が変わり、従ってLEDの点滅速度が変わ りますが、Wレジスタは8ビットなので、最大でも255までしか指定できません。それ以上点滅速度を遅くしたい場合は、一回の空ループのサ イクル数を増やすか、あるいは2重ループにするなど、別の方法で対処する必要があります。

 ループ本体は、「L3:」から「GOTO L3」の部分です。これは分かりやすいでしょう。その間に、「GOTO $+1」と、「DECFSZ  CNT1,F」の2つの命令が入っています。まず、「GOTO $+1」ですが、MPLABのアセンブラーでは、「$」は自分自身のアドレスを示します。従って、「$+1」は自分の次のアドレスを示します。すると、 「GOTO  $+1」は自分の次のアドレスにジャンプすることを示します。しかし、本来、何もしなくても自分の次のアドレスの命令が実行されることになっています。 従って、「GOTO  $+1」とは、何もしない命令ということになります。何もしないのならば、こんな命令は要らないではないかと思うかもしれませんが、そうではありません。 これは「命令を読み込み、それを解釈した結果、何もしなくてよろしいという命令であった」ということができます。つまり、 「GOTO  $+1」は結果的には何もしませんが、しかし、その間、時間が必要であるということです。具体的に言うと、この命令を実行するには、2サイクルのCPU時 間が必要です。何もしないのに時間だけを消費する、ということから、このような、タイミングを作るためのサブルーチンで好んで使われます。

 このプログラムでは使っていませんが、何もしない命令には、「NOP」という命令もあります。この命令も何もしない命令ですが、「GOTO  $+1」との違いは、「GOTO  $+1」が2サイクル命令であるのに対して、「NOP」の方は1サイクル命令であることです。このプログラムでは、

L3:
    GOTO        $+1         ← 2サイクル命令
    DECFSZ        CNT1,F     ← 1サイクル命令
    GOTO        L3          ← 2サイクル命令

となっていますが、PIC12F629の場合、GOTO命令が2サイクル、次に述べる「DECFSZ」命令が、スキップしない場合は1サイクル命令なの で、上の場合、ループ一回は、合計5サイクルとなります。もし、6サイクルのループを作りたければ、

L3:
    GOTO        $+1         ← 2サイクル命令
  NOP                ← 1サイクル命令
    DECFSZ        CNT1,F     ← 1サイクル命令
    GOTO        L3          ← 2サイクル命令

とすればよいし、もし、7サイクルとしたければ、

L3:
    GOTO        $+1         ← 2サイクル命令
    GOTO        $+1         ← 2サイクル命令
    DECFSZ        CNT1,F     ← 1サイクル命令
    GOTO        L3          ← 2サイクル命令

とすればよいことになります。つまり、「NOP」命令は、サイクル数の偶数奇数を調節するためにはさむことができます。通信プログラムなどで、正確にタイ ミングを合わせなければならないような場合は、このように「NOP」命令を使ってサイクル数を微調整することによりタイミングを合わせることが良くありま す。

 「DECFSZ  CNT1,F」は、ループを抜けるかどうかの判定です。これがないと、無限ループに陥ってしまいますので、注意が必要です。ここでは、CNT1に設定され たループ回数があります。一回ループするたびに、そのカウント数を1ずつ減らして行きます。そして、カウント数がちょうど0になれば、ループを終了するこ とにします。そのようなプログラムは、どうすれば実現できるでしょうか。このようなものは、プログラミングでは、条件分岐と呼ばれます。条 件分岐に関係する命令の一つに、「DECFSZ」命令があります。「DECriment File and Skip if Zero」という意味です。Decrimentというのは、一つ減らすという意味です(逆に、一つ増やすのはIncrimentという)。レジス タ・ファイルの値を1減らし、もし減らした結果が0でなければ次の命令を実行し、もし減らした結果が0に等しければ、次の命令を飛ばして、次の次の命令を 実行します。それとともに、減らした結果を、元のレジスタ・ファイルまたはWレジスタのどちらか一方に保存します。従って、この命令は、 「DECFSZ  引数1, 引数2」のように使います。引数(ひきすう)1には、減らす対象の変数名を、引数2にはFかWのどちらかを指定します。今の場合は、 「DECFSZ  CNT1,F」ですので、CNT1という変数の値を1だけ減らし、その結果を元のCNT1という変数に書き戻すとともに、その結果が0であれば、次の命令 をスキップします。

 「DECFSZ  CNT1,F」の次の命令は「GOTO  L3」ですので、CNT1の値が0にならなければこの命令が実行されてL3に戻り、ループが繰り返されます。しかし、ループを繰り返しているうちに CNT1の値がだんだん減っていき、ついに0に等しくなると、この命令はスキップされて、その次の「RETURN」が実行され、時間稼ぎのサブルーチンが 終了してメインルーチンに戻り、次のLEDの点灯に移るわけです。

 リストの一番最後の、「END」は、MPLABに対してプログラムの末尾を示します。

 ここまでの説明で分かるように、同じ命令と言っても、PICのCPUに対する機械語の命令と、アセンブルプログラムである MPLABに対する命令の2種類があることが分かります。このプログラムの例では、BANKSEL、CALL、DECFSZ、GOTO、 MOVLW、MOVWF、RETFIE、RETURNはCPUに対する命令、__CONFIG、__IDLOCS、CB、CBLOCK、END、 ENDC、ERRORLEVEL、INCLUDE、LIST、ORGはMPLABに対する命令です。前者のような命令に対して、後者のような命令を、擬 似命令とか制御命令とか言います。


戻る