GPIO outputs and 74HC595 Serial Latch by example, beginners guide

With reference to this forum post
https://forum.electro-smith.com/t/how-to-use-a-gpio/545

Below is an example code segment for writing a byte of data to the 74HC595, in my example displaying byte of data on 8 leds.

There is some confusion about mapping Daisy pin names to actual STM32H7 pins
I’ll not go into the detail of this mapping. The above forum post explains it

The mappings can be found here
https://github.com/electro-smith/DaisyExamples/blob/master/seed/experimental/Seed3Test/Seed3Test.cpp

Example

But first look at the data sheet for the 74HC595
https://www.ti.com/lit/ds/symlink/sn74hc595.pdf

Here is a good description of what we have to do, it has a very nice animated image about shift registers.
https://lastminuteengineers.com/74hc595-shift-register-arduino-tutorial/

And this excellent one
https://protostack.com.au/2010/05/introduction-to-74hc595-shift-register-controlling-16-leds/

In this last link there is a good sequence type diagram of what we need to do, it’s just after the 74HC595 pin out diagram.
Note: The data byte is presented to the DS pin it is loaded into the serial buffer of the 74HC595 on the rising edge of a clock on the SH_CP pin.
Once all the data byte has been ‘clocked in’ the data is then latched to the 74HC595 output buffer/latch; this is done by pulsing the ST_CP pin. The output pins Qn then output logic high or low.

Further down this article there are two C code examples for the ATMega8.
This code has been refactored using a series of #defines. And tbh I find this difficult to read but it shows the code in relationship to the ‘sequence’ diagram.

C++ code example
I’ve kept my code a simple as I can, once you’ve got it working refactor as you see fit.
Here’s the small C++ class that uses three GPIO pins to write a bye of data to the 74HC595

First the header file leds.h

#pragma once 

#define GPIO_4    {  DSY_GPIOC, 9 }
#define GPIO_5    {  DSY_GPIOC, 8 }
#define GPIO_6    {  DSY_GPIOD, 2 }

class Leds
{
  public:
    Leds() {}
    ~Leds() {}
    void Init();
    void Write(uint8_t data);
   
  private:

    dsy_gpio_pin pins[3] = {
        GPIO_4,  GPIO_5,  GPIO_6
    };
    dsy_gpio ledPins[3] ;

    const uint8_t BITMASK = 0x1;

};

Here I’ve copied only the specific GPIO #defines required
On the Daisy Seed these map to the following pins
Looking at the Daisy Seed pin out diagram
https://github.com/electro-smith/Hardware/blob/master/resources/300ppi/DaisyPinoutRev4.png

I’m using the pin numbers nearest to the circuit board i.e. the numbers in black font
e.g. GPIO_4 == 4 black font number

In Leds.cpp there are some comments as to which Daisy pins connect to the corresponding 74HC595 pins.

Leds.cpp

#include "leds.h"

#pragma GCC push_options
#pragma GCC optimize ("O0")

/*
ledPins[0] GPIO_4 = 74HC595 SCK pin 11 (clock)
ledPins[1] GPIO_5 = 74HC595 EN pin 12 (chip enable / latch ) 
ledPins[2] GPIO_6 = 74HC595 SER pin 14 (data) 
*/

void Leds::Init(){
    //setup the pins as outputs
    for(size_t i = 0; i < 3; i++) {
        ledPins[i].pin  = pins[i];
        ledPins[i].mode = DSY_GPIO_MODE_OUTPUT_PP;
        ledPins[i].pull = DSY_GPIO_NOPULL;
        dsy_gpio_init(&ledPins[i]);
        dsy_gpio_write(&ledPins[i], 1);
    }
}

void Leds::Write(uint8_t data) {
    //enable serial buffer , disable write to output buffer
    dsy_gpio_write(&ledPins[1], 0);
    for (uint8_t i = 0; i < 8; ++i) {
	    //check the least significant bit
        //and write the data pin high or low
        if ( (data & BITMASK) == BITMASK) {
            dsy_gpio_write(&ledPins[2], 1);  
        } 
        else {
            dsy_gpio_write(&ledPins[2], 0);   
        }
        //shift data down one bit, i.e. parallel to serial 
        data >>= 1;
        //clock low   
        dsy_gpio_write(&ledPins[0], 0);
        dsy_system_delay(1);
        //clock high
        dsy_gpio_write(&ledPins[0], 1);
        dsy_system_delay(1);
    }
    // after all 8 bits,  latch data from 74HC595 serial buffer to its output with a pulse
    dsy_gpio_write(&ledPins[1], 1);
    dsy_system_delay(1);
    dsy_gpio_write(&ledPins[1], 0);
}

#pragma GCC pop_options

Note:
#pragma GCC push_options
#pragma GCC optimize (“O0”)

I had to add this due to my complier options very aggressively optimising out what it thinks is redundant code.
These pragma’s effectively stop the compiler optimising this section of code.
This code is from a basic set of tests I have so it’s all a bit hacky

Leds::Init
This sets the GPIO pins as outputs and writes their outputs to high

Leds::Write(uint8_t data)

This takes a byte of data and in effect turns the parallel byte into a serial bit stream and latches it to the 74HC595.
The comments should describe what’s happening on the 3 pins and have a look at the ‘sequence ‘ diagram again
https://protostack.com.au/2010/05/introduction-to-74hc595-shift-register-controlling-16-leds/

The dsy_system_delay(1); delays are necessary to create the actual square wave type pulses. Please experiment with the delays and removing them , see what happens.

To implement the class, In main.cpp

 #include "leds.h"

 static Leds leds;
 leds.Init();

 leds.Write( 0b00000001 );
 dsy_system_delay(500);
 leds.Write( 0b00000010);
 dsy_system_delay(500);
 leds.Write( 0b00000100 );
 dsy_system_delay(500);
 leds.Write( 0b000001000 );
 dsy_system_delay(500);
 leds.Write( 0b000010000 );
 dsy_system_delay(500);
 leds.Write( 0b000100000 );
 dsy_system_delay(500);
 leds.Write( 0b001000000 );
 dsy_system_delay(500);
 leds.Write( 0b010000000 );
 dsy_system_delay(500);/
 leds.Write( 0b100000000 );
 dsy_system_delay(500);

Or neater, the above leds_Write in a loop. I did say it’s a hack :- )

Additional Hardware wiring for the 74HC595

Look at this
https://mutable-instruments.net/modules/braids/downloads/braids_v50.pdf

From Emilie’s diagram the 74HC595 pin 10 is wired to digital 3.3v and pin 13 is grounded.
Ignore pin 9 if you’re using one 74HC595

Here’s Emilie’s code for her hardware.
https://github.com/pichenettes/eurorack/blob/master/braids/drivers/display.cc

Logic Analysers

It’s nice to be able to view the sequence of pulses / timings in real time, but logic analysers can be quite expense.
However, a couple of months ago I came across this unbelievably cheap logic analyser from Germany.
https://www.amazon.co.uk/AZDelivery-Logic-Analyzer-24MHz-download/dp/B01MUFRHQ2/ref=sr_1_4?dchild=1&keywords=logic+analyser&qid=1598692033&sr=8-4

It has accompanying software to download and run the thing. Takes about 5 minutes to read and get it working. It is simple to use.
It works perfectly ok for debugging GPIO pins, but obviously for fast processor clocking speeds you’d need an expensive logic analyser, big band width and capture speeds etc.
BTW, I’m not affiliated to the manufacture, I don’t get any monies etc., and as always “buyer beware”

One last point about accessing Daisy pins.

Pins can be accessed using GetPin();

hardware.GetPin(16);

Where hardware = static DaisySeed hardware;

Hope this helps, stay safe.

1 Like

I’m afraid all numbers are in black font.
Did you connect a led to Daisy’s physical pin 5, which corresponds to libdaisy pin name GPIO_4 which corresponds to STM port { DSY_GPIOC, 9 } ?

Without any colour surrounds that is.
“using the pin numbers nearest to the circuit board”

No I didn’t connect a led directly to the Daisy in this example, the Daisy pins control the 74HC595
You can connect a led directly from a Daisy pin, but remember to connect a series resistor of value 220 ohm to 470 ohm.
The resistor current limits and prevents the led from burning out

ledPins[0] GPIO_4 [ Daisy header pin 4 ] = 74HC595 SCK pin 11 (clock)
ledPins[1] GPIO_5 [ Daisy header pin 5 ] = 74HC595 EN pin 12 (chip enable / latch )
ledPins[2] GPIO_6 [ Daisy header pin 6 ] = 74HC595 SER pin 14 (data)