PIC I/O の注意点

昨日、実は PIC で例の工作の続きをやってました。しかし、双方向 I/O のところでハマり、結局寝てしまいました。
そちらは単純なプログラムミスだと思われるのですが、PIC の双方向 I/O って、ビット操作命令と組み合わせて利用するときに注意が必要のようです。ある複数の双方向ポートビットに対して予め出力用ラッチを 0 に固定してから、その後でモードを write に切り替えたくても、ポートが入力モードだと出力用ラッチは読み出せないので、1ビット単位で操作する BSF や BCF では複数の出力用ラッチを 0 に固定できないことがあります。例えば複数のポートビットが外部でプルアップされていると、二回目の BCF では既に 0 に設定したはずのポートが、また 1 に戻ってしまう、ということになります*1。この辺の説明は、PICmicro Mid-Range MCU Family Reference Manual (33023a) の 9.10.1 Bi-directional I/O Ports 辺りに詳しく説明が出ています。
これを避けるためには、レジスタファイルへのビット操作命令 BSF, BCF を避けて、一度レジスタファイルを読み出して論理演算をして、バイト全体を書き戻すという操作が必要になります。この場合はアトミックな操作にならないので、割込禁止などの処置が必要になるかも知れません。

後記 (2009/02/05)

最近はちょっと Atmel AVR マイコンに興味があるのですが、この辺の汎用 I/O ポートの振る舞いは、AVR のほうがよく考えられているようです。もっとも、複雑さは同程度のような気もします。

HI-TECH C が変?

HI-TECH C PRO for the PIC10/12/16 MCU Family 9.60PL3 の LITE モードというのを使っているのですが、アセンブリ出力がなんだか変です。というか仕様なのかなあ。(ターゲットは、ちゃんと PIC16F716 になっています。)
というのは、TRISB レジスタへのビットアクセスをするコードがあるんですが、バンク切替のコードがおかしいのです。なんか、コード出力が 1サイクル遅れているのです。例えばリスティングファイルを見るとこんな感じ。

1417  039B  1803          btfsc   status,0
1418  039C  2B9F          goto    u2281
1419  039D  2BA1          goto    u2280
1420  039E  1683          bsf     status, 5       ;RP0=1, select bank1
1421                                  
1422                                  
1423  039F         u2281:
1424  039F  1606          bsf     (1076/8),(1076)&7

本当は、1124行目の bsf で TRISB を操作する前に、status レジスタのビット 5 を立てて欲しいのに、このコードではそんなことはできないような。確かに PIC のマニュアルでは TRISB レジスタをビット操作しないほうが良いって書いてありますが、これはそういう問題ではない気がします。
全体的に見て、このコンパイラの LITE モードでの出力コードは汚いです。いくらフリーだといっても、これでは PRO 版を買う気が起こりません。でも、まあ PRO 版をお試しで使ってみました。上記のコードは次のようになります。これは正しい動きをします。

 765  0274  1683          bsf     3,5     ;RP0=1, select bank1
 766  0275  1903          btfsc   3,2
 767  0276  2A79          goto    L3
 768  0277  1606          bsf     134,4
 769  0278  0008          return

もう、コードのクオリティが違いすぎます。でも、$1500なんて個人じゃ払えんぞ。ちなみに STD 版でも $1000 です。とほほ。(っていうか、こんなしょうもないバグがあるようじゃ、信用できないよねえ。)
今度、CCS のコンパイラも評価してみようっと。

後記

ATU の制御試作ソフト、無事に動きました。ATU の LC ネットワークが切り替わることが確認できました。操作性(使い勝手の部分)に問題があるので、もう少し詰めが必要ですが。

*1:一度、出力用ラッチを 0 に設定しても、次のビット操作で読み出すとポートが 1 に見えてしまうため。