BitBand

今回、ライブラリの使い方は置いといて、ARM Cortex-MのBitBandという機能について見てみます。

BitBandについては、こちらでわかりやすく説明されています。要約すると、RAMやレジスタを複数箇所で触っている場合、1ビット操作をしたいときに、普通はディスパッチ禁止区間を作って、リード、変更、ライトを行うところを、これらの処理を1命令でアトミックに実行できる機能で、Coretex-Mの特徴機能だそうです。

hw_types.h では、下記のように、BitBand エイリアス領域にアクセスするマクロが定義されています。BitBand エイリアス領域は、1MByteの領域に対して、1bit毎に1アドレス(32bit)を割り当てているので、空間上32Mbyte存在します。stellarisのSRAM 0x20000000~ は、0x22000000 から 0x223FFFFF までが、BitBand エイリアス空間ということになりますが、もちろん、実際は32KByte RAMの領域しかありません。

#define HWREG(x)                                                              \
        (*((volatile unsigned long *)(x)))
#define HWREGBITW(x, b)                                                       \
        HWREG(((unsigned long)(x) & 0xF0000000) | 0x02000000 |                \
              (((unsigned long)(x) & 0x000FFFFF) << 5) | ((b) << 2))

下のようなサンプルコードを書いてみました。
g_ulValue は SRAM 上の変数です。
テスト1では、マクロを使って1ビット書き込みをしています。
テスト2では、g_ulValue に設定した値を、マクロを使って1ビットずつ読みんでみます。
テスト3では、マクロを使わず、g_ulValue のアドレスである、0x20004000 に対するBitBand エイリアスアドレスに直接、値を設定しています。

#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/sysctl.h"
#include "utils/uartstdio.h"

static volatile unsigned long *g_ulValue = 0x20004000; // RAM上の適当な場所

int main(void)
{
    int i;
    unsigned long *alias;

    SysCtlClockSet(SYSCTL_SYSDIV_1 |
        SYSCTL_USE_OSC | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);

    // Init UART
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    UARTStdioInit(0);

    // test 1.
    // BitBand による 1 ビット書き込み
    *g_ulValue = 0;

    HWREGBITW(g_ulValue, 10) = 1;

    UARTprintf("0x%08x\n", *g_ulValue);

    // test 2.
    // BitBand による 1 ビット読み込み
    *g_ulValue = 0x4;
    for (i = 0; i < 32; i++) {
        UARTprintf("%d", HWREGBITW(g_ulValue, 31 - i));
    }
    UARTprintf("b\n");

    // test 3.
    // 直接 g_ulValue の 16 ビット目の BitBand エイリアスアドレスに書き込み
    *g_ulValue = 0;
    alias = 0x22080040;  // 0x22000000 + 0x4000 * 32 + 16 * 4
    *alias = 1;
    UARTprintf("0x%08x\n", *g_ulValue);

    while(1){}
}

結果は期待通りです。

0x00000400
00000000000000000000000000000100b
0x00010000

テスト3の 45 行目のアセンブリを見ても、エイリアスアドレス 0x22080040 へのSTRだけになっています。

44            alias = 0x22080040;  // 0x22000000 + 0x4000 * 32 + 16 * 4
0000073e:   480E     LDR             R0, $C$CON5
00000740:   9001     STR             R0, [SP, #0x4]
45            *alias = 1;
00000742:   9801     LDR             R0, [SP, #0x4]
00000744:   2101     MOV             R1, #0x1
00000746:   6001     STR             R1, [R0]