Show any number
Two operators pull a number apart into digits, and the loop runs backwards for a reason. What to do when the number has more digits than you have modules, and why a correct number can still show nothing.
Taking a number apart
A chain does not accept a number. It accepts one byte per module, so the number
has to be split up first, and the two operators that do it are % and /.
t % 10 is the rightmost digit and t / 10 throws it away. Whole-number division on an unsigned long keeps no fraction, so 73 / 10 is 7, not 7.3. Two lines and the number has been taken apart from the right.t % 10 is the remainder after dividing by ten, which is the rightmost digit.
t /= 10 divides and keeps no fraction — on an unsigned long, 73 / 10 is 7,
not 7.3 — which throws that digit away. Repeat, and the number comes apart from
the right.
The loop counts down through the array because the digit that comes off first is the ones digit, and the ones digit has to be the last byte sent. That is the rule from the previous article showing up in code.
The number that is too big
Set the slider past 99 on a two-module chain. Nothing errors. The loop runs twice, takes two digits, and the rest are simply never looked at — so 1234 shows as 34 and your sketch carries on believing it displayed 1234.
That is why showNumber() in the sample returns a bool. One comparison
against chainLimit() turns a silent wrong answer into something you can
handle:
if (!showNumber(reading)) {
// too wide for this chain — show an error, or scale it
}Blank is a digit too
0x00 lights nothing, which reads as a gap rather than a nought. That gives
you leading-zero suppression for free — walk the array from the left blanking
zeros until you hit something else — and it gives you somewhere to put a minus
sign, which is segment G on its own, 0x40.
Both cost a digit position. A two-module chain that can show a sign covers −9 to 99, and there is no arrangement of seven bars that avoids that.
A right number nobody can see
showNumber() returning true means the registers hold your number. It does not
mean the digits are lit: that is the PWM pad's decision, and the board ships with
it open. Sketch and board have to agree on which of the two ways you chose, as in
the counting sketch: D9 wired to PWM and
PWM_PIN 9, or the pad soldered shut and PWM_PIN -1. Set PWM_PIN to 9 on a
shorted pad and the pin is driving a wire that is held high.
The code
The same wiring as the counting sketch, PWM included, with one function you can call with any value. showNumber() refuses a number too wide for the chain instead of quietly truncating it.
// Show any number on chained 4" seven-segment modules with a 74HC595.
//
// Wiring, at the module's IN edge (reading the back, IN is on the left):
// 12 V supply + -> 12V two-pin header
// 12 V supply - -> GND two-pin header
// Uno 5V -> 3V3/5V six-pin header
// Uno GND -> GND six-pin header
// Uno D8 -> DATAIN (SER)
// Uno D11 -> LATCH (RCLK)
// Uno D12 -> CLOCK (SRCLK)
// Uno D9 -> PWM or leave PWM empty and solder the
// PWM pad shut (then PWM_PIN is -1)
//
// The board ships with the PWM pad OPEN. With nothing on PWM the digit
// has no supply and stays dark, however correct the number is.
//
// The 12 V supply's ground and the Uno's ground must be the same wire.
//
// Arduino IDE: Tools > Board > Arduino AVR Boards > Arduino Uno, then
// Tools > Port and pick the port the board appears on. No libraries.
#define NUM_DIGITS 2
#define SER 8 // DATAIN (SER)
#define LAT 11 // LATCH (RCLK)
#define CLK 12 // CLOCK (SRCLK)
#define PWM_PIN 9 // PWM; use -1 if you shorted the PWM pad instead
const uint8_t d[] = {
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F
};
const uint8_t BLANK = 0x00; // no segments
const uint8_t MINUS = 0x40; // segment G on its own
const uint8_t POINT = 0x80; // the decimal point
void updateDisplay(const uint8_t *buf) {
digitalWrite(LAT, LOW);
for (int i = 0; i < NUM_DIGITS; i++)
shiftOut(SER, CLK, MSBFIRST, buf[i]);
digitalWrite(LAT, HIGH);
}
// Highest number this chain can hold: 9, 99, 999 …
unsigned long chainLimit() {
unsigned long limit = 1;
for (int i = 0; i < NUM_DIGITS; i++) limit *= 10;
return limit - 1;
}
// true if it was shown, false if the number needs more modules than you
// have. Without this check the top digits vanish and nothing says so.
bool showNumber(unsigned long value, bool leadingZeros = true) {
if (value > chainLimit()) return false;
uint8_t b[NUM_DIGITS];
unsigned long t = value;
for (int i = NUM_DIGITS - 1; i >= 0; i--) {
b[i] = d[t % 10]; // rightmost digit of what is left
t /= 10; // drop it
}
if (!leadingZeros) { // blank the noughts in front of the number
for (int i = 0; i < NUM_DIGITS - 1 && b[i] == d[0]; i++) b[i] = BLANK;
}
updateDisplay(b);
return true;
}
void blank() {
uint8_t b[NUM_DIGITS];
for (int i = 0; i < NUM_DIGITS; i++) b[i] = BLANK;
updateDisplay(b);
}
void setup() {
pinMode(SER, OUTPUT);
pinMode(LAT, OUTPUT);
pinMode(CLK, OUTPUT);
if (PWM_PIN >= 0) {
pinMode(PWM_PIN, OUTPUT);
digitalWrite(PWM_PIN, HIGH); // high = the digit is supplied
}
blank();
if (!showNumber(42, false)) blank(); // 42 on a two-module chain
}
void loop() {
// nothing: the display holds what it was last given
}blank() and showNumber() are the two you will keep. Everything else is the same table and the same three-line update.
When it does not work
That is the failure this sketch's return value exists to catch. The peeling loop runs once per module and stops, so 1234 on a two-module chain shows 34 and nothing reports a problem. Check the value against chainLimit() before you send it.
Pass false for leadingZeros and the noughts in front of the number become blanks instead. For a clock you usually want the opposite — 09:05 reads better than 9:5 — so leave it true for the minutes and decide for the hours.
Put MINUS in the leftmost slot and the digits in the rest, which costs you one module's worth of range: a two-module chain shows -9 to 99. There is no other way round it, because a minus sign needs a digit position to live in.
showNumber() returned true, so the registers hold 42, and a dark digit is a supply question. The PWM pad ships open: wire PWM to D9 with PWM_PIN 9, or solder the pad shut and set PWM_PIN to -1. If the pad is shut and the digit is still dark, measure 12 V at the 12V pin against the Uno's GND.
It would. The output register holds its contents until something latches new ones into it, and a board reset does not touch the module. Call blank() at the top of setup() so a restart always clears what was there.
Seven faults in the order to look, starting with the pad that ships open, and the one check that settles each of them before you rewire anything.
When nothing lights →Edit this page — content/books/seg4/show-any-number.mdx
Questions about this product
See what other owners have asked, and read their solutions.
4″ 7-Segment LED Display with 74HC595
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.