A Python-based micro game console, optimized for game development workshops.
I really like the #PewPew FeatherWing as a platform for teaching game development, but the cost of Feather boards needed to use it makes it difficult to organize workshops for larger groups of people. I have previously tried to work around that problem by merging the FeatherWing with the schematic stolen from a Trinket M0 (with an additional flash memory), but the resulting design was complex, difficult to make and still a bit expensive. Now after having designed a few more CircuitPython boards I think I can really cut the costs and make a standalone device with all the functionality of the original shield, but optimized for workshops.
It's a kind of anniversary: I have reached the tenth prototype of the "standalone" version of PewPew. Admittedly, not all prototypes have been actually realized in hardware, but many were, and all were ready for it. In any case, I present you the tenth version:
I took some liberties in the design of the back side, since it will be mostly hidden anyways:
As you can see I narrowed the device back to the 32mm of the LED matrix, and I squeezed the pin header underneath, only accessible from the bottom. I think this makes the whole design more robust, and doesn't much affect convenience. I can put a color sticker on the top edge of the matrix to serve as a legend for the pins.
I'm also considering adding laser-cut pieces on the underside and top, similar to how this year's Hackaday Belgrade badge had. This way it will be thicker and easier to hold. Of course they are optional. I will prototype them with wood for now.
There is another option in there — I added a footprint for a voltage regulator next to the power switch. It will be unpopulated by default, but if you add it, you can use the device without battery, just connected to USB.
The battery holder is for 2×AAA batteries, and I made holes for two versions — because the order of cables coming out of them seems to be mostly random. A 2×AA holder would fit too, but it would cover the pin header, so not recommended.
There are two mounting holes next to the pin header. Those can be used to securely attach any kind of larger add-ons for the device. The laser-cut parts will have a suitable cutout, of course. There is also plenty of holes for either attaching a case, or attaching the device to a larger contraption.
Finally, I made both the power switch and the USB port a little bit recessed — so that they don't stick outside the outline of the board. That should make it easier to carry it in your pocket.
As the prototypes are coming, this device is becoming a pretty nice thing for teaching game programming, which is its main focus. However, wouldn't it be great if it could also be used for hobby electronics projects, like the micro:bit or any number of development boards? For that it would need to break out more pins than the SWD and SWC it breaks out right now, and they would need to be broken out in a way that makes it easy to connect stuff to them.
The current pin budget is as follows:
8 pins for the LED matrix rows
8 pins for the LED matrix columns
6 pins for the buttons
2 pins for programming/debugging/connecting stuff
2 pins for USB
One way to free some pins would be to drop the LED matrix and use charlieplexed LEDs like in the business card prototype — then I can get away with 9 pins instead of 16, and would have 7 free pins, which is enough for this kind of use in my opinion. However, while the parts cost is similar, the assembly costs might go up, and the display is much less readable, due to lack of diffusing. I could add a 3D-printed diffuser on top, but that again adds to the cost.
Is there any way I could save pins on the buttons? Right now I use one pin per button, which is kinda wasteful. I could arrange them in a 2×3 matrix, which would require 5 pins, saving me one, but I don't think it's worth it, especially since I would then need additional diodes to avoid ghosting. I could use a resistor ladder and connect all the buttons to an analog pin — but then reading the button state would take longer, as ADC is not as fast as simple digital pins. Can I share the LED matrix pins with the buttons? If I just connected them to the row pins via diodes, I could have an additional fake column for which I would scan the buttons, but that's 6 extra diodes. Hmm, what if the other end of the buttons wasn't connected to GND or VCC, but instead to an additional pin? I already scan the columns, so I could connect the buttons to that, and on every column check the additional pin (with a pullup) for the value of the corresponding button. As a bonus, no extra components and no extra cycles in the matrix scanning, so the brightness shouldn't suffer. That seems like the way to go!
And that gives me 5 free pins, which together with the 2 debugging pins give me a nice 7 pin header. That should be enough for most basic hobby electronic uses, especially since I can choose pretty much any pins for that, so I can take all the ones that have interesting peripherals on them: ADC, DAC, serial, I2C, SPI, timers.
Now, onto the connector.
I can't just use normal male headers, even though that would make it super-easy to connect the device to a breadboard, because it's supposed to be carried in a pocket, and the pins could lead to injuries, torn clothes and, what's worse, bent pins. I don't want to use an edge connector, like the microbit, because it forces you to buy special hardware and defeats the whole idea of a cheap device. I don't like the big holes for banana plugs/crocodile clips, because who uses banana plugs today? Also they are huge and wasteful. I could use a female pin header — then you could easily use dupont cables to connect it to anything you need. It would even look good next to the matrix display. Or I could just leave the holes for the header unpopulated (which saves me part and some soldering), and use staggered holes, so that you can insert a male pin header when you need it, or solder a female header if you want. I think I will go with that.
The last question is where should the header be located? Obviously we don't want to have it on the same side as the USB plug and power switch. In fact, if this is going to be connected to a breadboard, we don't want an USB plug anymore, and we will need to switch back to a micro USB socket, so that you can have some distance between your computer and the breadboard. But still we don't want them on the same side.
When we put all this together, we get something roughly like this:
Once I got my matrix-driving code working on the PewPew 8.0, it was just a question of hacking a tri-state gpio pin switching function and some trivial modifications to get it to work with a charlieplexed display. At least it seems so, because it looks like it's working:
There is a slight problem with brightness — they are way too bright now to distinguish individual shades. Of course my phone camera got overloaded and did something funny with the colors, to the naked eye they are all blindingly red. I will need to tune the PWM somewhat.
I'm powering it with a bench power supply for testing, because the poor little coin cell only lasts a few minutes with the display on at full power — even though the bench supply claims it's only drawing 20mA total.
We will see what I can do with it, but it definitely looks promising.
The flickering problems are solved by slowing down the clock to a reasonable frequency.
Well, the clock was running at its 48MHz and generating interrupt requests, but that doesn't mean that the interrupts were being executed at that speed. It takes time to execute them. So they were running as often as possible, back-to-back. Which means that any other interrupt, or anything affecting the speed of execution at all, would affect how often they run, which would be visible as flickering.
The solution? Slow the clock down to a reasonable rate, so that there is plenty of time between the interrupts for executing other things. Like the user python code, for example. Yes, as a side effect, user code can now run.
It was a really basic mistake, I blame the late hour. In any case, it's now fixed and I have the games running.
As a bonus, since I ended up not using the hardware PWM, I can use the old PCBs, of which I have 20.
The new version of the PCB arrived Friday from @oshpark, and in the evening I started working on it. Assembling was straightforward, with just 4 electronic components. I burned the bootloader, adjusted the board definition to the new pins and flashed the firmware, and the old code works. Next, I changed the rows from DigitalInputOutput objects into PWMOut objects with 100kHz frequency, and instead of setting them low or high when scanning the columns, I'm now instead setting the duty cycle to the desired shade. The code compiled and should work in theory...
In practice, turns out that setting the duty cycle sometimes takes longer to actually have an effect, and in that time we already switch to the next column (row on the photo, as the matrix is rotated 90°). The effect is a random flickering below every lit pixel.
I also found a bug in CircuitPython, where when you create a lot of PWMOut objects, some of them will share the compare channels, resulting in effectively always having the same brightness. I suspect that when that bug is fixed, I won't be able to create so many channels anymore anyways. So this approach, while it sounded great on paper, is not so promising anymore.
But not everything is lost yet! Today I decided to try and create a separate timer, just for the matrix. I found the code that sets up the timer for PulseOut objects, and pretty much copied it, only skipping connecting the pins to the timer. Unfortunately I had to modify the peripherals library in order to add my function to the interrupt handler — so this is no longer a single clean patch. But lo and behold, it works!
Pretty good for not having any idea about what I'm doing. Of course at the moment the code is one huge hack, but that's not important, I can clean it up. And believe or not, there are actually 4 recognizable shades on that photo (with a naked eye you can't see the bright spots inside the pixels, and they are all red, not yellow). This also means I will be able to do charlieplexing for that business card.
One problem though — if you look closely, especially on the dimmest-but-not-black pixels, you will see some flickering there. How come? I'm running that timer with prescaler 1, it should run at 48MHz, even dividing that by 8 columns and 6 rendering phases, that's still 1MHz, there is no way you would be able to see that with a naked eye! So what's happening? I really have no idea, but I hope it's something that can be fixed.
I was supposed to be writing a game for Ludum Dare, but instead I worked on this. At least it's also game-related.
Today is the PCB day. A purple envelope arrived, filled with goods. But before I assemble and test the prototype #8 of the device, with all row and column pins capable of PWN, I want to finish an experiment that I described in the first log here. The business card.
In an attempt to make the device as thin as possible, I decided to try and make most of the component "sunken". What does it mean? It means that the PCB has leaks. I mean holes. And the SMD components that would normally sit on top of the PCB instead go inside those holes, from the bottom, and are soldered to pads adjacent to those holes. Here's a view from the back with the switches soldered:
And here's the almost-finished device:
I still need to test the charlieplexing before I solder the rest of the LEDs, but you get the idea.
Of course this is not practical. The holes have to be manually corrected with a dremel (see the first image), because the CNC tool is a bit too big to make good corners (and because I got some sizes wrong). Then the parts need to be fit into the holes just right, and held in place while you solder them. It actually took me several tries to get a working USB socket — you can even see I stripped one of the pads, fortunately that one is unused.
So in conclusion, yes, it is possible to do, no, I wouldn't do it for more than a single unit. It's not practical.
It was a busy weekend, but I did some thinking about the best shape for this little device. If not for the constraints on price and preference to not use cables, I think I would like it to look like this:
Possibly with some kind of a case on the back, to hide the batteries. But of course that's not possible with the constraints I have, and I have a very hard time placing the USB plug with the horizontal version.
I also found some 6x6cm matrices, so it would be possible to make the same thing, only twice as big. Of course they are quite a bit more expensive, and the PCB would be more expensive, but maybe it would be nice to have a bigger version for the teacher? I even started to design it (it was similar to the one above), but then realized that I should focus, and stopped. The matrix is ordered, though, so maybe later.
The battery holders finally arrived, so I replaced the temporary one with a proper one. I must say they are flimsier than I expected, but I suppose I can't complain at this price.
With the device finally physically complete, I got to test how well it lies in the hands and how convenient it is to play on it. I have to say I'm not impressed. The two AA batteries add considerable weight and thickness, which together with the relatively small size of the device and closeness of the buttons doesn't work very well. It's not as bad as some handhelds I have seen, but could be better.
I'm thinking about switching to horizontal arrangement again, with the buttons on both sides of the display. Also, thinking about AAA batteries — they would be enough for a few days of playing still, but a bit lighter. They are slightly more expensive, though.
In the previous log I mentioned that I need a better solution to dim the LEDs — the system tick interrupt doesn't fire fast enough for me to have a good enough refresh rate to do it in software. I decided that I will do this by using PWM on the row pins — I can make the PWM frequency high enough that it won't interfere with the column scanning, and then I have a really nice 16-bit range of shades. However, there is a small problem with this: two of the row pins are connected to GPIO ports that don't support PWM. The culprits are R5 (PA27) and R7 (PA28). I checked if I could perhaps solder the matrix up-side-down to make those pins be connected to columns instead, but no, it works for PA28, but PA27 is then connected to R1.
So I have no choice: a new PCB design is needed. To be honest, that was coming anyways, as the spacing of pads in the USB plug wasn't perfect, and I wasn't proud of the right-angle traces. So I removed the connections, rotated the microcontroller by 90°, and re-connected everything, paying attention to use PWM-capable pins for the rows. I think it came out much cleaner this time:
The "forbidden" pins, PA02, PA03, PA27 and PA28 are all used for buttons now. I will mull over the design a little bit more, and order it from OSHPark this time.
The whole point of building this device is cutting the costs, and the two main tricks for it are using a single-color matrix and simulate the 4 colors with 4 shades, and driving the matrix in software directly from the microcontroller. Obviously, for this to work I need to write the code that would drive the matrix and that would let me display 4 recognizable shades.
Turns out this is more difficult than anticipated.
Initially I thought that I will simply plug the matrix-driving function into the system tick interrupt of CircuitPython. Something like this:
This works reasonably well. There are two problems, though. The system tick happens once every millisecond, so the least bright pixel is getting 41Hz refresh rate (it's only one once every 3 frames, and there are 8 columns to scan). That's slow enough that some blinking is visible. The other problem is that the two brightest shades are really difficult to tell apart — that is because human eye is logarithmic in its sensitivity, so to get good shades, I would need to put them on a logarithmic scale. That means that I need more than just 3 frames, and that makes the least bright shade blink even more.
I can think of three ways of fixing this problem.
The easiest and most hacky way, that I actually tested already, is to simply increase the speed of the system tick timer. Of course that has a side effect of all time-keeping functions also increasing in speed, but we could adjust for that. I'm not sure how much I can increase it before other side effects kick in, though, and I don't like how hacky that is.
The second easiest solution would be to use PWM on the row pins to control the brightness. I can set it to a pretty high frequency, and then refreshing the 8 columns could be done at 125Hz, which is enough for not seeing them blink. I would also get a really smooth scale of shades then. The problem is that my choice of pins for the rows was somewhat random, and not all of them support PWM — so I would need to design a new PCB. I might be able to work around this by rotating the matrix 180° — I need to check if then all the row pins support PWM.
Finally, the most "correct" solution would be to leave the system tick timer alone, and use a dedicated timer with a separate interrupt for doing this. Unfortunately that requires a little more skill than I have at the moment, but perhaps I could learn.