Bottleship Hex 61

A hexagonal prototype PCB using a CH32V003 microcontroller to run two charlieplexed LED matrices of 30 LEDs each (and 1 directly controlled LED). See also: bottleship.

This board is intended to be powered and controlled by an external microcontroller through the I2C interface. The I2C address will be set with a #define in the program, so many instances can share the same bus, simulating the planned bottleship arrangement. However, I have included space for two small push buttons on the back so it could be used on its own, perhaps for controlling brightness or selecting a very simple pattern (as most processing power of this tiny chip is needed to keep refreshing the display).

Updates

May, 2026

Photo of the hexagonal PCBs mounted in a grid within a 13x18 cm white frame on a wooden table in a dark room. The 61 LEDs within each hexagon are lit up to varying levels, creating an uneven illumination within the frame and shine a soft light onto the table top.
Framed and done!

After my breadboard proof of concept with the Raspberry Pi Pico, I've now soldered wires to the back of the boards and mounted them in a frame. It's very pleasing to draw more of a line under this phase of prototyping and have something less wobbly and nicer to look at. For now, I'm driving them with an ESP32-C3 super mini board and started writing a custom component for ESPHome to perhaps be able to control the brightness and animations from home assistant. Of course there's always room for improvements, as in the current configuration the frame rate reached is much lower (closer to 20Hz compared to the 50Hz I was getting before, although I've also re-enabled 8-bit PWM).

March-April, 2026

KiCad rendering of the 61-LED hexagonal circuit board, front (left) and back (right) sides. The dark green PCB has 61 LEDs in hexagonal concentric rings, each placed on a white silkscreen dot and connected to its neighbours by thin white lines. At the bottom is a tab with space for the microcontroller and four power and data through hole pins. The silkscreen pattern on the back mirrors the hexagonal LED pattern, and also  labels for the programming test pads and the power and data pins.
KiCad rendering of the v1 board

I ordered ten copies of this design on FR4 board with assembly (£30) so I could work on debugging the software. I extended my charlie-plexing DMA matrix code to handle the case where there are independent matrices on ports C and D (30 LEDs each), and there is one additional independent LED. This means the effective refresh frequency is increased as there are only six columns to cycle through, not nine as on the previous board. Reducing the number of LEDs from 90 to 61 also freed up RAM use due to the reduced bit stream buffer size, and meant the I2C receiver interrupt handler could be placed in RAM for increased performance. I flashed all boards with the same program, except they each used a different I2C address. The LEDs were a bit dimmer than expected but still plenty to work as a mood light in a dimly lit room, using significantly less current than expected. I think this is because the GPIO pins on the CH32V003 cannot pull all the way to the supply/ground rails when multiple pins are sourcing significant current. Perhaps I will have to actually increase the current limiting resistors and run them at 5V later to get a more significant margin above the LED forward voltage (2.7V).

After connecting it all up on a bread board (with a Raspberry Pi Pico as the I2C sender), I found only eight of the boards were working reliably, and the bus clock was much lower than expected. The bus clock issue was resolved by using much smaller value pull up resistors than I started with (now 1.5K currently), and it now runs at 1MHz and can transfer around 50 frames worth of 8-bit PWM values per second. The other two boards were hanging as soon as the I2C peripheral interrupts were enabled, then started very slowly and only displayed every second frame, but curiously worked well while the debugger was connected. After a long process of elimination (and several smaller and unrelated bugs fixed in the program), it turned out to be related to the debug prints (possibly related to this issue, although it describes the opposite behaviour). Removing all of the prints by wrapping them in an inline function that first checks for DebuggerDidAttach() avoids the print waiting for a debugger; although I can't exactly explain why that would cause this particular effect.

Finally, I have ten working boards, driving a total of 610 LEDs with individual PWM control (7-bit for now to save memory and speed up the refresh cycle), all on one I2C bus and receiving PWM data at around 50 fps from the Pi Pico. Now I get to think about mounting them and write some software on the sender side to create some prettier animations!

I also ordered (unpopulated) flexible versions using the same circuit for the next bottleship-flex-61 prototype.