MicroPython 포팅하기

MicroPython 프로젝트는 다양한 마이크로컨트롤러 제품군과 아키텍처에 대한 여러 포트를 포함합니다. 프로젝트 저장소에는 지원되는 각 포트에 대한 하위 디렉터리를 포함하는 ports 디렉터리가 있습니다.

포트에는 일반적으로 여러 “보드”에 대한 정의가 포함되며, 각 보드는 해당 포트가 실행될 수 있는 특정 하드웨어(예: 개발 키트나 장치)입니다.

minimal port 는 MicroPython 포트의 단순화된 참조 구현으로 제공됩니다. 이는 호스트 시스템과 STM32F4xx MCU 모두에서 실행될 수 있습니다.

일반적으로 포트를 시작하려면 다음이 필요합니다:

  • 툴체인 설정(Makefile 구성 등).

  • 부팅 구성 및 CPU 초기화 구현.

  • 개발 및 디버깅에 필요한 기본 드라이버 초기화(예: GPIO, UART).

  • 보드별 구성 수행.

  • 포트별 모듈 구현.

최소 MicroPython 펌웨어

MicroPython을 새 보드에 포팅하기 시작하는 가장 좋은 방법은 최소한의 MicroPython 인터프리터를 통합하는 것입니다. 이 워크스루에서는 ports 디렉터리에 새 포트를 위한 하위 디렉터리를 만드십시오:

$ cd ports
$ mkdir example_port

기본 MicroPython 펌웨어는 메인 포트 파일(예: main.c)에 구현됩니다:

#include "py/builtin.h"
#include "py/compile.h"
#include "py/gc.h"
#include "py/mperrno.h"
#include "shared/runtime/gchelper.h"
#include "shared/runtime/pyexec.h"

// Allocate memory for the MicroPython GC heap.
static char heap[4096];

int main(int argc, char **argv) {
    // Initialise the MicroPython runtime.
    mp_cstack_init_with_sp_here(2048);
    gc_init(heap, heap + sizeof(heap));
    mp_init();

    // Start a normal REPL; will exit when ctrl-D is entered on a blank line.
    pyexec_friendly_repl();

    // Deinitialise the runtime.
    gc_sweep_all();
    mp_deinit();
    return 0;
}

// Handle uncaught exceptions (should never be reached in a correct C implementation).
void nlr_jump_fail(void *val) {
    for (;;) {
    }
}

// Do a garbage collection cycle.
void gc_collect(void) {
    gc_collect_start();
    gc_helper_collect_regs_and_stack();
    gc_collect_end();
}

// There is no filesystem so stat'ing returns nothing.
mp_import_stat_t mp_import_stat(const char *path) {
    return MP_IMPORT_STAT_NO_EXIST;
}

// There is no filesystem so opening a file raises an exception.
mp_lexer_t *mp_lexer_new_from_file(qstr filename) {
    mp_raise_OSError(MP_ENOENT);
}

이 시점에서 포트를 위한 Makefile도 필요합니다:

# Include the core environment definitions; this will set $(TOP).
include ../../py/mkenv.mk

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

# Set CFLAGS and libraries.
CFLAGS += -I. -I$(BUILD) -I$(TOP)
LIBS += -lm

# Define the required source files.
SRC_C = \
    main.c \
    mphalport.c \
    shared/readline/readline.c \
    shared/runtime/gchelper_generic.c \
    shared/runtime/pyexec.c \
    shared/runtime/stdout_helpers.c \

# Define source files containing qstrs.
SRC_QSTR += shared/readline/readline.c shared/runtime/pyexec.c

# Define the required object files.
OBJ = $(PY_CORE_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o))

# Define the top-level target, the main firmware.
all: $(BUILD)/firmware.elf

# Define how to build the firmware.
$(BUILD)/firmware.elf: $(OBJ)
    $(ECHO) "LINK $@"
    $(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
    $(Q)$(SIZE) $@

# Include remaining core make rules.
include $(TOP)/py/mkrules.mk

Makefile을 들여쓰기할 때는 올바른 탭을 사용하는 것을 잊지 마십시오.

MicroPython 구성

위의 최소 코드를 통합한 후 다음 단계는 포트를 위한 MicroPython 구성 파일을 만드는 것입니다. 컴파일 타임 구성은 mpconfigport.h 에 지정되며, 시간 관리와 같은 추가 하드웨어 추상화 함수는 mphalport.h 에 지정됩니다.

다음은 mpconfigport.h 파일의 예입니다:

#include <stdint.h>

// Python internal features.
#define MICROPY_ENABLE_GC                       (1)
#define MICROPY_HELPER_REPL                     (1)
#define MICROPY_ERROR_REPORTING                 (MICROPY_ERROR_REPORTING_TERSE)
#define MICROPY_FLOAT_IMPL                      (MICROPY_FLOAT_IMPL_FLOAT)

// Fine control over Python builtins, classes, modules, etc.
#define MICROPY_PY_ASYNC_AWAIT                  (0)
#define MICROPY_PY_BUILTINS_SET                 (0)
#define MICROPY_PY_ATTRTUPLE                    (0)
#define MICROPY_PY_COLLECTIONS                  (0)
#define MICROPY_PY_MATH                         (0)
#define MICROPY_PY_IO                           (0)
#define MICROPY_PY_STRUCT                       (0)

// Type definitions for the specific machine.

typedef long mp_off_t;

// We need to provide a declaration/definition of alloca().
#include <alloca.h>

// Define the port's name and hardware.
#define MICROPY_HW_BOARD_NAME "example-board"
#define MICROPY_HW_MCU_NAME   "unknown-cpu"

#define MP_STATE_PORT MP_STATE_VM

이 구성 파일에는 다양한 MicroPython 기능을 활성화할지 여부와 같은 측면을 포함한 머신별 구성이 포함되어 있습니다(예: #define MICROPY_ENABLE_GC (1)). 이를 (0) 으로 설정하면 해당 기능이 비활성화됩니다.

다른 구성에는 타입 정의, 루트 포인터, 보드 이름, 마이크로컨트롤러 이름 등이 포함됩니다.

마찬가지로, 최소한의 mphalport.h 파일 예는 다음과 같습니다:

static inline void mp_hal_set_interrupt_char(char c) {}

표준 입출력 지원

MicroPython은 최소한 문자를 출력하는 방법이 필요하며, REPL을 갖추려면 문자를 입력하는 방법도 필요합니다. 이를 위한 함수는 mphalport.c 파일에 구현할 수 있습니다. 예를 들면:

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

// Receive single character, blocking until one is available.
int mp_hal_stdin_rx_chr(void) {
    unsigned char c = 0;
    int r = read(STDIN_FILENO, &c, 1);
    (void)r;
    return c;
}

// Send the string of given length.
void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
    int r = write(STDOUT_FILENO, str, len);
    (void)r;
}

이러한 입력 및 출력 함수는 특정 보드 API에 따라 수정해야 합니다. 이 예제는 표준 입출력 스트림을 사용합니다.

빌드 및 실행

이 단계에서 새 포트의 디렉터리에는 다음이 포함되어야 합니다:

ports/example_port/
├── main.c
├── Makefile
├── mpconfigport.h
├── mphalport.c
└── mphalport.h

이제 make 를 실행하여 포트를 빌드할 수 있습니다(또는 시스템에 따라 다른 방법으로).

위에 제시된 Makefile의 기본 컴파일러 설정을 사용하는 경우 직접 실행할 수 있는 build/firmware.elf 라는 실행 파일이 생성됩니다. 작동하는 REPL을 얻으려면 먼저 터미널을 raw 모드로 구성해야 할 수도 있습니다:

$ stty raw opost -echo
$ ./build/firmware.elf

그러면 MicroPython REPL이 제공됩니다. 그런 다음 다음과 같은 명령을 실행할 수 있습니다:

MicroPython v1.26.0-preview on 2025-08-01; minimal with unknown-cpu
>>> def sum(n, m):
...     return n + m
...
>>> 3, 4, sum(3, 4)
(3, 4, 7)
>>>

종료하려면 Ctrl-D를 사용하고, 그런 다음 reset 을 실행하여 터미널을 재설정하십시오.

포트에 모듈 추가하기

myport 와 같은 사용자 정의 모듈을 추가하려면, 먼저 modmyport.c 파일에 모듈 정의를 추가하십시오:

#include "py/runtime.h"

static mp_obj_t myport_info(void) {
    mp_printf(&mp_plat_print, "info about my port\n");
    return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_0(myport_info_obj, myport_info);

static const mp_rom_map_elem_t myport_module_globals_table[] = {
    { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_myport) },
    { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&myport_info_obj) },
};
static MP_DEFINE_CONST_DICT(myport_module_globals, myport_module_globals_table);

const mp_obj_module_t myport_module = {
    .base = { &mp_type_module },
    .globals = (mp_obj_dict_t *)&myport_module_globals,
};

MP_REGISTER_MODULE(MP_QSTR_myport, myport_module);

또한 Makefile을 편집하여 modmyport.cSRC_C 목록에 추가하고, 동일한 파일을 SRC_QSTR 에 추가하는 새 줄을 추가해야 합니다(이 새 파일에서 qstr이 검색되도록). 다음과 같이:

SRC_C = \
    main.c \
    modmyport.c \
    mphalport.c \
    ...

SRC_QSTR += modmyport.c

모든 것이 올바르게 진행되었다면, 다시 빌드한 후 새 모듈을 import할 수 있어야 합니다:

>>> import myport
>>> myport.info()
info about my port
>>>