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:
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:
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:
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.
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.
prosím jaký je kód pro připojení více obvodů max7219.
Automatický překlad: MAX7219 obsahuje datový výstup, který je vtažen do datového pinu dalšího MAX7219. Nápověda: https://forum.arduino.cc/t/daisy-chaining-multiple-max7219-chips/3305