SevSegShift works with Uno but not Teensy 4.0?

Hello all, I'm a long-time lurker, but I can't seem to find an answer to this particular problem of mine in this treasure trove of a forum, so now I'm making my first post in hopes of getting set straight.

I've been trying to make a capacitive discharge spot welder for assembling battery packs. I wanted to challenge myself so I got working a capacitive touchwheel to vary the pulse time, and all the supporting code for a clean, fixed X-millisecond pulse.

I tested my code on my Uno, then uploaded the project over to a Teensy 4 that I've been given (to ensure pulse width accuracy with it's 600Mhz clock). All worked except the SevSegShift library, which I had hoped to use to display the selected pulse width, from 1-999 milliseconds (just because I have a 3-digit 7-segment display kicking around).

I began testing this code on both controllers in order to try and isolate problems:

/* SevSegShift Counter Example

 Copyright 2017 Dean Reading,
 Copyright 2020 Jens Breidenstein

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.


 This example demonstrates a very simple use of the SevSeg library with a 4
 digit display. It displays a counter that counts up, showing deci-seconds.
 */

#include "SevSegShift.h"

#define SHIFT_PIN_DS   13
#define SHIFT_PIN_STCP 12
#define SHIFT_PIN_SHCP 11

SevSegShift sevseg(SHIFT_PIN_DS, SHIFT_PIN_SHCP, SHIFT_PIN_STCP); //Instantiate a seven segment controller object

void setup() {
  
  byte numDigits = 3;
  byte digitPins[] = {8+2, 8+5, 8+6}; // of ShiftRegister(s) | 8+x (2nd Register)
  byte segmentPins[] = {8+3, 8+7, 4, 6, 7, 8+4, 3,}; // of Shiftregister(s) | 8+x (2nd Register)
  bool resistorsOnSegments = true; // 'false' means resistors are on digit pins
  byte hardwareConfig = COMMON_CATHODE; // See README.md for options
  bool updateWithDelays = false; // Default 'false' is Recommended
  bool leadingZeros = false; // Use 'true' if you'd like to keep the leading zeros
  bool disableDecPoint = true; // Use 'true' if your decimal point doesn't exist or isn't connected

  sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments,
  updateWithDelays, leadingZeros, disableDecPoint);
  sevseg.setBrightness(90);

}

void loop() {
  static unsigned long timer = millis();
  static int deciSeconds = 0;

  if (millis() - timer >= 100) {
    timer += 100;
    deciSeconds++; // 100 milliSeconds is equal to 1 deciSecond

    if (deciSeconds == 1000) { // Reset to 0 after counting for 1000 seconds.
      deciSeconds=0;
    }
    sevseg.setNumber(deciSeconds, 1);
  }

  sevseg.refreshDisplay(); // Must run repeatedly
}

/// END ///

My breadboarding for the Uno looks like this:

Now this works wonderfully with the Uno. When I set up the Teensy 4 with the same pin layout (with power going into the 5v pin, separated from USB) and identical code (with 25Mhz clock set in the IDE) I get signals out of all 3 pins as I would expect, but nothing meaningful out of the 74hc595 outputs.

Things I've tried:
Running the entire board and Teensy with 3.3v (power pins adjusted accordingly)
Running the entire board and Teensy with 5v (power going to Vin on Teensy)
Running the Teensy from 5v, and using the Teensy's 3.3v 250mah output pin to run the 74hc595's.
Updating Arduino IDE to 1.8.13, and reinstalling Teensyduino

I thank anyone who's reading this for their time! Please let me know if I've forgotten anything that's needed, of if I've committed a faux pas.

I had a very quick look at this library GitHub - bridystone/SevSegShift: Seven segment display controller library for Arduino

He’s used PinMode() in the constructor which could have an implementation dependent effect.

The first thing to try is adding these statements at the end of setup() in your sketch.

pinMode( SHIFT_PIN_DS, OUTPUT ) ;
pinMode( SHIFT_PIN_STCP, OUTPUT ) ;
pinMode( SHIFT_PIN_SHCP, OUTPUT ) ;

Thanks for looking! I pasted the code in verbatim, but to no avail. The Teensy 4 (I don't believe I mentioned the Teensy iteration) is still outputting what appear to be the correct signals at 3.3v on what appear to be the correct pins, but still, no counting 7 segment.

Here's the code as modified, I could very well have goofed a ctrl-V, I've done it before!

/* SevSegShift Counter Example

 Copyright 2017 Dean Reading,
 Copyright 2020 Jens Breidenstein

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.


 This example demonstrates a very simple use of the SevSeg library with a 4
 digit display. It displays a counter that counts up, showing deci-seconds.
 */

#include "SevSegShift.h"

#define SHIFT_PIN_DS   13
#define SHIFT_PIN_STCP 12
#define SHIFT_PIN_SHCP 11

SevSegShift sevseg(SHIFT_PIN_DS, SHIFT_PIN_SHCP, SHIFT_PIN_STCP); //Instantiate a seven segment controller object

void setup() {
  
  byte numDigits = 3;
  byte digitPins[] = {8+2, 8+5, 8+6}; // of ShiftRegister(s) | 8+x (2nd Register)
  byte segmentPins[] = {8+3, 8+7, 4, 6, 7, 8+4, 3,}; // of Shiftregister(s) | 8+x (2nd Register)
  bool resistorsOnSegments = true; // 'false' means resistors are on digit pins
  byte hardwareConfig = COMMON_CATHODE; // See README.md for options
  bool updateWithDelays = false; // Default 'false' is Recommended
  bool leadingZeros = false; // Use 'true' if you'd like to keep the leading zeros
  bool disableDecPoint = true; // Use 'true' if your decimal point doesn't exist or isn't connected

  sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments,
  updateWithDelays, leadingZeros, disableDecPoint);
  sevseg.setBrightness(90);

  pinMode( SHIFT_PIN_DS, OUTPUT ) ;
  pinMode( SHIFT_PIN_STCP, OUTPUT ) ;
  pinMode( SHIFT_PIN_SHCP, OUTPUT ) ;

}

void loop() {
  static unsigned long timer = millis();
  static int deciSeconds = 0;

  if (millis() - timer >= 100) {
    timer += 100;
    deciSeconds++; // 100 milliSeconds is equal to 1 deciSecond

    if (deciSeconds == 1000) { // Reset to 0 after counting for 1000 seconds.
      deciSeconds=0;
    }
    sevseg.setNumber(deciSeconds, 1);
  }

  sevseg.refreshDisplay(); // Must run repeatedly
}

/// END ///

EDIT
I also uploaded the same modified code to the Uno, and hooked it up, pin for pin. It worked as before, no changes in behavior.

You’ve done the change correctly. Oh well. It was worth a try.
The Teensy pins operate at 3.3volts so powering the shift registers at 3.3 volts is the most reliable way assuming the Teensy regulator can handle the load. If you use 74HCT595 shift registers instead, these can be powered at 5.0 volts and guaranteed to work with 3.3volt logic.
That library does not use hardware SPI to drive the shift registers, so you are free to use any pins. Maybe try changing pins as the next step.

Edit

Your Fritzing diagram shows the breadboard positive power rail connected to Vin. Are you using an external power supply but not shown it, or are you using the USB of the Uno to power everything, in which case the Uno 5V pin should be used to power the breadboard.

Taking your suggestions in stride, I've changed the pins to: DS = 4, STCP = 3, SHCP = 2. I chose those because on the Teensy 4 pinout they're close together, and seem to have the least number of functions aside from digital listed by them. Trying to keep things simple.

I applied 5 volts power from my benchtop supply to the Teensy, and then used the Teensy's 250mah 3.3v output pin to power everything beyond (74hc595s and 7 segment, it's double the 140mah max that both shift registers can take together)

The Uno looks much the same, except it's dangling off to the right side, being abused for testing purposes. It's being powered with the benchtop's 5v via Vin, and everything else is jumpered into the benchtop supply as well. When the Uno is wired in, all the Teensy's outputs are removed and it's just left to churn on it's own.

I've tried to get into the habit of uploading the same code to both controllers, and then disconnecting them from USB in my mad quest for simplicity in this troubleshooting misadventure. The only power origin in both circuits is coming from my 5v benchtop supply.

SADLY, only the Uno worked. Here are the two breadboard setups
Uno:

Teensy:

Again, thanks for your efforts. If you need more detailed close-ups of anything, or perhaps pictures of my oscilloscope screens on various pins (the waveforms are a bit jumpy, so it might not be the best image) please let me know.

The 5 volt Uno works and the 3.3 volt Teensy does not, when coupled together with a 74HC595 shift register.
You've more or less ruled out the possible logic level voltage difference by powering the shift registers at 3.3 volts.
The other likely issue is that the Teensy is simply driving the emulated (bit banged) SPI for the shift registers too quickly.
The resistors you've got on pins 11,12 and 13 may contribute to this. Try first bypassing those.
If that doesn't work, you can try hacking the SevSegShift library.
I've marked 5 new lines in the code below with // HACK

From SevSegShift/SevSegShift.cpp at ShiftRegister · bridystone/SevSegShift · GitHub

/*
  pushing the data in _shiftRegisterMap to all the Shift Registers
  */
void SevSegShift::pushData2ShiftRegister() {
  // walk through the ShiftRegisterMap and push eveything to the ShiftRegister(s)
  for (int i = 8*_numberOfShiftRegisters-1; i >= 0; i--) {
    // put the data to the data PIN of Arduino
    digitalWrite(_pinDS, _shiftRegisterMap[i]);
    // push it to the next Register (DS -> 0 | 0->1 | 1->2 | ...)
    // this is done by createing a raising flank on SH_CP (ShiftPIN)
    delayMicroseconds(1) ;   // HACK
    digitalWrite(_pinSHCP, LOW);
    delayMicroseconds(1) ;   // HACK
    digitalWrite(_pinSHCP, HIGH);
    delayMicroseconds(1) ;   // HACK
  }
  // everything in position? - YES
  // now store it in the current state
  // this is done by a raising flank on the ST_CP (Store Pin)
  delayMicroseconds(1) ;   // HACK
  digitalWrite(_pinSTCP, LOW);
  delayMicroseconds(1) ;   // HACK
  digitalWrite(_pinSTCP, HIGH);
}

Ah, I added those resistors to help cut down on the ringing I was getting at the full 600Mhz clock.
I tested the code with and without, no success.

You bring up speed, which I too had thought might be an issue, but the datasheet for my 74HC595's states that at 2v, 5Mhz is the max. Now at 600Mhz the clock is blasting out at 7.5Mhz, but clocked at 24Mhz it's only 250-ish kilohertz. So I disregarded it at the time.

Applying your code modifications brought the clock speed down to 125-ish khz, but still, no success.

The coworker who gave the the Teensy took offense at the ever-so-subtle insinuation (EVER so subtle :wink:) that he had handed me a bum part, and gave me this code to test:


  //ST_CP pin
  const int latchpin = 4;
  //SH_CP pin
  const int clockpin = 3;
  //DS pin
  const int datapin = 2;

  //Patterns for characters 0,1,2,3,4,5,6,7,8,9,A,b,c,d,E,F
  int datArray[16] = {B11111100, B01100000, B11011010, B11110010, B01100110,
                      B10110110, B10111110, B11100000, B11111110, B11110110, 
                      B11101110, B00111110, B10011100, B01111010, B10011110,
                      B10001110};
                      
void setup() {

  pinMode(latchpin, OUTPUT);
  pinMode(clockpin, OUTPUT);
  pinMode(datapin,  OUTPUT);
}

void loop() {

  //count from 0 to 15
  for (int num = 0; num < 16; num++)
  {
    digitalWrite(latchpin, LOW);

    shiftOut(datapin, clockpin, MSBFIRST, datArray[num]);

    digitalWrite(latchpin, HIGH);

    delay(1000);

  }
    
}

And while it spat out gibberish (because I was lazy and didn't mess with my carefully breadboarded connections between the registers and 7-segment) it did indeed produce cycling LED patterns.

The clock pin with this particular piece of code runs at around 325khz, which makes me think that it's something in the SevSegShift library that just doesn't play well with the Teensy.
You've given me so much of your time, so I ask if you think it'd be best for me to start working from this crude base instead of trying to modify the library?

I would give up with that SevSeg library. The most I would do is submit a bug report saying the supplied example works with a Uno but not a Teensy 4.1.

I suggest you use that new code in post #7. However, it will be a little more complicated but a good learning experience. At the moment, you have the segments and anodes distributed between the 2 shift registers. You have to put the segments onto one and the anodes on the other. That is, put the segments on the last shift register in the chain and the anodes on the first.
Note the ordering of the segments in that table, example '0' where A,B,C etc are the segments from the data sheet.

B11111100 // digit '0' A B C D E F G Dp (decimal point).

Then use this code to check you've got the table correct. It is very similar to the code you have posted:

void loop() {
  //count from 0 to 15
  for (int num = 0; num < 16; num++)
  {
    digitalWrite(latchpin, LOW);
    shiftOut(datapin, clockpin, MSBFIRST, datArray[num]);  // Segments
    shiftOut(datapin, clockpin, MSBFIRST, 0xFF );  // Anodes - all on for test 1
    digitalWrite(latchpin, HIGH);
    delay(1000);
  }
}

All 3 digits positions will show the same digit.

The next step step to handle the multiplexing, where you want to simultaneously display different digits, is a little more complicated because you have to set up the segments for the first digit position, then the anode for that digit. Push that out to the shift register chain then wait (say 3mS) to allow for persistence of vision to work, then set up the segments for digit position 2 and its anode and push that out etc. You repeat this cycle for all digits endlessly.

The shift register solution is ideal for handling completely individual 7 segment displays but not quite so easy with a multi-digit seven segment display as you have.

There are also hardware solutions. A single MAX7219 chip could replace the 2 shift registers and handle the multiplexing. You could hit the same problem though if the library for that chip fails with the Teensy 4.1.

1 Like

Yeah, I'll submit a bug report and move on.

I honestly did not expect the most difficult thing in this project to be a numerical display! But I'm doing this to learn, and get a useful tool in the process. I've heard of the MAX7219/221 chips. 10-12 dollars canadian each, so a tad pricey relatively to a shift register, but certainly takes a load off the microcontroller.

Thank you again for all your help! You've given me an excellent launching pad to carry on with!