HOME > 技術談話 > PIC® microcontroller > C言語の使い勝手
はじめに データ型 定数 バイトオーダー 文字 コメント 構造体/共用体 列挙型(enum) 標準関数
有利なデータ型 ポインタパラメータ
HI-TECH C®はU.S.Aにおける Microchip Technology Inc.の登録商標です。 HI-TECH C®はANSI準拠のコンパイラです。既にC言語を利用している人であれば特に問題なく作業を開始できると思います。本項では私自身が気になる言語仕様をピックアップしていますが、より詳細な言語仕様についてはインストール時に添付されるユーザーズマニュアル(英文)を参照願います。 別項でHI-TECH C®の特殊な指定と宣言について言及しますが、おそらく他のメーカー製コンパイラでも同じ仕様であると思われるPICマイコンならではの使い勝手については本項で記述します。 ローカル変数やグローバル変数のデータメモリへの割り付け規則等は細かく調査していないので、各自で実験してみることを勧めます。また、サンプルプログラムをZIP形式で公開しますが、読み解くためにアセンブラの命令の理解が必要です。ベストなコーディング手法については各自で流儀を見出す方が勉強になるかと。 |
▲ |
利用可能なデータ型は以下の通りです。これらのデータ型はtypedefにより別名を割り付けすることができます。 |
型 | ビット幅 | 算術時の解釈 | |
bit | 1 | unsigned integer | |
signed char | 8 | signed integer | |
unsigned char | 8 | unsigned integer | |
signed short | 16 | signed integer | |
unsigned short | 16 | unsigned integer | |
signed int | 16 | signed integer | |
unsigned int | 16 | unsigned integer | |
signed short long | 24 | signed integer | |
unsigned short long | 24 | unsigned integer | |
signed long | 32 | signed integer | |
unsigned long | 32 | unsigned integer | |
float | 24 | real | |
double | 24 or 32 | real |
灰色の箇所は好みによりますが、移植時の挙動に注意して下さい。 double型のビット幅はプロジェクト設定で指定します。なお、符号非明示のchar型に対するプロジェクト設定はありません。符号非明示の場合は符号なし(unsigned char)として扱われます。 ビット型はローカル色が強く可能であれば構造体による宣言に置き換えた方が移植時に有利です。 なお、LSB/MSBの並びを入れ替えるプロジェクト設定はありません。 struct { unsigned int B0 : 1; // LSB unsigned int B1 : 1; unsigned int Dummy : 5; unsigned int B7 : 1; // MSB } sBit ビット型変数を使用したサンプルプログラムを公開します → TestType.zip このサンプルでは同一プログラムをHI-TECH C®V9.65とV9.70でコンパイルしており、展開内容はプロジェクトフォルダ内のLSTファイルで確認することができます。 今更ながら興味深い展開方法です。Lite版は最適化が無いはずですが、微妙に最適化を施しています。 0と1の定数の取り扱いはLite版ならではの展開方法です。一番使われる代入演算なのでLite版とPro版の違いを意識的に表現しているのかもしれません。なお、実験用に少しだけ悪戯をしてみました。 コンパイラの裏に回ろうと四苦八苦している箇所については気にしないでください。実はコンパイラが関数だけでなく、関数内で変数を操作しているかまで追跡していました。Lite版とはいえ少し関心。 |
▲ |
バイナリに対応しています。 |
基数(Radix) | 指定方法 | 記述例 | |
2進数(Binary) | 0bnumber or 0Bnumber | 0b10110101 | |
8進数(Octal) | 0number | 0265 | |
10進数(Decimal) | number | 181 | |
16進数(Hexdesimal) | 0xnumber or 0Xnumber | 0xb5 |
2進数表現は見た目が綺麗なのですが、他のコンパイラで利用できない場合があります。 |
▲ |
複数バイトで構成されるデータの取り扱いはLH型になっています。 |
メモリアドレス | 0x12 | 0x1234 | 0x12345678 | |
+0 | 0x12 | 0x34 | 0x78 | |
+1 | 0x12 | 0x56 | ||
+2 | 0x34 | |||
+3 | 0x12 |
LH型はLittle endian、HL型はBig endianと呼ばれます。あまりしっくりこない表現です…慣れでしょうけど。 通信系のプログラムを作るときは双方の機器で統一しないと大変なことになります。利用するマイコンの都合に合わせることが多いと思いますが、双方の機器が逆の場合は…担当者の力関係になりそうです。がんばってください。 無難なところでは「HL型のBig endianが通信処理の世界では常識」で落ち着きそうです。 バイトオーダーのサンプルプログラムを公開します → TestHL.zip 余談ですが、endianをインディアン、エンディアン。双方の呼び方があります。発音次第ですが文章上はエンディアンが正しいと思われます。また人によっては逆に解釈している場合があるので、念を押す意味でLH型、HL型を補足的にメモしておいた良いです。 大昔の話ですが、ビット7という表現がありました。危ないのでMSBという表現に変えたことを思い出します。 |
▲ |
文字コードはリテラル「'」で挟むことで定義されます。文字列はダブルクォーテーション「"」で挟むことで定義されます。このあたりは他のコンパイラと同様です。 パソコン用のアプリケーションを作成する人はあまり気にしないと思いますが、組み込み系の制御プログラムでは ・初期化済み変数 … 起動時にプログラムメモリ上の定数をデータメモリにコピーしてから参照。書き換え可。 ・初期化済み定数 … 必要な時にプログラムメモリ上の定数を参照する。データメモリは不要。書き換え不可。 という区分けがあります。プログラム動作時はどちらも(ほぼ)同等に扱うことができますが、データメモリの消費量や処理速度が異なるので文字列を宣言する場合は注意が必要です。 初期化済み変数の例: char tString0[] = "1234567890"; 初期化済み定数の例: const char tString1[] = "ABCDEFG"; どちらの方法も一長一短ありますが、 ・初期化済み変数 … 速度を上げる場合 ・初期化済み定数 … データメモリを節約する場合 の様に使い分けが必要です。 文字列の初期化済みデータのサンプルプログラムを公開します → TestChar.zip データメモリの消費を抑えるために初期化済み定数を利用した場合、定数を読み出す操作で独自関数を呼び出す場合があります。別項で記述しましたがスタックの量に限りがあるので見た目の関数の階層状況によらず、独自関数のスタック分も計算されるので作成する関数の階層数には余裕を持たせてください。 |
▲ |
コメントは他のコンパイラと同様、以下の方法で指定することができます。 ・/* コメント文字列 */ ・// コメント文字列 後者のコメント指定方法では行末までをコメントとして扱います。が、ここに注意点があります。 コメントの最後(行末)の文字が「¥(0x5c)」を含む漢字コードを使っている場合は次の行を巻き込んでコメントアウトします。このような確率は少ないと思いますが、見た目は動作する処理が実行できない、あるいはコンパイルエラーになり悩むことになります。このあたりは海外製のコンパイラには良くある事です。 以下の文字が該当します。これらの漢字を利用する場合は後ろにスペースを入れる等の工夫が必要です。 能(0x945c)、表(0x955c)、構(0x8d5c)、噂(0x895c)、貼(0x935c)、予(0x975c)、欺(0x8b5c) … 他にもあります。 ちなみに、私は「XXXが可能」というコメントでハマりました |
▲ |
特に問題なく利用することができます。 唯一の注意点はメモリのバウンダリが1バイトなので、PICマイコン用で作成された定義が他のコンパイラで位置がズレる場合があります。配置上の注意になりますが奇数バイトの位置に複数バイトの型を指定しない方が無難です。 例えば以下の様な構造体を考えます。 struct { unsigned char Data1; unsigned short Data2; unsigned char Data3; } sTest; この構造体をメモリに割りつけた場合、マイコンXでは以下の様な割り付けを行う場合があります。 |
メモリアドレス | PICマイコン | マイコン X | |
+0 | Data1 | Data1 | |
+1 | Data2(L) | ||
+2 | Data2(H) | Data2(L) | |
+3 | Data3 | Data2(H) | |
+4 | Data3 |
開発環境によっては空き領域(配置関係)を設定できる場合もありますが、もし新規で構造体を作成するのであれば他のマイコンに考慮した配置を心掛けるとトラブルは少ないです。 struct { unsigned char Data1; unsigned char Data3; unsigned short Data2; } sTest; この構造体をメモリに割りつけた場合はマイコンXでも共通の配置になるはずです。 |
メモリアドレス | PICマイコン | マイコン X | |
+0 | Data1 | Data1 | |
+1 | Data3 | Data3 | |
+2 | Data2(L) | Data2(L) | |
+3 | Data2(H) | Data2(H) |
通信系では相手装置が存在するので、できることならば相手装置の開発環境を確認してから作業を進めるのも良い方法だと思います(時として4バイトバウンダリも存在するので要注意)。 |
▲ |
特に問題なく利用することができます。 列挙型を使うことは個人的には好みでは無かったのですが…使ってみると結構便利です。 |
▲ |
一通りの標準関数が用意されてます。文字列関数(string.h)や算術関数(math.h)等々。 取り扱いについては他のコンパイラと同様ですので説明は省略します。 あえて、1点だけ注意事項。 標準関数は移植の際に便利なのですが、標準関数の種類によってはプログラムメモリを多く必要とします。その代表格がprintf系の関数群です。使ってはいけない。ということはありません。プログラムメモリに空きがあるのであれば問題ありませんが、PICマイコン自体がプログラム容量が少ないので、できることならば使用を控えたいところです。 |
▲ |
コンパイラ依存しそうな事項ですが…。 PICマイコンには1バイトサイズの汎用レジスタWがあります。1バイト演算は容易なのですが2バイト以上の演算はデータメモリを利用して行われるので効率が悪くなります。故に、大きな値の総数を求めるならまだしも、256未満のカウンタや分岐パラメータは1バイト型で宣言する方がプログラム容量、データメモリ容量共に押さえることができます。 これは関数への入出力パラメータに対しても適用され、1バイトのパラメータ1個、1バイトの戻り値であれば積極的にWレジスタが利用されます。 複数パラメータや戻り値が2バイト以上の場合は関数毎に設けられたパラメータ領域(データメモリ上に確保される擬似的なローカル変数)が利用されます。時と場合によりますが、関数に直接パラメータを渡すよりもグローバル変数経由でパラメータを送受した方が有利な場合があります。 |
▲ |
これもコンパイラ依存しそうな項目ですが…。 ほぼ大多数のマイコンではプログラムメモリ領域とデータメモリ領域が連続する論理(物理)アドレス上に存在しているので、同じマシン語命令を利用して双方の領域にアクセスすることができます。このルールはC言語でも適用されユーザはメモリの場所を意識しなくても単純にポインタを利用することでアクセスできます。 一方、PICマイコンのメモリはプログラムメモリ領域とデータメモリ領域に大別され、各領域をアクセスする為の専用のマシン語命令が必要です。C言語では区別をなくそうとしていますが、期待するデータ型と指定する対象メモリの関係でワーニングを表示します。 そんな感じで、関数に対してポインタを渡した場合の挙動を調べてみました。もしかしたらPICマイコン用の他のコンパイラでも似たり寄ったりだと思います。 結果は…コンパイラが賢いです。関数の型が同じでも参照させるメモリによりパラメータのサイズが変化します。 なお、ワーニングが表示されたものの動作結果は正常でした。 |
引数対象 | 入力型 | データメモリ指定時 | プログラムメモリ指定時 | |
データメモリのみ | unsigned char * | 引数:1バイト 下位関数無し |
||
const unsigned char * | 引数:1バイト 下位関数無し |
|||
プログラムメモリのみ | unsigned char * | 引数:1バイト Warning359 下位関数あり |
||
const unsigned char * | 引数:1バイト 下位関数あり |
|||
兼用 | unsigned char * | 引数:2バイト 下位関数あり |
引数:2バイト Warning359 下位関数あり |
|
const unsigned char * | 引数:2バイト 下位関数あり |
引数:2バイト 下位関数あり |
ワーニングが表示される理由はそれなりと言うか、やっぱりなんでしょうね。 今までワーニングが表示されなかった記述方法がPICマイコンのコンパイラで表示される場合があるので、今一度初心に戻って記述の仕方を確認してみることを勧めます。 正常動作するのであればワーニング表示を無視する。これは駄目なパターンです。何かしら問題を含んでいるはずなので、極力ワーニング表示を無くす努力が必要です。なお、コンパイラへの指定によりでワーニング表示を抑止することもできます(危険?)。このあたりは別項にて解説します。 今回の実験で用いたサンプルプログラムを公開します → TestPointer.zip 上表で「下位関数あり」と表記された部位ではコンパイラが汎用関数を実行する命令を生成します。スタックの利用状況に影響を与えるので注意してください。 不思議なPICマイコンがあります。例えばPIC16F887。このPICマイコンは368バイトのデータメモリを内蔵しています。上表では256バイト以下のデータメモリを持つPICマイコンでの実験になりましたが、256バイトオーバーのPICマイコンではポインタの扱いはどうなるか?これは宿題にしておきます。変わった結果が得られそうです。 |
▲ |