2 Seven Segment Displays Not Illuminating Properly (Shift Registers, SPI, Timer)

Hi!

GENERAL:
I’m currently building a circuit which should support a couple of seven segment displays and some rotary encoders. I am still testing it all out on a breadboard and focusing on the displays.
I use 2 TPIC6C595N shift registers and a 74HC595 to power two seven segment, four digit displays (I only use 3-3 digits from each). The TPICs sink the current while the 74s source it. The shift registers are daisy chained and I use SPI to drive them. The chaining order is: TPIC - second display, TPIC - first display, 74 - sources both displays, uses pins A-F.
I want to use SPI and interrupts because I want to receive data via serial from the PC which will be displayed later on. (I have done a variant a month ago with one 74, one seven segment display and it worked.)
Unfortunately I am not an engineer and just using Arduino as a hobby. I have spent many time googling and browsing the forums to get this far from simply using a shift register but now I am stuck.

ENCOUNTERED PROBLEM:
I try to use timer interrupts to get it working but I have run into problems:

  • If I put a delay inside the timer in the for loop, e.g. 100, then all digits are lit nicely. However I do not want to use delays, that’s why I try to use the TimerOne library.
  • If I do not put a delay in the code, all the digits are blank except the last one which is properly lit. If I change how many digits I use (from 6 to 4 or 7) then always the last one is lit, the others are not so it is not a wiring issue. Why is it lit? Why are the others not lit? Am I using the timer incorrectly somehow?
  • Upon changing the timer.initialize(interval) I have seen: nothing displayed, all digits all segments lit, flickering variations.
  • I have placed 0.1uF capacitors at each chip but that did not help. Honestly I am not sure if I have placed them at the correct location. (Not at the latch pins, I have read about that here :))

I attach a picture (delay commeneted out, delay active) about the wiring and the code I have written. I believe the bottom part is what matters.

Can you please help me to make it work?

*Update: uploaded sharper pictures

#include <SPI.h>
#include "TimerOne.h"

const int ssPin1 = 10;

//Contains positions
int digit[] = {
  B00000001, //first
  B00000010, //second
  B00000100,  //third
  B00001000,  //fourth
  B00010000,  //fifth
  B00100000,  //sixth
};
//Contains segments info
int number[] = {
  B00111111, //0
  B00000110, //1
  B01011011, //2
  B01001111, //3
  B01100110, //4
  B01101101, //5
  B01111101, //6
  B00000111, //7
  B01111111, //8
  B01101111, //9
  B10111111, //0.
  B10000110, //1.
  B11011011, //2.
  B11001111, //3.
  B11100110, //4.
  B11101101, //5.
  B11111101, //6.
  B10000111, //7.
  B11111111, //8.
  B11101111, //9.
  B01000000, //-
};

//Array sizes
int digits = sizeof(digit) / sizeof(int);
int numbers = sizeof(number) / sizeof(int);

//Holds the numbers to be displayed
int numToDisp[] = {
  0,1,2,3,4,5};

//How many fields are received from PC,
//values will be replaced by numToDisp, not important now
const int NUMBER_OF_FIELDS = 2;
int values[NUMBER_OF_FIELDS];
//Helps determining which COM port the arduino uses
boolean arduinoIdent = true;

/**
 * Clears the registers by filling them with zeroes.
 **/
void clearRegisters() {
  digitalWrite(ssPin1, LOW);
  SPI.transfer(B00000000);
  SPI.transfer(B00000000);
  SPI.transfer(B00000000);
  digitalWrite(ssPin1, HIGH);
}

void setup() {
  pinMode(ssPin1, OUTPUT);
  
  //SPI
  SPI.begin();
  //SPI.setClockDivider(SPI_CLOCK_DIV4);
  clearRegisters();

  //Serial
  Serial.begin(57600);

  // Timer1
  /* set a timer of length 100000 microseconds
   (or 0.1 sec - or 10Hz => the led will
   blink 5 times, 5 cycles of on-and-off, per second)*/
  Timer1.initialize(10000);
  Timer1.attachInterrupt(timerIsr); // attach the service routine here 
}

void loop() {
  if (arduinoIdent) {
    //Helps with identification only at first.
    if (Serial.available()) {
      char c = Serial.read();  
      if (c == 'T') {
        Serial.print(c);
        arduinoIdent = false;
      }
    }
  } 
  else {
    if (Serial.available()) { // only send data back if data has been sent
      for(int fieldIndex = 0; fieldIndex < NUMBER_OF_FIELDS; fieldIndex++) {
        values[fieldIndex] = Serial.parseInt();    
      }
      /*Serial.print(values[0]);
       Serial.print(", ");
       Serial.println(values[1]);*/
    }
  }
}

void timerIsr() {
  for (int j = 0; j < digits; j++) {
    digitalWrite(ssPin1, LOW);
    SPI.transfer(digit[j]); //Select digit.
    SPI.transfer(number[numToDisp[j]]); //Send number first display
    SPI.transfer(number[numToDisp[j]]); //Send number second display
    digitalWrite(ssPin1, HIGH); 
    delay(100);
  } 
}

I'll give you credit - you have one bypass capacitor near one of the TPICs and another for the HC595.

You are missing the cathode driver buffer transistors or FETs - the HC595s cannot safely provide enough current. Perversely, the TPICs can, but you are using them as segment - anode - drivers which do not need to provide (quite) as much current.

Now you have redundancy which you are not using in any way effectively. Your code drives one cathode at a time yet you have two separate anode (segment) drivers - you could more effectively drive one digit on the left hand display and simultaneously a different one on the right hand side. In which case, you only need three drive transistors to buffer only three outputs from the HC595. Or you could dispose of one TPIC and parallel the display anodes. There is something to be said for either approach.

And of course your code does as just you describe as you are using the interrupt routine (unnecessarily in actuality) wrongly, by coding to display all digits on every interrupt. What you wanted to do, was to display a different digit on each interrupt for which you need a global counter which steps each time the interrupt executes.

But you do not need interrupts at all. It suffices to display each digit for two (or three) milliseconds (eight digits, three milliseconds each, 24 ms, refresh 40 Hz) which could be done by a properly written loop which checks (not waits) for three millisecond "ticks" whilst reading and processing - if available - one input character at a time. Note that such a loop never waits, it may process a number of input characters on a number of traversals (and read encoders) before it determines that such time has passed as to display the next digit.


Oh yes, did I mention the photos which are too big but yet not adequately focused to see such detail as the colour bands on the resistors? 220 ohms I gather.

Thank you for your detailed answer! :) :) I know it takes a lot of time and patience to write this much, so thanks.

I have read it and I will take my time to understand the implications of it and modify things accordingly. Unfortunately I have not seen too many complete solutions for the problem that's why there are so many mistakes, but I will try my best to correct them and make it work. I only checked the photo on my phone and did not notice it, sorry. I will create a nicer one the day after tomorrow when I am home. (The resistors are 220 Ohms, and there are three capacitors but that's not well visible.)

Modify your post, click Additional Options, at the bottom, Browse to your locally stored files, and Attach the pics.

It was rather late at night (or extremely early morning) when I last wrote. It is now mid-morning and this has not helped my disturbed (for other reasons) sleep. :D

The TPIC6B595 is of course, a cathode driver and you are obviously using common anode displays. You actually need PNP buffer transistors or P-channel (logic) FETs to buffer the digit anodes; while you could use NPN transistors as emitter followers, you would be losing an extra half a volt, or more than a volt using Darlingtons. Of course using the PNP emitter followers, you then have to invert the logic sense.

All my other observations should still be appropriate.

Paul__B:
You are missing the cathode driver buffer transistors or FETs - the HC595s cannot safely provide enough current.

I have been so much focused on calculating that I forgot that I should take into consideration the 74 which is the bottleneck in this situation, you are right.

Paul__B:
Perversely, the TPICs can, but you are using them as segment - anode - drivers which do not need to provide (quite) as much current.

Is it really possible? I thought they are sinking only. I have attached the specs sheet.

Paul__B:
Now you have redundancy which you are not using in any way effectively. Your code drives one cathode at a time yet you have two separate anode (segment) drivers - you could more effectively drive one digit on the left hand display and simultaneously a different one on the right hand side. In which case, you only need three drive transistors to buffer only three outputs from the HC595. Or you could dispose of one TPIC and parallel the display anodes. There is something to be said for either approach.

I do not understand this part fully. This is because I have to get familiar with transistors. I will check some tutorials and get back to this part. But you are right I can already make use of displaying two digits at the same time - good idea.

Paul__B:
And of course your code does as just you describe as you are using the interrupt routine (unnecessarily in actuality) wrongly, by coding to display all digits on every interrupt. What you wanted to do, was to display a different digit on each interrupt for which you need a global counter which steps each time the interrupt executes.

Good idea I will try that one as well.

Paul__B:
But you do not need interrupts at all. It suffices to display each digit for two (or three) milliseconds (eight digits, three milliseconds each, 24 ms, refresh 40 Hz) which could be done by a properly written loop which checks (not waits) for three millisecond “ticks” whilst reading and processing - if available - one input character at a time. Note that such a loop never waits, it may process a number of input characters on a number of traversals (and read encoders) before it determines that such time has passed as to display the next digit.

Does it have to be this way? Is it more stable? If I can make the display things work I want to receive ~100 characters (numbers) via serial and then display them. I would like to make a change on the numbers later by using rotary encoders (not on all of them), so data will have to be able to get back as well. I just don’t know which method is better for that. I have already managed to use the millis() way once to make it work for 4 digits in one of my previous builds but that was one way only transfer.

Paul__B:
Oh yes, did I mention the photos which are too big but yet not adequately focused to see such detail as the colour bands on the resistors? 220 ohms I gather.

Newer pics uploaded using postimg.

I will get back to the forum once I have found the time to make some progress with it. I don’t know when that will happen, but I hope soon, because it is fun to work with arduino :slight_smile:

tpic6c59.pdf (180 KB)

szebenyib: I thought they are sinking only. I have attached the specs sheet.

Quite so - that is why I posted a correction when I woke up the following morning. XD

szebenyib: I do not understand this part fully. This is because I have to get familiar with transistors. I will check some tutorials and get back to this part. But you are right I can already make use of displaying two digits at the same time - good idea.

As I said, redundancy - you could either reduce the redundancy, or make more comprehensive use of it. Very much your choice, but might as well do one or the other. :D

szebenyib: Does it have to be this way? Is it more stable? If I can make the display things work I want to receive ~100 characters (numbers) via serial and then display them. I would like to make a change on the numbers later by using rotary encoders (not on all of them), so data will have to be able to get back as well. I just don't know which method is better for that. I have already managed to use the millis() way once to make it work for 4 digits in one of my previous builds but that was one way only transfer.

I think you can do either. A very common problem here is people trying to use interrupts for purposes for which they are quite inappropriate, such as reading manually operated switches or manually operated rotary encoders because these operations are ridiculously slow in terms of microprocessor speeds and contact bounce is a major problem which would cause spurious interrupts.

Your use of interrupts here is "on the line" as long as what you do in the timer interrupt is concise and you do not intend to call involved functions. "ShiftOut()" is probably OK though I would simply code it myself to be sure (it's almost trivial, but even then there are better and worse ways of doing it), while you simply must not attempt any form of "delay".

My point is however that a well-written "loop" can and will handle it just as adequately. You simply must avoid the expectation that complex operations (such as receiving your numbers via serial) are "atomic" and must be done "in one go", but factor them into individual brief steps (such as receiving one character at a time and that only if there is one available) which can be performed on separate passes through the "loop" with a "state machine" directing which step will be performed on the next pass. When each of the necessary tasks is properly organised in such a fashion, many can be performed harmoniously.

szebenyib: Newer pics uploaded using postimg.

Superb! An example to others posting here. I can read the resistors