SNSへはこちら

LPC11U35はUSBがラク!(3) - メモリの記述を改善する・自分でコードを書いてみる

前回まで使ってきたサンプルで、メモリのベースアドレスやサイズがベタ書きで良くないと言いました。なので本記事ではここを改善したいと思います。

GetMemSizeで確認

まず、どのくらいのメモリ領域が必要になるのかを調べる必要があります。そのために GetMemSize という関数が使えます。この関数は、初期化構造体を見ることで必要なメモリサイズを返してくれる関数です。
しかしながら、この実装はマクロでもなく ROM API 上にあるため、コンパイル時に結果はわかりません

なので、グローバル変数としてメモリ領域を宣言しておくにしても、定数でない限り無理というわけです。なので、sprintf 等を駆使し、ついでに USB CDC でホストにその情報を送ることで確認してみましょう。

具体的には以下のコードを書いて検証です。sprintf が RedLib に含まれていないようなので、使用ライブラリを NewLibNano に変更してからビルドしました。結果は以下です。

  • USB ペリフェラル: 704
  • CDC クラス: 76

コードにする

ということで、やっていきましょう。USB ペリフェラル用メモリは 2048byte、CDC は 4byte のアラインメントに整列せねばならないことをお忘れなく。

まずはグローバル領域に変数を宣言。

__attribute__((aligned(2048)))
volatile static uint8_t mem_usb[704];
__attribute__((aligned(4)))
volatile static uint8_t mem_cdc[76];

この時点で「2048bytes にアラインメントなんてできるのか」と思う方がいると思います。このサイトを参考にしてちょっと検証してみましょう。

$ arm-none-eabi-cpp -dD -I ../inc -I ../../LPC11Uxx_Driver_Lib/inc -I ../../CMSISv2p00_LPC11Uxx/inc ../src/main.c |grep __BIGGEST_ALIGNMENT__
#define __BIGGEST_ALIGNMENT__ 8

...えっ?最大で 8bytes のアラインメントにしか揃えられない???
こうなるともうこの記事の存在価値がなくなってしまうようですが、続けます。どうやらコンパイラさんは頑張ってくれたようです。

$ nm USB_ROM_CDC.axf | grep mem_
10000800 b mem_cdc
10001000 b mem_usb

よし、とりまやっていこう。

続いて、main 関数内の初期化コードを一部書き換えます。

  usb_param.mem_base = (uint32_t)(uintptr_t)mem_usb;
  usb_param.mem_size = sizeof(mem_usb);
...
  cdc_param.mem_base = (uint32_t)(uintptr_t)mem_cdc;
  cdc_param.mem_size = sizeof(mem_cdc);

これで...動きません。どうやら USB ペリフェラル用のバッファ容量が足りないようです。そうして動いてくれるギリギリの容量を頑張ってさがしてみたら、どうやら 1088 bytes 必要なようでした。なんだよ、GetMemSize の意味ないじゃん。
気を取り直して、宣言部を書き換えます。

__attribute__((aligned(2048)))
volatile static uint8_t mem_usb[1088];
__attribute__((aligned(4)))
volatile static uint8_t mem_cdc[76];

これでやっと動きました。

結論

さて、ちゃんとコンパイラ・リンカさんから割り当てられた領域を使うように仕向けることが出来ましたが、gcc の __attribute__ では最大8バイト境界にしか揃えられないという事実が発覚しました。

はい、大人しくメモリアドレス直打ちを使いましょう。

Postscript

P.S. のことらしいです。Poststcipt って言うんですね〜。さて追伸ですが、ここまでの知識を利用して、頑張ってコードを書いてみました。
自作コードなので公開しちゃいますね。

#include <romapi/power_api.h>
#include <romapi/mw_usbd_rom_api.h>
#define pUsbApi ((USBD_API_T*)((*(ROM **)(0x1FFF1FF8))->pUSBD))
#define pDivApi ((LPC_ROM_DIV_STRUCT*)((*(ROM **)(0x1FFF1FF8))->pROMDiv))
#include <usb_desc/usb_desc.h>
#include "LPC11Uxx.h"

USBD_HANDLE_T hUSB;
USBD_HANDLE_T hCDC;

static USB_CORE_DESCS_T descs = {
        .device_desc = VCOM_DeviceDescriptor,
        .full_speed_desc = VCOM_ConfigDescriptor,
        .high_speed_desc = VCOM_ConfigDescriptor,
        .string_desc = VCOM_StringDescriptor
};

static void usb_init(void) {
    // Enable GPIO, USB, USBRAM
    LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 6) | (1 << 14) | (1 << 26);
    LPC_IOCON->PIO0_3 = 0x81; // VBUS
    LPC_IOCON->PIO0_6 = 0x91; // USB_CONNECT
}

void usb_cdc_init(void) {
    usb_init();

    USBD_API_INIT_PARAM_T usb_init = {};
    usb_init.usb_reg_base = LPC_USB_BASE;
    usb_init.max_num_ep = 3;
    usb_init.mem_base = 0x10000000 + 4096 - 2048;
    usb_init.mem_size = 1024 + 256;
    pUsbApi->hw->Init(&hUSB, &descs, &usb_init);

    USBD_CDC_INIT_PARAM_T cdc_init = {};
    cdc_init.mem_base = usb_init.mem_base + usb_init.mem_size;
    cdc_init.mem_size = 128;
    cdc_init.cif_intf_desc = VCOM_ConfigDescriptor + USB_CONFIGUARTION_DESC_SIZE;
    cdc_init.dif_intf_desc = VCOM_ConfigDescriptor + USB_CONFIGUARTION_DESC_SIZE +
            USB_INTERFACE_DESC_SIZE + 0x0013 + USB_ENDPOINT_DESC_SIZE;
    pUsbApi->cdc->init(hUSB, &cdc_init, &hCDC);

    NVIC_EnableIRQ(USB_IRQn);
    pUsbApi->hw->Connect(hUSB, 1);
}

void USB_IRQHandler(void) {
    pUsbApi->hw->ISR(hUSB);
}

はい、ピッタリ50行です。しかも30分程度で動作まで漕ぎ着けた
main 関数部はこんな感じ。

int main(void) {
    GPIODir(0, 7, OUTPUT);
    usb_cdc_init();
    ms_wait(2000);

    while(1) {
        pUsbApi->hw->WriteEP(hUSB, 0x82, "Hello", 5); // 実はstrcpy要らなかった
        ms_wait(1000);

    }
    return 0;
}

色々と癖はあるものの、簡単に出来ますね!さすが ROM API です!!