05/16/2019 at 09:04 •
It took some time, but the PCBs are already here (USB plug for scale):
I'm not sure I like the new style of mousebites from @oshpark — I can see how they save on drills, and it's probably also much faster, but I can't remove them with a knife anymore — I have to use a rotary tool. Then again, with the rotary tool it looks cleaner, so maybe it's not so bad.
Also, looking at the number on the sticker, I think I might have missed a couple. Oops.
I have now almost all the parts, except for the buttons — they are on the slow boat. Unfortunately, I used the buttons as jumpers, to make some of the GND connections and save on vias, so it won't work without them.
04/27/2019 at 21:32 •
This week I'm paritcipating in Ludum Dare, trying to write a game from scratch in 48 hours, so not much time for hacking, but an interesting display arrived in the mail, so of course I had to give it a try:
It's a STN 9664 LCD display, with an ST7585 controller chip, 96×64 resolution and a bonus row of icons along the top edge. The documentation is sparse, as usual, and I didn't yet get it to work yet with a quick attempt at it.---------- more ----------
What makes it interesting for PewPew, apart from the friendly price point of $1.35 in singles? While it certainly takes more power with the backlight enabled, it can also work without the light, and then I would expect it to be super power efficient, which matters if I want to power it from a coin cell battery.
I'm also considering a number of other cheap displays on the market, but the main problem with them is that they are mostly out of production already, and hence it might be tricky to get a larger quantity of the device produced.
04/25/2019 at 15:47 •
So the Pew library is now ported. It's still not as fast as on the other boards, I might need to rewrite it in C after all, but it's a good first step for testing.
The heart of the whole porting effort is the function that draws those dithered pixels. It's surprisingly simple:
_PATTERNS = ( b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\x44\x11\x44\x11\x44\x11\x44\x11\x44\x11', b'\xee\xbb\xee\xbb\xee\xbb\xee\xbb\xee\xbb', b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff', ) def show(pix): pix_buffer = pix.buffer display_buffer = _display.buffer index = 24 for y in range(8): pix_index = pix.width * y for x in range(8): display_buffer[index:index+10] = _PATTERNS[pix_buffer[pix_index]] pix_index += 1 index += 10 index += 48 _display.show()
Basically we have pre-recorded images of the pixels, that we copy into the buffer at the right places based on the image to be displayed. I also tested doing this on the fly while sending data to the display, and with a per-line buffer, but that didn't make it faster, and resulted in some tearing effects — sending the whole frame all at once to the display has the advantage of it appearing instantly and whole.
I might try keeping track of dirty pixels, and only update those, and maybe even updating the display buffer right away when the drawing operations are happening, though that would require changes in Pew's architecture. Most likely I will probably keep the current logic and just move it to C to make it faster.
04/25/2019 at 08:20 •
With the display working, it's time to have some blitting fun. How about the good old classic bouncing ball?
We have a background of random dots, and a round ball, with black outline, bouncing around the screen on top of it, passing outside the screen edges. Those details are all important, for example we don't want to only be able to handle rectangular sprites, or to crash when a sprite is partially outside the screen. So how do we do that?---------- more ----------
We need two functions, which I have called "blit" and "erase". Let's start with "erase", because it's simpler: it erases a given area of the screen, overwriting it with the background image we have saved. It's simple enough:
def erase(self, x, y, background, size=8): buffer = self.buffer width = self.width if not -size < x < width: return if x < 0: size += x x = 0 if x > width - size: size = width - x page = y // 8 if 0 <= page <= 7: index = x + width * page buffer[index:index + size] = background[index:index + size] page += 1 if 0 <= page <= 7 and y % 8: index = x + width * page buffer[index:index + size] = background[index:index + size]
First we save the class attributes to local variables, just to gain a little bit of speed and avoid one lookup. Then we check if we should even bother displaying anything — if it's out of bounds, just return. Then we check the case when it's just at the edge — we simply adjust the size and position of the area then, to only handle the part that fits on the screen. Finally we copy the relevant portions of the background to the two pages of buffer.
Oh, I forgot to mention, we only handle sprites that are 8 pixels high, but any number of pixels wide. We can make larger sprites by stacking several on top of each other, if we need, and we can have smaller ones by manipulating the mask, as I will explain later on.
Great, we have a way of deleting anything we have drawn on the screen now, which is important for animation. But we still don't have a way of drawing anything. How do we do that? With the blit method!
The blit method takes some data, with an optional mask, and copies them to the specified coordinates of the screen. More specifically, the bits provided as mask are AND-end with the pixels already in the buffer, and then the bits given as data are XOR-ed with it. That lets us make some empty space with the mask first, and then put the white pixels in there. We could have OR-ed the white pixels instead, but with XOR we can have more interesting effects when we don't use the mask. The code follows:
def blit(self, x, y, data, mask=b''): buffer = self.buffer width = self.width size = max(len(data), len(mask)) if not -size < x < width: return if x < 0: data = data[-x:] mask = mask[-x:] size += x x = 0 if x > width - size: data = data[:width - size - x] mask = mask[:width - size - x] size = width - x page = y // 8 shift = y % 8 if 0 <= page <= 7: index = x + width * page for byte in mask: buffer[index] &= ~(byte << shift) index += 1 index = x + width * page for byte in data: buffer[index] ^= byte << shift index += 1 page += 1 if 0 <= page <= 7 and shift: shift = 8 - shift index = x + width * page for byte in mask: buffer[index] &= ~(byte >> shift) index += 1 index = x + width * page for byte in data: buffer[index] ^= byte >> shift index += 1
First we do the same kind of magic for handling screen edges as before. Then we apply the mask and the data to the two possible pages that they can touch, bit-rotated appropriately. Unfortunately we can't use the slice notation here, which is a great shame, because it would be faster.
And here is a simple program that tests this:
for x in range(1280): display.pixel(random.randint(0, 127), random.randint(0, 63), 1) box = b'\x00<fZZf<\x00' mask = b'~\xff\xff\xff\xff\xff\xff~' x = 0 y = 0 dx = 1 dy = 1 background = bytearray(display.buffer) while True: display.blit(x, y, box, mask) display.show() display.erase(x, y, background) x += dx y += dy if not -12 <= x < 128+4: dx = -dx if not -12 <= y <= 64+4: dy = - dy
And the effect:
04/24/2019 at 17:07 •
This is not my first encounter with those small OLED display modules. We actually go back a while.
First I tried to use it for the #Micro:Boy project, and I kept failing to get it working so much, that in the final version I decided to just use one of the ready modules. And of course after I did that and redesigned everything, I came back to the initial project and discovered that the display itself was physically faulty. But the project moved on, and I discovered other reasons why it won't work (not enough memory and code space on the micro:bit for the kind of games I wanted).
Second attempt was with the #CircuitPython Badge, where one of the prototypes I've build used that display. I used the same display module, but in 4-wire SPI configuration this time. I wasn't able to get it to work, and I've moved on to e-ink displays, and later, after rethinking the whole thing, to LED matrices, because of the specifics of the project (the display needed to be big, not necessarily high-res).
And here I am again, with the accursed display, in 4-wire SPI mode again. And again, I looked at the application notes in the datasheets both for SSD1306 and for SH1106, I looked at Adafruit's schematics for their breakout, and I am none the wiser. The connections are exactly the same as on that badge, and there is really nothing wrong with them, at least as far as I can see. Sure, all three sources of data use wildly different values for the capacitors — from 1µF, through 2.2µF, up to 4.7µF and 10µF. So which ones are correct? Who knows? Will the display work with my new PCB, if it didn't with the old one? Will I ever be able to get those things to work?
Facing all those questions and doubts, I decided to take arms against them, and answer them once and for all. I dug out of my drawers the failed badge prototype, and decided to try and get it to work. I started by finding the designs files for it, and re-creating the board definition for CircuitPython, so that I can use the latest version (it includes displayio module, which I will need to get to work with those OLED displays later on). Then I flashed it, and dug up some of my old libraries for MicroPython for this display, and quickly converted it to CircuitPython. A quick test, and of course it doesn't work. Dead.
Fine, as a next step, I tried all three combinations of capacitor values. No difference. Measured the voltages on those caps — hmm, looks like the charge pump is not even starting...
I de-soldered the display from the prototype, and from a known good breakout, and swapped them. Tried the breakout module with an Adafruit board I had lying around, using Adafruit's SSD1306 libraries. Ah-ha! The display did start, but it's an SH1106, not SSD1306. But it's not faulty, so it should be at least showing something, like in this breakout. Why doesn't it?
Next I tried the display from the known-good breakout in the prototype, with the code I had on it. Nope, doesn't work. And I'm sure this one is an SSD1306 and working. So something is wrong with the prototype. The charge pump is not switching on, so perhaps something with communications?
I checked the continuity for all the control pins — everything looks correct. I re-soldered them, just to be sure. Still nothing. It seems that physically everything is fine...
Wait a minute, what if I used those Adafruit libraries on this prototype? I copied them over and lo and behold! The display works! It was my crappy code all this time!
I re-soldered all the displays back to their places, and changed the init sequence to a minimal example from my tutorial — just enabling the charge pump and setting contrast.
And it works perfectly fine (the pattern on the display is the refresh of the OLED interfering with my potato camera).
That means not only that the PCBs I ordered are going to work, but also that I can already start testing code on this prototype.
04/22/2019 at 11:45 •
A few more hours finishing the display circuit (oh boy, does the SSD1306 datasheet suck in terms of example application), a bunch of capacitors added, and I think it's ready. Oops, a small DRC fail because I changed the USB connector. No problem, we will move that trace, thank you DRC! And now ordered from OSHPark.
We will see how that goes. Now I we can bet on what comes first: the buttons, the display, or the PCB?
04/21/2019 at 22:56 •
I had some time over the holidays, so I made some progress on the PCB. A new footprint for the buttons and for the screen connector, routed everything except for the screen. Next I will need to add a truckload of capacitors for the screen, and bunch of vias for the display signals. I think I will go with SPI, because it's two resistors less, it's faster, and I have the pins for it anyways. The whole PCB is 32x32mm right now.
04/17/2019 at 22:48 •
With the display tentatively chosen (we will see how well that one works, and if it doesn't, there are many other possibilities) I have started to look into adding all the rest of components and arranging them. The second biggest part, and one with which the user will have intimate contact, are buttons.
Of course for the price, going for simple capacitive touch pads, or cheap rubber dome buttons would be the best. But we do want to have at least a modicum of tactile feedback and comfort. So tact switches it is. I looked through what is available from the usual Chinese sellers, and mocked up a couple of layouts:
You never before noticed how huge those buttons are, did you? Well, turns out the smallest you can get are about the size of a 1206 SMD resistor. I used similar buttons in the nGame before (https://hackaday.io/project/27629-game/log/87435-ngame-revisited) but they were horrible — designed as reset buttons for development boards, they require a lot of force to be pushed. This time I decided to try something more similar to the C&K PTS815 switches — they have a bigger cap and hopefully require less force. We will see. They do have 4 pins, though, which is a bit of an annoyance.
But this would be too easy. I also want compatibility with PewPew Standalone boards in terms of the connector for the extra pins. That is 12 pins with 2.54mm pitch, so a bit difficult to fit, especially since the top edge is going to be taken up by the flex ribbon of the display. So I came up with something like this:
It's a bit bigger than initially anticipated, with some margins around the display — that should make it a little bit easier to fit everything. And it has the connector, and some free PCB real estate on the opposing margin, which can be useful for actually getting that thing mounted in a case, if it comes to that.
I'm sure the design will still change — I might decide to not put components under the display, for example — but this is at least some starting point.
04/16/2019 at 21:06 •
The LED matrices I'm using in all the other PewPew are great for several reasons: they have large pixels and small resolution, perfect for the kind of games I want to teach, where you don't have to worry about graphics. They are made of plastic, so are robust, scratch-resistant and light. And they only have 16 pins for soldering, and can be easily soldered by hand. But can there be anything cheaper?
How about individual LEDs, like on the MicroBit? They are cheap, right? Well, the components themselves are cheap, but the soldering of 128 pads is not. And forget about doing it by hand. Plus, they don't look very good.
Hmm, what else has a lot of LEDs in it, but only a few connectors, and can be had for cheap?
How about those tiny little OLED displays? They only require a few pins to control, they are cheap, low-power, and they have more LEDs on them than we need. Sure, they are also usually monochrome, but because they have much higher resolution than we need, we can use dithering to get our required 44 shades:
We even have some room left on the sides for some indicators!
Plus, if we added support for them to displayio, we could even have proper error messages displayed on them when something happens. That's an improvement in functionality.
(As an added bonus, it will probably be able to run arduboy games...)