Skip to content

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cube Timer Display

A Stackmat tournament-style 6-digit LED timer display for speedcubing. It listens to a Gen4/Gen5 Stackmat timer over a serial line and shows the solve time in the same style as the official SpeedStacks tournament display: dim idle 0.000, full brightness when you are on the pads, a blinking colon while the timer runs, and three blinks when a solve ends.

Hardware is built around an STM32F030F4P6 driving six large common-anode 7-segment digits (74HC595 + ULN2803 per digit, daisy-chained). A CH340 programming header lets you flash firmware without disconnecting the Stackmat cable.


Features

  • 6 static digits — no multiplexing; one shift register + Darlington driver per digit
  • Stackmat Gen4/Gen5 — 1200 baud, 10-byte packets, 6 time digits
  • Tournament-style UI — dim idle, bright active, colon blink while running, 3× blink at stop
  • No inspection mode — matches typical tournament display use
  • Auto-off — blanks after ~5 minutes without Stackmat packets
  • Exposed Programming Pins — J1 header wired to the STM32 ROM UART bootloader pins

Repository layout

Cube-Timer-Display/
├── Schematics/                 KiCad schematics
│   ├── Cube-Timer-Display.kicad_sch
│   ├── digit.kicad_sch
│   └── digit_colon.kicad_sch   digit 1 (minutes + colon)
├── firmware/                   STM32 firmware (PlatformIO)
│   ├── src/                    Application source
│   ├── build/                  Pre-built binaries
│   │   └── cube_timer_display.bin
│   ├── build.sh                One-command rebuild
│   └── platformio.ini
└── tools/
    └── extract_segment_map.py  Helper for 595 segment wiring

Hardware overview

Block Parts Role
MCU STM32F030F4P6 (TSSOP-20) Display + Stackmat decode
Display 6× 57 mm CA 7-segment + 74HC595 + ULN2803 Daisy-chained shift registers
Input LM393 comparator Stackmat serial into STACKMAT_RX
Power MT3608 boost (~10.5 V +VLED) Digit anodes
Program CH340 (external pogo programmer) UART flash via J1 Programming Pins

Digit chain (shift order): data enters digit 1 (minutes), exits digit 6 (milliseconds). The first byte you shift out lands in digit 6.

Digit 1 uses digit_colon.kicad_sch: the QH bit drives colon LEDs instead of the DP-Decimal Point. Digit 2-6 use QH for the decimal point after the seconds digit.


MCU pin map (U2)

Signal Pin Function
SER_D1 PA5 Shift data into digit 1
SRCLK PA7 Shift clock
RCLK PA4 Register latch
nOE PA6 Display enable / PWM dimming (active LOW)
STACKMAT_RX PA3 Stackmat serial RX (USART1, 1200 baud)
TX_PROGRAM PA9 J1 → CH340 RX (USART1 TX, bootloader)
RX_PROGRAM PA10 J1 → CH340 TX (USART1 RX, bootloader)
BOOT_PROGRAM BOOT0 J1 boot mode
RST_PROGRAM NRST J1 reset

PA3 and PA4 are not on the programming header. J1 uses PA9/PA10, which is exactly what the STM32F030 factory ROM bootloader expects.


Firmware behaviour

State Display
No Stackmat / timeout Blank (auto-off after ~5 min)
Idle on pads 0.000 dimmed
Hands on / ready / stopped time Full brightness
Running Time updates + colon blinks (~500 ms)
Just stopped 3 blinks at medium speed, then hold time

Configuration lives in firmware/src/config.h (brightness, timing, segment map, Stackmat invert, segment test on boot).


Build the firmware

Requirements

Cursor / VS Code: install the PlatformIO IDE extension when prompted (see .vscode/extensions.json).

Quick build (pre-built binary already in repo)

Flash-ready file:

firmware/build/cube_timer_display.bin

Build from source

cd firmware
./build.sh

Or:

cd firmware
pio run -e cube_timer_display

Output is copied to firmware/build/ and also under firmware/.pio/build/cube_timer_display/.

Typical size: ~16 KB flash (97% of 16 KB part — very little room left for big new features).


Flash the firmware

You do not need an ST-Link for normal updates if you use the J1 PROGRAM_PINS header with a CH340 programmer (pogo pins + BOOT0/RST circuit).

J1 pinout (PROGRAM_PINS)

J1 pin Signal Connect to
1 +3V3 3.3 V (optional if the board is already powered)
2 GND GND
3 RX_PROGRAM (PA10) CH340 TX
4 TX_PROGRAM (PA9) CH340 RX
5 BOOT_PROGRAM BOOT0 — high to enter bootloader
6 RST_PROGRAM NRST — reset pulse

UART is crossed: programmer TX goes to MCU RX (PA10), programmer RX goes to MCU TX (PA9).

Boot / reset on the board (R1, R2)

The MCU nets have fixed pull resistors — idle levels matter when the programmer is connected:

Part Net Resistor Idle when J1 pin is not driven
R1 NRST 10 kΩ to +3V3 NRST high (normal run)
R2 BOOT0 10 kΩ to GND BOOT0 low (boot from flash)
  • J1 pin 6 open → R1 holds NRST high.
  • J1 pin 5 open → R2 holds BOOT0 low.
  • To enter the ROM bootloader you must actively drive BOOT0 high (strong enough to overcome R2), then pulse NRST low and release.

If CH340 RTS/DTR are wired to your pogo BOOT_PROGRAM / RST_PROGRAM lines, set their idle levels so they do not fight these resistors when you are not flashing.

Step-by-step (STM32CubeProgrammer)

Use STM32CubeProgrammerUART mode.

A. Manual entry (pogo + transistor reset) — recommended

  1. Set UART options in CubeProgrammer before Connect (see table below). Use RTS = Low and DTR = High so idle serial lines match “run mode” and do not pull BOOT0 high or NRST low through your programmer.
  2. On the pogo: drive BOOT0 high (J1 pin 5), pulse reset (J1 pin 6), release reset while BOOT0 stays high.
  3. Click Connect in CubeProgrammer.
  4. Download firmware/build/cube_timer_display.bin at 0x08000000.
  5. Release BOOT0 (or let R2 pull it low), reset once — firmware runs.

B. If RTS/DTR automate boot/reset on your programmer

Typical wiring: RTS → BOOT_PROGRAM, DTR → reset transistor → RST_PROGRAM.

Phase RTS (BOOT0) DTR (reset circuit) Result on MCU
Enter bootloader High Low (reset) BOOT0 high, NRST low
Release reset High High BOOT0 high, NRST high → ROM UART bootloader
After flash / normal run Low High BOOT0 low (R2), NRST high (R1) → user flash

Sequence: RTS high, DTR low briefly, then DTR high while RTS stays high before Connect. After programming, set RTS low and DTR high before disconnect.

If your reset transistor is inverted (DTR high = reset active), swap the DTR column.

STM32CubeProgrammer UART settings

Setting Value
Mode UART
Port Your CH340 COM port
USART USART1
Baudrate 115200
Parity Even
Data bits 8
Stop bits 1
Flow control Off
RTS Low (idle — lets R2 hold BOOT0 low)
DTR High (idle — lets R1 hold NRST high)

Use the RTS/DTR row as the safe idle for manual pogo flashing. Only drive RTS high / DTR low when you intentionally enter the bootloader (manually or via your programmer sequence).

CLI equivalent for idle lines before a manual boot entry:

STM32_Programmer_CLI.exe -c port=COM3 uart=115200 parity=even rts=low dtr=high
  1. Run after download
    • RTS Low, DTR High (or BOOT0 released / J1 pin 5 open so R2 pulls low)
    • Reset the MCU
    • Display firmware should start

Command line (stm32flash)

With BOOT0 high and a reset into bootloader mode:

stm32flash -b 115200 -m serial \
  -w firmware/build/cube_timer_display.bin \
  -v -g 0 /dev/ttyUSB0

Replace /dev/ttyUSB0 with your CH340 device (COM3 on Windows, etc.).

Flashing tips

  • BOOT0 must be high during reset to enter the ROM bootloader, not only after the chip is running.
  • R2 (10 kΩ) pulls BOOT0 low when J1 pin 5 is open — you need a strong high on BOOT_PROGRAM to enter the bootloader, not a weak float.
  • R1 (10 kΩ) pulls NRST high when J1 pin 6 is open — reset is active low (pull NRST low, then release).
  • Set CubeProgrammer RTS Low / DTR High at idle so CH340 does not accidentally boot or reset the chip when connecting.
  • If connect fails, swap-check TX/RX against the J1 table above.
  • After flashing, BOOT0 must be low (RTS low or pin 5 open) otherwise the chip stays in bootloader mode.
  • If UART is flaky, tie 3.3 V on J1 pin 1 in addition to board power.
  • You can flash with Stackmat and display wiring connected — programming uses PA9/PA10 only.

ST-Link (optional)

SWD (PA13/PA14) is not on J1. ST-Link only helps if you add test pads or a debug header:

cd firmware
pio run -e cube_timer_display -t upload

First-time bring-up

  1. Flash firmware (above).
  2. Connect Stackmat to the comparator input; power the timer.
  3. If time/state decode looks wrong, set STACKMAT_RX_INVERT_DEFAULT = true in firmware/src/config.h and rebuild.
  4. If digits show wrong glyphs, set SEGMENT_TEST_ON_BOOT = true to cycle segments on boot, then adjust SEG_* in config.h if needed (see routed map in that file).

Segment wiring (595 → display)

Bits are routed for PCB layout, not A→B→C order:

595 output Segment
QA B
QB A
QC C
QD E
QE D
QF F
QG G
QH DP / colon

Schematics

KiCad 8+ sheets in Schematics/:

  • Cube-Timer-Display.kicad_sch — main sheet (MCU, power, J1, digit instances)
  • digit.kicad_sch — one digit driver (595 → ULN2803 → 7-segment)
  • digit_colon.kicad_sch — digit 1 variant with colon LEDs

To inspect 595→segment labels from the sheets:

python3 tools/extract_segment_map.py

License

See LICENSE (GPL-3.0).


Quick reference

Task Command / path
Flash binary firmware/build/cube_timer_display.bin @ 0x08000000
Rebuild cd firmware && ./build.sh
Flash docs BOOT0 high + reset → UART 115200 8E1; idle RTS Low, DTR High
Config firmware/src/config.h
Pins firmware/src/board_pins.h

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages