Introduction to the scrolling LED strip
This post describes working out how a 72 x 7 pixel LED strip works, and then connecting it up to an Arduino to display custom text under program control.
The intention was to use the strip to display custom data (for example, the temperature) rather than having to manually (and somewhat laboriously) type the text in through the inbuilt keypad.
The strip
This was purchased a while ago for around $50 from memory. It has 72 pixels horizontally and 7 deep, giving a total of 504 pixels. Also there is a 55-key keypad visible in the photo which lets you enter text:
The internals
The device consists of 5 circuit boards. Two hold the LEDs themselves:
Part number 80-947C 2002-09-12:
Part number 80-947D 2002-09-12:
Visible also in the above photos is the board containing the keyboard.
Once opened you can fold out the other two boards with the control logic on them:
Part number 80-947A-2 2004-04-15:
Part number 80-947B 2002-09-12:
Various parts are labelled following the investigation described below.
Reverse engineering
Some work with a magnifying glass revealed that the chips were 74HC595 8-bit shift registers.
More details about them here:
http://www.gammon.com.au/forum/?id=11518
These are standard output shift registers with a latch, which means you can shift out to multiple chips and then "latch" the data (copy from a temporary register to the output pins) in one operation, providing flicker-free updating.
With 72 columns of LEDs, and the 595 chips providing 8 bits each, it was reasonable to deduce that 9 of the chips were dedicated to driving the columns. Underneath each of those chips were 8 x 150 ohm resistors, which would be for current-limiting of the LEDs. Measurements indicate that there is around 4 mA per LED going through the resistors (a 600 mV voltage drop). This is a total drain of 32 mA for each 595 chip (if all LEDs are lit) which is within the spec for that chip.
The column drivers sink current (so to light an LED the corresponding output has to be zero).
The tenth 595 chip labelled "row driver" on the photo was clearly intended to source current for the rows, via the 7 x 8550 PNP transistors on the right, driven from that chip via 7 x 4.7k base resistors.
Since the row drivers are driven via a transistor which inverts the output, the row driver must also have an output as zero, in order for the transistor to source current.
In other words, if all 595 chips are outputting zero, then all LEDs are lit.
This video demonstrates how the multiplexing works:
As you can see from that video, column are lit in a batch, but one row at a time. When done really slowly you can't make out the whole letter. When sped up, persistence of vision makes it appear that they are all on at once.
Detail from the reverse side:
Keypad driver
One slightly puzzling aspect was the absence of any extra chips for the 55-key keypad, as all 10 chips had now been accounted for. There were 8 wires leading to it (on a ribbon cable) on the left, in the photo, and 7 wires on the right. This led to a deduction that this was a 7 x 8 key keypad matrix. Since that would give 56 possible keys, it seemed very likely.
A close inspection of the cable on the left showed 8 x 10k pull-up resistors for that cable, plus each of the 8 wires were connected directly to the CPU. On the other side, the "row driver" chip was also connected to the 7-wire cable going to the keypad.
Thus it seems that the design was that the row driver would be repeatedly activated for one row at a time (driving it low) which would source current for the LEDs via the transistors which invert the signal, plus at the same time sink current for the keypad (the keypad does not go via the transistors). The pull-up resistors would raise the input to the CPU high, except if a key was pressed. Thus the CPU could deduce which key was being pressed, while it was also outputting to the LEDs.
Taking control!
Knowing how the 595 works, it seemed reasonable to assume that if I could disconnect the clock, data, and latch signals from the processor, and supply my own, then I could make the LEDs show whatever I wanted.
After a considerable amount of time tracing PCB traces, I worked out that it was very easy to do, because each of those reached the processor chip via a jumper on the front.
To do this cut the three jumpers indicated and then solder wires onto the circled ends of the now-cut link (that is the part furthest from the processor).
Now the processor thinks it is sending data to the 595 chips, but it isn't.
Those three wires (clock, data, latch) are then connected to the Arduino as shown (pin numbers for a Uno or similar). You also need to connect the ground wire as well, I took it from the electrolytic capacitor nearby as shown.
[EDIT] Subsequent testing shows that the ground point was not well-chosen. That is disconnected from the circuit if you plug in a wall-wart into the power socket. I suggest that you choose another ground point (eg. pin 8 of any of the 595 chips).
Programming it
This test sketch should light up every second column:
#include <SPI.h>
const byte LATCH = 10;
void setup ()
{
SPI.begin ();
SPI.setClockDivider(SPI_CLOCK_DIV8);
digitalWrite (LATCH, LOW);
for (byte col = 0; col < 9; col++)
SPI.transfer (0xAA);
SPI.transfer (0); // <------- row driver
digitalWrite (LATCH, HIGH);
} // end of setup
void loop ()
{
}
If you change the line marked "row driver" to this:
SPI.transfer (0xCC); // <------- row driver
Then you will see that now only 4 of the rows are alight.
Assuming that works, now you can try something more elaborate.
Main sketch:
#include <SPI.h>
#include "font5x7.h"
const byte LATCH = 10;
const byte CHIPS = 9;
const byte ROWS = 7;
const byte PIXELS_PER_LETTER = 5;
// what to show on the LEDs
byte bitmap [CHIPS] [ROWS];
// timer Interrupt Service Routine (ISR) to update the LEDs
ISR (TIMER2_COMPA_vect)
{
static byte row = 0;
digitalWrite (LATCH, LOW);
for (byte col = 0; col < CHIPS; col++)
SPI.transfer (~bitmap [col] [row]);
SPI.transfer (~ (1 << row));
digitalWrite (LATCH, HIGH);
// wrap if necessary
if (++row >= ROWS)
row = 0;
} // end of TIMER2_COMPA_vect
// Build one letter into the bitmap.
// The variable column is updated to point past the current letter.
// Negative columns are OK, and will just skip without writing (for sideways scrolling purposes).
// Returns true once we have gone past the end of the bitmap, so the outer loop can stop.
bool outputLetter (byte c, int & column)
{
if (c < 0x20 || c > 0x7F)
c = 0x7F; // unknown glyph
c -= 0x20; // force into range of our font table (which starts at 0x20)
for (byte i = 0; i < PIXELS_PER_LETTER; i++)
{
if (column >= 0)
{
// work out which byte (chip) this will end up in
int whichChip = column / 8;
if (whichChip >= CHIPS)
return true;
// work out which column within the chip it will be in
byte whichColumn = column % 8;
char pixels = pgm_read_byte (&font [c] [i]);
for (byte bit = 0; bit < ROWS; bit++)
if (bitRead (pixels, bit))
bitSet (bitmap [(CHIPS - 1) - whichChip] [(ROWS - 1) - bit], whichColumn);
} // end of column not negative
column++; // next row of pixels
} // end of for each of the 5 pixels in the glyph
// gap between letters
column++;
return false;
} // end of outputLetter
// Turn a text string into a bitmap, return its length
int convertMessageToBitmap (const char * message, int column = 0)
{
noInterrupts ();
memset (bitmap, 0, sizeof bitmap);
for (const char * s = message; *s; s++)
{
if (outputLetter (*s, column))
break;
} // end of for each byte in the message
interrupts ();
return strlen (message);
} // end of convertMessageToBitmap
void setup ()
{
SPI.begin ();
SPI.setClockDivider(SPI_CLOCK_DIV8);
memset (bitmap, 0, sizeof bitmap);
// Stop timer 2
TCCR2A = 0;
TCCR2B = 0;
// Timer 2 - gives us a constant interrupt to refresh the LED display
TCCR2A = bit (WGM21) ; // CTC mode
OCR2A = 63; // count up to 64 (zero relative!!!!)
// Timer 2 - interrupt on match at about 2 kHz
TIMSK2 = bit (OCIE2A); // enable Timer2 Interrupt
// start Timer 2
TCCR2B = bit (CS20) | bit (CS22) ; // prescaler of 128
} // end of setup
int column;
void loop ()
{
static char buf [20];
sprintf (buf, "Uptime %ld", millis ());
int len = convertMessageToBitmap (buf, column--);
delay (25);
// work out when to wrap
if (column > CHIPS * 8)
column = - (len * (PIXELS_PER_LETTER + 1));
else if (column < - (len * (PIXELS_PER_LETTER + 1)))
column = CHIPS * 8;
} // end of loop
For the font data make a new tab in the IDE called "font5x7.h" and put this into it.
font5x7.h
// font data - each character is 8 pixels deep and 5 pixels wide
byte font [96] [5] PROGMEM = {
{ 0x00, 0x00, 0x00, 0x00, 0x00 }, // space (0x20)
{ 0x00, 0x00, 0x2F, 0x00, 0x00 }, // !
{ 0x00, 0x07, 0x00, 0x07, 0x00 }, // "
{ 0x14, 0x7F, 0x14, 0x7F, 0x14 }, // #
{ 0x24, 0x2A, 0x7F, 0x2A, 0x12 }, // $
{ 0x23, 0x13, 0x08, 0x64, 0x62 }, // %
{ 0x36, 0x49, 0x55, 0x22, 0x50 }, // &
{ 0x00, 0x05, 0x03, 0x00, 0x00 }, // '
{ 0x00, 0x1C, 0x22, 0x41, 0x00 }, // (
{ 0x00, 0x41, 0x22, 0x1C, 0x00 }, // (
{ 0x14, 0x08, 0x3E, 0x08, 0x14 }, // *
{ 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
{ 0x00, 0x50, 0x30, 0x00, 0x00 }, // ,
{ 0x08, 0x08, 0x08, 0x08, 0x08 }, // -
{ 0x00, 0x30, 0x30, 0x00, 0x00 }, // .
{ 0x20, 0x10, 0x08, 0x04, 0x02 }, // /
{ 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0 (0x30)
{ 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1
{ 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2
{ 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3
{ 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4
{ 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5
{ 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6
{ 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7
{ 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
{ 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9
{ 0x00, 0x36, 0x36, 0x00, 0x00 }, // :
{ 0x00, 0x56, 0x36, 0x00, 0x00 }, // ;
{ 0x08, 0x14, 0x22, 0x41, 0x00 }, // <
{ 0x14, 0x14, 0x14, 0x14, 0x14 }, // =
{ 0x00, 0x41, 0x22, 0x14, 0x08 }, // >
{ 0x02, 0x01, 0x51, 0x09, 0x06 }, // ?
{ 0x32, 0x49, 0x79, 0x41, 0x3E }, // @ (0x40)
{ 0x7E, 0x11, 0x11, 0x11, 0x7E }, // A
{ 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B
{ 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C
{ 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D
{ 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E
{ 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F
{ 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G
{ 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H
{ 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I
{ 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J
{ 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K
{ 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L
{ 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M
{ 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N
{ 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O
{ 0x3F, 0x09, 0x09, 0x09, 0x06 }, // P (0x50)
{ 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q
{ 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R
{ 0x46, 0x49, 0x49, 0x49, 0x31 }, // S
{ 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T
{ 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U
{ 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V
{ 0x3F, 0x40, 0x30, 0x40, 0x3F }, // W
{ 0x63, 0x14, 0x08, 0x14, 0x63 }, // X
{ 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y
{ 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z
{ 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [
{ 0x02, 0x04, 0x08, 0x10, 0x20 }, // backslash
{ 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ]
{ 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^
{ 0x40, 0x40, 0x40, 0x40, 0x40 }, // _
{ 0x00, 0x01, 0x02, 0x04, 0x00 }, // ` (0x60)
{ 0x20, 0x54, 0x54, 0x54, 0x78 }, // a
{ 0x7F, 0x50, 0x48, 0x48, 0x30 }, // b
{ 0x38, 0x44, 0x44, 0x44, 0x20 }, // c
{ 0x38, 0x44, 0x44, 0x48, 0x7F }, // d
{ 0x38, 0x54, 0x54, 0x54, 0x18 }, // e
{ 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f
{ 0x0C, 0x52, 0x52, 0x52, 0x3E }, // g
{ 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h
{ 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i
{ 0x20, 0x40, 0x44, 0x3D, 0x00 }, // j
{ 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k
{ 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l
{ 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m
{ 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n
{ 0x38, 0x44, 0x44, 0x44, 0x38 }, // o
{ 0x7C, 0x14, 0x14, 0x14, 0x08 }, // p (0x70)
{ 0x08, 0x14, 0x14, 0x08, 0x7C }, // q
{ 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r
{ 0x48, 0x54, 0x54, 0x54, 0x20 }, // s
{ 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t
{ 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u
{ 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v
{ 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w
{ 0x44, 0x28, 0x10, 0x28, 0x44 }, // x
{ 0x0C, 0x50, 0x50, 0x50, 0x3C }, // y
{ 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z
{ 0x00, 0x08, 0x36, 0x41, 0x00 }, // {
{ 0x00, 0x00, 0x7F, 0x00, 0x00 }, // |
{ 0x00, 0x41, 0x36, 0x08, 0x00 }, // }
{ 0x30, 0x08, 0x10, 0x20, 0x18 }, // ~
{ 0x7F, 0x55, 0x49, 0x55, 0x7F } // unknown char (0x7F)
};
This should scroll the words "Uptime xxxxxxxx" where xxxxxxxx is the number of milliseconds since the sketch started.
You can change the scroll rate by altering this line:
As you can see the main loop is very simple. The updating of the display is done in a timer interrupt routine, called about 2000 times a second. Each time it is called it draws another row, wrapping back to row 0 after drawing row 7.
Because this is interrupt driven you could be doing other things in the main loop, like finding the temperature, time, etc.
To display some other message, simply do something like this as required:
convertMessageToBitmap ("Hello world!");
The second argument is the column number to start the text at (it can be negative). By updating the column number as in the sketch above, the text can be made to scroll. It is clipped to the bitmap, so it is OK to have a column which is negative, or larger than the size of the display.
Bear in mind you have 72 pixels horizontally and each letter takes 6 pixels, so you can fit 12 characters across the screen at once.
By subtracting one from the column variable it scrolls from right to left, if you add one it scrolls from left to right. Scrolling from right to left might sound counter-intuitive, but since you read from left to right, it is much easier to read a sentence if it scrolls from right to left.
You could "blink" text by alternating some text with blank text, eg.
convertMessageToBitmap ("Hello world!");
delay (1000);
convertMessageToBitmap ("");
delay (1000);
This photo shows it displaying the main sketch. The letters are blurred because of the time taken to take the photo:
This shows the connection between the Uno and the LED device. (It is actually a Ruggeduino because I was worried I might blow something up during testing).
As you can see, only four wires are needed between the Arduino and the display. The display was independently powered because of the power required to drive all the LEDs.
Schematic
A partial schematic is below. It doesn't show all the 595 chips, nor all of the LEDs, transistors, keyboard interface etc. However the main points which show the LED multiplexing are there:
Demonstration video
Completed project
After researching all the above I turned my sign (for the time being) into a temperature and humidity display.
Example display:
I made up an freestanding "Arduino-like" board using the Evil Mad Scientist Atmega target board. This was bolted to the inside of the battery compartment. The battery holder had been discarded because the batteries had leaked and corroded it.
Opening the door to the compartment you can see the board in place with five wires running into the connection points on the LED strip (the four wires described earlier, plus a connection to the +3.3v Vcc line to power the processor).
The board with the various parts shown in detail:
The wiring and relevant code were taken from the temperature and humidity sensor project described here:
http://www.gammon.com.au/forum/?id=12106
Code for displaying the temperature and humidity
#include <SPI.h>
#include "font5x7.h"
#include <FormatDouble.h>
#include <DHT.h>
const byte CHIPS = 9;
const byte ROWS = 7;
const byte PIXELS_PER_LETTER = 5;
const byte PIXELS_PER_CHIP = 8;
const byte HISTORY_ITEMS = CHIPS * PIXELS_PER_CHIP;
const unsigned long UPDATE_INTERVAL = 1000UL * 60 * 5; // every 5 minutes (fills after 6 hours)
const byte DHTPIN = 5;
const byte DHTTYPE = DHT22; // DHT 22 (AM2302)
// DHT-22 temperature and humidity sensor
DHT dht (DHTPIN, DHTTYPE, 6); // 6 for 16 MHz, 3 for 8 Mhz (defines pulse width for zero/one)
float humidity;
float temperature;
// Thermistor stuff
// -----------------------------------------------------
// Wiring: Gnd <----> 5K Thermistor <----> | <----->> 4.7K resistor <-----> 5V
// |
// v
// A1
const byte THERMISTOR_PIN = 1; // A1
// temp. for nominal resistance (almost always 25 C)
const int TEMPERATURENOMINAL = 25;
// resistance at TEMPERATURENOMINAL (above)
const int THERMISTORNOMINAL = 5000;
// how many samples to take and average, more takes longer but is more 'smooth'
const int THERMISTOR_SAMPLES = 5;
// The beta coefficient of the thermistor (usually 3000-4000)
const int BCOEFFICIENT = 3960;
// the value of the 'other' resistor (measure to make sure)
const int SERIESRESISTOR = 4684;
// how many Kelvin 0 degrees Celsius is
const float KELVIN = 273.15;
char humidityString [10];
char temperature1String [10];
char temperature2String [10];
const byte LATCH = 10;
int temperatureHistory [HISTORY_ITEMS];
unsigned long lastHistoryTime;
// what to show on the LEDs
byte bitmap [CHIPS] [ROWS];
// timer Interrupt Service Routine (ISR) to update the LEDs
ISR (TIMER2_COMPA_vect)
{
static byte row = 0;
digitalWrite (LATCH, LOW);
for (byte col = 0; col < CHIPS; col++)
SPI.transfer (~bitmap [col] [row]);
SPI.transfer (~ (1 << row));
digitalWrite (LATCH, HIGH);
// wrap if necessary
if (++row >= ROWS)
row = 0;
} // end of TIMER2_COMPA_vect
// Build one letter into the bitmap.
// The variable column is updated to point past the current letter.
// Negative columns are OK, and will just skip without writing (for sideways scrolling purposes).
// Returns true once we have gone past the end of the bitmap, so the outer loop can stop.
bool outputLetter (byte c, int & column)
{
if (c < 0x20 || c > 0x7F)
c = 0x7F; // unknown glyph
c -= 0x20; // force into range of our font table (which starts at 0x20)
for (byte i = 0; i < PIXELS_PER_LETTER; i++)
{
if (column >= 0)
{
// work out which byte (chip) this will end up in
int whichChip = column / PIXELS_PER_CHIP;
if (whichChip >= CHIPS)
return true;
// work out which column within the chip it will be in
byte whichColumn = column % PIXELS_PER_CHIP;
char pixels = pgm_read_byte (&font [c] [i]);
for (byte bit = 0; bit < ROWS; bit++)
if (bitRead (pixels, bit))
bitSet (bitmap [(CHIPS - 1) - whichChip] [(ROWS - 1) - bit], whichColumn);
} // end of column not negative
column++; // next row of pixels
} // end of for each of the 5 pixels in the glyph
// gap between letters
column++;
return false;
} // end of outputLetter
// Turn a text string into a bitmap, return its length
int convertMessageToBitmap (const char * message, int column = 0)
{
noInterrupts ();
memset (bitmap, 0, sizeof bitmap);
for (const char * s = message; *s; s++)
{
if (outputLetter (*s, column))
break;
} // end of for each byte in the message
interrupts ();
return strlen (message);
} // end of convertMessageToBitmap
void setup ()
{
SPI.begin ();
SPI.setClockDivider(SPI_CLOCK_DIV8);
memset (bitmap, 0, sizeof bitmap);
dht.begin();
// force immediate update
lastHistoryTime -= UPDATE_INTERVAL;
// Stop timer 2
TCCR2A = 0;
TCCR2B = 0;
// Timer 2 - gives us a constant interrupt to refresh the LED display
TCCR2A = bit (WGM21) ; // CTC mode
OCR2A = 63; // count up to 64 (zero relative!!!!)
// Timer 2 - interrupt on match at about 2 kHz
TIMSK2 = bit (OCIE2A); // enable Timer2 Interrupt
// start Timer 2
TCCR2B = bit (CS20) | bit (CS22) ; // prescaler of 128
} // end of setup
void getThermistorTemperature ()
{
float average = 0.0;
// take N samples in a rowy
for (byte i = 0; i < THERMISTOR_SAMPLES; i++)
average += analogRead (THERMISTOR_PIN);
average /= THERMISTOR_SAMPLES;
// convert the value to resistance
average = 1023 / average - 1;
average = SERIESRESISTOR / average;
float steinhart = average / THERMISTORNOMINAL;
steinhart = log (steinhart);
steinhart /= BCOEFFICIENT;
steinhart += 1.0 / (TEMPERATURENOMINAL + KELVIN);
steinhart = 1.0 / steinhart;
steinhart -= KELVIN; // back to celsius
fmtDouble (steinhart, 1, temperature2String, sizeof temperature2String);
} // end of getThermistorTemperature
// reference: http://wahiduddin.net/calc/density_algorithms.htm
float dewPoint (float celsius, float humidity)
{
float RATIO = 373.15 / (273.15 + celsius); // RATIO was originally named A0, possibly confusing in Arduino context
float SUM = -7.90298 * (RATIO - 1);
SUM += 5.02808 * log10(RATIO);
SUM += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO ))) - 1) ;
SUM += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ;
SUM += log10(1013.246);
float VP = pow(10, SUM - 3) * humidity;
float T = log(VP/0.61078); // temp var
return (241.88 * T) / (17.558 - T);
} // end of dewPoint
void getTemperatureAndHumdity ()
{
humidity = dht.readHumidity();
temperature = dht.readTemperature();
if (isnan (humidity))
strcpy (humidityString, "NaN");
else
fmtDouble (humidity, 1, humidityString, sizeof humidityString);
if (isnan (temperature))
strcpy (temperature1String, "NaN");
else
fmtDouble (temperature, 1, temperature1String, sizeof temperature1String);
} // end of getTemperatureAndHumdity
int column;
void loop ()
{
static char buf [20];
int len;
getThermistorTemperature ();
getTemperatureAndHumdity ();
if (!isnan (humidity) && (millis () - lastHistoryTime >= UPDATE_INTERVAL))
{
lastHistoryTime = millis ();
memmove (&temperatureHistory [0], &temperatureHistory [1],
sizeof (temperatureHistory) - sizeof (temperatureHistory [0]));
temperatureHistory [HISTORY_ITEMS - 1] = temperature;
}
if (isnan (humidity))
{
convertMessageToBitmap ("Starting ...");
delay (2000); // let DHT22 chip get ready
}
else
{
sprintf (buf, "Temp: %s C", temperature1String);
len = convertMessageToBitmap (buf);
delay (2000);
convertMessageToBitmap ("");
delay (200);
// do bars indicating temperature over last 6 hours.
// each bar is every 5 minutes
// lowest temperature is 15, highest is 33 (goes up every 3 degrees)
/*
@ 33
@ 30
@ 27
@ 24
@ 21
@ 18
@ 15
*/
noInterrupts ();
memset (bitmap, 0, sizeof bitmap);
for (int column = 0; column < HISTORY_ITEMS; column++)
{
int thisTemperature = temperatureHistory [column];
// skip if not recorded yet
if (thisTemperature <= 0)
continue;
// work out which byte (chip) this will end up in
int whichChip = column / PIXELS_PER_CHIP;
// work out which column within the chip it will be in
byte whichColumn = column % PIXELS_PER_CHIP;
if (thisTemperature < 15)
thisTemperature = 15;
if (thisTemperature > 34)
thisTemperature = 34;
for (byte bit = 0; bit < ROWS; bit++)
if (((thisTemperature - 15) / 3) >= bit)
bitSet (bitmap [(CHIPS - 1) - whichChip] [bit], whichColumn);
} // end of each history column
interrupts ();
delay (2000);
convertMessageToBitmap ("");
delay (200);
sprintf (buf, "Humid %s %%", humidityString);
len = convertMessageToBitmap (buf);
delay (2000);
convertMessageToBitmap ("");
delay (100);
} // end of temperature / humidity known
} // end of loop
|