Wind Direction via 4 bit Gray Code Optical Encoder

I am trying to get the best (=fastest/smallest) code to get a serialprint output of one of 16 directional codes based on 4 IR phototransistors at the A0-A3 analog inputs using an optical encoder. I can post the Gray Code, but that's the easy part. I have thought about array and if statements, but I'm not sure which is the best, or if there's a better way. Any suggestions???

If you've got an array (aka "lookup table") there's no need for "if" statements. Why are the inputs analogue?

Thanks so much for the responses.

It sure looks like an array (aka lookup table) is the best bet. I am using the analog inputs since I am trying to keep power to a minimum, and some of the ir detectors are only giving me an output of 3.8 V when active. . .not enuf for digital 'high' I suspect.

If you have any suggestions or examples, I'd love to see them. Otherwise I'll look around for array info and see what I can kluge.

The project background is an old (but well constructed-by me [smiley=vrolijk_1.gif]) weather station (aka Heath ID-4001) that I am going to make wirelesss. Here in South Carolina, anything higher than a weed is attractive to Thor. And any wires up to the roof are a sure target. So, having had this weather station for the last 30 or so years, and not seeing any with good/fast refresh rates, I am hell-bent on making this work. I have gotten the wind speed all set using an interrupt with 750 millisecond updates. After I get wind direction done, I will be putting a 433MHz wireless link in to make this all come together. I would then be able to have both the original console (very wife friendly) operating as it was (wirelessly) AND have a handy Duino (geek friendly) that I can use around the house wherever I am to display conditions.

Thanks again!

Actually I have a mini pro to be delivered Saturday. I will give that a try since that will be the platform that is in the sky. I wasn't aware of the different input voltage spec. As it turns out, although the Atmel is spec'ed at 0.7Vcc, I find that at 3.6, it just doesn't sut it reliably. Perhaps I could 'tune' the input divider to opptimize.

You might want to consider putting opto-isolators between your wind direction sensor and the rest of your system... it might diminish the chance of a lighting induced problem, among other things. If you do, I would do it at ground level, not up in the sensor head, where they will be a pain to get at WHEN (not if!) servicing becomes necessary.

A good lightning rod near your sensor might also be an idea. I am of the school of thought which says that lighting rods PREVENT strikes in their area, not "take the hit" to protect nearby alternative targets.

Opto-isolators explained at....

(Yes, I did see that you are currently treating the outputs from the wind sensor as analog signals. Opto-isolators have the additional benefit of turning those signals into digital signals.)

Agreed. I will explain. The wind vane contains 4 discrete IR emitters and detectors along with an encoder disk (Heathkit optimized to prevent IR bleed) that ouputs Gray code on 4 lines. Detectors are IR transistors, and the collectors of all detectors are powered by the +4.98VDC line. The emitters yield an output that is being monitored by analog inputs A0-A3 that each have a 100K resistor to ground. The output voltage levels from 2 of the detectors are 3.56VDC (H) / 0.185V (L) and the other 2 are 4.88v (H) / 0.050V (L). (2 devices were changed after a 'near miss' years ago). The latter have no problem triggering the digital inputs, however, the former (3.56) will not trigger the digital inputs using Arduino Uno. I am open to any suggestions on how best to get 16 compass points from these outputs.


what exactly are your requirements for small and fast. What kind of sampling rate do you want to achieve or what are you space constraints?

The other interesting questions is why you don't hook up your photo-transistors to digital pins (preferably 3 consecutive ones on the same port). If you did that, decoding could be done in one line of code.


Richard- I will post a schematic today.

Korman- I LOVE that idea - if I can get the input signal to register as such on the digital inputs. Thus far, the 3.6V has not reliably been recognized. Not sure why, as Richard has pointed out that is well within the device input spec (~ 3V).

I will post a schematic later this morning in the hopes that I might get some insights from you, and others who might contribute in the forum.


How do I go about getting a schematic in here???

Here is the schematic of the sensor assembly that is to be fed to the Arduino.

Maybe this code will help:


/* Gray <==> binary conversion routines */
/* written by Dan T. Abell, 7 October 1993 */
/* please send any comments or suggestions */
/* to */
typedef unsigned short ALLELE;

void gray_to_binary (Cg, Cb, n)
/* convert chromosome of length n+1 */
/*      from    Gray code Cg[0...n] */
/*        to  binary code Cb[0...n] */

allele    *Cg,*Cb;
int     n;
     int j;

     *Cb = *Cg;               /* copy the high-order bit */
     for (j = 0; j < n; j++) {
        Cb--; Cg--;         /* for the remaining bits */
        *Cb= *(Cb+1)^*Cg;   /* do the appropriate XOR */

void binary_to_gray(Cb, Cg, n)
/* convert chromosome of length n+1 */
/*      from  binary code Cb[0...n] */
/*        to    Gray code Cg[0...n] */

allele    *Cb, *Cg;
int     n;
     int j;

     *Cg = *Cb;               /* copy the high-order bit */
     for (j = 0; j < n; j++) {
        Cg--; Cb--;         /* for the remaining bits */
        *Cg= *(Cb+1)^*Cb;   /* do the appropriate XOR */

I agree, Richard, that I should be able to sample these as digital signals, even with the variation in IR detector output levels of 3.6V to 4.8V as 'high'. I could then simply sample all as digital signals. I'm not sure whay this isn't working either. I will tear into it this evening and report back.

The voltage divider is comprised of the 100k resistor and the equivalent input impedance of the analog input pin (or digital if that would only work). It should be rather insignificant, however, the current is now limited by the 100K resistor to less than 50 microamps assuming a 5V input; Is this too low????

I just can't seem to put my finger on the problem. 3.6V should be detectable at digital inputs as you said!

Point taken. As I said earlier, in a 'near miss' some years ago, two of the devices were destroyed and replaced. Judging from what I am seeing now, and your comments, the remaining devices may well have suffered some damage at that time as well - although oddly they continue to work in the original console. I will replace the entire group of devices - and hopefully eliminate the issue altogether.

The encoder disks are perfect. Crystal clear where they are supposed to be, and black where they are supposed to be. I also touched up some tiny spots with flat black paint.

I appreciate your comments and I'll let you know how I progress.


here's the promised one line version to read and print the wind direction.

void setup(void) {
    pinMode (8, INPUT); pinMode (9, INPUT);
    pinMode (10, INPUT); pinMode (11, INPUT);

void loop() {
Serial.println ((int []){0, 90, 180, 270, 45, 135, 225, 315, 22, 112, 202, 292, 67, 157, 247, 337}[PINB & 0xf]);
delay (2000);

It uses at the moment the ports 8 to 11 for input. The ports need to be on the same IO-Register and consecutive. If you want to use other pins, you will have to add a >> and perhaps change PINB to PIND, depending on which ports you use.

The decoding table is the part after int[], I just assigned it random numbers. You will have to put there the correct values for each combination.

That's the most compact decoding I came up with, though it is a little hard to maintain and I wouldn't advise using this except for bragging value.


Awesome, Korman!!!! I didn't think it would really be one line! Programming is like shopping...have you really ever been back in 5 minutes????

Thanks so much!

I will make sure that your name is placed besides that one line in the code remarks. ;)

Don’t use it that way, that’s more a stunt. If you don’t have very tight performance requirements, use something more portable, more flexible and easier to understand. Something like this (that code isn’t tested, this is just to give you the right idea):

#include <avr/pgmspace.h>

// Table with the pins to use as input
const int pinCount = 4;
int pins[pinCount] = {8, 9, 10, 11};

// Decoding table
const int decodeCount = 16;
const int decode[decodeCount] PROGMEM = {
    0, 90, 180, 270,
    45, 135, 225, 315,
    22, 112, 202, 292,
    67, 157, 247, 337};

void setup(void) {
    // Configure pins for input
    for (int i = 0; i < pinCount; i++) {
        pinMode (pins[i], INPUT);


void loop() {
    int gc = 0;
    // Build index for decoding table
    for (int i = 0; i < pinCount; i++) {
        gc = (gc << 1) | digitalRead(pins[i]);
    if (gc < decodeCount) {
        Serial.println (decode[gc]);
    else {
        Serial.print ("And how should I decode ");
        Serial.print (gc);
        Serial.println (" with that puny table of yours?");
    delay (2000);

As a detail on the side I’ve moved the decoding table to the flash memory to leave the RAM free for other stuff.


I’ve ordered replacement emitters and sensors for the anemometer and directionsl sensors. As an experiment, I increased drive to the IR LEDs, and lo and behold, got full (4.8V) digital outputs that are easily recognized by the Arduino. These devices must be defective and will be replaced with all of the same type next week, however, I am now able to use all digital inputs.

Thank you for the Gray Code help. It is working well and I am now able to produce directional data with 16 point resolution, and wind speed readings at 750 millisecond intervals. Awesome! Is there a way I can get the “degrees” to be output in the ordinal points??? For example, 22.5 = NNE, 225 = SW, etc. I am ok with the degrees, but I wouldreally like it to display the direction (for those less geeky).

Thank you both again, for all of your help and patience.

Replace the defintion of decod with this table:

const *char[] decode[decodeCount] = {
   "N", "S", "E", "W",
   "NE", "NW", "SE", "SW",
   "NNW", "NNE", "SSE", "SSW",
   "ESE", "ENE", "WNW", "WSW"};

If you want to move the string table to flash memory, that takes a little more and you'd be advised to use the Flash library, which simplifies the matter.


Thanks, Richard. I will replace the devices just to eliminate any possibility of issues.

Korman- Thanks for the replacement table. It seems I get an error however when I insert it. I will give you the specifics later this afternoon.

There was a cut and paste error. After "W", there was a lonely s. I fixed it in my previous message.