Sunday, August 28, 2011

DHT22 Temperature/Humidity Sensor

Here's a little something I cobbled together from a DHT22 Sensor, an Adafruit DC BoArduino, and a 3-digit LED display.
More to come!




Here is a schematic of the external hardware:

Figure 1: External Hardware

======================================================
ARDUINO SKETCH FOLLOWS BELOW
======================================================

A few changes:
I added the means to change the brightness and scroll-rate using external analog signals.  Two ordinary pots wired between 0 and +5V (variable voltage divider), with the wiper going to the respective analog pin is all you need.  These parts are not shown in the above schematic.

The code for both is present, however, only the Brightness Control is activated. The scroll rate code is currently commented out, with a fixed scroll rate of 8 char/sec.  Please find the relevant section of code (shown below) in the sketch, and follow the directions.


/* ---------------------------------------------------------------------
      To use an external analog signal controls scroll rate, 4 to 20 char/sec:
      t1Preset = map(analogRead(scrollSpeed),0,1023,250,50);
      (Comment out the line above, "t1Preset = 125;"
      ------------------------------------------------------------------------*/




/*


 DHT22 SCROLLING TEMPERATURE AND HUMIDITY DISPLAY

 ---------------------------------------------
 Author: EasternStarGeek, Johnson City, TN
 Date: 27 August, 2011
 ---------------------------------------------

 This sketch reads a DHT22 Temperature and Humidity Sensor and displays the
 readings on a 3-digit, 7-segment display.

 The three readings scroll by, "Marquee" fashion: Temperature in Degrees F, Temp. in Deg. C, and Percent relative Humidity.

 Normal Display Format:
 (+/-)XXX.y *F  (+/-)XXX.y *C   XX.XH

 (If the senor returns invalid data, all values will show "-999.9")

 Readings repeat in between display updates.


 Hardware:
 Controller: Adafruit DC Boarduino

 Sensor: DHT22
 - 10K, 1/4W Resistor

 Display:
 - 7-Segment LED, 3-Digit, Common Cathode
 - 74HC595 Shift Register
 - 220 ohm, 1/4W resistors (8)
 - 2N2222 Digit Drivers (3)
 - 10K, 1/4W Resistors (3)

 For a complete schematic of th external hardware, please see details in my blog:
 http://easternstargeek.blogspot.com/2011/08/dht22-temperaturehumidity-sensor.html

 This software uses the Adafruit DHT Sensor Library:
 https://github.com/adafruit/DHT-sensor-library

 */

// Include Library:
#include "DHT.h"  // DHT Sensor Library 

// Hardware Connections to I/O Pins:
#define DHTPIN A0     //DHT22 Data Pin
// Note: The sensor data is digital.  Any I/O Pin can be used.

const int dataPin = 11;   // 74HC595 Shift Register Data, IC Pin 14
const int clockPin = 12;  // 74HC595 Shift Register Data Clock, IC Pin 11
const int latchPin = 8;   // 74HC595 Shift Register Latch Clock, IC Pin 12

// Arduino pins for Digit Driver Transistors: 1(MSD), 2, 3(LSD)
const int digitEnable[3] = {
  2,3,4}; // Array holding pin numbers for Digit Driver transistors

const int brightLevel = A5;  // Analog pin for Brightness Control
const int scrollSpeed = A4;  // Analog pin for Scroll Rate Control

// Global Declarations:
#define DHTTYPE DHT22   // DHT 22  (AM2302)
DHT dht(DHTPIN, DHTTYPE);  // Declare Sensor Class

// Lookup Table containing all segment patterns used in this project:
// Character Set:
// 0,1,2,3,4,5,6,7,8,9,<null>,H,t,C,F, -, _, =, <deg symbol>

// Mapping of Display Segments by bit: (MSB) dp,g,f,e,d,c,b,a (LSB)
const byte LUT_Chars[20] = {
  0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
  0x00,0x76,0x78,0x39,0x71,0x40,0x08,0x48,0x63};

// Proxies for non-numeric characters:
const int chr_nul = 10;  // Pointer to pattern for null (blank) character
const int chr_H = 11;    // Pointer to pattern for 'H'
const int chr_t = 12;    // Pointer to pattern for 't'
const int chr_C = 13;    // Pointer to patterrn for 'C'
const int chr_F = 14;    // Pointer to pattern for 'F'
const int chr_min = 15;  // Pointer to pattern for dash (minus sign)
const int chr_usc = 16;  // Pointer to pattern for underscore
const int chr_equ = 17;  // Pointer to pattern for equal sign
const int chr_deg = 18;  // Pointer to pattern for degree symbol

int valLength;  // Length of returned numerical substring after conversion
int numFieldChars[5]; // Global array hilding converted numerical subsrting
int CharBuffer[3];  // Buffer holding segment patterns for currently displayed digits

int Brightness = 255;  // Brightness Level, 1 to 255
unsigned int T_On;  // LED ON Time  // For display brightness control
unsigned int T_Off; // LED OFF Time  // For display brighness control

int pointerScroll;  // Index to Character Buffer (1=MSD, 3=LSD)
int SCR = 10;  // Sequence Control Register, used w/ Switch/Case
int DisplayLine[47];  //Array holding segment patterns
// for the entire string to be displayed, pre-scroll

int pointerDL;
float h;  //Raw sensor data, Percent Relative Humidity
float tc;  //Raw sensor data, Temperature, Degrees Celsius

/* Timers:
 This project uses a Timer Services that run on every scan, making use of the millis()
 function.  This allows the code to scan continuously with minimal wait-states
 */
// Declare Timer 1 Variables
boolean t1Bit;   // Timer 1 Control Bit
boolean t1Edge;  // Timer 1 Edge Bit
long t1Preset;     // Timer 1 Preset
long t1Temp;     // Timer 1 Temporary Value
long t1Remain;     // Timer 1 Word
// End Timer 1 Variables

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

  dht.begin();  // Initialize Sensor Functions
  // I/O Pin Modes::
  pinMode (dataPin, OUTPUT);
  pinMode (clockPin, OUTPUT);
  pinMode (latchPin, OUTPUT);

  for (int i=0; i<3; i++)  {
    pinMode(digitEnable[i], OUTPUT);
  }

  pinMode (brightLevel, INPUT);
  pinMode (scrollSpeed, INPUT);
}

void loop()
{

  // Switch/Case used to control delayed sequential processes without wait-states
  switch (SCR)
  {

  case 10:
    // ======== Get Raw Sensor Data ==============================
    h = dht.readHumidity();    //Get the Humidity Data
    tc = dht.readTemperature();  // Temperature in deg C (Float)

    // check if returned sensor data is invalid, put in dummy data so
    // that all temp and humidity values will read "-999.9"
    if (isnan(h) || isnan(tc))
    {
      h = -999.9;
      tc = -999.9;
    }

    // ======== Build the Display String ============================
    /* Containing the 7-segment patterns for the entire
     assembled message, pre-scroll:
     */

    pointerDL = 0;  // Initialize the Display Line Pointer
    // Start the Display Line with three null characters
    for (int n=0; n<4; n++)  {  // Add four blanks to the Display Line
      DisplayLine[pointerDL] = LUT_Chars[chr_nul];
      pointerDL +=1;  // Increment the Pointer
    }


    //Convert the Temperature Data into Fahrenheit, and convert it to a 7-segment pattern:
    valLength = float_to_SevenSegment((tc * 1.8)+ 32);
    // Copy the Temperature Substring to the Display Line
    for (int i=0; i < valLength; i++) {
      DisplayLine[pointerDL + i] = numFieldChars[i];
    }
    pointerDL += valLength;
    // Add the Degree Symbol character, the 'F' character, and three null (blank) characters
    DisplayLine[pointerDL] = LUT_Chars[chr_deg];
    pointerDL +=1;  // Increment the Pointer
    DisplayLine[pointerDL] = LUT_Chars[chr_F];
    pointerDL +=1;  // Increment the Pointer
    for (int n=0; n<4; n++)  {  // Add four blanks to the Display Line
      DisplayLine[pointerDL] = LUT_Chars[chr_nul];
      pointerDL +=1;  // Increment the Pointer
    }

    // Convert the Celsius Temperature to a 7-segment pattern:
    valLength = float_to_SevenSegment(tc);
    for (int i=0; i < valLength; i++) {
      DisplayLine[pointerDL + i] = numFieldChars[i];
    }
    pointerDL += valLength;
    // Add the Degree Symbol character, the 'C' character, and three null (blank) characters
    DisplayLine[pointerDL] = LUT_Chars[chr_deg];
    pointerDL +=1;  // Increment the Pointer
    DisplayLine[pointerDL] = LUT_Chars[chr_C];
    pointerDL +=1;  // Increment the Pointer
    for (int n=0; n<4; n++)  {  // Add four blanks to the Display Line
      DisplayLine[pointerDL] = LUT_Chars[chr_nul];
      pointerDL +=1;  // Increment the Pointer
    }

    //Get the Humidity Data, and convert it to a 7-segment pattern:
    valLength = float_to_SevenSegment(h);
    // Copy the Humidity Substring to the Display Line
    for (int i=0; i < valLength; i++) {
      DisplayLine[pointerDL + i] = numFieldChars[i];
    }
    pointerDL += valLength;
    // Add the 'H' character and three null (blank) characters
    DisplayLine[pointerDL] = LUT_Chars[chr_H];
    pointerDL +=1;  // Increment the Pointer
    for (int n=0; n<3; n++)  {  // Add three blanks to the Display Line
      DisplayLine[pointerDL] = LUT_Chars[chr_nul];
      pointerDL +=1;  // Increment the Pointer
    }

    /* Diagnostics:
     Serial.print("Humidity: ");
     Serial.print(h);
     Serial.print(" %   ");
     Serial.print("Temp: ");
     Serial.print(tc);
     Serial.print(" *C   ");
     Serial.print((tc * 1.8)+ 32);
     Serial.println(" *F");
    
     // Print out contents of Display "String"
     for (int j=0; j<pointerDL; j++)
     {
     Serial.print(DisplayLine[j],HEX);
     Serial.print(" ");
     }
     Serial.println();
     */
    pointerScroll = 0;  // Initialize the Marquee Pointer
    SCR = 20;
    break;

    // ======= Scroll the 3-character Display over the Display Line =========
  case 20:
    // Update the contents of the three LED Digits
    if (pointerScroll <= (pointerDL - 3))
    {
      CharBuffer[0] = DisplayLine[pointerScroll];
      CharBuffer[1] = DisplayLine[pointerScroll + 1];
      CharBuffer[2] = DisplayLine[pointerScroll + 2];
      pointerScroll +=1;
      // Set Timer to 125mS for scroll delay
      t1Preset = 125; 
         
      /* ---------------------------------------------------------------------
      To use an external analog signal controls scroll rate, 4 to 20 char/sec:
      t1Preset = map(analogRead(scrollSpeed),0,1023,250,50);
      (Comment out the line above, "t1Preset = 125;"
      ------------------------------------------------------------------------*/
    
      t1Bit = 1; 
      SCR = 30;
    }
    else
    {
      SCR = 10;  // Get another Reading
    }
    break;
  case 30:
    if (!t1Bit)  // When Timer expires, scroll the next character
      SCR = 20;
    break;
  }  // End of Switch/Case statement for SCR

  // ################################################################
  // ################################################################

  // ###################################################
  // ################## SERVICES #######################
  // ###################################################
  // Multiplex 3-digit 7-segment display
/*  Here is a line of code that allows you to control the
    display brightness with an external analog voltage: */
   
  Brightness = map(analogRead(brightLevel),0,1023,1,255);
 
//  If not using this feature, substitute a number between 1 and 255

  Brightness = constrain (Brightness,1,255);  // Traps bad Brightness Level Data
  T_On = Brightness * 20;
  T_Off = (256 - Brightness) * 20;
  // Scan the three digits once
  for (int i=0; i < 3; i++)
  {
    // Transmit Segment Data for current digit to the Shift Register:
    digitalWrite(latchPin, LOW);
    shiftOut (dataPin, clockPin, MSBFIRST, CharBuffer[i]);  // Shift out  Byte
    digitalWrite(latchPin, HIGH);
    // Energize Digit Driver for current Digit
    digitalWrite(digitEnable[i], HIGH);
    // Leave energized, according to Brighness Level
    delayMicroseconds(T_On);
    // Blank Digit Outputs according to Brighness Level
    digitalWrite(digitEnable[0], LOW);
    digitalWrite(digitEnable[1], LOW);
    digitalWrite(digitEnable[2], LOW);
    delayMicroseconds(T_Off); 
  }
  // ### END Display Multiplexing ###

  // ********** Pulse Timer Service **********
  /* This timer runs as a service, allowing delayed sequences to be built without hanging-up
   the rest of the project code
  
   When the sequence wants to use the timer, it must specify the Time Delay (t1Preset)in milliseconds,
   and then set the Timer Bit (t1Bit = HIGH).
   From that point forward, the Timer Service will keep track of the elapsed time (via the millis() function).
   When the time interval expires, the Timer Service will reset the Timer Bit (t1Bit) which can be checked by
   code running elsewhere.
  
   */
  // arm the timer
  if (t1Bit && !t1Edge)
  {
    t1Edge = HIGH;
    t1Temp = millis();
    t1Remain = t1Preset;
  }
  // decrement the Timer Word as long as the timer is active 
  if ( t1Edge ) 
  {
    t1Remain = t1Remain - abs(millis() - t1Temp);
    t1Temp = millis();
  }
  // check for timer expiration (Timer can also be cancelled by forcing t1Bit to LOW)
  if ( (t1Remain <= 0) || !t1Bit ) 
  {
    t1Bit = LOW;
    t1Edge = LOW;
    t1Remain = 0;
  }
  // *** END Pulse Timer 1 ***     
} // Loop Procedure End

// #######################################################################
// #######################################################################
/*
*****************
 *** FUNCTIONS ***
 *****************
 */
/* CONVERT FLOATING POINT VALUE TO 7-SEGMENT CHARACTER ARRAY
 Display Format: -999.9 TO 999.9, with one fixed decimal place
 Return Value = "String Length"
 (The "string" is a series of 1-byte segment patterns, not an actual ASCII string)

 Notes:
 a. The decimal point does not occupy a character position, but
 is incorporated into the units digit.

 b. Numbers between -0.9 and 0.9 are always displayed with a leading zero.
 c. Over/under range values are displayed as "999.9" or "-999.9" respectively

 */
int float_to_SevenSegment (float inputVal)  {
  // Declare local function variables
  int numFieldVal[5];
  // Constrain input value
  inputVal = constrain (inputVal, -999.9, 999.9);
  // Convert constrained Float to Word * 10, display as 1 Fixed decimal place
  int  inputInt = word(inputVal*10); 

  // Clear Array
  for (int i=0; i<5; i++) {
    numFieldVal[i] = chr_nul;
  }
  int charPos = 0; //Initialize Character Position Pointer
  // Is Input Value Negative?
  if (inputVal < 0)
  {
    inputInt = inputInt * -1;  // Negate Input Integer
    numFieldVal[charPos] = chr_min; // Insert a Minus Sign
    charPos +=1;  // Increment the Character Position Counter
  }
  // Round-off Integer value
  if ((inputVal*10) - inputInt >= 0.5)
    inputInt +=1;
  // if Input value is less than 100.0
  if (inputInt < 1000)
  {
    // if Input is less than 10.0
    if (inputInt < 100)
    {
      numFieldVal[charPos] = inputInt /10 ;
      numFieldVal[charPos+1] = inputInt % 10;
      charPos +=2;  // Increment the Character Poaition Output by 2
    }
    // if Input is greater than 10.0
    else
    {
      numFieldVal[charPos] = inputInt / 100;
      numFieldVal[charPos+1] = (inputInt - numFieldVal[charPos]*100)/10 ;
      numFieldVal[charPos+2] = (inputInt - numFieldVal[charPos]*100) % 10;
      charPos +=3;  // Increment the Character Poaition Output by 3
    }
  }
  // if Input is greater than or equal to 100.0
  else
  {
    numFieldVal[charPos] = (inputInt) / 1000;
    numFieldVal[charPos+1] = (inputInt - numFieldVal[charPos]*1000) /100 ;
    numFieldVal[charPos+2] = (inputInt - numFieldVal[charPos]*1000 - numFieldVal[charPos+1]*100)/10 ;
    numFieldVal[charPos+3] = (inputInt - numFieldVal[charPos]*1000 - numFieldVal[charPos+1]*100)%10 ;
    charPos +=4;  // Increment the Character Poaition Output by 4
  }
  // Fetch the Segment Patterns
  for (int i=0; i<=charPos; i++) {
    numFieldChars[i] = LUT_Chars[numFieldVal[i]];
    if (i == charPos-2)  //is the current digit the units digit?
      numFieldChars[i] += 0x80; //set bit 7 (dp)
  }
  return charPos;
}

Friday, August 26, 2011

Nifty Electromechanical Vane Display

================== IMPORTANT UPDATE! ==========================
Attention everyone who wants to build my circuit:  I previously failed to consider current flow through sneak-paths in adjacent digits when more than one are connected in a multiplexing scheme.  This wouldn't be a problem with LEDs, but current can flow both ways through the coils!

To prevent this from happening, a diode must be added in series with each coil (not the coil common!) .  You will need 14 diodes per digit.  Use a general purpose rectifier like the ubiquitous 1N4001, 1N4002, etc. These are popular 1A silicon diodes, differing only by their reverse voltage (PIV) ratings.  The 1N4001 is good up to 50 volts.
I have seen pictures of some vane displays that already include a companion PCB containing such diodes.  If you have one, don't throw it away!  (Check the diode polarity, however)

Figure 1 has been updated to include the diodes.  The rest of the figures are OK.

(Thanks, and a tip o' the hat, to commenter"Zapro")
===============================================================


Recently, I answered a forum question on the Adafruit Industries site regarding Vane Displays.  Someone managed to score a few of these at a Ham Fest, and was asking how to make a clock project with them using an Arduino.  (View forum topic here)

The same fellow (named for a Black Cat somewhere in Nashville?) also posted to Dangerous Prototypes, who was kind enough to feature this solution on their front page today! (31 Aug, 2011)  Thanks, Ian!

So, anyway...
The displays in question are of a 7 segment electromechanical design.  These are not light-up displays, but rather, reflective.  Each segment is a "vane" that has been painted a bright contrasting color, and mounted on a hinge, such that it can be swung into, or hidden from view.  This is accomplished with two solenoid coils sharing a common pin.  In order to show a segment, the Show coil must be pulsed for about 50mS, which will flip the vane into view.  A permanent magnet keeps it there, even after the coil is switched off.  To hide the segment, just pop the Hide coil for 50mS.  Pretty simple, no?

So, anyway, Figure 1, shows how to prepare each digit for use in this project.  As stated earlier, vintage vane displays come in all shapes and sizes, and if you're lucky, there will be isolation diodes included, but if not, you must add them.  Build up each digit to the diagram below:
Figure 1: Preparation of a Single Vane Digit
    
Next, is a sketch that shows how to hook up the display to some driver circuitry.  Since several of these will be used, a multiplexing scheme will be employed.  
For clarity, Figure 1, above, has been reduced to a single black box, with the show and hide coil connections grouped together.

Figure 2: Driving the Display Coils
As you can see, the coil loads must be driven from both ends.  At the top, is a high-side  Digit Driver circuit, consisting of a PNP power transistor and associated components.  This driver, when activated, will give the digit life by bringing power to one end of all 14 coils.  (See Figure 4, below for how it is activated via the Digit Strobe connection)
At the bottom are 14 low-side Segment Drivers, consisting of a pair of ULN2004 darlington transistor arrays. 
Two ULN2004s are needed, one for the Segment (show) coils, and the other for the "anti-Segment" (hide) coils.  I previously toyed with a few different schemes, but I think this one will look the nicest in operation, since all vanes belonging to a single digit will be able to change state simultaneously.  The logic signals to each ULN2004 come from a pair of 74HC595 serial-to-parallel latching shift registers, using a well-known scheme for Arduino Output expansion.  (See documentation at the Arduino website, http://www.arduino.cc/en/Tutorial/ShiftOut.
Note that each ULN2004 contains 7 channels, while the 74HC595 handles 8 bits, so Bit 0 (pin 15) of the 'HC595s is not used.  For the following examples, data must be clocked into the shift registers Most Significant Bit, (MSB) first.

Since the switched loads are inductive, a high-voltage negative spike (Back-EMF) will be generated when they are switched off.  This spike can easily smoke sensitive semiconductors, so two means of protection are necessary:  The first uses the built-in protection diodes of the ULN2004, since this IC is designed for driving inductive loads.  For these diodes to be able to do this very important job, however, pin 9 (common) MUST be tied to the Load (coil) supply.  If we weren't using a digit driver at the top, no further protection would be necessary, but we are, so a TVS (Transient Voltage Suppressor) diode is wired across the PNP transistor as shown. 

This diode is a ruggedized zener that will start to conduct when the reverse voltage exceeds 15 volts, opening a path through which the spike can be safely dissipated.  Such diodes are very handy- being available in a wide range of voltages.  Care must be taken to select the right part so that it won't conduct under normal operation!

Since additional digits will be multiplexed, no further segment drivers are necessary, no matter how many digits are added, since each one will be individually updated.  These four ICs are enough for an entire six digit clock (and then some).

Now, on to Sketch 3, which shows how to connect multiple digits to the segment drivers:
Figure 3: Multiplexing Scheme
To keep the sketch from being too cluttered, not all the wires are shown.  The scheme is repetitive, and predictable, however.  Just proceed carefully!
Note that each digit does require a complete Digit Driver circuit, however.  In the next sketch, we will show how to connect the Digit Drivers to the Arduino.

Figure 4:  Digit Selection and Connections to the Arduino
(Note: Ignore the bar over Y0-Y7 of the 74HC238)
Here, a 74HC238 3-to-8 line decoder is used to generate the six individual digit selector signals.  Outputs Y1 to Y6 are connected to a 74LS06 Hex Inverter with Open Collector outputs, which properly drive the PNP High-Side digit drivers.  Open Collector outputs are necessary because the Coil Supply is +12V, which is higher than the logic supply of +5V.  The outputs of each Inverter go to the base resistor (10K) of each Digit Driver.  (Never attempt to drive a PNP high-side driver from a totem-pole type logic output if the load voltage is higher than the logic voltage!)
The 'HC238 is a great chip for this application, because it saves Arduino I/O, and  only one output at a time can be energized, eliminating the possibility of overloading the ULN2004 if a software bug tries to select more than one digit at a time.  The chip will use 3 Arduino outputs, which must produce a binary code to select a digit:  001=Digit 1,  010=Digit 2 ... 110=Digit 6.  (Codes 000 (Y0) and 111 (Y7) are not used).
The black box at the bottom of the sketch (Latch Circuit) was detailed on Page 2.  Here, we see how to hook up the logic signals to the Arduino.  The /MR (Master Reset) is connected to a passive RC differentiator, which will apply a short negative-going pulse upon power-on, clearing the shift registers of any garbage that might be present.  If you want, you can connect this line to another Arduino output.

Finally, because this will hopefully be a real circuit someday, real construction techniques must be used!  The last sketch shows how to distribute the DC power for the Coil circuits, as well as the Logic circuits.  Please follow this carefully, to avoid frustrating problems caused by noise and transients.

Figure 5: Power Distribution Method
 
So there you have the hardware!

==================================================

Tackling the software!

So, now we need to start thinking about how to develop the code that will make these lovely displays do what they were designed to do.
The first thing is to examine the limitations of the hardware.  Multiplexing is great because it saves wiring, glue chips and I/O, but it also means we can only update one digit at a time.  Since the vanes require about 50mS to flip, we cannot scan the digits faster than that.  If only one digit at a time is being changed, nobody will know, but at midnight, all 6 will change, making a lovely "thup-thup-thup-thup-thup-thup" sound.  Arguably, that is part of the charm of this vintage technology!
So, anyway- the next thing to consider is that there is no regular relationship between digital integers and the segment patterns required to display them.  Therefore, our code must use a Lookup Table to map the required segment patterns onto a 16 bit word (data type unsigned int).  Take a moment to review the sketch on Page 2.  The segment data is being clocked into the two shift registers, but it will originate from a 16 bit word in the Arduino.  Since Bit 0 (Pin 15, Q0) of neither SR chip is used, bits 1 - 7 will drive the Show Coils a - g, respectively, and bits  9 -15 drive the Hide Coils, /a - /g.  
Finally, when a digit is updated, all segments to be shown must get a "1" and so must all segments that will be hidden.
What we will do, is use the character to be displayed as an index (0 - 9) into a 1-dimensional array of 11 unsigned integers. (Index 10 will be used to blank a digit).  The structure of the lookup table appears below:



Figure 6: Lookup Tabl;e, Segment Data

Don't let the concept of a Lookup Table intimidate you.  This is what it will look like in your Arduino sketch:

 const unsigned int SegmentTable[11] =
{
0x807E, 0xF20C, 0x48B6, 0x609E, 0x32CC,
0x24DA, 0x04FA, 0xF00E, 0x00FE, 0x20DE, 0xFE00
}; 

See?  Nothing to it!

Note that the organization of the lookup table must agree with the hardware!  Looking once again at page 2, the two 74HC595 chips are hooked up in a sequential cascade.  The very first bit (Bit 15) to be clocked-in will wind up at Hide Coil "/g."  (Pin 7, Q7 of the right-hand chip)
The last bit (Bit 0) doesn't do anything, because the corresponding pin (Pin 15) of the left-hand chip isn't connected, but the next-to-last bit (Bit 1) to be clocked-in will wind up at Show Coil "a" (Pin 1, Q1 of the left-hand chip)

Don't forget, when you set up your Arduino ShiftOut procedure, be sure that the Most Significant Bit (MSB) gets clocked-out first!


Figure 7: Internal structure of the 74HC595 Shift Register

Now, the more astute amongst you may have noticed that the required state of each "anti-segment" is always opposite of the segment state.  Naturally, that begs the question, "Well, why not use only one shift register, and a 74HC240 octal inverter to drive the second ULN2004?"
Why, indeed?  If you'd rather do it that way, have at it! 

(The remaining post pertains only to the original double-shift register approach.)


================================================

Crawl, before you Walk!

OK, so now let us try testing a single digit.  Wire up the circuit shown in Figure 2, above, except ignore the connections to other digits.  We're just going to hook up one vane display and test it out.  You will need:

12VDC, 1Amp Power Supply
Arduino of your choice (+5V operation)
Vane Display (1)
ULN2004 (2)
74HC595 (2)
TIP115 PNP Transistor
P4KE15A TVS Diode
2N2222 NPN Transistor
100K, 1/4W resistor (2)
10K, 1/4W resistor
4.7K, 1/4W resistor
470uF, 25V Electrolytic Capacitor
220uF, 25V Electrolytic Capacitor
0.1uF, 50V ceramic capacitor (2)

One more thing- because we're only testing one digit, we have to use a special version of the High-Side driver, diagrammed in Figure 8, below.  Instead of a stage from the Hex Inverter in Figure 4, a discrete NPN transistor (Q2) is used instead.  This ensures that the PNP transistor, operating at +12V can be properly controlled by the +5V Arduino logic signal.

Figure 8: Special Detail for Digit Driver, Single Digit Test

When wiring this test circuit, see Fig 5, above for proper Power Supply connections

Connect the logic signals to your Arduino as follows:

a.  Digit Strobe to Digital I/O 2
b.  Shift Register Data to Digital I/O 11
c.  Shift Register Clock to Digital I/O 12
d.  Shift Register Latch to Digital I/O 8
e.  /MR to +5V
f.  /OE to GND


Try out this code, which will cycle the display from 0-9, blank, then repeat about once per second:

/*
Single-Digit counter, using Seven-Segment Vane Display
 (Per instructions in EasternStarGeek's Blog:
 
http://easternstargeek.blogspot.com/2011/08/nifty-electromechanical-vane-display.html

 Author: Eastern Star Geek, Johnson City, TN
 Date: 26 August, 2011

 This code will increment a single-digit Vane Display from 0 to 9, blank, then repeat
 */


const byte dataPin = 11;  // Shift Register Data Pin
const byte clockPin = 12;  // Shift Register Clock Pin
const byte latchPin = 8;  // Shift Register Latch Pin
const byte digitStrobe = 2;  // Digit Driver Strobe

// Lookup Table for Segment and Anti-Segment data
const unsigned int segmentTable[11] =
{
  0x807E, 0xF20C, 0x48B6, 0x609E, 0x32CC,
  0x24DA, 0x04FA, 0xF00E, 0x00FE, 0x20DE, 0xFE00 };


unsigned int segmentData;  // Holds current Segment/Anti-segment Data
unsigned int Counter = 0;  // Counter Value, 0 to 10

void setup()  {
  pinMode (dataPin, OUTPUT);
  pinMode (clockPin, OUTPUT);
  pinMode (latchPin, OUTPUT);
  pinMode (digitStrobe, OUTPUT);
}


void loop()  {
  constrain (Counter, 0, 10);  // Prevents bad data from being fetched
  segmentData = segmentTable[Counter];  // Get segment data from Lookup Table

  // Prepare Latch for Data Transmission
    digitalWrite(latchPin, LOW);

  // Shift out High Byte
  shiftOut (dataPin, clockPin, MSBFIRST, (segmentData >> 8));

  // Shift out Low Byte
  shiftOut (dataPin, clockPin, MSBFIRST, segmentData); 

  // Assert Latch to transfer Data
  digitalWrite(latchPin, HIGH);

  digitalWrite(digitStrobe, HIGH);  // Enable Digit
  delay(75);  // Allow 75ms for vanes to Flip  
  digitalWrite(digitStrobe, LOW);  // Disable Digit

  if (Counter < 10)
    Counter = Counter + 1;
  else
    Counter = 0;

  delay(925);
}

















...More to come, Stay Tuned!