TAP-PicMicrocontroler
long int が使えるPICのCコンパイラです。ほとんど備忘録的ですが紹介したいと思います。
ここで使用したバージョンはVer4.00aというものです。問題となるのはページング関連なので、F87xシリーズなどを想定しています。
PICをFED-Cで開発する |
FED-Cコンパイラについて |
イギリスのベンチャーだとおもいます。Forest Electronic Developmentsという会社です。販売元のIPIのページで説明されているようにfloatは使えませんが32bitのlong intが使えるのがおいしいです。
あと、エディタが2バイトコードに対応していないようで(多分Delphiでかかれている)エディタがまともに使えませんので、外部エディタを併用してコーディングしなければなりません。マウスのメッセージがエディタに落ちるだけでメモリアクセス違反が出てしまいまうことがあります。NTでも動きます。
●Cコンパイラに関して
・変数は先に述べたようにlong intが32bitとなります。
・unsigned char xとか指定して#pragma locate x 0x120等で番地の指定ができます。
・C2Cと違いスタックポインタの概念があるコンパイラですが、スタックを使わずにグローバル固定番地を使用して高速化するオプションがあります。コンパイル時に指定できます。
・スタックはソフトスタックとPICのハードスタックを選択できます。
・ポインタもありますが関数へのポインタはこの高速化と併用できません。下のバンクだとunsigned char ram *pPointerとするとポインタが8bitになり高速化できます。
・const char buf[]="Hello World!"等、constで指定するとプログラムメモリに配置されます。F87xシリーズでもフラッシュ読み出しではなくretlw展開されるようです。
・コンパイラが使用するアキュムレータレジスタがあります。これは自動で作成されるものです。
ライブラリについて |
付属しているライブラリについて簡単に書きます。特定用途ですよーというのがはっきりしている感じです。
・ソフトウェア同期シリアル通信
入出力ピンの指定だけで使えます。
・EEPROMの読み書き
書き込み完了は待ちませんが、書き込む前に書き込み可能かチェックするそうです。
・ソフトウェアI2Cシリアル通信
EEPROMチップへ読み書き機のサンプルがあります。
・IrDA1.0ドライバ
HDSL1001などにつなげられるでしょう。変調復調をするプログラムです。割り込みはその間は休んでくださいとのことです。
・割り込み駆動シリアル通信
非同期シリアル通信モジュール内装のPIC用です。これは使える。QuickInterrupt専用ですのでこれを使いたいときは、QuickInterruptを使うことになります。
・キー入力
4×4のキーボードをスキャンするルーチンです。ポートの指定だけで使えます。
・LCDドライバ
ポートを指定して所定の配線をすれば、ストローブ信号処理なんかをやってくれます。
・ソフトウェア非同期シリアル通信
ピンの指定だけで使えます。割り込みではないので、受信はブロックします。
・文字列処理
Ansi-Cにあるような文字列処理をしてくれる関数群です。特徴は上記のramポインタ版があることと、チェックサム計算があることです。
・文字出力
char,int,long intをASCII文字列に直します。
・ウェイト
単位はmsでintで与えます。
割り込みプログラムについて |
FED-Cコンパイラは割り込みモードが2つあるとhelpにはあるのですが、片方しか私の環境ではコンパイルできないようです。NormalとQuickがありますが、Quickの方は使えました。Normalの方は上記のアキュムレータまで退避するため40命令ほどかかりますが、Quickの方はW-reg,STATUS,FSRのみ退避します。よってQuick時には、計算はできず、代入やif文の一部以外を使うとアキュムレータを壊してしまいます。
しかし通常はPICのハードスタック+QuickInterruptの組み合わせで使うことを奨励しているようです。
QuickInterruptにするにはコード中に
const int QuickInt=1;
を入れておきます。このとき割り込みルーチンで使えるCの命令は
if,&=,|=,&,=
だけと指定してあるのでHelpによると
unsigned char Flag; const int QuickInt=1; void Interrupt(void) { if(INTCON & (1<<T0IF)){ INTCON &= ~(1<<T0IF); LED_toggle(); //何かの関数をコール、中はすぐにインラインアセンブラ } Flag = 1; Flag |= 2; return; }
のようにifの中は比較式なしで、代入は右辺がconst値だけということです。割り込みルーチンからのコールも引数や戻り値は不可です。これだけでは何もできないような気がしますが、割り込みルーチンは基本はフラグ立てに専念してメインルーチンでポーリングしてくださいとのことです。
追伸2001/04/09
オフィシャルにOKではありませんが、割り込みルーチン内では8bitのインクリメントおよびconst値の足し算もできるようです。基本は吐き出されたコード見てアキュムレータを使用していなければ大丈夫そうです。たとえば
TMR0+=15;
Flag++;
とかは大丈夫のようです。(新田さんより)
これだけでは何もできないですが、抜け道はあって、割り込みルーチンは基本はインラインアセンブラを使ってくださいとのことです。
インラインアセンブラについて |
さて割り込みルーチンは基本はインラインアセンブラを使えとのことなので、インラインアセンブラを使用するのですが、これがちょっとコツがいるようです。そもそもこのコンパイラは出力をMPLABにおけるマクロでひたすらページング処理をしているようです。よってコンパイラにbuild inされたマクロを有効に使わないといけないようです。
まずはさしあたり、引数なし、Cの内部関数やアセンブラルーチンをインラインアセンブラから呼び出さない一番簡素なものから書きたいと思います。ほとんどはこれで済むはずです。
●インラインアセンブラの宣言
#pragma asm
・・・・
#pragma asmend
で囲まれたところがいきなりアセンブラコードになります。(いきなりかよ!?)
●インラインアセンブラコーディングの仕方
アセンブラルーチン内ではページング処理を自分で行わないといけません。これを先に述べたマクロを使用して処理するようです。このためにはコンパイラの仕組みに準拠しなくてはいけませんが、それは簡単に記すと次のものがおもなものです。
(1)プログラムコードページは超えてはいけない。そのためのマクロが用意されている。(MFORCEPAGE)
(2)インラインアセンブラに入ったときはそのコードのあるページにPCLATHがセットされている。インラインアセンブラから抜けるときは、PCLATHをクリアした状態にしないといけない。(MLABEL)
(3)ただしこのタイミングでページングするとバグるので毎回アセンブラのエントリでPCLATHをセットしなおす。(LSETPCH)
(4)内部ではRAMページングは基本的にRP1,RP0ともに0となっていることを想定している。マクロもその条件でしかきちんと動かない。インラインアセンブラから抜けるときはRP1,RP0をともに0にして抜けないといけない。(MCLEARRP 0x180)
この3つを守り、追加のマクロでOKです。例は次のようなものです。グローバルで宣言されている値val1の値をval2に移すものです。
unsigned char val1; unsigned char val2; void AsmTestFunc(void) { #pragma asm MFORCEPAGE 30 ;アセンブラ内でページングしないように MLABEL ;このタイミングでページングするとバグるので LSETPCH AsmEntryLabel ;PCLATHをAsmEntryLabelのアドレスに従わせる AsmEntryLabel: MSETRP val1 ;RP1,RP0をval1のものにセットする movf val1,w ;アンダーバーはつけない MCLEARRP val1 ;val1で立てたRPだけ戻す MSETRP val2 movwf val2 AsmExitLabel: MCLEARRP 0x180 ;RP1,RP0を強制的に0にする MLABEL ;PCLATHを強制的にクリアする #pragma asmend return; }
緑に色がついているところがマクロです、ってほとんどマクロで埋め尽くされていますね。最初のMFORCEPAGEはこの数字の間はページングが起こらないところに配置せよという命令です。さらにEntry前に関数内でページングが起こったときの処理がなされることを想定してprogram pageを再設定しないといけません。上記のコードで注意点はMCLEARRPは立てたビットしか戻さないということです。よってすでにRP1,RP0が立っているときはまともに機能しません。よって、毎回MCLEARRP 0x180をいれていいのですがこれには2命令必要となるため、それを1つもしくはなしにするためのマクロといえると思います。上記の例では最後のRPの強制クリアはMCLEARRP val2でもいいのですが念のためこうしています。
その他引数や戻り値ありの場合はスタックの操作が必要ですが、そもそもグローバルで渡した方が速くて短いので別の機会に譲ります。とりあえずスタック操作にひつよなキーワードはMCALL, MGETFSRSPOなどです。これらはCの内部関数をアセンブラからコールするときなどに使います。
ところで、helpには触れられていませんが、もしグローバルで値を引き渡すなら、C2Cのようにグローバルにアドレスを指定してインラインアセンブラで参照する変数を作る(#pragma locateを使う)ほうがスマートではないかと思います。こうするとすでにアドレスが見えているのでマクロも要らないです。(ただし#pragma locateの落とし穴は下のバグの項を参照。)インラインアセンブラから抜けるときにPCLATHとRP1,RP0をクリアすればそれでいいのですから。
追伸(20010225)
#pragma locateでインラインアセンブラ用のアドレス設定するときは0x0ff以下の番地にすべし。下を参照。もちろんF877の場合は0xefより下で。
その他のトピック |
●コンフィグワードの設定
コンフィグワードの設定方法は#__config 値 で設定できますがわかりづらいですのでコード中にインラインアセンブラをいれることで処理することもできます。コード中にインラインアセンブラで
#pragma asm
__CONFIG _CP_OFF & _WDT_OFF & _BODEN_ON & _PWRTE_ON
& _HS_OSC & _WRT_ENABLE_ON & _LVP_OFF & _DEBUG_OFF
& _CPD_OFF
#pragma asmend
といれることでMPLABのように処理できます。上記は #__config 0x3F72 と書くのと同じです。ただし_DEBUG_OFFはマイクロチップの方のバグで *.inc ファイル中で_DEGUB_OFFとなっているバージョンのものが入っている場合があるので、そのときは元のファイルを直しましょう、笑。
●EEPROMデータをコード中に入れる
秋月のライターなどを使っている場合はEEPROMデータは0x2100番地からのデータとしてHEXファイルに入れると書き込めるようになっていますが、これをコード中に入れれると便利です。そのときは
/*for EEPROM setup*/ #pragma asm VARIABLE CUR_ADDR DUMMY_LABEL CUR_ADDR=DUMMY_LABEL ORG 0x2100 RETDATA 0,"Hello World.",0x3f ORG CUR_ADDR #pragma asmend
というように、マクロ系の命令を使い、ダミーのラベルを作って最後のORG命令でアドレスを元に戻せばマクロ系の処理を乱すことなく埋め込むことができます。これは関数と関係なくファイルの最後にでも付け加えるといいと思います。コンパイルするときにエリア外ですよ、と警告が出ますが無視しましょう。
●#pragma locateとrampage2の使用について
下のバグの中での項目がふえてコンフリクトしそうなので、いまのところの使い方をまとめてみたいと思います。以下に問題の起こらない方法を箇条書きします。
・rampage2以降はポインタアクセスすべし。
・#pragma locateは0xff (F877なら0xef) から後ろから低いほうへ取る。しかもすべてもコンパイラ任せの変数をとったあとに。アドレスはリストファイルをコンパイル後に見て、通常変数の最後のラベルと重なってなければいい。追伸2001/04/29
: もしくはextern修飾してpragma locateすると番地にのみ直接アクセスできます。このときはコンパイラ任せの変数を取る前でも大丈夫です。しかしリストを見るのを忘れずに。
基本はインラインアセンブラではアドレスがわかっていたほうが使いやすいのとキャラバッファのために rambank2を有効活用するのが目的です。
●バグについて
バグに近いものを見つけたら挙げていきたいと思います。以下の記述は私個人でためしただけなので間違ってるよ、という場合はご指摘いただけたら幸いです。
Don't hold me to below descriptions. But if you have a notice about correct infomations or insights, please mail me.
追伸2001/04/09
新田さんからこの項目についていくつかコメントをいただきました。それを追加いたしました。ありがとうございました。
----構造体とビットフィールドについて(新田さんより)----
構造体の中で1ビットのビットフィールドを使えるのは最初の8bit分だけです。
コード内で、式の右辺や条件分で参照されると btfsc test,24 といった具合に構造体のシンボルの番地からまともに足し算されたオペランドが吐き出されてしまいます。24は下位3bitだけが有効となります。FED-Cにはポート入出力用として
PA.B5 = 0 とかでアクセスできますが、このような方法だけに限定したほうがよさそうです。でもフラグの扱いをするときは効率的なコードになりますので上の8bit条件さえ守れば有用です。
----enumについて(新田さんより)----
enumはconst値でしか使用できません。helpを読むとenum型はcharだったりintになったりするとかかれています。ということはenumは型として宣言できないと(拡大)解釈することもできます。つまりenumで定義されたラベルが#defineと同じように数字に置き換わるだけのようです。
----変数の番地指定と初期化についてその1----
グローバル変数は、変数のアドレスを指定することができますが、このとき
unsigned int val = 0;
#pragma locate val 0x120
などとして初期値を指定すると、全くプログラムが走りません。よって
unsigned int val;
#pragma locate val 0x120
として、メイン関数で初期化しましょう。特にF877系では0x110以降のramは手動で配置しないといけないので要注意。
----番地指定変数についてその2----
F877等は0x120等に変数を取ることはできますが、これはいまのところ代入の左辺にしか使えません。結局インラインアセンブラのアキュムレータ以外には使えないということか?なにしろ
movf 288,w とかいうめちゃくちゃな展開されるんじゃ困る。0xff以降の#pragma locateはおまけ機能でしょう。ただスタックはbank3以降に配置するようなので安心。
けれども文字列バッファはなぜかきちんと動く。おそらくポインタアクセスしてるため。たとえば
buf[16];
#pragma locate buf 0x110
としてポインタアクセスするときとかは、iPrtString(buf,iVal)とかしてできた文字列をポインタアクセスすることは今のところ問題ありません。
----番地指定変数についてその3----
さらに番地指定について。なんと、コンパイラは#pragma locateした番地を避けて変数を認識してくれないことがあります。よって、番地指定は0xff以内でかつコンパイラが取得してこないであろう場所、つまり最後の方の番地しか指定できません。c2cの場合はこればかりはちゃんとやってくれるのに。
----番地指定変数についてその4----
まだまだある。たとえばその1みたいにキャラクタバッファをつくって番地を指定しても、バッファ領域はもとのまま残ってしまう。つまり2重にRAM
を確保していることになってしまう。ポインタアクセスはf877のばあいなどにおいてrambank2でもきちんとうごくので、rambank2にアドレスを取るときは、
unsigned char *buf = (unsigned char*)0x110;
として、ポインタアクセスに割り切りましょう。ちなみにポインタそのものはrambank0,1におくように。
普通の変数でも2重に取られるようです。
追伸2001/04/29 : extern修飾して番地を指定すると2重にとらないことを確認。たとえばTMR1に関して
extern unsigned int TMR1;
#pragma locate TMR1 0x0e
で16bitカウンタにint型でアクセスできるようになります。extern修飾をつけてもこの場合リンカエラーは出ませんです。
----LCD初期化について----
LCDの初期化が2行モードにならないのは僕だけ?モジュールが違うせい?ライブラリのソースをいじって初期化シーケンスを改良しても駄目だったので、多分ウェイトが短すぎるのだと思う。
追伸2001/03/07 : CDROMを見てみたらLCDのパッチが入っていました。日本仕様のものがきちんと入っていました。申し訳ありません。
----goto命令について----
gotoもswitchも単独では使えるのですがそれら同士を絡めた場合はまともに動かなくなるようです。これはswitchのほうが内部ではgoto先をコントロールしてそこへgotoしているからだと思います。また、goto命令を使っている状態で、if文を使いまくっているとやがてコンパイルできなくなったり、ジャンプ先のアドレスが変わってしまったりする。だから分岐はswitchを使って、gotoは一切使わないようにしています。とにかくgotoを使ってると何かとおかしくなるってこと。
----その他いまだ原因のわからない現象----
どの場合に再現できるかわからないが再現だけはできるおかしな現象について書きたいと思います。ここの章はいまだ解析中ですが、解析がもしできているかたがいらっしゃいましたら、ご教授いただけないでしょうか。よろしくお願いします。
○ローカル変数
サブルーチンの中でさらにサブルーチンを呼び出したとき、そのローカル変数がおかしくなることがある。これはLocalOptのせいかなあ。リカーシブルはもちろん駄目なのはわかるけど、LocalOptを既に使っているルーチンからさらにLocalOptを使うとこけるのかもしれない。
----そのた細かいことについて----
○forループについて
for文で初期値の欄がないとコンパイル時にコンパイラがアクセス違反を起こし、リソースを解放せずに止まってしまいます。たとえばfor(
; ; )といった形で無限ループする場合はwhile(1)を使いましょう。またforのカッコないにカンマで区切って複数の計算式をいれることもできません。
○#defineについて
・#define内で文字型データ( 'a' など)は使えません特にマクロ使用時に注意。0x30とかは可能です(新田さんより)。
・#define内でロジカルラインセパレータ '\' は使えません。
・#define内では/* -- */形式のコメントは使えない。//形式は使えます。
○long intの型名について
私は型名をフルネームで書く癖があるのですが、
unsigned long int
は
unsigned long
にしないと通りません。
感想 |
とにかくマクロごり押しの出力コードでページング処理をしているところがすごいです。マクロだけでページングできるんですねぇ。インラインアセンブラはこれを使いまくることになりますが、このときは自分でそれらを処理しなくてはならないのはPICならしかたないところです。コンパイラの構造はインラインアセンブラ時にC2Cのほうが、展開コードがすべてグローバル変数となるのでわかりやすいと思います。しかし、便利なライブラリがある点ではこちらも捨てがたいです。
無理してANSI-Cに準拠させるとコードサイズが大きくなりそうですが(もちろん一概には言えませんが)、うまい感じにライトに準拠している感じでPIC用として好感がもてます。C2Cのほうがもっと軽い感じですが、そのバランスポイントがこのコンパイラの設計点なのだと思います。やっぱこのコンパイラはlong intが32bitなのが威力抜群だと思います。
自分の中ではCになれている私としては、アセンブラで書くより数倍楽です。あと不可避なバグが今のところないというのもすごいです(もちろんc2cのほうにも今のところ不可避なバグはありません)。
結論、FED-Cはすごく使える。