続いて 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
みたいなビットごとに指定できる書き方がスマートで個人的に好きなんですよね。
今回はそれの再現を試みました。実際めちゃくちゃ使いやすいです。本家と若干違うところも多いですがね。あくまで個人ユースを目的として作ったファイルであり、配布はついでであるというご理解をして頂ければと思います。