AVRコードがビットシフトを使用する理由[終了]


7

AVRプログラミングでは、レジスタビットは、a 1を適切なビット位置に左シフトすることによって常に設定され、それらの1の補数によってクリアされます。

例: ATtiny85の場合、PORTB、b 4を次のように設定します。

PORTB |= (1<<PB4);

または、次のようにクリアします。

PORTB &= ~(1<<PB4);

私の質問は次のとおりです。なぜそれがこのように行われるのですか?最も単純なコードは、ビットシフトの混乱になります。なぜビットがマスクの代わりにビット位置として定義されるのですか?

たとえば、ATtiny85のIOヘッダーにはこれが含まれています:

#define PORTB   _SFR_IO8(0x18)
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

私にとっては、代わりにビットをマスクとして定義する方がはるかに論理的です(このように):

#define PORTB   _SFR_IO8(0x18)
#define PB5     0x20
#define PB4     0x10
#define PB3     0x08
#define PB2     0x04
#define PB1     0x02
#define PB0     0x01

したがって、次のようなことができます。

// as bitmasks
PORTB |=  PB5 |  PB3 |  PB0;
PORTB &= ~PB5 & ~PB3 & ~PB0;

ビットb 5、b 3、およびb 0をそれぞれオンおよびオフにします。とは対照的に:

// as bit-fields
PORTB |=  (1<<PB5) |  (1<<PB3) |  (1<<PB0);
PORTB &= ~(1<<PB5) & ~(1<<PB3) & ~(1<<PB0);

ビットマスクコードは、ビット、、およびを設定してPB5、より明確に読み取ります。さらに、ビットをシフトする必要がなくなるため、操作を節約できるように見えます。PB3PB0

nビットAVRからmビット(例:8ビットから32ビット)へのコードの移植を可能にするために、一般性を保つためにこの方法で行われたのではないかと思いました。しかし#include <avr/io.h>、ターゲットマイクロコントローラーに固有の定義ファイルに解決されるため、これは当てはまらないようです。ターゲットを8ビットATtinyから8ビットAtmegaに変更する場合(ビット定義が構文的にからPBxに変更さPORTBxれる場合など)でも、コードを変更する必要があります。


3
私はこれを2番目にします。ユビキタス_BV(b)を利用することさえも、(1<<b)物事を不必要に乱雑にする。私は通常_BV()、たとえばでビットニーモニックを定義します#define ACK _BV(1)
カルシウム3000

2
コンパイラがこれらを同じ定数として解釈することがわかったら、ソースコードで使用する定数は実際には好みの問題です。あなた自身のコードであなたが最も賢いと思うものは何でもしてください。既存のプロジェクトを変更する場合は、その伝統に固執してください。
Chris Stratton

3
「ビットマスクアプローチは明らかにより読みやすいエンドユーザーコードを生成するため」-あなたの個人的な意見。1と0を適切な場所にシフトすることは、加算されるいくつかの数値がビットマスクであるかどうかを推測する必要があるよりもはるかに明確です。
トムカーペンター

3
@TomCarpenter興味深い。ええと、私はうっかりして意見に基づく質問をしました。いずれにせよ、いくつかの良いフィードバックがありました。より多くの(TI)DSPバックグラウンド(ビットマスクが標準である)から来たのは、奇妙な構文のようであり、具体的な理由があると私は思いました。
ブレアフォンヴィル2018

1
@BlairFonvilleたぶんご存知かもしれませんが、ARMは(ビットマスクを使用して)説明したとおりに機能します。
チー

回答:


7

最も単純なコードは、ビットシフトの混乱になります。なぜビットがマスクではなくビット位置として定義されるのですか?

いいえ、まったく違います。シフトはCソースコードにのみあり、コンパイルされたマシンコードにはありません。あなたが示したすべての例は、単純な定数式であるため、コンパイル時にコンパイラによって解決できます。

(1<<PB4) 「ビットPB4」と言うだけの方法です。

  • したがって、機能するだけでなく、コードサイズも増えません。
  • 人間のプログラマにとって、ビットマスク(たとえば32)ではなく、インデックス(たとえば5)でビットに名前を付けることも理にかなっています。この方法では、ビットの識別に連続番号0..7を使用して、 2(1、2、4、8、.. 128)。

  • そして、別の理由があります(おそらく主な理由):
    Cヘッダーファイルは、Cコードだけでなく、アセンブラソースコード(またはCソースコードにインライン化されたアセンブラコード)にも使用できます。AVRアセンブラコードでは、ビットマスク(ビットシフトによってインデックスから作成できる)を使用したいだけではありません。一部のAVRビット操作アセンブラー命令(SBI、CBI、BST、BLDなど)では、命令オペコードで即値演算子としてビットインデックスを使用する必要があります。
    SFRのビットをインデックスで識別する場合のみ(ビットマスクではなく)このような識別子をアセンブラー命令の即値オペランドとして直接使用できます。それ以外の場合、SFRビットごとに2つの定義が必要でした。1つはビットインデックスを定義し(1つは前述のビット操作アセンブラー命令のオペランドとして使用できます)、もう1つはビットマスクを定義します(バイト全体を処理する命令にのみ使用できます)操作されます)。


1
という事は承知しています。私はそれが機能するかどうか疑問に思っていません。それは知っている。なぜ定義がそのまま書かれているのかを尋ねています。私には、ビット位置ではなくマスクとして定義されている場合、コードの可読性が大幅に向上します。
ブレアフォンヴィル2018

5
この答えは要点を逃していると思います。彼はコードの効率やコンパイラについて決して話しません。それはすべてソースコードの混乱についてです
パイプ

4
@Blair Fonville:そのようなマクロを定義する簡単な方法はありません。2を底とする対数を計算する必要がありました。対数を計算するプリプロセッサ機能はありません。つまり、それはテーブルを使用してのみ実行でき、それは非常に悪い考えだと思います。
豆腐

2
@pipe:「コード汚染」または「ソースコードの乱雑さ」(または、それを呼び出したいもの)とは見なさないので、話をしません。それどころか、プログラマー/リーダーが彼が使用している定数は2の累乗である(そしてそれはシフト式を使用して行われる)ことを思い出させることはさらに有用だと思います。
豆腐

1
@RJR、ブレアフォンヴィル:もちろん、そのようなマクロを簡単に定義できますが、単純なプリプロセッサ定義を使用しても問題ありません。デバッガ)非常に透過的です。
カード

4

おそらく、ビットシフトだけがPB*定義の使用例ではありません。おそらく、PB*定義がシフト量としてではなく直接使用される別のユースケースがあります。その場合、DRYの原則により、PB*反復的な情報を持つ2つの異なる定義セットではなく、両方のユースケース(これらの定義など)に使用できる1つの定義セットを実装するようになると思います。

たとえば、最大8つのADCチャネルから測定を実行できるアプリケーションを作成しました。新しい測定を開始するためのインターフェイスが1つあり、8ビットフィールドを介して複数のチャネルを指定できます(チャネルごとに1ビット)。(複数のチャネルが指定されている場合、測定は並行して実行されます。)次に、個々のチャネルの測定結果を返す別のインターフェースがあります。したがって、1つのインターフェースはチャネル番号をビットフィールドへのシフトとして使用し、もう1つのインターフェースはチャネル番号を直接使用します。両方のユースケースをカバーするために、単一の列挙を定義しました。

typedef enum
{
    CHANNEL_XL_X = 0,
    CHANNEL_XL_Y = 1,
    CHANNEL_XL_Z = 2,
    CHANNEL_G_X = 3,
    CHANNEL_G_Y = 4,
    CHANNEL_G_Z = 5,
    CHANNEL_AUX1 = 6,
    CHANNEL_AUX2 = 7
} ChannelNum;

struct MeasurementResult;

void StartMeasurement(uint8_t channel_mask);
MeasurementResult ReadMeasurementResult(ChannelNum channel_num);

main
{
    ...

    StartMeasurement( (1 << CHANNEL_XL_X) | (1 << CHANNEL_XL_Y) | (1 << CHANNEL_XL_Z) );

    meas_result_x = ReadMeasurementResult(CHANNEL_XL_X);
    meas_result_y = ReadMeasurementResult(CHANNEL_XL_Y);
    meas_result_z = ReadMeasurementResult(CHANNEL_XL_Z);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.