Thursday , 18 August 2022

Running micropyton on STM32Nucleo-F4

Everyone who knows me is also aware of the fact that my favorite programming language is Python. I wrote hundreds of thousands lines of code in Python, ranging from web apps to software running on embedded single board computers like the Raspberry PI. Due to its nature of high level and interpreted programming language, Python is still not present on true embedded microcontrollers, even if they are becoming always more powerful. For example, the STM32 platform is a really cost effective family of MCU considering the feature it implements.

But things are changing. Micropython is a really impressive effort from Damien George (and the community) to port Python 3.x to low cost MCU. μPython is not a simple one-to-one port of Python, but it's a complex effort to adapt the official CPython to hardware architectures with really low physical resources (especially RAM). The μPython project has also successfully raised for founds on kickstarter for two electronic boards: the PyBoard, a really simple prototyping board based on the STM32F4 family, and WiPy based on the CC3200 Wi-Fi chip from TI.

Being the PyBoard based on the STM32F4 family of MCU, and the HAL provided by ST, it's quite simple to adapt μPython to other boards with the same MCU, even to custom designed board. In this post I'll show you how to successfully compile μPython for the Nucleo-F401RE board, the one I've already used in my past tutorials. Since there is no official support for this board, I've forked the github repo and done all the necessary modifications. You can download it from my github account.

Assumptions and requirements

In this post I'll assume the following hardware and system requirements:

  • You have a STM32Nucleo-F401RE board.
  • You have a UNIX like based PC (both Linux and MacOS are welcome - It should be not too difficult to arrange my instructions to a Windows PC).
  • You have a fully working GCC toolchain installed in ~/STM32Toolchain/gcc-toolchain
    • you are free to use a different path, but arrange my instructions accordingly;
    • if you need to install a complete GCC/Toolchain, you can follow this guide.
  • You have successfully compiled OpenOCD (I suggest the latest 0.9.0 version) and properly installed it in ~/STM32Toolchain/openocd.

Quick guide for impatient guys

Before I describe the changes to the main μPython source, I'll show a really quick guide that allows you to flash your Nucleo board with μPython in less than 5 minutes. At the end of this procedure you'll be able to run the classical Python interactive console (also know as read–eval–print loop (REPL)) and start having fun with it. First of all, start cloning my micropython repo form github inside the ~/STM32Toolchain directory:

Next, enter inside the micropython/stmhal subdirectory and start compiling it with the following command:

After a while, you should obtain firmware binaries, as shown below:

WOW. The most is done 🙂

At this point, official μPython documentation follows showing you how to flash the PyBoard using DFU mode (this is the way STM32 MCUs - and all ARM based MCU in general - can be flashed without using additional circuitry on the final board). However, all the Nucleo boards provide an integrated ST-Link debugger. This means that there is no way (unless you decide to play with the pins exposed on the morpho connector) to directly program the target MCU using DFU protocol. For this reason, we'll use OpenOCD.

Without leaving the stmhal subdir, connect the Nucleo board to your PC and type the following command:

If all is ok, these messages will appear on the terminal:

Now that OpenOCD is connected to our Nucleo, we can transfer the generated binary to the target MCU. To do this, we need to send commands to OpenOCD via a telnet session.

Open another terminal console and type the following command:

We are now connected to OpenOCD and we can start uploading the firmware with these commands:

Ok. We have finished 🙂

To test μPython you need a terminal emulator, like Kermit or PicoCOM. In UNIX like systems, ST-Link Virtual COM Port is usually mapped with a device name similar to /dev/tty.usbmodem1a1313. Check exactly the name in your case. If you have kermit, you can type the commands shown in the following image.

Schermata 2015-06-01 alle 14.48.43

Now you can start having fun with Python and your Nucleo. To test if all works correctly, you can start turning ON the LD2 led (the green one) on the board using this instruction:

As you can see, it is really simple to port μPython to a Nucleo-F4 board. This is mainly thanks to the excellent work done from the μPython community. But if you are interested to the full story, you can continue reading this tutorial. The information I'll give can be really useful if you need to adapt μPython to a custom board based on STM32F4 platform.

How to adapt micropython to a different board

Let's me clarify once again: the process I'm going to describe is for STM32 based boards. For different architectures you have to play with μPython sources.

To adapt μPython sources to a custom board, you need essentially these steps:

  • Create a subdir inside the stmhal/boards with the name of your board (in our case, STM32F4NUCLEO).
  • Place inside that dir:
    • a file named mpconfigboard.h containing directive which describes your board (more next).
    • a file named containing link to Alternate Functions (AF) tables  and GNU LD configuration script.
    • a file named pins.csv with the list of used pins (this depends on the hardware layout of your board).
    • a file named stm32f4xx_hal_conf.h which contains the definition and import declarations for STM32Cube HAL (the one generated from STM32CubeMX tool should be sufficient).

Let's analyze in depth these files.

The file mpconfigboard.h says to μPython more about the board capabilities. This file is essentially composed by #define directives, which enable and configure the stmhal for your specific board. In the case of STM32NucleoF4 i used the following definitions:

The differences between Nucleo and PyBoard are mainly a lower number of user LEDs (just one in the case of Nucleo), different UARTs (by default, the Nucleo VCP is associated to UART2), a different configuration for user switch (the blue one on Nucleo) and no external SD card support. This is a non-trivial aspect for use μPython on Nucleo, since there is no quick way to upload python source files like in PyBoard. For this reason, we'll have to enable interactive console (REPL) adding these two macros inside the file mpconfigboard.h: :

How to debug micropython

During the port of μPython to a different board it could be really useful to do a live debug of the uploaded firmware. This happened to me and I think that this is a really common situation. To do a step-by-step debugging you can use GDB in conjunction with OpenOCD. Before to debug μPython, you need to compile it with debug symbols. This is done with the following commands:

However, I had to modify a couple of things to allow debugging μPython on my Nucleo. The PyBoard MCU has 1Mb of flash, while the Nucleo MCU has only 512kb. So i had to modify the Makefile at line 57 changing the "COPT = -O0" to "COPT = -O1" (this enables first compiler optimizations level reducing dramatically the binary dimension). Moreover, I had to increase the FLASH_ISR region from 16k to 32k (two blocks of 16k) inside the GNU Linker script file (stm32f401.ld), as shown below:

When the firmware binary is generated, you can upload and debug it using GDB and OpenOCD. To do this, start GDB with the following command:

When invoking GDB in this way, we are doing three things:

  • --tui starts GDB with Text User Interface, a mode that allows to see source code while debugging;
  • --eval-command="target remote localhost:3333" automatically connect GDB to OpenOCD using port 3333 (the port where OpenOCD waits for connection from GDB)
  • build-STM32F4NUCLEO/firmware.elf says to GDB to load the firmware binary (both code and debug symbols).

Ok. Now we can upload the firmware to Nucleo MCU in the same way we did before:

Once completed, we can place a breakpoint at main() function and start execution:

The following screen shot shows the GDB interface while debugging:

Schermata 2015-06-02 alle 07.36.03

To do a step-by-step debugging you can use classical GDB commands like s(tep) to do step-in and n(ext) to do a step-out debugging.

Programming with micropython

As I said before, STM32Nucleo doesn't provide support to external SD card like PyBoard does. This means that there is no direct way to upload source .py files and check for execution. There are only two options that came out from the box with standard μPython source tree. The first one is to use the REPL. However, this is quite inconvenient if you want to do serious things. The second choice is to use a convenient interface available in μPython that allows to remote execute commands on the target board from a PC. This tool is named pyboard and it's available inside the tools subdirectory in μPython main tree.

pyboard it's really simple to use. It can be used both as a library and a command line tool. For example, suppose that you have created a source file named and you want to execute it on the Nucleo. You can invoke pyboard in this way:

Every time a file is transferred on the PyBoard, μPython does a soft reset. This option is really useful during the development process. However, this requires uploading again the program instructions every time the board is hard-rebooted.

I introduced a third way to program our Nucleo board with μPython: a file transfer mode.

Transfer .py files to internal flash memory

Every time we flash our Nucleo board with μPython, the whole flash is erased. On the first boot after flash programming, μPython recreates an internal FAT filesystem mounted as /flash. μPython also creates a file named and another one called This task is accomplished by the  init_flash_fs() function, inside the stmhal/main.c file:

What I've implemented is really simple. I've added another mode available in the REPL. Hitting CTRL+F puts μPython in file transfer mode. μPython waits for the name of the file, followed by CTRL+A character. When CTRL+A is sent to the REPL, μPython opens for creation the file in the /flash partition and starts waiting for all characters contained in the file, until another CTRL+A is sent to the REPL. When this happens, μPython close the file and starts waiting for another file name. μPython exits from this loop when a CTRL+C char is sent, and performs a soft reset.

So, let's recap the procedure:

  1. Sending CTRL+F to REPL puts μPython in file transfer mode;
  2. μPython waits for the name of the file we want to create or overwrite;
  3. when finished, a CTRL+A puts μPython waiting for the content of the file;
  4. for each char sent on the console, μPython places it inside the file until a CTRL+A is sent;
  5. μPython return to point 2 until a CTRL+C is sent.

In this way is possibile to save the python program inside the Nucleo flash. I've also implemented a way to delete files, simply adding a "-" before the file name. For example, to delete the file simply write when prompted for filename.

To simplify the file upload, I've modified the file tools/ To transfer a list of file to Nucleo, simply run:

Instead, to delete a file run:

If you are curious about what I've done, you can do a git diff between my repo and the official one. However, the most of the job is done by the pyexec_file_upload() function in pyexec.c file.

Check Also

Correct way to perform re-annotation of designators in Altium

It's really common that at the end of the board layout we have that all …


  1. Ciao Carmine,

    I just came by chance across your post about porting MicroPython to STM32 Nucleo-F4. Very interesting, and it provides several answers to interrogations I had right now.

    A few words about the context: I have developed a module for prototyping smart objects and more, which I plan to launch on kickstarter soon ( I'm more a hardware guy (although I do program in C), but friends tell me Python is really cool and I'd love to support it. The MCU I use is the new STM32-L4. Since it's based on Cortex-M4 I'd been assuming a porting should be doable. Your post confirms this and provides interesting insights about the way to go.

    I was wondering if you would be ok to go a little bit further into that subject with me. Please mail me if so.
    Grazie !


  2. I have managed to build the hex file for thE stm32 nucleo but when I flash it it using STM32 Link utility, nothing appears to happen. I get no prompts out the serial port. (I assume it is 115200). You you have a HEX or bin file that you know works??

  3. Thanks for the update. Yes I am using a Nucleo-F401RE. I downloaded your elf file and converted it to a bin file using

    arm-none-eabi-objcopy -O binary micropython-nucleo-f401re.elf micropython-nucleo-f401re.bin

    I then programmed the bin file using STM32 st-link utility starting at address 0x08000000. After restart I still see nothing out the usb serial port of the of Nucleo-F401RE.

    What should I expect to see out the serial port? Is the baud 115200?

    I do know this board works as I have programmed other applications on it including using the serial port.


  4. Thanks. I really appreciate you help. But the hex file did not work either. Nothing comes out the serial port. I am using Hyperterminal for the serial port program, set to 115200, but no matter how long I wait I get no prompt in Hyperterminal. I even created an SREC file from your ELF file, hoping that would work. I didn't try debugging as you mention above as I do not have openOCD.

    Is the problem I am having due to the ST Link Utility?? Is this the incorrect way of programming the nucleo board?

  5. Thanks. I downloaded the new hex file but it is identical to the first one.

    • Sorry, use this other ZIP file containing the HEX:

      • Thanks for the new file, but this version does not flash any LEDs nor have REPL appear on USB Com Port.

      • Was going to order a pyboard, just stumbled on your work today and I have 2 NUC 401 board on hand. Just thought I would mention, the fastest way to get it running on Windows, it to use the STM DeFuSe Demo, connect to the NUC, load the hex binary, then use program and verify. Already had DeFuSe so it took about 2minutes to get up and running. Again, neat project! I may following your post make a target definition one of the NUCLEO144 boards I have.

  6. hi i am getting this error

    Open On-Chip Debugger 0.9.0 (2015-10-17-17:36)
    Licensed under GNU GPL v2
    For bug reports, read
    Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
    adapter speed: 2000 kHz
    adapter_nsrst_delay: 100
    none separate
    srst_only separate srst_nogate srst_open_drain connect_deassert_srst
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    Info : clock speed 1800 kHz
    Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
    Error: open failed
    in procedure 'init'
    in procedure 'ocd_bouncer'

    • This means that you already have an open connection to the nucleo board via another debugger or the ST--Link Utility program

  7. Hi Carmine,

    Thank you for very good job, I've successfully programmed .hex in my NucleoF401 and all work perfectly.
    I've started to play with REPL (Windows with Putty on STLinkV2 VCP).

    My question is how can edit to use your file transfert (CTRL+F - CTRL+A) method ?

    Thank you.

  8. Hi Carmine,

    Loved your book! Trying to develop my python scripting skills so that I can get an entry level position as an embedded systems engineer. I am a complete beginner when it comes to python in embedded systems, but I have gotten up to the point of writing scripts and using ./ to load them onto the STM32F446RE, where they run successfully.

    1 - When I use ' pyb.LED(1).on() ', what python library am I accessing, and what C library does this library access?
    2 - How do I write my own python libraries and link them to C libraries?

    If this is too big of a question for a blog post answer, it would be great if you could point me to some literature!

    Thank you,


  9. Hi, Carmine Noviello

    I have an issue with this part:

    msadr471@Mohammad:/mnt/c/Users/Mohammad/Desktop/micropython/stmhal$ PATH=~/STM32Toolchain/gcc-toolchain/bin:$PATH BOARD=STM32F4NUCLEO make Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity. Create build-STM32F4NUCLEO/genhdr/pins.h Create stmconst build-STM32F4NUCLEO/modstm_qstr.h GEN build-STM32F4NUCLEO/genhdr/qstrdefs.generated.h /bin/sh: 1: arm-none-eabi-gcc: not found Traceback (most recent call last): File "../py/", line 108, in do_work(sys.argv[1:]) File "../py/", line 83, in do_work cfg_bytes_len = int(qcfgs['BYTES_IN_LEN']) KeyError: 'BYTES_IN_LEN' make: *** [../py/ build-STM32F4NUCLEO/genhdr/qstrdefs.generated.h] Error 1 make: *** Deleting file 'build-STM32F4NUCLEO/genhdr/qstrdefs.generated.h'


Leave a Reply to Carmine Noviello Cancel reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.