1. Introduction
MicroPython is a compact and efficient implementation of Python 3, containing a subset of the optimized Python standard library that can run on microcontrollers and other resource-constrained platforms. MicroPython also supports several advanced features, such as interactive prompts, arbitrary precision integers, closures, list comprehensions, generators, exception handling, etc. Its implementation is very small, capable of running on platforms with only 256k of code space and 16k of memory, making it especially suitable for porting to MCUs.
The design of MicroPython aims to be as compatible as possible with standard Python, allowing easy transfer of code from desktop platforms to run on microcontrollers or embedded systems. Personally, I believe that porting MicroPython to one’s own platform for automated validation testing is a very good application. After setting up the MicroPython environment, colleagues in verification and testing can directly write Python scripts for validation tests without needing software engineers’ full cooperation, thus improving collaboration efficiency.
The official provides various adaptations for pyboard, which can directly compile corresponding firmware for use.
2. Obtaining the Source Code
Download the source code git clone https://github.com/micropython/micropython.git

MicroPython has its own build method, which defaults to adapting to some platforms. A more general approach is to use its source code to port it to our own projects.
To understand the code framework, you can first build it on an officially supported platform to learn about the build process, which will help you know which source codes need to be compiled and what processes are involved.
You can directly use vs
to open micropython\mpy-cross\mpy-cross.vcxproj
to compile the compiler.
Then open micropython\ports\windows\micropython.vcxproj
to compile the interpreter.
Understand its project architecture. Note that before building, you need to cancel the read-only attributes of all files.
2. Add Source Code
We will port a minimal implementation, temporarily not adding external modules, only using some built-in modules. The overall code framework is as follows

Py Source Code
The py
directory contains the core code of the MicroPython interpreter, which we will copy to our own project directory.
Copy all c
files under it to our own project.
And add the py
path to the header file include path.
The following files should be selected according to the platform; here I choose asmthumb.c
for the ARM CORTEX-M series, and the others are not needed.
asmarm.c
asmrv32.c
asmthumb.c
asmx64.c
asmx86.c
asmxtensa.c
Shared Source Code
Temporarily only add the following content; others can be added as needed later.
Shared/runtime/pyexec.c pyexec.h
Shared/readline/readline.c readline.h
extmod Source CodeSource Code
Temporarily only add the following content; others can be added as needed later.
modplatform.h
virtpin.h
py_port Source Code
Then create a porting folder py_port
. We reference micropython\ports\minimal
for porting.
Copy the files from micropython\ports\minimal
to our py_port
path.
Add py_port
to our project directory.
The files are as follows, where uart_core.c
implements the serial input/output interface, mpconfigport.h
is the configuration file, mphalport.h
is some HAL layer interface implementation, py_main
is the interpreter entry point, and qstrdefsport.h
is for qstrs specific to this port.
The unistd.h
is a substitute for a Unix-like platform.

Py_main.c
Change py_port/main.c
to py_port/py_main.c
.
It is implemented based on stm32
or pc
host, and we need to make some modifications according to our platform.
Remove the content of #if MICROPY_MIN_USE_STM32_MCU
and #if MICROPY_MIN_USE_CORTEX_CPU
because we are using our platform.
The main
function name should be changed to py_main
.
Then call py_main
at the appropriate place in your platform.
Ensure it can compile first, and we will look at how to implement py_main
later.
Uart_core.c
We need to implement the following interfaces, which we will look at later.
#include "py/mpconfig.h" /* * Core UART functions to implement for a port */ // Receive single characterint mp_hal_stdin_rx_chr(void) { unsigned char c = 0; return c;} // Send string of given lengthmp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { mp_uint_t ret = len; return ret;} void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len){} void mp_hal_stdout_tx_str(const char *str){}
Unistd.h
Defines ssize_t
and some other macros.
#ifndef MICROPY_INCLUDED_UNISTD_H#define MICROPY_INCLUDED_UNISTD_H typedef int ssize_t;#define F_OK 0#define W_OK 2#define R_OK 4 #define STDIN_FILENO 0#define STDOUT_FILENO 1#define STDERR_FILENO 2 #define SEEK_CUR 1#define SEEK_END 2#define SEEK_SET 0 #endif
mphalport.h
HAL layer interface
static inline mp_uint_t mp_hal_ticks_ms(void) { return 0;}static inline void mp_hal_set_interrupt_char(char c) {}
qstrdefsport.h
// qstrs specific to this port// *FORMAT-OFF*
mpconfigport.h
Configuration file, which is very important. Pay attention to whether the MICROPY_PY_XXX
macros are enabled for certain modules.
#include <stdint.h> // options to control how MicroPython is built // Use the minimal starting configuration (disables all optional features).#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_MINIMUM) // You can disable the built-in MicroPython compiler by setting the following// config option to 0. If you do this then you won't get a REPL prompt, but you// will still be able to execute pre-compiled scripts, compiled with mpy-cross.#define MICROPY_ENABLE_COMPILER (1) //#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool#define MICROPY_HELPER_REPL (1)#define MICROPY_MODULE_FROZEN_MPY (0)#define MICROPY_ENABLE_EXTERNAL_IMPORT (0) #define MICROPY_PY_IO (0)#define MICROPY_PY_IO_IOBASE (1)#define MICROPY_PY_ERRNO (1)#define MICROPY_PY_MATH (1)#define MICROPY_PY_CMATH (1)#define MICROPY_PY_BUILTINS_FLOAT (1)#define MICROPY_PY_BUILTINS_COMPLEX (1) #define MICROPY_PY_GC (1)#define MICROPY_ENABLE_GC (1)#define MICROPY_PY_SYS (1)#define MICROPY_PY_MICROPYTHON (1)#define MICROPY_USE_INTERNAL_PRINTF (0)#define MICROPY_PY_ARRAY (1)#define MICROPY_PY_BUILTINS_BYTEARRAY (1)#define MICROPY_PY_BUILTINS_MEMORYVIEW (1)#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE)#define MICROPY_ALLOC_PATH_MAX (256) // Use the minimum headroom in the chunk allocator for parse nodes.#define MICROPY_ALLOC_PARSE_CHUNK_INIT (16) // type definitions for the specific machine typedef intptr_t mp_int_t; // must be pointer sizetypedef uintptr_t mp_uint_t; // must be pointer sizetypedef long mp_off_t; // We need to provide a declaration/definition of alloca()#include <alloca.h> #define MICROPY_HW_BOARD_NAME "minimal"#define MICROPY_HW_MCU_NAME "unknown-cpu" #define MICROPY_HEAP_SIZE (2048) // heap size 2 kilobytes #define MP_STATE_PORT MP_STATE_VM#define MICROPY_USE_INTERNAL_ERRNO 1
4. Porting
Standard Library Dependencies
The standard libraries required are as follows:
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdarg.h>
#include <math.h>
#include <limits.h>
#include <unistd.h> which we implement ourselves in py_port
.
If there is no #include <errno.h>
, then configure MICROPY_USE_INTERNAL_ERRNO
to be 1
.
#include <alloca.h> This is supported by the compiler.
Generating genhdr
We can build from other already built sources or use Windows samples to build first, then copy the corresponding header files.
moduledefs.h defines some module information. Since we are not using the official build process, it may not be automatically updated, so we can manually modify and reduce certain modules. Later we will manually call the corresponding scripts in MDK’s project configuration at the appropriate time.
mpversion.hqstrdefs.generated.hroot_pointers.h
The generation process refers to ports\windows\msvc\genhdr.targets
.
which contains commands like:
<Exec Command=”type $(QstrDefsCollected) >> $(DestDir)qstrdefs.preprocessed.h”/>
<Exec Command=”$(PyPython) $(PySrcDir)makeqstrdata.py $(DestDir)qstrdefs.preprocessed.h > $(TmpFile)”/>
where the specified file is qstrdefscollected.h
.
<QstrDefsCollected>$(DestDir)qstrdefscollected.h</QstrDefsCollected>
This file is generated by the following command:
<Exec Command=”$(PyPython) $(PySrcDir)makeqstrdefs.py split qstr $(DestDir)qstr.i.last $(DestDir)qstr _”/>
<Exec Command=”$(PyPython) $(PySrcDir)makeqstrdefs.py cat qstr _ $(DestDir)qstr $(QstrDefsCollected)”/>
Corresponding to the commands below:
makeqstrdata.py is located in the py
directory. Open the command line here:
First execute:
python makeqstrdefs.py split qstr qstr.i.last qstr _
python makeqstrdefs.py cat qstr _ qstr qstrdefscollected.h
Then execute:
type qstrdefscollected.h >> qstrdefs.preprocessed.h
Then execute:
python makeqstrdata.py qstrdefs.preprocessed.h >qstrdefs.generated.h.tmp
Adapting Serial Ports
In py_port/uart_core.c
, remove:
#include <unistd.h>
and add our own serial interface:
#include “uart.h”
Remove:
#if MICROPY_MIN_USE_STM32_MCU
#endif
#if MICROPY_MIN_USE_STDOUT
#elif MICROPY_MIN_USE_STM32_MCU
#endif
the content, which implements blocking read of one character mp_hal_stdin_rx_chr
and blocking send string interface mp_hal_stdout_tx_strn
, mp_hal_stdout_tx_strn_cooked
, mp_hal_stdout_tx_str
. The serial implementation can refer to the article, which adopts FIFO.
https://mp.weixin.qq.com/s/vzjWu2LxpVGZw-msCooh8Q?token=1312261758&lang=zh_CN Super Simplified Series 16: Serial Driver Based on IO Simulation + FIFO:
Uart.c is as follows:
#include <stdio.h>#include <stdbool.h>#include "uart.h"#include "fifo.h"#include <stdio.h> #include "fr30xx.h" #define CriticalAlloc()#define EnterCritical() __disable_irq()#define ExitCritical() __enable_irq() static uint8_t s_uart_rx_buffer[64]; static fifo_st s_uart_fifo_dev={ .in = 0, .len = 0, .out = 0, .buffer = s_uart_rx_buffer, .buffer_len = sizeof(s_uart_rx_buffer),}; volatile bool g_data_transmit_flag = false;uint8_t rx_buffer[1]; void uart_rx_cb(uint8_t* buff, uint32_t len){ fifo_in(&s_uart_fifo_dev, buff, len);} uint32_t uart_send(uint8_t* buffer, uint32_t len){ g_data_transmit_flag = false; for(uint32_t i=0;i<len;i++) { putchar(buffer[i]); } return len;} uint32_t uart_read(uint8_t* buffer, uint32_t len){ uint32_t rlen; CriticalAlloc(); EnterCritical(); rlen = fifo_out(&s_uart_fifo_dev, buffer, len); ExitCritical(); return rlen;}
#ifndef UART_H#define UART_H #ifdef __cplusplusextern "C" {#endif #include <stdint.h> uint32_t uart_send(uint8_t* buffer, uint32_t len);uint32_t uart_read(uint8_t* buffer, uint32_t len);void uart_rx_cb(uint8_t* buff, uint32_t len); #ifdef __cplusplus}#endif #endif
#include <string.h>#include "fifo.h" #define FIFO_PARAM_CHECK 0 /** * in????? 0~(buffer_len-1)? * out????? 0~(buffer_len-1)? * in == out?????,?????,????len?????????? * ???in??,?????out??? * ????out??,?????in??? * in??out??[out,in)???????? * in??out??[out,buffer_len)?[0,in)???????? *********************************************************** * 0 buffer_len-1 buffer_len * (1)?? in?out??0 * | | * in(0) * out(0) * len = 0 * (2)??n???? in??n?out??0 ??in??out??? * | | * out(0)————————————>in(n) | * len = n * (3)??m????(m<n) in??n?out??m ??in??out??? * | | * out(m)————>in(n) * len = n-m * (4)??????,?????,??in??out??? * | | * out(m)————————————————————————————————> * ——>in(k) * len = k + buffer_len-m */uint32_t fifo_in(fifo_st* dev, uint8_t* buffer, uint32_t len){ uint32_t space = 0; /* ?????????? */ /* ???? */ #if FIFO_PARAM_CHECK if((dev == 0) || (buffer == 0) || (len == 0)) { return 0; } if(dev->buffer == 0) { return 0; } #endif /* ??len??????buffer?? */ if(len > dev->buffer_len) { len = dev->buffer_len; } /* ???????? * ??dev->len?????dev->buffer_len */ if(dev->buffer_len >= dev->len) { space = dev->buffer_len - dev->len; } else { /* ???????, ?????? */ dev->len = 0; space = dev->buffer_len; } /* ???????, ??len???????????????? */ len = (len >= space) ? space : len; if(len == 0) { return 0; /* ??????????,???? */ } /* ??len??????????,?????? */ space = dev->buffer_len - dev->in; /* ??????in???????????? */ if(space >= len) { /* ??????in??????????? */ memcpy(dev->buffer+dev->in,buffer,len); } else { /* ??????in???????,????????? */ memcpy(dev->buffer+dev->in,buffer,space); /* ???tail?? */ memcpy(dev->buffer,buffer+space,len-space); /* ???????? */ } /* ????????????? */ dev->in += len; if(dev->in >= dev->buffer_len) { dev->in -= dev->buffer_len; /* ????? ?? dev->in %= dev->buffer->len */ } dev->len += len; /* dev->len??dev->buffer->len,??%= dev->buffer->len */ return len;} uint32_t fifo_out(fifo_st* dev, uint8_t* buffer, uint32_t len){ uint32_t space = 0; /* ???? */ #if FIFO_PARAM_CHECK if((dev == 0) || (buffer == 0) || (len == 0)) { return 0; } if(dev->buffer == 0) { return 0; } #endif /* ??????? */ if(dev->len == 0) { return 0; } /* ?????????????????? */ len = (dev->len) > len ? len : dev->len; /* ??len??????????,?????? */ space = dev->buffer_len - dev->out; /* ??????out???????????? */ if(space >= len) { /* ??????out??????????? */ memcpy(buffer,dev->buffer+dev->out,len); } else { /* ??????out???????,????????? */ memcpy(buffer,dev->buffer+dev->out,space); /* ???tail?? */ memcpy(buffer+space,dev->buffer,len-space); /* ???????? */ } /* ????????????? */ dev->out += len; if(dev->out >= dev->buffer_len) { dev->out -= dev->buffer_len; /* ????? ?? dev->out %= dev->buffer->len */ } dev->len -= len; /* ??dev->len ?????len,???? */ return len;} uint32_t fifo_getlen(fifo_st* dev){ #if FIFO_PARAM_CHECK if(dev == 0) { return 0; } #endif return dev->len;} void fifo_clean(fifo_st* dev){ #if FIFO_PARAM_CHECK if(dev == 0) { return 0; } #endif dev->len = 0; dev->in = 0; dev->out = 0;}
#ifndef FIFO_H#define FIFO_H #ifdef __cplusplusextern "C" {#endif #include <stdint.h> /** * ruct fifo_st * FIFO?????. */typedef struct { uint32_t in; /**< ???? */ uint32_t out; /**< ???? */ uint32_t len; /**< ?????? */ uint32_t buffer_len; /**< ???? */ uint8_t* buffer; /**< ??,???? */ } fifo_st; /** * n fifo_in * ?fifo???? * ypedef[in] dev ef fifo_st * ypedef[in] buffer ?????? * ypedef[in] len ?????? * etval ?????????? */uint32_t fifo_in(fifo_st* dev, uint8_t* buffer, uint32_t len); /** * n fifo_out * ?fifo???? * ypedef[in] dev ef fifo_st * ypedef[in] buffer ?????? * ypedef[in] len ????????? * etval ?????????? */uint32_t fifo_out(fifo_st* dev, uint8_t* buffer, uint32_t len); uint32_t fifo_getlen(fifo_st* dev); void fifo_clean(fifo_st* dev);#ifdef __cplusplus}#endif #endif
The final uart_core.c
implementation is as follows:
#include <string.h>#include "py/mpconfig.h" /* * Core UART functions to implement for a port */ // Receive single characterint mp_hal_stdin_rx_chr(void) { unsigned char c = 0; while(0 == uart_read(&c,1)) { }; return c;} // Send string of given lengthmp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { mp_uint_t ret = len; uart_send((uint8_t*)str,len); return ret;} void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len){ uart_send((uint8_t*)str,len);} void mp_hal_stdout_tx_str(const char *str){ uart_send((uint8_t*)str,strlen(str));}
Py_main Entry
In py_main.c
:
Set MICROPY_ENABLE_GC
to 0
.
Set MICROPY_ENABLE_COMPILER
to 1
.
Set MICROPY_REPL_EVENT_DRIVEN
to 0
.
The execution process is as follows:
mp_init();->pyexec_friendly_repl();
int py_main(int argc, char **argv) { int stack_dummy; stack_top = (char *)&stack_dummy; #if MICROPY_ENABLE_GC gc_init(heap, heap + sizeof(heap)); #endif mp_init(); #if MICROPY_ENABLE_COMPILER #if MICROPY_REPL_EVENT_DRIVEN pyexec_event_repl_init(); for (;;) { int c = mp_hal_stdin_rx_chr(); if (pyexec_event_repl_process_char(c)) { break; } } #else pyexec_friendly_repl(); #endif // do_str("print('hello world!', list(x+1 for x in range(10)), end='eol\n')", MP_PARSE_SINGLE_INPUT); // do_str("for i in range(10):\r\n print(i)", MP_PARSE_FILE_INPUT); #else pyexec_frozen_module("frozentest.py", false); #endif mp_deinit(); return 0;}
Create a thread to run the py_main
function.
5. Testing
After running, we see the following output:

Perform a simple calculation:

Using alloca.h
to allocate resources from the stack, so be careful to configure the stack size larger.
6. Summary
Some online materials can at most be considered as building MicroPython, but not porting. Here, I share the complete process of porting MicroPython source code to our own project from scratch. This can help understand what files are necessary for a minimal MicroPython port and its basic architecture. Later, we will introduce the addition of external modules, which is the most substantial work of porting, and to make it practical, it is essential to port various platform-related resource modules.