PyBadge Hacking

Hacking on Adafruit's PyBadge

Similar projects worth following

Adafruit's PyBadge is coming soon, and I got my hands on a prototype. Now I will do science to it.

What is PyBadge? It's a handheld device the size of a credit card with a display, a bunch of buttons, a microcontroller running CircuitPython and a bunch of extras (neopixels, accelerometer, speaker, flash storage, battery charging, light sensor). It is also a secretly a Feather, as you can connect FeatherWings to it.

It is similar to, but much more powerful than my earlier project #µGame (and the reason why I paused work on #µGame Turbo). I should be able to get my libraries and games from µGame to run on it, but I hope that I can also do some other fun things with it.

  • Adafruit Learn Guide

    deʃhipu06/30/2019 at 19:13 0 comments

    I wrote a learn guide for Adafruit for using the Stage library on the PyGamer and PyBadge consoles. You can find it here:

    It's basically a re-hashing of the bouncing-ball tutorial from µGame documentation, but slightly adjusted. Hopefully it will get people started on writing games with CircuitPython.

  • Jumper Wire with displayio for PyGamer

    deʃhipu06/24/2019 at 15:35 2 comments

    Over the weekend I sat down and ported the Jumper Wire platformer game to use displayio entirely instead of my stage/ugame libraries.

    Here are some thoughts I have from that exercise:

    • Since there is no one common library for handling initialization and inputs (like the "ugame" library for stage), there is currently no way to write a portable game — you have to target a particular revision of a particular device — the PyGamer in this case, which uses "gamepadshift" for the buttons, and "analogio" for the joystick. Other devices might use "gampad" or "seesaw" for the buttons and/or joystick, or even come up with something entirely new.
    • The refresh rate is fixed and decoupled from the game logic, which results in the sprites moving in a jittery fashion and missing animation frames. Fixing the game logic to the refresh rate would help, but would result on widely varying playing experience depending on the device and version of firmware, as performance varies.
    • The removal of "flip_x" attribute forced me to double the number of sprite graphics, and consequently the memory being used up by them, to include flipped versions of practically all of them.
    • While the partial screen updates sped up displayio enough to make such a game possible in the first place, the result is still slower on SAMD51 than the same game using stage library on the SAMD21. This alone makes me thing that it's worth it to keep stage around.
    • The analog joystick knob on the PyGamer is horrible — maybe a better version could be 3D-printed.
    • Having to pre-commit on a fixed size of the groups is a huge pain — especially when your game creates sprites dynamically (missiles, explosions, drops). Adding them to the group at the right layer is a challenge as well.
    • Figuring out how to load a BMP file was a bit of a challenge, and opening the file in the wrong mode stopped me for quite a while.

    And finally, here's the repository with the game:

  • PyGamer

    deʃhipu06/17/2019 at 20:46 0 comments

    Two news here. First of all, the Stage library now has support for the PyGamer device, so you can play all the games on it. The analog joystick is translated into button presses.

    Second, the official displayio library grew support for partial screen updates, which makes it much faster — sufficiently to replace the Stage library in the future. So far I have ported the bouncing balls demo to it, and I'm working on the remaining games. I also plan to write some tutorials.

  • Finish

    deʃhipu05/09/2019 at 21:20 0 comments

    Since I have no means of fixing or even debugging that problem (it doesn't appear on the board I have), there is really little I can do further. I'm ending up a proud owner of one of the only few PyBadges in the world on which my games work, yay.

  • Bugs Bugs Bugs

    deʃhipu05/08/2019 at 20:37 0 comments

    So there is some problem with running my games on the actual final version of the PyBadge — the display goes white randomly several seconds after starting the game. If you have stumbled into that, don't worry, it's not you, it's me.

    The bad news is that it's very hard to debug without actually having a unit that exhibits the problem — it all works perfectly fine on the prototype I have. There has been some changes to pins, in particular the TFT_RST pin was swapped with the TFT_LITE pin internally, but that should be handled by the firmware. I'm still trying to figure out what is happening there.

  • On Sale

    deʃhipu04/24/2019 at 20:20 0 comments

    I just noticed that the PyBadge is finally on sale at Adafruit:

  • Stage Library

    deʃhipu04/13/2019 at 10:43 2 comments

    I have now ported the #Stage, a Tile and Sprite Engine to CircuitPython's 4.0 displayio system, so now it runs on any of the Adafruit boards. Here is the Jumper Wire example game running on the PyBadge:

    The screen on the PyBadge is a bit larger, so you can see a bit of trash on the right hand side of the screen. If the game was written for a bigger screen, it wouldn't be there.

    Unfortunately the Stage library is sensitive to how the MADCTL register of the display is set, as it doesn't do rotation in software. That means I had to modify the initialization code on the pybadge, to make it fit what ugame does. Fortunately displayio doesn't care, as it does have software rotation, so the two could be brought in sync.

    The sound on the prototype of PyBadge I have is pretty bad, so I have sound disabled in this demo. Hopefully the final version will be better.

  • Buttons

    deʃhipu04/11/2019 at 13:07 0 comments

    Handling buttons on a game console requires some special consideration, since it is going to affect how responsive the games feel. Human touch can discriminate really small timing differences, and we definitely don't want to miss any button presses — human input is sacred for computers.

    Since you will usually have quite a few buttons — at least 5, for directions nad fire, and possibly more — there is a big temptation to save some pins by using trick in how the buttons are connected. You may connect them in a matrix, or even charlie-plex them, you can connect them to an analog pin with different voltage dividers, you can use different capacitors and rely on the signal length, or you can use capacitive touch pads to save on components. Whatever you do, there is one problem with all those tricks — they require some logic in the software to work. Even if you have your buttons connected directly to pins, you want them to be either handled by a pin change interrupt, or scanned in background in a consistent manner.

    One way to handle the buttons in the background without imposing too much work on the people programming your device is to include all the required code in a library, and use interrupts — so that they don't have to follow strict requirements about calling a polling function in regular intervals, etc. This lets them write code that blocks, uses delays and so on — which is much easier to understand for a beginner. That is why I wrote the "gamepad" module for CircuitPython — so that I can use it for buttons on the #µGame.

    Another approach is to outsource the task to a dedicated chip, such as the HT16K33 on the #PewPew FeatherWing or a custom programmed ATtiny24 on the #D1 Mini X-Pad Shield — they can do the scanning and the debouncing for your, and you just need a small snippet of code to read the currently buffered button state from them over I²C. Easy!

    Now, back to the PyBadge. That device uses a 74HC165 shift register for the buttons. This is a problem, because it doesn't buffer the button presses — every time you get the data from it, you get the state of buttons at the moment the data was latched, when you started the communication — and not all the presses since you read it last time, which is what you get with the HT16K33 and ATtiny. You can read that state in Python easily enough by bit-banging:

    def get_pressed():
        pressed = 0
        latch.value = 1
        for i in range(8):
            clock.value = 0
            if data.value:
                pressed |= 1 << i
            clock.value = 1
        latch.value = 0
        return pressed

    But in your game you would have to do it constantly, very fast — and at the same time run all the animations and calculations. Not very convenient, and if any of your things takes more time than anticipated, you are going to miss button presses. That makes the experience very frustrating.

    Fortunately, that shift register is very fast — you can get the data out of it in no time at all. So I modified the "gamepad" module a bit, to make it also work with a shift register. Every 8ms it polls the shift register for the current button state, and saves the button presses in a buffer, which is emptied when you query it. This way you can use the same code on the PyBadge as you would on the µGame, just the initialization is different.

    The pull request with the changes is currently in review at

View all 8 project logs

Enjoy this project?



Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates