-
Shield Connectors
11/18/2017 at 15:51 • 0 commentsOne of the main challenges when making an accessory for the micro:bit is connecting it to the device. The large holes might be convenient for alligator clips, but not for pretty much anything else, and the edge connector is huge and hard to design a PCB for. This time, since I only need two pins for I2C and one for the speaker, I decided to go with bolts. However, I don't have room on the front side of the board for bolt holes (the display and buttons are too wide), so I need them in a "surface mount" version. Turns out that some large pads with vias, together with solder paste and a generous application of the hot air gun work pretty well for that:
I even found some brass bolts without heads dedicated to be soldered to a PCB, but it will be a while before those arrive, so in the mean time I'm testing with regular bolts.
-
Quick and Dirty
10/15/2017 at 21:34 • 0 commentsThe code from the previous log does its job, and is not too slow. But we can do much better! The display is organized into 8 "pages" of memory, each containing 8 lines of the display. Instead of updating every page on every refresh, we can keep track of which pages changes, and also the minimum and maximum column of the changed pixels, and only redraw those. The code below does that:
import microbit class SH1106: move = bytearray(b'\x00\xb0\x02\x10') def __init__(self): self.buffer = bytearray(1024) self.dirty_min = bytearray([0] * 8) self.dirty_max = bytearray([128] * 8) microbit.i2c.init(freq=400000, scl=microbit.pin2, sda=microbit.pin1) for i in range(2): # First two transaction after init always fail. try: microbit.i2c.write(0x3c, b'') except OSError: pass microbit.i2c.write(0x3c, b'\x00\xae\xd5\x80\xa8\x3f\xd3\x00\x40\x80\x14' b'\x20\x00\xa1\xc0\xa0\xda\x12\x81\xcf\xd9\xf1\xdb\x40\xa4\xa6\xaf') def show(self): index = 0 buf = self.buffer for page in range(8): dmin = self.dirty_min[page] dmax = self.dirty_max[page] if dmax: self.move[1] = 0xb0 | page self.move[2] = 0x00 | ((2 + dmin) & 0x0f) self.move[3] = 0x10 | ((2 + dmin) >> 4) & 0x0f microbit.i2c.write(0x3c, self.move) microbit.i2c.write(0x3c, b'\x40' + buf[index + dmin:index + dmax]) index += 128 self.dirty_max[page] = 0 self.dirty_min[page] = 127 def pixel(self, x, y, color=None): if not 0 <= x < 128 or not 0 <= y < 64: return page = y // 8 index = x + page * 128 mask = 1 << (y % 8) if color is None: return bool(self.buffer[index] & mask) elif color: self.buffer[index] |= mask else: self.buffer[index] &= ~mask self.dirty_max[page] = max(self.dirty_max[page], x + 1) self.dirty_min[page] = min(self.dirty_min[page], x)
This is all good, you say, but we had to add 10 lines of code, that now have to be executed. Is it really faster this way, and if so, by how much?
Turns out it's quite a bit faster — almost an order of magnitude. I ran a simple test, where I light up the same amount of pixels as the display has, except randomly, not filling the whole screen, and I do an update after every pixel. The simple code did this in four minutes fifty seconds. The optimized code took forty five seconds. Of course this is an extreme case, normally the speedup won't be as large, as nobody does a refresh after a single pixel, right?
-
Basic Display
10/15/2017 at 21:18 • 0 commentsSo here's basic code for the Micro:bit for handling a SH1106 display, just in case anybody needs it:
import microbit class SH1106: move = bytearray(b'\x00\xb0\x02\x10') def __init__(self): self.buffer = bytearray(1024) microbit.i2c.init(freq=400000, scl=microbit.pin2, sda=microbit.pin1) for i in range(2): # First two transaction after init always fail. try: microbit.i2c.write(0x3c, b'') except OSError: pass microbit.i2c.write(0x3c, b'\x00\xae\xd5\x80\xa8\x3f\xd3\x00\x40\x80\x14' b'\x20\x00\xa1\xc0\xa0\xda\x12\x81\xcf\xd9\xf1\xdb\x40\xa4\xa6\xaf') def show(self): index = 0 buf = self.buffer for page in range(8): self.move[1] = 0xb0 | page microbit.i2c.write(0x3c, self.move) microbit.i2c.write(0x3c, b'\x40' + buf[index:index + 128]) index += 128 def pixel(self, x, y, color=None): if not 0 <= x < 128 or not 0 <= y < 64: return page = y // 8 index = x + page * 128 mask = 1 << (y % 8) if color is None: return bool(self.buffer[index] & mask) elif color: self.buffer[index] |= mask else: self.buffer[index] &= ~mask
This is the naive approach of updating the whole screen every time. It's probably fast enough for most use cases.