Porting MicroPython

I suddenly thought of it, so I tried it.

Preparation

Bring the forked one on GitHub and make a branch.

git clone [email protected]:ysat0/micropython.git
cd micropython
git checkout -b rx

Get a rough overview. First of all, find the CPU-dependent part because it should work for the time being.

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

Wild intuition told me to look at ports, so I will look there.

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

It seems good to use minimal as a template, so copy it.

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

So I was able to put it on a cutting board.

Look

Now that we have a template, we will seriously investigate what happens to the CPU-dependent parts.

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

Some of them can be guessed by the file name, but I don't know where to fix it, so look inside and check the purpose.

file name Use Fix
Makefil Makefile To do
README.md Description Does not matter
frozentest.mpy Result of byte compilation of ↓ do not do
frozentest.py Test script do not do
main.c Initialization+main To do
mpconfigport.h Various settings To do
mphalport.h Definition of hardware-dependent functions You don't have to
qstrdefsport.h Special definition of qstr type do not do
stm32f405.ld LD script To do
uart_core.c UART input / output To do

You don't have to fix it so much.

Preparation of development environment

So, when I thought about fixing it, I noticed a serious problem that I did not prepare a development environment (because it is not usually for baremetal), so I will prepare it first. It uses a normal rx-elf toolchain (binutils + gcc + newlib), so build it yourself or use KPIT's.

Considering the target

I want to run it with qemu for the time being, so I will match it with the hardware emulating qemu. The specifications of qemu are

However, this time I will not use the external memory, but only the CPU built-in memory.

I will fix it

Fix it as shown in the table above. main.c The original code is included in main.c, such as reset processing, but the part written in assembler is easier to put in .s, so it is decomposed into crt0.s and 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

It is simply the flow of DATA / BSS initialization → hardware initialization → main. There are many more vector tables, but since they shouldn't be called if there is nothing, only the reset part is registered.

main.c


(Abbreviation)
#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;
}

Same as the original main.c except for hardware initialization. This is also the minimum initialization. There is no initialization in uart_core for some reason, so I initialize it here. By the way, writing in C is the reason why writing in assembler is troublesome.

mpconfigport Appropriate because I'm not sure which setting affects where. This is also the minimum necessary modification.

mpconfigport.h


(Abbreviation)
#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

There is ~ THUMB on the upper side, but it seems to be invalid, so I leave it as it is.

uart_core.c Responsible for low-level I / O for dialogue. As I wrote above, for some reason there is no initialization part, only the actual input / output.

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
}

This is hardware dependent, so I'm rewriting it. However, the usage of UART is almost the same, so it doesn't change so much.

ldscript As expected, stm32 ~ is confusing, so I will change the name. This is also hardware dependent, so I'll fix it. That said, it's not too much of a hassle as it just allocates text / data / bss to the internal memory properly. If you want to run it with R5F562N7, which is widely available in the world, you should fix FLASH and RAM appropriately.

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
}

By the way, the unique section name is the rx-elf specification. However, the fact that the response is halfway is troublesome.

Makefile Change build options or add crt0. I thought I'd do it, but I was asked for setjmp / longjmp, so I linked libc easily. If it was about setjmp, I could have prepared it myself, but it was troublesome to look up the definition of jmpbuf, so this happened.

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 [email protected] -L $(dir $(LIBC))
else
CFLAGS = -m32 $(INC) -Wall -Werror -std=c99 $(COPT)
LDFLAGS = -m32 -Wl,[email protected],--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

After that, I thought that deploy would be left, but I didn't need it separately for qemu, and I deleted it because there was no suitable writing program from make. I remembered that, but there is this, so if you really want to use it, please use it (promote it) Keep it).

Build

Obediently make cross=1 You can make it with.

Try to move

Now that it's done, let's move it.

qemu-system-rx -bios build/mpyton.bin


 Start with and switch the screen to the serial port![Start.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316355/1e891cb0-37a4-f9e6 -639a-5dc4a33328b5.png)
 Something is working, and if you enter it properly,![Print.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316355/076e4079-0cd8-8495 -7206-9a2098799ad2.png)
 I'll get a proper reply, so maybe it will work as it should.

# end
 So, if you move it for the time being, it's pretty easy to do.
 Maybe time to fix the code <time to prepare the development environment.
 I think I have to prepare more drivers so that I can use it properly, but I was satisfied with the place where it worked with qemu, so the future schedule is undecided.


Recommended Posts

Porting MicroPython
ARM Architecture »Porting