MicroPython is (according to their website) “a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments”.
I’ve used it for a couple of projects on the ESP-8266 and ESP32 platforms and have always been impressed with the speed in which you can go from basic idea to working prototype. The FAT based filesystem and inclusion of a REPL mean that it is possible to iterate at a much faster pace compared to Arduino or ESP-IDF, but there are a few flaws - MicroPython is more resource intensive than other languages (get used to peppering your code with gc.collect()
) and the community has a long way to go before it can match the sheer variety of hardware support and external libraries offered by the Arduino ecosystem.
However, MicroPython is not limited to the embedded domain. A port for Unix exists and despite the lack of hardware capabilities found on ports for microcontrollers, the tiny binary size and minimal dependencies are well suited to the constrained Linux environment found on the Kindle.
Setting up the toolchain
Before we can do anything else, we need to build an ARM cross compiler toolchain. The Kindle uses an extensively patched kernel and ancient glibc
release, which means that we cannot use the cross-compile toolchains found within the repositories of most Linux distributions. Amazon does include instructions for setting up a Poky environment to build binaries within their GPL releases, but as far as I know, nobody has been able to successfully build it.
Luckily, @NiLuJe has put in a significant amount of time and effort into maintaining Kindle compatible toolchains based upon crosstool-ng. To get started, clone the koxtoolchain repository:
We will also need to install all of the dependencies needed to compile the toolchain:
We now need to establish which toolchain that we need to build. This needs to match the specific Kindle model that we want to build binaries for:
TC | Supported Devices | Target |
---|---|---|
kindle | Kindle 2, DX, DXg, 3 | kindle-legacy |
kindle5 | Kindle 4, Touch, PW1 | kindle |
kindlepw2 | Kindle PW2 & everything since | kindlepw2 |
In this case, we’re using a Kindle Paperwhite 3, so we need the kindlepw2
toolchain. To start the compilation of the toolchain, we need to run these commands:
Note that this will take a while to complete - on my old-ish laptop with an Intel i5-7300U CPU, it took just under 40 minutes. When compiled, the toolchain will be located within the ~/x-tools
folder.
Setting up Micropython build enviroment
- First, clone a copy of the MicroPython source code
- We now need to build
mpy-cross
- this is used to pre-compile MicroPython scripts so that they can be embedded (or frozen, in MicroPython parlence) into the MicroPython binary.
- Next, add the
/bin
directory containing the toolchain to your $PATH variable - I use this bash snippet within my./bash_aliases
file:
- Once the prior steps are completed, enter the Unix port directory and build the dependencies required by MicroPython using the cross-compile toolchain.
- We are ready to begin building MicroPython. I ran into a few minor issues when doing this and have included the problems and solutions as they occurred.
- As we are cross-compiling instead of building for the host platform, we immediately run into a problem:
- The
libffi
headers are not included as part of the cross-compile toolchain build environment, so we need to set thePKG_CONFIG_PATH
variable to point to the version included with MicroPython:
- This time round, we run into a linker problem when running
make
:
- In theory, the
clock_*
functions are available on the Kindle as part ofglibc
(they were moved fromlibrt
toglibc
in version 2.17; the PW3 uses 2.20). However, as the cross-compiler toolchain uses 2.12 for compatibility with other Kindle models, we need to link againstlibrt
. We can do this by setting the LDFLAGS_EXTRA variable to-lrt
:
- The build should now be successful:
- At this point, we should copy the binary over to the Kindle so that we can test it:
- An error is thrown due to the absense of a suitable version of libffi. There’s a few ways that we can resolve this issue:
- Creating a symlink to the existing
libffi
version already on the Kindle. This isn’t a great idea as MicroPython might be using functionality that is only found in a recent version of the library. - Adding the MicroPython version of
libffi
to/usr/lib
on the Kindle. This would work well, but I prefer to avoid making changes to the root filesystem of the Kindle unless I have a way to track and revert changes. - Rebuilding MicroPython with an extra shared library search path that points to a location on
/mnt/us
. This would also work well, but we would need to transfer ourlibffi
build separately. - Statically linking MicroPython. This gives us a single binary that runs without the need to perform any additional tasks, at the expense of significantly increased file size.
- Creating a symlink to the existing
Out of these approaches, the last 2 are preferable.
Building with extra shared library path
- Delete the existing MicroPython build:
- Re-link MicroPython, with
-Wl,-rpath=/mnt/us/micropython/lib
appended toLDFLAGS_EXTRA
:
- Create a directory to contain
libffi
on the Kindle:
- Copy the cross-compiled libraries to the Kindle:
- Copy the MicroPython binary to the Kindle
- We should verify that the Kindle is able to load the shared library from the additional search path - the output from
ldd
should look similar to this:
- If
libffi
is found, MicroPython will run successfully:
Building with statically linked libraries
- Delete the existing MicroPython build:
- Re-link MicroPython with
LDFLAGS_EXTRA
set to-static -lrt -lffi
- This will emit a few warnings, but they can be safely ignored:
- Copy the MicroPython binary to the Kindle:
- MicroPython should run successfully:
- Statically linking the binary has increased the size by around 700KB, but this isn’t a major problem as the total size is still under 1MB:
Installing additional libraries with upip
MicroPython includes a package manager that is vaguely similar to pip
. The package selection is limited (with many being stub packages that offer no functionality) and packages need to be created specifically for MicroPython; you cannot use packages for other implementations like CPython without adapting them beforehand. These libraries are available on micropython.org and PyPI - the full listing can be found here.
By default, packages are installed in the home directory of the current user. This presents a problem in the case of the Kindle - the default home directory is /tmp/root
, which means that any installed packages will be deleted when the device is rebooted. According to the documentation, it is possible to specify an installation path for upip
using the -p
flag:
However, it’s easier to change the install path so that we don’t need to specify it each time we want to install a package.
- To do this, open
micropython/tools/upip.py
on your host system. The function that we need to alter isget_install_path
:
- Change
install_path
fromsys.path[1]
to/mnt/us/.micropython/lib
:
- Once you have made this change, rebuild MicroPython using one of the processes above, then transfer the binary to the Kindle. When a package is installed, the installation directory will be shown:
You should now have a working version of MicroPython on your Kindle and hopefully, a clearer idea of how basic cross compilation works. Although the utility of MicroPython is limited by the lack of deeper hardware access found on microcontroller ports, the ability to access a lightweight scripting language with support for extra packages can be incredibly useful, especially once you understand the concept of freezing modules into a build.
If you found this post interesting, it’s definitely worth checking out the MobileRead Kindle Developers forum to see what else you can do with your Kindle, and the MicroPython forums to get a sense of other things you can can achieve with this minimal language.