Adventures In Electronics: PC Volume Knob
I have some rather unusual holes in my education. I never, for example, learned much about electronics. It has always been a mistery, a thing other people knew about.
And then one day I ran into this gadget:
What is it? A volume knob for your PC. It plugs via USB and you can use it to turn volume up or down. I suppose if you press it it mutes audio or something.
It costs 50 dollars. 50 fucking dollars. For a dial.
Sure, it's pretty but how hard can it be?
So, I started looking at how to do it. Here's what I learned first:
- The thingie that spins and gives feedback is called a "rotary encoder"
- I would need something like a microcontroller to ... well, control it.
- It would probably involve soldering.
- There are a bunch of tutorials / instructables on how to do it.
- The components are damn cheap!
The last item is important. When I was a kid in the 70s, electronics was a thing for wealthy kids. I was not wealthy. So, the possibility of doing this sort of thing? With cheap stuff? Sign me up!
So, I did what everyone does to learn stuff in 2019: I jumped into youtube and asked to be taught electronics. And a day later ... well, I know enough to break things and to implement this!
The goal is:
- USB volume knob.
- Pressing it lights a LED
- A button click mutes the speakers
- A longer click enables the microphone while the dial is pressed (push-to-talk)
So, here is the BOM:
- The cheapest Arduino-like thing with a USB interface: Digispark
- A rotary encoder. I used a KY-040 because it's cheap and works.
- A generic LED (red)
- Some breadboard cables.
- A breadboard
- A USB-A male/female cable
- A 1k resistor
A second stage (once I have another Digispark) will involve making it nice, but for now let's make it work.
Here is the wiring, which is probably a pile of crap but works for me (sorry, don't want to learn how to do it properly).
Wiring between the Digispark and the KY-040:
P0 -> CLK
P1 -> SW
P2 -> DT
5V -> +
GND -> GND
I also connected KY-040's SW -> 1K resistor -> LED -> 5V
so the LED turns on when the button is pressed, but that's optional.
IMPORTANT NOTE In order for P1 to work properly, I needed to scratch off a connection to disable the onboard LED so, if that's a problem, you may be able to use P5 instead but P5 is disabled in the cheap Digispark clones. We can't use P3 and P4 because they are needed for USB. So, your choice.
So, here is all the wiring. If the image differs from my description, trust the image because it's working ;-)
Once you have everything wired, we need to work on the software side of things.
I used a couple of libraries:
- SimpleRotary to read the rotary encoder.
- TrinketHidCombo to send keys to the PC.
I had to configure a global shortcut to enable/disable the michrophone. I used the F10 key and the command pulseaudio-ctl mute-input
but you figure out what you want to do.
I wrote a Sketch that does the following:
- When the encoder rotates clockwise: send Volume Up key.
- When the encoder rotates counter-clockwise: send Volume Down key.
- When the encoder is clicked less than half a second: send mute key.
- When the encoder is pressed for more than half a second: send mute-input toggle shortcut.
- When the encoder is released after being clicked more than half a second: send mute-input toggle shortcut.
This way, if you want to mute, just click. If you want to talk, make sure you mute input when the session starts, then click-and-hold and while it's pressed the microphone is enabled. Nice, isn't it?
Does it work? Oh yeah! (No, the music is not coming from the PC, just look at the screen to see what changes) and sorry this video is so crappy.
And here's the code (which is my 1st arduino sketch, but I have been programming for a long time ;-)
#include "TrinketHidCombo.h"
#include <SimpleRotary.h> // https://github.com/mprograms/SimpleRotary
// Pin A, Pin B, Button Pin
// Setting the button to 5 because this code handles it manually.
SimpleRotary rotary(0, 2, 5);
void setup() {
TrinketHidCombo.begin();
pinMode(1, INPUT);
}
void loop() {
static unsigned long time_pressed = 0;
static byte ptt_flag = 0;
byte i = rotary.rotate();
if (i == 1) {
TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_UP);
}
else if (i == 2) {
TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_DOWN);
}
int button = digitalRead(1);
if (button == LOW) { // Yes, clicking the button makes it LOW
if (time_pressed == 0) { // It's a new click
time_pressed = millis();
}
else { // Button has been pressed a while
if ((millis() - time_pressed) > 500 && ptt_flag == 0) {
// Pressed half a second, switch to push-to-talk
// I configured my machine to toggle the input muting when F10 is clicked
TrinketHidCombo.pressKey(0, KEYCODE_F10);
TrinketHidCombo.pressKey(0, 0);
ptt_flag = 1;
}
}
}
else { // Button not pressed
if (time_pressed) {// Has been pressed
time_pressed = 0;
if (ptt_flag == 0) { // Was a short click
// Toggle mute
TrinketHidCombo.pressMultimediaKey(MMKEY_MUTE);
}
else { // Was a long click
// Toggle push-to-talk
TrinketHidCombo.pressKey(0, KEYCODE_F10);
TrinketHidCombo.pressKey(0, 0);
ptt_flag = 0;
}
}
}
TrinketHidCombo.poll();
}