USB volume knob for Windows, Mac OS and Linux – Part 1

In this article I want to take a look at the Atmel ATmega32U4 8-Bit microcontroller, which has a USB 2.0 controller built-in and therefore should enable anybody to make their own USB compatible HID devices. I’ll try to show the process by building a USB volume knob, which will allow the end-user to change the volume or completely mute all sounds on the device it is connected to.I’ll start with a simple test setup. For my experiment I bought an Arduino with the necessary microcontroller. If you want to do this as well, make sure you don’t get a clone or incompatible device, as these microcontrollers usually don’t have the USB capability we’re looking for. So make sure you get a genuine Atmel ATmega32U4 microcontroller.

I also got a generic rotary encoder from banggood, an LED with a matching resistor and a male USB A connector with a suitable cable attached to it. That’s all you will need, as most of the work will be done by the code.

If you don’t want to read the series, you can also watch this video:

Rotary encoders

So, I was talking about rotary encoders. But how do they work? I can imagine, that most people with access to (relatively) modern electronics have already used one of those components at least once in their life. They usually come in the form of a volume knob, which you can turn in both directions and it usually is a multi-turn device, which means you can turn its main axis around multiple times.

Several techniques exist to determine the current rotation of the shaft, but I’ll use a Gray encoded rotary encoder. “What does that mean?”, you might ask.

For the sake of it, let’s imagine, that the rotary encoder is an electrical switch with 8 different positions and three outputs, which can be read by a microcontroller. With 3 binary digits, which are represented by the state of the output pins, you can have 8 different electrical switch states (Because 2^3 = 8).

Now let’s imagine that internally the “switch” is a sensing contact and the part, that rotates is a disk with conductive sections at different positions:

rotary_encoder_0
Figure 1: The disk, that is rotating with the knob.

As you can see in fig. 1, I labelled the sections 0 to 7 and the contacts A, B and C. So let’s say our “switch” is in position 1:

rotary_encoder_1
Figure 2: The disk was moved to position 1

As you can see, our output will look like this: A = 0; B = 0; C = 1; You can also see, that each section is identified by exactly one combination and that adjacent sections have a hamming-distance of 1.

Now let’s rotate the knob over to position two (Note, that the disk is rotating, not the green sensing part, I just kept the disk in position, so it’s easier to read):

rotary_encoder_2
Figure 2: The disk was rotated to position 2

So as you can see, this leaves us the following table:

Sector Output A Output B Output C
0 0 0 0
1 0 0 1
2 0 1 1
3 0 1 0
4 1 1 0
5 1 1 1
6 1 0 1
7 1 0 0

Obviously this was a very simplified example and I tried to explain the underlying concept in a simple way. Another thing to note: Usually you can have a whole lot more of these rings, depending on how many sections you have. Many larger encoders used in cars, robots and other machines use LEDs and light sensors and a slotted disk to determine the position.

Other rotary encoders

The above section describes how rotary encoders should typically work. That doesn’t necessarily mean, that all of them will work that way. For example these cheap no-name rotary encoders from banggood:

encoder
Figure 4: The rotary encoder I used

This encoder uses two binary digits to communicate its state. But unlike described above, you can not determine in which exact position the switch rests at any given time. The data pins will only change, when you turn the knob in either direction and then they both will become either high or low, depending on how you connected the switch. I connected mine with some pull-down resistors, so the resting state will be LOW and the data lines, as well as the push-button, will become HIGH when activated.

This leaves us with the following table (assuming you use pull-down resistors):

Data0 Data1 State
0 0 Initial state (no change)
0 1 Turned left (decrease)
1 0 Turned right (increase)
1 1 undefined (no change)

So as you can see, this is very similar to a gray code rotary encoder. Depending on what exact encoder you get, you might need to find out how it works in detail.

Necessary parts

Quantity Part Notes
1 Arduino Pro Micro Needs an Atmel ATmega32U4
1 Rotary encoder
3 1K resistor Pull-up/down

As stated above, make sure, that the Arduino you buy has an original Atmel ATmega32U4 microcontroller. The cheaper clones sometimes don’t have that exact chip and others might lack the direct USB compatibility, we are looking for in this build.

Also, if you want to use the exact same encoder, you can get these.

Test setup

So the test-setup is pretty close to the finished circuit this time. Here it is:

rotary-test-circuit_Steckplatine
Figure 5: The test circuit

Hook the encoder up to the Arduino according to the following image and table, but make sure not to leave any input pins floating (use pull-down resistors):

rotary-wires
Figure 6: The pinout of the used encoder
Signal name Color Arduino Pin
DATA1 Yellow 8
DATA0 Blue 9
SWD (Switch data) Green 7
+5V Red VCC

Test code

The test code is quite simple, but I still included comments and tried to explain the functions as good as possible:

#define DATA0 9
#define DATA1 8
#define PUSH 7

// A value that can be de- and increased with the rotary encoder
int val = 0;
// These variables will hold the input values of the last cycle
int oldData0 = 0, oldData1 = 0, oldButtonState = 0;

void setup()
{
    Serial.begin(9600);

    // Set the needed pins to inputs
    pinMode(PUSH, INPUT);
    pinMode(DATA0, INPUT);
    pinMode(DATA1, INPUT);

    // And set their initial state to low
    digitalWrite(PUSH, LOW);
    digitalWrite(DATA0, LOW);
    digitalWrite(DATA1, LOW);
}

void loop()
{
    // Read the input pins
    int newData0 = digitalRead(DATA0);
    int newData1 = digitalRead(DATA1);
    int newButtonState = digitalRead(PUSH);

    // Check if the button state changed since the last cycle
    if(oldButtonState != newButtonState)
    {
      // If it did and the new button state is 1 (button pressed down)
      if(newButtonState == 1)
      {
        // Reset the value
        val = 0;
        Serial.println("Count reset!");

        // De-bounce delay
        delay(50);
      }

      oldButtonState = newButtonState;
    }

    // Only change the count if the old states differ from the new
    // states on either pin
    if(newData0 != oldData0 || newData1 != oldData1)
    {
      // Check both data pins and check if the state changed
      // compared to the last cycle
      if(!oldData0 && !oldData1 && !newData0 && newData1)
          Serial.println(--val);
      else if(!oldData0 && !oldData1 && newData0 && !newData1)
          Serial.println(++val);

      oldData0 = newData0;
      oldData1 = newData1;

      // Small delay for de-bouncing
      delay(25);
    }
}

Please note that this code might not work for you. It depends on the encoder used.

USB Human Interface Devices (HIDs)

So the next step is to connect the Arduino to a computer and make it change the volume. But how can we achieve maximum compatibility for our volume knob?

We’ll use the so-called HID protocol, which is a standard nowadays for user-input devices like keyboards, mice, joysticks, graphic-tablets, etc.

I don’t necessarily want to implement the protocol in this article, so I’ll use a pre-made library. However I might do that in a later article. You can get an overview of the HID protocol here.

Final code

Like stated above, we want to turn the Arduino into an HID device, to achieve this, I used the “NicoHood HID” Library. To be more precise, I only used this part of it.

The final code is not very different to the test code. I only added the necessary commands at the appropriate positions in the code, right underneath the Serial-prints and added a bit more of a delay, for de-bouncing purposes.

You can download the code file here.

Last steps

From here, there’s only one more thing to do, which is to put the entire electronics into a nice looking case! In my case (pun totally intended), I decided to go with a cylindrical, wooden case, but you can simply put all the components into any basic case and finish it off with a knob like this one.

However, as this article is already long enough, I won’t cover the details of the case right now. Instead I’ll publish a follow-up later.

Table of contents

Part 1 – The theory, electronics and source code (You are here)
Part 2 – The case and finished product

Image sources

[Figure 1-3] Modified; Original work: Encoder disk, wikipedia.org
[Figure 4] Generic rotary encoder, banggood.com

comment-banner

4 thoughts on “USB volume knob for Windows, Mac OS and Linux – Part 1

Leave your two cents, comment here!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.