Arduino MIDI 7 Segment Controller

An Arduino is quite capable of driving a seven segment display directly without additional circuitry, but it does use a lot of pins.  But the advantage is that you have a much larger range of displays to choose from and you have the option to include it on your own board or shield in the future.

This project is a remake of the Arduino MIDI Button Controller using a directly connected seven segment display.

2021-01-15 18.18.13

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

These are the key Arduino tutorials for the main concepts used in this project:

If you are new to Arduino, see the Getting Started pages.

Parts list

  • Arduino Uno
  • 4 digit 7 segment display (common cathode or common anode type)
  • 2x buttons
  • 4x 1kΩ resistors
  • Breadboard and jumper wires

The Circuit

ArduinoMIDI7SegmentController_bb

Seven segment displays come in a number of varieties, but the general principle consists of a set of 7 LEDs (the “segments”) for each digit, arranged in a “figure eight”, each of which has its own pin, and then an additional pin for each digit (the “common” pins).  The wiring arrangement is best shown with a diagram, so see this one borrowed from the excellent tutorial at circuitbasics.com.

Arduino-7-Segment-Tutorial-4-Digit-Display-Schematic

An LED is illuminated when the Arduino’s IO pins cause current to flow in the right direction between one of the “digit” pins and one of the “segment” pins (A-G/DP above).  A single digit can be illuminated with the right combination of control on the A-G and DP lines. But as these are shared across all digits, the Arduino has to “scan” each digit in turn. If this done slowly, you’d see each digit illuminated in turn, one at a time, but it does it so quickly that persistence of vision effects mean that it looks to us like all four digits light up at the same time.

As this uses LEDs, current limiting resistors are required in each circuit.  The simples arrangement is to include a resistor on each “digit” line, so for a four digit display, four resistors are required.  But this only works if we don’t try to illuminate more than one segment at a time.  If we plan to do that, we need a resistor on each segment instead.  Thankfully the library we’re using (SevSeg – see below) can cope with either mode.

The A-G relate to the individual segments of the display, as shown here (once again taken from the circuitbasics tutorial).

Arduino-7-Segment-Display-Tutorial-Segment-Layout-Diagram

The problem comes when you try to map these digit and segment pins to actual pins on the displays as there are lots of different ways of doing it!  This is where some experimentation with a test sketch or coin cell can help a lot.

There are two types of display – common cathode and common anode.  This just relates to which direction the current flows to light up the LEDs – i.e. are the common “digit” pins the cathode or anode for the LEDs.  There are many variants: different sizes; with or without decimal points; with or without a clock “:”; different colours; different numbers of digits; and so on.

My display looks like the following and happens to be a common cathode type with 12 pins, including a decimal point.

2021-01-15 18.19.30

The pins are arranged as follows – but yours may well vary.  I’ve seen a number with the same arrangement of A-G and pins, but the ordering of the digit pins might be different, but take nothing for granted. If you don’t have the datasheet for your display, test it out first.

D1--A--F-D2-D3--B
| |
E--D-DP--C--G-D4

The digits are numbered in the order D1-D2-D3-D4 – i.e. from left to right, so in my case D4 is the least significant digit and D1 is the most significant digit.

This uses a lot of pins.  In the end Arduino pins 2,3,4,5 are used for the digits and 6 to 13 are used for the segments.

To get the two buttons for the MIDI controller, I use A0 and A1 in “digital pin” INPUT_PULLUP mode.  And TX is connected to one of the Arduino MIDI Interfaces.

The Code

As mentioned above, each digit has to be “scanned” to keep the display working.  Thankfully there is a simple library available to take out most of the work of driving 7 segment displays directly – the SevSeg Library by Dean Reading.  It is available from the Arduino library manager by searching for “SevSeg”.

There are some example sketches as you might expect.  I found the “SevSeg_Counter” useful for testing my display and working out the pin connections.  A-G seemed fine, but the digits were the wrong way round compared to pinouts from other tutorials.

You initialise the library with the pin numbers and some other parameters related to your display, then call the setNumber() function to put number on it.  It doesn’t update immediately however, it has to be “scanned” by calling refreshDisplay() repeatedly to display each of the digits in turn.

There is a problem with this approach though. If refreshDisplay() isn’t called quick enough you don’t get the persistence of vision effect but instead start to see the individual segments lighting in turn.  To get around this requires two things:

  • repeatedly calling refreshDisplay() every run of the main loop().
  • keeping the time between each run of loop() to a minimum.  This means you can’t ty to do too much on each run through the loop().

There is another consequence too though – you cannot use the delay() function in the loop() as that effectively stops all other processing for the duration of the delay and halts the display.

There are a number of strategies one might choose to get around a problem like this:

  • Take the refreshDisplay() out of the loop() and call it from a periodic interrupt.  We did this before when performing direct digital synthesis to get a regular sample output rate, by utilising the TimerOne library.  Unfortunately in this case that is not an option as the SevSeg library includes loops and its own waiting that makes it unreliable if called from a periodic interrupt.
  • Make the delays asynchronous to the loop() function – i.e. make them “non-blocking”.  This means providing logic to create a state machine so that the loop can keep running while delaying, but on each pass it knows if it is meant to be in a waiting state or not.  This is not too bad for a single delay, but as there are a number of delays in this code (to handle the buttons, MIDI, etc) this gets very complicated really quickly.

As the display is the only thing we don’t want to pause when performing a delay, I opted to write my own delay function that kept calling refreshDisplay() whilst hanging around.  Effectively I’ve taken the approach of creating a delay() function with a “callback routine” to stop it completely blocking and wasting time.  In principle the callback could do anything the main code needs to keep “ticking over” whilst waiting, but in my case I just need it to update the display.

This is what I ended up with.  To implement the delay it checks the millis() timer until the required number of milliseconds has passed, calling the display function after each check.

void delayWithDisplay(unsigned long mS) {
unsigned long end_mS = mS + millis();
unsigned long time_mS = millis();
while (time_mS < end_mS) {
disp.refreshDisplay();
time_mS = millis();
}
}

This was by far the simplest option – I just had to remember not to call delay() in the loop() (and any functions called from within the loop()) and instead call delayWithDisplay() and all worked fine.

In fact it means that the main logic for the button and MIDI handling it exactly the same as the Arduino MIDI Button Controller project.

Find it on GitHub here.

Advanced – the SevSeg Library

This is a quick diversion into how the SevSeg library works and how it affects the current implications for the Arduino’s IO pins.

In order to support the current requirements for use of simple low-power displays like this on an microcontroller, the library will “scan” the display – it won’t ever attempt to light up all segments on all digits at the same time.  In fact it can’t, as the segment lines are all shared anyway so at the very least it would have to use the right combinations of digit and segment lines to make sure the right parts of the displays are on at the right time.

It recognises a number of modes, but the two relevant ones here relate to the arrangement of the resistors.  It needs to be told “resistors on digits” or “resistors on segments”.  Depending on the mode, it will either pick a digit and work out which segments to light up (resistors on segments); or pick a segment and work out which digits to light up (resistors on digits).  Then on the next scan through it will move to the next digit or segment respectively.

I am using “resistors on digits” with a COMMON_CATHODE display.  This means that for a segment to be illuminated, the digit line must go low and the segment line must go high. So the code does something like the following:

Each call of "refresh":
Turn all segments OFF (for COMMON_CATHODE this means segments go LOW)
Turn all digits OFF (for COMMON_CATHODE this means digits go HIGH)
Pick the segment to work on for this scan
Turn the segment ON (for COMMON_CATHODE this means set it HIGH)
FOR EACH digit (1 to 4):
IF (segment should be lit for this digit) THEN
Turn the digit ON (for COMMON_CATHODE this means set it LOW)
ELSE
Leave the digit OFF

Each call to the refresh function will process a different segment, so it will take 8 calls of the refresh function within the persistence of vision timings to show a complete display (including the decimal point).

The maximum current for any individual IO pin is thus just the maximum current through a single LED at a time.  For the 5V supply, my 1kΩ resistor, and assuming a typical “forward voltage” for a red LED of 1.6V (I don’t know the actual value for my LEDs):

I = V / R = (5 – 1.6) / 1000 = 3.4mA

The maximum number of LEDs driven at once will be one per digit (and only ever one per pin), so the total current for the Arduino is 13.6mA maximum.  All well within the limits for an Arduino’s IO.  But only because SevSeg is doing the scanning.

The library has alternative modes that support driving LEDs via transistors for higher current requirements, but none of that is required here for these simple displays.

Closing Thoughts

You may well be wondering why bother going through all this when there are neat modules that use fewer pins and great libraries to drive them.  Well you have a point, but on the one hand it is useful to know what goes on “behind the scenes” with these libraries and modules.

“Doing it yourself” also opens up the door for more bespoke hardware setups than can be found in off-the-shelf modules.  There are so many variations of LED display available!  I have some that provide “bar displays” for example, like in an old-time graphic equaliser that look fun to play with at some point.

If we start running out of pins, there are other options too – shift registers are a common way of driving LEDs like this and there are libraries to support that too.

But for me the main advantage is the option to build all this into a MIDI controller in a nice, neat “all in one” type package.  So that will probably be what happens next.

Kevin

Leave a comment