Close

Log 3: Asyncio and the Adafruit Macropad Library

A project log for Adafruit Macropad Modding

I am using this project to document the changes and additions I make to my Adafruit Macropad firmware. Currently written in CircuitPython

neatloaf7neatloaf7 03/12/2025 at 04:240 Comments

After reading the source code, I learned the macropad.display shares the same attributes as a display initialized with board or displayio. I have rewritten my code to use the macropad library as it takes care of initializing the macropad keys, neopixels, and encoder. This is a good reminder to myself to read the source code.

One of the issues I ran into was code latency, which was especially noticeable during neopixel or OLED animations. One thing I did to mitigate this was to split up the sprite sheet I was initially using. The original sprite sheet had an entire 128x64 tile for every unique frame, which resulted in a 241kb sprite image, which was too large to load into memory. By splitting up the animation into several smaller sprite sheets, the final size for images comes out to 13kb. I switched to using adafruit_imageload over displayio's OnDiskBitmap to load the images into memory. I didn't do any testing but the code definitely initializes faster now. 

The main bottleneck for speed was actually the QT Gamepad. The analog and digital button reads took up alot of processing time; almost a factor of 30 compared to running the macropad without the gamepad. I figured out it is the way the seesaw module reads and writes; by default there is a 0.008s delay after each read, unless you specify otherwise. I set the delay to 0 which might cause issues if many inputs are received in a short time, but it seems alright for now. The input checks still slow down the code, but the mouse is significantly more usable now. 

Another thing that helped was refactoring my code to use asyncio. It simplifies the timing and checks for neopixel and OLED animations, and everything runs smoother as a whole now. There is a slight learning curve for those as inexperienced as I am, but I think it was worth it to refactor everything into asyncio functions. 

Some other minor changes I made was moving the profile configs into a JSON file for easier editing, and bumping up the clock speed in the boot.py to 250MHz. I'm not sure if it made much of a difference but I'll keep it for now.

One thing I will note is that I changed a lot of variables concurrently, so I'm not sure how much each change contributed to speeding up the program. Everything runs pretty smooth now so I probably won't go back and check.

I have uploaded the new code to github here for reference. Now that the Macropad is an acceptable state, I can finally play games again.

One issue I have left to solve is when switching between profiles quickly, the neopixel cascade animation sometimes bugs out and you can end up with the wrong neopixel colors for the profile. I could add a failsafe check to make sure animations match with the profile state but that might be overkill.

I'm not sure what I should use the encoder buttons for either; my keyboard encoder already does previous/next track and pause. With all the free space I have now I could add some more animations but I'd have to be pretty bored to try making more pixel art.

Discussions