Using 7 Segment LED displays, Part 4 – dual quadruple units

Okay, I thought the series on 7 segment LED displays would run into three parts, but it has turned out to be a Douglas Adams-esque trilogy in four parts. I had completely forgotten to write about a clock based on a unit of two 4 digit, 7 segment, red-color displays. These have somewhat different method of being run from the prevous instalments.

In the past, you saw how to run a single digit display with a 74HC595 shift register, then how to do a four-digit system with the same chip, and then how to run giant 10 cm single digit displays so that you get a four digit countdown timer. These had shift registers, which were chained.

The unit that I will discuss in this blog post is different. It looks like this:

Dual 4 digit, 7 segment display. Image courtesy hub360

It is called a MAX7219 dual display, and as you see, it comes mounted on a printed circuit board, and there are only five pins to the entire unit. This is very handy when you start setting this up. Here’s how my system looks before being mounted on a 3D printed plate:

Device showing date and time, using an ESP32 and MAX71219 dual

The full code is available on my Github page.

Let’s have a look at the constituent parts of this contraption.

MAX7219 Display Driver

This nifty little integrated circuit is really powerful, yet costs only 8 euros. On Adafruit’s site, it is described thus:

When you need some help driving a lot of LEDs, the MAX7219 is the best friend you could hope for. Many of us know that if you want to control a lot of LEDs, you’ll want to use multiplexing, a technique that lets you control 64 LEDs (say) with only 16 pins (8×8). The annoying thing about ‘plexing is that you need to use 8 power transistors (or a power register/latch, that can supply over 100mA per pin) AND you have to constantly refresh the display to keep the image stable. If you need to get something together quickly, or don’t want to bother with writing all that code, and especially if you want clean wiring, this chip is the one-stop-solution!

The MAX7219 does all the control and refresh work for you in driving either an 8×8 matrix display or 8 x 7-segment displays (usually these also have a dot so its really an 8-segment display) – 64 LEDs total. All you have to do is send it serial commands via the 4-pin SPI interface and it will auto-magically take care of the rest.

Courtesy adafruit.com

This means that you can do this unit with a single MAX7219 display driver. The datasheet is here and in it, there is this picture of the connectors, and a typical use:

MAX7219 wiring, courtesy of adafruit

As you can see, all you need to bring in from the Arduino or ESP32 (as in my case) is three pins. This is a whopping improvement over the previous episodes, when you want to run eight digits. The connections are DIN (Serial Data IN), Load CS (Load Data Input and Chip Select, these deal with passing data into the registers), and CLK (clock signal to tell the device when to load and when to display).

You can use the MAX7219 for driving a very large amount of LEDs and add even more by chaining them

This display has five pins or holes for pins. It also has the MAX7219 chip buried underneath the displays. This way you can just lead these five wires to it from the controller unit.

  • VCC, 5 volts in
  • GND, ground
  • DIN, data in
  • LOAD, load data to displays
  • CLK, clock signal to time the works

VCC and Ground are connected to ESP32’s 5V and Ground pins. The others are programmatically selected when writing the code.

The DHT11 sensor

The DHT11 is a standard piece of equipment when working with Arduinos or ESP32s, and there is a need for temperature and humidity sensing. It’s a four pin sensor that takes a 10K resistor to work properly. Its setup is amply described in this page, and I will not redo it here.

Basic DHT11. Image courtesy indiamart.com

The VCC is five volts from ESP32, and GND is ground. NC is left unconnected, but the DATA pin is also connected to the VCC via a 10 K resistor. I soldered a resistor between the two pins and it works nicely.

Code structure

This program has a few functions in it, but the general outline is as follows, but you may want to get the entire file from Github to see the whole code.

  • initialise variables and setup
  • go get the time from the Internet and turn off WLAN
  • go into the main loop:
  • once a second, check to see whether the mode switch is in the DATE, TEMP or HUMI position
  • based on that, set the first four digits to show the selected value
  • set the last four digits to show the time
  • push the values to the MAX7219 and on to the digits
  • every five days, reinitialise the clock to fix any eventual drift in the ESP32 real time clock, which isn’t as accurate as the Swiss clocks.

The initials are as always in any Arduino/ESP32 project, but there are some interesting parts.

define MAX2719_DIN 27 // Pins connected to the MAX chip
define MAX2719_CS 25
define MAX2719_CLK 26
define DHTTYPE DHT11 // DHT 11
define DHTPIN 16 // Digital pin connected to the DHT sensor
include "DHT.h" // temp and humidity sensor library
include "WiFi.h" // wifi library
include "time.h" // time functions library
DHT dht(DHTPIN, DHTTYPE);

The MAX2719 can be set to listen to almost any pins, but I set them as such in this app. DHTs come in two flavors, DHT11 and DHT16, and as the library can use both, it must be explicitly stated as above. Also, DHTPIN must be selected for use here. WiFi.h and time.h are needed to connect to the WLAN and get the time from the Network Time Server.

The next bit gets the time structures in place:

// time service settings
const char* ntpServer = "pool.ntp.org"; // server address
// timezone setting. GMT+1 = 3600 GMT+2 = 7200 etc.
const long gmtOffset_sec = 7200;
// in effect when Daylight Saving Time is in effect
const int daylightOffset_sec = 3600;
struct tm timeinfo;

After this, you have an object called timeinfo that has the time for further reference. It is loaded using the function goGetTime:

 void goGetTime() {
// connect to WiFi
Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" CONNECTED");
// init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
return;
}
// disconnect WiFi as it's no longer needed
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}

So now you have the function to get the Real Time Clock initialised and running.

The setup starts the DHT, gets the LEDs initialised and goes to get the time.

void setup() {
dht.begin(); // start the temp-humi sensor
Serial.begin(115200); // start serial to device
// set pin modes
pinMode(modePinTemp, INPUT_PULLUP);
pinMode(modePinHumi, INPUT_PULLUP);
pinMode(resetButtonPin, INPUT_PULLUP);
pinMode(ledPinHumi, OUTPUT);
pinMode(ledPinTemp, OUTPUT);
pinMode(ledPinDate, OUTPUT);
initialise(); // function for starting LED displays
goGetTime(); // update system time from Network Time
}

Then there are a few functions to help figure out what the user wants to see. I have a three way switch rigged in the system, and two pins are connected to the switch. If it is in the up position, you see the date, if down, you see the temperature, and if in the middle and both pins are off, you see the humidity.

// function to check if the mode switch is in TEMP setting
boolean checkModePinTemp() {
if (digitalRead(modePinTemp) == HIGH) {
Serial.println("Temp mode");
return true;
}
else {
return false;
}
}

// function to check if the mode switch is in HUMIDITY setting
boolean checkModePinHumi() {
if (digitalRead(modePinHumi) == HIGH) {
Serial.println("Humidity mode");
return true;
}
else {
return false;
}
}

// function to check if the RESET button is pushed
boolean checkResetButton() {
if (digitalRead(resetButtonPin) == HIGH) {
Serial.println("Reset request");
return true;
}
else {
return false;
}
}

The main loop

The main loop is longish, so I’ll just illustrate a few points from it.

The DHT is read using the following code:

float h = dht.readHumidity(); // Read humidity
String humiStr = String(h);
float t = dht.readTemperature();// Read temperature as Celsius (the default)
String tempStr = String(t);
float f = dht.readTemperature(true);// Read temperature as Fahrenheit (isFahrenheit = true)
// error handling routine
if (isnan(h) || isnan(t) || isnan(f)) {
Serial.println(F("Failed to read from DHT sensor!"));
delay(1000);
return;
}

They are converted into strings, because later on, the numbers must be picked out into tens, ones, decimal tens, and decimal ones, for both the humidity and the temperature displays. For temperature, it looks like this:

// this returns the tens of the temperature as Integer
tempTens = tempStr.substring(0, 1).toInt();
// this returns the ones of the temperature as Integer
tempOnes = tempStr.substring(1, 2).toInt();
// this returns the decimal tens of the temperature as Integer
tempDeciOnes = tempStr.substring(2, 3).toInt();
// this returns the decimal ones of the temperature as Integer
tempDeciTens = tempStr.substring(3, 4).toInt();

Now I have four integer variables that can be passed to the display unit as single digits. tempTens is picked from the string that contains the temperature with the substring structure. Picking the substring of character 0 to 1 gives you the single digit, but it is in the datatype String. Therefore you need to add the function .toInt() to it, before you pass it on to the LED later on – the digit has to be Integer, not String.

Figuring that out took me a loooong time.

Before actually lighting up the eight LED digits, I will buffer values into variables once more, to make the actual data passage understandable.

// for showing temperature
if ((boolTempPin == true) && (boolHumiPin == false)) {
// if (loopCounter<10) {
ledLeft1 = tempTens;
ledLeft2 = tempOnes + 128; // +128 gives the decimal point 
ledLeft3 = tempDeciTens;
ledLeft4 = tempDeciOnes ;
digitalWrite(ledPinTemp, HIGH);
digitalWrite(ledPinHumi, LOW);
digitalWrite(ledPinDate, LOW);
}

Here you also see that I have three LEDs, which indicate the mode of the display. In this IF clause, I pull the ledPinTemp UP HIGH and the others remain LOW.

If the switch is in the middle position, both pins are LOW and the system is set up to show temperature. And when the switch is in the up position, the ledPinHumi is HIGH, and the system displays the humidity in the left digits.

The time is always passed to the right side 4 LEDS, hence the time is also parsed into separate digits as thus:

// tens digit of day
dayTens = timeinfo.tm_mday / 10;
// ones digit of day
dayOnes = timeinfo.tm_mday % 10;
// tens digit of month
monthTens = timeinfo.tm_mon / 10;
// ones digit of month, +1 needed to adjust
monthOnes = timeinfo.tm_mon % 10 + 1;
// tens digit of hour
hourTens = timeinfo.tm_hour / 10;
// ones digit of hour
hourOnes = timeinfo.tm_hour % 10;
// tens digit of minutes
minuteTens = timeinfo.tm_min / 10;
// ones digit of minutes
minuteOnes = timeinfo.tm_min % 10;
// set the right LED values for time
// ths happens every time, because clock is always on
ledRight1 = hourTens;
ledRight2 = hourOnes + 128;
ledRight3 = minuteTens;
ledRight4 = minuteOnes;

Pushing the digits to the display

Once all the data is set in single digit variables, ie. ledLeft1-4 and rightLed1-4, they can be pushed out to be displayed by the dual LED display. This is achieved by using this function:

void output(byte address, byte data)
{
digitalWrite(MAX2719_CS, LOW);
// Send out two bytes (16 bit)
// parameters: shiftOut(dataPin, clockPin, bitOrder, value)
shiftOut(MAX2719_DIN, MAX2719_CLK, MSBFIRST, address);
shiftOut(MAX2719_DIN, MAX2719_CLK, MSBFIRST, data);
digitalWrite(MAX2719_CS, HIGH);
}

It is given a address as a byte as well as a piece of data as a byte. First the clock pin of the chip is pulled LOW to reset it, then it does two shiftOut operations. Then it is pulled HIGH to display the byte. When this is done eight times, all eight digits are loaded by the shift register, and then displayed at one go.

// send all data to LED units
output(0x08, ledLeft1); // Left led 1 value
output(0x07, ledLeft2); // Left led 2 value
output(0x06, ledLeft3); // Left led 3 value
output(0x05, ledLeft4); // Left led 4 value
output(0x04, ledRight1); // Time value in hours, tens
output(0x03, ledRight2); // Time value in hours, ones
output(0x02, ledRight3); // Time value in minutes, tens
output(0x01, ledRight4); // Time value in minutes, ones

Wiring the system

I’ll describe the wiring in writing here because this is a rather simple system. Remember to prepare the DHT11 so that you have a 10K resistor between the data lead and the 5V lead – there are still three leads from it, voltage, data, and ground.

The parts you need are

  • ESP32
  • MAX7219 dual 4 digit display
  • three LEDS
  • a power supply – I used an old phone charger of which I cut off the connector and added two female connectors for 5V and Ground
  • one three position switch
  • one two position switch for power
  • one push button for resetting the device.
  • You can build a box for this from dense cardboard or use a 3D printer
  • Shared leads for both 5V and ground pins – you need to supply voltage to the display, and the DHT11.

The connections are:

  • DIN pin of the display to the ESP32 pin 27
  • CS pin of the display to the ESP32 pin 25
  • CLK pin of the display to the ESP32 pin 26
  • VIN pin to 5V
  • GND pin to Ground
  • Humidity LED positive pin to ESP32 pin 20, negative to ground
  • Temperature LED positive pin to ESP32 pin 17, negative to ground
  • Date LED positive pin to ESP32 pin 21, negative to ground
  • Humidity switch lead to ESP32 pin 19
  • Temperature switch lead to ESP32 pin 18
  • Humidity-temperature switch common to ground
  • ESP32 pin 16 to DHT sensor data pin

It is easier for you to understand how this thing works if you take down the code from Github, assemble the parts, and do the connections carefully. This is a video showing you how it works.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.