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

すべてが順調に終了する


C言語ツールを使用して、MKのアドレス空間でレジスタの固定位置を決定する方法(パート1)、レジスタ内の個々のビットグループを決定する方法(パート2)を検討したので、これらのグループでどのようにできるかを検討します動作するように。 全体としてビットのグループを操作しても問題は発生せず、ビットフィールドの形式での説明に基づいており、すでに実証されていますが、フィールドの個々のビットを操作する必要がある場合があり、プログラムの効率または理解の理由から、グループを個別のフィールドに分割することは実用的ではありません。

この例のコマンドフィールドの高位ビットを個別に操作する必要があるとします。 最初に頭に浮かぶのはユニオンですが、ユニオンには少し長さはありません。 レジスタ記述の2つのバージョンを作成し、それらを既に結合するオプションがあり、それは機能します:

#pragma bitfields=reversed typedef struct { unsigned :1; unsigned int code:3; unsigned :26; const unsigned flag1:1; unsigned flag:1; } tIO_STATUS; typedef struct { unsigned :1; unsigned int start:1; unsigned :30; } tIO_STATUSA; #pragma bitfields=default typedef union { tIO_STATUS; tIO_STATUSA; } tIO_STATUS2; #define IO_ADR 0x20000004 volatile tIO_STATUS2 * const pio_device = (tIO_STATUS2 *) (IO_ADR); pio_device->code = 3; while (pio_device->flag) {}; pio_device->start=1;
      
      



、しかし、2つの追加タイプを作成することは幾分冗長です(私の意見では)。

グループの個々のビットを操作するための代替手段はすべて同じビットマスクであり、次のタイプの共構造になります。
 #define BITNUM 2 //      0 #define BITMASK (1<<BITNUM) pio_device->code |= (1<< BITNUM); //   pio_device->code &= ~BITMASK;; //  
      
      



コンパイラーは、(定数を割り当てるのではなく)そのような操作の値の検証を実行しないことに注意してください。 また、ビット番号が適用され、そこからビットマスクがシフトによって作成されることに注意してください。 これは、ビットマスク(0x40000000)を設定するよりもダイヤルを間違えるのが難しく、それを頭に入れなければならないためです。しかし、コードに違いはありません(もちろん、これは好みの問題です)。 そして今、本当に深刻な発言-組み込みプログラミングに関する記事の著者(私自身を含む)は、テキストでそのような構成を使用することを強くお勧めしませんが、ビットフィールドを設定およびリセットするマクロを定義します
 #define SETBIT(DEST,MASK) (DEST) |= (MASK) #define CLRBIT(DEST,MASK) (DEST) &= ~(MASK)
      
      



それらのみを使用し続けます。
  SETBIT(pio_device->code,1 << BITNUM); CLRBIT(pio_device->code,BITMASK);
      
      



第一に、2番目のケースでビットごとの否定(〜)を置くことを忘れたり、論理否定を置く代わりに攻撃的な間違いを犯すことはありません(!)(そのような間違いを犯したことがない人は、非常に注意深い人々です。私は属していません)。 第二に、ビット単位のアドレス指定でMKに切り替えることにより、ハードウェア機能を考慮してこのマクロを(1ビットのみに対して)再定義し、非常に高速なコードを取得できます。 第三に、これらの操作をアトミックに変換する必要がある場合(場合)、プログラム全体で追跡するよりもマクロを定義する方がはるかに簡単です。



最後の側面については、さらに詳しく話すのが理にかなっています。 ご存知のように、リソースへのアクセスを求めて競合するプロセスが複数ある場合、アトミック操作の必要性が生じます。 したがって、外部デバイスのレジスタにアクセスするには、単一プロセス(メインのWhileループ)の場合でも、割り込みサービスルーチンとの暗黙的な競合があります。したがって、VUレジスタにアクセスする場合、読み取り-変更-書き込みシーケンスはアクションの連続性を確保するという観点から脅威となります。 実際、コマンドセットのマスクのマスクセット/リセットビットマップ操作は、アドレス空間セルで直接操作できないため、スレーブのレジスタのアトミックな変更を保証できません。 MK開発者がそのようなアプローチの欠点を理解していないとは言えませんが、問題を直接解決する方法はまだありません。これは、これを妨げる内部的な側面の存在を明確に示しています。 この問題に対するさまざまなアプローチが知られています。 アドレス空間に2つのレジスタが存在し、一方にユニットを書き込むと値ビットが設定され、もう一方にユニットを書き込むと値ビットがリセットされます。 各ビットがアドレス空間内の個別の値に対応する場合のビットバンディングメカニズムの存在(当然、レジスタ全体にアクセスする通常のメカニズムに加えて)。 さて、pervaganiyaを無効にするための広範なメカニズムは、操作の開始前に、その最後に許可を得て許可されます。 たぶん私は知らないかもしれませんが、MKにはまだ良いハードウェアアトミック操作がありません。



それでは、定数について話しましょう。 原則として、VUレジスタとの通信には、有効なフィールド値の特定のセットがあり、適切なプログラミングスタイルは、これらの機能の説明を意味のある名前を持つ定数のセットと見なし、割り当て時に値の有効性をチェックすることです(これまではマジックナンバー3を使用していましたが、これは排他的にトレーニング目的)。 C言語はこの問題を解決するためにどのような機会を提供しますか? それらの2つ-#defineを介して定数を定義し、列挙型を作成します。 これらの各選択肢を分析します。 デバイスが2つのコマンドのみを受け入れることができると仮定します-コード3で「作業を開始」し、コード2で「作業を停止」します。
 #define IO_DEVICE_START 3 #define IO_DEVICE_STOP 2 pio_device->code=IO_DEVICE_START;
      
      



、最も頻繁に行われます。 そのため、ビットフィールドのサイズに準拠しているかどうかのチェックもありますが、式
  pio_device->code=1;
      
      



コンパイラは有効なものとしてスキップします。 つまり、許容性の値を制御するタスクは開発者の肩にかかっており、ASSERTによって実装されています。 このメソッドは非常に機能します。頻繁に使用しますが、より便利でない場合、つまり列挙型を使用する場合は許容できます。
 #pragma bitfields=reversed typedef struct { unsigned :1; enum { O_DEVICE_START=3, IO_DEVICE_STOP=2, } code:3; unsigned :26; const unsigned flag1:1; unsigned flag:1; } tIO_STATUS; #pragma bitfields=default pio_device->code=IO_DEVICE_START; SETBIT(pio_device->code,BITMASK); pio_device->code |= BITMASK; pio_device->code=pio_device | BITMASK;
      
      



最後の行で型の非互換性に関する警告が表示され、同じことを行う前の2つの行では表示されないことに注意してください(これはバグではなく、この機能です)。 なぜこの方法がより便利なのですか? まず、可能な値の列挙を構造記述の本文に直接配置できます。これは読みやすいです。 第二に、コンパイラは定義の値をチェックし、フィールドの範囲を超えることを許可しません。 第三に、そして最も重要なことは、コンパイラーはフィールドに無効な(リストされていない)値を割り当てることを許可しないことです。 要するに、すべてが素晴らしく、素晴らしいですが、C標準ではビットフィールドにint以外の使用は許可されていないため、そのような構成をコンパイラで使用することはできません。 さらに、IARでも、適切なアライメントを確保するために、追加のコンパイラディレクティブ--enum_is_intが必要です。 しかし、コンパイラーへの依存を恐れないのであれば、このメソッドは非常に美しく、透過的で便利です(これは移植性を大幅に低下させるというコメントを書いている人と事前に同意します)。



さて、結論として、それらの関数とラッパーについてのいくつかの考えです。 多くの場合、ライブラリを表示すると、次のようなものが見つかります。
 dev_data_r_w (int n, int data_command, int r_w, int *adr) { ... }; int dev_data(int n, int data_comand, iint *adr) { return dev_data_r_w (n, 1, 1, int *adr); int read_dev(int n, int *adr) { return dev_data(n,1,adr); }; int ch_read_dev( int *adr) { return read_dev(1,adr); };
      
      



、最初の関数が実際の作業を行い、他のすべての関数がそのラッパーを作成して、対応する定数パラメーターを書き込まないようにするのは簡単です。 C ++(および他の多くの)で、同様の問題はデフォルトのパラメーター値によって削除されますが、Cの場合は依然として関連しています。 私の個人的な意見は、これを行うべきではないということです。 動的な型変換が不要な場合は、マクロを使用して、一般的な関数の便利な(使いやすい)シノニムを作成します。
 #define dev_data(N,DC,ADR) dev_data_r_w ((N),(DC),1,(ADR)) #define read_dev(N,ADR) dev_data((N),1,(ADR)) #define ch_read_dev(ADR) read_dev(1,(ADR))
      
      



このような定義はそれほど複雑ではなく、コードのサイズによって少し失われますが、実行時間と使用済みメモリのサイズによって勝ちます。 このようなマルチリンク構成は、割り込みルーチンで特に影響を受けます。 そしてもう一つの観察-何らかの理由で、一部のプログラマー(読者の中にそのようなものがある場合は、理由を書いてください)は、独自の列挙型を作成すると信じています
 enum { SET=1, RESET=0 } ACTIVE;
      
      



-それはクールです。 この型を使用して値をビットに書き込むときはいつでも理解できますが、その値をいつ制御するのですか? bool型はこの型に完全に取って代わるものと思われますが、他の意見を聞く準備はできています。



私は記事の第3部を要約します-目標は、VUレジスタへのアクセス方法の説明のいくつかの一般的な規則に同意し、いくつかの語彙を開発することでした。 、UART)(詳細な調査では非常に単純なデバイスはそれほど多くありませんが)非常に複雑なもの(USB、イーサネット)まで。 原則として、タスクは完了しました。プログラムの設計についてはまだ多くのコメントがありますが、それらはすでに進行中です。



All Articles