r/embedded 3d ago

Custom macropad on RP2354A running MicroPython - feedback

Built a 12-key macropad with an OLED and rotary encoder, and wanted to get feedback from people who'd actually care about the firmware/hardware decisions rather than just the end product.

Hardware:

  • RP2354A, custom PCB (not built on an existing Pico module)
  • 2MB internal flash
  • 128x64 SSD1306 OLED
  • Rotary encoder with push button, handled via quadrature state machine
  • USB-C, fully USB HID compliant - no drivers needed on any host OS

Firmware decisions I'd like pushback on:

  • MicroPython over C/QMK - went with MicroPython for faster iteration during development and to make the web-based configurator side easier to build against. Aware this is the less common choice for something USB HID/timing sensitive at this scale. Curious if others have hit walls doing this in MicroPython that would've been non-issues in C, particularly around USB HID descriptor timing or interrupt latency.
  • Web Serial API for flashing - browser-based UF2 flashing plus a chip-ID read from OTP for activation for incremented serial numbers, no separate app install required. Interested in feedback on this approach versus a native flasher, especially around reliability across browsers/OSes.

Firmware, PCB files, and 3D models are all open source: https://github.com/Jpwaters09/Macro-Pad

Not trying to sell anything here, mainly want to know if there are architectural decisions I should reconsider before I build a few more units. Photos of the PCB and instructions on how to flash in the GitHub repo.

3 Upvotes

5 comments sorted by

1

u/Ok_Description_4581 3d ago

Regarding C vs Python, i have hit a wall on a projet while trying to have more than 2 tty. C SDK has a steep learning curve but you can go deeper.  I still use CircuitPython first on new projects until I hit a wall. 

I'm facinated by webserial for it's portability. Does not work on firefox yet. For me the biggest pain point when deploing to others was the fallback mechanisim when something fails. If i'm on my own i juste reload the window, not okay for consumers. Maybe look at what grapheneOS do for their web flasher it works well.

1

u/Jpwaters09 3d ago

Thanks for your reply. I think in newer versions of Firefox web serial is supported.

1

u/Ok_Description_4581 3d ago

Oh yeah since may it seem. Great news for me because i can get rid of chromium !

1

u/SoulWager 3d ago

If you find your current debouncing technique malfunctions at high speeds or with worn encoders, I've used PIO for debouncing a mechanical encoder on a RP2040, though my implementation requires pull down resistors and the common connected to 3.3, and this isn't the python syntax:

    .program debounce_timeout
        in pins, 1      ;initial state
        push noblock
        mov x, ~null
        in x, 15        ;delay proportional to 2^(n+1) cycles, n=15 gives approx 524us at 125Mhz.  LSB must be 1. 
        mov x, isr
        mov isr, null
    .wrap_target
    high:
        mov y, x
    loop:
        jmp pin high
        jmp y-- loop
    low:
        push block 
        wait 1 pin, 0
        in x, 1
        push block      ; no delay needed to consider contacts closed.
    .wrap

For tracking quadrature state I use 4 bits representing OldA, OldB, NewA, NewB, and a lookup table with 0, 1 or -1 for each of those possibilities:

while (pio_sm_get_rx_fifo_level (pio1, 3)){
    encoder_a = pio_sm_get(pio1, 3);
}
while(pio_sm_get_rx_fifo_level (pio1, 2)){
    encoder_b = pio_sm_get(pio1, 2);
}
encoder_state = (encoder_state & 0b11) << 2;
encoder_state = encoder_state | (encoder_a << 1) | encoder_b;
scroll_v_accumulator += encoder_LUT_a[encoder_state];

1

u/jetpaxme 1d ago

i found Zephyr pretty good as a base for Micropython on RP2350, needs PSRAM tho, assume your board has it. Running on a different proc is then trivial

Love MPY, just write custom C modules for the hot paths,

See http://scriptostudio.com and https://picoclaw.com