SNSへはこちら

RAマイコンを試食(4) - プロジェクトの作成

続いて ARM マイコンらしく開発できるようにライブラリ等を入れていきます。
なお、レジスタ構造体は自作ですからここが面倒でした。

ディレクトリ構成

ソース用ディレクトリ src、ヘッダ用ディレクトリ inc、オブジェクトファイル用ディレクトリ obj を作ります。
まあ通常通りですね。

$ mkdir Project
$ cd Project
$ mkdir src inc obj

Makefileとリンカスクリプト

こちらは示すだけ。まずは Makefile です。

ARCH = arm-none-eabi

CC = $(ARCH)-gcc
CXX = $(ARCH)-g++
AS = $(ARCH)-as
LD = $(ARCH)-gcc
OBJDUMP = $(ARCH)-objdump
OBJCOPY = $(ARCH)-objcopy
SIZE = $(ARCH)-size

TARGET = Register.elf

ASMSRCS = $(wildcard src/*.s)
CSRCS = $(wildcard src/*.c)
CXXSCRS = $(wildcard src/*.cpp)
OBJS = $(subst src/,obj/,$(ASMSRCS:.s=.o) $(CSRCS:.c=.o) $(CXXSCRS:.cpp=.o))

INCLUDES = -I./inc -I./CMSIS/Core/Include -I./CMSIS/Device/ARM/ARMCM23/Include

FLAGS = -mcpu=cortex-m23 -mthumb -mfloat-abi=soft
CFLAGS = $(FLAGS) -std=gnu11 -Os -Wall -ffunction-sections -fdata-sections
CXXFLAGS = $(FLAGS) -std=c++20 -Os -Wall -ffunction-sections -fdata-sections
LDFLAGS = $(FLAGS) -T linker.ld -Wl,--gc-sections --specs=nano.specs

$(TARGET): $(OBJS)
    $(LD) $(LDFLAGS) -o $@ $^
    $(OBJDUMP) -d $@ > $(TARGET:.elf=.dmp)
    $(OBJCOPY) -O binary $(TARGET) $(TARGET:.elf=.bin)
    $(SIZE) $@

obj/%.o: src/%.s
    $(AS) $(FLAGS) -c -o $@ $^

obj/%.o: src/%.c
    $(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $^

obj/%.o: src/%.cpp
    $(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $@ $^

clean:
    $(RM) $(TARGET) $(TARGET:.elf=.dmp) $(TARGET:.elf=.bin) obj/*

続いてリンカスクリプトです。前回の記事に多少載せていますが、全体はこちら。プロジェクトルートに linker.ld という名前で保存してください。

ENTRY(Reset_Handler)

MEMORY {
    VECTOR(rx) : ORIGIN = 0x00000000, LENGTH = 0xC0
    OPT(r) : ORIGIN = 0x00000400, LENGTH = 0x3C

    ROM(rx) : ORIGIN = 0x00000440, LENGTH = 256K - 0x440
    RAM(wx) : ORIGIN = 0x20000000, LENGTH = 16K
}

_estack = ORIGIN(RAM) + LENGTH(RAM);

SECTIONS {
    .isr_vector : {
        . = ALIGN(4);
        KEEP(*(.isr_vector))
        . = ALIGN(4);
    } > VECTOR

    .opt : {
        . = ALIGN(4);
        KEEP(obj/opt.o(.opt))
        . = ALIGN(4);
    } > OPT

    .text : {
        . = ALIGN(4);
        *(.text)
        *(.text.*)
        . = ALIGN(4);
    } > ROM
    .rodata : {
        . = ALIGN(4);
        *(.rodata)
        *(.rodata*)
        . = ALIGN(4);
    } > ROM

    .preinit_array : {
        . = ALIGN(4);
        PROVIDE_HIDDEN (__preinit_array_start = .);
        KEEP (*(.preinit_array*))
        PROVIDE_HIDDEN (__preinit_array_end = .);
        . = ALIGN(4);
    } > ROM

    .init_array : {
        . = ALIGN(4);
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array*))
        PROVIDE_HIDDEN (__init_array_end = .);
        . = ALIGN(4);
    } > ROM

    .fini_array : {
        . = ALIGN(4);
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP (*(SORT(.fini_array.*)))
        KEEP (*(.fini_array*))
        PROVIDE_HIDDEN (__fini_array_end = .);
        . = ALIGN(4);
    } > ROM

    _sidata = LOADADDR(.data);

    .data : {
        . = ALIGN(4);
        _sdata = .;
        *(.data)
        *(.data*)

        . = ALIGN(4);
        _edata = .;
    } > RAM AT> ROM


    .bss : {
        . = ALIGN(4);
        _sbss = .;
        __bss_start__ = _sbss;
        *(.bss)
        *(.bss*)
        *(COMMON)

        . = ALIGN(4);
        _ebss = .;
        __bss_end__ = _ebss;
    } > RAM
    _end = .;
}

CMSIS

ARM といったら CMSIS ライブラリです。今回もご多分に漏れず、導入しておきます。
現在 CMSIS はバージョンが5となり、RTOS や DAP 関連もひっくるめてそう呼ばれるようになっています。ですが今回必要なのはヘッダです。それだけを取っていきましょう。

まずは GitHub からどこかのディレクトリにクローンします。

$ git clone https://github.com/ARM-software/CMSIS_5

そうしたら、CMSIS ディレクトリをプロジェクトルートにコピーし、その中は Core と Device のみを残します。
それ以外は全く使わないので邪魔なだけですから、消しておきましょう。

このヘッダは既に上の Makefile にインクルードパスとして指定してありますので、何か追加で設定する必要はありません。

ここまでで、こんな構成であればOKです↓

.
├── CMSIS
├── Makefile
├── inc
├── linker.ld
├── obj
└── src

スタートアップコードの作成

ARM Cortex-M シリーズの特徴、スタートアップコードがC言語だけで書ける(アセンブリ不要)というのを活かして記述していきましょう。src/ 内に置きます。僕は src/init.c としました。
コード全体はベクタテーブルの記述等、無意味に長いので、一部だけを抜粋します。コード全体はこちらで御覧ください。

#include <stddef.h>

extern int _estack;
void __libc_init_array(void);
extern int _sdata, _edata, _sidata;
extern int _sbss, _ebss;
int main(void);

void Reset_Handler(void) {
    // Clear .bss section
    int *p = &_sbss;
    while( p != &_ebss ) {
        *p = 0;
        p++;
    }

    // Copy .data section
    const int *src = &_sidata;
    int *dest = &_sdata;
    while( dest != &_edata ) {
        *dest = *src;
        src++;
        dest++;
    }

    main();
    while(1);
}

至って普通のことをしています。まず、リセット後に Reset_Handlerにジャンプするので、そこで各種データの準備をしています。__libc_init_array() は ARM の newlib に付属しているもので、コンストラクタ属性を付けた関数の実行を行います。
普通のマイコンではスタックポインタの初期化をアセンブリ言語でやる必要がありますが、ARM Cortex-M ではベクタテーブル中にスタックポインタの初期値が含まれるため、その必要がなく全てC言語で書けるのです。

あ、でもこのままでは「main 関数がないぞ」と怒られるので、別途 src/main.c でも作って、

#include <ARMCM23.h>

int main(void) {

    while(1) {

    }
}

とでもすればいいです。これでビルドが通るはず。

レジスタ用ヘッダファイルを置く

ざっと調べた所、ヘッダファイルが見つかりませんでした。なので自作しました。若干ルネサスの iodefine.h に比べて異なるところはありますが、許容してね!
今回は特別にこちらを zip ファイルとして配布します。inc ディレクトリ内に置いてください。

ただし、自分が使ったペリフェラルしか作成していないこと、レジスタ名やそもそもアドレスに間違いがある可能性があることを承諾いただいた方のみに配布いたします!!!勿論間違いのご指摘はコメント欄にてしていただきたいのですが、「間違ってるぞ、ふざけるな」等感じられる方はご遠慮ください。
このヘッダを入れた場合は、src/main.c を以下のように追記することをおすすめします。

#include <ARMCM23.h>
#include "ra.h"

int main(void) {

    while(1) {

    }
}

これでレジスタ構造体が読み込まれました。あとはヘッダのソースコードを読みながら実装を頑張ってください!

ARM マイコンなので、それに準じた準備で済みましたね。

そして、自作レジスタ構造体ヘッダの提供まで大盤振る舞いしてしまいました。ルネサスのヘッダって、HOGE.HAGE.BIT.HIGE みたいなビットごとに指定できる書き方がスマートで個人的に好きなんですよね。
今回はそれの再現を試みました。実際めちゃくちゃ使いやすいです。本家と若干違うところも多いですがね。あくまで個人ユースを目的として作ったファイルであり、配布はついでであるというご理解をして頂ければと思います。