-
1Prerequisites
A CircuitPython bootloader must be installed on the device. This is very easy to do. This has already been covered by many guides in the past, but I would recommend this one for starters: https://learn.adafruit.com/welcome-to-circuitpython
Next, a few libraries need to be copied to the device. These are compiled MicroPython files made by Adafruit/CircuitPython to help with interpreting the signals from the accelerometer. The following items will need to be copied to the device to get the accelerometer to work, which are in this zip folder: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/download/20201229/adafruit-circuitpython-bundle-6.x-mpy-20201229.zip
Download the zip file, extract it, and copy over the lib/adafruit_bus_device/ (the entire folder) and the file lib/adafruit_lis3dh.mpy into the lib folder on the device. To copy any files onto the device with CircuitPython installed, you should be able to simply drag and drop the file or folder onto the drive that shows up when you plug in the device to your computer. Once any file transfer is complete the device will restart automatically.
-
2Audio
This is the class that generates tones. Tones can have a frequency in Hertz or can be specified in pitch/octave notation. For example, the call play_note("C4", 0.5) will play middle C for half a second. Alternatively, play_note("R", 1.0) will play nothing (rest) for one second. Tones are generated using a sine wave, and the CircuitPython AudioIO class is used to play the raw wave. Sequencing is done using a queue of notes and their timing, and every time the tick function is called (code.py calls this in its main loop) the queue is popped. Note timing uses CircuitPython’s built-in time.monotonic() which returns an ever increasing count of seconds. Timing is done using the difference in the current call to that function and a previous check. Due to the nature of the device clock, note timing is not very accurate, but this isn’t much of a problem for a small game. This class is initialized with the audioio.AudioOut object in the constructor, to know where to send the raw signal.
-
3Draw
This class handles all CircuitPython DisplayIO related screen drawing using bitmaps. For more information on screen drawing and bitmaps, this is a great resource. In this implementation, there are two bitmaps which are stored a single group, which is sent to the display. The main bitmap, display_bitmap handles the drawn image. Every time the cursor is moved, it draws a square of the current cursor size in the current cursor position, then changes the tuple the current position to reflect the direction moved.
The second bitmap, cursor_bitmap is more simple. It is the size of the largest possible cursor size, and on display_cursor it draws a square of the current cursor size either in the current color, or the “off” color – this is set to the index of black by code.py. This function is called with alternating values for the display parameter in the main program loop to make the blink. The rest of the cursor bitmap is filled with the index for an “invisible” color – this is an index outside of the bitmap palette that looks invisible and allows the display bitmap underneath to be visible even if the cursor bitmap is overlapping.
There is a custom value for offIdx passed to this constructor which should be the index of black in the rgbColors list. This is used for a special case where if the current cursor color is black, the cursor should flash white (or whatever is the first color in the colors list). This color is also used as the reset color when the bitmap should be cleared, which is triggered by shaking the device. If you would like to use any other color as the background, this would be the parameter to change. The other parameters passed to the class constructor are board_display, the pin to send DisplayIO messages to – this should come from board.DISPLAY, and rgb_colors, a list of RGB colors. This should be a list of lists in the form of [[R0, G0, B0], [R1,G1,B1]… ]. Each of R, G, B represents a value of red, green, and blue between 0-255.
Some example colors:
White: [255, 255, 255] Red: [255, 0, 0] Yellow: [255, 180, 0] Black: [0, 0, 0]
For more colors, Google has a color picker that can be found by searching for "color picker"
-
4Input
Possibly the most useful class, the ButtonPress class holds a list of Button objects that match the PyBadge board. Buttons can be easily added or removed, and the secret sauce behind the ultra-fast button processing is a CircuitPython module called gamepadshift. This is a shift register which contains the most recent button information, and multiple buttons can be read at the same time. Every tick of the main loop in code.py calls the function check_pressed in this class with the contents of the button register. Each device button can be called from the button_from_name function in this class to get a reference to the respective Button object. Each button can be assigned a function to call on button up, down, and long press. The long press time is even customizable. There is an additional setting for debouncing. This means that if a button press is detected, the respective function will be called only once until the button is released and repressed. For instance, the d-pad buttons are not debounced so every tick, the cursor move function can be called while a button is held down. The A button is debounced so that we aren’t flipping through multiple colors while the button is held down. A tick is fast enough that what may seem like a quick press may call a non-debounced button function multiple times.
-
5Code
All of the main loop and setup code is in this class. CircuitPython automatically runs any code in a file called code.py, so this is the first thing called. The important stuff is in main_loop(), which is the main program loop that runs every TICK_RATE_S seconds.
If TICK_RATE_S is set to 0.1, every tenth of a second:
- The button register will be checked
- The tone player either pops the queue to play the next note in a sequence, or continues outputting the current note
- The accelerometer will be checked against the previously recorded values for each dimension XYZ to determine if the device was shaken (more than a certain ACCEL_SHAKE_THRESH)
- And finally, the display cursor blinks
Thanks for reading! All of the code is here: https://github.com/benbenbob1/Etch-a-Sketch-PyBadge
Just download all the python files in the repo and copy them (including the lib folder) to the device. The device should automatically restart and run the code.
-
6Next steps
I would love to add saving to this project because just like a real Etch-A-Sketch, your beautiful creations are only temporary. Most Adafruit microcontrollers are compatible with wifi co-processors, so wifi saving is possible, so this is a possible next step.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.