Electronic – How to drive a single 7 segment display with I2C

7segmentdisplayi2c

I am looking for a solution to drive a single 7 segment display with I2C. I found some websites that shows 2 or 4 digits with I2C but I didn't find a solution for driving a single digit. I'm making a big screen with 5×4 7 segment displays where I'd like to address each digit with I2C.
Any suggestions?

Best Answer

You probably just need a chip like the TCA9554.

  • Connect VCC and GND to digital power and ground, respectively.
  • Connect A0, A1, and A2 to VCC or GND to set the lower 3 bits of the I2C address.
  • Connect the P0-P7 pins to the display segments through current-limiting resistors.
  • Connect SCL and SDA to your I2C bus.
  • If the display is cannon cathode, connect the common cathode pin to GND. Writing a 1 to the corresponding PIO bit will turn the segment on.
  • If the display is cannon anode, connect the common anode pin to VCC. Writing a o to the corresponding PIO bit will turn the segment on.
  • You can leave the INT pin disconnected or pull it up.

Just noticed that the TCA9554 has a polarity inversion register. This makes things a bit easier in that you can set all the bits to invert polarity in the common anode case, and then a 1 bit always means "turn the LED on".

So, in the code, you just do the following (omitting the address step for each transaction) to initialize:

  • I2C Write 0x01 0x00 -- Clear all outputs.
  • I2C Write 0x02 0xFF -- Invert all outputs (only do this for common anode).
  • I2C Write 0x03 0x00 -- Set the pins to outputs.

Then to set a digit, you just send the bits that light up the correct segments. For example, if P0 is hooked to segment 'a', P1 to 'b', etc. through P7 to 'h', you write 0x01 0x3F to display a '0'.


So you don't want to drive just one digit, like the title says? Well, you have a few options.

Lots of chips

You could always go with the brute force solution, and have 20 chips. This is for when you want to have all the segments driven all the time. It's also the most expensive. As noted, there's a problem where this particular chip can't have more than 8 addresses. The availability of the TCA9554A, which offers 8 more addresses, doesn't help either; you're four short.

You could deal with this by using more than one I2C bus, and having some of the chips on one bus and some on the other. That assumes you have more than one I2C bus.

You could also deal with this by using chips that have a lot more I/O pins. For example, the TCA9535, which has 16 I/O pins and thus can directly drive two digits per chip, and can have one of eight addresses, along with the TCA9539, which can drive two displays each and have one of four addresses. Between these, you can drive up to 24 digits.

One problem you will have by parking all these chips on the I2C bus is excessive loading. Since I2C uses a pull-up resistor to drive the line back high, the capacitive load of so many chips will slow down your rise time. Further, there is a lower limit on that pull-up resistor because your I2C master can only sink so much current before burning out. This is why most of these chips aren't available in more than eight addresses: putting more than eight loads on an I2C bus is questionable at best.

Which leads us to the solution that just about everyone uses when dealing with an array of LEDs...

Scanned Display

Since your users are humans, and thus use human Mk I eyeballs, there is an interesting effect you can take advantage of called "persistence of vision." In a nutshell, this means that so long as a light source lights up at least once every 4-8 ms or so, it is perceived as a steady, non-flickering source. What this means for us is that we can throw the one-output-pin-per-LED requirement out the window. So long as we light LEDs up at least once per couple milliseconds, we'll be fine.

For a 20-digit display like this, arranged as a 5x4 display, I'd wire it up to turn on only one row of five digits at a time, and switch to the next row every 2.0 ms or so. This means there's a total of 40 segments to drive at a time, with four pins needed to drive the rows themselves. Total of 44 I/O pins, or three TCA9535s.

So now what you do is wire the displays up as an array. Do this as follows:

  • Connect matching segments together in each row, then connect that through a current-limiting resistor (probably in the hundred-ohm range) to a different PIO pin. For example:
    • Connect the 'a' segment pins of the first digit of each row together, then through a 100 ohm resistor to P00 on the first TCA9535.
    • Do the same for the 'b' segments of the first digit of each row, ending at P01.
    • The 'h' segments for the first digit should hook up to P07, the 'a' segments of the second digit connect to P10, etc. Segment 'h' of the fifth display on each row should connect to P07 on the last TCA9535.
  • For each row, wire their common pins (anode or cathode) together, then connect them to the drain pin of an enhancement-mode logic-level (i.e. |Vgth| < 3 V) MOSFET.
    • If your displays are common-anode, use P-channel MOSFETs and connect their source pins to VCC.
    • If your displays are common-cathode, use N-channel MOSFETs and connect their source pins to GND.
    • Make sure the MOSFET's rDS(on) is much, much lower than the current-limit resistor values; I'd look for MOSFETs with less than an ohm.
    • Wire each MOSFET's gate pin to a different PIO pin. This way, you turn on a row by driving the gate pin to whatever voltage the source pin is not connected to. Continuing the above example, the first row's gate would connect to P10 on the last TCA9535, the next to P11, then P12, and the last row is on P13. P14-17 are unused; you can hook them up to other things or just leave them disconnected.

Now on the software side, you need some kind of function that gets called every 2 ms. For microcontrollers, the usual thing is to set up some internal timer peripheral to interrupt at this interval, then have the ISR set a flag (or give a semaphore or whatever) to kick off a display update (because that much display updating isn't going to fit in an ISR by itself). The code that is triggered by that flag generates the 48 bit pattern needed and writes it out over the I2C bus to the TCA9535s.

The bit pattern is as follows:

  • TCA9535 #1 low byte: first digit in the row.
  • TCA9535 #1 high byte: second digit in the row.
  • TCA9535 #2 low byte: third digit in the row.
  • TCA9535 #2 high byte: fourth digit in the row.
  • TCA9535 #3 low byte: fifth digit in the row.
  • TCA9535 #3 high byte: row selects.

Each byte is generated the same way as above for a single-digit case. The order of operations should be:

  1. Write to TCA9535 #3 to turn off all the rows; this keeps the display from "leaking" digits onto the wrong rows as you update the other TCA9535s.
  2. Write the correct patterns into TCA9535 #1 and #2 to set the first four digits.
  3. Finally, write the pattern into TCA9535 #3 to set the fifth digit and turn the row on.

And that's it. The I2C transactions to do all this need to finish in that 0.1 ms we calculated above, to keep the off-time of the displays to a minimum. That usually means your I2C bus needs to be running pretty fast. Like many I2C devices, the TCA9535 can run up to 400 kHz; you should try to run at this speed or as fast as possible, since you need 32 clocks per chip to set its pins, and thus about 150 clocks (.375 ms) to update the whole display. If the time to update the display is too much of the total row time, then you need to scan the display slower (i.e. more ms per row). Your limit there is how slow you can go before your users see the display flicker.

Side note

This is one place where an SPI port performs better, because:

  • Clock rate can usually be much higher than 1 MHz.
  • An output-only PIO port on an SPI bus is just a plain old shift register chip.
  • No addressing (and thus no address limits)
  • No fanout limits.
  • Much, much simpler software.