The best way to learn something new is to try it out in small projects. Here I'll try to describe some parts of the development process, ideas, challenges and solutions for problems that appeared during the development process. These projects are mostly software projects for StickC, and the the only hardware component that I used besides StickC is UnitV.
UIFlowOne of the main development platforms (board firmware) provided for StickC is UIFlow. Even though it's advertised as a block-based programming framework, it can be actually used only as a driver layer for micropython, providing APIs for built-in hardware components.
Frequently used componentsfrom m5stack import lcd, btnA, btnB
Importing libraries to control things, like buttons and LCD is just that easy.
1. Classic Snake gameBack in the day, there was a game in every Nokia phone that was popular and really fun to play. The idea is quite simple. You play as a Snake, you have to grow your tail by eating food and avoid the walls and your own tail.
M5StickC has 3 buttons, 2 of them can be used in UIFlow. They can be mapped to change Snake direction.
Usually, in a game, there is a loop, that is rendering current screen state every frame. To prevent flickering, the best option is to draw only the parts that are changed between frames.
Since there is only 1 snake, 1 piece of food at a time and 1 game, every class is used once, directly by its name, using a singleton pattern instead of creating class instances.
Also, I made a 500-byte JS snake project a while ago, it uses 1D-to-2D mapping, and works a little bit differently, but still follows the same rules.
2. TickerStickC's screen resolution is 80x160, which is not enough to display large amounts of text. UIFlow text-rendering library by default breaks a line if text width is larger than screen size. It also does not allow to put text on negative coordinates and create a smooth movement animation. However, there is still an option to cut parts of a text by its length and display them as a ticker.
So I made it as a library, that can be used both in portrait and landscape modes, and can be customized. Take a look at project's code for more details
import Ticker
t = Ticker("This is a very long text", 0xffffff, sliding=False, delay=2)
There is also an option to use multiple lines, controlled by the same Timer. To do it, provide text
argument as a list of strings and add multiline=True
argument.
Typing text with only two hardware buttons is a really slow and annoying experience. It can be improved by accelerometer built into StickC. I have seen a few projects with accelerometer used to control on-screen cursor and I decided to implement a keyboard library which works in a similar way.
A
button is used to input selected character, and B
button is used to delete the last character (backspace).
from accelKeyboard import Keyboard
inputText = Keyboard().loop()
It's really easy to just use it like shown above, and you can also customize it:
Keyboard(cursorSize=4, sensitivity=0.6, cursorColor=0x11f011, topBG=0, keyboardBG=0x444444, textColor=0xffffff)
If current keyboard layout isn't good enough for your project, feel free to modify the code.
4. Fire Animation (remake)I saw a project by MajorSnags written in C and decided to implement it in Micropython, to push software and hardware to the limits.
Tip: A better and more memory-efficient way to store lists/tuples of numbers is to use array
module. See the docs for more details.
How to make your code run faster?
There is a great documentation page about optimizing the code and compiler options available in micropython. Basically, you can use @micropython.native
decorator to compile python directly to native code.
The trade-off for the improved performance (roughly twice as fast as bytecode) is an increase in compiled code size.
Another option is to use @micropython.viper
compiler decorator. It's way more strict and it's basically a strongly typed python. I recommend you to read the documentation about its limitations. I have to mention that it also has some weird behavior when you try to modify nonlocal/global variables, and I noticed a limit on a number of different functions that can be called from within a @viper compiled function.
Finally, you can use C language to add native modules. However, they also have a lot of restrictions. I noticed that dynamic variables and arrays in C can randomly change if a C-module function is called from python multiple times in non-instant time intervals (for example, from REPL).
Take a look at project's source to see speed comparison of different implementations.
5. Twitch clientInteraction with dynamic content and something that looks like a real app, those were the goals of this project. But twitch is a video streaming platform, what can be done with it? Well, it's true that you can't play encoded live-videos on ESP32 device, you can still browse data about most popular streams and games and display things like channel name and viewer count, and this app provides a few options to navigate between channels. Finally, you can select the channel and join related chat room. If chat's speed isn't too high, you can actually read the messages. I made two different scrolling modes, one of them uses a Ticker library, mentioned above and the other just relies on default line-break behavior.
Displaying previews.
UIFlow provides a function to draw images on the screen, they even added PNG support recently. The main problem is that an image has to be saved in flash memory, and there is no option yet to display images from RAM or RAMfs. UIFlow devs said that it's fine and StickC has more than 100, 000 write cycles, but for projects like this, it seems a bit too much to save every preview in flash memory. In order to not waste precious resources of StickC, I decided to make a custom JPEG decoder, by porting another JPEG decoder from python2. Initially, I planned to display emotes in the chat, so I also made a custom PNG decoder from scratch. In the end dealing with default line-break behavior was too much for me and I dropped this part of the project.
Twitch API allows you to specify a limit of amount of received data, and customize size of preview images, this is why it's possible to make a project like this one.
Twitch chat servers can be accessed using IRC or WebSocket protocols, and this websocket library is perfect to provide access to chat messages.
6. Maps appSince StickC is a ESP32-based device, it has built-in Wi-Fi module. Which means that we can use it to get data from the internet, and not only that, but also to use the data about nearest Wi-Fi networks to get current location from the internet using a Geo-positioning service. Alternatively, in this project it's possible to input location information using the keyboard from project #3.
After location data is known, we can use a tiling map service to get map images from the internet. A custom PNG decoder can be used to decode and draw them without saving them to flash.
As a part of this project I made a library, for wifi- and text- based Geo-positioning, which you can use in your projects. Don't forget to read the license of used API services.
Take a look on project's page on github to dive deeper in project details.
7. StickC+UnitV UART-CameraThe goal of this project was to take a photo using UnitV module, and transfer it to StickC and show it on the screen using micropython on both devices.
Since current version of UIFlow doesn't allow to load photos from RAMFS, we can either save them to flash memory or draw them pixel-by-pixel or use custom JPEG decoder (in this case, it's not enough RAM to use mpy-img-decoder). This project uses the second approach.
8. StickC+UnitV IP-CameraThis project is similar to project #7, however, to speed up the process, compressed JPEG-images are transmitted to StickC, and built-in MicroWebSrv server library is used to stream images directly to frontend webpage. That page provides some basic options for dynamic camera configuration. In this project I also decided to use UARTHS, which increases data transfer rate, but limits REPL-debugging options.
I'm not sure if my UART messaging algorithm is optimal, In some cases data is lost in the transmission process, and I had to implement timed limits and full frame drop, when that happens. I'll wait until UIFlow developers merge the latest micropython release into their firmware, to get more flexible uasyncio capabilities.
9. Public transport real-time panelThis project shows bus (and not only) arrival times, based on data provided by Russian service Yandex Maps. It shows, bus/minbus/trolleybus/tram/train number, and how much time is left until it arrives to a specific stop.
Here's how easy it is to get current time and format it.
from m5stack import rtc
time = rtc.now()
txt = '{:02d}:{:02d}:{:02d}'.format(*time[-3:])
and NTP time synchronization is also really simple:
def syncTime(yourTimezone):
from m5stack import rtc
from ntptime import client
n = client(host='pool.ntp.org', timezone=yourTimezone)
n.updateTime()
rtc.setTime(*n.time[:-1])
Development environmentDuring development I used Thonny IDE. I have to say that sometimes it causes the board to crash, hopefully someday this will be fixed. The main advantages of this IDE are: a really convenient way to work with files on the board, an option to run the code without saving it to the board, a built-in COM-terminal to work with REPL, and a plotter to display charts (useful during the debugging of things like accelerometer, gyroscope). It's also cross-platform and I managed to install it on a tv-box running ARMbian.
The hardest partFor me, the hardest part was working with memory (RAM). UIFlow only has ~84kb heap available to the user and I frequently faced this limit. What happened to 520kb of ESP32 RAM? ¯\_(ツ)_/¯ Apparently, this is all eaten by Micropython core components.
import esp32
esp32.heap_info()
According to output of heap_info, there is still 153kb of free heap outside of micropython. I would be glad to know, how to use that.
Thank you for reading this post, if you have any questions or suggestions, feel free to ask here, or contact me directly in Telegram.
Comments