When considering tooling for TinyML on microcontrollers, you might initially think that CircuitPython isn’t the best tool for the job. After all, it is interpreted and significantly slower than compiling something written in C++ using TFLite micro. While I acknowledge the capabilities of TFLite micro, I’m not particularly fond of it. My primary issue is that it is incredibly difficult for beginners to get started with.
Don’t get me wrong—cloning a TFLite micro repository and compiling examples is straightforward. However, the real challenge arises when you try to integrate TFLite with your existing setup. Finding drivers for peripherals like displays and cameras and getting them to compile with TFLite has always seemed an insurmountable task to me. This might be partly due to my limited familiarity with C/C++ compilers and toolchains, but understanding concepts like CMake and Make is no small feat.
In contrast, CircuitPython comes bundled with a wealth of useful libraries. Python is a much simpler language to learn and write. Adafruit has done a fantastic job with CircuitPython, offering libraries and drivers to interact with a staggering number of devices right out of the box, much like Arduino. Another significant advantage of using CircuitPython is its “write once, run everywhere” capability. If something works with CircuitPython, it is guaranteed to work with the more than 200+ boards currently supporting CircuitPython.
One of the features I love most about CircuitPython is the convenience of being able to edit a Python file and simply press CTRL+S in my text editor to see the changes immediately. While CircuitPython may be slower in practice, many libraries are written in C/C++ and perform reasonably well.
One of my favorite libraries in CircuitPython is “ulab“, which offers a limited but useful API similar to NumPy for working with matrices. The “ulab” library is fast and supports many common operations of NumPy. However, it only supports a maximum of two-dimensional matrices in CircuitPython. It seems to have support for up to four dimensions, but that would require delving into the CircuitPython code and recompiling it.
Due to “ulab” supporting only two dimensions, matrices must be tactfully converted to Python’s lists of lists where the inner list holds a two-dimensional “ulab” array. Despite this, I find this workaround worth the effort and much simpler than working with TFLite. Additionally, debugging with CircuitPython is significantly easier.
In summary, for most of my experiments, I prioritize speed and ease of development, allowing me to try multiple things in the limited time I have. Once an experiment is successful, it might make sense to port it to C/C++.