Using a 74HC165 shift register through SPI with samd21xplained pro board

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


 

 

Hello,

 

I'm currently trying to understand how to use a 74hc165 shift register (PISO) through SERCOM SPI bus for a project.

I'm working with the SAMD21XPLAINED PRO board on Microchip Studio with ASF.

 

I'm quite used to work with SERCOM SPI bus for SIPO shift register (74hc595) but it seems to be quite different with a PISO shift register...

 

For my test I connect 8 buttons to the register and I try to control LEDs with it (Each led is connected to a different pin on the board and is supposed to be switched on by clicking on the associated button.).

The problem I have is that no mater what I can do with the 7 first buttons, the associated LEDs won't turn on.

It's only when I press the 8th button that all LEDs turn on.

 

I think the problem comes from the way I read the data that are coming from the shift register.

It looks like i don't shift between each read on my shift register but I don't manage to fix the problem...

 

My code bellow :

 

spi_config.h :

#ifndef SPI_CONFIG_H_
#define SPI_CONFIG_H_

#include <asf.h>

	/*
	 * With mux_setting is SPI_SIGNAL_MUX_SETTING_E
	 *
	 * Based on samd21 datasheet 26.6 "Data In Pinout" :
	 * DIPO 0x0 :
	 * - DI on SERCOM0 PAD[0] (PA04)
	 *
	 * Based on samd21 datasheet 26.7 "Data Out Pinout" :
	 * DOP0 0x1 :
	 * - DO on SERCOM0 PAD[2] (PA06)
	 * - SCK on SERCOM0 PAD[3] (PA07)
	 * - SS on SERCOM0 PAD[1] (PA05)
	 */
#define SERCOM_PORT			EXT1_SPI_MODULE // SERCOM0
#define MUX_SETTING			SPI_SIGNAL_MUX_SETTING_E // DIPO 0x0 / DOPO 0x1
#define MUX_PAD0			EXT1_SPI_SERCOM_PINMUX_PAD0 // MISO PA04
#define MUX_PAD1			EXT1_SPI_SERCOM_PINMUX_PAD1 // Slave Selection PA05
#define MUX_PAD2			EXT1_SPI_SERCOM_PINMUX_PAD2 // MOSI PA06
#define MUX_PAD3			EXT1_SPI_SERCOM_PINMUX_PAD3 // SCK PA07
#define SLAVE_SELECT_PIN	EXT1_PIN_SPI_SS_0 // SPI slave selection pin

struct spi_module spi_master_instance;
struct spi_slave_inst slave;

void configure_spi(void);

#endif /* SPI_CONFIG_H_ */

 

spi_config.c :

#include "spi_config.h"

void configure_spi(void){
	struct spi_config master_config;
	struct spi_slave_inst_config slave_config;

	spi_get_config_defaults(&master_config);

	master_config.mux_setting = MUX_SETTING;
	master_config.pinmux_pad0 = MUX_PAD0; // MISO PA04
	master_config.pinmux_pad1 = MUX_PAD1; // Slave Selection PA05
	master_config.pinmux_pad2 = MUX_PAD2; // MOSI PA06
	master_config.pinmux_pad3 = MUX_PAD3; // SCK PA07

	master_config.data_order = SPI_DATA_ORDER_MSB;
	master_config.mode_specific.master.baudrate = 4000000;

	spi_slave_inst_get_config_defaults(&slave_config);
	slave_config.ss_pin = SLAVE_SELECT_PIN;

	spi_init(&spi_master_instance, SERCOM_PORT, &master_config);
	spi_attach_slave(&slave, &slave_config);

	spi_enable(&spi_master_instance);
}

 

main.c :

#include <asf.h>
#include "spi_config.h"

#define NUMBER_OF_LED	8

#define LED_1		IOPORT_CREATE_PIN(1, 12)
#define LED_2		IOPORT_CREATE_PIN(1, 13)
#define LED_3		IOPORT_CREATE_PIN(1, 14)
#define LED_4		IOPORT_CREATE_PIN(1, 15)
#define LED_5		IOPORT_CREATE_PIN(1, 16)
#define LED_6		IOPORT_CREATE_PIN(1, 17)
#define LED_7		IOPORT_CREATE_PIN(1, 10)
#define LED_8		IOPORT_CREATE_PIN(1, 11)

static uint8_t ledValue[NUMBER_OF_LED];

uint8_t *LED[NUMBER_OF_LED] = {
					(uint8_t*)LED_1,
					(uint8_t*)LED_2,
					(uint8_t*)LED_3,
					(uint8_t*)LED_4,
					(uint8_t*)LED_5,
					(uint8_t*)LED_6,
					(uint8_t*)LED_7,
					(uint8_t*)LED_8,
};

void btn_scan(void);
void refresh_light(void);

int main (void)
{
	system_init();
	ioport_init();
	delay_init();

	configure_spi();

	for (uint8_t i = 0; i < NUMBER_OF_LED; i++){
		ioport_set_pin_dir((ioport_pin_t)LED[i], IOPORT_DIR_OUTPUT);
	}

	while (1) {
		btn_scan();
		refresh_light();
	}
}

void refresh_light(void){
	uint8_t *ptrLedVal = ledValue;

	for (uint8_t i = 0; i < NUMBER_OF_LED; i++){
		*(ptrLedVal + i) ? ioport_set_pin_level((ioport_pin_t)LED[i], true) : ioport_set_pin_level((ioport_pin_t)LED[i], false);
	}
}

void btn_scan(void){

	uint8_t *ptrLedVal = ledValue;

	spi_select_slave(&spi_master_instance, &slave, true);

	// Read 8 value and put each in a box of "ledValue" table
	spi_read_buffer_wait(&spi_master_instance, ptrLedVal, 8, 0b00);

	spi_select_slave(&spi_master_instance, &slave, false);
}

 

74hc165 pin configuration :

    

 

Shift register's connections :

  • 1 : PA05 (SPI SS Pin)
  • 2 : PA07 (SPI SCK Pin)
  • 3-6 : Buttons
  • 7 : Nothing
  • 8 : GND
  • 9 : PA04 (SPI MISO)
  • 10 : Nothing (No cascade)
  • 11-14 : Buttons
  • 15 : Nothing
  • 16 : 3.3V

 

(Edit) Schematic :

Last Edited: Tue. Apr 12, 2022 - 09:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why does it take 3 pages of code to read a shift register?? You should be able to do so in maybe 8-10 lines of C code, even if manually bit-banging the lines.  What is the purpose of all the complication --to increase the number of places to make a wrong turn?

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Tue. Apr 12, 2022 - 03:48 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually I need to do it through SPI (or I think so) because in the future I will have to control a lot of LEDs (more than 500) and I need a quite fast refresh.

The two firsts pages are used to initialize the SPI bus.

I only read the shift register in the main file (function "void btn_scan(void)").

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perhaps it's not a h/w issue, can you post your schematic of how the shift register is wired to your SPI pins.

Jim

Edit:

I do not see any code where you wiggle the PL (parallel load) pin on the chip before shifting out the data!

 

FF = PI > S.E.T

 

Last Edited: Tue. Apr 12, 2022 - 04:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I edited my question to add the schematic.

My SPI configuration :

MUX_PAD0; // MISO PA04
MUX_PAD1; // Slave Selection PA05 (Latch)
MUX_PAD2; // MOSI PA06
MUX_PAD3; // SCK PA07
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why are there so many descriptions for one pin?  Call one thing one thing, or maybe two, otherwise it creeps like lava.  After awhile it is hard to know what is what. Imagine what happens with 20 pins, a full database will be needed. 

 

MUX_PAD1, EXT1_SPI_SERCOM_PINMUX_PAD1, Slave Selection, PA05, LATCH, PL, pinmux_pad1SS on SERCOM0 PAD[1] 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm still waiting for the code that wiggles the latch pin

 

 

FF = PI > S.E.T

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Review the data sheet for the shift register. Examine the timing diagram and truth table to see how the Shift/Load pin works. I recall that shifting is inhibited while Load is low. Normal SPI function is to hold CS low during shift. This shift register doesn't work with normal SPI timing. The get the proper strobe on the load pin you need to select then deselect then read:

	spi_select_slave(&spi_master_instance, &slave, true);

    spi_select_slave(&spi_master_instance, &slave, false);

	// Read 8 value and put each in a box of "ledValue" table
	spi_read_buffer_wait(&spi_master_instance, ptrLedVal, 8, 0b00);

	

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Example Arduino code to read buttons on a 165

 

/*
 * SN74HC165N_shift_reg
 *
 * Program to shift in the bit values from a SN74HC165N 8-bit
 * parallel-in/serial-out shift register.
 *
 * This sketch demonstrates reading in 16 digital states from a
 * pair of daisy-chained SN74HC165N shift registers while using
 * only 4 digital pins on the Arduino.
 *
 * You can daisy-chain these chips by connecting the serial-out
 * (Q7 pin) on one shift register to the serial-in (Ds pin) of
 * the other.
 * 
 * Of course you can daisy chain as many as you like while still
 * using only 4 Arduino pins (though you would have to process
 * them 4 at a time into separate unsigned long variables).
 * 
*/

/* How many shift register chips are daisy-chained.
*/
#define NUMBER_OF_SHIFT_CHIPS   2

/* Width of data (how many ext lines).
*/
#define DATA_WIDTH   NUMBER_OF_SHIFT_CHIPS * 8

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

/* Optional delay between shift register reads.
*/
#define POLL_DELAY_MSEC   1

/* You will need to change the "int" to "long" If the
 * NUMBER_OF_SHIFT_CHIPS is higher than 2.
*/
#define BYTES_VAL_T unsigned int

int ploadPin        = 8;  // Connects to Parallel load pin the 165
int clockEnablePin  = 9;  // Connects to Clock Enable pin the 165
int dataPin         = 11; // Connects to the Q7 pin the 165
int clockPin        = 12; // Connects to the Clock pin the 165

BYTES_VAL_T pinValues;
BYTES_VAL_T oldPinValues;

/* This function is essentially a "shift-in" routine reading the
 * serial Data from the shift register chips and representing
 * the state of those pins in an unsigned integer (or long).
*/
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);
}

/* Dump the list of zones along with their current status.
*/
void display_pin_values()
{
    Serial.print("Pin States:\r\n");

    for(int i = 0; i < DATA_WIDTH; i++)
    {
        Serial.print("  Pin-");
        Serial.print(i);
        Serial.print(": ");

        if((pinValues >> i) & 1)
            Serial.print("HIGH");
        else
            Serial.print("LOW");

        Serial.print("\r\n");
    }

    Serial.print("\r\n");
}

void setup()
{
    Serial.begin(9600);

    /* Initialize our digital pins...
    */
    pinMode(ploadPin, OUTPUT);
    pinMode(clockEnablePin, OUTPUT);
    pinMode(clockPin, OUTPUT);
    pinMode(dataPin, INPUT);

    digitalWrite(clockPin, LOW);
    digitalWrite(ploadPin, HIGH);

    /* Read in and display the pin states at startup.
    */
    pinValues = read_shift_regs();
    display_pin_values();
    oldPinValues = pinValues;
}

void loop()
{
    /* Read the state of all zones.
    */
    pinValues = read_shift_regs();

    /* If there was a chage in state, display which ones changed.
    */
    if(pinValues != oldPinValues)
    {
        Serial.print("*Pin value change detected*\r\n");
        display_pin_values();
        oldPinValues = pinValues;
    }

    delay(POLL_DELAY_MSEC);
}

compare this to your code, hope it helps.

Jim

 

 

FF = PI > S.E.T

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@avrcandies :

 

Actually most of these names are imposed by ASF or by the hardware. When you configure you SERCOM bus you have yo define the associated mux configuration.

 

I only renamed once the different pin names given by ASF to make it clearer (for example I prefer MUX_PAD1 instead of  EXT1_SPI_SERCOM_PINMUX_PAD1).

 

Slave selection has to be defined for the slave configuration.

PA05 is the pin number imposed by the hardware (I don't use it in my code, it's only comment).

LATCH is the function of one of the register's pin (I also don't use it in my code).

pinmux_pad1 is a generic name defined by ASF (part of the "spi_config" struct) it has to be specified with a pin name (here MUX_PAD1).

SS is for "Slave Selection" and it's used by ASF as part of struct "spi_slave_inst_config" (slave_config.ss_pin). It has to be specified with a pin name (here SLAVE_SELECT_PIN).

 

The only optimization I could have used (except using the ASF names that I do not find meaningful) would have been to use MUX_PAD1 instead of SLAVE_SELECT_PIN but knowing that these two pins can be different in another configuration, I preferred to keep two distinct names.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@ki0bk :

 

Actually the code that wiggles the latch pin is the "spi_select_slave()" function that toggles the slave's ss_pin.

This function is part of ASF and it is presented as follows :

 

enum status_code spi_select_slave(
		struct spi_module *const module,
		struct spi_slave_inst *const slave,
		const bool select)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);
	Assert(slave);

	/* Check that the SPI module is operating in master mode */
	if (module->mode != SPI_MODE_MASTER) {
		return STATUS_ERR_UNSUPPORTED_DEV;
	}
#  ifdef FEATURE_SPI_HARDWARE_SLAVE_SELECT
	if(!(module->master_slave_select_enable))
#  endif
	{
		if (select) {
			/* Check if address recognition is enabled */
			if (slave->address_enabled) {
				/* Check if the module is ready to write the address */
				if (!spi_is_ready_to_write(module)) {
					/* Not ready, do not select slave and return */
					port_pin_set_output_level(slave->ss_pin, true);
					return STATUS_BUSY;
				}

				/* Drive Slave Select low */
				port_pin_set_output_level(slave->ss_pin, false);

				/* Write address to slave */
				spi_write(module, slave->address);

				if (!(module->receiver_enabled)) {
					/* Flush contents of shift register shifted back from slave */
					while (!spi_is_ready_to_read(module)) {
					}
					uint16_t flush = 0;
					spi_read(module, &flush);
				}
			} else {
				/* Drive Slave Select low */
				port_pin_set_output_level(slave->ss_pin, false);
			}
		} else {
			/* Drive Slave Select high */
			port_pin_set_output_level(slave->ss_pin, true);
		}
	}
	return STATUS_OK;
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@balisong42 :

 

I've tried what you said but it doesn't work. I don't read anything anymore, even when I press the last button.

I've made some other tests without SERCOM SPI bus and I manage to obtain the results I want (the problem is that I don't know if it will refresh fast enough when I will work with 500 LEDs...).

So I still try to make it work with SPI.

 

My code :

#include <asf.h>

#define NUMBER_OF_LED	8

#define SS_PIN	IOPORT_CREATE_PIN(0, 5)
#define CLK_PIN	IOPORT_CREATE_PIN(0, 7)
#define DT_PIN	IOPORT_CREATE_PIN(0, 4)

#define LED_1		IOPORT_CREATE_PIN(1, 12)
#define LED_2		IOPORT_CREATE_PIN(1, 13)
#define LED_3		IOPORT_CREATE_PIN(1, 14)
#define LED_4		IOPORT_CREATE_PIN(1, 15)
#define LED_5		IOPORT_CREATE_PIN(1, 16)
#define LED_6		IOPORT_CREATE_PIN(1, 17)
#define LED_7		IOPORT_CREATE_PIN(1, 10)
#define LED_8		IOPORT_CREATE_PIN(1, 11)

static uint8_t ledValue[NUMBER_OF_LED];

uint8_t *LED[NUMBER_OF_LED] = {
					(uint8_t*)LED_1,
					(uint8_t*)LED_2,
					(uint8_t*)LED_3,
					(uint8_t*)LED_4,
					(uint8_t*)LED_5,
					(uint8_t*)LED_6,
					(uint8_t*)LED_7,
					(uint8_t*)LED_8,
};


void btn_scan(void);
void refresh_light(void);

int main (void)
{
	system_init();
	ioport_init();
	
	ioport_set_pin_dir(SS_PIN, IOPORT_DIR_OUTPUT);
	ioport_set_pin_dir(CLK_PIN, IOPORT_DIR_OUTPUT);
	ioport_set_pin_dir(DT_PIN, IOPORT_DIR_INPUT);
	
	for (uint8_t i = 0; i < NUMBER_OF_LED; i++){
		ioport_set_pin_dir((ioport_pin_t)LED[i], IOPORT_DIR_OUTPUT);
	}
	
	while (1) {
		btn_scan();
		refresh_light();
	}
}


void refresh_light(void){
	uint8_t *ptrLedVal = ledValue;
	
	for (uint8_t i = 0; i < NUMBER_OF_LED; i++){
		!*(ptrLedVal + i) ? ioport_set_pin_level((ioport_pin_t)LED[i], true) : ioport_set_pin_level((ioport_pin_t)LED[i], false);
	}
}


void btn_scan(void){
	uint8_t *ptrLedVal = ledValue;
	
	ioport_set_pin_level(SS_PIN, HIGH);
	for (uint8_t i = 0; i < NUMBER_OF_LED; i++){
		ioport_set_pin_level(CLK_PIN, LOW);
		*(ptrLedVal + i) = ioport_get_pin_level(DT_PIN) ? 0 : 1;
		ioport_set_pin_level(CLK_PIN, HIGH);
	}
	ioport_set_pin_level(SS_PIN, LOW);
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If SH/LD is low clock inputs are ignored. To realize the shift function, SH/LD should be high.

is a problem when you use the typical SPI Slave Selection for that. Possibly you can just move the

	spi_select_slave(&spi_master_instance, &slave, false);

And you have nothing on the chip select pin 15? It needs to be low I guess.

/Lars