SN74HC165 shift register revisited

Status
Not open for further replies.

Gerrit

Well-known member
The SN74HC165 shift register makes it easy to add digital input to a Teensy/Arduino. On the Arduino playground a description can be found: SN74HC165N, a Sparkfun tutorial relies on the same approach.
It always seemed strange to me that shifting in would require 4 wires, whereas shifting out can make do with three. Turns out that you only need three wires, the clock inhibit is superfluous. The tutorial mentioned refer to the timing diagram:

sequence.png

The way I interpret the datasheet this diagram refers to the case where there's a continuously running clock, not when you have full control over the clock. Also in the datasheet is an example of a typical application with a microcontroller:

typicalApplication.png

In this example the clock inhibit (pin 15) is connected to ground and just three wires are used.

The tutorials also make use of delay() statements for timing purposes.

Code:
/* Width of pulse to trigger the shift register to read and latch.
*/
#define PULSE_WIDTH_USEC   5


BYTES_VAL_T read_shift_regs()
{
    long bitVal;
    BYTES_VAL_T bytesVal = 0;

    /* Trigger a parallel Load to latch the state of the data lines,
    */
    digitalWrite(clockEnablePin, HIGH);
    digitalWrite(ploadPin, LOW);
    delayMicroseconds(PULSE_WIDTH_USEC);
    digitalWrite(ploadPin, HIGH);
    digitalWrite(clockEnablePin, LOW);

    /* Loop to read each bit value from the serial out line
     * of the SN74HC165N.
    */
    for(int i = 0; i < DATA_WIDTH; i++)
    {
        bitVal = digitalRead(dataPin);

        /* Set the corresponding bit in bytesVal.
        */
        bytesVal |= (bitVal << ((DATA_WIDTH-1) - i));

        /* Pulse the Clock (rising edge shifts the next bit).
        */
        digitalWrite(clockPin, HIGH);
        delayMicroseconds(PULSE_WIDTH_USEC);
        digitalWrite(clockPin, LOW);
    }

    return(bytesVal);
}

Using an oscilloscope I performed some tests to see whether the delay is actually required. The test setup (alle delay statements removed and clock inhibit connected to ground) is shown in the following picture:

shift-test-setup.jpg

The stripboard contains four 74HC165 shift registers for the buttons and three 74HC595 shift registers for the leds, the other buttons are on two separate boards. Alle the IC sockets have 100nF decoupling caps build in. A Teensy 3.6 is mounted on a Tall Dog extension board, it is mounted on its side for easy access to all the pins.
I'm no electrical engineer but this looks like a decent clock pulse to me. Here's a screenprint showing the clock measured at the last IC in the chain:

shift-wave-last.png

The clock frequency is just over 2MHz, pulse width is about 175ns. From the datasheet:

timing.png

Worst case is a maximum of 6MHz and a minimum pulse width of 80ns so we're well in the clear. No need for any delays in the code.

The code running in the test:
Code:
unsigned long readShiftRegisters()
{
    int bitVal;
    unsigned long bytesVal = 0;
    // Trigger a parallel Load to latch the state of the data lines
    digitalWrite(ploadPin, LOW);
    digitalWrite(ploadPin, HIGH);
    // Loop to read each bit value from the serial out line of the SN74HC165N.
    for(int i = 0; i < DATA_WIDTH; i++)
    {
        bitVal = digitalRead(dataPin);
        // Set the corresponding bit in bytesVal.
        bytesVal |= (bitVal << ((DATA_WIDTH-1) - i));
        // Pulse the Clock (rising edge shifts the next bit).
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);
    }
    return(bytesVal);
}


Kind regards,

Gerrit
 
What you are doing looks basically like a bit-banging emulation of a slow ancestor of the SPI bus. Great to see that it works!
 
What you are doing looks basically like a bit-banging emulation of a slow ancestor of the SPI bus. Great to see that it works!

I had a look at the SPI library documentation, it seems this is intended for more specialised applications. Using shift registers this way is really easy as there is no protocol overhead and all buttons can be debounced simultaneously.
While browsing the libraries I found a similar case of clock ringing mentioned in the OctoWS2811 documentation so I thought I'd try a putting a series resistor in the clock line. I started with 220Ω and and then tried 100Ω. The resistor is mounted on the cable connector at the Teensy side.

clock-100ohm-220ohm.jpg

220Ω is a bit too much, the corners are rounded off and the rise time increases, 100Ω is just fine. The little blips in front of the first pulse are the result of crosstalk from the parallel load line, adding a series resistor cleans it up a little. Here's a before-after comparison of the clock signal without any series resistors and with 100Ω series resistors in the clock an parallel load lines:

shift-wave-before-after.jpg

I'm quite satisfied with this result. A nice, clean clock signal and 32 buttons scanned in about 15µs.

Kind regards,

Gerrit
 
The finished button panel:

button-panel-board.jpg

Real old school wiring, it looks a little messy but remember that with shift registers the high frequency clock and data signals are confined to the ICs so there's no risk of RF interference. This is about as far as I'd go without designing a PCB but at least when using strip board like this it is easy to get exact dimensions for a front panel design by counting holes. Mounted on the front panel it look like this:

button-panel.jpg

The board is mounted on 8 M3 threaded standoffs on the back of the frontpanel, fortunately the measurements were correct so all the buttons are perfectly aligned :) In case you're wondering, this is part of a DAW (Digital Audio Workstation) plugin controller project I'm working on.

Kind regards,

Gerrit
 
Looks good!

Wonder if you could use the Arduino function shiftin (https://www.arduino.cc/reference/en/language/functions/advanced-io/shiftin/)
To do each byte... But since you already have it working...

Thanks.

I thought about using shiftIn() but, as you said, it's working now and there's still a lot to do. When my project is finished I'll take another look at this, it could well be that shiftIn() is faster.
In the meantime I've got to the point where all the buttons and leds are operational with the DAW software (Logic Pro X).

Kind regards,

Gerrit
 
I would like to know where you got the switches with leds in them? Part number and supplier?

Thanks.

The switches are from the C&K Digitast series. I can't make heads or tails of the pricing which varies a lot per supplier and type. I got them from RS Components and Mouser, here's a link to the black ones with the green leds

Here's the datasheet: View attachment DIGITAST.pdf

These switches have a very nice feel and precise pressure point, I selected the wide version because they're almost exactly the width of a finger. I also have some no name imitations but they're definitely not as refined as the C&K.

Kind regards,

Gerrit
 
I'm interested in how you made/cut the panel for the switches and the labelling. I often don't get past doing "a proof of concept" because of a lack finishing details and equipment. Thanks.

Peter.
 
I'm interested in how you made/cut the panel for the switches and the labelling. I often don't get past doing "a proof of concept" because of a lack finishing details and equipment. Thanks.

Peter.

The panel was made by Schaeffer using their free front panel designer software. It's not cheap but the result looks great and you can check the price at anytime in the design software, when finished the panel can be ordered from within the application.

Kind regards,

Gerrit
 
It really is a beautiful build.

But why a shift-register instead of a mux or GIPO expander? Seems like a lot of effort was expended to get this working, presumably not to save a few cents on the chips.

Also, what's with the TRS connectors? Is this thing going to be an audio interface too?

I hope you'll do a write up of the full project when complete.
 
It really is a beautiful build.

But why a shift-register instead of a mux or GIPO expander? Seems like a lot of effort was expended to get this working, presumably not to save a few cents on the chips.

Also, what's with the TRS connectors? Is this thing going to be an audio interface too?

I hope you'll do a write up of the full project when complete.

Thanks:)

Here's a picture with the display and rotary encoder panel:

Zeus-DPC-Lexicon-front.jpg

For study I once took apart a Behringer controller and the thing is full of them (and the 595) so I figured I'll do it like this too. I also like the fact that there no multiplexing of the buttons, i.e. high frequency switching all over the pcb.

Here's the rear view of the display and rotary panel:

Zeus-DPC-rear.jpg

Level shifters for the encoders and two more shift registers for the encoder buttons.

The trs jacks are for future expansion with foot pedals and other stuff, I thought I'd put them in now so I don't need to drill holes in the chassis later on. No audio functionality is planned, this is purely a (Mackie control compatible) controller.
As soon as I find the time to create a little video demonstrating the controller I'll start a thread on the subject.

Kind regards,

Gerrit
 
Shifting up...

The step & pattern sequencer I'm working on has 96 switches so stripboard is definitely not an option anymore. I created schematics and pcb boards for the control board with 32 switches and the step/pattern matrix with 64 switches. In hindsight it probably would have been smarter to invest the time to learn using pcb layout software in stead of building on stripboard but at the time it seemed like stripboard building was simpler but it isn't actually.
The matrix pcb without the switches:

matrix_pcb.jpg

The control pcb with mounted switches:

Control_pcb_buttons.jpg

To prevent any issues due to the length of the cables and the amount of shift registers driven I used 74LS244 buffers on all the outputs and 74LVC244 buffer/level shifters on the inputs.

Here's a render of the main board with the buffers and the series resistors for driving the cables:

Zeus-SPS-16_main_render.jpg

The matrix is split into two groups of 32 switches sharing clock and latch but with a separate data line, together with the control board they are processed simultaneously.

Code:
void readButtonShiftRegisters(){
  int bitVal;
  unsigned long bytesValControl = 0;
  unsigned long bytesValMatrix1 = 0;
  unsigned long bytesValMatrix2 = 0;
  // Trigger a parallel Load to latch the state of the data lines
  digitalWrite(PLOAD_PIN, LOW);
  digitalWrite(PLOAD_PIN, HIGH);
  // Loop to read each bit value from the serial out line of the SN74HC165N.
  for(int i = 31; i >=0; i--){
    bitVal = digitalReadFast(CONTROL_BUTTON_DATA_PIN);
    // Set the corresponding bit.
    bytesValControl |= bitVal << i;
    newPanelButtonStates[0] =bytesValControl;
    
    bitVal = digitalReadFast(MATRIX1_BUTTON_DATA_PIN);
    bytesValMatrix1 |= bitVal << i;
    newPanelButtonStates[1] =bytesValMatrix1;
    
    bitVal = digitalReadFast(MATRIX2_BUTTON_DATA_PIN);
    bytesValMatrix2 |= bitVal << i;
    newPanelButtonStates[2] =bytesValMatrix2;
    // Pulse the Clock (rising edge shifts the next bit).
    digitalWriteFast(CLOCK_PIN, HIGH);
    digitalWriteFast(CLOCK_PIN, LOW);
  }
}

By using digitalReadFast() and digitalWriteFast() the speed can be increased significantly. Reading the state of 96 switches takes just 5µs, setting the 96 LEDs takes also just 5µs. The clock signal doesn't like like much but it's all working perfectly. The shift in clock, just over 7MHz, at the last shift register of the matrix board:

Matrix_button_clock.png

The shift out clock, 10MHz, (74HC595) also at the shift register which is furthest away from the main board:

Matrix_led_clock.png

There're no glitches with the LEDs or the switches so I guess as long as the criteria (rise time, duration) for the clock and other signals are met the waveform does not matter that much.
 
Status
Not open for further replies.
Back
Top