Writing to two bits individually on the 74HC595

I need some help currently, i want to control 2 train signals (one of the signals uses an exta yellow and green signal, so it uses 5 outputs)
I got some advice and was told to use a &= operator to help clear only the appropriate bit that is being uses for that one signal. However some of the switch cases are not properly bring cleared, leading to some bits staying on. Below is the code:

// Pin assignments
const int dataPin = 2;   // Connect to DS (Pin 14) on the 74HC595
const int latchPin = 3;  // Connect to ST_CP (Pin 12) on the 74HC595
const int clockPin = 4;  // Connect to SH_CP (Pin 11) on the 74HC595

// Timing intervals
const unsigned long intervalSignal1 = 1000;  // 1000 ms for Signal 1
const unsigned long intervalSignal2 = 579;   // 579 ms for Signal 2

// State variables for timing and state tracking
unsigned long previousMillisSignal1 = 0;
unsigned long previousMillisSignal2 = 0;
int stateSignal1 = 0;
int stateSignal2 = 0;

// Global state of all outputs
byte currentState = B00000000;

void setup() {
    // Set pin modes
    pinMode(dataPin, OUTPUT);
    pinMode(latchPin, OUTPUT);
    pinMode(clockPin, OUTPUT);

    // Initialize the shift register with all outputs off
    updateShiftRegister();
}

void loop() {
    // Check the time for Signal 1
    if (millis() - previousMillisSignal1 >= intervalSignal1) {
        previousMillisSignal1 = millis();  // Reset the time
        updateSignal1();
    }

    // Check the time for Signal 2
    if (millis() - previousMillisSignal2 >= intervalSignal2) {
        previousMillisSignal2 = millis();  // Reset the time
        updateSignal2();
    }
}

void updateSignal1() {
    // Cycle through the states for Signal 1
    switch (stateSignal1) {
        case 0:
            currentState &= B00001111;  // Clear Signal 1 bits (Q0, Q1, Q2, Q4, Q6)
            currentState |= B00000100;  // Set Q2
            break;
        case 1:
            currentState &= B00001111;  // Clear Signal 1 bits
            currentState |= B01010000;  // Set Q4 + Q6
            break;
        case 2:
            currentState &= B00001111;  // Clear Signal 1 bits
            currentState |= B00010010;  // Set Q1 + Q4
            break;
        case 3:
            currentState &= B00001111;  // Clear Signal 1 bits
            currentState |= B00000011;  // Set Q0 + Q1
            break;
    }
    stateSignal1 = (stateSignal1 + 1) % 4;  // Cycle through 4 states
    updateShiftRegister();  // Update the shift register with the new state
}

void updateSignal2() {
    // Cycle through the states for Signal 2
    switch (stateSignal2) {
        case 0:
            currentState &= B00001111;  // Clear Signal 2 bits (Q3, Q5, Q7)
            currentState |= B10000000;  // Set Q7
            break;
        case 1:
            currentState &= B00001111;  // Clear Signal 2 bits
            currentState |= B00100000;  // Set Q5
            break;
        case 2:
            currentState &= B00001111;  // Clear Signal 2 bits
            currentState |= B00001000;  // Set Q3
            break;
    }
    stateSignal2 = (stateSignal2 + 1) % 3;  // Cycle through 3 states
    updateShiftRegister();  // Update the shift register with the new state
}

void updateShiftRegister() {
    // Use latch to update shift register output
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, currentState);
    digitalWrite(latchPin, HIGH);
}

(Q3, Q5, Q7 are signal 2, rest are for signal)
What changes neeed to be made so the only the appropriate bits are cleared and masked? Thank you

Try this one and that one. Without a schematic, that's about as good of a guess as I can make. For the logic to work, you need to know which pin of the 595 is connected to what and exactly how it is wired. Providing a clear schematic would make troubleshooting much easier.

1 Like

This does not make sense. You are clearing 4 bits but list 5 things in the comments.

            currentState &= B00001111;  // Clear Signal 1 bits
            currentState |= B01010000;  // Set Q4 + Q6

Same here. You clear the lower 4 bits and then set 2 that weren't one of those.

There are similar unexplained or erroneous reset/set operations in the other states.

1 Like

To be clear - he cleared upper 4 bits with this code

The Arduino language provides some special functions to make this kind of thing easy for beginners.

bitWrite(myByte, bitNumber, value);

bitSet(myByte, bitNumber);

bitClear(myByte, bitNumber);

These methods has some important drawbacks: to clear multiple bits one have to use bitClear() multiple times. If he needs to clear them simultaneously -this function doesn't fit at all.

1 Like

In some rare situations, I agree. But I don't think controlling model train signals is one of those situations. Especially since in this case the data is being shifted out to a 74hc595. All bit changes will appear at the outputs simultaneously, on the falling (rising?) edge of the latch signal. :smile:

1 Like

Hi @train_fanatic ,

To make this as generic as possible for all your future needs in clearing or setting some bits from a byte, word or whatever lenght in a logical and easy to remember way the code follows this (I use uint8_t. uint16_t and uint32_t as I work with several MCUs with different wordlenghts and its easy to know how many bits I'm working with instead of using int, long, etc):

1 ) Build a "mask" with the whole lenght in bits placing 0's in the positions you want to keep unchanged, 1's in the bits you want to change.

uint8_t bitMask = 0b00001111

2.a ) If you want to clear those bits logically negate your mask and then logically AND the result to the value you want to modify. I'll be doing it in several steps to help the understanding...

bitMask = ~bitMask;  // bitMask becomes 0b11110000  
currentState = currentState & bitMask;  // Just using the simpler to understand notation instead of &=`

2.b) If you want to set those bits logically just OR the original mask to the value you want to modify:

`currentState = currentState | bitMask;  // Just using the simpler to understand notation instead of |=`

2.c) If you want to invert the values of the bits set as 1 in the mask, lógically XOR the original mask to the value you want to modify. The resulting value will have those bit positions of your mask marked with 1's inverted, meaning where you had 0's you'll have 1's, and vice versa.

`currentState = currentState ^ bitMask;  // Just using the simpler to understand notation instead of ^=`

There you have how to modify your original value however you want without just limiting for a strict use of one symbol or another.

About your question referring to a 74HC595, the shift regiter will present whatever it has loaded in it's buffer to it's pins as soon as you send a latching signal, it will change the current output to the new output, so from the pinout perspective you'll be changing just the bits you modified, but you'll have to move all the 8 bits in again, the unmodified and the modified, because if you interrupt the process of sending bits just after a limited number are sent you'll end up with the other bits shifted... not a good idea!

If you need further reference of how to manipulate the 74HC595 in code just ask!

Good Luck!
Gaby.//

If you want to clear some bit in a byte, do as you did but with all bits set to 1 except those you want to clear :

currentState &= B01010111;  // Clear Signal 2 bits (Q3, Q5, Q7)

Had to also get someone else to help, I still dont fully understand why the flipping the bits with the ~ operator helps to mask out only the bits you want changed, but with some help I rewrote the code with that operator and it seems to be working now! Im bummed because i dont like having something and not knowing how it fully works :frowning_with_open_mouth: Thanks to everyone for there response. If theres anyway I can improve the code, let me know!!

// Pin assignments for the 74HC595 shift register
const int dataPin = A2;   // Connect to DS (Pin 14) on the 74HC595
const int latchPin = A0;  // Connect to ST_CP (Pin 12) on the 74HC595
const int clockPin = A1;  // Connect to SH_CP (Pin 11) on the 74HC595
const int Dpin_1 = A3;
const int Dpin_2 = A4;
const int Dpin_3 = A5;
int sensor2 = 0;
int sensor3 = 0;
// Timing intervals for each signal
const unsigned long intervalSignal1 = 1000;  // 1000 ms for Signal 1
const unsigned long intervalSignal2 = 589;   // 589 ms for Signal 2
unsigned long prevMillis1 = 0;
unsigned long prevMillis2 = 0;
// Variables to track the last update time for each signal
unsigned long previousMillisSignal1 = 0;
unsigned long previousMillisSignal2 = 0;
unsigned long interval = 1000;

// Global state of all outputs
byte currentState = B00000000;

// State indices to track the current state for each signal
int currentStateSignal1 = 0;
int currentStateSignal2 = 0;

// Define masks for Signal 1 and Signal 2
const byte maskSignal1 = B01010111; // Mask for Q0, Q1, Q2, Q4, Q6
const byte maskSignal2 = B10101000; // Mask for Q3, Q5, Q7
unsigned long currentMillis;
void setup() {
    // Set pin modes
    pinMode(dataPin, OUTPUT);
    pinMode(latchPin, OUTPUT);
    pinMode(clockPin, OUTPUT);
    pinMode(Dpin_1, INPUT);
    pinMode(Dpin_2, INPUT); 
    pinMode(Dpin_3, INPUT); 
    // Initialize the shift register with all outputs off
    updateShiftRegister();
    
}

void loop() {
    currentMillis = millis();
    advanceSignal1(prevMillis1);
    advanceSignal2(prevMillis2);

}

//Home signal
void advanceSignal1(unsigned long &prevMillis) {
   int sensor2 = digitalRead(Dpin_2);
   //Serial.println(currentMillis);
    // Clear Signal 1 bits in currentState using mask
    currentState &= ~maskSignal1;
    //Cycle through signal if signal sensor is cut
    if (sensor2 == 0){
      currentStateSignal1 = 0;
      prevMillis = currentMillis;
    }
    // Cycle through the states for Signal 1 based on the current state index
    switch (currentStateSignal1) {
        case 0:
            currentState |= B00000100;  // Set Q2
            if (currentMillis - prevMillis >= interval) {
            prevMillis = currentMillis;
            currentStateSignal1 = 1;
            }
            break;
        case 1:
            currentState |= B01010000;  // Set Q4 + Q6
            if (currentMillis - prevMillis >= interval) {
            prevMillis = currentMillis;
            currentStateSignal1 = 2;
            }
            break;
        case 2:
            currentState |= B00010010;  // Set Q1 + Q4
            if (currentMillis - prevMillis >= interval) {
            prevMillis = currentMillis;
            currentStateSignal1 = 3;
            }
            break;
        case 3:
            currentState |= B00000011;  // Set Q0 + Q1
            if (currentMillis - prevMillis >= interval) {
            prevMillis = currentMillis;           
            }
            break;
    }

    // Move to the next state, wrapping around after the last state
    //currentStateSignal1 = (currentStateSignal1 + 1) % 4;

    updateShiftRegister();  // Update the shift register with the new state
}

//Regular/timer signal
void advanceSignal2(unsigned long &prevMillis) {
    int sensor3 = digitalRead(Dpin_3);
    // Clear Signal 2 bits in currentState using mask
    currentState &= ~maskSignal2;
    // Cycle through the signal states if signal is cut
    if (sensor3 == 0){
      currentStateSignal2 = 0;
      prevMillis = currentMillis;
    }
    switch (currentStateSignal2) {
        case 0:
            currentState |= B10000000;  // Set Q7
            if (currentMillis - prevMillis >= interval) {
            prevMillis = currentMillis;
            currentStateSignal2 = 1;
            }
            break;
        case 1:
            currentState |= B00100000;  // Set Q5
            if (currentMillis - prevMillis >= interval) {
            prevMillis = currentMillis;
            currentStateSignal2 = 2;
            }
            break;
        case 2:
            currentState |= B00001000;  // Set Q3
            break;
    }

    // Move to the next state, wrapping around after the last state
   // currentStateSignal2 = (currentStateSignal2 + 1) % 3;

    updateShiftRegister();  // Update the shift register with the new state
}

void updateShiftRegister() {
    // Use latch to update shift register output
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, currentState);
    digitalWrite(latchPin, HIGH);
}

Hi @train_fanatic ,

Good to know you got it running. The explanation about bit logic operation is quite simple but I'd recommend looking up the term "truth table" and the web will give you millions of answers...

Unfortunately there's always something to improve, the phenomena is part of what is called "falling in love with the design" (I've studied it in spanish, so the translation surely sucks) ... you always find a way to improve your design, add a twist, a better touch of style...

In the code you provide nothing really pops up, just some indentation inside the if that might rise a warning in some compilations configurations (the misleading indentation warning)... but it's not an error.

Going to the real practical thing, in your code you've still not used the Dpin_1, Dpin_2 and Dpin_3, eventually you'll have to ensure you have propper pull ups or pull downs for those inputs to avoid floating values... But not today!

Good Luck!
Gaby.//

Yeah i think if i saw the logic table of why inverting bits will do the masking id understand it better.
those Dpin_ are for connecting the IR sensor, i plan on adding more, i just tested 2 to confirm that i can properly output different signals to the IC chip.

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