I have built up a rough set of CircuitPython code that sends a bitmap to show on LCD screen of a salvaged Canon Pixma MX340 multi-function inkjet. I abandoned my effort to get sprite compositing via displayio, but I still want to do something fun with this screen. As an experiment, I wrote a loop that continuously sent LCD frames to display, exploring its theoretical ~14fps top speed. The result was very jerky, and CircuitPython console output showed a lot of my error message indication transmission failures that had to be retried.
The first data point was adding a small delay between frames to see if that changes behavior. It did not, so I believe these failures were not caused by a relentless nonstop stream of frames. The next data point was to remove my heartbeat LED, and that stopped the stream of errors. Interesting.
The heartbeat LED is a separate task, with asyncio switching back and forth between LCD updates and heartbeat LED updates. Heartbeat is a simple loop that blinks the “In Use/Memory” LED on and off by transmitting corresponding control flags. Each toggle is a two-byte transmission, but the LCD screen update is a much longer sequence of data transmissions.
Task switching could occur at any await statement, which means it is possible for heartbeat LED update command to barge in the middle of a LCD sequence. The most inconvenient time would be right after the 0x04 0xC4 command “Bulk transfer of 196 bytes incoming”. If the next transmission is a two-byte LED toggle instead of the expected 196 byte transfer, everything would understandably go awry.
To prevent interleaving data transmission between tasks, I need to add a locking mechanism so heartbeat LED updates will hold off until LCD screen update is complete. And in case of continuous nonstop LCD animation, it needs to make sure heartbeat LED update gets a chance in between frames. Out of habit I started writing my own locking mechanism but then realized I was being silly. I went to read documentation and confirmed a lock is already available as asyncio.Lock. I only had to create a single instance of the object, then as per documentation add an “async with” statement on that instance to prevent my two data transmission tasks from stepping into each other’s sequences.
It worked! I could put my heartbeat LED back into the system, spin the LCD animation updates to full speed, and they both run. No longer interrupt each other and causing transmission errors. But this experience really gives me a feeling I’m wielding powers beyond my comprehension. I barely understand async/await and now I’m adding “with” context management which I also barely understand. This is probably a good time to pause my uninformed code-slinging and read some tutorials and/or specifications to understand what’s going on.