Hi, everyone-
I have had the idea of revisiting the durable (and very un-PC) mnemonic for remembering the Resistor Color Code. Many of us old-timers remember the one we all learned, and it was a perfectly fine mnemonic device (if you were a 14 year old boy back in the 1950's.) I'm not going to post it here, because it is not suitable for polite company.
Considering that the demographic of electronic hobbyists has changed quite a bit since then, I think it's time for something a little more modern, and so I would like to start the process of designing a contest- one that will present you with a challenge to come up with a better way to remember the code. For those of you unfamiliar, the basic code consists of 10 different colors, assigned a value of 0 through 9, as follows:
0 - Black
1 - Brown
2- Red
3 - Orange
4 - Yellow
5 - Green
6 - Blue
7 - Violet
8 - Gray
9 - White
Additionally, there are three more colors that are used either as multipliers or tolerance values, appearing in this order:
Gold
Silver
None
A complete explanation can be found here:
http://en.wikipedia.org/wiki/Electronic_color_code
Some rules we can use:
1. Build a phrase of words, where each word starts with the first letter of each color, in numerical order.
2. Be original
3. Be civilized
4. Since the letters "B" and "G" appear twice, extra consideration will be given to those who find a way to eliminate the ambiguity, and prevent the colors from getting mixed-up.
5. Extra credit for a coherent sentence.
6. Extra credit for clever humor.
7. Super-duper credit for anyone who can incorporate "Gold" Silver" and "None"
Prizes?
First Prize: Unending Fame and Fortune
Second Prize: Finite Fame and Fortune
Third Prize: Warm Fuzzies.
If we get lucky, maybe a few vendors will want to sponser the contest by donating some cool prizes!
Check back often as this challenge develops.
Remember- this is only a solicitation to help develop the contest, not the contest itself- that will come later, so please do not send in your mnemonics now, OK?
Check back often as this idea develops!
Send in your ideas via comments. I'll come up with another contact means, soon.
About Me
- Eastern Star Geek
- Aspiring Renaissance Man making machines dance for fun and profit.
Sunday, December 11, 2011
Tuesday, October 25, 2011
The 7400 Logic Electronic Etch-a-Sketch!
UPDATE:
Yay! This placed in the 2nd Place Category of the 2011 Dangerous Prototypes Open 7400 Logic Competition!
Thanks to all the judges, participants and readers who made this all possible. There were many interesting, creative and just plain fun entries submitted by people all over the world, making this a most exciting event!
Especially deserving of extra-special thanks, of course, is my DW, who endured the long hours of my absence and general obliviousness to everything else while I was building this!
Yay! IT WORKS!!
Unfortunately, my first attempt at the electronic Etch-a-Sketch didn't work in time for the contest. A number of personal matters left me with too little time to build, troubleshoot and refine the design in time for the deadline. I probably got to within 98% of the task, but that elusive last 2% scuttled the project.
Oh well... Them's the breaks!
If you want to know more details about the design and operation of this thing, check the prior post.
Hat Tip to the nice folks at Adafruit Industries, supplier of the beautiful 8x8 Dual-Color LED Matrices and sturdy Rotary Encoders for the X, Y and Page knobs!
So, anyway- this is what it looks like in action:
This isn't the greatest video quality, and there are still a few refinements I want to make to the circuit, but at least here is proof that it does, in fact, work!
Please check back as I make this gadget perform some new tricks and shoot some better video.
Here is the slightly newer design!
Yay! This placed in the 2nd Place Category of the 2011 Dangerous Prototypes Open 7400 Logic Competition!
Thanks to all the judges, participants and readers who made this all possible. There were many interesting, creative and just plain fun entries submitted by people all over the world, making this a most exciting event!
Especially deserving of extra-special thanks, of course, is my DW, who endured the long hours of my absence and general obliviousness to everything else while I was building this!
Yay! IT WORKS!!
Unfortunately, my first attempt at the electronic Etch-a-Sketch didn't work in time for the contest. A number of personal matters left me with too little time to build, troubleshoot and refine the design in time for the deadline. I probably got to within 98% of the task, but that elusive last 2% scuttled the project.
Oh well... Them's the breaks!
If you want to know more details about the design and operation of this thing, check the prior post.
Hat Tip to the nice folks at Adafruit Industries, supplier of the beautiful 8x8 Dual-Color LED Matrices and sturdy Rotary Encoders for the X, Y and Page knobs!
So, anyway- this is what it looks like in action:
Please check back as I make this gadget perform some new tricks and shoot some better video.
Here is the slightly newer design!
Display and Controls Board - This one works! |
Logic Board. This one works! |
Thursday, October 20, 2011
Dangerous Prototypes 7400 Logic Contest Entry
Electronic Realization of Etch-A-Sketch Toy with 32-Page Flip-Pad
UPDATE:
Yay! This placed in the 2nd Place Category! Thanks to all the judges, participants and readers who made this all possible. There were many interesting, creative and just plain fun entries submitted by people all over the world, making this a most exciting event!
Especially deserving of extra-special thanks, of course, is my DW, who endured the long hours of my absence and general obliviousness to everything else while I was building this!
Some more stuff on this project appears in my next Blog entry:
http://easternstargeek.blogspot.com/2011/10/7400-logic-electronic-etch-sketch.html.
Kvetch-A-Sketch, Overview |
Dangerous Prototypes TTL Logic Contest
“Electronic “Kvetch-A-Sketch with 32-Page Flip Pad”
Submitted by Eastern Star Geek, Johnson City, Tennessee USA
UPDATE:
Yay! This placed in the 2nd Place Category! Thanks to all the judges, participants and readers who made this all possible. There were many interesting, creative and just plain fun entries submitted by people all over the world, making this a most exciting event!
Especially deserving of extra-special thanks, of course, is my DW, who endured the long hours of my absence and general obliviousness to everything else while I was building this!
Some more stuff on this project appears in my next Blog entry:
http://easternstargeek.blogspot.com/2011/10/7400-logic-electronic-etch-sketch.html.
This project is an electronic realization of the famous Ohio Arts “Etch-A-Sketch” toy, with a twist. Like the original, two rotary knobs are used to move a cursor horizontally and vertically, respectively, but unlike the original, a third knob selects one of 32 different sketch pages. Simple flip-pad animations can therefore be created by twirling the page knob rapidly.
The screen is built from four 8x8 dual-color LED matrix displays, for a total screen area of 16x16 pixels. The artist has a palette of three colors from which to choose- Red, Green and Yellow.
The three rotary knobs are quadrature encoders with a built-in pushbutton.
Several toggle switches and pushbuttons give the artist the ability to:
1. Draw dots, either continuously, or one at a time, using the built-in pushbutton in the X knob.
2. Erase dots, either continuously, or one at a time, using the built-in pushbutton in the Y knob.
3. Choose the color of the dot(s) to be drawn or erased
4. Choose between Edit and Playback modes (Cursor is turned off in Playback)
5. Clear the current page
6. Go to the “Home Page” using the built-in pushbutton in the Page knob.
Theory of Operation:
1. Memory Organization:
The system uses an 8-bit data bus, and each line requires 4 bytes of data to be displayed:
Byte 0: Red Dots, 0-7
Byte 1: Red Dots, 8-15
Byte 2: Green Dots, 0-7
Byte 3: Green Dots, 8-15
The data in each byte represent the four “segments” within a line.
Screen data is stored in a 2Kx8 non-volatile RAM (Dallas DS1220 type), which is addressed as follows:
A0: Red/Green
A1: Low Segment/High Segment
A2-A5 Line Address (0-15)
A6-A10 Page Address (0 -31)
The memory was chosen for it’s non-volatility achieved with an on-board Lithium cell, which allows an infinite number of write cycles.
The system uses an 8-bit data bus, and each line requires 4 bytes of data to be displayed:
Byte 0: Red Dots, 0-7
Byte 1: Red Dots, 8-15
Byte 2: Green Dots, 0-7
Byte 3: Green Dots, 8-15
The data in each byte represent the four “segments” within a line.
Screen data is stored in a 2Kx8 non-volatile RAM (Dallas DS1220 type), which is addressed as follows:
A0: Red/Green
A1: Low Segment/High Segment
A2-A5 Line Address (0-15)
A6-A10 Page Address (0 -31)
The memory was chosen for it’s non-volatility achieved with an on-board Lithium cell, which allows an infinite number of write cycles.
2. LED Screen Data Management and Addressing:
The LED screen is made from four Betlux BL-M12A883DUG-11 Dual-Color 8x8 Matrix Displays. These are Row-Cathode, Column Anode types with one red and green LED per pixel. They are connected as a row/column matrix for a total of 256 pixels. Each line segment is driven by ‘HC574 8-bit latch, which drive the columns directly. The latch signals to each segment come from ½ of a 1-of-8 selector which decodes A0 and A1. The selector is gated by the Timing Generator, as well. Each group of 8 Rows are driven with a ULN2803 octal darlington arrays, which are in turn driven by a ‘HC238 1-of-8 selector which decodes A2-A5 of the address bus.
The LED screen is made from four Betlux BL-M12A883DUG-11 Dual-Color 8x8 Matrix Displays. These are Row-Cathode, Column Anode types with one red and green LED per pixel. They are connected as a row/column matrix for a total of 256 pixels. Each line segment is driven by ‘HC574 8-bit latch, which drive the columns directly. The latch signals to each segment come from ½ of a 1-of-8 selector which decodes A0 and A1. The selector is gated by the Timing Generator, as well. Each group of 8 Rows are driven with a ULN2803 octal darlington arrays, which are in turn driven by a ‘HC238 1-of-8 selector which decodes A2-A5 of the address bus.
3. Rotary Encoder Signal Processing and Decoding:
Each rotary encoder consists of two switch contacts with a common wiper for quadrature channels A and B, as well as a third contact that closes momentarily when the knob is pressed. When the knob is rotated, pulses are generated on the A and B channels at an equivalent frequency proportional to the speed of rotation, but displaced in phase by 90 electrical degrees. The direction of rotation can be determined by which channel leads the other.
First, the quadrature channels of each encoder are de-bounced with a Motorola MC14490 6-channel debouncer chip. Then, for each encoder, one section of a 74HC74 D-Flip-flop decodes the direction. The other half of the ’74 divides the frequency by two to make the encoder less sensitive and make the direction signal not coincident with the pulse signal. When all is said and done, each encoder will result in a Pulse and a Direction signal, which will be used by binary UP/DOWN counters to generate the cursor positions and page addresses.
Each rotary encoder consists of two switch contacts with a common wiper for quadrature channels A and B, as well as a third contact that closes momentarily when the knob is pressed. When the knob is rotated, pulses are generated on the A and B channels at an equivalent frequency proportional to the speed of rotation, but displaced in phase by 90 electrical degrees. The direction of rotation can be determined by which channel leads the other.
First, the quadrature channels of each encoder are de-bounced with a Motorola MC14490 6-channel debouncer chip. Then, for each encoder, one section of a 74HC74 D-Flip-flop decodes the direction. The other half of the ’74 divides the frequency by two to make the encoder less sensitive and make the direction signal not coincident with the pulse signal. When all is said and done, each encoder will result in a Pulse and a Direction signal, which will be used by binary UP/DOWN counters to generate the cursor positions and page addresses.
4. Cursor Generation:
The cursor appears as a blinking dot in the currently selected color. Two 4-bit binary up/down counters are used for the X and Y motions. The X counter drives a 1-of-8 selector that places the cursor in a horizontal line. The Y counter binary value is compared to the current line address, and when they are coincident, the cursor is allowed to be displayed. A simple 2Hz oscillator creates the blink effect, which must be temporarily muted when data is being written to the memory.
5. Cursor and Line Data Integration:
The cursor and screen line data must be combined in such a way as to allow dots to be written or erased, and the cursor to always blink, regardless of the state of the current pixel at the cursor location. Two 74LS181 4-Bit Arithmetic Logic Units (ALUs) are used to generate the necessary bitwise logic operations between the current line segment and the cursor data as follows:
Show Cursor (F = A XOR B) 0110
Store Hole (F = A AND /B) 0111
Store Dot (F = A OR B) 1110
Hide Cursor (F = A) 1111
Erase Page (F = 0) 0011
“A” represents line segment data, “B” represents the Cursor data, and “F” represents the result, which is displayed on the screen, and/or well as written to memory.
The cursor appears as a blinking dot in the currently selected color. Two 4-bit binary up/down counters are used for the X and Y motions. The X counter drives a 1-of-8 selector that places the cursor in a horizontal line. The Y counter binary value is compared to the current line address, and when they are coincident, the cursor is allowed to be displayed. A simple 2Hz oscillator creates the blink effect, which must be temporarily muted when data is being written to the memory.
5. Cursor and Line Data Integration:
The cursor and screen line data must be combined in such a way as to allow dots to be written or erased, and the cursor to always blink, regardless of the state of the current pixel at the cursor location. Two 74LS181 4-Bit Arithmetic Logic Units (ALUs) are used to generate the necessary bitwise logic operations between the current line segment and the cursor data as follows:
Show Cursor (F = A XOR B) 0110
Store Hole (F = A AND /B) 0111
Store Dot (F = A OR B) 1110
Hide Cursor (F = A) 1111
Erase Page (F = 0) 0011
“A” represents line segment data, “B” represents the Cursor data, and “F” represents the result, which is displayed on the screen, and/or well as written to memory.
6. Timing:
Line segment and line addresses are continuously generated by a master counter chain, with, each address, A0 – A5 representing individual line segments. A master oscillator drives a chain of three flip-flops, each of which divides the signal by 2. The divider chain output hen drives a pair of 4-bit binary counters. With the addition of a handful of gates, the full compliment of timing signals, in addition to segment addresses, are generated.
Line segment and line addresses are continuously generated by a master counter chain, with, each address, A0 – A5 representing individual line segments. A master oscillator drives a chain of three flip-flops, each of which divides the signal by 2. The divider chain output hen drives a pair of 4-bit binary counters. With the addition of a handful of gates, the full compliment of timing signals, in addition to segment addresses, are generated.
Display and Control Board, Wiring Side View |
Logic Board, Chip Side |
Logic Board, Wiring Side |
Logic Board Schematic |
Schematic Diagram, Display and Control Panel |
This project is from an original idea by the author, and was designed and created from whole cloth especially for this contest.
Thursday, September 15, 2011
Calling all Gadgeteers!
Have you built a project using any of the ideas posted in this blog?
I'd like to showcase what you've done. I am most interested in projects that have used creative and innovative adaptations of my ideas- especially if they have improved upon the originals!
This blog is all about sharing. Hope to hear from you, so please post a comment or leave me the means to contact you.
I'd like to showcase what you've done. I am most interested in projects that have used creative and innovative adaptations of my ideas- especially if they have improved upon the originals!
This blog is all about sharing. Hope to hear from you, so please post a comment or leave me the means to contact you.
Thursday, September 1, 2011
ASCII to Seven-Segment table
With all the fun we have been having with Seven-Segment displays, lately, I decided to create a semi-automated ASCII to Seven Segment table using MS Excel (yeah, yeah... I know!)
You still have to enter the patterns yourself, but the Hex value and a handy string for inclusion in your lookup table are generated automatically.
You can use this chart to display more than just numerals, although some ASCII characters just cannot be rendered in seven segments, and some might be a bit of a stretch, but do with it what you will.
(Note: This chart will have to be modified slightly if you want to use it with the Vane Display example in an earlier blog-post, because the segments a-g are mapped to bits 1-7, instead of the more conventional 0-6. You can change the chart, or wire the circuits differently. Your choice)
If you want to make your own spreadsheet, here are the formulae used in the different columns:
Note: In the image below, the first cell containing data (32) is Cell B5
Cell B5: No formula. Just input the ASCII code
Cell C5: =DEC2HEX(B5)
Cell D5: =CHAR(B5) (Note- sometimes I substitute text)
Cells E5 to L5: No formula, enter a "1" for segment ON, and a "0" for segment off
Cell M5: =BIN2HEX(VALUE(5&F5&G5&H5&I5&J5&K5&L5),2)
Cell N5: ="0x"&M5&", // "&D5&IF(HEX2DEC(M5)=0," (npr)","") (Note- you can cut and paste the result directly into your IDE when you build your lookup table)
Have fun!
You still have to enter the patterns yourself, but the Hex value and a handy string for inclusion in your lookup table are generated automatically.
You can use this chart to display more than just numerals, although some ASCII characters just cannot be rendered in seven segments, and some might be a bit of a stretch, but do with it what you will.
(Note: This chart will have to be modified slightly if you want to use it with the Vane Display example in an earlier blog-post, because the segments a-g are mapped to bits 1-7, instead of the more conventional 0-6. You can change the chart, or wire the circuits differently. Your choice)
If you want to make your own spreadsheet, here are the formulae used in the different columns:
Note: In the image below, the first cell containing data (32) is Cell B5
Cell B5: No formula. Just input the ASCII code
Cell C5: =DEC2HEX(B5)
Cell D5: =CHAR(B5) (Note- sometimes I substitute text)
Cells E5 to L5: No formula, enter a "1" for segment ON, and a "0" for segment off
Cell M5: =BIN2HEX(VALUE(5&F5&G5&H5&I5&J5&K5&L5),2)
Cell N5: ="0x"&M5&", // "&D5&IF(HEX2DEC(M5)=0," (npr)","") (Note- you can cut and paste the result directly into your IDE when you build your lookup table)
Have fun!
Adafruit Industries: A happy Customer Service story!
Dear Readers:
I have purchased from adafruit industries numerous times since the end of last year, and I am happy to say that it has always been a very satisfying experience.
Recently, however, there were a series of unfortunate glitches with an order.
While I don't expect anyone, including my vendors, to be perfect, I do expect them to fix problems quickly, professionally and equitably.
I am happy to report that adafruit industries understands and practices these principles of Excellent Customer Service! So friends, help support them. They are thorough, nice, professional folks to deal with, and their heart is in the right place. May it always be so.
(Brava, Lady Ada!)
I have purchased from adafruit industries numerous times since the end of last year, and I am happy to say that it has always been a very satisfying experience.
Recently, however, there were a series of unfortunate glitches with an order.
While I don't expect anyone, including my vendors, to be perfect, I do expect them to fix problems quickly, professionally and equitably.
I am happy to report that adafruit industries understands and practices these principles of Excellent Customer Service! So friends, help support them. They are thorough, nice, professional folks to deal with, and their heart is in the right place. May it always be so.
(Brava, Lady Ada!)
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:
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;
}
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:
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.
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:
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.
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.
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:
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!
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.
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!
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 |
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 |
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) |
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!
Subscribe to:
Posts (Atom)