Driving 4 digit 7 segment displays from arduino with only 3 pins

Just changing this thread as I haven't found an alternative to the original question...

Basically I have the need for a small display that can be attached to the PCB's for basic operations such as count down timer, clock or to check/set a variable using a simple menu.

As always, I want the thing to perform as quickly as possible and the library to have the least possible impact. For example, I can either run it on a UNO or an ATTINY.

A small 4 digit 7 segment display seems to fit the bill nicely. It is cheap, small enough to just about fit in a PCB and simple enough to control that memory footprint is not a major issue.

A number of solutions are available, none of which I think its ideal, so I built my own implementation using a pair of shift registers. Refresh is taken care by polling timer 0 output Compare A interrupt, which should not affect timing or PWM functionality.

The display uses only 3 pins. Its a bit-banged SPI implementation, so should work fine on devices without the SPI/I2C port such as the smaller ATTINY's.


The code:

// SW SPI PIN SETUP
//-----------------------------------------------------------------------------------------------------------
int SS1 = 2; // set slave select 1 pin
int CLK = 3; // set clock pin
int MOUT = 4; // set master out, slave in pin
int i;
//-----------------------------------------------------------------------------------------------------------

// SW SPI TEMP VARIABLES
//-----------------------------------------------------------------------------------------------------------
byte control = 0;              // Used to define the digit to print 
byte data_byte = 0;            // This is the actual data printed on each digit
byte work = 0;                 // Working byte, used to bit shift data out

// DEFINE OUTPUT POLARITY (Change for common Anode/Cathode displays)
//-----------------------------------------------------------------------------------------------------------
#define DIGIT_ON  HIGH
#define DIGIT_OFF  LOW
#define SEGMENT_ON  HIGH
#define SEGMENT_OFF LOW

void setup() {      

  // Set up Pins used for SPI Data transmission as digital outputs  
  pinMode(SS1, OUTPUT); // set CS pin to output
  pinMode(CLK, OUTPUT); // set SCK pin to output
  pinMode(MOUT, OUTPUT); // set MOSI pin to output
  digitalWrite(SS1, HIGH); // hold slave select 1 pin high, so that chip is not selected to begin with
  TIMSK0 |= _BV(OCIE0A);                           // Timer0 compare output interrupt enable (A)
}

void loop() 
{
  Serial.println((millis()/1000));
  //displayNumber(millis()/1000);  
}


void displayNumber(int toDisplay) 
{
  long beginTime = millis();
  for(int digit = 4 ; digit > 0 ; digit--) 
  {
  //Turn on a digit for a short amount of time
  switch(digit) 
    {
    case 1:
      control &= ~_BV(1); // CHIP SELECT - DSPIC DATA, SPI
      break;
    case 2:
      control &= ~_BV(2);
      break;
    case 3:
      control &= ~_BV(3);
      break;
    case 4:
      control &= ~_BV(4);
    break;
    }
  //Turn on the right segments for this digit
  lightNumber(toDisplay % 10);
  toDisplay /= 10;
  //Turn off all segments
  lightNumber(10); 
  control = 0b00011110;
  }
}

//Given a number, turns on those segments
//If number == 10, then turn off number
void lightNumber(int numberToDisplay) 
{
  switch (numberToDisplay){
  case 0:
    data_byte = 0b11111100;
    break;
  case 1:
    data_byte = 0b01100000;   
    break;
  case 2:
    data_byte = 0b11011010;    
    break;
  case 3:
    data_byte = 0b11110010;
    break;
  case 4:
    data_byte = 0b01100110;
    break;
  case 5:
    data_byte = 0b10110110;
    break;
  case 6:
    data_byte = 0b10111110;
    break;
  case 7:
    data_byte = 0b11100000;  
    break;
  case 8:
    data_byte = 0b11111110;
    break;
  case 9:
    data_byte = 0b11110110;
    break;
  case 10:
    data_byte = 0;
    break;
  }
  // Finally shift the data out to the display
  spi_out(SS1, data_byte, control); // send out data to chip 1, pot 0 
  //delay(2);
}

void spi_transfer(byte working) 
{
  ; // function to actually bit shift the data byte out
  for(int i = 1; i <= 8; i++) 
    { 
    // setup a loop of 8 iterations, one for each bit
    if (working > 127) { // test the most significant bit
    digitalWrite (MOUT,HIGH); // if it is a 1 (ie. B1XXXXXXX), set the master out pin high
    }
  else 
    {
    digitalWrite (MOUT, LOW); // if it is not 1 (ie. B0XXXXXXX), set the master out pin low
    }
  digitalWrite (CLK,HIGH); // set clock high, the pot IC will read the bit into its register
  working = working << 1;
  digitalWrite(CLK,LOW); // set clock low, the pot IC will stop reading and prepare for the next iteration (next significant bit
  }
}

void spi_out(int SS, byte cmd_byte, byte data_byte) 
{ 
  // SPI tranfer out function begins here
  digitalWrite (SS, LOW); // set slave select low for a certain chip, defined in the argument in the main loop. selects the chip
  work = cmd_byte; // let the work byte equal the cmd_byte, defined in the argument in the main loop
  spi_transfer(work); // transfer the work byte, which is equal to the cmd_byte, out using spi
  work = data_byte; // let the work byte equal the data for the pot
  spi_transfer(work); // transfer the work byte, which is equal to the data for the pot
  digitalWrite(SS, HIGH); // set slave select high for a certain chip, defined in the argument in the main loop. deselcts the chip
}

ISR(TIMER0_COMPA_vect)          // timer compare interrupt service routine
{
  
  i++;
  if (i>5)
    {
    i=0;
    displayNumber(millis()/1000);  
    }
}

Ill follow up at a later stage with schematics and a PCB

Ill follow up at a later stage with schematics and a PCB

Good, because that mess of wires is completely useless for anyone else wanting to do the same thing.

Nice work, but why not just buy a 7 segment display with an I2C 'backpack'?

you can find them for just a few dollars out there in eBay world.

BulldogLowell:
Nice work, but why not just buy a 7 segment display with an I2C 'backpack'?

you can find them for just a few dollars out there in eBay world.

Simply because I want to integrate it in my projects (finished PCB's) and the MAX7219 controller itself costs more than that module on eBay.

I also have a HD44780 in SPI mode, which is much faster than the I2C version

I hate to break the news to you, but Sparkfun already makes one based on an Atmega328.

PaulS:
Good, because that mess of wires is completely useless for anyone else wanting to do the same thing.

Absolutely. I normally change a few bits and pieces, so not worth to post anything until the schematics are ready. And here they are.

The current version was intended as a very small PCB that fits right behind the display so I omitted the drive transistors for each segment and the current limiting resistors (the software does PWM, so it is self limiting). The PCB is 32mm*17mm

Will post the original files when the PCB's arrive after the CNY.

aarg:
I hate to break the news to you, but Sparkfun already makes one based on an Atmega328.

I've seen them.
Its a pretty neat design. I was actually trying to find an Atmel that was small enough to fit on the same place as the shift registers but couldn't find one. The idea for this one was simply to be as cheap and small as possible (No idea why these displays dont come with a packed driver, it would add pennies to the final price).

Here's a similar version but for an HD44780.
I have an 8 bit version, in which the control lines are toggled directly by the Atmel, for speed, and this is the 4 bit version that only requires MOSI, SCK, CS, VCC and GND.


The PCB has a built in DC-DC converter, so the display can be operated with anything from 3 to 5.5V: Ideal for projects operated from a single lithium cell.

PS: Not sure why, the forum is not showing the full picture size. For full resolution click on image properties and copy the link to the browser

That's almost a clone of the 74HC595 based "Digital Tube" brand modules on Ebay. The main problem is, the need for continuous updating by the CPU. The PWM does not limit the peak current of the LEDs, but does barely squeeze through the acceptance gate for max current on the 595.

I just wonder, what is the point, when it is such a hassle to program correctly (in a real world scenario, with other devices in play). To drive it properly, you have to assign a timer. The practical result is that a lot of other interrupt driven devices cease to function due to latency problems.

aarg:
That's almost a clone of the 74HC595 based "Digital Tube" brand modules on Ebay. The main problem is, the need for continuous updating by the CPU. The PWM does not limit the peak current of the LEDs, but does barely squeeze through the acceptance gate for max current on the 595.

That's very interesting. I wasn't aware of the existence of those. For £1 I'm going to keep my system to embed on the PCB's and anything else just use them. I just have to change or define an option for the likely different way those are wired (and hopefully my code will be of some use for other here)

The 595 does indeed limit the peak current trough the LED's. The one that selects the digits has to sink or source the current of the other (up to) 8 segments. Not the best practice, but in all honesty it works well enough and I never had any issues with it. The brightness can be further adjusted trough the refresh rate of the display (anything from 1 to 20ms) - That is, all 4 digits are refreshed at once, the frequency of the loop is what determines how bright they are.

aarg:
I just wonder, what is the point, when it is such a hassle to program correctly (in a real world scenario, with other devices in play). To drive it properly, you have to assign a timer. The practical result is that a lot of other interrupt driven devices cease to function due to latency problems.

Timer 0 is already used by the system, what I am doing is tapping into the Compare match, which by default is triggered with the same frequency as the Overflow (used to update the millis function). I wouldn't really see a case where this would cause much hassle, in fact even the PWM output for this timer can be used normally.

I got my PCB's and tested both the LED version and the fast 8 bit SPI Interface.

Ill add a library to the second one. I'm currently using direct port manipulation, so will add arduino functions such as the SPI library to make it compatible with a range of different boards.
Here's a video showing operation: YouTube

There is also a 4 bit version, but i haven't got the time to write code to it, just yet.

This is faster than the stock library.

As to the LED version it works fine and its embedded on a product I designed but the code is a bit complex to port into a library. Direct port manipulation is required, otherwise it simply takes too much time (This works at 60Hz*Number of digits, so about 250Hz.

I can, provide a bit of code that can be added (I also modified that to work with the 74HC595 4 bit digital tube) to a sketch, using pins 2, 3 and 4 on an arduino UNO.

The PCB can be cut the same size as the display, unlike the eBay versions.

Here's something I designed using this display, to implement a menu

There is demo code for both attached below. Should be easy to try on a breadboard.
For the Seven Segment, open the file "SEVEN_SEGMENT_DEMO.ino"
For the 8 bit Shift register, open the file "_8_bit_SR_LCD_adapter.ino"

SEVEN_SEGMENT_DEMO.zip (2.3 KB)

Eagle files.zip (383 KB)

_8_bit_SR_LCD_adapter.zip (4.64 KB)

Nice, but you have no question? I think this belongs in the exhibition/gallery forum.

As to the original... There is also the MAX7219.
Designed to directly drive 7 segment displays.
Drive current is set by a single resistor and available in an SOT package...

Doc

Docedison:
As to the original... There is also the MAX7219.
Designed to directly drive 7 segment displays.
Drive current is set by a single resistor and available in an SOT package...

Doc

Sure, and the TM1637.

Well its all about adding options. Whenever method is the most suitable is up to the designer to evaluate and decide (Based on cost, size, availability, resources...)

I add a different method, in applications where refresh is a problem: Using individual 7 segment displays driven by a 74XX595 shift register avoids the hassle with refresh interrupts, as the internal latch keeps the display on, pretty much the same way a driver does. Brightness can be adjusted by PWM'ing the return to the anode/cathode of all displays (A single pin needed) and a unlimited number of displays can be stacked together.

Plus, since this method is not dependent on a refresh interrupt, HW SPI/USI can be used, rather than a software implementation

Example:


Full Size

Link for the HD44780 Version moved here

casemod:
Well its all about adding options. Whenever method is the most suitable is up to the designer to evaluate and decide (Based on cost, size, availability, resources...)

I add a different method, in applications where refresh is a problem: Using individual 7 segment displays driven by a 74XX595 shift register avoids the hassle with refresh interrupts, as the internal latch keeps the display on, pretty much the same way a driver does. Brightness can be adjusted by PWM'ing the return to the anode/cathode of all displays (A single pin needed) and a unlimited number of displays can be stacked together.

Plus, since this method is not dependent on a refresh interrupt, HW SPI/USI can be used, rather than a software implementation

Example:


Full Size

codes for arduino uno?