// Note: I should probably put resistors between the output shift register and the transistors controlling the DPDT relays
// Include Libraries and Information
#include <LiquidCrystal_I2C.h>
// LCD Setup:
#define I2C_ADDR 0x27
#define LCD_COLUMNS 20
#define LCD_LINES 4
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
// Pin Definitions:
const byte dataPinOut = 6; // "DS"
const byte latchPinOut = 7; // "STCP"
const byte clockPinOut = 8; // "SHCP"
const byte clockPinIn = 9; // "CP"
const byte latchPinIn = 10; // "PL"
const byte dataPinIn = 11; // "Q7"
const byte allPowerOnPin = 12; // Toggle power ON to all tracks
const byte allPowerOffPin = 3; // Toggle power OFF to all tracks
const byte enableProgrammingTrackPin = 13; // Enable a programming track
const byte rotaryCLKPin = 2;
const byte rotaryDTPin = 4;
const byte rotarySWPin = 5;
const byte leadTrackPowerOutputPin = A0;
const byte leadTrackPowerButtonPin = A1;
// Global Constant Definitions:
const int bitsPerChip = 8; // The number of bits per shift register.
const int numShiftRegsIn = 1; // The number of shift registers (for input).
const int numShiftRegsOut = 2; // The number of shift registers (for output).
const int numBitsIn = bitsPerChip*numShiftRegsIn; // Set to 8x the number of input shift registers.
const int numBitsOut = bitsPerChip*numShiftRegsOut; // Set to 8x the number of output shift registers.
const int delayTime = 50; // How long to wait between each running of the main loop
// const int toggleAllDelayTime = 250; //
// const int progTrackToggleDelayTime = 100; // How long to delay when switching to/from being a programming track (power off --> switch --> power on)
// const int debounceDelayTime = 50; // How long to wait after a button press (non interrupt/shift register)
#define NUM_SHIFT_REGS_OUT 2
const uint8_t numOfRegisterPinsOut = NUM_SHIFT_REGS_OUT * 8;
const bool showOutArrayDebug = HIGH; // Controls if the toggleArray is printed to the Serial Monitor for debug/testing (HIGH = Displayed, LOW = Not Displayed)
const bool showLoopTimeDebug = HIGH; // Controls if the loop time is printed to the Serial Monitor for debug/testing (HIGH = Displayed, LOW = Not Displayed)
const int numTracks = 3; // Number of tracks to select from (count starting with 0)
// Global Variable Definitions:
bool enableProgTrack = LOW; // Variable to control if a programming track should be enabled (HIGH = Yes, LOW = No)
bool enableProgTrackPrevious = LOW; // Variable to store the previous value for if a programming track should be enabled (HIGH = Yes, LOW = No)
bool enableProgTrackSwitchState = HIGH; // Variable to store the state of the switch to control if a programming trach should be enabled
int progTrackSelection = 0; // Variable to store the programming track that is being selected
int progTrackSelectionConfirmed = 0; // Variable to store active programming track
bool progTrackSelectionUpdate = true; // Variable to store if the programming track that is being selected has changed
bool progTrackSelectionConfirmedUpdate = true; // Variable to store if the programming track that is being selected has changed
bool leadTrackPowerState = LOW; // Variable to store the power if the power is on or off to the lead track
bool leadTrackToggleButtonState = HIGH; // Variable to store the state of the button to toggle power to the lead track
bool leadTrackToggleButtonPreviousState = HIGH; // Variable to store the previous state of the button to toggle power to the lead track
bool leadTrackPowerStateUpdate = true; // Variable to store if the power state of the lead track has/should change
bool storageTrackPowerStateUpdate = true; // Variable to store if the power state of one of the storage tracks has changed
bool allPowerOnButtonState = HIGH; // Variable to store the state of the button to turn all track power on
bool allPowerOnButtonPreviousState = HIGH; // Variable to store the previous state of the button to turn all track power on
bool rotaryButtonState = HIGH; // Variable to store the state of the rotary encoder's button
bool rotaryButtonPreviousState = HIGH; // Variable to store the previous state of the rotary encoder's button
bool storageTrackPowerButtonStates[numTracks+1]; // Array to store the states of the storage track power toggle buttons
bool storageTrackPowerButtonPreviusStates[numTracks+1]; // Array to store the previous states of the storage track power toggle buttons
bool storageTrackPowerStates[numTracks+1]; // Array to store the power states of the storage tracks
bool progTrackBoolArray[numTracks+1]; // Array to store the programming track status of the storage tracks // If more memory is needed, this array is probably redundant
bool combinedOutputArray[numBitsOut]; // Array to store the values to be output to the shift register(s)
// combinedOutputArray = [prog 1, prog 2, prog 3, prog 4, power 1, power 2, power 3, power 4, green 1, green 2, green 3, green 4, red 1, red 2, red 3, red 4]
bool storageTrackOccupancyState[numTracks+1]; // Array to store if the storage tracks are occupied (HIGH = occupied)
bool storageTrackOccupancyPreviousState[numTracks+1]; // Array to store if the storage tracks were occupied on the previous loop (HIGH = occupied)
bool storageTrackSignalStates[(numTracks+1)*2]; // Array to store the signal states for the storage tracks [green 1, green 2, green 3, green 4, red 1, red 2, red 3, red 4]
bool storageTrackOccupancyUpdate = true; // Variable too store if there has been a change to storage track occupancy
bool inputArray[numBitsIn]; // Array to store the inputs from the input shift register(s)
// inputArray = [power 1, power 2, power 3, power 4, occupancy 1, occupancy 2, occupancy 3, occupancy 4]
// Debug Configuration:
const bool serialPrintDebug = false; // Should debug information be printed to the serial monitor
void setup() {
// Set pinModes:
pinMode(dataPinOut, OUTPUT);
pinMode(latchPinOut, OUTPUT);
pinMode(clockPinOut, OUTPUT);
pinMode(clockPinIn, OUTPUT);
pinMode(latchPinIn, OUTPUT);
pinMode(dataPinIn, INPUT);
pinMode(allPowerOnPin, INPUT_PULLUP);
pinMode(allPowerOffPin, INPUT_PULLUP);
pinMode(enableProgrammingTrackPin, INPUT_PULLUP);
pinMode(rotaryCLKPin,INPUT_PULLUP);
pinMode(rotaryDTPin, INPUT);
pinMode(rotarySWPin, INPUT_PULLUP);
pinMode(leadTrackPowerOutputPin, OUTPUT);
pinMode(leadTrackPowerButtonPin, INPUT_PULLUP);
// Attach Interrupt(s):
attachInterrupt(digitalPinToInterrupt(rotaryCLKPin), rotaryISR, FALLING);
attachInterrupt(digitalPinToInterrupt(allPowerOffPin), allPowerOffISR, FALLING);
// Begin Serial Communication
if (serialPrintDebug == true) {
Serial.begin(115200);
Serial.println("Printing debug information to serial monitor.");
}
// Pre-set array values:
for (int i=0; i<=numTracks; i++) {
storageTrackPowerStates[i] = LOW; // Pre-set array for storage track power states
progTrackBoolArray[i] = LOW; // Pre-set array for programming track selection
storageTrackPowerButtonStates[i] = LOW; // Pre-set array for state of storage track power state toggle buttons
storageTrackPowerButtonPreviusStates[i] = LOW; // Pre-set array for state of previous storage track power state toggle buttons
storageTrackOccupancyState[i] = LOW;
}
for (int i=0; i<(numTracks+1)*2; i++) {
storageTrackSignalStates[i] = LOW;
}
for (int i=0; i<numBitsIn; i++) {
inputArray[i] = LOW;
}
for (int i=0; i<numBitsOut; i++) {
combinedOutputArray[i] = LOW;
}
// Setup LCD:
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0,0);
lcd.print("TRK PWR:");
lcd.setCursor(0,1);
lcd.print("ACTIVE PROG TRK:");
lcd.setCursor(0,2);
lcd.print("SELECT PROG TRK:");
if (digitalRead(enableProgrammingTrackPin) == LOW) {
lcd.setCursor(16,1);
lcd.print(" ");
lcd.print(progTrackSelectionConfirmed);
lcd.print(" ");
} else if (digitalRead(enableProgrammingTrackPin) == HIGH) {
lcd.setCursor(16,1);
lcd.print(" -- ");
}
lcd.setCursor(16,2);
lcd.print(" ");
lcd.print(progTrackSelection);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print("LEAD TRK PWR:");
lcd.setCursor(14,3);
if (leadTrackPowerState==LOW) {
lcd.print("OFF");
} else if (leadTrackPowerState==HIGH) {
lcd.print("ON ");
}
}
void loop() {
// Read non-interrupt inputs:
readShiftRegisterInputs();
readMiscInputs();
// Translate inputs:
translatePowerInputs();
translateProgrammingTrackInputs();
translateOccupancyInputs();
// Apply changes:
writeOutputs();
// Update LCD:
updateTrackPowerLCD();
updateProgSelectionLCD();
// Reset update states:
progTrackSelectionUpdate = false;
progTrackSelectionConfirmedUpdate = false;
storageTrackPowerStateUpdate = false;
leadTrackPowerStateUpdate = false;
storageTrackOccupancyUpdate = false;
delay(delayTime);
}
// ISRs: (Interrupt Service Routines)
void rotaryISR() { // ISR to run if the rotary decoder has been turned
byte dtPinStatus = digitalRead(rotaryDTPin);
if (dtPinStatus == HIGH) { // Clockwise
progTrackSelection = progTrackSelection + 1;
}
if (dtPinStatus == LOW) { // Counterclockwise
progTrackSelection = progTrackSelection - 1;
}
// Check for selection limits:
if (progTrackSelection > numTracks) { // If we have exceeded the number of tracks controlled
progTrackSelection = 0;
}
if (progTrackSelection < 0) {
progTrackSelection = numTracks;
}
progTrackSelectionUpdate = true;
}
void allPowerOffISR() {
leadTrackPowerState = LOW;
leadTrackPowerStateUpdate = true;
for (int i=0; i<=numTracks; i++) {
storageTrackPowerStates[i] = LOW;
combinedOutputArray[i+(numTracks+1)] = storageTrackPowerStates[i];
}
storageTrackPowerStateUpdate = true;
writeOutputs();
}
void writeOutputs() {
if (leadTrackPowerStateUpdate==true) {
digitalWrite(leadTrackPowerOutputPin, leadTrackPowerState);
}
if ((storageTrackPowerStateUpdate==true) || (progTrackSelectionConfirmedUpdate==true) || (storageTrackOccupancyUpdate==true)) {
digitalWrite(latchPinOut, LOW);
for (int i=numOfRegisterPinsOut - 1; i>=0; i--) {
digitalWrite(clockPinOut, LOW);
digitalWrite(dataPinOut, combinedOutputArray[i]);
digitalWrite(clockPinOut, HIGH);
}
digitalWrite(latchPinOut, HIGH);
}
}
void readShiftRegisterInputs() {
// Read buttons on shift register:
digitalWrite(latchPinIn, LOW);
digitalWrite(latchPinIn, HIGH);
for (int i=0; i<numShiftRegsIn; i++) {
for (int j=(bitsPerChip-1); j>=0; j--) {
int indexVar = (bitsPerChip*i) + j;
int bit = digitalRead(dataPinIn);
if (bit == HIGH) {
inputArray[indexVar] = LOW;
} else {
inputArray[indexVar] = HIGH;
}
digitalWrite(clockPinIn, HIGH);
digitalWrite(clockPinIn, LOW);
}
}
}
void readMiscInputs() {
enableProgTrackSwitchState = digitalRead(enableProgrammingTrackPin); // Read switch to control if a programming track should be enabled
rotaryButtonState = digitalRead(rotarySWPin); // Read the button on the rotary encoder
allPowerOnButtonState = digitalRead(allPowerOnPin); // Read the button to turn on all track power
leadTrackToggleButtonState = digitalRead(leadTrackPowerButtonPin); // Read the button to toggle power to the lead track
}
void translatePowerInputs() {
// Handle individual track power control:
for (int i=0; i<=numTracks; i++) {
storageTrackPowerButtonStates[i] = inputArray[i];
}
for (int i=0; i<=numTracks; i++) {
if (storageTrackPowerButtonStates[i] == HIGH && storageTrackPowerButtonPreviusStates[i] == LOW) {
storageTrackPowerStates[i] = !storageTrackPowerStates[i];
combinedOutputArray[i + (numTracks+1)] = storageTrackPowerStates[i];
storageTrackPowerStateUpdate = true;
}
}
// Handle lead track power control:
if (leadTrackToggleButtonState == LOW && leadTrackToggleButtonPreviousState == HIGH) {
leadTrackPowerState = !leadTrackPowerState;
leadTrackPowerStateUpdate = true;
}
// Handle turning on power to all tracks:
if (allPowerOnButtonState == LOW && allPowerOnButtonPreviousState == HIGH) {
for (int i=0; i<=numTracks; i++) {
storageTrackPowerStates[i] = HIGH;
combinedOutputArray[i + (numTracks+1)] = HIGH;
}
leadTrackPowerState = HIGH;
storageTrackPowerStateUpdate = true;
leadTrackPowerStateUpdate = true;
}
// Set previous states for next loop:
leadTrackToggleButtonPreviousState = leadTrackToggleButtonState; // Update button states for toggling lead track power
allPowerOnButtonPreviousState = allPowerOnButtonState; // Update button state for button to turn on power to all tracks
for (int i=0; i<=numTracks; i++) {
storageTrackPowerButtonPreviusStates[i] = storageTrackPowerButtonStates[i];
}
}
void translateProgrammingTrackInputs() {
if (enableProgTrackSwitchState == LOW) {
enableProgTrack = HIGH;
} else {
enableProgTrack = LOW;
}
if (enableProgTrack != enableProgTrackPrevious) {
progTrackSelectionConfirmedUpdate = true;
if (enableProgTrack == HIGH) {
for (int i=0; i<=numTracks; i++) {
progTrackBoolArray[i] = LOW;
combinedOutputArray[i] = LOW;
}
progTrackBoolArray[progTrackSelectionConfirmed] = HIGH;
combinedOutputArray[progTrackSelectionConfirmed] = HIGH;
} else {
for (int i=0; i<=numTracks; i++) {
progTrackBoolArray[i] = LOW;
combinedOutputArray[i] = LOW;
}
}
}
if (rotaryButtonState == LOW && rotaryButtonPreviousState == HIGH) {
progTrackSelectionConfirmed = progTrackSelection;
for (int i=0; i<=numTracks; i++) {
progTrackBoolArray[i] = LOW;
combinedOutputArray[i] = LOW;
}
if (enableProgTrack == HIGH) {
progTrackBoolArray[progTrackSelectionConfirmed] = HIGH;
combinedOutputArray[progTrackSelectionConfirmed] = HIGH;
}
progTrackSelectionConfirmedUpdate = true;
}
// Set previous states for next loop:
rotaryButtonPreviousState = rotaryButtonState; // Update button state for button on rotary encoder
enableProgTrackPrevious = enableProgTrack;
}
void translateOccupancyInputs() { // Use 10k pull up resitors (if useing NCE BD20) https://www.dccconcepts.com/themencode-pdf-viewer-sc/?tnc_pvfw=ZmlsZT1odHRwczovL3d3dy5kY2Njb25jZXB0cy5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTYvMDIvTkNFLUJEMjAtTWFudWFsLnBkZiZzZXR0aW5ncz0xMTExMTExMTExMTExMTExMTAwJmxhbmc9ZW4tVVM=#page=&zoom=auto&pagemode=
for (int i=0; i<=numTracks; i++) {
storageTrackOccupancyState[i] = inputArray[i+(numTracks+1)];
}
for (int i=0; i<=numTracks; i++) {
if ((storageTrackOccupancyState[i] == HIGH) || (storageTrackPowerStates[i]==LOW) || (progTrackBoolArray[i]==HIGH)) {
storageTrackSignalStates[i] = LOW;
storageTrackSignalStates[i+(numTracks+1)] = HIGH;
} else {
storageTrackSignalStates[i] = HIGH;
storageTrackSignalStates[i+(numTracks+1)] = LOW;
}
}
for (int i=0; i<=((2*numTracks)+1); i++) {
combinedOutputArray[i+((numTracks+1)*2)] = storageTrackSignalStates[i];
}
for (int i=0; i<=numTracks; i++) {
if (storageTrackOccupancyState[i] != storageTrackOccupancyPreviousState[i]) {
storageTrackOccupancyUpdate = true;
}
}
for (int i=0; i<=numTracks; i++) {
storageTrackOccupancyPreviousState[i] = storageTrackOccupancyState[i];
}
}
void updateTrackPowerLCD() {
if (storageTrackPowerStateUpdate == true) {
lcd.setCursor(9,0);
for (int i=0; i<=numTracks; i++) {
lcd.print(storageTrackPowerStates[i]);
}
}
if (leadTrackPowerStateUpdate == true) {
lcd.setCursor(14,3);
if (leadTrackPowerState==LOW) {
lcd.print("OFF");
} else if (leadTrackPowerState==HIGH) {
lcd.print("ON ");
}
}
}
void updateProgSelectionLCD() {
if (progTrackSelectionUpdate == true) {
lcd.setCursor(17,2);
if (progTrackSelection < 10) {
lcd.print(progTrackSelection);
lcd.print(" ");
} else if (progTrackSelection >=10 && progTrackSelection < 100) {
lcd.print(progTrackSelection);
lcd.print(" ");
}
}
if (progTrackSelectionConfirmedUpdate == true) {
lcd.setCursor(17,1);
if (enableProgTrack == HIGH) {
if (progTrackSelectionConfirmed < 10) {
lcd.print(progTrackSelectionConfirmed);
lcd.print(" ");
} else if (progTrackSelectionConfirmed >=10 && progTrackSelectionConfirmed < 100) {
lcd.print(progTrackSelectionConfirmed);
lcd.print(" ");
}
} else if (enableProgTrack == LOW) {
lcd.print("-- ");
}
}
}