Cortex-Mマイコン上で計算を行うアセンブリコードのデバッグを、エミュレータを使ってパソコンの上で行いたいです。
大学までのバスの中でパソコンからマイコン垂らしながらデバッグするのは不便で少し恥ずかしいので。
Cortex-M3プロセッサに対応しているエミュレータとしてQEMUがありますが、用意されているマイコンボードが LM3S811EVB / LM3S6965EVB の2つしかありません。
wiki.qemu.org
私は普段シリアルの設定などちょっと大変なところはすべてmbedライブラリにおまかせしているので、できればmbedの対応しているボードを使いたいです。
mbedに対応したマイコンボードの使えるエミュレータはないのでしょうか? ということで調べてみたのが今回の内容となります。
まとめ
- GNU ARM Eclipse QEMUを使えばmbed対応ボードのNUCLEO-F103RBのエミュレーションができる
- mbedライブラリに一部修正を加えることでLチカは動作した
- TimerやUARTは今の所利用方法不明。要調査。
mbed対応ボードのエミュレーションができるツールを探していたところ、GNU ARM Eclipseというプロジェクトを見つけました。
こちらはARM開発のためのツールやEclipseプラグインなどを開発するプロジェクトのようです。
この中の一つにCortex-Mマイコン対応を増やしたQEMU forkであるGNU ARM Eclipse QEMUがあり、mbed対応ボードも2つほど
サポートボードリストに含まれています。
これを使えばmbedライブラリを使いつつ、Cortex-Mマイコンのプログラムをエミュレータで実行できるかもしれません。
早速試してみましょう。
実行環境
以降の作業はThinkPad x201(x86_64、RAM 8GB)のArch Linuxで行いました。
対象ボードはNUCLEO-F103RBを選択します。
基本は以下を参照です。
gnuarmeclipse.github.io
Windowsの方はEXEファイルをダウンロードしてインストールするだけ。
Macの方はpkgファイルをダウンロードしてインストールするだけ。
Linuxの方はtgzファイルをダウンロードして適当なディレクトリで解凍するだけです。
私の使っているArch LinuxではAURにパッケージがあったので、yaourt を使ってgnuarmeclipse-qemu-binをインストールするだけでインストール完了しました。
$ yaourt -S gnuarmeclipse-qemu-bin
インストールしたファイルは /opt/gnuarmeclipse/qemu/ 以下に置かれます。
Lチカプログラムの作成
Lチカのプログラムはmbedのサンプルを作成してGCC用にエクスポートしてビルドするだけ……だと良かったのですが、
うまくいかないのでビルドを通すための修正を加えた後、QEMUで動くように修正を加える必要がありました。
※このときのmbed-srcのリビジョンはこちらの表記で635:a11c0372f0ba
でした
mbed-srcライブラリのインポート
まずはじめにmbedコンパイラでmbed_blinkyサンプルを作り、mbedライブラリをmbed-srcライブラリに置き換え、GCC用にエクスポートを行ってください。
mbed-srcライブラリのインポート方法については、以下のページにわかりやすく書かれています。
nora66.com
エクスポートしたソースを解凍した後、ビルドが通るようにするためMakefileに以下の修正を加えます。
- CC_FLAGSとCPPC_FLAGSとCC_SYMBOLSに書かれた -DDEVICE_SERIAL_ASYNCH=1 を -DDEVICE_SERIAL_ASYNCH=0 に変更する
- CC_FLAGSとCPPC_FLAGSとCC_SYMBOLSに書かれた -DDEVICE_CAN=1 を -DDEVICE_CAN=0 に変更する
- LD_FLAGSから-Wl,--wrap,main -Wl,--wrap,malloc_r -Wl,--wrap,free_r -Wl,--wrap,realloc_r -Wl,--wrap,calloc_rを削除する
1.はビルド時に出る以下のエラーを回避します。
../mbed-src/targets/hal/TARGET_STM/TARGET_STM32F1/serial_api.c: In function 'serial_break_set':
../mbed-src/targets/hal/TARGET_STM/TARGET_STM32F1/serial_api.c:337:48: error: 'serial_t {aka struct <anonymous>}' has no member named 'uart'
UartHandle.Instance = (USART_TypeDef *)(obj->uart);
DEVICE_SERIAL_ASYNCHが有効になっている場合、serial_api.hで定義されているserial_t型の中身がuartメンバを持つserial_s型でなくなるために起こるエラーです。
根本的な解決は大変そうなので、今回はDEVICE_SERIAL_ASYNCHを0にして対応します。
2.は以下のエラーを回避します。
In file included from ../mbed-src/api/mbed.h:50:0,
from ../main.cpp:1:
../mbed-src/api/CAN.h:235:21: error: field '_can' has incomplete type 'can_t {aka can_s}'
can_t _can;
^~~~
In file included from ../mbed-src/api/CAN.h:23:0,
from ../mbed-src/api/mbed.h:50,
from ../main.cpp:1:
../mbed-src/hal/can_api.h:55:16: note: forward declaration of 'can_t {aka struct can_s}'
typedef struct can_s can_t;
C++詳しくなくてすぐに理由がわからないので、DEVICE_CANを0にして対応します。
3.を行わない場合、main等のラッパ関数を用意してないので以下のエラーが出ます。
ラッパ関数は特に必要ない(と思う)ので、オプションを削除して対応します。
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-fclose.o): In function `_fclose_r':
fclose.c:(.text._fclose_r+0x4a): undefined reference to `__wrap__free_r'
fclose.c:(.text._fclose_r+0x58): undefined reference to `__wrap__free_r'
fclose.c:(.text._fclose_r+0x7a): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-fflush.o): In function `__sflush_r':
fflush.c:(.text.__sflush_r+0x7c): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-fvwrite.o): In function `__sfvwrite_r':
fvwrite.c:(.text.__sfvwrite_r+0x22c): undefined reference to `__wrap__malloc_r'
fvwrite.c:(.text.__sfvwrite_r+0x2b4): undefined reference to `__wrap__realloc_r'
fvwrite.c:(.text.__sfvwrite_r+0x2c4): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-makebuf.o): In function `__smakebuf_r':
makebuf.c:(.text.__smakebuf_r+0x2c): undefined reference to `__wrap__malloc_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-malloc.o): In function `malloc':
malloc.c:(.text.malloc+0x6): undefined reference to `__wrap__malloc_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-malloc.o): In function `free':
malloc.c:(.text.free+0x6): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-wsetup.o): In function `__swsetup_r':
wsetup.c:(.text.__swsetup_r+0x98): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-mprec.o): In function `_Balloc':
mprec.c:(.text._Balloc+0x22): undefined reference to `__wrap__calloc_r'
mprec.c:(.text._Balloc+0x3e): undefined reference to `__wrap__calloc_r'
collect2: error: ld returned 1 exit status
クロックソースの変更
クロックソースは内蔵RCオシレータ(HSI)にしないと動かなかったので、system_stm32f1xx.cのUSE_PLL_HSE_EXTCとUSE_PLL_HSE_XTALを0にセットします。
#define USE_PLL_HSE_EXTC (0)
#define USE_PLL_HSE_XTAL (0)
wait関数(というよりTimer関係)がうまく動いてくれないので、Lチカプログラムのwait関数をfor文の空回しに変更します。
#include "mbed.h"
DigitalOut myled(LED1);
int main() {
while(1) {
myled = 1;
for (int i = 0; i < 100000000; i++);
myled = 0;
for (int i = 0; i < 100000000; i++);
}
}
修正に関するdiff
上記修正前と修正後のdiffを以下に示します。
diff --git a/Makefile b/Makefile
index d4de525..567fecf 100644
--- a/Makefile
+++ b/Makefile
@@ -50,19 +50,20 @@ SIZE = $(GCC_BIN)arm-none-eabi-size
CPU = -mcpu=cortex-m3 -mthumb
-CC_FLAGS = -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os -std=gnu99 -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=1 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=1 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB -include mbed_config.h -MMD -MP
-CPPC_FLAGS = -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os -std=gnu++98 -fno-rtti -Wvla -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=1 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=1 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB -include mbed_config.h -MMD -MP
+CC_FLAGS = -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os -std=gnu99 -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=0 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=0 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB -include mbed_config.h -MMD -MP
+CPPC_FLAGS = -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os -std=gnu++98 -fno-rtti -Wvla -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=0 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=0 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB -include mbed_config.h -MMD -MP
ASM_FLAGS = -x assembler-with-cpp -D__CMSIS_RTOS -D__MBED_CMSIS_RTOS_CM -D__CORTEX_M3 -DARM_MATH_CM3 -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os
-CC_SYMBOLS = -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=1 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=1 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB
+CC_SYMBOLS = -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=0 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=0 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB
-LD_FLAGS =-Wl,--gc-sections -Wl,--wrap,main -Wl,--wrap,_malloc_r -Wl,--wrap,_free_r -Wl,--wrap,_realloc_r -Wl,--wrap,_calloc_r -mcpu=cortex-m3 -mthumb
+LD_FLAGS =-Wl,--gc-sections -mcpu=cortex-m3 -mthumb
LD_SYS_LIBS = -lstdc++ -lsupc++ -lm -lc -lgcc -lnosys
+DEBUG=1
ifeq ($(DEBUG), 1)
- CC_FLAGS += -DDEBUG -O0
+ CC_FLAGS += -DDEBUG -O0 -g3
else
- CC_FLAGS += -DNDEBUG -Os
+ CC_FLAGS += -DNDEBUG -Os -g3
endif
diff --git a/main.cpp b/main.cpp
index ccb0f20..2cea268 100644
--- a/main.cpp
+++ b/main.cpp
@@ -6,9 +6,8 @@ int main() {
while(1) {
myled = 1;
- wait(0.5);
+ for (int i = 0; i < 100000000; i++);
myled = 0;
- wait(0.5);
+ for (int i = 0; i < 100000000; i++);
}
}
diff --git a/mbed-src/targets/cmsis/TARGET_STM/TARGET_STM32F1/TARGET_NUCLEO_F103RB/system_stm32f1xx.c b/mbed-src/targets/cmsis/TARGET_STM/TARGET_STM32F1/TARGET_NUCLEO_F103RB/system_stm32f1xx.c
index 196ddff..65ff176 100644
--- a/mbed-src/targets/cmsis/TARGET_STM/TARGET_STM32F1/TARGET_NUCLEO_F103RB/system_stm32f1xx.c
+++ b/mbed-src/targets/cmsis/TARGET_STM/TARGET_STM32F1/TARGET_NUCLEO_F103RB/system_stm32f1xx.c
@@ -141,8 +141,8 @@
*/
/* Select the clock sources (other than HSI) to start with (0=OFF, 1=ON) */
-#define USE_PLL_HSE_EXTC (1) /* Use external clock */
-#define USE_PLL_HSE_XTAL (1) /* Use external xtal */
+#define USE_PLL_HSE_EXTC (0) /* Use external clock */
+#define USE_PLL_HSE_XTAL (0) /* Use external xtal */
/**
* @}
動かしてみる
インストールしたQEMUを用いて、Lチカのプログラムを動かしてみたいと思います。
コマンドラインからQEMUを動かす方法は以下に書かれています。
gnuarmeclipse.github.io
QEMU実行コマンドのテンプレートは以下になります。
qemu-system-gnuarmeclipse --verbose --verbose --board ボード名 \
--mcu マイコン名 -d unimp,guest_errors --gdb tcp::1234 \
--nographic --image プログラム名.elf \
--semihosting-config enable=on,target=native \
--semihosting-cmdline プログラム名
今回の対象ボードはNUCLEO-F103RBなので、ボード名にはNUCLEO-F103RB、マイコン名にはSTM32F103RBを設定します。
プログラム名はmbed_blinkyなので、実行するコマンドは以下になります。
$ /opt/gnuarmeclipse/qemu/bin/qemu-system-gnuarmeclipse \
--verbose --verbose --board NUCLEO-F103RB --gdb tcp::1234 \
--mcu STM32F103RB -d unimp,guest_errors \
--image mbed_blinky.elf \
--semihosting-config enable=on,target=native \
--semihosting-cmdline mbed_blinky
このコマンドはmbed_blinky.elfのあるディレクトリで行う必要があるため、
mbedからエクスポートしてきたプログラムをmakeした後、
mbed_blinky.elfの生成されている.buildディレクトリに移動してQEMUを起動します。
$ cd .build
$ /opt/gnuarmeclipse/qemu/bin/qemu-system-gnuarmeclipse \
--verbose --verbose --board NUCLEO-F103RB --gdb tcp::1234 \
--mcu STM32F103RB -d unimp,guest_errors \
--image mbed_blinky.elf \
--semihosting-config enable=on,target=native \
--semihosting-cmdline mbed_blinky
これで正しく動いていればNUCLEOの画像が表示されLEDの部分が点滅するはずです。
とりあえず今日はここまで。
おまけ: デバッガを繋いでみる
QEMU起動時に--gdb tcp::1234オプションをつけているので、TCP1234ポートにgdbでアタッチしてデバッグができます。
まず以下のコマンドでgdbを起動します。
$ arm-none-eabi-gdb mbed_blinky.elf
gdbが起動したら、QEMUのgdbサーバーに接続します。
(gdb) tar remote :1234
後は普通にデバッグするだけです。
おまけ: その他情報
LPC1768サポートを追加した方がいらっしゃるようです(当方未調査)。
myokota.hatenablog.jp
QEMUのFeaturesリストを見るとCortex-M0サポートなども予定してるみたいです。
Features/QOM/CPU - QEMU