4
2

More than 3 years have passed since last update.

Porting MicroPython

Last updated at Posted at 2020-01-20

ふと思い立ったのでやってみました。

用意

GitHubでforkしたものを持ってきてブランチまで作ります。

git clone git@github.com:ysat0/micropython.git
cd micropython
git checkout -b rx

大まかな全体像を把握します。とりあえず動けばいいので、CPU依存部をまずは見つけます。

$ ls
ACKNOWLEDGEMENTS    CONTRIBUTING.md  docs/      extmod/  mpy-cross/  tests/
CODECONVENTIONS.md  LICENSE          drivers/   lib/     ports/      tools/
CODEOFCONDUCT.md    README.md        examples/  logo/    py/

野生の勘がportsを見ろと言ってきたので、そこを見ます。

$ ls ports
bare-arm/  esp8266/     nrf/       qemu-arm/  teensy/   zephyr/
cc3200/    javascript/  pic16bit/  samd/      unix/
esp32/     minimal/     powerpc/   stm32/     windows/          

minimalを雛形にするのが良さそうなので、コピーします。

$ mkdir ports/rx
$ cp -R ports/minimal/* ports/rx

ということでまな板にのせるところまで出来ました。

ながめる

雛形を用意したので、CPU依存部がどうなっているのかまじめに調べます。

$ ls ports/rx 
Makefile   frozentest.mpy  main.c          mphalport.h     stm32f405.ld
README.md  frozentest.py   mpconfigport.h  qstrdefsport.h  uart_core.c

ファイル名でなんとなく推測できるものもありますが、どこを直せばいいのかわからないので、中を見て用途を確認します。

ファイル名 用途 修正
Makefil メイクファイル する
README.md 説明 どうでもいい
frozentest.mpy ↓をバイトコンパイルした結果 しない
frozentest.py テスト用のスクリプト しない
main.c 初期化+main する
mpconfigport.h 諸々の設定 する
mphalport.h ハードウエア依存関数の定義 しなくても良さそう
qstrdefsport.h qstr型の特殊な定義 しない
stm32f405.ld LDスクリプト する
uart_core.c UART入出力 する

そんなに直さなくても良さそうです。

開発環境の用意

ということでちまちま直そうと思った所で、開発環境を用意していない(いつもはbaremetal用じゃないので)という重大な問題に気がついてしまったので、先に用意します。
普通のrx-elf toolchain(binutils + gcc + newlib)を使うので、自分でビルドするなり、KPITのを使うなりして下さい。

ターゲットの検討

とりあえずqemuで動かしたいので、qemuのエミュレートしているハードウエアに合わせます。
qemuの仕様は

  • CPU - R5F562N8
  • 外部メモリ - 0x0100000 から 16MByte
  • コンソール - SCI0

なのですが、今回外部メモリは使わないで、CPU内蔵メモリだけで動かします。

直していく

上の表にある通りに直していきます。

main.c

元のコードはリセット処理とかもmain.cに入っていますが、アセンブラで書く部分は素直に.sに入れたほうが簡単じゃないかいということで、crt0.sとmain.cに分解します。

crt0.s
        .global _start

        .section        ".vector","ax"
        .long   _start

        .text
_start: 
        mov     #_estack, r0
        mov     #_sdata,r1
        mov     #_edata,r3
        sub     r1,r3
        mov     #_etext,r2
        smovf
        mov     #_sbss,r1
        mov     #_ebss,r3
        sub     r1,r3
        mov     #0,r2
        sstr
        bsr     _rx62n_init
        sub     r1,r1
        sub     r2,r2
        bsr     _main
        bra     .
        .end

単純にDATA/BSSの初期化→ハードウエアの初期化→mainの流れです。
ベクタテーブルはもっといっぱいありますが、なにもなければ呼ばれないはずということで、リセットのところだけ登録してます。

main.c
()
#define SCKCR 0x00080020
#define MSTPCRB 0x00080014
#define SCR0 0x00088242
#define SMR0 0x00088240
#define BRR0 0x00088241

void rx62n_init(void) {
    /* CPG Initialize */
    /* XTAL:12MHz, ICLK:96MHz (x8), PCLK:48MHz (x4) */
    *((volatile unsigned long *)SCKCR) = 0x00010100;
    /* SCI0 enable */
    *((volatile unsigned long *)MSTPCRB) &= ~0x80000000;
    /* SCI0 Initialize */
    *((volatile unsigned char *)SCR0) = 0x00;
    *((volatile unsigned char *)SMR0) = 0x01;
    *((volatile unsigned char *)BRR0) = 39;     /* 9600 bps */
    *((volatile unsigned char *)SCR0) = 0x30;
}

ハードウエア初期化以外は元のmain.cと同じです。
ここも最低限の初期化です。なぜかuart_coreに初期化がないので、ここで初期化してます。
ちなみにCで書いているのは、アセンブラで書くのが面倒くさいというしょうもない理由です。

mpconfigport

どの設定がどこに影響するのかよくわからないので適当。
これも必要最小限の修正で済ませてます。

mpconfigport.h
()
#define MICROPY_HW_BOARD_NAME "minimal"
#define MICROPY_HW_MCU_NAME "Renesas RX"

#ifdef __linux__
#define MICROPY_MIN_USE_STDOUT (1)
#endif

#ifdef __RX__
#define MICROPY_MIN_USE_RX_CPU (1)
#endif

上の方に〜THUMBとかあるのですが、無効ぽいのでそのまま放置してます。

uart_core.c

対話用の低レベル入出力担当です。
上に書いた通りなぜか初期化部分がなくて、実際に入出力するところしかありません。

uart_core.c
#include <unistd.h>
#include "py/mpconfig.h"

/*
 * Core UART functions to implement for a port
 */

#if MICROPY_MIN_USE_RX_CPU
#define TDR0 0x00088243
#define SSR0 0x00088244
#define RDR0 0x00088245
#define SSR_TDRE (1 << 7)
#define SSR_RDRF (1 << 6)
#endif

// Receive single character
int mp_hal_stdin_rx_chr(void) {
    unsigned char c = 0;
#if MICROPY_MIN_USE_STDOUT
    int r = read(0, &c, 1);
    (void)r;
#elif MICROPY_MIN_USE_RX_CPU
    // wait for RDRF
    while ((*(volatile unsigned char *)SSR0 & SSR_RDRF) == 0) {
    }
    c = *(volatile unsigned char *)RDR0;
#endif
    return c;
}

// Send string of given length
void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
#if MICROPY_MIN_USE_STDOUT
    int r = write(1, str, len);
    (void)r;
#elif MICROPY_MIN_USE_RX_CPU
    while (len--) {
        // wait for TDRE
        while ((*(volatile unsigned char *)SSR0 & SSR_TDRE) == 0) {
        }
        *(volatile unsigned char *)TDR0 = *str++;
    }
#endif
}

ここはハードウエア依存なのでごっそり書き直しています。
といってもUARTの使い方はだいたい同じなのでそんなに変わらなかったりしますが。

ldscript

さすがにstm32〜じゃ混乱するので名前変えます。
これもハードウエア依存なのでごっそり直します。
と言ってもtext/data/bssを適切に内蔵メモリに割り当てるだけなので、そんなに面倒じゃないです。
世にたくさん出回ったR5F562N7で動かしたい場合は、FLASHとRAMのところを適当に直せばいいはず。

rx62n.ld
/*
    GNU linker script for RX62N
*/

/* Specify the memory areas */
MEMORY
{
    FLASH (rx)      : ORIGIN = 0xfff80000, LENGTH = 0x00080000 - 0x4 /* 512 KiB 
*/
    VECTOR (r)      : ORIGIN = 0xfffffffc, LENGTH = 0x0000004
    RAM (xrw)       : ORIGIN = 0x00000000, LENGTH = 0x00018000 /* 96 KiB */
}

/* top end of the stack */
_estack = ORIGIN(RAM) + LENGTH(RAM);

/* define output sections */
SECTIONS
{
    /* The program code and other data goes into FLASH */
    .text :
    {
        . = ALIGN(4);
        *(.text.startup)
        *(P)
        *(C)
        *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */

        . = ALIGN(4);
        _etext = .;        /* define a global symbol at end of code */
        _sidata = _etext;  /* This is used by the startup in order to initialize the .data secion */
    } >FLASH

    .vector :
    {
        *(.vector)
    } > VECTOR

    /* This is the initialized data section
    The program executes knowing that the data is in the RAM
    but the loader puts the initial values in the FLASH (inidata).
    It is one task of the startup to copy the initial values from FLASH to RAM. 
*/
    .data : AT ( _sidata )
    {
        . = ALIGN(4);
        _sdata = .;        /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */
        *(D*)          /* .data* sections */

        . = ALIGN(4);
        _edata = .;        /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */
    } >RAM

    /* Uninitialized data section */
    .bss :
    {
        . = ALIGN(4);
        _sbss = .;         /* define a global symbol at bss start; used by startup code */
        *(B*)
        *(COMMON)

        . = ALIGN(4);
        _ebss = .;         /* define a global symbol at bss end; used by startup code */
    } >RAM
}

ちなみにセクション名が独特なのはrx-elfの仕様です。が、その対応が中途半端なのが地味に面倒くさい。

Makefile

ビルドオプションを変えたり、crt0を追加したりします。
で済ませようと思ったのですが、setjmp/longjmpを要求されたので、安易にlibcもリンクしました。
setjmpくらいなら自前で用意しても良かったんですが、jmpbufの定義を調べたりするのが面倒くさいので、こうなりました

include ../../py/mkenv.mk

CROSS = 0
TARGET = mpython
# qstr definitions (must come before including py.mk)
QSTR_DEFS = qstrdefsport.h

# include py core make definitions
include $(TOP)/py/py.mk

ifeq ($(CROSS), 1)
CROSS_COMPILE ?= rx-elf-
endif

INC += -I.
INC += -I$(TOP)
INC += -I$(BUILD)

ifeq ($(CROSS), 1)
CFLAGS_RX = -fsingle-precision-constant -Wdouble-promotion
CFLAGS = $(INC) -Wall -Werror -std=c99 -nostdlib $(CFLAGS_RX) $(COPT)
LIBC =  $(shell $(CROSS_COMPILE)gcc -print-file-name=libc.a)
LDFLAGS = -T rx62n.ld -Map=$@.map -L $(dir $(LIBC))
else
CFLAGS = -m32 $(INC) -Wall -Werror -std=c99 $(COPT)
LDFLAGS = -m32 -Wl,-Map=$@.map,--cref -Wl,--gc-sections
endif

CSUPEROPT = -Os # save some code space

# Tune for Debugging or Optimization
ifeq ($(DEBUG), 1)
CFLAGS += -O0 -ggdb
else
CFLAGS += -Os -DNDEBUG
endif

LIBS = -l c

SRC_C = \
    main.c \
    uart_core.c \
    lib/utils/printf.c \
    lib/utils/stdout_helpers.c \
    lib/utils/pyexec.c \
    lib/libc/string0.c \
    lib/mp-readline/readline.c \
    $(BUILD)/_frozen_mpy.c

SRC_S = crt0.s

OBJ = $(PY_CORE_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) $(addprefix $(BUILD)/, $(SRC_S:.s=.o))

ifeq ($(CROSS), 1)
all: $(BUILD)/$(TARGET).bin
else
all: $(BUILD)/$(TARGET).elf
endif

$(BUILD)/_frozen_mpy.c: frozentest.mpy $(BUILD)/genhdr/qstrdefs.generated.h
    $(ECHO) "MISC freezing bytecode"
    $(Q)$(TOP)/tools/mpy-tool.py -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h -mlongint-impl=none $< > $@

$(BUILD)/$(TARGET).elf: $(OBJ)
    $(ECHO) "LINK $@"
    $(Q)$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
    $(Q)$(SIZE) $@

$(BUILD)/$(TARGET).bin: $(BUILD)/$(TARGET).elf
    $(Q)$(OBJCOPY) -O binary -j .vector -j .text -j .data $^ $@

$(BUILD)/$(TARGET).srec: $(BUILD)/$(TARGET).elf
    $(Q)$(OBJCOPY) -O srec -j .vector -j .text -j .data $^ $@

include $(TOP)/py/mkrules.mk

あとは、deployも残そうかなーと思ったんですが、qemuなら別にいらないし、makeから書込プログラムに適当なものがないよなーということでバッサリ消しました。
と書いていて思い出しましたが、こんなのがありますので、どうしてもという人は使ってみてください(と宣伝しておく)。

ビルド

素直に
make CROSS=1
で作れます。

動かしてみる

出来上がったので動かします。
qemu-system-rx -bios build/mpyton.bin
で起動して、画面をシリアルポートに切り替えるとstart.png
なんか動いているぽくて、適当に入力すると、print.png
ちゃんと返事が返ってくるので、たぶんそれなりには動くんじゃないかと。

おわり

ということで、とりあえず動かすならかなり簡単に出来てしまいました。
たぶんコードを直す時間<開発環境を用意する時間くらい。
まあちゃんと使えるようにするためにはドライバ類をもっと用意しないといけないと思いますが、qemuで動いた所で満足してしまったので、この先の予定は未定。

4
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2