HOME > 技術談話 > PIC microcontroller > 標準シンボル
はじめに いきなりサンプル ファイルの構成 ファイル関連図 特別な宣言 おしまい
▲ |
いきなりですがサンプルをアップします → TemplatePIC.zip 2010.12.27 今回はPICマイコンを対象とし、開発環境はMPLAB IDE V8.40 と HI-TECH C V9.70 を利用しています。 ダウンロードした圧縮ファイルを解凍すると以下の3ファイルが展開されます。 |
ファイル名 | 内容 | 依存度 | |
_AtrDef.h | データ型とよくある定数を定義。 | 汎用 | |
_AtrMpu.h | マイコン依存のデータ型や定数を定義。_AtrDef.hから参照されます。 | マイコン依存 | |
version.h | Make用、版数管理用のバージョン番号定義。必須では無い。 | マイコン依存 |
今回はPICマイコンを対象としましたが、マイコン依存ファイルを修正することで他のマイコンにも再利用できます。 なお、定義に関しては「コレ」という様な絶対的な「決め」がありません。皆さん経験を積むに従って考え方が収束する方向にあると思いますが、私自身このサンプルで Fix したとは考えていません。日々プログラムを作り込むことで、より良い宣言の仕方、より良いインクルードの仕方を身につけて頂ければと思います。 |
▲ |
(1)_AtrDef.h このファイルではデータ型(char/short/long)の typedef 宣言と真偽判定、コードシンボルを定義しています。 データ型の宣言手法は uITRON や tEngine での宣言方法を参考にしています。なお、参考対象が和製であるため海外のライブラリを引用した場合はシンボルが衝突する可能性があります。そのような場合はどちらかのシンボルを修正する必要があります。 このファイルを各プログラムモジュール(コンパイル対象のソースプログラム)でインクルードします。 |
(2) _AtrMpu.h このファイルではマイコンに依存したデータ型と定数、そしてマイコン自身のレジスタを定義しています。 マイコンに依存したデータ型はずばり、int型、BOOL型、BIT型、そしてHL関係です。このあたりはお好みで。 BOOL型は真偽判定を行うために定義しました。一般的にはint型なのですが、レジスタ幅とコンパイラの性能に応じて修正して下さい。今回のPICの例ではプログラム容量、処理速度の面から有利なchar型を適用しています。 レジスタ定義はマイコンの型番、もしくはPICマイコンであれば pic.h やhtc.h を指定します。たいていの場合、マイコンメーカーからレジスタ定義ファイルが公開されているので、これを指定します。このレジスタ定義を隠蔽することで前述のデータ型を含め、各プログラムモジュールがマイコンを直接を意識する必要が軽減されます(ゼロでは無い)。 このファイルは _AtrDef.h からインクルードされるので各プログラムモジュールからのインクルードは不要です。 |
(3) version.h 版数管理で引用するバージョン番号が定義されています。 今回のサンプルではPIC10/12/16系マイコン用にと作り始めたのですが、PIC18系では上手く参照できないので ifdef で分けています。マイコンが固定されれば一方の記述だけで問題有りません。 このファイルを各プログラムモジュールからインクルードしますが、ファイルを分けているところがポイントです。 プログラムの評価が完了し、バージョン番号を修正する場合。もしバージョン番号がプログラムの中に埋め込まれていた場合はどうなるでしょう?修正作業は数秒で完了すると思いますが、動作が確認されているファイルを修正することになります。ということは、修正ミスした場合は悲しい結果を招くことになります。 修正後に修正前のファイルと比較照合する手段もありますが、精神論的には触らなくても良いファイルを触らずに済むのであれば、その方が気分的に楽です。 このファイル。少なくとも1つのプログラムモジュールでインクルードしていれば用は足りるので、必ずしも総てのプログラムモジュールに指定する必要はありません。 が…。 総てのプログラムモジュールがこのファイルをインクルードすることで、バージョン番号を更新した後のビルド作業がリビルド動作(全コンパイル)になるというメリットがあります(関連づけ必要)。これもまた精神論的な話ですね(笑 |
(4)蛇足 ファイル名については私の趣味です。特に冒頭2つのファイルには「_(アンダースコア)」が付与されています。 別の名前でも良いのですが…。 Windowsパソコンでファイル一覧を表示した場合、たいていの場合ファイル名順で表示されると思いますが、「_(アンダースコア)が付いていると優先的に上方に表示されます。特別なファイルは近くにあるとビジュアル的に綺麗です。 |
▲ |
特別な事はしていません。各プログラムモジュールは _AtrDef.h をインクルードするだけでマイコンに依存するデータ型やレジスタ定義を自動的に引用します。要は
_AtrDef.h がマイコンの種類を隠蔽しているところがポイントです。 3つの標準シンボルファイルと各プログラムモジュールの関係を図示すると以下の様なイメージになります。 |
各プログラムモジュールがレジスタ定義を示すファイルを直接インクルードする方法も間違いではありません。この場合はマイコンが変更される度に各プログラムモジュールを変更する必要があります。現実的な話としては利用するレジスタが変更されるので、そのついでに修正ということになりますが、総てのプログラムモジュールを修正するのも手間ですし、修正ミスの可能性も考慮する必要があります。 マイコン型番を示す英数字からなるレジスタ定義を示すファイルは注意が必要です。数字が1つ違うだけで全く別のマイコンを指定することになります。まぁ、コピペ作業をすれば間違いは少ないのですけどね(汗 前述の version.h の箇所でも述べていますが「触らなくても良いファイルを触らずに済む」というのが私のポリシーでして、各プログラムモジュールでは中間的なファイルを指定。マイコンに依存する部分を1カ所にまとめるという方法が無難であると考えます。 参考までに、開発環境として HI-TECH C コンパイラを利用している場合は、マイコンの型番からなるレジスタ定義ファイルをインクルードせず、単純に pic.h もしくは htc.h というファイルをインクルードすればレジスタ定義を利用することができますが、これもまたマイコンの型番を隠蔽するための工夫です。 プログラムを作る人の思考って最終的には同じ方向に進むのでしょうか? |
▲ |
サンプルに含まれる宣言の内、いくつかをピックアップして解説します。 |
(1) typedef データ型の宣言に利用していますが、define と異なり、シンボルは後出しになります。また、セミコロンが必要です。 typedef と define は紙一重であり、どちらも似た様な動作をします。例えば以下の様なケース。 #define UB unsigned char typedef unsigned char UB; どちらも同じように変数のデータ型として利用することができます。では、何が違うのか? #define POI unsigned char * typedef unsigned char * POI; これは異なる解釈をする場合があります。 POI PoiA, PoiB; 2つのポインタを宣言したことになりますが、コレを展開すると defineの場合 … unsigned char *PoiA, PoiB; typedefの場合 … unsigned char *PoiA, *PoiB; define の場合は PoiB が単なる符号無し1バイト整数として扱われます。 色々なケースを考えると、データ型を宣言する場合は typedef を利用した方が正論です。 |
(2) volatile 他力本願で恐縮ですが検索すると沢山出てきます 検索 (新しいウィンドウが開きます)。 細かく説明すると volataile の用法だけで十数ページを必要とします。それなので、ここでは簡単に説明します。 volatile 修飾子は変数を宣言する際に利用します。例えば、 volatile unsigned char Value; という感じです。 主な用法としては最適化の抑止、というところでしょうか。 例えば、 Value = 1; Value = 2; という感じで命令が並んでいた場合、コンパイラはどの様に解釈するでしょうか?同じ変数に値を代入するだけなら後勝ちです。最適化が正しく機能するのであれば先行する命令は無視されるでしょう。 ところが、組み込み系のプログラムで多用するポート操作では不都合が生じます。例えばクロック信号の出力。 PortA = 0b00000000; // CLK L … ポートAのLSBにクロック信号が割り付けられていると想定 PortA = 0b00000001; // CLK H PortA = 0b00000000; // CLK L PortA = 0b00000001; // CLK H 最適化が働いてしまうと後勝ちルールでクロック信号を生成できません。 こんな時に volataile を利用します。PortA に volatile 識別子が付与されていれば最適化を行わず、指定された値を1つずつ出力することでしょう。 さて、ポートアクセスに関する例を説明しましたが、単純変数に対しても volatile 修飾子は有用です。それは変数を割り込み処理と共用する場合です。 メイン処理と割り込み処理の双方で変数をアクセスし、かつ、コンパイラの最適化によりレジスタに保持された内容を再参照できる構成と仮定した場合に誤動作を誘発します。その一例を以下に示します。 メイン処理: ValueA = ValueA + 1; // 最適化が有効な場合、ValueA の値はレジスタに残る // チェックポイント if (ValueA > 1) { // 最適化が有効な場合、ValueA の値はレジスタを参照する ValueB = 2; } 割り込み処理: ValueA = 0; 非常に単純な例です。なお、メイン処理側で割り込み禁止の操作は説明を簡単にするために省略しています。 チェックポイント後の作業になりますが、コンパイラが優秀であれば変数を参照しません。レジスタに値が残っている前提で処理を続行します。ところが、チェックポイントの箇所で割り込みが発生した場合どうなるでしょう? 割り込み処理から戻ったときに変数の値とレジスタの値に不一致が生じます。最適化が優秀すぎると困ります。 そんな事態を回避するために ValueA に volatile 識別子を付与してレジスタ参照を明示的に阻止します。 別の回避策として、メイン処理側を割り込み禁止区間に設定すれば回避することもできます、が、後日処理の追加が行われて割り込み禁止区間の外で変数が参照された場合はアウトです。 PICマイコンでは保有するレジスタの数が少ないので問題が発生することは少ないと思いますが、高性能のコンパイラを使用した場合、レジスタを沢山有する高機能のマイコンを利用する場合は要注意です。 |
(3) 割り込み 任意の関数をメイン処理と割り込み処理の双方で利用したい場合、かつ、その処理には割り込み禁止操作が必要。 大抵の場合、割り込み処理中は多重割り込みが禁止されていると思います。ですが、上述の様な場合、割り込み処理内で割り込み許可を行うと多重割り込みが許可されてしまいます。メイン処理用と割り込み処理用で処理を分けて作る方法もありますが、2つ作るのも面倒。だけど関数を何処で使われるか予想ができない。 そんな場合に _AtrMpu.h に定義された mPushIpt/mPopIpt を利用します。これはマイコン依存のマクロ命令です。 mPushIpt … 現在の割り込み情報をバックアップして割り込み禁止 mPopIpt … バックアップされた割り込み情報を復元 mPopIpt がポイントです。単純に割り込み許可をするのではなく、バックアップされた状態に基づき割り込み許可を行います。この2つのマクロ命令を利用した関数は割り込み処理でもメイン処理でも利用することができます。どちらかというと汎用ライブラリ向けですね。 そうそう、忘れてはならないのがバックアップ情報の保存位置です。必ずローカル変数として定義して下さい。グローバル変数として定義すると再帰呼び出し(同関数をメイン処理と割り込み処理から同時に呼び出すこと)できません。 記述例: _IPT Backup; mPushIpt(Backup); // 任意の処理 mPopIpt(Backup); |
▲ |
標準シンボルの定義名については十人十色、プログラムを作る人により様々と思います。また、プログラムを作り続ける度に変わる場合もあると思います。私自身二十数年プログラムを作り続けていますが、定義名は年代と共に変化しています。 定義名を使わずに原始的な表現、例えば unsigned char を使用するならば万国共通ですが、これはこれでキーボードの打ち込み数が増え、また1行の長さが長くなり開発効率が低下します。 ベストな定義は…。皆が使っている形に合わせること。コレが難しい。皆が公開しているとは限りません。 参考にできる情報って少ないですよね。現実問題として。 確実に断言できるのは「複数人のチームで作業を行う場合、定義名は統一すること」。これは非常に大切です。 チーム内で定義名が同じで実体が異なる。これは一番避けたい事態ですね。 コーディング基準を作成し、定義名だけでなく変数名や関数名の付与の仕方を統一することを勧めます。 ちょっと心残り。 標準シンボルの定義で必要となるインクルードを語りたいのですが volatile と同様、十数ページ必要です。 これは別の機会に説明したいと思います。指定の仕方によってはプログラムの再利用確率が高くなります。 |
▲ |