Cの外部デバイスレジスタの操作、パート2

真夜中が来て、シェレザダは許可されたスピーチを続けました


まず、前の記事へのコメント-私は自分自身がASSERTについて無礼に話すことを許可しました-私は自分の立場を説明します。 あまり好きではありません ASSERTでの検証は実行段階でのみ実行されるため、コンパイル段階での検証がより効果的です。 コンパイル段階で必要なチェックを行うことができるデザインの存在についてPMに通知されました(チェックしました-本当に機能します)。このトピックに関する投稿はすぐにサンドボックスに表示されると思います。

だから、私たちは続けます...

前述のように、外部デバイスのレジスターは、アドレス空間内の位置(ここでは既に決定済み)と制御要素の構成によって特徴付けられます。 実際には、多くの制御要素は特定のMKのワード全体を占有するほど大きくないため、アドレス空間を節約するために、複数の制御要素を1つのレジスタにパックできます。 このソリューションでは、特定の条件下で、この外部デバイスと対話するプログラムの速度をわずかに上げることができるため、非常に一般的です。 同時に、このソリューションはプログラミング言語に関して特定の問題を引き起こします。これについては後で説明します。



たとえば、制御レジスタを備え、レジスタのさまざまなビットがさまざまな機能を実行する外部デバイスを考えます。つまり、下位(ゼロ)ビットにはデバイスビジーフラグが含まれます(0-デバイスは解放され、次のコマンドを実行する準備ができています、1-デバイスはコマンドの処理でビジーです) 、および30..28(32ビットワードがあると想定)の2年目から始まる3桁には、コマンドコードが含まれています(この構造が論理的に見えない場合、実際のレジスタの説明は表示されませんでした)。 C言語を使用して、このようなレジスタの個々のフィールドへのアピールを整理するにはどうすればよいですか?..さらに、1つのフィールドを変更しても、レジスタの残りのビットは変更しないでください。

最初に頭に浮かぶのは(そしてしばしばそこに残る唯一のもの)ビットマスクです。 上記の場合、次のようなものがあります。
#define IO_STATUS_CMD_MASK 0x70000000 #define IO_STATUS_CMD_BIT 28 #define IO_STATUS_FLAG_MASK 0x01 while (*pio_status & IIO_STATUS_FLAG) {}; *pio_status=(*pio_status & ~IO_STATUS_CMD_MASK) | ((command << IO_STATUS_CMD_BIT) & IO_STATUS_CMD_MASK);
      
      



おそらく、操作のシーケンスを覚えていれば、一部のブラケットはここでは不要ですが、実行ステージでブラケットは再び費用がかからないという意見が常にありました。対応する優先順位テーブルを覚えたり、ミスを犯すことを禁じるよりも、余分なペアを置く方が簡単です 何が見えますか? 最初の行では、3つの連続したビット(純粋に理論的には非連続ビットからのフィールドがあるかもしれませんが、これはすでに倒錯に非常に似ていますが、私たちはまだ普通の人です)が単一のフィールドを形成することをコンパイラーに通知します

次の行は、このフィールドの低次数を報告します。 3行目は、1ビットからビットフィールドを定義します。なぜなら、それが必要ないため、最下位(単一)ビットの数を決定しないからです。 4行目では、必要な(単一の)ビットをレジスタから選択し、その一致を確認します。 しかし、5行目はやや複雑に見えますが、複雑ではありません-左から右に解析します。レジスタの現在の値を取得し、すべてのビットを残します。変更する必要があるものを除き、新しいフィールド値を取得し、フィールドの最下位ビットの数だけ左にシフトします。結果には、変更するビットのみを残し、取得した2つの値を組み合わせて、必要なものを取得します。 毎回最後の行を書かないように、マクロを作成できます
 #define DATA_SET(ADR,MASK,BIT,DATA) (ADR)=((ADR) & ~(MASK)) | (((DATA) <<(BIT)) & (MASK)) DATA_SET(*pio_status,IO_STATUS_CMD_MASK,IO_STATUS_CMD_BIT,command);
      
      



投稿はそこで終わっていなかったため、このソリューションには欠点があります。それらを示します。 まず、最初の2行は明らかに相互接続されていますが、それらは互いに異なって存在しているので、私たち自身がそれらの対応を監視する必要があります。 ビットマスクから単一の最下位ビットを持つビットマスクを作成するマクロを書くのは簡単です
 #define LOWBIT(MASK) (((~(MASK)-1)) & (MASK)) #define LOWBIT(MASK) (~(MASK << 1) & (MASK)) #define LOWBIT(MASK) (~(MASK)+1) & (MASK)
      
      



、難しい(私は成功しなかった)、低ビット数を強調する美しいマクロ。 しかし、そのようなビットマスクは私たちにとって十分であり、値転送マクロのコードはわずかに変更されます
 #define DATA_SET(ADR,MASK,DATA) (ADR)=((ADR) & ~(MASK)) | (((DATA) * LOWBIT(MASK)) & (MASK)) DATA_SET(*pio_status,IO_STATUS_CMD_MASK,command);
      
      



注意深い読者が尋ねますが、効率はどうですか? 古いバージョンではシフトがありましたが、ARMではクロックごとに行われ、新しいバージョンでは乗算があります(読み取りと除算に必要な場合)? さて、定数値を設定するとマクロは折りたたまれますが、コマンドフィールドが変数の場合はどうでしょうか。 幸いなことに、コンパイラは本当に才能のある人々によって設計されており、少なくとも平均的な最適化レベルが有効になっている場合、1ビットの定数で乗算および除算が対応するシフト数になります(少なくとも私にとっては)。 この(または類似の)バージョンのコードは広く普及しており、デバイスレジスタ(BSP)を操作するためのプログラムのさまざまなライブラリで提供されています。 著者の欠点は、フィールドを参照するためにマクロを使用する必要があることです(テキストの最初の例の5行目を直接使用するオプションも考えたくありません-これは単なる怪物です)。マクロで使用されるマスク値を注意深く監視する必要がありますのような攻撃的な間違いを犯す
 DATA_SET(*pio_status,IO_STATUS_FLAG_MASK,command);
      
      



、コンパイラは何も検証できないため。 もちろん、別のマクロを書くことができます
 #define IO_CMD_SET(DATA) DATA_SET(*pio_status,IO_STATUS_CMD_MASK,DATA) IO_CMD_SET(command);
      
      



、しかし、そのようなマクロはすぐにエラーになりかねません。 別の欠点は、型チェックの欠如、つまり次のような操作です
 IO_CMD_SET(36);
      
      



コンパイラーは疑いを引き起こしませんが、実行の結果は不愉快に思われるかもしれません。

さて、共通メソッドのすべての欠点を見てきましたので、それらがないレジスタを記述する方法を提示する義務があります(同時に独自のレジスタを持っています)。 そのため、シーンにはビットフィールドが表示されます。ビットフィールドは、Cの本でかなり説明されていますが、1つの単語に「短い」オブジェクトを配置することでメモリを節約します。 同時に、これらの言語ツールを使用して既存のレジスタを記述することを妨げるものは何もありません。 この場合、フィールドを一言でまとめるプロセス、つまりパッケージングの方向と位置合わせの要件を非常に明確に制御する必要があります。 問題のコンパイラでこれらのプロセスを制御するには、ディレクティブがあります。
 #pragma bitfields = disjoint_types //  ,      #pragma bitfields = reversed //  ,     
      
      



次に、問題のレジスタの構造を次のように記述できます。
 #pragma bitfields=reverse typedef struct { unsigned :1; //    (31) unsigned code:3; // (30..28) unsigned : 27; //  27  (27..1) unsigned flag:1; // (0 -  ) } tIO_STATUS; #pragma bitfields=default volatile tIO_STATUS * const pio_status = (tIO_STATUS *) (IO_ADR);
      
      



注:例の最後から2番目の行は、定義の前にプラグマを含めることを作成者が気にしなかったフラグメントがある場合に、フィールドを配置するモードをデフォルト値に戻します(しかし、私たちはそうではないので、慎重です)。 この方法の欠点に注意してください-埋めるフィールドの長さを手動で計算する必要があり(これについては後で説明します)、利点のリストに進む必要があります:マスクとビットを決定しないでください(コンパイラーがすべてを行います)、さらに、彼はレコードをチェックします有効性に関するフィールド定数データへ。 登録フィールドは標準言語ツールを使用してアクセスされるようになりました
 while (pio_status->flag) {}; poi_status->code=3;
      
      



、および自動入力と型チェックの作業、つまり演算子
 pio_status->code=34;
      
      



コンパイラから警告を受け取ります。

今、効率について-私は他のコンパイラでどのように知っていますが、私の(つまり、IARでは)生成されたコードに違いはありません、つまり、コンパイラはすべての必要なシフトと論理演算でビットフィールドをビットマスクにアピールしますが、それ以上。 さらに、ビット単位のアドレス指定をサポートするMKの場合、シングルビットフィールドを操作するコードの方が効率的です。 要約すると、ビットフィールドを使用すると、オーバーヘッドなしで作業が大幅に快適になります。 このような方法の唯一の欠点は、複数のフィールドを同時に使用する1人のオペレーターで標準的な方法で作業できないことです。これは、マスクを使用する場合に可能です。 同時に、のような演算子
 *pio_status=(*pio_status & ~(IO_STATUS_CMD_MASK | IO_STATUS_FLAG_MASK)) | ((command << IO_STATUS_CMD_BIT) & IO_STATUS_CMD_MASK) | (IO_STATUS_FLAG_MASK * flagset);
      
      



メンテナンスが困難なため、使用を推奨することはできません。 それにもかかわらず、それらの必要がある場合、つまり、フィールドへのこのような同時アクセスが外部デバイスの機能によって必要とされる場合、タイプの構造
 #define WORD(adr) *(int*)(adr) WORD(pio_status)=WORD(pio_status) & ...
      
      



ロープを必要なサイズまで伸ばすことができますが、個人的には、そのような状況では、一時的な構造を作成し、修正してからレジスタに送信することを好みます。
 tIO_SATUS tmp; tmp=*pio_status; //      tmp.code=command; tmp.flag=flagset; *pio_status=tmp;
      
      



しかし、これは効率の理由で受け入れられない場合があります。 もう1つの発言-そのような演算子が必要な場合、構造内でいくつかのフィールドをconstとして記述すると、特定の困難が発生します
 #pragma bitfields=reverse typedef struct { unsigned :1; //    (31) unsigned code:3; // (30..28) unsigned : 27; //  26  (27..2) unsigned const readflag:1; //     (1) unsigned flag:1; // (0 -  ) } tIO_STATUS; #pragma bitfields=default volatile tIO_STATUS * const pio_status = (tIO_STATUS *) (IO_ADR); tIO_STATUS tmp={0,0,1}; //  ,       tmp=*pio_status; //     *(int*)(&tmp)=*(int*)pio_device; //   tmp.code=3; tmp.flag=1; *(int*)(pio_device)=*(int*)(&tmp);
      
      



それは少し不器用に見えます。

まあ、結論として、ビットの手動計算に関連する欠点-ポストが考案されたとき、次のようなレジスタの説明を受けた豪華なマクロがあったはずです
 REGISTR(tIO_STATUS,code[30..28],readflag[1],flag[0])
      
      



プログラマーの作業を大幅に簡素化し、その後の使用のために構造の適切な説明を作成しました(MASMでプログラミングしたユーザーは、同様のマクロに適合しました)。 そして、ここで不愉快な驚きが私を待っていました-C言語プリプロセッサはマクロ言語ではありません、なぜならそれは多くの必要な機能を奪われているからです。 これは実際には単なるプリプロセッサであり、プログラムテキストを処理する機能は非常に限られています。 最初はそのような発見を信じていなかったので、約1時間、自分を馬鹿だと考え、情報を見つけることができませんでした。 それから、彼は著者たちをインターネット上で1時間馬鹿だと考え、私が必要とするマクロを構築するために倒錯した方法で試みました。 原則として、おそらく2パスプリプロセッサの条件で実行できますが、ひどく見えます。 それで、プリプロセッサの開発者がマクロ言語を作成するというタスクを自分で設定していないことに気付きました(理由は明らかではありませんが、彼らはよく知っています)。彼らはすべてよくできており、私もそれほど悪くはありません。 それでも、問題は解決しません-M4やPowerShellなどの外部プリプロセッサを使用することは可能です。Perlなどのスクリプト言語を使用することはできます。Javaモジュールまたは実行可能ファイルの形で独自の最小プリプロセッサを使用することも可能ですが、これはすべて松葉杖であり、美しく、そして最も重要なことに、便利ではありません決定。 私が何かを誤解した場合は、コメントで教えてください。

一般的な結論は、C言語のビットフィールドは外部デバイスのレジスタを記述するための効果的で便利なツールであり、最小限の制御手段を講じるときに使用することを強くお勧めします。

何かが再び判明したので、フィールドに記入する問題(#define VS enum)と関連する質問のいくつかは、第3部で対処します。



All Articles