H8マイコンプログラミング ルネサス統合環境標準IO定義ファイルの使い方 [RX&SH&H8]
今まで解説をサボっていましたが、HEWのウイザードの途中で(勿論選択すれば)自動的に生成されるCPU内蔵周辺IOの定義ファイルに付いて解説します。
プロジェクトウイザードで生成されたCソースファイルが有る同じディレクトリに「iodefine.h」と言うヘッダーファイルが作成されています。このファイルをHEWのエディターウインドウに開いてみて下さい。
例えばIOポート5のレジスタであるPDR5レジスタは以下の様に定義されています。
H8マイコンの周辺IOはMemory mapped IOであり、プログラムメモリやワークメモリ等と同じメモリ空間に存在しています。
一番下の行の記述が、0xFFD0を構造体st_ioのアドレスとして定義しています。
構造体st_ioの定義自体がその上にある記述で、特にPDR5レジスタは符号無し8bitデータとビットフィールドで共用体を構成しています。つまりこのレジスタへのアクセス方法としてbyte単位とビット単位の2つの方法を提示しており、まあ御好きな方をお使いくださいと言ったところでしょうか。
H8/3694Fのハードウエアマニュアルを見るとPDR5レジスタは以下の様になっています。
まず肝心な事はPCR5レジスタとPDR5レジスタは対になっていると言う事です。
PDR5の0~7までのビットはそれぞれのポートに対応しており、PCR5の対応するビットが入力に設定されていた場合、PDR5レジスタを読む事で端子の状態を知る事ができます。
また、PCR5の対応するビットが出力に設定されている場合はPDR5レジスタに書き込む事で端子の状態を変化させる事ができ、読み出しを行うと、現在PDR5に書き込まれている値を読み出す事ができます。
ルネサスのH8用Cコンパイラのビットフィールドのbitオーダーは標準ではMSBから順に割り付けが行われます。他の周辺IOのレジスタもそうですが、ハードウエアマニュアルのビット名の記述となるべく一致させ、ビット毎の意味をイメージし易くしています。
※LSBから割り付けを行う事も可能です。詳しくはコンパイラーマニュアルのコンパイルオプションの項目を見てください。
ところでビットフィールドを利用するには条件があります。該当するメモリやレジスタが、必ず読み出した時に書き込んだ内容を反映する事です。
例えば「iodefine.h」の中のPCR5レジスタの記述はどうなっているか、下記に書き出してみました。
これは構造体st_ioのunsigned char型のメンバーとして登録されているだけで、PDR5の様にビットフィールドとしては記述されていません。
もう一度ハードウエアマニュアルのPCR5レジスタの表を見ると、R/Wの項目はWだけになっています。つまりこのレジスタに対しては書き込み(W)のみ保証され、読み込み(R)してもその値は書いた値を反映していないと言う事です。
組み込みマイコンで頻繁に利用されるビット操作は、Read modify Write動作、つまり読み出して、変更して、また書き込む動作なので、読み出した値が書き込んだ値を反映していない場合、書き戻しの動作時に間違った値を書き込んでしまいます。
この事からHEWが生成するiodefine.hの中では、上記の様に間違ったビット操作を行わない様に配慮されています。
さて、C言語の記述の上ではPDR5レジスタへのアクセス方法はbyte単位、ビット単位で可能です。この2つの違いは実際にはどうなのでしょうか?。
ここからは実際にコードを書いてみて確かめてみます。
ちょこっといじって確かめるにはこの辺りのプロジェクトが良さそうですね。
http://hamayan.blog.so-net.ne.jp/2009-06-07-1
記述したコードを左の図に示します。
この基板上にはP80にLEDが接続され、P80にLOWを出力するとLEDが点灯します。
更にP50にスイッチを接続し、スイッチの状態をLEDに反映する様にコードを書きました。
図の中ではビットフィールドを使用して記述しています。
もしこれをビットフィールドを使わずに記述するなら、以下の様になるでしょうか。
共にデバックモニタ上にロードして実行させ、スイッチを押したり離したりすると、それに合わせてLEDが点滅します。機能的には同じですが、コードは違うでしょう!。と言うわけでどの様なコードが書かれているか調べてみます。
この様な場合、デバックモニタ上にロードしたプログラムの逆アセンブル表示を見て比較する方法がありますが、もっと簡単にコンパイル時のオプションとしてリスト出力を設定します。
ツールチェインのコンパイラータブのカテゴリ=リストで、「コンパイルリスト出力」にチェックを入れて、「オブジェクトプログラムのリスト出力」もチェックします。
これで再ビルドを行うと、現在実行中のセッションのディレクトリの中になんとか.lstと言うファイルが作成されていますので、それを開きます。
下がビットフィールドを使った時のリスト出力です。※オリジナルはCソースと混在で比較し難いので、アセンブル出力のみ抽出。
以下はbyte単位
byte単位の場合、特定のビットのみ代入する記述ができないので、条件式により動作を変える方法を取っていますが、それがこの様な結果になっていますね。
命題として「P50の状態をP80に反映する」となっているので、ビットフィールドを使った場合の方がより直接的に理解し易く記述できている点も見逃せません。
しばしばビットフィールドは移植性が無いと敬遠され勝ちですが、組み込みマイコンのポートを操作している時点で移植性なんてまるっきり期待できませんし、コンパイラによってビットオーダーが変わるとバグになり易いとか言う話も、その辺はコンパイラメーカーも良く判っていて、先に述べた様にコンパイルオプションで対応可能なのです。基本的に新しいコンパイラを使用する時は、まずはマニュアルに目を通しましょう。
そんな訳でまあ今更ビットフィールドを否定する理由は特に無いんじゃないかと思います。
但し、HEWで生成するiodefine.hや、ルネサスの半導体セミナーのページで配付しているCPU別ヘッダーファイルは、当然その動作保証はルネサスのコンパイラに限られるので、それ以外のコンパイラで使う方は良く自分で使うコンパイラのマニュアルを調べてから使った方が良いです。
※実はこのビット同士の代入の例題はずるです。H8マイコンではこのビットフィールドの直接代入を行う記述をした方が効率が良い事は知っていましたからね。
プロジェクトウイザードで生成されたCソースファイルが有る同じディレクトリに「iodefine.h」と言うヘッダーファイルが作成されています。このファイルをHEWのエディターウインドウに開いてみて下さい。
例えばIOポート5のレジスタであるPDR5レジスタは以下の様に定義されています。
struct st_io { /* struct IO */ 途中を省略している。 union { /* PDR5 */ unsigned char BYTE; /* Byte Access */ struct { /* Bit Access */ unsigned char B7:1; /* Bit 7 */ unsigned char B6:1; /* Bit 6 */ unsigned char B5:1; /* Bit 5 */ unsigned char B4:1; /* Bit 4 */ unsigned char B3:1; /* Bit 3 */ unsigned char B2:1; /* Bit 2 */ unsigned char B1:1; /* Bit 1 */ unsigned char B0:1; /* Bit 0 */ } BIT; /* */ } PDR5; /* */ 途中を省略している。 #define IO (*(volatile struct st_io *)0xFFD0) /* IO Address*/
H8マイコンの周辺IOはMemory mapped IOであり、プログラムメモリやワークメモリ等と同じメモリ空間に存在しています。
一番下の行の記述が、0xFFD0を構造体st_ioのアドレスとして定義しています。
構造体st_ioの定義自体がその上にある記述で、特にPDR5レジスタは符号無し8bitデータとビットフィールドで共用体を構成しています。つまりこのレジスタへのアクセス方法としてbyte単位とビット単位の2つの方法を提示しており、まあ御好きな方をお使いくださいと言ったところでしょうか。
H8/3694Fのハードウエアマニュアルを見るとPDR5レジスタは以下の様になっています。
まず肝心な事はPCR5レジスタとPDR5レジスタは対になっていると言う事です。
PDR5の0~7までのビットはそれぞれのポートに対応しており、PCR5の対応するビットが入力に設定されていた場合、PDR5レジスタを読む事で端子の状態を知る事ができます。
また、PCR5の対応するビットが出力に設定されている場合はPDR5レジスタに書き込む事で端子の状態を変化させる事ができ、読み出しを行うと、現在PDR5に書き込まれている値を読み出す事ができます。
ルネサスのH8用Cコンパイラのビットフィールドのbitオーダーは標準ではMSBから順に割り付けが行われます。他の周辺IOのレジスタもそうですが、ハードウエアマニュアルのビット名の記述となるべく一致させ、ビット毎の意味をイメージし易くしています。
※LSBから割り付けを行う事も可能です。詳しくはコンパイラーマニュアルのコンパイルオプションの項目を見てください。
ところでビットフィールドを利用するには条件があります。該当するメモリやレジスタが、必ず読み出した時に書き込んだ内容を反映する事です。
例えば「iodefine.h」の中のPCR5レジスタの記述はどうなっているか、下記に書き出してみました。
unsigned char PCR5; /* PCR5 */
これは構造体st_ioのunsigned char型のメンバーとして登録されているだけで、PDR5の様にビットフィールドとしては記述されていません。
もう一度ハードウエアマニュアルのPCR5レジスタの表を見ると、R/Wの項目はWだけになっています。つまりこのレジスタに対しては書き込み(W)のみ保証され、読み込み(R)してもその値は書いた値を反映していないと言う事です。
組み込みマイコンで頻繁に利用されるビット操作は、Read modify Write動作、つまり読み出して、変更して、また書き込む動作なので、読み出した値が書き込んだ値を反映していない場合、書き戻しの動作時に間違った値を書き込んでしまいます。
この事からHEWが生成するiodefine.hの中では、上記の様に間違ったビット操作を行わない様に配慮されています。
さて、C言語の記述の上ではPDR5レジスタへのアクセス方法はbyte単位、ビット単位で可能です。この2つの違いは実際にはどうなのでしょうか?。
ここからは実際にコードを書いてみて確かめてみます。
ちょこっといじって確かめるにはこの辺りのプロジェクトが良さそうですね。
http://hamayan.blog.so-net.ne.jp/2009-06-07-1
記述したコードを左の図に示します。
この基板上にはP80にLEDが接続され、P80にLOWを出力するとLEDが点灯します。
更にP50にスイッチを接続し、スイッチの状態をLEDに反映する様にコードを書きました。
図の中ではビットフィールドを使用して記述しています。
もしこれをビットフィールドを使わずに記述するなら、以下の様になるでしょうか。
/*LEDが接続されているポートを出力に切り替える*/ IO.PCR8 = 0x01; /*0000 0001 ポート8入出力*/ /*スイッチが接続されているポートを入力に切り替える*/ IO.PCR5 = 0x00; /*0000 0000 ポート5入出力 但しリセット直後は入力となっている*/ /*スイッチが接続されているポートのプルアップを有効にする*/ IO.PUCR5.BYTE |= 0x01; /**/ while( 1 ) { if(IO.PDR5.BYTE & 0x01) IO.PDR8.BYTE |= 0x01; else IO.PDR8.BYTE &= ~0x01; }
共にデバックモニタ上にロードして実行させ、スイッチを押したり離したりすると、それに合わせてLEDが点滅します。機能的には同じですが、コードは違うでしょう!。と言うわけでどの様なコードが書かれているか調べてみます。
この様な場合、デバックモニタ上にロードしたプログラムの逆アセンブル表示を見て比較する方法がありますが、もっと簡単にコンパイル時のオプションとしてリスト出力を設定します。
ツールチェインのコンパイラータブのカテゴリ=リストで、「コンパイルリスト出力」にチェックを入れて、「オブジェクトプログラムのリスト出力」もチェックします。
これで再ビルドを行うと、現在実行中のセッションのディレクトリの中になんとか.lstと言うファイルが作成されていますので、それを開きます。
下がビットフィールドを使った時のリスト出力です。※オリジナルはCソースと混在で比較し難いので、アセンブル出力のみ抽出。
000C F801 MOV.B #1,R0L 000E 38EB MOV.B R0L,@65515:8 0010 1800 SUB.B R0H,R0H 0012 30E8 MOV.B R0H,@65512:8 0014 7FD17000 BSET.B #0,@65489:8 0018 L6: 0018 7ED87700 BLD.B #0,@65496:8 001C 7FDB6700 BST.B #0,@65499:8 0020 4000 BRA L6:8
以下はbyte単位
000C F801 MOV.B #1,R0L 000E 38EB MOV.B R0L,@65515:8 0010 1800 SUB.B R0H,R0H 0012 30E8 MOV.B R0H,@65512:8 0014 7FD17000 BSET.B #0,@65489:8 0016 L6: 0016 7ED87300 BTST.B #0,@65496:8 001A 4700 BEQ L8:8 001C 7FDB7000 BSET.B #0,@65499:8 0020 4000 BRA L6:8 0022 L8: 0022 7FDB7200 BCLR.B #0,@65499:8 0026 4000 BRA L6:8
byte単位の場合、特定のビットのみ代入する記述ができないので、条件式により動作を変える方法を取っていますが、それがこの様な結果になっていますね。
命題として「P50の状態をP80に反映する」となっているので、ビットフィールドを使った場合の方がより直接的に理解し易く記述できている点も見逃せません。
しばしばビットフィールドは移植性が無いと敬遠され勝ちですが、組み込みマイコンのポートを操作している時点で移植性なんてまるっきり期待できませんし、コンパイラによってビットオーダーが変わるとバグになり易いとか言う話も、その辺はコンパイラメーカーも良く判っていて、先に述べた様にコンパイルオプションで対応可能なのです。基本的に新しいコンパイラを使用する時は、まずはマニュアルに目を通しましょう。
そんな訳でまあ今更ビットフィールドを否定する理由は特に無いんじゃないかと思います。
但し、HEWで生成するiodefine.hや、ルネサスの半導体セミナーのページで配付しているCPU別ヘッダーファイルは、当然その動作保証はルネサスのコンパイラに限られるので、それ以外のコンパイラで使う方は良く自分で使うコンパイラのマニュアルを調べてから使った方が良いです。
※実はこのビット同士の代入の例題はずるです。H8マイコンではこのビットフィールドの直接代入を行う記述をした方が効率が良い事は知っていましたからね。
2009-06-24 00:34
nice!(0)
コメント(2)
トラックバック(0)
> HEWで生成するiodefine.hや、ルネサスの半導体セミナーのページで
> 配付しているCPU別ヘッダーファイルは、当然その動作保証はルネサスの
> コンパイラに限られる
動作保証されるかどうか、私は信用していないのでアセンブラ出力の確認は当然のように行っています。特に新製品の場合には、問題を起こす場合が多いように思います。本気でバグ出ししているんだろうか?
by noritan (2009-06-24 09:42)
(笑)
既にnoritanさんはデバック要員に数えられている様な気がする。
by hamayan (2009-06-24 09:51)