การพอร์ต MicroPython

โปรเจกต์ MicroPython ประกอบด้วยพอร์ตหลายตัวสำหรับตระกูลไมโครคอนโทรลเลอร์และสถาปัตยกรรมต่าง ๆ repository ของโปรเจกต์มีไดเรกทอรี ports ที่มีไดเรกทอรีย่อยสำหรับแต่ละพอร์ตที่รองรับ

พอร์ตโดยทั่วไปจะมีการกำหนด "board" หลายรายการ แต่ละรายการเป็นฮาร์ดแวร์เฉพาะที่พอร์ตนั้นสามารถรันได้ เช่น development kit หรืออุปกรณ์

minimal port มีไว้เป็น reference implementation ที่เรียบง่ายของพอร์ต MicroPython สามารถรันได้ทั้งบนระบบ host และ MCU STM32F4xx

โดยทั่วไป การเริ่มต้นพอร์ตต้องการ:

  • การตั้งค่า toolchain (การกำหนดค่า Makefile เป็นต้น)

  • การนำ boot configuration และการเริ่มต้น 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

อย่าลืมใช้ tab ที่ถูกต้องในการเยื้อง Makefile

การกำหนดค่า MicroPython

หลังจากรวมโค้ดขั้นต่ำข้างต้นแล้ว ขั้นตอนต่อไปคือการสร้างไฟล์การกำหนดค่า MicroPython สำหรับพอร์ต การกำหนดค่าในเวลาคอมไพล์ระบุไว้ใน mpconfigport.h และฟังก์ชัน hardware-abstraction เพิ่มเติม เช่น การเก็บเวลา ระบุไว้ใน 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) จะปิดใช้งานฟีเจอร์นั้น

การกำหนดค่าอื่น ๆ ได้แก่ type definitions, root pointers, ชื่อบอร์ด, ชื่อไมโครคอนโทรลเลอร์ เป็นต้น

ในทำนองเดียวกัน ตัวอย่างไฟล์ mphalport.h ขั้นต่ำจะมีลักษณะดังนี้:

static inline void mp_hal_set_interrupt_char(char c) {}

การรองรับ standard input/output

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;
}

ฟังก์ชัน input และ output เหล่านี้ต้องได้รับการแก้ไขตาม board API เฉพาะ ตัวอย่างนี้ใช้ standard input/output stream

การสร้างและรัน

ณ ขั้นตอนนี้ ไดเรกทอรีของพอร์ตใหม่ควรประกอบด้วย:

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

ตอนนี้พอร์ตสามารถสร้างได้โดยการรัน make (หรืออื่น ๆ ขึ้นอยู่กับระบบของคุณ)

หากคุณใช้การตั้งค่าคอมไพเลอร์เริ่มต้นใน Makefile ที่ให้ไว้ข้างต้น จะสร้างไฟล์ executable ชื่อ build/firmware.elf ที่สามารถรันได้โดยตรง หากต้องการ REPL ที่ใช้งานได้ คุณอาจต้องกำหนดค่าเทอร์มินัลเป็น raw mode ก่อน:

$ 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.c ลงในรายการ SRC_C และเพิ่มบรรทัดใหม่ที่เพิ่มไฟล์เดียวกันลงใน SRC_QSTR (เพื่อให้ค้นหา qstrs ในไฟล์ใหม่นี้) ดังนี้:

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

SRC_QSTR += modmyport.c

หากทุกอย่างถูกต้อง หลังจากสร้างใหม่แล้ว คุณควรสามารถนำเข้าโมดูลใหม่ได้:

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