Small project #4 — Serial Knob


Want one? Check out

Is it useful? Maybe. I have a couple of existing projects that would benefit from a rotary knob, but not enough code-space is left to add encoder handling. Serial comms is already working, so this is the simpler way to make it work.



The onboard microcontroller does the decoding, optionally some more processing, and sends byte-sized data to the main project board. The byte contains information about direction, push-button status and maybe even about average rotation speed (encoded in the higher bits). I think I will like it – if it works ;-)

A quadrature encoder has exactly four states, which are determined by the 2 output signals A and B. These states may be called: (00,01,10,11). The signals A and B are 90° out of phase, shifted with respect to each other. Ideally only one signal can change at any given time, never both. That way one can determine which one changes first (A before B, B before A), which carries the information about direction of rotation.


You can use an edge-triggered interrupt to do it, but that requires absolutely clean edges. No switch bouncing tolerated. Otherwise the cpu load would surge. We don’t want that. This is also not about reading a high-frequency signal, so the polling approach is sufficient. We also don’t care about sleep modes, so there’s no need for interrupts to wake up the micro.

As it happens, there is already a timer interrupt running at 1kHz, which provides ‘time’ and ‘delay’ functionality (recycled code from another project). Reading and evaluating the encoder state is simple enough, so we just plug that into the system-ticker routine.

By comparing the current vs. the previous encoder state, one can determine if a step has been made and in which direction. This assumes that every step is recognized. If steps are skipped, there are disambiguities and it can’t be determined for sure what has happened. Time to increase the polling frequency. But for a hand-turned knob with just 24 ticks per revolution this extremely unlikely.

A typical time-sequence of states may look like this:

… – 11 – 01 – [ 00 - 10 - 11 - 01 ] – 00 – 10 – …

Going from left to right could be clockwise rotation, right to left anti-clockwise rotation.

The state names (00,01…) also match the bits that are set or unset in the GPIO input register of the microcontroller. One way to make the decision of what step has taken place is to use a lot of if-statements, but that is a lot of text to write. A simpler way is to create a truth-table of sorts (array), with elements of {+1, 0, -1}, representing the step-direction. The bit-values of the encoder states (previous and current) are joined into one byte, which will become the index of the array. If a combination indicates a valid step forwards, the associated array-element will be ‘+1′. A negative step will of course be represented by ‘-1′. No change or any invalid combination (e.g. from 00 to 11) will get a ‘0’.

The main loop checks for a changed encoder state (flag is set by the interrupt), and processes the data – i.e. send out a status-byte via serial. Done.

Quick and dirty application: Volume control knob for Amarok or kmix. All you need is a 5V USB-serial adapter and a little perl script that listens for data. Oh, and linux of course. Amarok’s volume is controlled using dbus calls. Very simple to do, actually. Part of the demo code for this little project!

As usual you can have all design files and code. Search for “Serial-Knob” and take a look inside the “ATtiny_projects” repo for code: [

How to get started with my demo firmware

I’ll blatantly assume you use linux. From my point of view it is the easier way to get things running. Using virtualbox is a good way for you winblows users.

1) Download the latest Arduini-IDE. Currently this is V1.0.3
2) Unzip in your home folder. Anywhere you like it to be.
3) Download the firmware and save in a nice place. I’ll just call it fw-dir from now on.
4) cd to fw-dir and modify ‘config.txt’ to match Your installation / board.

# relative to your HOME directory


Now the script knows where to find the compiler etc.

5) Make adjustments to the code as necessary.
6) Run ‘./ XX’ to recompile. ‘XX’ can be 25, 45 or 85.

The scripts accepts a parameter to compile for either attiny25, 45 or 85. If you got your ‘Knob’ from Tindie, just omit the placeholder ‘XX’. If you have a different microcontroller (25 or 85), just replace the ‘XX’ with either 25 or 85.

7) Connect your programmer to the ISP header. Orientation does matter.
8) run ‘./ XX’ to program the chip. ‘XX’ can be 25, 45 or 85.

This entry was posted in Electronics. and tagged , , , , , . Bookmark the permalink.

11 Responses to Small project #4 — Serial Knob

  1. Pingback: Fun With Rotary Encoders « adafruit industries blog

  2. kscharf says:

    With optical encoders you are asured of clean edges. I’ve used two interrupt pins to detect edge changes and built an encoder interface that way. Newer AVR’s have pin change interrupts and you can use this as a trigger and then poll to see which pin changed (allows multiple encoders on one micro) With switch (mechanical) encoders bounce is a problem, but you can clean things up with ’14 type inverters and an RC network. For a dedicated encoder controller your approach is fine. Hint: if you are bit-banging the serial port up the baud rate and use the timer interrupt to both poll the encoder and send/receive data.

    • robert says:

      Optical encoders cost a bit more, cost was an issue here. I could get higher rotation speeds out of them. The mechanical ones max out pretty soon. With a 1kHz timer I should get quite a high rate of revolution for a simple knob, but the mechanical switches just don’t deliver. I should take a look at the waveform to see what happens. Probably looks pretty awful.

      Size was also an issue, so I opted to go without any analog signal conditioning. At least the quadrature signals don’t require any debouncing with a timer approach. Using a pin-change interrupt without debouncing is a nice educational lesson. I don’t need this one anymore. The push-button needs some debouncing though. Maybe a bit of bit-shifting will suffice.

      Of course I could have the timer take care of the data transmission as well, but I don’t see the point right now. I only send single characters, so no need for a buffer and non-blocking writes either. Upping the data rate sounds better. I’ll have to check the baud-rate errors for 8MHz system clock first.

      I only used 9600 as the other project for which this device was made as an upgrade uses that. I could change the baud rate for everything, but… never change a running system.

  3. robert says:

    A happy customer ;-)

    Navid Gornall incorporated “The Knob” into one of his creations.

  4. Olfried says:

    this is a great product, no hassling with debugging, either hardware nor software. It took some time to understand what happens and how the knob works. (the function of the DIR-Pin is unclear at all :) ).
    For all noobs of us, here’s an arduino example which uses SoftwareSerial (Not sure if you have to add this to your library folder today – the IDE tells you that SoftwareSerial is not defined)
    so here’s the code:

     test the function of the SerialKnob
     The circuit:
     * RX is digital pin 10 (connect to TX of other device)
     * TX is digital pin 11 (connect to RX of other device)
     based on the switch (case) example from
     and Software serial example
     by Olfried Joergensen
     September 10th, 2014
     tested on arduino pro mini 5V / 16MHz ATMEGA 328
     compiled with Arduino 1.0.5-r2 IDE
     This example code is in the public domain.
    SoftwareSerial mySerial(10, 11); // RX, TX
    int dircounter; // holds the actual number
    int oncounter; // to count how long the knob is pressed
    void setup() {
      // initialize serial communication to arduino IDE
      Serial.println("READY TO GO");
      // initialize serial communication to SerialKnob
      mySerial.begin(9600); // only 9600 is possible
      Serial.println("Softserial Ready");
    void loop() {
      int inByte =; // read the Knob
      switch (inByte) {
      case 0x2F: // the begin of pressing the knob
        Serial.println("Caught / Begin");
      case 0xC2:
        Serial.println("CAUGHT AE char");
      case 0xAF:
        Serial.println("CAUGHT OVERLINE char");
        oncounter++; //how long is the knob pressed
      case 0x5C: // the end of pressing the knob
        Serial.println("CAUGHT \\ END");
        Serial.print("ONCOUNTER = ");
        Serial.print("DIRCOUNTER = ");
        oncounter=0; // reset the oncounter for next events
      case 0x2B:
        Serial.println("CAUGHT +");
        dircounter++; // + 1 to the dircounter
      case 0x2D:
        Serial.println("CAUGHT -");
        dircounter--; // - 1 from the dircounter
        // do nothing
      // if we pressed the knob for a longer time, delete
      // the dircounter
      if(oncounter > 6){

    Hope this helps someone.

    • Olfried says:

      The include statement should be
      #include <SoftwareSerial.h>
      looks like the wp-plugin tries to make a html tag from it :)

    • robert says:

      Yes, the DIR pin isn’t really used. When I made the board I had “Step / Dir” output in mind, for driving some stepper-motor controllers.

      Glad you like the knobs and they work as expected. I do my best to only create properly working devices.

  5. KB9YEN says:

    I’m curious if it would be possible to connect 16 or more of these to a single device.

    If so I might have to get some for a project I’m working on with my Odroid.

    • robert says:

      There are basically two options.

      1) you use a whole lot of usb-hubs + el-cheapo usb-serial adapters from ebay (2 bucks each), one for each device. This would give you a lot of /dev/ttyUSB devices and move all of the complexity into your code. Unless you require more information than just “+” and “-” for ticks and button presses, it should run as is. If you need more info, changes to the firmware would be necessary (e.g. polling for rotation speed). Price-wise this is quite mad.

      2) To share a single communication port, the firmware would need a significant rewrite to make them addressable on a bus (e.g. minimal bit-banged I2C or something completely custom using the partially existing software serial code). That would then imply polling them for data, which may or may not be fast enough for what you have in mind. Certainly doable, but it looks like a sizable amount of work. There are USB-to-I2C modules on ebay. Some should work on linux (FTDI hardware).

      • KB9YEN says:

        The basic idea is to have a 16 step midi sequencer. Then use the linux box as the audio/synth source. Button press toggles a few options (note on, off, glide), and rotation selects note and octave. There are probably other ways but my electronics knowledge is limited and this seemed like an easier solution. I will have to investigate it further.

Leave a Reply

Your email address will not be published. Required fields are marked *



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>