HOME > 技術談話 > PIC® microcontroller > 特殊な指定と宣言
はじめに リセットと割り込み Build時のワーニング スタートアップ処理 事前定義シンボル 変数 CONFIG ID
DELAY CLRWDT/DI/EI 変数のバンク指定 スタックの算出 Release/Debug
HI-TECH C®はU.S.Aにおける Microchip Technology Inc.の登録商標です。 コンパイラ特有の使い方をピックアップしました。他のコンパイルに移植する際の参考になれば幸いです。 これらの特有の項目をファイル運用等で隠蔽することができれば作成したプログラムの移植性が向上します。 |
▲ |
プログラムはリセットベクタよりスタートアップ処理を実行してからメイン処理へ分岐します。割り込みの場合は割り込みベクタから割り込み処理へ分岐します。 大きく分けてメイン処理と割り込み処理に大別されますが、メイン処理は関数名「main」が期待され、割り込み処理は修飾子「interrupt」が期待されます(割り込み処理名は任意)。PIC10/12/16系のPICマイコンでは割り込みベクタは1つだけなのでinterrupt修飾子が許される関数は1つのプロジェクトで1つだけです。 void main(void) { ; // メイン処理(通常は無限ループ構造ですが、終了した場合は再度メイン処理が呼び出されます) } void interrupt isr(void) { ; // 割り込み処理 } メイン処理を呼び出すスタートアップ処理はコンパイラにより自動生成されます。内容は違いますが割り込み処理の実行前後でSTATUSレジスタ、Wレジスの退避と復帰を行う命令列もコンパイラにより自動生成されます。 |
▲ |
Build時のコンパイルエラーは必ず修正する必要がありますが、ワーニングは見逃したい場合が時々あります。 例えば以下の様なワーニングが表示されたとします。 Warning [359] F:\PicTest\16F84A\_main.c; 3.18 illegal conversion between pointer types このワーニングを見逃す場合…要は表示を行わないだけですが、ソースプログラムの冒頭で #pragma warning disable 359 と記述することでワーニング番号359に関しては表示が抑止されます。 ワーニング表示は何かしらの問題が含まれることを示すので、できることならばワーニングが表示されないプログラムを目指したいものです。 蛇足ですが、Lite版でコンパイルを行う場合必ず表示されるワーニングがあります。 (1273) Omniscient Code Generation not available in Lite mode (warning) 残念ながらLite版を使う限り、この表示だけは抑止することはできません。 |
▲ |
メイン処理の実行に先立ちスタートアップ処理が実行されますが、このスタートアップ処理では、 ・変数領域のクリア(Clear bss) ・初期化済み変数領域の初期設定(Initialize data) ・内部クロックのキャリブレーション(Use OSCCAL…デバイスによっては指定は無意味) が行われます。PICマイコン自体の初期設定は行われません。 このスタートアップ処理は自動生成されますが、幾つかの機能はユーザが設定できるようになっています。 プロジェクト設定の画面例を以下に示します(Project -> BuildOptions -> Project、Linker)。 |
PIC10/12/16用のLite版なので総てを指定できませんが、通常はディフォルト設定のままで大丈夫です。 項目設定の組み合わせ実験を行いましたが Clear bss / Initialize data の設定は効果を確認できず(Lite版の制限?)常に機能が有効になっているようです。Use OSCCALは効果を確認できましたが、PICマイコンによってはOSCCAL機能が搭載されていない種類があり、このようなデバイスでは設定は無意味でした(デバイスを判断して最終的に展開される)。 |
▲ |
他のコンパイラと同様、#で始まる宣言を利用することができます(#if、#else、#elif、#ifdef、#ifdef、#ifndef、#define、#undef)。特殊な例として#asm~#endasmで挟まれた区間をインラインアセンブラとして記述することができます。 ソースプログラムの中で利用可能な事前定義シンボルを以下に載せます(抜粋)。 これらのシンボルをうまく使い分けると移植の際に便利です。 |
シンボル | 説明 | |
HI_TECH_C | コンパイラにHI-TECH Cを利用したことを示します。 | |
_MPC_ | Microchip PICマイコン系をコンパイラしたことを示します。 | |
_PIC12_ | 12bitデバイスを選択していることを示します。 | |
_PIC14_ | 14bitデバイスを選択していることを示します。 | |
_ROMSIZE | 利用可能なプログラムメモリサイズを示します。 | |
_EEPROMSIZE | 利用可能なEEPROMメモリサイズを示します。 | |
_chipname | 選択されているデバイス名称を示します。 | |
__FILE__ | ファイル名を示します(フルパス名)。 | |
__DATE__ | 現在の年月日を示します。 ex."Sep 19 2009" … 2009年9月19日 | |
__TIME__ | 現在の時刻を示します。 ex."15:18:22" … 午後3時18分22秒 |
ファイル名、年月日、時分秒シンボルを利用するプログラムを時々見かけます。使うことは問題ないのですが、完成済みの任意プロジェクトを別の日時、別のパソコンで再コンパイルした時にHEXファイルの照合がNGになります。マイコン用のプログラムはパソコン用のプログラムと異なり、生成物(HEXファイル)が完全一致しやすく、それを期待している人もいます。できることならば乱用を避けた方が後に続く人を迷わせなくて良いかと思います。 インラインアセンブラについて補足します。 前述の #asm~#endasm を利用する以外に asm("~") を利用することができます。アセンブラ内で変数、関数、そしてデバイスのレジスタを参照する場合はシンボルの前に「_(アンダスコア)」を付与します。このあたりは他のコンパイラと同様です。なお、変数やデバイスのレジスタを利用する際に必要となるバンク切り替えはユーザの責任で行う必要があるので忘れないでください。 |
▲ |
(1)グローバル変数
グローバル変数は他のコンパイラと同様、自由に宣言することができます。なお、データメモリがバンク分割されているので大きなバッファを確保することは困難です。 |
(2)ローカル変数
ローカル変数の扱いは特殊です。他のコンパイラの様にスタック領域へ変数の積み上げを行わず、データメモリの領域へ関数毎に宣言したシンボル名で予約します(見掛け上はグローバル変数)。なお、関数同士で領域の共有を行うことでデータメモリの使用量を抑制しています。 コンパイラは関数の依存関係を確認しローカル変数を実メモリへ割り付けます。これは呼び出す側、呼ばれる側という関係だけでなく、割り込み処理で利用されるかも確認しています。任意の関数がローカル変数を利用し、その関数がメイン処理と割り込み処理で利用されるならば自動的に2つの関数を作成し、それぞれに異なるローカル変数を割り付けます。任意の関数はプログラムメモリ、データメモリ共に倍の領域が必要になるのでプログラム構造に注意して下さい。 |
(3)作業用変数
他のコンパイラでも同様ですが、算術演算の組み合わせや下位関数の戻り値同士を演算するような場合に作業用変数が生成されます。生成規則はローカル変数の手法に準じます。 普段は意識しなくても大丈夫ですが、ローカル変数、すなわちデータメモリの領域が予約されるのでデータメモリの不足を招く恐れがあります。コーディングの手法で回避することもできますが、設計当初の段階でデータメモリを総て使うような仕様設定は避けるようにして下さい。 |
(4)関数の入出力パラメータ
唯一の汎用レジスタであるWレジスタは1バイトの大きさを持ちます。関数の入出力パラメータが1バイトの場合はWレジスタが利用されます(受け側の関数で作業用変数に入れ替える場合があるので効果は今一つ)。 2バイトを超えるパラメータ、あるいは複数個のパラメータの場合は、関数毎に設けられた専用の作業用変数を用います。この作業用変数の生成規則はローカル変数の手法に準じます。 |
▲ |
ウォッチドッグタイマの利用有無、源発振の種類の設定等、PICマイコンの環境を設定します。 利用可能なパラメータは利用するPICマイコン毎に異なるので、対応するヘッダファイルを確認してください。 ヘッダファイルはコンパイラをインストールしたパソコンに保存されています。標準的なインストールであれば、 C:\Program Files\HI-TECH Software\PICC\9.70\include に保存されています(9.70はコンパイラのバージョンなので異なる場合があります)。 記述例として、 __CONFIG(UNPROTECT & BOREN & INTIO); の様に、先頭に「_(アンダスコア)」を2個、行末に「;(セミコロン)」が必要です。各パラメータは「&」で連結します。 見た目を良くするためにパラメータ単位で複数行に分けてコメントを書きたくなります。その際、行末の「¥」は利用できません。指定しないことで複数行にわたって記述することができます。 例えば、こんな感じでコーディングできます。 |
__CONFIG( | UNPROTECT | // プロテクトしない | |
& | BOREN | // ブラウンアウト検出許可 | |
& | INTIO | // 内蔵クロック利用 | |
); |
config指定はプロジェクトの中で1つだけ指定することができます。 |
▲ |
デバイスに書き込むIDを指定します。指定は __IDLOC 、もしくは __IDLOC7 で行います。 |
使用例 | 格納される値 | |
__ODLOC(12AB); | 0x01, 0x02, 0x0a, 0x0b | |
__IDLOC7(0x12, 0x34, 0x56, 0x78); | 0x12, 0x34, 0x56, 0x78 |
双方共に、先頭に「_(アンダスコア)」を2個、行末に「;(セミコロン)」が必要です。 前者の __IDLOC は#defineで宣言したシンボル指定することができます。その場合、#define側では「"(ダブルクォーテーション)」は不要です。細かい話ですが、桁数が短い場合は頭詰め(省略部位の扱いは不明)、桁数が多い場合はコンパイルエラーになります。 後者の __IDLOC7 は文字コードで指定することもできます。その場合「'(シングルクォーテーション)」で括る必要があります。細かい話ですが、必ず4組指定し、値は7ビット数値表現できる値を指定します。 IDをプログラム版数管理に使用する場合の記述例を示します。情報を一元管理した状態でIDの指定だけでなくプログラムからも参照できるようにしてみました。もう少しスッキリした上手い方法がありそうですが…。書き込み機での表示が綺麗になる様にしてます。 |
#define | VER1 | 0 | // 版数上位十の位 | |
#deifne | VER2 | 1 | // 版数上位一の位 | |
#define | VER3 | 0 | // 版数下位十の位 | |
#define | VER4 | 0 | // 版数下位一の位 | |
__IDLOC7(VER1, VER2, VER3, VER4); | // IDの宣言 | |||
const | unsigned char | tVersion[] = { | // プログラム版数文字列 | |
VER1 | '0', VER2 | '0', '.', VER3 | '0', VER4 | '0', 0x00 | ||||
}; |
▲ |
ちょっとした時間待ちを行うための命令です。 源発振の値を事前に登録して絶対時間を待機する命令とマシンサイクルを基準とした待機があります。 |
命令 | 説明 | 備考 | |
_delay(Value) | マシンサイクル単位の待機 | ||
__delay_us(Value) | マイクロ秒単位の待機 | 源発振登録必要 | |
__delay_ms(Value) | ミリ秒単位の待機 | 源発振登録必要 |
源発振の登録はシンボル _XTAL_FREQ に値を登録します。例えば4MHzを指定するのであれば、 #define _XTAL_FREQ 4000000 // Hz単位で指定する になります。 各命令はマクロ展開されます。変数を指定した場合はリンクエラーになるので定数のみ指定してください(なぜかエラー)。待機時間が 0 の場合は命令の展開は行われません。 各待機時間はマシンサイクルに変換されますが、時間を変換する演算での桁あふれに注意してください。あまり大きな待機時間を指定すると桁あふれにより短時間待機になってしまう場合があります。 |
delayを使用したサンプルプログラムを公開します → TestDelay.zip 妙な挙動もあります。待機時間の値により CLRWDT 命令が導入されます。長時間待機の場合に有利ですが、時間の指定との関係は無いように見えます。できることならば CLRWDT 命令はプログラムの中で唯一の場所で利用したところです。本件はコンパイラの不具合の可能性も考えられるので、現在の状態で CLRWDT が入っているからと言って将来にわたって維持されるとは限らない可能性があります。 使い道にもよりますが、ちょっとした待機を行う場合は便利です。なお、ソフトウェアタイマになるので待機中にプログラムが進まない、割り込み処理により待機時間が延びるという欠点もあります。また、小さい時間単位の待機を関数化して繰り返し呼び出した場合はオーバーヘッドが大きくなります。 余談ですが長時間待機を利用したプログラムのシミュレーションデバッグを行う場合はパソコンの性能に注意してください。なかなかもどってきません。できることならばデバイスが備えるタイマを利用した待機を利用したいところです。 |
▲ |
ウォッチドッグタイマのクリア、割り込み禁止操作はマクロにて提供されます。 |
機能 | 記述例 | |
ウォッチドッグタイマのクリア | CLRWDT(); | |
総ての割り込み禁止 | di(); | |
総ての割り込み許可 | ei(); |
▲ |
変数の配置条件はデータメモリのバンク構成により異なります。バンクの構成はデバイス毎に異なるのでデータシートにより確認してください。 グローバル変数の割り付けはコンパイラ、リンカにより自動的に行われます。この時、指定される変数のサイズが任意のバンクに入らない場合は別のバンクに割り当てます。 時として処理速度対策のために任意のバンクに変数を配置したい場合があります。その場合は変数に対して bank キーワードを付与します。変数をバンク1に割り当てる場合の記述例を以下に示します。 bank1 unsigned char Value; コーディングはこれで済みますが、これだけでは割り付けが正しく行われません。コンパイラのプロジェクト設定でバンクを利用する旨を指定しなければなりません。 プロジェクト設定の画面例を以下に示します(Project -> BuildOptions -> Project、Compiler)。 |
画面下の方にある「Address qualifiers」でバンク指定の機能を選択します。 |
設定値 | 効果 | |
Ignore | 初期状態です。bank 指定を無視し自動割り付けを行います。 | |
Request | bank 指定を有効にしますが、配置できない場合は自動割り付けになります。 | |
Requier | bank 指定を有効にします。配置できない場合はエラーになります。 | |
Reject | 常に bank0 で動作します(動作未確認)。 |
「Request」を指定してもよいのですが、変数のアクセスに伴いバンク切り替えが頻繁に発生してプログラムの実行効率が低下する場合があるので、バンク指定を意識しているのであれば「Requier」を指定した方が無難です。 単純変数はバンク指定だけで済むのですが、ポインタを利用する場合は解釈に注意が必要です。 |
コーディング | 意味 | |
bank2 char Value1; | バンク 2 に変数 Value1 を配置。 | |
bank2 char * Value2; | バンク 2 をアクセスする変数 Value2 は自動配置。 | |
bank1 char * bank2 Value3; | バンク 1 をアクセスする変数 Value3 はバンク 2 に配置。 |
バンクの詳細については以下のリンク先で「bank」をキーワード検索すると参考記事を閲覧することができます。 http://www.htsoft.com/support/faqs.php |
▲ |
コンパイラ、リンカはスタックの消費量を自動的に算出し、対象デバイスで利用可能なスタックサイズを超えて関数呼び出しが行われる場合は以下の様なワーニングを表示します。 Warning[1258]…行数…possible stack overflow when calling function 関数名 ワーニング表示ですが有効スタック数以上のスタック消費が行われる場合、プログラムは暴走します。 スタック消費量の算出はメイン処理、割り込み処理双方の合計になります。main 関数の突入時が消費量 0 、割り込み処理を利用する場合は消費量 1 となり、以後利用する関数の階層に応じて合計値を算出します。割り込み処理を優先的に計算するのでエラーメッセージで示される関数名はメイン処理側の関数になります。 明示的な関数だけでなく暗黙的に使用される関数、例えば乗算ライブラリが利用された場合(結果的に関数を利用するケース)でもスタック消費量の算出に利用されます。仮に乗算ライブラリでスタック消費量を超える場合は関数名 _l_mul がワーニング対象になります。 スタック消費量の算出において幾つかの特別ルールがありました。これはコンパイラのバージョンアップにより状況が変わるかもしれません。 ・割り込み処理に記述された delay は関数としてカウントされる(マクロ展開のはずですが)。 ・メイン処理に記述された delay は関数としてカウントされない。 ・スタック最大数は有効数から 1 減じた値を利用している(詳細不明、デバッガを考慮している可能性あり)。 これらのことから、ワーニングが表示された場合に問題があるかと言うと、時には問題が無い場合もあります。 微妙なまとめになりましたが、スタック消費量には余裕を持つことが大切です。 |
▲ |
他の開発環境と同様、Release/Debug を切り替えることができます(MPLAB®IDE 上での操作)。切り替え操作によりビルドオプションが影響を受け「__DEBUG(先頭に"_"が2個)」という宣言が付与されるか否かが異なります。 プログラムで参照するのであれば、 #ifdef __DEBUG // Debugモード時に利用する命令群 #endif #ifndef __DEBUG // Releaseモード時に利用する命令群 #endif の様なイメージです。値として数字の1が指定されますが、値そのものを判定する必要はありません。 組み込み系のプログラム開発ではリビルド時の再現性が良く、開発環境のバージョン、作業対象のファイルが同一であれば開発用のパソコンを変更しても同一の生成物(Hex ファイル)を得られます。今回 Release/Debug の切り替えを試した限りでは宣言が有るか無いか、だけの様です。プログラムで宣言を参照して流れを切り替えていない限り生成物の内容は同一になります。また、Release を選択してもプログラムのデバッグを行うこともできます。 注意点としては、 ・プログラム開発に利用するライブラリが宣言を参照している場合がある。 ・日付や時間を含む宣言を参照している場合、結果的に生成物が変化する場合がある。 でしょうか。過信は禁物です。 運用上は当該宣言を利用せず Release だけでも十分ですが、プログラムの内容を変えず、外部からの指定だけで局所的な動作の確認をする時に便利です。ただ、複数人数で開発を行う場合に各自が同時に宣言を利用した場合は…不可解な動作をするプログラムが生まれるかもしれません。 宣言の利用は範囲を狭めるか、排他的に利用する様な工夫が必要です。 |
▲ |