micro:bitの開発環境構築をつくる(C言語)

8 min読了の目安(約8000字TECH技術記事

はじめに

nrfx で提供されているコードを利用して、LED点滅、UART出力を行う簡単なCのプログラムのビルドまでをする。
新規に作成する *.c ファイル、Makefileについては
nrfx/mdk/ 以下に作成するものとする(あんまり綺麗ではないですが)

環境

  • micro:bit V1.5
    最新はV2ですが、ここではV1.5の話(V2持ってないので)。
  • arm-none-gcc バージョンは以下
$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907]
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

リセットハンドラ

micro:bit(V1.5)にのっているプロセッサはNordic nRF51822-QFAA-R rev 3 なので、
以下のコードを使用する。

gcc_startup_nrf51.Sのアセンブル

gcc_startup_nrf51.S は以下のようにアセンブルをする。
(gcc_startup_nrf51.Sの中身を見ると、#defineという記載もあるので、正確にはプリプロセス->アセンブル)

$ arm-none-eabi-gcc -c -g -mcpu=cortex-m0 -o gcc_startup_nrf51.o gcc_startup_nrf51.S

補足:ここで、-cオプションを付けているのはリンクは行わないようにするため。
3.14 Options for Linking

-c
-S
-E
If any of these options is used, then the linker is not run, and object file names should not be used as arguments.

system_nrf51.cのコンパイル

インクルードファイルを辿ると、core_cm0.hというファイルをインクルードしている。これはCMSIS_5にある。
core_cm0.hの場所はCMSIS/Core/Include/core_cm0.h
ちなみにCMSISはCortex Microcontroller Software Interface Standard のこと。

コンパイルは以下のようにするとできる。
Makefileで書くと以下のようになる。
CFLAGSに書いてあるオプションの説明は省略。

CMSISPATH=<PATH to CMSIS_5>/CMSIS
NRFXPATH=<PATH to nrfx>

IPATH=-I $(CMSISPATH)/Core/Include/
IPATH+=-I $(NRFXPATH)/hal/
IPATH+=-I $(NRFXPATH)/drivers/include/
IPATH+=-I $(NRFXPATH)/
IPATH+=-I $(NRFXPATH)/templates/
IPATH+=-I $(NRFXPATH)/mdk/

CFLAGS = -DNRF51
CFLAGS += -mcpu=cortex-m0
CFLAGS += -mthumb -mabi=aapcs
CFLAGS +=  -Wall -Werror -O0 -g
CFLAGS += -mfloat-abi=soft
CFLAGS += -ffunction-sections -fdata-sections -fno-strict-aliasing
CFLAGS += -fno-builtin --short-enums 

system_nrf51.o : system_nrf51.c
	arm-none-eabi-gcc -c $(CFLAGS) $(IPATH) -o system_nrf51.o system_nrf51.c

main関数の作成

baremetal examples notmain.cを参考に
以下のようなコードを書いてみた。
LEDの一つを点滅して、hello!をずっとURATに出力するだけのプログラム。

#include<nrfx.h>
#include<nrf_gpio.h>
#include<nrf_uart.h>
#include<nrfx_uart.h>

#define ROW3 15
#define ROW2 14
#define ROW1 13
#define COL9 12
#define COL8 11
#define COL7 10
#define COL6  9
#define COL5  8
#define COL4  7
#define COL3  6
#define COL2  5
#define COL1  4

static nrfx_uart_t app_uart_inst = NRFX_UART_INSTANCE(0);

int main ( void )
{
    nrfx_uart_config_t p_config = NRFX_UART_DEFAULT_CONFIG(24, 25);
  
    nrfx_uart_init(&app_uart_inst, &p_config, NULL);

    nrf_gpio_pin_dir_set(ROW1, NRF_GPIO_PIN_DIR_OUTPUT);
    nrf_gpio_pin_dir_set(COL9, NRF_GPIO_PIN_DIR_OUTPUT);

    nrf_gpio_pin_clear(COL9);

    char hello[] = "hello!\n";
    while(1)
    {
        nrf_gpio_pin_clear(ROW1);
        for(int i = 0; i<200000;i++);
        nrf_gpio_pin_set(ROW1);
        for(int i = 0; i<200000;i++);
        nrfx_uart_tx(&app_uart_inst, (uint8_t *)hello, sizeof(hello));
    }
    return(0);
}

templates/nrfx_config_nrf51.hを以下のように変更。

diff --git a/templates/nrfx_config_nrf51.h b/templates/nrfx_config_nrf51.h
index 5f0e45c..9a4a4e3 100644
--- a/templates/nrfx_config_nrf51.h
+++ b/templates/nrfx_config_nrf51.h
@@ -1077,13 +1077,13 @@
 // <e> NRFX_UART_ENABLED - nrfx_uart - UART peripheral driver
 //==========================================================
 #ifndef NRFX_UART_ENABLED
-#define NRFX_UART_ENABLED 0
+#define NRFX_UART_ENABLED 1
 #endif
 
 // <q> NRFX_UART0_ENABLED - Enable UART0 instance
 
 #ifndef NRFX_UART0_ENABLED
-#define NRFX_UART0_ENABLED 0
+#define NRFX_UART0_ENABLED 1
 #endif
 
 // <o> NRFX_UART_DEFAULT_CONFIG_IRQ_PRIORITY  - Interrupt priority

Makefileは以下

CMSISPATH=<PATH to CMSIS_5>/CMSIS
NRFXPATH=<PATH to nrfx>

IPATH=-I $(CMSISPATH)/Core/Include/
IPATH+=-I $(NRFXPATH)/hal/
IPATH+=-I $(NRFXPATH)/drivers/include/
IPATH+=-I $(NRFXPATH)/
IPATH+=-I $(NRFXPATH)/templates/
IPATH+=-I $(NRFXPATH)/mdk/

CFLAGS = -DNRF51
CFLAGS += -mcpu=cortex-m0
CFLAGS += -mthumb -mabi=aapcs
CFLAGS +=  -Wall -Werror -O0 -g
CFLAGS += -mfloat-abi=soft
CFLAGS += -ffunction-sections -fdata-sections -fno-strict-aliasing
CFLAGS += -fno-builtin --short-enums 

LDFLAGS = -mthumb -mabi=aapcs
LDFLAGS += -mcpu=cortex-m0
LDFLAGS += -Wl,--gc-sections
LDFLAGS += --specs=nano.specs -lc -lnosys

all : main.hex

main.hex : main.elf
	arm-none-eabi-objcopy main.elf main.hex -O ihex

main.elf : gcc_startup_nrf51.o system_nrf51.o main.o nrfx_uart.o
	arm-none-eabi-gcc $(LDFLAGS) -T nrf51_xxab.ld -o main.elf gcc_startup_nrf51.o system_nrf51.o nrfx_uart.o main.o

system_nrf51.o : system_nrf51.c
	arm-none-eabi-gcc -c $(CFLAGS) $(IPATH) -o system_nrf51.o system_nrf51.c

main.o : main.c
	arm-none-eabi-gcc -c $(CFLAGS) $(IPATH) -o main.o main.c

gcc_startup_nrf51.o : gcc_startup_nrf51.S
	arm-none-eabi-gcc -c -g -mcpu=$(MCPU) -o gcc_startup_nrf51.o gcc_startup_nrf51.S

nrfx_uart.o : <PATH to nrfx>/drivers/src/nrfx_uart.c
	arm-none-eabi-gcc -c $(CFLAGS) $(IPATH) -o nrfx_uart.o <PATH to nrfx>/drivers/src/nrfx_uart.c

clean:
	rm -f *.hex
	rm -f *.o
	rm -f *.elf

リンカスクリプト

nrf51_xxab.ld を使用。上記 Makefileで -T nrf51_xxab.ld としてスクリプトを指定している。

qemu

qemuもVer4あたりからmicrobitに対応している。
make してできた main.hexを指定して動かすこともできる。

$ qemu-system-arm -M microbit -device loader,file=main.hex -serial stdio
hello!
hello!
hello!
hello!
hello!
hello!
hello!
<CTRL Cで終了>

gdb + Visual Studio Codeでデバッグ

以下のように-s -Sをつけると、デバッグが可能。
以下のように、qemuを実行

$ qemu-system-arm -M microbit -device loader,file=main.hex -serial stdio -s -S

-s -Sの意味は以下。

$ qemu-system-arm --help
QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.8)
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
usage: qemu-system-arm [options] [disk_image]
<省略>
-S              freeze CPU at startup (use 'c' to start execution)
<省略>
-s              shorthand for -gdb tcp::1234
<省略>

Visual Studio Codeで以下のようにlaunch.jsonを作成し、デバッグの実行を行う。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 起動",
            "type": "cppdbg",
            "request":"launch",
            "program": "~/nrfx/mdk/main.elf",
            
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "arm-none-eabi-gdb",
            "miDebuggerServerAddress": "localhost:1234",
            
            "setupCommands": [
                {
                    "description": "gdb の再フォーマットを有効にする",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                    
                }
            ]
        }
    ]
}

以下のように、brak pointを貼ったところで処理を止めることができる。