5

私は組み込みプロジェクトに取り組んでおり、マクロを使用して USART のレジスタへのアクセスを最適化するコードの一部にさらに構造を追加しようとしています。プリプロセッサの #define されたレジスタ アドレスを const 構造に整理したいと思います。構造体をマクロで複合リテラルとして定義し、それらをインライン化された関数に渡すと、gcc は十分に賢く、生成されたアセンブリでポインターをバイパスし、構造体メンバーの値をコードで直接ハードコーディングします。例えば:

C1:

struct uart {
   volatile uint8_t * ucsra, * ucsrb, *ucsrc, * udr;
   volitile uint16_t * ubrr;
};

#define M_UARTX(X)                  \
    ( (struct uart) {               \
        .ucsra = &UCSR##X##A,       \
        .ucsrb = &UCSR##X##B,       \
        .ucsrc = &UCSR##X##C,       \
        .ubrr  = &UBRR##X,          \
        .udr   = &UDR##X,           \
    } )


void inlined_func(const struct uart * p, other_args...) {
    ...
    (*p->ucsra) = 0;
    (*p->ucsrb) = 0;
    (*p->ucsrc) = 0;
}
...
int main(){
     ...
     inlined_func(&M_UART(0), other_parms...);
     ...
}

ここで、UCSR0A、UCSR0B、&c は、uart レジスタとして左辺値として定義されています。

#define UCSR0A (*(uint8_t*)0xFFFF)

gcc は構造リテラルを完全に排除することができ、inlined_func() に示されているようなすべての代入は、レジスタのアドレスをマシン レジスタに読み込む必要がなく、間接アドレス指定もなしに、レジスタ アドレスに直接書き込みます。

A1:

movb $0, UCSR0A
movb $0, UCSR0B
movb $0, UCSR0C

これは値を USART レジスタに直接書き込み、アドレスをマシン レジスタにロードする必要がないため、構造体リテラルをオブジェクト ファイルに生成する必要はまったくありません。構造体リテラルはコンパイル時の構造体になり、抽象化のために生成されたコードにコストはかかりません。

マクロの使用をやめたかったので、ヘッダーで定義された静的定数構造体を使用してみました。

C2:

#define M_UART0 M_UARTX(0)
#define M_UART1 M_UARTX(1)

static const struct uart * const uart[2] = { &M_UART0, &M_UART1 };
....
int main(){
     ...
     inlined_func(uart[0], other_parms...);
     ...
}

ただし、gcc はここで構造体を完全に削除することはできません。

A2:

movl __compound_literal.0, %eax
movb $0, (%eax)
movl __compound_literal.0+4, %eax
movb $0, (%eax)
movl __compound_literal.0+8, %eax
movb $0, (%eax)

これにより、レジスタ アドレスがマシン レジスタにロードされ、間接アドレッシングを使用してレジスタに書き込まれます。とにかく、gccにC2 CコードのA1アセンブリコードを生成するよう説得できることを知っている人はいますか? __restrict 修飾子のさまざまな使用法を試しましたが、役に立ちませんでした。

4

3 に答える 3

2

UART と USART に関する長年の経験の後、私は次の結論に達しました。

structUART レジスタとの 1:1 マッピングにはa を使用しないでください。

コンパイラは、知らないうちにメンバー間にパディングを追加できるstructため、1:1 対応が台無しになります。

UART レジスタへの書き込みは、直接または関数を介して行うのが最適です。

volatileレジスターへのポインターを定義するときは、必ず修飾子を使用してください。

アセンブリ言語によるパフォーマンスの向上はほとんどありません

アセンブリ言語は、メモリ マップではなくプロセッサ ポートを介して UART にアクセスする場合にのみ使用してください。C 言語はポートをサポートしていません。ポインタを介した UART レジスタへのアクセスは非常に効率的です (アセンブリ言語のリストを生成して検証します)。場合によっては、アセンブリでのコーディングと検証に時間がかかることがあります。

UART 機能を別のライブラリに分離する

これは良い候補です。さらに、コードがテストされたら、そのままにしておいてください。ライブラリは常に (再) コンパイルする必要はありません。

于 2010-02-02T22:42:45.327 に答える
1

「コンパイルドメイン間で」構造体を使用することは、私の本の大罪です。基本的に、構造体を使用して何か、何か、ファイルデータ、メモリなどをポイントします。その理由は、コンパイラに関係なく、失敗するため、信頼性が低くなるためです。これには多くのコンパイラ固有のフラグとプラグマがありますが、より良い解決策はそれを行わないことです。アドレスプラス8をポイントし、アドレスプラス8をポイントする場合は、ポインタまたは配列を使用します。この特定のケースでは、あまりにも多くのコンパイラがそれを実行できないことがあり、コンパイラが構造体のようにレジスタアクセスを混乱させないことを保証するためにアセンブラPUT32 / GET32 PUT16/GET16関数を記述します。 32ビットレジスタに8ビットしか書き込まれていない理由を理解するのに時間がかかります。関数へのジャンプのオーバーヘッドは、安心とコードの信頼性と移植性の価値があります。また、これによりコードの移植性が非常に高くなり、ネットワーク間でputおよびget関数のラッパーを配置し、hdlシミュレーターでハードウェアを実行し、シミュレーションにアクセスしてレジスターの読み取り/書き込みなどを行うことができます。シミュレーションから組み込み、OSデバイスドライバー、アプリケーション層機能に変更されません。

于 2010-02-02T22:48:50.650 に答える