-
Great Success
07/08/2021 at 20:37 • 0 commentsVersion two has the battery holder tabs longer, and they work just right without breaking!
I also left out one of the capacitors — I guess the displays works without filtering its power.
The latest CircuitPython flashed, now time to write some sprite libraries.
-
Version 2
06/23/2021 at 09:41 • 0 commentsSecond attempt, with longer battery tabs — so they will hopefully have enough flex in them to hold the battery without breaking. I also had to re-route some traces, as there wasn't enough room around the holes anymore. Finally, I added fancier silkscreen on the front. I'm thinking this time I will use a red PCB, so that the traces are not so apparent on the front.
-
The pew Library
06/15/2021 at 13:58 • 0 commentsAt the heart of every PewPew device is the pew Python library, which gives you access to the 8x8 screen and buttons. That library is different on different devices, but gives your code a consistent interface, so that a PewPew game written on one device will work on any other. I already had a version of the pew library for the SH1106 OLED screen, but it was written before displaio had support for those screens, and just talks to the screen directly over SPI.
This time I wanted to use the higher-level capabilities of displayio — mostly because then when your program raises an exception, you can see it on the screen and read the error without having to use the serial connection. Unfortunately, doing it the naive way, by using TileGrid, would be too slow. So I went for a kind of compromise — I still generate the data to be sent to the display myself, but then I use the FourWire bus object on the board.DISPLAY to send that data to the display:
def show(pix): pix_buffer = pix.buffer bus = board.DISPLAY.bus for y in range(8): pix_index = pix.width * y index = 0 board.DISPLAY.bus.send(0x0a, b'') board.DISPLAY.bus.send(0x11, b'') for x in range(8): bus.send(0xb0|y, _PATTERNS[pix_buffer[pix_index]]) index += 10 pix_index += 1
The 0x0a and 0x11 commands send the column to 24 (to center the image), and the 0xb0|y command sets the row to the value of y. Then I just keep sending the pre-defined pattern data for the current pixel, in chunks of 10 bytes. Simple yet effective.
Of course now all the PewPew games will work on this device.
-
Adding Display Initialization to the Firmware
06/12/2021 at 18:46 • 0 commentsLast time we got the CircuitPython firmware running on the device, but the display initialization code was all in Python. Today we are going to move it all into the firmware, so the device starts with the display initialized.
We will be putting our initialization code into the board.c file, and it looks like this:
#include "supervisor/board.h" #include "shared-bindings/board/__init__.h" #include "shared-bindings/displayio/FourWire.h" #include "shared-module/displayio/__init__.h" #include "shared-module/displayio/mipi_constants.h" #include "shared-bindings/busio/SPI.h" displayio_fourwire_obj_t board_display_obj; #define DELAY 0x80 uint8_t display_init_sequence[] = { 0xae, 0, // sleep 0xd5, 1, 0x80, // fOsc divide by 2 0xa8, 1, 0x3f, // multiplex 64 0xd3, 1, 0x00, // offset 0 0x40, 1, 0x00, // start line 0 0xad, 1, 0x8b, // dc/dc on 0xa0, 0, // segment remap = 0 0xc0, 0, // scan incr 0xda, 1, 0x12, // com pins 0x81, 1, 0xff, // contrast 255 0xd9, 1, 0x1f, // pre/dis-charge 2DCLKs/2CLKs 0xdb, 1, 0x20, // VCOM deslect 0.770 0x20, 1, 0x20, 0x33, 0, // VPP 9V 0xa6, 0, // not inverted 0xa4, 0, // normal 0xaf, 0, // on }; void board_init(void) { busio_spi_obj_t *spi = &displays[0].fourwire_bus.inline_bus; common_hal_busio_spi_construct(spi, &pin_PA09, &pin_PA08, NULL); common_hal_busio_spi_never_reset(spi); displayio_fourwire_obj_t *bus = &displays[0].fourwire_bus; bus->base.type = &displayio_fourwire_type; common_hal_displayio_fourwire_construct(bus, spi, &pin_PA10, // Command or data &pin_PA01, // Chip select &pin_PA00, // Reset 1000000, // Baudrate 0, // Polarity 0); // Phase displayio_display_obj_t *display = &displays[0].display; display->base.type = &displayio_display_type; common_hal_displayio_display_construct(display, bus, 128, // Width 64, // Height 2, // column start 0, // row start 0, // rotation 1, // Color depth true, // grayscale false, // pixels in byte share row. Only used with depth < 8 1, // bytes per cell. Only valid for depths < 8 false, // reverse_pixels_in_byte. Only valid for depths < 8 true, // reverse_pixels_in_word 0, // Set column command 0, // Set row command 0, // Write memory command 0xd3, // set vertical scroll command display_init_sequence, sizeof(display_init_sequence), NULL, 0x81, 1.0f, // brightness false, // auto_brightness true, // single_byte_bounds true, // data as commands true, // auto_refresh 60, // native_frames_per_second true, // backlight_on_high true); // SH1107_addressing } bool board_requests_safe_mode(void) { return false; } void reset_board(void) { }
This is basically copied from pygamer's board.c, with the initialization sequence and the display arguments copied from the SH1106 driver code. There are, however, three changes I made.
First of all, of course I had to change which pins are being used — this is pretty straightforward.
Then, I had to rotate my display up-side-down. I could have done this with the "rotation" argument of the display, but I want to write some code that writes to the display directly, so I really wanted to rotate it in its initialization. This is what the "segment remap" and "scan direction" registers are for, and I changed them both to 0.
Finally, the way the displays I got are constructed, the first two columns are not connected, so I set "column start" to 2, to compensate for that.
After compiling and flashing this, and removing the Python code from the last time, I get a working display. I also tested that it works powered from a CR2032 battery.
-
Adding the board to CircuitPython
06/12/2021 at 18:33 • 0 commentsLast time I went all the way up to flashing an UF2 bootloader on the SAMD21 chip, now we need to get CircuitPython running on it. Normally I would just use the firmware for #Fluff M0 — it has all the pins available, so it's very convenient for such things, but this time we are going to need the displayio module compiled into the firmware, and later we will also add the display initialization, so that the display just works as soon as the device is switched on, without us having to initialize it in our own code.
To add a new SAMD board, the easiest way is to copy an existing board definition, like the fluff_m0 one, in ports/atmel-samd/boards, and rename it. Then we can edit the files to make our changes:
mpconfigboard.h
#define MICROPY_HW_BOARD_NAME "PewPew OLED" #define MICROPY_HW_MCU_NAME "samd21e18" #define MICROPY_PORT_A (0) #define MICROPY_PORT_B (0) #define MICROPY_PORT_C (0) #define CIRCUITPY_INTERNAL_NVM_SIZE 0 #define CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_SIZE (48 * 1024) // USB is always used internally so skip the pin objects for it. #define IGNORE_PIN_PA24 1 #define IGNORE_PIN_PA25 1 #define SAMD21_BOD33_LEVEL (6)
There are three noteworthy things here. We change the NVM size to 0 (this is a part of the flash reserved for storing additional information, we are not going to be using that), and the filesystem size to 48kB (from the default of 64kB). This is needed to make more room for the firmware in the flash, because we want to include displayio, which is quite big.
Finally, the SAMD21_BOD33_LEVEL variable controls the brown-out detection voltage level. We need to set it as low as practical, so that our device can work from a puny 3V battery without going into safe mode.
Later on we can add all the pins we are not using on the device, to save some RAM. It's not important right now.
pins.c
#include "shared-bindings/board/__init__.h" STATIC const mp_rom_map_elem_t board_global_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_PA09) }, { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_PA08) }, { MP_ROM_QSTR(MP_QSTR_OLED_DC), MP_ROM_PTR(&pin_PA10) }, { MP_ROM_QSTR(MP_QSTR_OLED_CS), MP_ROM_PTR(&pin_PA01) }, { MP_ROM_QSTR(MP_QSTR_OLED_RESET), MP_ROM_PTR(&pin_PA00) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_UP), MP_ROM_PTR(&pin_PA02) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_DOWN), MP_ROM_PTR(&pin_PA05) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_LEFT), MP_ROM_PTR(&pin_PA03) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_RIGHT), MP_ROM_PTR(&pin_PA04) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_O), MP_ROM_PTR(&pin_PA06) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_X), MP_ROM_PTR(&pin_PA07) }, }; MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table);
This defines which pins on the microcontroller are used for what.
mpconfigboard.mk
USB_VID = 0x239A USB_PID = 0x80B0 USB_PRODUCT = "PewPew OLED" USB_MANUFACTURER = "Radomir Dopieralski" CHIP_VARIANT = SAMD21E18A CHIP_FAMILY = samd21 INTERNAL_FLASH_FILESYSTEM = 1 LONGINT_IMPL = NONE CIRCUITPY_FULL_BUILD = 0 CIRCUITPY_DISPLAYIO = 1 CIRCUITPY_TOUCHIO = 1 CIRCUITPY_ANALOGIO = 0 CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_AUDIOBUSIO_I2SOUT = 0 CIRCUITPY_AUDIOCORE = 0 CIRCUITPY_AUDIOIO = 0 CIRCUITPY_AUDIOMIXER = 0 CIRCUITPY_AUDIOMP3 = 0 CIRCUITPY_AUDIOPWMIO = 0 CIRCUITPY_BITBANG_APA102 = 0 CIRCUITPY_BITBANGIO = 0 CIRCUITPY_BITBANGIO = 0 CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_BLEIO = 0 CIRCUITPY_BUSDEVICE = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_FREQUENCYIO = 0 CIRCUITPY_GAMEPAD = 0 CIRCUITPY_GAMEPADSHIFT = 0 CIRCUITPY_I2CPERIPHERAL = 0 CIRCUITPY_MATH = 0 CIRCUITPY_MSGPACK = 0 CIRCUITPY_NEOPIXEL_WRITE = 0 CIRCUITPY_NVM = 0 CIRCUITPY_PIXELBUF = 0 CIRCUITPY_PS2IO = 0 CIRCUITPY_PULSEIO = 0 CIRCUITPY_PWMIO = 0 CIRCUITPY_RGBMATRIX = 0 CIRCUITPY_ROTARYIO = 0 CIRCUITPY_ROTARYIO = 0 CIRCUITPY_RTC = 0 CIRCUITPY_SAMD = 0 CIRCUITPY_ULAB = 0 CIRCUITPY_USB_HID = 0 CIRCUITPY_USB_MIDI = 0 CIRCUITPY_USB_VENDOR = 0 CIRCUITPY_VECTORIO = 0 CIRCUITPY_DISPLAY_FONT = $(TOP)/ports/atmel-samd/boards/ugame10/brutalist-6.bdf OPTIMIZATION_FLAGS = -Os
You can see a looooong list of disabled modules here. That's because I was trying to reclaim as much space as possible for displayio, before I gave up and simply shrunk the filesystem. There is also a custom tiny font. For now I'm using the same VID/PID as the fluff_m0, I will need to ask for unique ones when I will be adding that board to CircuitPython repository.
And that's it, the board.c file is not needed for us right now — we will use it for display initialization later on, though.
Next, I just looked for an SH1106 display driver for CircuitPython and displayio on the Adafruit Github, compiled the new board, and we have a working device:
-
First Prototype Failure
06/11/2021 at 22:02 • 0 commentsThe boards finally arrived, and I got a chance to test the battery holder:
The tabs can just barely be lifted enough to insert the coin cell in place, but it's extremely easy to break them. On the other hand, they hold the battery in place quite well, so I think this will work, I just need to make the thin parts of the tabs much longer.
Having tested that part, I went ahead and assembled one board, with a regular battery holder this time:
I have the bootloader flashed, now I need to prepare the board definition for CircuitPython and get display to work on it.
-
Designing the Battery Holder
06/11/2021 at 21:57 • 0 commentsTouch buttons and a USB socket are relatively easy to make, and I did them before. But a battery holder is a new thing, and it requires some thought. I made a number of sketches:
But eventually I decided to got with the simplest possible design that just might work:
Here is how it's supposed to work:
I'm not entirely sure how elastic the PCB will be, and how much I will need to bend it, so I assume some experimenting will be needed. I started with relatively short but thin tabs. I also included a footprint for a regular battery holder on top of it, so that if my first design doesn't work, I can still use the boards to test and prototype other parts.