Code for controlling large 7-segment display using four MCP23017

Hey guys

Beforehand: I did find a few threads related to projects using the MCP23017 I2C-demultiplexer but my main issue are my missing programming skills. I read everything I could find concerning similar projects but still am kinda lost when it comes to generating the actual code for this specific application. The main aim of this thread is to get suggestions for code I can use for this project.

The project

We´re going to build a scoreboard for a school gymnasium.

The board needs three functions:

-display time

-set/pause/reset a timer

-optical and acoustic feedback when timer is stopped or time is elapsed (there´s gonna be a buzzer and red and green indicator lights)

-display scores of each team (though that´s quite probably the easiest part to implement)

-do each of the aforementioned things via IR-remote (not focusing on that yet at current stage)

The brain is going to be an Arduino Uno which, via its I2C-Bus controls four MCP23017 demultiplexer ICs (each IC having 16 outputs and being controlled only via the SDA and SCL outputs of the Arduino on that so called "I2C-Bus"), which in turn control the separate segments of eight 7-segment-digits. Four of these are going to be for displaying the current time and using the timer functionality.

Two digits for the red team score, two digits for the blue team score.

The actual digits on the board are going to be size A3 (420x297 mm or 16,54x11,7 inches), use fourteen 10 mm diameter LEDS per segment and will have a dedicated control- and load-circuit and 24V, 100W power supply.

For testing the code though, I´m using a plastic breadboard and regular sized (about an inch in height) 7-segment-digits with 270 Ohm current limiting resistors.

I added a Fritzing illustration to make it easier to imagine what we´re actually talking about here.
That Fritzing layout is NOT exactly true to reality. For example I didn´t find an actual passive common cathode, 2-digit-7-segment block with 18 pins. The actual hardware are MAN 6740 and 270Ohm resistors. The generic Fritzing IC I used to represent the MCP23017 does NOT accurately reflect the pin layout on an actual MCP23017 - but for this purpose it doesn´t matter.
Keep in mind - there are going to be six more 7-segment-digit-blocks and three more MCP23017 connected to my breadboard soon. So the code would need to be able to correctly address all MCPs using their hardware adresses and the Adafruit library (open for other suggestions, using wire.h seems more difficult to me though at my skill level.
Also - the colour code on the 270 ohm resistors is off. Fritzing didn´t have 270 Ohm to be selected.

The code

So far - I managed to write code for a rudimentary timer function using only two digits.

I´m using the Adafruit MCP23017 library (GitHub - adafruit/Adafruit-MCP23017-Arduino-Library: Arduino Library for Adafruit MCP23017).

In the beginning of the code you can see that the outputs A0 - A7 (A0 is "0", A1 is "1",.. in the Adafruit library) of the MCP23017 are connected to segments A - G and dotpoint (D.P.1 "DP" in the code) of the first 7-segment-digit. The outputs B0 - B7 (B0 is "8", B1 is "9",.. in the Adafruit library) are connected to segments A - G and dotpoint (D.P.2 "DP1" in the code) of the second 7-segment-digit.

The function "void_display number" distributes whichever value is set in the void loop for "i" between the first and second 7-segment-digits, using a division by 10 and modulo and "void display_digit".

I used the relation of A0 being "0" and B0 being "8" to use an offset by 8 to use the code of "void display_digit" both for the first and second 7-segment-digits.

my problems (at least perceived):

  • missing experience coding, this is my first actual project coding something. I did learn basic Python programming and did programming examples at school but actually implementing sth like this in a language that´s also pretty unknown to me turns out to be tough. I´m lacking imagination in this department on how to structure code architecture.

  • in the current version of the code, the outputs A0 - A7 of the MCP are connected to the segments in a logical order (see above). Turns out though - when I wanna connect eight little 7-segment digits and four MCPs on breadboard it gets really messy and non-portable fast. So I decided to transfer the whole breadboard circuit to perforated PCB board instead. And there - it turns out the whole thing is tidier and easier to route if I don´t follow the logical order (see above). That means connect f.e. A0 to C2 (segment C, digit 2) instead of segment A of digit 1 (the whole pattern is described above), A1 to G2,... and so on.
    That does make the installation tidier but messes up my nice offset function in my code...

  • the hardware adresses of each MCP23017 are determined by biasing three pins (A0, A1, A2) either high or low. There´s a maximum of eight MCPs that can be controlled by one I2C-bus. Therefore there are eight possible hardware adresses (0x20-0x27, the three biasing pins constitute the three last bits in the binary representation of these eight possible adresses)
    Therefore - each MCP23017 can be adressed via SDA and SCL, using it´s address.

In the Adafruit MCP23017 library, this is - as I understand it - implemented via "begin.I2C()" if you put a zero "0" in the brackets, the MCP with hardware address 0x20 will be adressed, if you put a "1" in there, the second MCP (address 0x21) will be adressed and so on..

Thing is - I couldn´t really find a good explanation of all the possibilites of the Adafruit library anywhere. There are things I don´t understand - for example: I can´t see the actual line "begin.I2C()" being used anywhere in my code (which is built on an example from Github) other than to trigger a serial output:

"if (!mcp.begin_I2C()) {Serial.println("Error.");
while (1);" (line 154 in the code)

It works anyways though :man_shrugging:
What´s whith the "while (1)" (line 156) here :thinking:? That doesn´t even make sense to me but I left it in there because I thought it plays a role.

  • if I want to address the other two digits on my clock/timer display (mm:ss format) I definitely NEED to address them using begin.I2C(), not sure WHERE in the code I need to put that though when it´s not even present atm as far as I can see :thinking:

So - right now I´m at a loss on how to structure the code to use the Adafruit library correctly and control all four MCP23017s.

  • Later on - I´ll be dealing with threading in the necessary prompts from the IR receiver to prompt all those functionalities. Not even there yet...

ANY HELP is greatly appreciated!

CODE

#include <Adafruit_MCP23X17.h>

#define LED_PIN_A 0
#define LED_PIN_B 1   // MCP23XXX pin LED is attached to
#define LED_PIN_C 2
#define LED_PIN_D 3
#define LED_PIN_E 4
#define LED_PIN_F 5
#define LED_PIN_G 6
#define LED_PIN_DP 7
#define LED_PIN_A2 8
#define LED_PIN_B2 9   // MCP23XXX pin LED is attached to
#define LED_PIN_C2 10
#define LED_PIN_D2 11
#define LED_PIN_E2 12
#define LED_PIN_F2 13
#define LED_PIN_G2 14
#define LED_PIN_DP2 15

// uncomment appropriate line
// Adafruit_MCP23X08 mcp;
Adafruit_MCP23X17 mcp;

void clear_digit() {
  mcp.digitalWrite(LED_PIN_A, LOW);
  mcp.digitalWrite(LED_PIN_B, LOW);
  mcp.digitalWrite(LED_PIN_C, LOW);  
  mcp.digitalWrite(LED_PIN_D, LOW);
  mcp.digitalWrite(LED_PIN_E, LOW);
  mcp.digitalWrite(LED_PIN_F, LOW);  
  mcp.digitalWrite(LED_PIN_G, LOW);  
  mcp.digitalWrite(LED_PIN_DP, LOW);
  mcp.digitalWrite(LED_PIN_A2, LOW);
  mcp.digitalWrite(LED_PIN_B2, LOW);
  mcp.digitalWrite(LED_PIN_C2, LOW);  
  mcp.digitalWrite(LED_PIN_D2, LOW);
  mcp.digitalWrite(LED_PIN_E2, LOW);
  mcp.digitalWrite(LED_PIN_F2, LOW);  
  mcp.digitalWrite(LED_PIN_G2, LOW);  
  mcp.digitalWrite(LED_PIN_DP2, LOW);
}

void display_number(int number){
  int left_digit;
  int right_digit;
  left_digit=number/10;
  right_digit=number%10;
  display_digit(left_digit,1);
  display_digit(right_digit,0);
}
void display_digit(int digit,int left){
  int offset;
  if(left == 1)
  {offset = 0;}
  else{offset = 8;}
  if(digit == 0) {
    mcp.digitalWrite(LED_PIN_A+offset, HIGH);
    mcp.digitalWrite(LED_PIN_B+offset, HIGH);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, HIGH);
    mcp.digitalWrite(LED_PIN_E+offset, HIGH);
    mcp.digitalWrite(LED_PIN_F+offset, HIGH);
    mcp.digitalWrite(LED_PIN_G+offset, LOW);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
  } else if (digit == 1) {
    mcp.digitalWrite(LED_PIN_A+offset, LOW);
    mcp.digitalWrite(LED_PIN_B+offset, HIGH);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, LOW);
    mcp.digitalWrite(LED_PIN_E+offset, LOW);
    mcp.digitalWrite(LED_PIN_F+offset, LOW);
    mcp.digitalWrite(LED_PIN_G+offset, LOW);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
  } else if (digit == 2) {
    mcp.digitalWrite(LED_PIN_A+offset, HIGH);
    mcp.digitalWrite(LED_PIN_B+offset, HIGH);
    mcp.digitalWrite(LED_PIN_C+offset, LOW);
    mcp.digitalWrite(LED_PIN_D+offset, HIGH);
    mcp.digitalWrite(LED_PIN_E+offset, HIGH);
    mcp.digitalWrite(LED_PIN_F+offset, LOW);
    mcp.digitalWrite(LED_PIN_G+offset, HIGH);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
  } else if (digit == 3) {
    mcp.digitalWrite(LED_PIN_A+offset, HIGH);
    mcp.digitalWrite(LED_PIN_B+offset, HIGH);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, HIGH);
    mcp.digitalWrite(LED_PIN_E+offset, LOW);
    mcp.digitalWrite(LED_PIN_F+offset, LOW);
    mcp.digitalWrite(LED_PIN_G+offset, HIGH);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
  } else if (digit == 4) {
    mcp.digitalWrite(LED_PIN_A+offset, LOW);
    mcp.digitalWrite(LED_PIN_B+offset, HIGH);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, LOW);
    mcp.digitalWrite(LED_PIN_E+offset, LOW);
    mcp.digitalWrite(LED_PIN_F+offset, HIGH);
    mcp.digitalWrite(LED_PIN_G+offset, HIGH);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
  } else if (digit == 5) {
    mcp.digitalWrite(LED_PIN_A+offset, HIGH);
    mcp.digitalWrite(LED_PIN_B+offset, LOW);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, HIGH);
    mcp.digitalWrite(LED_PIN_E+offset, LOW);
    mcp.digitalWrite(LED_PIN_F+offset, HIGH);
    mcp.digitalWrite(LED_PIN_G+offset, HIGH);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
  } else if (digit == 6) {
    mcp.digitalWrite(LED_PIN_A+offset, HIGH);
    mcp.digitalWrite(LED_PIN_B+offset, LOW);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, HIGH);
    mcp.digitalWrite(LED_PIN_E+offset, HIGH);
    mcp.digitalWrite(LED_PIN_F+offset, HIGH);
    mcp.digitalWrite(LED_PIN_G+offset, HIGH);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
  } else if (digit == 7) {
    mcp.digitalWrite(LED_PIN_A+offset, HIGH);
    mcp.digitalWrite(LED_PIN_B+offset, HIGH);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, LOW);
    mcp.digitalWrite(LED_PIN_E+offset, LOW);
    mcp.digitalWrite(LED_PIN_F+offset, LOW);
    mcp.digitalWrite(LED_PIN_G+offset, LOW);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
  } else if (digit == 8) {
    mcp.digitalWrite(LED_PIN_A+offset, HIGH);
    mcp.digitalWrite(LED_PIN_B+offset, HIGH);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, HIGH);
    mcp.digitalWrite(LED_PIN_E+offset, HIGH);
    mcp.digitalWrite(LED_PIN_F+offset, HIGH);
    mcp.digitalWrite(LED_PIN_G+offset, HIGH);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
}   else if (digit == 9) {
    mcp.digitalWrite(LED_PIN_A+offset, HIGH);
    mcp.digitalWrite(LED_PIN_B+offset, HIGH);
    mcp.digitalWrite(LED_PIN_C+offset, HIGH);
    mcp.digitalWrite(LED_PIN_D+offset, HIGH);
    mcp.digitalWrite(LED_PIN_E+offset, LOW);
    mcp.digitalWrite(LED_PIN_F+offset, HIGH);
    mcp.digitalWrite(LED_PIN_G+offset, HIGH);
    mcp.digitalWrite(LED_PIN_DP+offset, LOW);
}}

void setup() {
  Serial.begin(9600);
  //while (!Serial);
  Serial.println("MCP23xxx Blink Test!");

  // uncomment appropriate mcp.begin
  if (!mcp.begin_I2C()) {
    Serial.println("Error.");
    while (1);
  }

  // configure pin for output
  mcp.pinMode(LED_PIN_A, OUTPUT);
  mcp.pinMode(LED_PIN_B, OUTPUT);
  mcp.pinMode(LED_PIN_C, OUTPUT);
  mcp.pinMode(LED_PIN_D, OUTPUT);
  mcp.pinMode(LED_PIN_E, OUTPUT);
  mcp.pinMode(LED_PIN_F, OUTPUT);
  mcp.pinMode(LED_PIN_G, OUTPUT);
  mcp.pinMode(LED_PIN_DP, OUTPUT);
  mcp.pinMode(LED_PIN_A2, OUTPUT);
  mcp.pinMode(LED_PIN_B2, OUTPUT);
  mcp.pinMode(LED_PIN_C2, OUTPUT);
  mcp.pinMode(LED_PIN_D2, OUTPUT);
  mcp.pinMode(LED_PIN_E2, OUTPUT);
  mcp.pinMode(LED_PIN_F2, OUTPUT);
  mcp.pinMode(LED_PIN_G2, OUTPUT);
  mcp.pinMode(LED_PIN_DP2, OUTPUT);

  //Serial.println("Looping...");
}

void loop() {
  int i = 0;
  for (i=90; i>=0; i--) {
    display_number(i);
    delay(1000);}
  }

It's certainly being used. The return value is being checked to see whether the call was successful. Also a begin method should only be called once under 99.999999% of circumstances.

How much time do you have to complete this? Frankly, reading your questions, it seems way over your head.

Also, I won't hold back. Your Fritzing is one of the worst I've ever seen (even though Fritzing makes bad easy). It looks like some creature from a black lagoon.

How are you going to make gym-sized digits? I assume that 3/4" high digits aren't going to be visible from the bleachers...

It is? Could you point out the code line in charge of that for me? Because I´m certainly not seeing it.
It can only be

if (!mcp.begin_I2C()) {
    Serial.println("Error.");
    while (1);

I have to do an intermediate presentation on February 19th, showing what we got so far. Gotta have the finished product in May 2022.

It IS way over my head programming-wise, which is why I´m here aarg :wink:
I´m more of a hardware guy.

Yeah - the Fritzing is pretty awful I admit. I really never found out how to route the wires so they don´t block the view. Haven´t used Fritzing often. It´s just so people understand better reading the thread.

We already designed the PCB for the separate segments. As I wrote in the beginning, there are going to be 14 LEDs with 1cm diameter, drawing about 20mA on each single Segment. Also a constant current circuit using two BC547B bipolar transistors and two resistors.
The output from the MCP23017s goes into the bases of these transistors, switching the load circuit on and off.

But what exactly are you hoping for? You can't complete an entire semester of programming in one forum thread.

Although... due in May, you could conceivably learn it by then. Just not here.

I´m hoping for people to give me ideas on how to code that and to help me if they feel like it.
It´s half a year aarg. It´s not like I can´t code at all. That program works and I did it.

What's wrong with the code you wrote? Just extend it if it works. The problem with asking for "ideas" is that the project is fairly complex, so to provide sufficiently detailed help would require a lot of work. Nobody considers that "help", it's more like working for free.

I´m obviously stuck. Hence again why I´m here.

Atm it´s changing the pin designation that kinda overthrew my offset idea and now I´m a little lost.

That´s ok man - if you don´t feel like helping just let this thread sit and we´ll see.

Stuck on what? You see, you are saying things that nobody can really work with... you said the program works... yes? no?

You can get specific answers, but only for specific questions. Not generalities.

Yeah - thanks aarg.

What about the pin designation? From what I read back there, it looked like you were experiencing a mere inconvenience, not a lack of understanding of how to do it...

I do have one general suggestion to leave with you - try to encapsulate your actions in functions, so you don't have to write repetitive code. Example, a function to display one numeral on one digit. Then you can fully separate the timer code. Like:

writeDigit(2, 0); //display a 0 in digit 2 of the display

Also spend more time with the Adafruit library to learn how to create the additional MCP objects.

Please provide a real schematic to supplant the mainly useless Fritzing diagram. Don't sweat the method. Pencil and paper will do. Clearly label all the connections.

If the 16 output pins of the MCP23017 aren´t connected in a logical order - so that segment A of digit1 is always offset a value of 8 from segment A of digit 2, the same with segment B of digit 1 and segment B of digit 2. If that´s not the case - the offset concept I used in the code doesn´t work. And I´m trying to think of an alternative but I can´t. That´s why I´m looking for input.

The Adafruit library is very poorly documented - that´s the problem. It´s not about spending time with it - I did that. It´s all in the text man.

Yeah - that´s good thanks.
The question I´m asking is more - how do I then integrate that in my code to show a timer across four 7-segment-digits.

I´ll try to make a schematic ASAP.

An array of pin numbers.

can you elaborate on that?

Think about it.

Ok - I will. Thanks.

Hint - you can arrange the elements of an array in any order you wish.

Hmmmm :thinking:
allright - thanks
Off to read about arrays.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.