Toxicantidote One day build - Mini macro pad
Advertisement
Advertisement

One day build - Mini macro pad

Introduction

After reading a HaD article about needing 'wins' for electronic projects, I started thinking about how to apply this to my own projects. I noticed that I had many projects which had started to feel like a chore because they dragged on. They required more effort than planned, I kept adding features, I got frustrated and cut corners, they just plain did not work, or any combination of those.

What to do then? Follow the advice of the article and give myself a 'win'. But even this is clouded. If I set the bar too low, it feels disingenuous, and I don't get the satisfaction of a real 'win'. Set it too high and I risk this project becoming another failure and/or lost interest. I need a goal that is realistic. There still needs to be a chance of failure, to make me feel like my success is actually worthwhile. I often only have one day a week where I can really sit down and get a few good hours in on a project, so I decide to set myself aside a day specifically to work on this project. This leads to my decision for a project timeline: one day. What can I build, from zero to finished product, in one day that is useful to me? I feel the most satisfaction from a project when it is immediately useful and/or visible to me.

One day places some obvious constraints on the project: it needs to be relatively simple, it needs to use parts and tools I already have on-hand and it needs to use techniques I am already familiar with. Also, so that potential failure isn't as impactful, it needs to be something that is nice to have, not a necessity.

The plan

While I am in my workshop, I like to have music going in the background. This runs off of an old laptop that usually sits on a shelf out of reach unless I am programming something due to limited bench space. If I want to control the music, I have to grab the laptop down or stand up to change it (I know, such a first world problem). But, alas, there's my easy win: make a macro pad that allows me to control my music.

What are my requirements?

What to use? Lets keep it easy and use an Arduino. I already know there are USB HID libraries available for it to that cuts my development time way down. Now my task is pretty much reduced to just a few steps:

That looks pretty achievable, so I set to work.

The parts

There's nothing especially remarkable here. I had a Leonardo Tiny and some pushbuttons buttons with coloured caps. With some small gauge wire and some solder, that's all I really need.

The enclosure

I'm way out of practice with professional CAD tools, so I'm just going to use TinkerCAD. I grab my trusty verneir caliper (possibly my most useful tool I have) and start measuring my buttons, their caps, and the Arduino board.

I create a 3D model of my button with cap on top. The cap gets some extra diameter to allow for movement and print tolerance. I then duplicate this model six times (one for each button) in my CAD work plane and make sure they are symmetricaly spaced with a couple of millimetres between them for clearance and enclosure strength. I add some wire channels and subtract the button models from a solid block. This will be the top half of my enclosure.

Next I create a 3D model of the basic shape of my Arduino board with its USB connector. I subtract this from another block to make the bottom half of my model. Again, I add some cable channels and voids for easy fitment.

With both halves created, I then add some ridges to the top half that are in turn subtracted from the bottom half. This will allow the two halves to slot together nicely. Last of all I add a hole for single screw and nut to hold the whole thing together and sit flush with the enclosure. No fancy inserts here, I haven't tried that technique yet, and this isn't the time for trying new things. I put my 3D printer to work printing the enclosure halves, and move on to the programming.

Programming

There's not much to this, with the USB HID library abstracting away (almost) any difficulty. Now I say almost any difficulty, because it wasn't immediately clear to me that in the context of this library, media keys are not part of a keyboard device, but rather part of a consumer device. Modern keyboards apparently present themselves as multiple HID endpoints. With that minor issue overcome, I set to work writing a simple program that sets each of the button pins to pulled-up inputs and then polls them in a loop, sending the appropriate media key code if the button is pressed with appropriate debouncing.

Assembly

There isn't really much to this. Each button has one side connected to GND, and the other side to its own input on the Arduino. No fancy multiplexing this time - I have plenty of inputs on my Arduino. I simply fit the caps to the buttons, slide them in to the enclosure, solder it all up and slot the Arduino in to the bottom half of the enclosure. I close the two halves together - a perfect fit, and secure them with the screw and nut.

The parts All of the parts waiiting to be assembled

Testing

I connected to a Linux laptop and tested. I can change the volume, mute, play/pause and skip backwards and forwards. To be sure, I test it on a Windows PC and the result is the same. Success!

Conslusion

So, I've designed, build and tested a project, all in a day! Is it a basic project? Yes. Is there room for imrovement? Sure. Do I feel satisfied with what I have made? Absolutely! It's satisfied an itch, and I feel a sense of accomplishment - my 'win' for this project. Plus it kind of looks like a colourful Lego brick knockoff.

Finished The finished macro pad

Resources

I have the 3D model of the two halves on Thingiverse. Arduino code is below. Note that serial debugging statements have been left in this code, and it depends on the USB HID library mentioned earlier.


#include "HID-Project.h"

#define PIN_BTN_PREV 11
#define PIN_BTN_PAUSE A1
#define PIN_BTN_NEXT A0

#define PIN_BTN_VOL_DOWN A2
#define PIN_BTN_VOL_MUTE 10
#define PIN_BTN_VOL_UP 9

#define LED_BOARD 13

bool pressed_btn_prev = false;
bool pressed_btn_pause = false;
bool pressed_btn_next = false;

bool pressed_btn_vol_down = false;
bool pressed_btn_vol_mute = false;
bool pressed_btn_vol_up = false;

void setup() {
  // Sends a clean report to the host. This is important on any Arduino type.
  Consumer.begin();

  Mouse.begin();

  pinMode(PIN_BTN_PREV, INPUT_PULLUP);
  pinMode(PIN_BTN_PAUSE, INPUT_PULLUP);
  pinMode(PIN_BTN_NEXT, INPUT_PULLUP);

  pinMode(PIN_BTN_VOL_DOWN, INPUT_PULLUP);
  pinMode(PIN_BTN_VOL_MUTE, INPUT_PULLUP);
  pinMode(PIN_BTN_VOL_UP, INPUT_PULLUP);

  pinMode(LED_BOARD, OUTPUT);

  Serial.begin(9600);
  Serial.println("Ready for input");
}

void jiggle() {
  // jiggle mouse to disable screensaver. otherwise media keys may be ignored
  Mouse.move(5, 5, 0);
  delay(100);
}

void loop() {

    if (digitalRead(PIN_BTN_PREV) == LOW) {
      digitalWrite(LED_BOARD, HIGH);
      if (pressed_btn_prev == false) {
        pressed_btn_prev = true;
        Serial.println("Previous");
        jiggle();
        Consumer.write(MEDIA_PREVIOUS);
        
      }
    } else {
      pressed_btn_prev = false;
    }

    if (digitalRead(PIN_BTN_PAUSE) == LOW) {
      digitalWrite(LED_BOARD, HIGH);
      if (pressed_btn_pause == false) {
        pressed_btn_pause = true;
        Serial.println("Play/Pause");
        jiggle();
        Consumer.write(MEDIA_PLAY_PAUSE);
      }
    } else {
      pressed_btn_pause = false;
    }

    if (digitalRead(PIN_BTN_NEXT) == LOW) {
      digitalWrite(LED_BOARD, HIGH);
      if (pressed_btn_next == false) {
        pressed_btn_next = true;
        Serial.println("Next");
        jiggle();
        Consumer.write(MEDIA_NEXT);
      }
    } else {
      pressed_btn_next = false;
    }

    if (digitalRead(PIN_BTN_VOL_DOWN) == LOW) {
      digitalWrite(LED_BOARD, HIGH);
      if (pressed_btn_vol_down == false) {
        pressed_btn_vol_down = true;
        Serial.println("Volume down");
        jiggle();
        Consumer.write(MEDIA_VOLUME_DOWN);
      }
    } else {
      pressed_btn_vol_down = false;
    }

    if (digitalRead(PIN_BTN_VOL_MUTE) == LOW) {
      digitalWrite(LED_BOARD, HIGH);
      if (pressed_btn_vol_mute == false) {
        pressed_btn_vol_mute = true;
        Serial.println("Mute");
        jiggle();
        Consumer.write(MEDIA_VOLUME_MUTE);
      }
    } else {
      pressed_btn_vol_mute = false;
    }

    if (digitalRead(PIN_BTN_VOL_UP) == LOW) {
      digitalWrite(LED_BOARD, HIGH);
      if (pressed_btn_vol_up == false) {
        pressed_btn_vol_up = true;
        Serial.println("Volume up");
        jiggle();
        Consumer.write(MEDIA_VOLUME_UP);
      }
    } else {
      pressed_btn_vol_up = false;
    }

    //delay(25);
    digitalWrite(LED_BOARD, LOW);
}

Update August 2024

I built one of these for another system that is accessible via a KVM, with the unit connected directly to the host rather than via the KVM. I found that if the host had blanked its screen, the media keys would not respond and I had to jiggle the mouse first. I have updated the above code so that it jiggles the mouse slightly before sending the media key to try and get around this.