Assign multiple outputs to individual inputs via shift registers

I have hit a wall trying to work out the best way to assign multiple outputs to single inputs using the shift registers in my project.

My project is a train layout controller and will be taking inputs from a human and switching outputs on the train layout - pretty simple.

ie. A button will trigger an LED to illuminate, another light to come on and a solenoid to fire. This is just an example of something I may want to do on this train layout.

I am prototyping with 74HC165's for inputs and 74HC595's for outputs. Communication is via SPI and I'm buffering each shift register with 74HC125's to test the high impedance state allowing other SPI devices on the same bus as well as buffering the clock, latch and MISO/MOSI lines to avoid fan-out issues when I scale this up. The final project will have approx 6 x input shift registers and 8 x output shift registers - ie. 6 bytes of input and 8 bytes of output.

I am struggling a bit with the data structures to represent the layout inputs / outputs and then how I assign multiple outputs to a single input.

I'll prefix the following with a disclaimer about me knowing Python and Ruby and not so much C++ !

I decided to play with C++ structs to build a data structure that represented inputs and outputs to capture things like what shift register pin number, name for printing out to screens etc.

The code I have come up with is below. I am not sure of the appropriate way to find the bits that changed (or if I even have to). I am XOR'ing individual bytes to work out if the byte as a whole has changed as I read them from the shift registers, but not sure how to get the bit position number of the bit that changed (you'll see why I think I need to do that in the comments). Maybe I just go through each button and find the outputs and build the individual bytes to send to the shift registers.

I don't want a massive switch / case statement. I want to set the attributes in the struct declarations.

I think what I want to do is as follows:

  • Read a byte from each input shift register into an array
  • Iterate through this array and compare the new byte to a previous version of the byte and work out if it changed
  • Now I know if a button has been pressed
  • Work out what bit position changed don't know how to do this - possibly just iterate through every bit 0's and 1's and find the associated outputs
  • Iterate through the button_t object for the bit identified above and find the outputs associated with it
  • Set the output bits in the appropriate byte with bitSet() / bitClear()
  • Do this for all changed bits identified
  • Write the chain of bytes to the output shift registers

I'm not sure of the most efficient bitwise tools to use to accomplish this. Knowing how I think about code there is probably an easier way to do this but the simple requirements are:

  • Read a button
  • Find that button's associated outputs (one or many)
  • Set the outputs state
  • Repeat

I am not a software engineer or an electronics engineer - I am using this project as a way to learn more about both fields.

Latest code in post #6 below

Work out what bit position changed don't know how to do this

Use an XOR

byte changedBits = oldBits ^ newBits;

For each bit that has changed, changedBits will contain a 1; non-changed bits will be 0.

Further, if you change

output_t outputs[5]; //can have max 5 outputs for each button; this holds an array of output_t objects

in your button_t struct to

output_t *outputs; // outputs
byte numOutputs; // number of outputs

You do not have the limitation of 5 outputs; due to the use of a byte variable it's now limited to 256 (and if you use an unsigned int, it will be 65536.

sterretje:
Use an XOR

byte changedBits = oldBits ^ newBits;

For each bit that has changed, changedBits will contain a 1; non-changed bits will be 0.

I am already doing the XOR comparison to see if the byte changed, how do I get the position that changed ? Do I just iterate through the byte and look for the 1's ?

I will look at your other suggestion as well - thanks.

ilium007:
I am already doing the XOR comparison to see if the byte changed, how do I get the position that changed ? Do I just iterate through the byte and look for the 1's ?

I will look at your other suggestion as well - thanks.

A bitRead to keep it simple, else a for-loop

for(int bitCnt=0;bitCnt<8;bitCnt++)
{
  if((changeBits & (1 << bitCnt)) == (1 << bitCnt))
  {
    // bit is set
  }
}

This is just sucked from the thumb and might contain errors. It masks all bits except for 1 (left hand side) and compares it against the bit that is set (right hand side).

You can also look how the bitRead() function is implemented to get ideas.

OK thanks !

sterretje:
Further, if you change

output_t outputs[5]; //can have max 5 outputs for each button; this holds an array of output_t objects

in your button_t struct to

output_t *outputs; // outputs

byte numOutputs; // number of outputs



You do not have the limitation of 5 outputs; due to the use of a byte variable it's now limited to 256 (and if you use an unsigned int, it will be 65536.

I limited to 5 so that I could iterate a known number if times in a for loop later as I can't find the length of an array in code to allow doing it dynamically.

Is this still an array of output_t structs ?

output_t *outputs; // outputs
byte numOutputs; // number of outputs

I changed to this (array of pointers):

byte num_outputs;
output_t *outputs; //this holds an array of pointers output_t objects

And assign them members like this:

button0.num_outputs = 3;
button0.outputs[0] = output0;
button0.outputs[1] = output1;
button0.outputs[2] = output2;

Where does button0.num_outputs get used ? How is the length of the array of pointers defined ?

This is what I came up with. It uses an inputs and outputs struct that holds the metedata about each input or output. Each shift register shift register input (button / switch / IR sensor) will be associated with one or more outputs. The code compares each byte from the input shift registers and only processes a byte if it is different from the previous version of that input byte (reduce time in loop) and then look at witch bit changed. From here I can look up the outputs associated with that input bit and then set the new state of just the outputs that need changing.

Code is still pretty messy but this was just a proof of concept. I need to do more work on the data structures, putting code into functions and using pointers to the structs to reduce memory footprint.

In the code below I simulate the input shift register reads with 2 x hardcoded input bytes.

Any advice welcome on how to do this better - I'm sure there are better bitwise comparison techniques I could be using rather than trying to work out bit positions in various shift registers.

I simulate the button presses with two new input bytes:

inBytes[0] = B11110000;  // 4 bits have changed
inBytes[1] = B10000000;  // 1 bit has changed

Input byte0 will change 4 output shift register bits (1 each) and input byte1 has one bit changing but will trigger two output bits to change.

Serial output:

Start
bit 4 changed in shift register0
bitState: 1
outputNum: 4
outShiftRegister: 0
outShiftBit: 4

bit 5 changed in shift register0
bitState: 1
outputNum: 5
outShiftRegister: 0
outShiftBit: 5

bit 6 changed in shift register0
bitState: 1
outputNum: 6
outShiftRegister: 0
outShiftBit: 6

bit 7 changed in shift register0
bitState: 1
outputNum: 7
outShiftRegister: 0
outShiftBit: 7

bit 7 changed in shift register1
bitState: 1
outputNum: 15
outShiftRegister: 1
outShiftBit: 7
outputNum: 2
outShiftRegister: 0
outShiftBit: 2

Output shift register 0: 11110100
Output shift register 1: 10000000

As expected - bits 2,4,5,6,7 changed in shift register0 and bit 7 changed in register1

test_io.ino

#include "myTypes.h"

const int NUM_SHIFT_IN = 2;  // number on input shift registers
const int NUM_SHIFT_OUT = 2;  // number on output shift registers

// array to hold each previous byte read in from the shift registers for comparison
byte prevInBytes[NUM_SHIFT_IN];

// array to hold each byte read in from the shift registers
byte inBytes[NUM_SHIFT_IN] = {
  B00000000,  // input shift register0
  B00000000   // input shift register1
};

// array to hold each byte to be written to the shift registers
byte outBytes[NUM_SHIFT_OUT] = {
  B00000000,  // output shift register0
  B00000000   // output shift register1
};

// array of input_t structs that holds metatdata about each input
input_t inputs[NUM_SHIFT_IN * 8] = {  // 8 bits per shift register
  {LOW,1,{0}},  // input0
  {LOW,1,{1}},  // input1
  {LOW,1,{2}},  // input2
  {LOW,1,{3}},  // input3
  {LOW,1,{4}},  // input4
  {LOW,1,{5}},  // input5
  {LOW,1,{6}},  // input6
  {LOW,1,{7}},  // input7
  
  {LOW,1,{8}},  // input8
  {LOW,1,{9}},  // input9
  {LOW,1,{10}},  // input10
  {LOW,1,{11}},  // input11
  {LOW,1,{12}},  // input12
  {LOW,2,{13,14}},  // input13
  {LOW,2,{0,1}},    // input14
  {LOW,2,{15,2}},   // input15
};

void setup() {
  delay(2000);
  Serial.begin(115200);
  Serial.println("Start");

  // copy current input bytes into prevInBytes[] to allow comparison later
  memcpy( prevInBytes, inBytes, NUM_SHIFT_IN );
  // read in inputs
  // would be an SPI transaction to read each byte in a loop
  // place byte into inBytes[]
  inBytes[0] = B11110000;  // 4 bits have changed
  inBytes[1] = B10000000;  // 1 bit has changed

  // loop over input array compare bytes and work out what has to be processed
  for (int i = 0; i < NUM_SHIFT_IN; i++) {
    byte compareByte = inBytes[i] ^ prevInBytes[i];  // 1 if a bit is different
    if (compareByte) {
      // find which bit/s changed
      for(int bitCnt=0;bitCnt<8;bitCnt++) {
        // use bit mask  of 1 shifted by the bit position; 1 & 1 = 1
        if((compareByte & (1 << bitCnt)) == (1 << bitCnt)) {
          Serial.print("bit ");
          Serial.print(bitCnt);
          Serial.print(" changed in shift register");
          Serial.println(i);
          
          // read the new bit state
          bool bitState = bitRead(inBytes[i], bitCnt);
          Serial.print("bitState: ");
          Serial.println(bitState);
          
          // need an index lookup into inputs[] array
          int inputNum; 
          if (i > 0) {
            inputNum = bitCnt + (i * 8);  // shift along 8 bits for each shift register
          } else {
            inputNum = bitCnt;
          }

          // loop over the outputs array within inputs[] array
          for (int j = 0; j < inputs[inputNum].numOutputs; j++) {
            
            byte outputNum = inputs[inputNum].outputs[j];
            Serial.print("outputNum: ");
            Serial.println(outputNum);
            
            byte outShiftRegister = (outputNum/8);
            Serial.print("outShiftRegister: ");
            Serial.println(outShiftRegister);
            
            byte outShiftBit;
            if (outShiftRegister == 0) {
              outShiftBit = outputNum;
            }  else {
              outShiftBit = outputNum - (outShiftRegister * 8);
            }
            Serial.print("outShiftBit: ");
            Serial.println(outShiftBit);

            // now we can write the output bit
            bitWrite(outBytes[outShiftRegister], outShiftBit, bitState);
          }
          Serial.println();
        }
      }
    }
  }
  
  for (int i = 0; i < NUM_SHIFT_OUT; i++) {
    Serial.print("Output shift register ");
    Serial.print(i);
    Serial.print(": ");
    prntBits(outBytes[i]);
  }
}

void prntBits(byte b)  //function to print bytes in binary form
{
  for(int i = 7; i >= 0; i--)
    Serial.print(bitRead(b,i));
    Serial.println();  
}

void loop() {
  // nothing to do here, running once in setup for testing
}

myTypes.h

typedef struct {
  bool state; // not used yet, may be used to work out short press / long press
  byte numOutputs;
  byte outputs[5];  // max 5 outputs per input
} input_t;