Back to Posts

Macropad? Hell yeah!

Helldivers 2 is pretty fun to play with friends. Using a 60% board and having to stop to punch in ⬆️ ⬇️ ➡️ ⬅️ ⬆️ Stratagem codes while being chased by a bunch of bugs that puke murderous venom is not. Enter The Hellpad.

The idea came from my friend while we were in the middle of difficult mission and we kept dying trying to evade enemies. While he went and bought one, I realized I wanted to make one.

The build is heavily inspired by Tom's Hardware's Custom Macro Pad guide with a few modifications to suit my needs.

My Requirements

  1. It should look and feel similar to the profile of my RAMA Works Kara HHKB.
  2. It should only be 4 keys with direct keycode input, not macros. This isn't for cheats!
  3. It should use USB C becuase its 2024.

I made a quick prototype, inbetween game sessions and wired it up with my breadboard Raspberry Pi Pico and "It worked." Welp, time to get busy with a few revisions and what I finally ended on.

Prototype

Quick prototype, printed with lots of squishing and quickly soldered using a Pico intended for a breadboard, using the code exactly as shown in Tom's Hardware guid.

V1

This one was actually thought through and designed to be sturdy and match my keyboard profile. In my head, 4 in a row, while non traditional was the way to go since I do have 4 digits to use! In practicallity, it messes with whats engrained in my muscle memory. Also this was where I realized the keycodes were constantly triggering when held down causing some mishaps in my first couple test games. This chip also died! I am unsure of what happened but it seems to be bricked. I was attempting to restore it using a nuclear solutions from Raspberry Pi.

V2

With V2, I went back to a traditional keyboard arrow cluster. I also made improvements to the plate as the keys easily popped out. For V3, I will reduce the plate by around 1mm as its slightly too thick and while the switches are in there tightly, if its slimmer they click into place.

Current Code

The code provided is a great jumping off place but I did run into an issue. If you hold down the key, it send an endless number of key commands, the same as if you were to hold a key in notepad or another text editor. Great for those games, less great for a game where its expecting exact keycodes. Enter a state machine.

By leveraging a state machine, we only send the keycode when the button is first pressed. Testing in the Statagem Mini Game proved out this works.

# code.py
import time
import board
import digitalio
import usb_hid

from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode

# Setup buttons
btn_pins = [board.GP3, board.GP4, board.GP5, board.GP6]
buttons = []
for pin in btn_pins:
    button = digitalio.DigitalInOut(pin)
    button.direction = digitalio.Direction.INPUT
    button.pull = digitalio.Pull.DOWN
    buttons.append(button)

# States to track if a button was already pressed
btn_states = [False] * len(buttons)

keyboard = Keyboard(usb_hid.devices)

def check_button(index, keycode):
    # Check if button is pressed
    if buttons[index].value and not btn_states[index]:
        keyboard.send(keycode)
        btn_states[index] = True  # Mark as pressed
        time.sleep(0.02)  # Debounce delay
    elif not buttons[index].value and btn_states[index]:
        btn_states[index] = False  # Reset state on release

while True:
    check_button(0, Keycode.UP_ARROW)
    check_button(1, Keycode.DOWN_ARROW)
    check_button(2, Keycode.LEFT_ARROW)
    check_button(3, Keycode.RIGHT_ARROW)
    # Reduce sleep to make loop more responsive but still avoid too fast polling
    time.sleep(0.02)

Ok cool, now for the Issues:

USB Storage

By default, when you plug in the device, it shows up as a storage device. To disable this, you can add a `boot.py` file in the root of your device with this simple code to disable the device from showing up as a storage drive.

# boot.py
import storage

# warning: set this once and you won't be loading any more files
storage.disable_usb_drive()

Hold your horses. That will do what we want with unintended consequences. What if we want to get back to storage mode to change the running software!? Ok, this was where I failed. My first attempt, I didn't even consider this and had a device that I could no longer program with some bad python that needed debugging. In rev 2, I followed some advice and tried to check if a button was pressed while booting to launch it into storage mode, but, it didn't work. For now, I am stuck with a storage device, which is fine as I am still coming up with ideas about how I want to build this as well as learning what I can do with the Pico.

Random Disconnects

Since I am leaving the device in USB Storage mode for now due to some bad boot code, I have noticed the device likes to periodically disconnect and reconnect. This is NOT GREAT when you are dealing with bile titans and liberty depends on you calling in your friends. I haven't had time to troubleshoot this much, but I'm hoping to find a solution. Could it be related to the chip not being an official Raspberry Pi Pico?

Conclusion

Ok, so how does it work!? Pretty well with a couple caveats. Every once in a while there seems to be a delay in registering repeated keys. I am still adjusting the sleep time and seeing if I can even get rid of it, but below 0.02 seconds was not always registering the hit.

Being able to punch in codes while fleeing an aggressive swarm of bugs or robots is great, albeit something I need to get more used to. But Phil, why don't you just use a 65% board with a key cluster like that nice Vega you have sitting in your closet? C'mon!

Whats next? I am going to laser cut some custom feet from some 3M rubber as the handcut ones look rough. I would also like to redesign the bottom plate so it could contain a screw on weight, similar to how a lot of mechanical keyboards are built now. Adding a little heft to it will ensure it stays put.

Pie-in-the-sky ideas? Well, I've got a little screen that came with my Pico kit. I would like to build a mini version of Stragagem Hero into the controller that can be operated independently of the game. Using that code, it could be used to show the current key input commands and a success/failure message after each input while in the game.

Resources

© 2024