PIC アセンブラの覚え書き(旧 MPLAB)

初版作成  2009.11
最終更新  2024.04.01


◆◆ 役に立つサイト ◆◆


http://ww2.tiki.ne.jp/~maro/old/PIC/tips.html


◆◆ レジスタの使用法 ◆◆


様々なデータの移動は汎用レジスタである Wreg を介して
行うことが多い。


◆◆ 大文字と小文字 ◆◆


ニーモニックは大文字と小文字を区別しない。
変数名、ラベル名は大文字と小文字を区別する。

ラベル名以外は小文字を使用するのが見やすいと思う。


◆◆ 定数の表記法 ◆◆


2進数    B'11000100'
10進数    D'12'
16進数    H'6A' or 6AH or 0x6A
アスキー  A'a'   'a' のアスキーコードが入る

◆◆ ニーモニック中で使われる略称 ◆◆


L : リテラル
W : Wreg(ワーキングレジスタ)
F : ファイルレジスタ(PIC ではメモリをファイルレジスタと呼ぶ)


◆◆ 特殊なニーモニック(マクロ) ◆◆


banksel    addr        そのアドレスが存在するバンクに切り替える
            アドレスはバンク 0 が 00h〜7Fh
            バンク 1 が 80h〜FFh
            インクルードファイルを参考にすること


◆◆ デフォルトで定義されている定数 ◆◆


C:\Program Files\Microship\MPASM Suite 以下に
P16F84A.INC のようなファイルがあり、その中にレジスタのアドレス、
定数、コンフィギュレーションの設定項目などが列挙されている。

STATUS レジスタ用
Z    2   ゼロフラグの位置
C    0   キャリーフラグの位置

MOVF などのコマンド用
W    0   ワーキングレジスタ (Wreg)
F    1   ファイルレジスタ(メモリをこう呼ぶ)


◆◆ 定数定義 ◆◆


T1    EQU    0x20  アドレスを指定して変数名として使うことが多い
T2    EQU    0x21
T3    EQU    0x22


◆◆ 変数の定義 ◆◆


一番原始的な方法は、EQU を使って定数定義する方法である。
CBLOCK を使うのが良い

cblock 0x20        確保したい領域の先頭アドレス
VAR1, VAR2
VAR3
endc

上記の方法では変数 1 個につき 1 バイトづつ確保する。
VAR1 = 0x20, VAR2 = 0x21, VAR3 = 0x22 となる。

複数バイト確保したい場合は次のように書く

org 0x20        先頭アドレス
VAR1    RES    2    2 バイト確保する
VAR2    RES    1    1 バイト確保する
VAR3    RES    10    0x10 ( 16 ) バイト確保する

以上のように書くと、VAR1 = 0x20, VAR2 = 0x22, VAR3 = 0x23 と
なる。バイト数は 16 進表記なのに注意。
org を忘れると、プログラム先頭に書いた場合、org = 0 なので、
特殊レジスタ領域と重なる場所に変数領域をとることになり、ダメである。

PIC はデータとコードを異なるメモリに格納するが、
org 命令は両方に作用する。

プログラム先頭で上のように変数領域を確保した後、必ず、絶対に

org  0

を入れてから、コードを書く。
これを怠ると、上の例の場合コードが 0x33 から配置される
(HEX ファイルを閲覧するとわかる)。
リセット直後は 0 番地から実行するので、PIC がまともに動かない。

PIC はリセット直後は 0 番地から実行し、割り込みがかかると 4 番地
から実行するので、

    org    0h
    goto    MAIN
    org    4h
    goto    INTERRPT

MAIN

という書き方が定番的である。

◆◆ define と ifdef 〜 endif ◆◆


;#define PIC84A
#define PIC819

ifdef PIC84A
    LIST     P=PIC16F84A
    INCLUDE  P16F84A.INC
    __CONFIG _HS_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF
endif

ifdef PIC819
    LIST     P=PIC16F819
    INCLUDE  P16F819.INC
    __CONFIG _HS_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BODEN_ON & _MCLR_ON & _LVP_OFF
endif


-------------------------- ここから命令 ------------------------

◆◆ ビット操作命令 ◆◆


bcf    addr,b        ビット b をクリア(0 にする) b は 0〜7 の値をとる
bsf    addr,b        ビット b をセット(1 にする)

<注意!>
ビット操作命令をポートに対して行う場合、ポートから全ての bit を読み出し、
指定のビットをセットしてから、ポートに書き戻す。
その直後にビット操作命令がきた場合、ポートに書き戻したデータが確定しない
まま読み出しが起こり、ビットが反転する可能性がある。

20 MHz で駆動する場合、ポートに対するビット操作命令が連続する場合、
nop を入れる必要がある。

例
bsf    addr,0        アドレス addr の bit 0 を 1 にする
bcf    addr,7        アドレス addr の bit 7 を 0 にする


◆◆ クリア(全ての bit を 0 にする) ◆◆


clrf    addr        addr ← 0
clrw            Wreg ← 0  その結果 Z ← 1

例
clrf    INTCON        割り込み禁止


◆◆ MOVE ◆◆


movlw    B'00001111'    Wreg ← B'00001111'

movf    addr,d        d = W(0) : Wreg ← addr
            d = F(1) : addr ← addr
            Z フラグに影響する

movwf    addr        Wreg → addr

例 : アドレス DATA に 3 を入れる
movlw    D'3'        Wreg ← 3
movwf    DATA        Wreg → DATA


◆◆ 加算、減算 ◆◆


addwf    addr,d        d = W(0) : Wreg ← Wreg + addr
            d = F(1) : addr ← Wreg + addr

addlw    num        Wreg ← Wreg + num

subwf    addr,d        d = W(0) : Wreg ← addr - Wreg(順番に注意!)
            d = F(1) : addr ← addr - Wreg
            結果が正または 0 のとき C フラグが 1

            addr の内容が 5 ならジャンプ、というようなとき
            d = W(0) として addr の内容は不変にして
            フラグを見てジャンプをすればよい。

sublw    num        Wreg ← num - Wreg(順番に注意!)

例1 : 加算
movf    DATA_A,W    Wreg ← DATA_A
addwf    DATA_B,W    Wreg ← Wreg + DATA_B

例2 : 減算
movf    DATA_A,W    Wreg ← DATA_A
subwf    DATA_B,W    Wreg ← DATA_B - Wreg
            DATA_B >= DATA_A  のとき C = 1

◆◆ インクリメント・デクリメントと分岐 ◆◆


decf    addr,d        addr の内容を -1 する
            d = W(0) : 結果を Wreg に格納
            d = F(1) : 結果を addr に格納

incf    addr,d        addr の内容を +1 する

decfsz    addr,d        addr の内容を -1 した後、結果が 0 のとき
            次の命令をスキップする。

例 ループ

    movlw    D'250'
    movwf    address,F
LOOP_TOP
    nop
    decfsz    address,F
    goto    LOOP_TOP
    return

◆◆ ラベル ◆◆


1 桁目から文字列を書き始める。ラベルの後に : は不要


◆◆ ジャンプ ◆◆


goto    LABEL        無条件ジャンプ

call    LABEL        サブルーチンコール

return            サブルーチンからリターン


◆◆ 条件分岐 ◆◆


btfsc    addr,b        addr のビット b が 0 なら次の命令をスキップ

btfss    addr,b        addr のビット b が 1 なら次の命令をスキップ

例1 ループ

decf    COUNT,F        COUNT の値を -1 して結果を COUNT に格納
btfss    STATUS,Z    Z フラグが 1 なら(結果が 0)
goto    LOOP_TOP    次の GOTO は無視してループを抜ける

例2 比較

movf    DATA_B,W    Wreg ← DATA_B
subwf    DATA_A,W    Wreg ← DATA_A - Wreg
btfss    STATUS,C    C フラグが 1 なら ( DATA_A >= DATA_B )
goto    C_EQ_0        次の命令は無視

例3 Wreg が num と等しいとき

sublw    num        ; Wreg ← num - Wreg
btfsc    STATUS,Z
goto    等しいときの処理

sublw    num
btfss    STATUS,Z
goto    等しくないときの処理

例4 DATA が num と等しいとき

movlw    num
subwf    DATA,W        ; Wreg ← DATA - Wreg(num)
btfsc    STATUS,Z
goto    等しいときの処理

movlw    num
subwf    DATA,W        ; Wreg ← DATA - Wreg(num)
btfss    STATUS,Z
goto    等しくないときの処理

別のやり方

movf    DATA,W        ; Wreg ← DATA
sublw    num        ; Wreg ← num - Wreg(DATA)
btfsc    STATUS,Z
goto    等しいときの処理

movf    DATA,W        ; Wreg ← DATA
sublw    num        ; Wreg ← num - Wreg(DATA)
btfss    STATUS,Z
goto    等しくないときの処理

例5 DATA が 0 のとき

movf    DATA,F
btfsc    STATUS,Z
goto    0 のときの処理

movf    DATA,F
btfss    STATUS,Z
goto    0 以外のときの処理

例6 DATA が num 以上のとき

movlw    num
subwf    DATA,W        ; Wreg ← DATA - num
btfsc    STATUS,C
goto    条件成立のときの処理

movlw    num
subwf    DATA,W        ; Wreg ← DATA - num
btfss    STATUS,C
goto    条件不成立のときの処理

例7 DATA が num 未満のとき

movlw    num
subwf    DATA,W        ; Wreg ← DATA - num
btfss    STATUS,C
goto    条件成立のときの処理

movlw    num
subwf    DATA,W        ; Wreg ← DATA - num
btfsc    STATUS,C
goto    条件不成立のときの処理

例8 Wreg が num+1 以上のとき

sublw    num        ; Wreg ← num - Wreg
btfss    STATUS,C    ; 0 または 正のとき C フラグが 1 になる
goto    条件成立のときの処理

sublw    num        ; Wreg ← num - Wreg
btfsc    STATUS,C    ; 0 または 正のとき C フラグが 1 になる
goto    条件不成立のときの処理

例9 Wreg が num 以下のとき

sublw    num        ; Wreg ← num - Wreg
btfsc    STATUS,C    ; 0 または 正のとき C フラグが 1 になる
goto    条件成立のときの処理

sublw    num        ; Wreg ← num - Wreg
btfss    STATUS,C    ; 0 または 正のとき C フラグが 1 になる
goto    条件不成立のときの処理


◆◆ ビット操作(シフト) ◆◆


rlf    addr,d        addr の内容を 1 ビット左へずらし、結果を
            d に書き込む。0 bit 目は C が入る。
            d = W(0) : Wreg
            d = F(1) : addr
rrf    addr,d        addr の内容を 1 ビット右へずらす


◆◆ ビット操作(AND, OR, XOR, SWAP) ◆◆


andlw    B'00001111'    Wreg ← Wreg && '00001111'
andwf    addr,d        d = W(0) : Wreg ← Wreg && addr
            d = F(1) : addr ← Wreg && addr

iorlw    B'00001111'    Wreg ← Wreg || '00001111'
iorwf    addr,d        d = W(0) : Wreg ← Wreg || addr
            d = F(1) : addr ← Wreg || addr

xorlw    B'11111111'    Wreg ← Wreg XOR '00001111'
xorwf    addr,d        d = W(0) : Wreg ← Wreg XOR addr
            d = F(1) : addr ← Wreg XOR addr

swapf    addr,d        上位 4 bit と下位 4 bit を入れ替える
            d = W(0) : Wreg ← addr を入れ替えたもの
            d = F(1) : addr ← addr を入れ替えたもの

◆◆ インデックスレジスタの使用法 ◆◆


FSR  に読み出したいアドレスを入れる。
INDF にアクセスすると、FSR に入れたアドレスのデータにアクセスできる。


◆◆ 主なレジスタ(全 PIC 共通) ◆◆


STATUS    6-5    バンク切り替え
    2    Z フラグ
    0    C フラグ

PCLATH        プログラムカウンタ
INTCON        割り込みの状態

PORTA        入出力用ポート
PORTB        同上

TRISA        PORTA の in/out の方向を決める
        0 = output     1 = input
TRISB        PORTB の in/out の方向を決める

◆◆ __CONFIG の項目の書き方 ◆◆


C:\Program Files\Microchip\MPASM Suite 以下に
P***.INC というファイルがあるので、自分がインクルードする
PIC の INC ファイルを開く。一番最後に指定項目が載っている。

各項目の意味は、各型番ごとに用意されたデータシートの
Special features of CPU という項目に書いてある。


        --------------------------------------------

◆◆ 陥りやすい間違い ◆◆


movf    ADDR

上のは

movf    ADDR,W

のつもりだった。,W を省略してもコンパイルエラーにならず、
,F と解釈される。従って、上の命令は Wreg <--- ADDR のつもりが、
ADDR <--- ADDR となり、Z フラグの変化だけでプログラムが進行する。

     ---

movf    D'10'

上のは

movlw    D'10' のつもりだった( Wreg ← 10 )。

上述の通り、movf の引数を省略したときは ,F とみなされる
ので、10 番地の内容により Z フラグが変化するだけである。

同様に下記のような間違いもある。

andwf    B'01111111'

上のは andlw のつもりだったが、うっかり andwf と書いてしまった。
andwf は引数を 2 つ取るが、省略してもコンパイルが通り、,F と
みなされる。その結果

Wreg ← Wreg && '01111111'

のつもりが

アドレス 7F ← Wreg && アドレス 7F の内容

という動作になってしまう。


◆◆ 16F819 A/D コンバータ(AD コンバータのレジスタは型番によって微妙に異なる) ◆◆


A/D コンバータは入力チャンネル切り替え後、電圧を Hold する
キャパシタの充電が完了するまで 20μs が必要なので、
「チャンネルセレクト設定」→「20μs のディレイ」→「AD 変換開始」
という手順が必要である。

コントロールレジスタ

ADCON0    7-6    W  : AD コンバータのクロック   ADCON1 の bit6 も関係する
    5-3    W   : 入力チャンネル番号
    2    R/W : 1 で変換開始
              0 になったら変換終了
    0    W   : 1 で AD 変換を使用する

ADCON1    7    W   : 変換結果の格納フォーマット
              0 : 左寄せ      1 : 右寄せ
    6    W   : AD コンバータのクロック
    3-0    W   : 5 つの A/D 入力をどのように使うか
              アナログ/デジタル/リファレンス電圧

結果格納用レジスタ

ADRESL
ADRESH

AD コンバータのクロック  :  最小値は 1.6μs

クロック 20MHz のとき Fosc/32
クロック 10MHz のとき Fosc/16
クロック  8MHz のとき Fosc/16

が使用可能


◆◆ 16F886, 16F88 CONFIG の書き方 ◆◆


16F886, 16F88 は CONFIG レジスタが 2 バイトある。
下記の例は 16F886 用である。

    conf1  = _LVP_OFF & _FCMEN_OFF & _IESO_OFF
    conf1 &= _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_ON
    conf1 &= _PWRTE_ON & _WDT_OFF & _HS_OSC

__CONFIG _CONFIG1, conf1


◆◆ 16F886 PORTB に対する注意 ◆◆


PORTB に対して

bsf        PORTB,2

とやると、bit 2 は 1 になるが、bit 0〜5 が全て 0 になるという
現象が発生する。

16F873A ではこの現象は発生しない。

この理由は bsf 命令は一旦 PORTB の内容を読み出した後、
bit 2 を 1 にセットしてから PORTB に書き出すからである。

デフォルトで PORTB の bit 0〜5 はアナログモードになっており、
読み出すと 0 になるようだ。

これを防止するには PORTB をデジタル入出力モードに設定する。
PORTB のモードは ANSELH レジスタで設定する。
ANSELH の bit 0〜5 が PORTB の bit 2,3,1,4,0,5 に対応している。
0 がデジタルモード、1 がアナログモードなので、
以下のようにすればよい。

なお、ANSELH レジスタはバンク 2 という珍しい位置にあるので、
バンクの切り替えにも要注意である。

banksel        ANSELH
clrf        ANSELH


◆◆ 16F886 AD 変換に対する注意 ◆◆


普通の PIC では AD 変換スタートは ADCON0 の bit 2 だが、
16F886 は ADCON0 の bit 1 である。ソースファイルの流用時に注意。


◆◆ 16F84A → 16F819 移行時の注意 ◆◆


16F84A 用のプログラムを 16F819 に流用するときの注意点

・_CONFIG で _LVP_OFF (Low Voltage Programming) に設定する
  デフォルトは _LVP_ON。このとき、PORTB の bit 0 (3 だったかも)
  から出力出来ない。
・ユーザー用メモリの開始アドレス: 0x1c → 0x20
・PORTA の入力モードはデフォルトでアナログ
  デジタルにするには ADCON1 を設定する


◆◆ 16F88 設定項目 ◆◆


IOポートの使用法(アナログ、デジタル)
    ANSEL  p.113 : 0=digital  1=analog

IOポートの入出力方向
    TRISA, TRISB : 0=output  1=input

AD変換
    ADCON0    7-6 : クロック
        5-3 : 入力チャンネル
        2   : 1 で変換スタート  0 になったら変換終了
        0   : 1 で AD 変換モジュール on
    ADCON1    7   : 結果を格納するフォーマット
              0 = 左寄せ  1 = 右寄せ
        6   : AD クロックを 1/2 にするためのビット
              0 = 何もしない
              1 = システムクロック使用時 AD 変換クロック 1/2 にする
        5-4 : リファレンスの取り方
              00= Vdd と Vss

RS-232C 通信の設定
    TXSTA    6   : 8 or 9 ビット転送
              0 = 8bit    1 = 9bit
        5   : 1 のとき RS-232C 送信を行う
        4   : 同期モード
              0 = 非同期     1 = 同期
        2   : 非同期モード使用時のボーレート
              0 = 低速用の表使用       1 = 高速用の表使用
        1   : 送信用レジスタのステータス
              0 = full     1 = empty
        0   : 9 ビット送信時のパリティビット

    SPBRG    p.101 の表に設定すべき値が 10 進数で書いてある
        Fosc は 8, 4, 2, 1 MHz のときの表しかない。
        20 MHz のときはどうなるのか?
        87x の表と比べると 4 MHz のときの値は同一なので 87x の
        表に従って設定すればよいと思われる。

    RCSTA    7   : 1 のとき RS-232C 受信を行う
        6   : 8 or 9 bit
              0 = 8bit    1 = 9bit
        5   : 非同期モードのとき関係ない
        4   : 連続受信可能か否か
              0 = 不可能   1 = 可能
        3   : 非同期 9 bit のときのみ関係ある
        2   : フレームエラー発生か否か
              0 = なし  1 = 発生
        1   : オーバーランエラー発生か否か
              0 = なし  1 = 発生
        0   : 受信データの 9 ビット目


◆◆ RS-232C 通信時の注意 ◆◆


PIC の RS-232C バッファは 2 byte しかない。
RS-232C からデータを読み出さない状態で、PC から 3 バイト送ると
オーバーランエラーのフラグが 1 になる。

オーバーランエラーのフラグが 1 になった場合、
クリアせねばならない。そのためには、
RCSTA のビット 4 (CREN : Continuous Receive Enable) を
まず 0 にセットし、次に 1 にセットする。

これを怠ると

  1 byte 目 ---> 2 byte 目 ---> 2 byte 目 ---> 2 byte 目

となり、永遠に 2 byte 目が読み出され、新しいデータを読み込めない。

フラグをクリアしてから順番に読み出すと

  1 byte 目 ---> 2 byte 目 ---> 次のデータ

となり、3 バイト目は失われる。


        --------------------------------------------

◆◆ ディレイ ◆◆


PIC でディレイを実現するには、無駄なループを回す。

4 クロックで 1 サイクル
大抵の命令は 1 サイクル。goto, call, return のみ 2 サイクル
条件分岐において次の命令をスキップするとき、次の命令を nop に置き換えて
実行する。

20MHz のとき 1 サイクルは 0.2 μs

ディレイは次のようなパターンとなる

DELAY
    movlw    D'x'        1
    movwf    count        1

    nop を α 回

LABEL
    decfsz    count,F        1
    goto    LABEL        2
    return            2

ループを回っている間は、ループ 1 回あたり decfsz と goto で
合計 3 サイクル。ループを抜けるときは goto が nop に置き換わる
ので、decfsz と nop で 2 サイクル。ゆえに上記の関数の所用サイクルは

    2 + α + 3 * x - 1 + 2

である。ディレイ時間をサイクル数で割ると、何サイクルのディレイが必要かが
わかる。これを D 回とすると

2 + α + 3 x - 1 + 2 = D
               3 * x = D - 3 - α

例えば、D = 40 のとき 3 * 12 = 37 - 1 だから、
x = 12, α = 1 となる。

長いディレイを組む場合、上で示したサブルーチン DELAY を
上位のルーチンが呼ぶという構成になる。

LOOP2
    call    DELAY        2
    decfsz    count2,F    1
    goto    LOOP2        2

上記のループ全体のサイクル数は「サブルーチン DELAY で
消費するサイクル数 + 5 サイクル」である。


    -------------------------------------------

◆◆ バイパスコンデンサについて ◆◆


Vcc と GND の間に 0.1μF 程度のコンデンサを入れるとノイズによる
誤動作が軽減され、動作が安定すると言われている。

しかし、ブレッドボードで組んだときに、コンデンサの位置が悪い場合に
「コンデンサの挿入により誤動作する」という現象に遭遇した。
PIC のすぐ傍に設置れば問題なかった。

赤外線リモコンのフォーマットで、8 bit の信号を送信、受信する回路を
それぞれ組んだところ、以下の現象に遭遇した。

< 送信回路 >

ブレッドボード上に送信回路を組んだところ、16F84A の場合は 0.1μF の
コンデンサをブレッドボード上のどこに差しても正常に動作した。
16F819の場合は、コンデンサを PIC の真上に取り付けた場合は正常に動作するが、
PIC からかなり離れた場所(5cm 以上?)に取り付けると正常に動作しない。

動作が異常な状態で、オシロのプローブを PORTB の bit 0 や 1 に
取り付けると動作が正常になり、オシロのアースの位置を変えると、
再び正常に動作しなくなった。原因は不明である。

< 受信回路 >

ブレッド上に受信回路を組み、送受信の信号は 8 bit とし、
受信した 8 bit のパターンを白色 LED で表現する回路を組んだところ、

 (1) リセットスイッチを押した直後
 (2) 9V の電池を 3 端子レギュレータで 5V に落として使う
 (3) 普通の直流電源

以上の場合において、PIC が誤作動する場合があった。
LED の点滅パターンがデタラメになった。
ただし、必ず起こるとは限らない。
0.1μF のコンデンサを装着すると、誤動作はなくなった。