Garmin 430 tuning knobs for X-plane

On X-plane, manipulating the Garmin 430/530 keys using the mouse can be cumbersome. Yoke keys can be assigned for these functions, but there are not enough keys on a typical yoke to make this assignment. One solution is to buy a full blown panel such as this one from Realsimgear. But it is expensive. Besides, I really don’t need the display screen because I primarily use Xplane with 3D VR. The best approach for me was to make a simple panel using rotary encoders, and make it communicate with the computer as key presses. This can be built for under $35. So that is what this article is about.

Garmin 430 panel

The panel consists of just the two rotary encoders. This was a reasonable choice for me because the other keys on the GNS430 are easy to access using the mouse. Besides, too many physical keys would make it difficult to find them under VR.

The heart of the system consists of two dual-rotary encoders with push button switches, and an Attiny 861A microcontroller. You can get Garmin-style rotary encoders from Propwashsim for $13 each. These encoders are the most expensive part of the whole system.

Wave form from the rotary encoder when it is rotated quickly in one direction.

The encoders are not simple potentiometers as one might think. Each click on the knob toggles two SPST switches slightly offset in time from each other. This means, switch #1 will toggle first, followed by switch #2 a few milliseconds later. The sequence will be reversed when the knob is turned in the opposite direction, i.e., switch #2 will toggle followed by switch #1. The microcontroller should sense this timing to determine whether it is a left or right rotation, and then send the appropriate commands through the USB cable.

So, the code has two major parts. The first part is the encoder detection. The second part is the USB communication.

Encoder Detection

The entire code is available on github, but below is the encoder detection part:

tmp = getinput(AorB,pin0);      /* Check Connection 0 */
if (tmp != *state0){            /* If Connection 0 has changed state */
    _delay_ms(delay);           /* Wait for the debounce time */
    tmp = getinput(AorB,pin0);  /* Recheck the Connection 0 state */
    if (*state0 != tmp){        /* If the changed state of Connection 0 is still valid, we can move on to Connection 1 */
        *state0 = tmp;          /* Update the state of Connection 0 */
        tmp = getinput(AorB,pin1); /* Check the state of Connection 1 */
        while (tmp == *state1){  /* Wait for Connection 1 to change state */
            tmp = getinput(AorB,pin1);}
            *state1 = tmp;             /* Update the state of Connection 1 */

The code checks for a change in state (0 or 1) of one of the encoder lines (pin0). A switch debounce delay is used to make sure that it is a persistent change. Then we check the second encoder line (pin1) and wait for that to change. When that change is detected, we can conclude that a one-click turn has occurred. The process is repeated in the reverse direction to detect a turn in the opposite direction.

We have four encoders (each knob has an inner and outer knobs). Additionally, there is also a push-button function on each knob. This makes a total of ten different sensing functions: left/right on each of the four encoders, plus two push button switches. Each one of these functions can be mapped to a key press. In my case, I decided to use A, B, C, D, E and a, b, c, d and e for all ten representations.

USB

Attiny 861A does not have a built-in USB interface. But it is relatively easy to implement this function using the V-USB library. This library mimics the USB hardware function at the software level. The penalty of course is the size of the code (which will inevitably be larger) and the possible reliability issues. But for a simple application like this where the USB device is simply mimicking a keyboard, this should not be a problem. See my previous post on this topic.

Schematic

Schematic

The circuit is relatively simple, though a bit crowded due to the number of wires running between the encoders and the board. I used all surface-mount components except the Attiny861A, which I prefer to have it in a DIP package with an IC socket because it allows me to unplug the chip and flash the program on a separate programming board.

Assembled circuit
Garmin style knobs for flightsim

V-USB on Attiny45/85 Microcontrollers

Attiny 45/85 microcontrollers are very popular among hobbyists because they are cheap (about $1 each) and can be programmed to do many simple functions. But one thing lacking in these microcontrollers is an integrated USB interface. Many larger microcontrollers come with USB interfaces, but they can be an overkill for many small jobs. One way to get around this problem is by using the V-USB library. This library can be compiled with your regular code to allow chips like Attiny 45 to communicate via USB. However, since it is a software solution to a hardware problem, it comes with some caveats.

Clock

The CPU clock is the most critical aspect of USB signalling. This clock has very tight tolerances, and needs to be one of several specified clock rates (12 MHz, 16 MHz or 16.5 MHz). Using an external crystal oscillator is the best way to ensure accuracy. I don’t know about you, but I don’t have these specific crystals laying around in my component collection. So I would prefer to use the chip’s internal oscillator (known as the RC oscillator). Among the three clock speeds, 16.5 MHz is the most forgiving, making it possible to use the internal RC oscillator. The RC oscillator nominally runs at 8 MHz. By enabling the PLL in the fuse bytes, this can be multiplied 8-fold to 64 MHz. However, the 64 MHz only goes to the peripheral devices. It gets divided by 4, and becomes the CPU clock at a nominal rate of 16 MHz.

The 8 MHz RC oscillator can be adjusted by writing a value into the OSCCAL register. Increasing numbers increase the clock rate. Therefore, it is possible to tune the oscillator to get 16.5 MHz.

How to measure the CPU clock

The direct method to measure the clock is by sending it to one of the output pins. This can be done by setting the CKOUT fuse bit, which will send the CPU clock to CLKO output (pin #3). This can be measured using a benchtop oscilloscope. Alternatively, it is possible to divide this clock to a slower speed, and then measure it using the Pokitmeter (which is a small tool that runs on the Android phone). It is a lot more convenient (and cheaper) than a benchtop oscilloscope, and you can do it from your office desk. The code below allows the CPU clock to be scaled down by a factor of 2048 for the Attiny 85 chip.

/* Created: 6/20/2020 6:33:18 PM
 * Author : Andrew Sarangan  */ 

#include <avr/io.h>

/* Fuse bits LOW.CKDIV8 should be unset*/ 
/* Fuse bits CKSEL(3:0) should be set for 64 MHz, which is 0001*/
/* This fuse is combined with SUT, so the byte is called LOW.SUT_CKSEL */
/* The waveform on 0C1B will be F_CPU/4/256/2. */
/* At 16.5 MHz, the waveform will be 8056.6 Hz */

int main(void)
{
	OSCCAL = 82;  /* This adjusts the RC oscillator frequency */
	DDRB |= (1 << PB4);    /* Use the OC1B function of the PB4 pin */
	TCCR1 |= (1 << CS10) | ( 1 << CS11); /* Divide Timer1 by 4 */
	GTCCR |= (1 << COM1B0); /*Sets the OC1B toggle operation when comparison is successful */
	OCR1B = 0;	/* This is the value to compare with Timer1 */
          
	while (1){
         ;
	}
}

By flashing this program into the chip, the output on pin #3 can be used to measure the CPU clock. When the CPU clock is 16.5 MHz, the measured pulse rate on pin #4 will be 8056.6 Hz. By trial and error, the value needed to reach 16.5 MHz was found to be OSCCAL = 82.

Pokitmeter oscilloscope display showing 8064.7 Hz (corresponding to 16.517 MHz)

Voltage Levels

USB power line is 5V, but the D+ and D- lines have to be around 3.3V. This voltage conversion can be done using one of several ways:

  • Use a voltage regulator to drop 5V to 3.3V, and run the chip on 3.3V.
  • Alternatively, instead of a voltage regulator, a diode (or two) in series can be used to drop the 5V supply down to either 4.3V or 3.6V. Although these are not exactly 3.3V, the USB will still detect the signals correctly.
  • We can also run the chip at 5V, and use two 3.3V Zener diodes (small signal diodes, not the regulating type) on the D+ and D- lines to pull the voltage down. This is the least preferred way because the Zener diodes can significantly affect the impedance and the transient response.
Fig 1. Using 3.3V regulator
Fig 3. Using two series diodes on the supply line
Fig 2. Using a series diode on the supply line
Fig 4. Using Zener diodes on the data lines

Test Results

Clock measurements were made using the Pokitmeter, and whether or not the USB connection worked was determined by connecting to a Windows 10 desktop computer. I did not evaluate the accuracy of the frequency measurement. Your results may vary depending on hardware, operating system, component tolerances and environmental conditions.

CircuitVoltageOSCCALSpeedWorks?
Fig 13.3V8116.440 MHz No
Fig 13.3V8216.517 MHzYes
Fig 13.3V8516.826 MHzYes
Fig 13.3V8616.985 MHzYes
Fig 13.3V8717.148 MHzNo
Fig 24.3V8116.328 MHzNo
Fig 24.3V8216.516 MHzYes
Fig 24.3V8616.905 MHzYes
Fig 24.3V8717.067 MHzNo
Fig 33.55V8116.421 MHzNo
Fig 33.55V8216.496 MHzYes
Fig 33.55V8616.960 MHzYes
Fig 33.55V8717.148 MHzNo
Fig 45V8316.611 MHzNo

Conclusions

The V-USB clock frequency and the voltage levels are surprisingly forgiving. At the 16.5MHz mode, the frequency can be as high as 17MHz (which is almost 3%), but it cannot be lower than 16.5 MHz. The corresponding OSCCAL range was about 82 to 86. The voltages on the data line has to be between 3.3V and 4.3V. Using Zener diodes on the data lines did not work for me, even though I verified that the Zeners were correctly clamping the voltage to 3.5V. Presumably, the switching characteristics of the Zeners were inadequate.

Bipolar operation of DC circuits

Normally, a bridge rectifier chip is used to convert AC to a pulsing DC. However, it can also be used to make a DC circuit insensitive to input polarity. This is really handy because I have destroyed more circuits than I like to remember because I accidentally switched the polarity of the power supply.

The downside is that the input voltage has to be about 1.4V higher than the required supply voltage. The power dissipated in the bridge rectifier is 1.4 times the current. This may not be a good solution in low voltage circuits with less head room, but for most normal applications, it is a simple way to protect the circuit from inadvertent polarity changes.