Run MicroPython on ESP8266

4 minute read

MicroPython is a python implementation aimed to be running on MCUs such as ESP8266/ESP32, stm32 etc, all the ports can be found here, the following will only focused on ESP8266 module.

Although MicroPython can run on modules with a minimum flash size of 512KB, the recommended flash size is 1MB and above, as with 512KB build, there is no file system support, and the features depending on it such as WebREPL and upip will not work.

Environment Setup

The recommended way to build MicroPython is use a docker image, I’m gonna try the native way, install esp-open-sdk, which is described in MicroPython port to ESP8266.

Follow the instructions in README.md of esp-open-sdk to build the toolchain, before building the esp sdk, the following packages need to be installed:

$ sudo apt install -y help2man libtool-bin

Build SDK

Clone the esp-open-sdk repository:

$ git clone --recursive https://github.com/pfalcon/esp-open-sdk.git

The toolchain was generated with crosstool-NG, the required tarballs will be downloaded during the build, with an exception of newlib-2.0.0 which URL was broken, and need to download manually:

$ cd crosstool-NG/.build/tarballs
$ wget -c ftp://sourceware.org/pub/newlib/newlib-2.0.0.tar.gz

Simply do a make and wait for its completion, this process takes a few minutes.

You will see the following message at the end:

Xtensa toolchain is built, to use it:

export PATH=/opt/workdir/esp-open-sdk/xtensa-lx106-elf/bin:$PATH

Espressif ESP8266 SDK is installed, its libraries and headers are merged with the toolchain

Append the export line to ~/.oh-my-zsh/custom/path.zsh.

Build MicroPython for ESP8266

Clone micropython, submodules and build:

$ git clone --depth=1 https://github.com/micropython/micropython.git
$ make -C ports/esp8266 submodules
$ make -C mpy-cross
$ cd ports/esp8266
$ make

The firmware info will be dumped at the end of the build process:

[...]
LINK build-GENERIC/firmware.elf
   text	   data	    bss	    dec	    hex	filename
 620460	   1012	  66376	 687848	  a7ee8	build-GENERIC/firmware.elf
Create build-GENERIC/firmware-combined.bin
esptool.py v1.2
flash     32896
 .text    30764 at 0x40100000
 .data    1012 at 0x3ffe8000
 .rodata  1080 at 0x3ffe8400
padding   3968
irom0text 588616
total     625480
md5       1d6b745f49a0158a26a989ce529f2d06

Flash MicroPython to ESP8266

# Erase entire Flash before write
$ esptool.py erase_flash
$ esptool.py --baud 230400 write_flash -fs 4MB -fm dio -ff 80m \
    0x00000 build-GENERIC/firmware-combined.bin
esptool.py v3.0
Found 2 serial ports
Serial port /dev/ttyUSB2
Connecting....
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: 48:3f:da:49:c7:6d
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 230400
Changed.
Configuring flash size...
Flash params set to 0x024f
Compressed 624676 bytes to 411637...
Wrote 624676 bytes (411637 compressed) at 0x00000000 in 18.3 seconds (effective 273.1 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

Another way to flash the firmware is use the deploy target:

$ make PORT=/dev/ttyUSB2 BAUD=230400 FLASH_MODE=dio deploy
Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity.
Writing build-GENERIC/firmware-combined.bin to the board
esptool.py v1.2
Connecting...
Auto-detected Flash size: 32m
Running Cesanta flasher stub...
Flash params set to 0x0240
Writing 626688 @ 0x0... 626688 (100 %)
Wrote 626688 bytes at 0x0 in 27.3 seconds (183.7 kbit/s)...
Leaving...

NOTE the default baud rate 460800 does not work for my ESP8266 module.

You will see the REPL prompt after startup:

MicroPython f305c62a5-dirty on 2021-02-27; ESP module with ESP8266
Type "help()" for more information.
>>>

Blink LED

The blue LED on my nodemcu board is controlled by GPIO16, when the pin outputs 0, the LED will be turned on, otherwise will be off, the following python code illustrate how to control out value of gpio on ESP8266:

import machine
import time

def led_blink(num=16, interval=100):
    pin = machine.Pin(num, machine.Pin.OUT)
    while True:
        pin.value(0)               # Turn on LED
        time.sleep_ms(interval)
        pin.value(1)               # Turn off LED
        time.sleep_ms(interval)
led_blink()

Upload Python Script to ESP8266 Board

There are two python scripts that will be executed automatically on startup, the first script is boot.py which will be executed first, the other one is main.py there is no main.py on ESP8266 by default, create one and paste the above blink code to it and upload to board with either ampy, rshell or mpfshell.

$ python3 -m pip install --user adafruit-ampy
$ python3 -m pip install --user rshell
$ python3 -m pip install --user mpfshell

Before uploading, make sure ttyUSBx is not opened by any program.

Upload with ampy

# Check if the blink script works properly before upload
$ ampy -p /dev/ttyUSB0 run main.py
$ ampy -p /dev/ttyUSB0 put main.py
$ ampy -p /dev/ttyUSB0 ls

Upload with rshell

$ rshell -p /dev/ttyUSB0
Using buffer-size of 32
Connecting to /dev/ttyUSB0 (buffer-size 32)...
Trying to connect to REPL  connected
Testing if ubinascii.unhexlify exists ... Y
Retrieving root directories ... /boot.py/
Setting time ... Feb 27, 2021 18:41:42
Evaluating board_name ... pyboard
Retrieving time epoch ... Jan 01, 2000
Welcome to rshell. Use Control-D (or the exit command) to exit rshell.
/tmp> boards
pyboard @ /dev/ttyUSB0 connected Epoch: 2000 Dirs: /boot.py /pyboard/boot.py
/tmp> cp /tmp/main.py /pyboard/
/tmp> cat /pyboard/main.py
import machine
import time

def led_blink(num=16, interval=100):
    pin = machine.Pin(num, machine.Pin.OUT)
    while True:
        pin.value(0)               # Turn on LED
        time.sleep_ms(interval)
        pin.value(1)               # Turn off LED
        time.sleep_ms(interval)

led_blink(interval=200)

The script will be copied to root directory during startup.

Upload with mpfshell

$ mpfshell ttyUSB0
Connected to esp8266

** Micropython File Shell v0.9.2, sw@kaltpost.de **
-- Running on Python 3.6 using PySerial 3.5 --

mpfs [/]> ls

Remote files in '/':

       boot.py
       main.py

mpfs [/]> get main.py led.py
mpfs [/]> put led.py main.py

or use below command to put main.py to board without entering shell:

$ mpfshell -n -c "open ttyUSB0; put main.py"

Troubleshooting

MicroPython can output verbose debug message to serial console if enabled, which is useful during development phase, enable it with:

import esp

esp.osdebug(0)          # redirect vendor O/S debugging messages to UART(0)
esp.osdebug(None)       # turn off vendor O/S debugging messages