いまさらアセンブラでPIC
Home
始めに
ArduinoだRaspberry piだと便利な物があるがほんのちょっとだけつまらない事をしたいときにそれらは大げさすぎなのよ。で、PICマイコンでいいじゃないとなるわけですが、今度はMPLAB X IDEとか出てきてこれがまたわけわからん。高機能なのは分かっているが、おじさん、そういう物まではいらんのよ。それにじっくり取り組むほどの課題ではないし、そういう熱意ももうないのよということで、ちょっとしたことならハンドアセンブルでごりごりやっちゃえって方針で進めてきたことの備忘録がこのページ。だからわかってもらう構成ではなくたまに自分が使うときに思い出せるような構成になっているので説明が相当前後します。
ターゲットとするPICと道具立て
ターゲットとするPICは16F84A, 16F88, 12F675辺り。すごく旧式と思いますが、構造が単純なことやかつて使っていたこと、チップの手持ちも潤沢にあること。それに何よりも私の持っているライタ(ADWINさんのPIC PROGRAMMER A+)がこの辺りまでしか対応していないのよ。
プログラムの先頭
PICは電源を投入されたりリセットされるとメモリの0番地からスタートして次々と命令が実行されていく。ただ、割り込みが発生すると強制的にメモリの4番地へジャンプする。だから何もしないとスタートするとすぐに割り込み用の命令が実行されてしまう。これを避けるために0番地でいきなり8番地に飛ぶという命令を書いておき、実際のプログラムは8番地からスタートするというのが通例。割り込みなんて一切使用しないという豪の者なら0番地からジャンプなしにゴリゴリ書いてもいけなくはないと思う。
命令の実行時間
アセンブラの命令は1クロックもしくは2クロックで実行される。1クロック分の時間は1秒÷クロック数で求められる。仮にクロック数が10MHzなら1秒÷10MHz=0.1マイクロ秒となるので1クロック使う命令なら0.1マイクロ秒で処理されるとわかる。この計算を厳密に扱うことである処理にかかる時間を正確に求めることができる。
レジスタ
PICマイコンが持つ様々な機能を使ったり、マイコンの状態を確認するのはレジスタを介しておこなう。PIC16F84Aのレジスタは下のようになっていて、BANK0とBANK1という2カ所に分かれている。BANK0が選択されているときにはBANK0側しか読み書きできない。BANK0が選択されているときにBANK1側のレジスタを読み書きしたい場合は、まずBNAK1に切り替えてから読み書きするという手間をかけなきゃいけない。ここでちょいっとややこしいのが例えばTRISAは下の図ではBANK1のアドレス85hにあるとなっているが、このTRISAを使うためにBANK1を選択した後には80h引いた05hにあるという落とし穴がある。これ、地味に大事。
0Chから4Fhまではユーザーが自由に使える。変数の内容はここに置く。BANK1に切り替えている場合でもこの領域はBANK1側にもコピーされているのでここを参照する場合にわざわざバンク切り替えする必要はない。

BANK切り替え(STATUSレジスタ 03h, 83h)
STATUS
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
| IRP | RP1 | RP0 | /TO | /PD | Z | DC | C |
Bit7 常にゼロとなっている。
Bit6 常にゼロとなっている。
Bit5 1:Bank1が選択される 0:Bank0が選択される
Bit4 Time-out Bit ウォッチドッグタイマ関連 よくわからん
Bit3 Power down Bit スリープに関係するっぽい よくわからん
Bit2 計算や論理演算の結果がゼロであれば1, ゼロでなければ0となる。フラグのチェックや数字の比較に使える
Bit1 下から4桁目でキャリーアウトが起きたかどうかかな? よくわからん
Bit0 下から4桁目でキャリーアウトが起きたかどうかかな? よくわからん
ポートAとPORTAとTRISA, ポートBとPORTBとTRISB
16F84AのI/Oポートは5ビット分のポートAと8ビット分のポートBに分かれている。このI/Oポートを使うための手順としてはポートAを使いたいときにはピンごとの入出力をTRISAで設定する。TRISAは次のようになっておりTRISA 4-0を1にするとそのピンは入力, 0にすると出力ピンとして設定される。
TRISA
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
| ー | ー | ー | TRISA 4 | TRISA 3 | TRISA 2 | TRISA 1 | TRISA 0 |
出力に設定したピンから出力したい場合はPORTAの対応するビットを0/1のどちらかにする。入力に設定したピンから入力したい場合はPORTAの対応するビットを確認する。HIGHが入力されていればそのビットは1になっているしLOWが入力されていれば0が読みだされる。PORTBについては同じことをTRISBとPORTBで行う。
Hexファイル
最終的にライタに読み込ませるためにはHexファイルを造らなければいけない。
へxファイルの中身はテキスト形式で
1.スタートコード 1バイトというか:(コロン)
2.データ長 2バイト
3.アドレスオフセット 4バイト
4.レコード種別 2バイト(PICの場合テキストで00~02のどれか)
5.データ 任意長(16進数2桁x好きなだけ)。ここのバイト数が上のデータ長となる。
6.チェックサム 2バイト
データ長はデータ部の長さが何バイトか16進数で表現
アドレスオフセットはPICのどこのアドレスからデータ部を書き込むかをビッグエンディアンの16進数4桁で示す。だから0100であれば256番地からデータ部のデータを書き込むことになる。
レコード種別は次の中から指定する。
00:データ部分は本当に書き込むデータ
01:ファイルの末尾 データ長は00でデータ部はブランクとなる
02:拡張セグメントアドレス アドレスが16ビットを超える場合に指定。これを指定するとこれ以降のアドレスはここでの値x16+アドレスオフセットの値となる。そんな大きなものを作らないのでまず使わない。
チェックサムはデータ長+アドレスオフセット+レコード種別+データを足していったものの2の補数。合計が16進数2桁を超えた場合下2桁について扱う。
例えば0010h番地から01 02 03 04と4バイト分セットしようとすると
データ長は4バイトなので04
アドレスオフセットは0010h番地からなので0010
レコード種別はデータなので00
データ部は01020304なんだけどリトルエンディアンで表現するので2バイトずつ上下が入れ替わって02010403
チェックサムは16進数で足し算していき04 + 00 + 10 + 00 + 02 + 01 + 04 + 03 = 1E
1Eの2の補数はE2なのでE2
全部繋げて:04 0010 00 01020304 E2
スペースを入れちゃダメなので:0400100002010403E2
あとはこれで終了と宣言するので
データ長は終了宣言は0バイトなので00
アドレスオフセットは無視 0000
レコード種別は終了なので01
データ部は無し
チェックサムは16進数で足し算していき00 + 00 + 00 + 01 = 01
01の2の補数はFFなのでFF
結局:00 0000 01 FF
上のを2つならべた下の2行をテキストエディタに打ち込んで拡張子をhexとして保存
:0400100002010403E2
:00000001FF
実際にADWINさんのPIC PROGRAMMER A+用の書き込みソフトであるPICproで読み込んでみると・・・

テーレッテレー!。画像をクリックすると大きく表示されますが、きちんと0010h番地=0008番地から書き込めることが確認できました。後は意味のあるプログラムを書くだけ!
ここからはハンドアセンブルの時間だ!
これである程度の準備はできたのでまずはPICのコマンドを一気見しよう!
出てくる表現
Wレジスタ:PICが自由に使える短期記憶領域みたいなもの。
レジスタ:STATUSとかTRISBとかPORTBといったもの
ADDWF f, d
機能:W + f → W(d=0), f(d=1)
オペコード: 00 1111 dfff ffff
ANDWF f, d
機能:W and f → W(d=0), f(d=1)
オペコード: 00 0101 dfff ffff
CLRF f
機能:f = 0
オペコード: 00 0001 1fff ffff
CLRW
機能:W = 0
オペコード: 00 0001 0xxx xxxx
COMF f, d
機能:レジスタfの中身をビット反転 → W(d=0), f(d=1)
オペコード: 00 1001 dfff ffff
DECF f,d
機能:f - 1 → W(d=0), f(d=1)
オペコード: 00 0011 dfff ffff
DECFSZ f, d
機能:f - 1 → W(d=0), f(d=1) 結果がゼロになったら次の命令をNOPとして扱う。
オペコード: 00 1011 dfff ffff
INCF f, d
機能:f + 1 → W(d=0), f(d=1)
オペコード: 00 1010 dfff ffff
INCFSZ f, d
機能:f + 1 → W(d=0), f(d=1) 結果がゼロになったら次の命令をNOPとして扱う。
オペコード: 00 1111 dfff ffff
IORWF f, d
機能:W or f → W(d=0), f(d=1)
オペコード: 00 0100 dfff ffff
MOVF f, d
機能:f → W(d=0), f(d=1)
オペコード: 00 1000 dfff ffff
MOVWF f
機能:f → W
オペコード: 00 0000 1fff ffff
NOP
機能:何もしない
オペコード: 00 0000 0xx0 0000
RLF f, d
機能:レジスタfを左シフト、7ビット目をCフラグ、Cフラグを0ビット目へ移動
オペコード: 00 1101 dfff ffff
RRF f, d
機能:レジスタfを右シフト、Cフラグを7ビット目、0ビット目をCフラグへ移動
オペコード: 00 1100 dfff ffff
SUBWF f, d
機能:f - W → W(d=0), f(d=1)
オペコード: 00 0010 dfff ffff
SWAPF f, d
機能:<3:0> → <7:4>, <7:4> → <3:0>, d=0なら移動先はWレジスタd=1ならfレジスタ
オペコード: 00 1110 dfff ffff
XORWF f, d
機能:W xor f → W(d=0), f(d=1)
オペコード: 00 0110 dfff ffff
BCF f, b
機能:f<b> = 0
オペコード: 01 00bb bfff ffff
BSF f, b
機能:f<b> = 1
オペコード: 01 01bb bfff ffff
BTFSC f, b
機能:f<b>=0なら次の命令をスキップ
オペコード: 01 10bb bfff ffff
BTFSS f, b
機能:f<b>=1なら次の命令をスキップ
オペコード: 01 11bb bfff ffff
ADDLW k
機能:Wレジスタとリテラルkの和をとってWレジスタに収める
オペコード: 11 111x kkkk kkkk
ANDLW k
機能:WレジスタとリテラルkのANDをとってWレジスタに収める
オペコード: 11 1001 kkkk kkkk
CALL k
機能:アドレスkにあるサブルーチンを呼び出す。
オペコード: 10 0kkk kkkk kkkk
CLRWDT
機能:WDTのクリア
オペコード: 00 0000 0110 0100
GOTO k
機能:アドレスkに飛ぶ
オペコード: 10 1kkk kkkk kkkk
IORLW k
機能:W or k → W
オペコード: 11 1000 kkkk kkkk
MOVLW k
機能:k → W
オペコード: 11 00xx kkkk kkkk
RETFIE
機能:割り込みからの復帰
オペコード: 00 0000 0000 1001
RETLW k
機能:k - W → W
オペコード: 11 01xx kkkk kkkk
RETURN
機能:CALLされた時点に戻る
オペコード: 00 0000 0000 1000
SLEEP
機能:スリープする
オペコード: 00 0000 0110 0011
SUBLW k
機能:k - W → W
オペコード: 11 110x kkkk kkkk
XORLW k
機能:WレジスタとリテラルkのXORを取る。結果をWレジスタに収める。
オペコード: 11 1010 kkkk kkkk
超小さなプログラム
ループによる時間稼ぎ
割込みを使って時間待ちしてもいいけど、時間待ち中に何もできなくても問題ないというときは古典的にループで時間待ち。
基本的な考え方を図示する。

上図のようにループ回数nを決めてループさせると5n+5サイクル分の時間が必要になる。n=255とすると、1280サイクルになる。PICが1サイクルに4クロックを必要とし、4MHzのクロックを与えていると1サイクルの時間は1マイクロ秒となる。だから1280サイクルなら1.28ミリ秒となる。
これをさらにループさせると設定した時間だけ何もしない関数が作成できる。
/MCLR
Master Clear。Lowになるとリセットがかかる。だから動作中はHighに保つ。
ブラウンアウトリセット
電源電圧が閾値よりも下がることでマイコンにかかるリセット。電源が不安定になったときの誤動作防止の目的で使用される。
VssとVDD
電界効果トランジスタを使っている回路の電源。極論するとVssがGND側、VDDが+電源側。sはソースのs、DはドレインのD。2回続けているのはソース電圧Vsやドレイン電圧VDと区別するためと言われている。
ウォッチドッグタイマ
マイコンを監視して停止や誤動作といった異常事態と判断したらマイコンをリセットするためのタイマ。正常時にはウォッチドッグタイマ回路に定期的に信号を送るようにしておく。この信号が設定時間内に来ない場合マイコンはリセットされる。
スタックメモリ
PICのスタックメモリーは8個なのであまりしつこくサブルーチンを呼ばないこと。
データシート
16F84Aのデータシート(英語)
16F88のデータシート(英語)
12F629/675のデータシート(英語)自分的なメモはこちら