Home » Radio with a MAX7219, 8 digit, 7 segment display

Radio with a MAX7219, 8 digit, 7 segment display

by Bits and Droids
4 comments
Arduino flight sim components

This code only works on connector version 0.8.7 or above.

Sometimes the TM1637 6 digit 7 segment displays can be hard to source locally. Luckily we have many different options, including the MAX7219 LED driver chip. With this chip, we’re able to achieve the same results in a different package. The code involved isn’t hard, but it enables us to use some more advanced tricks and techniques. I’ll try to really break it down, and hopefully, you’ll understand how the code moves after this article. We will be working with functions, loops, and even some basic algorithms to display our data. Now don’t be daunted. An algorithm sounds intimidating but is nothing more than a chain of logical steps to solve a problem.

Components

Libraries used:

Bits and Droids flight connector
https://www.bitsanddroids.com/downloads

LedControl
https://github.com/wayoda/LedControl

Parts used:

these are affiliate links that help support this content.

Wiring

The MAX7219 displays have a slightly different pinout than the screens we’ve used so far. They don’t utilize the I2C protocol but rather make use of the SPI interface of the Arduino. SPI is basically a communication protocol allowing us only to use 3 pins to drive our display. If you look at the back of your 7 segment display, you notice 24 solder joints visible. If we would drive the displays without any chip, we’d need all 24 pins. By utilizing the SPI interface, we save ourselves a whopping 21 pins. Another neat feature is that we’re able to daisy chain the displays after each other. This will basically save us 21 + 24 pins (45 pins total).

Now I won’t fully detail how this protocol works, but we usually need certain dedicated SPI pins to utilize the interface fully. It’s important to note that the LedControl library does not utilize this feature. In short, we’re able to use any 3 pins that we desire on our Arduino. The downside is that we lose some speed that we’d normally gain by using an approach that uses the interface to its fullest.

In our example, we’ve hooked up the display to pins 12 (DATA-IN or DIN), 11 (CLK), 10 (LOAD or CS). The displays usually have 2 sides. 1 side has a DIN pin, while the other has a DOUT pin. To keep things simple, DIN is for incoming data. A good example would be the data coming from our Arduino towards our display. The DOUT is for outgoing data. In our case, we use this pin to send data to our second screen. The DOUT of the first screen is hooked up to the DIN pin of the second screen. We could match the first pin of the first screen to go to the first pin of the second screen and the second to the second, etcetera.

There

Soldering

It’s no necessity to solder anything in this project. It’s totally optional, but instead of wiring each screen to the 5V and ground line separately, you have the option to pass the current through the first screen to the second. We need to remove the diode located at the D1 mark and bridge the traces with some wire or solder.

The contacts to bridge

There is also the possibility to create multiple LedControl instances in our code using 3 pins each per displau. For the reason “because we’re able to” I’m going to show the daisy chain method and the code involved surrounding this approach.

The full code

Let us first take a look at the full code. Now remember it may seem daunting but we will go through it step by step afterwards.

#include<BitsAndDroidsFlightConnector.h>
#include<LedControl.h>

BitsAndDroidsFlightConnector connector(false);
LedControl segmentDisplay = LedControl(12,11,10,2);

int bufferArray[6];


long  activeCom1 = 666666L;
long  oldActiveCom1= 0L;

long  standbyCom1 = 666666L;
long  oldStandbyCom1= 0L;



void printNumbers(long numberToPrint, int screen){
   /* Our frequency has 6 digits there we loop through this code block as long as: i (our counter) is smaller than 6.
  This is because an array starts counting at 0, in our case frequency = 6 digits in our array they would be stored in 0, 1, 2, 3, 4, 5. Therefor < 6 */  
for (int i = 0; i < 6; i++) { 
    
  
   bufferArray[i] = numberToPrint % 10;

   if(i == 3){
    segmentDisplay.setDigit(screen, i, bufferArray[i], true);
   } else{
     segmentDisplay.setDigit(screen, i, bufferArray[i], false);
   }
    numberToPrint /= 10;
   }
   
         /*Divide the numberToPrint to ensure the next loop through this block checks the next tenfold. 
   In this example 123456 / 10 leaves us with 12345 (since it's an int the decimal gets ommited).
   The next loop we encounter 12345 / 10 = 1234, etc. etc. By calculating the remainder we can loop through each character per loop*/


    }
   



void setup() {
  //We start our Serial connection
  Serial.begin(115200);
  //How long the Arduino waits before moving on to the next incomming value
  Serial.setTimeout(15);

  //This block enables our screen, 
  segmentDisplay.shutdown(0,false);
  //Sets the intensity to 8(a medium brightness)
  segmentDisplay.setIntensity(0,4);
  //And clear any possible characters that are displayed at the start
  segmentDisplay.clearDisplay(0);

  //repeat the block above for the second screen
  segmentDisplay.shutdown(1,false);
  segmentDisplay.setIntensity(1,4);
  segmentDisplay.clearDisplay(1);
}

void loop() {
  connector.dataHandling();
  activeCom1 = connector.getActiveCom1();
  //Check if the incoming frequency differs from the current one displayed
  if(activeCom1 != oldActiveCom1){
    //call our function passing the number to display and on which screen we want to display it (0 is screen 1 and 1 is screen 2)
    printNumbers(activeCom1, 0);
    oldActiveCom1 = activeCom1;
  }
  
  standbyCom1 = connector.getStandbyCom1();
  if(standbyCom1 != oldStandbyCom1){

    printNumbers(standbyCom1, 1);
    oldStandbyCom1 = standbyCom1;
  }
  
}

To fully understand what is happening, I like to relate computer logic to real-world objects. In our case, the 7 segment displays both have their own Max7219 chip that drives the LEDs. The chip can address each LED individually. The easiest way to visualize this would be a row of drawers in a cupboard. Each drawer contains a single item. In our case, we have 2 cupboards with 8 drawers each (2, 7 segment modules containing 8 digits each). To change a certain number, we have to select which cupboard to open, followed by which drawer to open. A number can reference our cupboards and drawers. Or in our case, our digits can be referenced by a number just like the screen itself. Computers start counting at 0, so our first screen would be 0 while our second screen is 1. The same applies to the numbers. We have 8 digits per screen. Starting at 0, it will give us a range of 0 – 7. If we want to alter the first value of the second screen, we need to address 1,0 (1 for the second screen and 0 for the first character).

Overview visualizing arrays and our 7 segment displays

LedControl library

We could write the entire logic from scratch, but the LedControl library is widely used and easily implemented. We start by including the library at the top of the file. While we are there, we will include the Bits and Droids library. For both instances, we need to create an object. Since we use 2 MAX7219 chips, it’s important to tell the library this when creating a new instance.

 #include <BitsAndDroidsFlightConnector.h>
#include <LedControl.h>

/*the parameters required for the LedControl library are the 3 
pins and the amount of 7219 chips in our circuit. 
We daisy chaned 2 displays granting us 2 chips in our circuit*/ 
LedControl displays = new LedControl(12,11,10,2);

//Here we just create a new connector instance, head over to the docs to learn more
BitsAndDroidsFlightConnector connector(false);

Ints and Longs

By default, an Int is a 16 bit variable on our Arduino. A 16-bit byte can hold a value ranging from -32768 to 32767. Our frequency usually contains 6 numbers. 100.000 would be the lowest possible containing 6 digits meaning that it would be too big for an Int. If we define our int as a long int, we double the bits to 32, giving us a range of  -2,147,483,648 to 2,147,483,647 (this will fit just fine for our frequency). It’s not advised to convert every int too long, but this could be a workable solution in some cases. It’s good only to choose this when the situation calls for it.

Displaying data on our 7 segment display can be quite resource-intensive. It’s advisable only to update the screen when the data has actually changed. To achieve this, we create 2 variables, a new frequency and an old frequency. We can then check if the new frequency is different from the old. Only if that is the case, we update the display. The variables will remain put only the value changes. That is why we add them to the top of our file underneath the 2 library objects.


Here we also create our first drawer system. This drawer system in code is called an Array. An Array is a container that holds a certain type of data. In our case, we want to store an array of bytes (the smallest size possible). It’s also important to define the size of the array. In our case, it’s going to hold the frequency with a single number in each drawer.

//#includ<.....
//...
//..connector(false)
//-------------------------

long  activeCom1 = 666666L;
long  oldActiveCom1= 0L;

long  standbyCom1 = 666666L;
long  oldStandbyCom1= 0L;

int bufferArray[6];

The setup

In our setup block we mainly initiate a Serial line and boot up our displays.

void setup() {
  //We start our Serial connection
  Serial.begin(115200);
  //How long the Arduino waits before moving on to the next incomming value
  Serial.setTimeout(15);

  //This block enables our screen, 
  segmentDisplay.shutdown(0,false);
  //Sets the intensity to 8(a medium brightness)
  segmentDisplay.setIntensity(0,4);
  //And clear any possible characters that are displayed at the start
  segmentDisplay.clearDisplay(0);

  /*repeat the block above for the second screen 
    Our first display was number 0 while this display is number 1*/
  segmentDisplay.shutdown(1,false);
  segmentDisplay.setIntensity(1,4);
  segmentDisplay.clearDisplay(1);
}

Functions and our algorithm

This will be the slightly harder part. Adding our first simple algorithm to the code. We first need a game plan. The LedControl library wants us to specify for each character where we want to put it. We can’t just pass the number 100. The number 100 needs to be broken down into single digits. Number 1 goes to screen 0 digit 0, number 2 goes to screen 0 digits 1, and number 0 goes to screen 0 digits 2.

Another factor is the’.’. After 3 characters we want to display a ‘.’ to represent the seperation between Mhz and Khz.

Because we want to apply the same steps each time we update the screen, it’s wise to create a function. A function is a block of code that we can call upon, which executes each time we call his name. Our setup block and loop block are also a sort of function. The setup only gets called once while the loop block get’s executed constantly. In our loop block, we want to implement a check telling us if the incoming frequency differs from the current frequency. Suppose that is the case, we want to call upon our function to handle the logic of splitting our numbers and displaying them on the screen of our liking. Our function, therefore, requires 2 parameters, the number to display and the screen where we want to display it.

void printNumbers(long numberToPrint, int screen){
   /* Our frequency has 6 digits there we loop through this code block as long as: i (our counter) is smaller than 6.
  This is because an array starts counting at 0, in our case frequency = 6 digits in our array they would be stored in 0, 1, 2, 3, 4, 5. Therefor < 6 */  
for (int i = 0; i < 6; i++) { 
    
  
   bufferArray[i] = numberToPrint % 10;
   if(i == 3){
    segmentDisplay.setDigit(screen, i, bufferArray[i], true);
   } else{
     segmentDisplay.setDigit(screen, i, bufferArray[i], false);
   }
    numberToPrint /= 10;
   }
   
         /*Divide the numberToPrint to ensure the next loop through this block checks the next tenfold. 
   In this example, 123456 / 10 leaves us with 12345 (since it's an int the decimal gets omitted).
   The next loop we encounter 12345 / 10 = 1234, etc. etc. By calculating the remainder we can loop through each character per loop*/

    }
   

% Remainder

The remainder (%) is a mathematical operation that returns the remainder when we divide a value by another value. 100 % 10 will return 0. 101 % 10 will return the value 1 (10 can fit 10 times into 100, 101 – 10 times 10 leaves us with 1, in this example, 1 is the remainder). If we take our long 6 digit number to apply % 10, we can isolate the last digit. 124.850 % 10 = 0. The next step would be to divide the new value by 10. 124.850 / 10 = 12485. Again, we can apply the remainder, which returns the value 5 (12485 % 10 = 5). If we repeat these steps for each digit, we can split our 6 digit value into 6 single digits without too much effort. We store each of our single digits in the Array, and we have a handy dandy cupboard that contains each of our numbers.

123450% 100
12345% 105
1234% 104
123% 103
12% 102
Remainder example

Some food for thought

For those of you that have a keen eye, the buffer is overkill. Since each time we calculate our remainder, we store it in our array. We then immediately print that number to our screen before calculating the next. We could theoretically print the digit and obliterating the buffer.

I’ve also manually set each value to 6. But what would happen if we want to use all 8 digits? Do we need to copy our code? No, if we would really fine-tune our code, we’d probably include a variable at the top of the file determining the number of characters to process. This way, we could change the value once, and our logic would handle the rest. One of the benefits of using a function is that our code becomes very modular. We can pass different values to the same function without having to retype the code.

You may also like

4 comments

Martin Thijssen June 16, 2021 - 8:05 pm

Hi, Nice project! I like to build it but during compilation I got the next error:

/Users/martin/Documents/Arduino/Radio/Radio.ino: In function ‘void loop()’:
Radio:64:67: error: cannot convert ‘String’ to ‘long int’ in assignment
connector.dataHandling(); activeCom1 = connector.getActiveCom1();
^
Radio:72:42: error: cannot convert ‘String’ to ‘long int’ in assignment
standbyCom1 = connector.getStandbyCom1();
^
exit status 1
cannot convert ‘String’ to ‘long int’ in assignment

Maybe I do something wrong, I am a newbie at Arduino…
Can someone advise me?

Thanks in advance.
Best regards,
Martin

Bits and Droids June 17, 2021 - 10:18 am

If you can send your full sketch to info@bitsanddroids.com I’ll have a look to see what is going wrong

Martin Thijssen June 16, 2021 - 8:26 pm

Oh and what about the Encoder? I didn’t see how it works in the coding…

Bits and Droids June 17, 2021 - 10:17 am

You are completely right! It’s in here I should have linked it in the article as well.
https://www.bitsanddroids.com/create-your-own-arduino-mfs2020-radio-stack/

Leave a Comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept