shiftOut with Arrays

I am using multiple shift registers (in this case 15) SN74HC595s to drive relays,

#define registerCount 15
byte relayCoils[registerCount * 8];

in my setup():

digitalWrite(latchPin, LOW);
for (int i = 0; i < sizeof(relayCoils); i++) {
  relayCoils[i] = 0;
}
  
for (int i = 0; i < sizeof(relayCoils); i++) {
  shiftOut(dataPin, clockPin, MSBFIRST, relayCoils[i]);
}
digitalWrite(latchPin, HIGH);

and I have a function that manages the writing of the array out to the relays

  digitalWrite(latchPin, LOW);
  for (int i = 0; i < sizeof(relayCoils); i++) {
    shiftOut(dataPin, clockPin, MSBFIRST, relayCoils[i]);
  }
  digitalWrite(latchPin, HIGH);
}

My question is:

when I write relayCoils[suffix] = 1; and the relays() function has run, does the relay of that suffex not change state, but if (relayCoils[suffix] == 1) {interface.println("Closed")} returns closed?

Posting snippets makes no helper happy. Please post the entire code and use code tags, the </> symbol.

There are other issues with my code, but at the moment, it is only the array issue I am hunting down, the other issues will be cleaned up once I have MVP working

#include <Arduino.h>
#include <avr/wdt.h>
#include "src/ArduinoUniqueID/ArduinoUniqueID.h"
#include "src/Vrekrer_scpi_parser/Vrekrer_scpi_parser.h"

/*  
  This is the pin mapping for MimicOctopus v1.0
*/

#define VerPin0 50
#define VerPin1 51
#define VerPin2 52
#define VerPin3 53
byte cibVersion = 0;
const byte VERPINS[4] = {
  VerPin0,
  VerPin1,
  VerPin2,
  VerPin3
};


#define SDAPin 20
#define SCLPin 21

#define dataPin 23
#define latchPin 25
#define clockPin 27
#define registerCount 15  // Count of how many shift registers there are in total.

byte relayCoils[registerCount * 8];

// The Chanels 1 and 2 Shift register is the first one!

#define heartLED 13  // This is also the built in LED pin on the Mega 2560

#define V3_3_test A0  //Netname 3v3_test - Issue with non letter first charictor.
#define V12_test A1   //Netname 12v_test
#define V5_test A2    //Netname 5v_test

#define Ain3 A3
#define Ain4 A4

#define DUT_V_Mon A15
#define DUT_I_Mon1 A9
#define DUT_I_Mon2 A10

#define Eq_V_Mon1 A14
#define Eq_I_Mon1 A8
#define Eq_V_Mon2 A13
#define Eq_I_Mon2 A7
#define Eq_V_Mon3 A12
#define Eq_I_Mon3 A6
#define Eq_V_Mon4 A11
#define Eq_I_Mon4 A5

#define RAG1 36
#define RAG2 37
#define Enable 38

#define Door_Closed 6
#define InterlockLoop 5
#define Test_Start 4

#define CIO0 22
#define CIO1 12
#define CIO2 11
#define CIO3 10

#define EIO0 9
#define EIO1 8
#define EIO2 7

#define EIO6 3
#define EIO7 2


#define FIO3 39
#define FIO4 40
#define FIO5 41
#define FIO6 42
#define FIO7 43

#define MIO0 44
#define MIO1 45
#define MIO2 46

#define Debug Serial1





SCPI_Parser my_instrument;

void setup() {



  // Grab the hardware revision once at the startup.
  hardWare();

  initScpi();
  Serial.begin(9600);
  Debug.begin(9600);

  // Heartbeat
  pinMode(heartLED, OUTPUT);

  // fill the relayCoil array with zeros (off).
for (int i = 0; i < sizeof(relayCoils); i++) {
  relayCoils[i] = 0;
}

  // Shift Register setup
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);

  // Set latchpin low, this means the relays won't chatter during power up.
  digitalWrite(latchPin, LOW);

  // All relay coils set to off:
  for (int i = 0; i < sizeof(relayCoils); i++) {
    shiftOut(dataPin, clockPin, MSBFIRST, relayCoils[i]);
  }
  digitalWrite(latchPin, HIGH);  //return the latch pin high to signal chip that it is over


  // CIB Version detection
  for (int i = 0; i < 4; i++) {
    pinMode(VERPINS[i], INPUT);
  }

  pinMode(V3_3_test, INPUT);
  pinMode(V5_test, INPUT);
  pinMode(V12_test, INPUT);

  pinMode(DUT_V_Mon, INPUT);
  pinMode(DUT_I_Mon1, INPUT);
  pinMode(DUT_I_Mon2, INPUT);

  pinMode(Eq_V_Mon1, INPUT);
  pinMode(Eq_I_Mon1, INPUT);
  pinMode(Eq_V_Mon2, INPUT);
  pinMode(Eq_I_Mon2, INPUT);
  pinMode(Eq_V_Mon3, INPUT);
  pinMode(Eq_I_Mon3, INPUT);
  pinMode(Eq_V_Mon4, INPUT);
  pinMode(Eq_I_Mon4, INPUT);
}

void loop() {
  heartBeat();
  relays();
  my_instrument.ProcessInput(Serial, "\n");
}

/*  
  Blinks the built in LED at 2 Hz to confirm that the processor is still alive.
*/
byte heartbeat = LOW;
unsigned long previousBeat = 0;

void heartBeat() {
  if (millis() - previousBeat >= 500) {
    // save the last time you blinked the LED
    previousBeat = millis();

    // if the LED is off turn it on and vice-versa:
    if (heartbeat == LOW) {
      heartbeat = HIGH;
    } else {
      heartbeat = LOW;
    }
    digitalWrite(heartLED, heartbeat);
  }
}

/* 
  Hardware Revision
*/

void hardWare() {
  for (int i = 0; i < 4; i++) {
    cibVersion |= (digitalRead(VERPINS[i]) << i);
  }
}

/*
  Smoothing

  Reads repeatedly from an analog input, calculating a running average and
  printing it to the computer. Keeps 10 readings in an array and continually
  averages them.
*/

const int NUMREADINGS = 10;


float readValue(float inputPin, int normalised) {
  int readings;       // the readings from the analog input
  int readIndex = 0;  // the index of the current reading
  int total = 0;      // the running total
  float average = 0;  // the average

  for (int thisReading = 0; thisReading < NUMREADINGS; thisReading++) {
    // read from the sensor:
    readings = analogRead(inputPin);
    // add the reading to the total:
    total = total + readings;
    // advance to the next position in the array:
    readIndex = readIndex + 1;
  }

  // calculate the average of the readings:
  average = total / NUMREADINGS;

  // map the average onto the range 0 - normalised
  return average / normalised;
}

/*
 scpi commands
*/
const String COPYRIGHT = "ChargePoint 2022";
const String PRODUCT = "CIB - MimicOctopus";
const String SOFTWAREREV = "0.1";
const String DIVIDER = ";";

void initScpi() {
  // Set up scpi tree
  // Use "#" at the end of a token to accept numeric suffixes.

  my_instrument.RegisterCommand(F("*IDN?"), &Identify);
  my_instrument.RegisterCommand(F("*RST"), &SetReset);

  my_instrument.RegisterCommand(F("RELay#?"), &RelayState);
  my_instrument.RegisterCommand(F("RELay#"), &RelayStateChange);

  my_instrument.SetCommandTreeBase(F("MEASure:VOLTage"));
  my_instrument.RegisterCommand(F(":DUT#?"), &GetVoltDUT);
  my_instrument.RegisterCommand(F(":EQUIpment#?"), &GetVoltEquipment);
  my_instrument.RegisterCommand(F(":CIB#?"), &GetVoltCIB);

  my_instrument.SetCommandTreeBase(F(":MEASure:CURRent"));
  my_instrument.RegisterCommand(F(":DUT#?"), &GetCurDUT);
  my_instrument.RegisterCommand(F(":EQUIpment#?"), &GetCurEquipment);
}

// SCPI Commands
/*
 * Respond to *IDN?
 * 
 * Uses the ArduinoUniqueID Library to get the Unique ID / Manufacture Serial Number from the Atmel AVR.
 */
void Identify(SCPI_C commands, SCPI_P parameters, Stream& interface) {
  interface.print(COPYRIGHT + DIVIDER + PRODUCT + DIVIDER + "SW:" + SOFTWAREREV + DIVIDER + "HW:" + cibVersion + DIVIDER + "SN:");
  for (size_t i = 0; i < UniqueIDsize; i++) {
    interface.print(UniqueID[i], HEX);
  }
  interface.println(F(""));
}

/*
 * Respond to *RST
 * 
 * Delay is required to allow for entire string to be sent over serial.
 */
void SetReset(SCPI_C commands, SCPI_P parameters, Stream& interface) {
  interface.print("Resetting");
  delay(1000);
  wdt_disable();
  wdt_enable(WDTO_15MS);
  while (1) {}
}

void GetVoltEquipment(SCPI_C commands, SCPI_P parameters, Stream& interface) {
  //MEAS:VOLT<index>?
  //Queries the voltage on [index]
  //Return values are Analouge values, normalised to the expected voltage of the line being read.
  //Examples:
  // MEAS:VOLT:EQUP1?      Queries the Equipment Chanel 1

  //Get the numeric suffix/index (if any) from the commands
  String header = String(commands.Last());
  header.toUpperCase();
  int suffix = -1;
  sscanf(header.c_str(), "%*[MEAS:VOLT]%u", &suffix);
  if (suffix = 1) {
    interface.print(readValue(Eq_V_Mon1, 12));
  } else if (suffix = 2) {
    interface.print(readValue(Eq_V_Mon2, 12));
  } else if (suffix = 3) {
    interface.print(readValue(Eq_V_Mon3, 12));
  } else if (suffix = 4) {
    interface.print(readValue(Eq_V_Mon4, 12));
  }
}

void GetVoltCIB(SCPI_C commands, SCPI_P parameters, Stream& interface) {
  //MEAS:VOLT<index>?
  //Queries the voltage on [index]
  //Return values are Analouge values, normalised to the expected voltage of the line being read.
  //Examples:
  // MEAS:VOLT:CIB33?      Queries the CIB 3v3 rail

  //Get the numeric suffix/index (if any) from the commands
  String header = String(commands.Last());
  header.toUpperCase();
  int suffix = -1;
  sscanf(header.c_str(), "%*[MEAS:VOLT]%u", &suffix);
  if (suffix = 33) {
    interface.print(readValue(V3_3_test, 3.3));
  } else if (suffix = 5) {
    interface.print(readValue(V5_test, 5.0));
  } else if (suffix = 12) {
    interface.print(readValue(V12_test, 12));
  }
}

void GetVoltDUT(SCPI_C commands, SCPI_P parameters, Stream& interface) {
  //MEAS:VOLT<index>?
  //Queries the voltage on [index]
  //Return values are Analouge values, normalised to the expected voltage of the line being read.
  //Examples:
  // MEAS:VOLT:DUT1?      Queries DUT supply Voltage

  //Get the numeric suffix/index (if any) from the commands
  String header = String(commands.Last());
  header.toUpperCase();
  int suffix = -1;
  sscanf(header.c_str(), "%*[MEAS:VOLT]%u", &suffix);

  interface.print(readValue(DUT_V_Mon, 12));
}

void GetCurEquipment(SCPI_C commands, SCPI_P parameters, Stream& interface) {
  //MEAS:CURR:EQUIpment<index>?
  //Queries the current on [index]
  //Return values are Analouge values, normalised to the expected current of the line being read.
  //Examples:
  // MEAS:CURR:EQUI1?      Queries the Equipment Chanel 1

  //Get the numeric suffix/index (if any) from the commands
  String header = String(commands.Last());
  header.toUpperCase();
  int suffix = -1;
  sscanf(header.c_str(), "%*[MEAS:VOLT]%u", &suffix);
  if (suffix = 1) {
    interface.print(readValue(Eq_I_Mon1, 12));
  } else if (suffix = 2) {
    interface.print(readValue(Eq_I_Mon2, 12));
  } else if (suffix = 3) {
    interface.print(readValue(Eq_I_Mon3, 12));
  } else if (suffix = 4) {
    interface.print(readValue(Eq_I_Mon4, 12));
  }
}

void GetCurDUT(SCPI_C commands, SCPI_P parameters, Stream& interface) {
  //MEAS:CURR:DUT<index>?
  //Queries the current on DUT[index]
  //Return values are Analouge values, normalised to the expected current of the line being read.
  //Examples:
  // MEAS:CURR:EQUP1?      Queries the Equipment Chanel 1

  //Get the numeric suffix/index (if any) from the commands
  String header = String(commands.Last());
  header.toUpperCase();
  int suffix = -1;
  sscanf(header.c_str(), "%*[MEAS:VOLT]%u", &suffix);
  if (suffix = 1) {
    interface.print(readValue(DUT_I_Mon1, 12));
  } else if (suffix = 2) {
    interface.print(readValue(DUT_I_Mon2, 12));
  }
}

void RelayState(SCPI_C commands, SCPI_P parameters, Stream& interface) {

  // RELay<index>?

  String header = String(commands.Last());
  header.toUpperCase();
  int suffix = -1;
  sscanf(header.c_str(), "%*[RELay]%u", &suffix);

  if ((suffix >= 0) && (suffix < sizeof(relayCoils))) {
    if (relayCoils[suffix] == 1) {
        interface.println("Closed");
    } else {  //LOW
      interface.println("Open");
    }
  }else{
    interface.println("Error - Check relay number");
  }
}

void RelayStateChange(SCPI_C commands, SCPI_P parameters, Stream& interface) {

  //RELay<index> state

  //Get the numeric suffix/index (if any) from the commands
  String header = String(commands.Last());
  header.toUpperCase();
  int suffix = -1;
  sscanf(header.c_str(),"%*[RELay]%u", &suffix);

   //If the suffix is valid,
  //use the first parameter (if valid) to set the digital Output
  String first_parameter = String(parameters.First());
  first_parameter.toUpperCase();

  if ( (suffix >= 0) && (suffix < sizeof(relayCoils))) {
    if ((first_parameter == "HIGH") || (first_parameter == "ON") || (first_parameter == "1") ) {
      relayCoils[suffix] = 1;
    } else if ((first_parameter == "LOW") || (first_parameter == "OFF") || (first_parameter == "0") ) {
      relayCoils[suffix] = 0;
    }
  }
}

void relays() {
  digitalWrite(latchPin, LOW);
  for (int i = 0; i < sizeof(relayCoils); i++) {
    shiftOut(dataPin, clockPin, MSBFIRST, relayCoils[i]);
  }
  digitalWrite(latchPin, HIGH);
}

That's okey. What is the issue?

Filling the array with zeros and writing it out to the shift registers opens all the relays,
Filling the array with ones and writing it out to the shift registers closes relay numbers 7, 15, 23, 31 etc (starting at relay zero).

Writing a 1 to the array using the command (via serial) rel{1…20} 1 changes the response I get from rel{1…20}? from open to closed, but none of the relays change state (I only have the first 48 positions currently wired up)

Did you try writing a test sketch that only exercises the relays? Especially, one that uses only the simplest possible logic to do so? Otherwise how do you know you don't have a hardware problem?

If the hardware is working, at the moment, how do you know whether the problem is in your input string processing, or in the relay handling? You need to divide the problem so you know where to look.

Only very rarely can you solve a problem like this by simple inspection of existing code.

1 Like

That sounds logical and okey. Please post a schematic for the relay and shift register circuitry. Are they relay boards or pure relay coils? In that case, are kick back diodes used?

uploading directly to the forum is failing at the moment.

The flyback diode(s) are in the ULN2803A, which is a Darlington pair driver chip. the LEDs are also driven from the 12V relay coil line, by the ULNs and not directly from the shift registers.

@anon57585045 I have tested the relays (and attached LEDs) with a previous iteration of the code before I started working on adding the arrays to it.

Each of the bytes in your array represents one full shift register(8 bits) so your "15 * 8" array has data for 120 shift registers, not 5.

You want:
byte relayCoils[registerCount];

You are only setting the bottom bit of each register so you are controlling the top bit of each shift register eight times. You have to use the 8 bits of each byte for the 8 bits of each shift register.

for (int i = 0; i < sizeof relayCoils; i++) {
  relayCoils[i] = 0x00;  // All off.  Use 0xFF for all on.
}

I got a solution :slight_smile: shiftOut was overcomplicating the process, removing it, and performing the data pin writes myself was the solution.

Set up the pins to the shift register and set the array size:

#define dataPin 23
#define latchPin 25
#define clockPin 27
#define registerCount 4  // Count of how many shift registers there are in total.

bool relayCoils[registerCount * 8 + 1];

Setup the pins, and the array values

void setup() {
  // Shift Register setup
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);

  // fill the relayCoil array with zeros (off).
  for (int i = 0; i < sizeof(relayCoils); i++) {
    relayCoils[i] = LOW;
  }
}

And for the loop:

void loop() {
  digitalWrite(latchPin, LOW);
  for (int i = 0; i < sizeof(relayCoils); i++) {
    digitalWrite(dataPin, relayCoils[sizeof(relayCoils)-i]);
    // Toggle the clock
    digitalWrite(clockPin, HIGH);
    digitalWrite(clockPin, LOW);
  }
  digitalWrite(latchPin, HIGH);
}

Obviously, I have removed a lot of code from this solution (including the SCPI handling), but now writing a bool value to any position in the array (other than zero) will cause the associated relay to change state.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.