Cable harness tester with 3 connectors (<30 test points)

Hi! I need some help with my mini project. I'm trying to build a cable tester that checks continuity, cross-wiring, and for short or open circuits.

The cable has 4 wires and connects 3 connectors: A, B, and C.

  • Connector A has 4 pins (A1 to A4).
  • Connector B has 2 pins (B1, B2).
  • Connector C has 2 pins (C1, C2).

Here’s the wiring:

  • A1 → B1
  • A2 → B2
  • A3 → C1
  • A4 → C2

I already made a schematic and even built a physical version, but I haven’t tested it yet. I'm struggling with both the schematic and the code, so I’d really appreciate it if someone could take a look at my schematic and let me know if there are any mistakes.

Big thanks to LarryD for his support
Schematic_New-Project_2025-04-05.pdf (89,2 Ko)


Reduce the above wiring mess to just the most simple case, like four wires. A schematic is what you intend, and a photograph of a hand-drawn wiring diagram is what is needed. You will also need a 'truth table' type of code; search the forum for an example.

2 Likes

Do not cross-post. Which topic do you want to keep, the one in the English section or the one in the French section?

Both is not an option as it is against the rules of the forum.

1 Like
  • Here is an example you should be able to learn from:

//
//================================================^================================================
//                             C a b l e   T e s t e r   S k e t c h
//
//  URL
//
//  LarryD
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ========================================================================
//  1.00       22/03/01    Running code
//  1.10       22/04/01    Added resistance measurement
//  1.20       23/09/23    Added short/long press for testing, added class TIMER stuff
//  1.30       24/03/18    Changed to "go no go" cable testing
//  1.40       25/02/20    At powerup/reset time, switch adusts number of wires in the standard cable
//
//
//
// Notes:
// - See "CableTester" schematic.
// - Arduino UNO, 2 X CD74HC4067 (16 channel analog mux), N.O. switch
// - Tests cables with up to 16 wires.
// - Connector resistance for each wire is checked.
// - Only one connection from one connector to the other connector is allowed,
//   Ex: pin #1 on the left side of our cable is connected to pin #2
//       on the right side of our cable
//
//


//=================================================================================================
#define LEDon              HIGH   //PIN---[220R]---A[LED]K---GND
#define LEDoff             LOW

#define PRESSED            LOW    //+5V---[Internal 50k]---PIN---[Switch]---GND
#define RELEASED           HIGH

#define CLOSED             LOW    //+5V---[Internal 50k]---PIN---[Switch]---GND
#define OPENED             HIGH

#define ENABLED            true
#define DISABLED           false

#define RELAYon            LOW
#define RELAYoff           HIGH

#define NOconnection       255


//                          millis() / micros()   B a s e d   T I M E R S
//================================================^================================================
//
/*example

  //========================
  makeTIMER toggleLED =
  {
     //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
     MILLIS/MICROS, 500ul, ENABLED/DISABLED, YES/NO, A0-A5

     //.SpeedAdjustPin defaults to 0 i.e. no speed adjustment is used
     //if .SpeedAdjustPin = A0-A5, a potentiometer on this pin adjusts the TIMER's speed (for diagnostics)
     //class static flag "makeTIMER::normalFlag" can be used to ENABLE/DISABLE adjustable TIMER speed,
     //ENABLE = normal speed, DISABLED = potentiometer controls TIMER speed
  };

*/

//These TIMER objects are non-blocking
class makeTIMER
{
#define MILLIS               0
#define MICROS               1

#define ENABLED              true
#define DISABLED             false

#define YES                  true
#define NO                   false

#define STILLtiming          0
#define EXPIRED              1
#define TIMERdisabled        2

  private:
  public:

    static bool              s_normalFlag;    //when ENABLED, adjustable TIMERs run at normal speed

    unsigned long            Time;            //when the TIMER started

    //these "members" are needed to define a TIMER
    byte                     TimerType;       //what kind of TIMER is this? MILLIS/MICROS
    unsigned long            Interval;        //delay time which we are looking for
    bool                     TimerFlag;       //is the TIMER enabled ? ENABLED/DISABLED
    bool                     Restart;         //do we restart this TIMER   ? YES/NO
    byte                     SpeedAdjustPin;  //a potentiometer on this pin, A0-A5, adjusts TIMER speed


    //================================================
    //constructor with no parameters
    makeTIMER()
    {
      TimerType = MILLIS;
      Interval = 1000ul;
      TimerFlag = ENABLED;
      Restart = YES;
      SpeedAdjustPin = 0;

      Time = 0;
    }

    //================================================
    //constructor with parameters
    makeTIMER(byte _TimerType, unsigned long _Interval,
              bool _TimerFlag, bool _Restart, byte _SpeedAdjustPin = 0)
    {
      TimerType = _TimerType;
      Interval = _Interval;
      TimerFlag = _TimerFlag;
      Restart = _Restart;
      SpeedAdjustPin = _SpeedAdjustPin;

      Time = 0;
    }

    //================================================
    //condition returned: STILLtiming (0), EXPIRED (1) or TIMERdisabled (2)
    //function to check the state of our TIMER        ex: if(myTimer.checkTIMER() == EXPIRED);
    byte checkTIMER()
    {
      //========================
      //is this TIMER enabled ?
      if (TimerFlag == ENABLED)
      {
        //============
        //is this an adjustable TIMER OR is the "normalSpeed" switch closed ?
        if (SpeedAdjustPin == 0 || s_normalFlag == ENABLED)
        {
          //============
          //this TIMER "is not" speed adjustable,
          //has this TIMER expired ?
          if (getTime() - Time >= Interval)
          {
            //============
            //should this TIMER restart again?
            if (Restart == YES)
            {
              //restart this TIMER
              Time = getTime();
            }

            //this TIMER has expired
            return EXPIRED;
          }
        }

        //============
        //this TIMER is speed adjustable
        else
        {
          //============
          //for diagnostics, we use a potentiometer to adjust TIMER speed,
          //has this TIMER expired ?
          if (getTime() - Time >= Interval / adjustInterval())
          {
            //============
            //should this TIMER restart again?
            if (Restart == YES)
            {
              //restart this TIMER
              Time = getTime();
            }

            //this TIMER has expired
            return EXPIRED;
          }
        }

        return STILLtiming;

      } //END of   if (TimerFlag == ENABLED)

      //========================
      else
      {
        //this TIMER is disabled
        return TIMERdisabled;
      }

    } //END of   checkTime()

    //================================================
    //function to enable and restart this TIMER       ex: myTimer.enableRestartTIMER();
    void enableRestartTIMER()
    {
      TimerFlag = ENABLED;

      //restart this TIMER
      Time = getTime();

    } //END of   enableRestartTIMER()

    //================================================
    //function to disable this TIMER                  ex: myTimer.disableTIMER();
    void disableTIMER()
    {
      TimerFlag = DISABLED;

    } //END of    disableTIMER()

    //================================================
    //function to restart this TIMER                  ex: myTimer.restartTIMER();
    void restartTIMER()
    {
      Time = getTime();

    } //END of    restartTIMER()

    //================================================
    //function to force this TIMER to expire          ex: myTimer.expireTimer();
    void expireTimer()
    {
      //force this TIMER to expire
      Time = getTime() - Interval;

    } //END of   expireTimer()

    //================================================
    //function to set the Interval for this TIMER     ex: myTimer.setInterval(100);
    void setInterval(unsigned long value)
    {
      //set the Interval
      Interval = value;

    } //END of   setInterval()

    //================================================
    //function to return the current time
    unsigned long getTime()
    {
      //return the time             i.e. millis() or micros()
      //========================
      if (TimerType == MILLIS)
      {
        return millis();
      }

      //========================
      else
      {
        return micros();
      }

    } //END of   getTime()


    //================================================
    //for diagnostics, a potentiometer on an analog pin is used to adjust TIMER speed, thanks alto777
    unsigned int adjustInterval()
    {
      unsigned int Speed = analogRead(SpeedAdjustPin);

      //using integer math to save on memory
      Speed = 1 + (Speed * 14) / 1023;  //Speed will have a range from 1 to 15

      return Speed;

    } //END of   adjustInterval()

}; //END of   class makeTIMER

//================================================
//initialize the static "s_normalFlag" variable,
//when ENABLED, adjustable TIMERs run at normal speed
bool makeTIMER::s_normalFlag = DISABLED;


//                                T I M E R   D e f i n i t i o n s
//================================================^================================================
//
//========================
//example: uses default library values
//.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
//    MILLIS,    1000ul,    ENABLED,      YES,       0
//makeTIMER testTIMER{};

//======================== 500ms
makeTIMER heartbeatTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin (A5 is the potentiometer pin)
  MILLIS, 500ul, ENABLED, YES, 0
};

//======================== 5ms
makeTIMER switchesTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MILLIS, 5ul, ENABLED, YES, 0
};

//======================== 1ms
makeTIMER machineTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MICROS, 1 * 1000ul, ENABLED, YES, 0
};

//======================== 5ms
makeTIMER commonTIMER =
{
  //.TimerType, .Interval, .TimerFlag, .Restart, .SpeedAdjustPin
  MICROS, 5 * 1000ul * 1000ul, ENABLED, YES, 0
};


//                                  c l a s s    m a k e I n p u t
//================================================^================================================
//
//a class to define "Input" objects, switches or sensors

//================================================
class makeInput
{
#define NOTvalidated       0
#define VALIDATED          1
#define NOchange           2

  private:
  public:

    static byte s_filter;
    //say the above validating "s_filter" variable is set to 10
    //if we scan "inputs" every 5ms
    //i.e. we sample our inputs every 5ms looking for a change in state.
    //5ms * 10 = 50ms is needed to validate a switch change in state.
    //i.e. a switch change in state is valid "only after" 10 identical changes are detected.
    //This technique is used to filter out EMI (spikes), noise, etc.
    //i.e. we ignore switch changes in state that are less than 50ms.

    unsigned long switchTime;       //the time the switch was closed
    byte counter;                   //a counter used for validating a switch change in state

    //these "members" are needed to define an "Input"
    byte pin;                       //the digital input pin number
    byte lastState;                 //the state the input was last in


    //================================================
    //constructor with parameters
    makeInput(byte _pin, byte _lastState)
    {
      pin = _pin;
      lastState = _lastState;

      switchTime = 0;
      counter = 0;

      pinMode(pin, INPUT_PULLUP);
    }

    //================================================
    //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
    //check to see if the input object has had a valid state change
    byte validChange()
    {
      byte currentState = digitalRead(pin);

      //===================================
      //has there been an input change in state ?
      if (lastState != currentState)
      {
        //we have had another similar change in state
        counter++;

        //============
        //is the "change in state" stable ?
        if (counter >= s_filter)
        {
          //an input change has been validated
          //get ready for the next scanning sequence
          counter = 0;

          //update to this new state
          lastState = currentState;

          //============
          if (currentState == CLOSED)
          {
            //capture the time when the switch closed
            switchTime = millis();
          }

          return VALIDATED;
        }

        return NOTvalidated;
      }

      //===================================
      //there has not been an input change in state
      counter = 0;

      return NOchange;

    } //END of   validChange()

}; //END of   class makeInput

//================================================
//a change in state is confirmed/validated when 10 identical state changes in a row is seen
byte makeInput::s_filter = 10;


//                                    S t a t e   M a c h i n e
//================================================^================================================
//
//the states in our machine
enum STATES : byte
{
  STARTUP, POWERUP_WAITING, WAITING, TEST_STANDARD, READ_STANDARD, DISPLAY_STANDARD, TEST_TARGET,
  READ_TARGET, DISPLAY_TARGET, COMPARE_CABLES, UPDATE_LCD, FINISHED
};

//========================
STATES mState = STARTUP;


//                              G P I O s   A n d   V a r i a b l e s
//================================================^================================================
//
//which controller is being used ?
//# define ESP32

//========================
# ifdef ESP32

//Analogs
//================================================
//
const byte muxPin                  = 34;
const byte referencePin            = 32;

//OUTPUTS
//================================================
//
const byte s0TXpin                = 19;
const byte s1TXpin                = 18;
const byte s2TXpin                = 5;
const byte s3TXpin                = 17;

const byte s0RXpin                = 16;
const byte s1RXpin                = 4;
const byte s2RXpin                = 2;
const byte s3RXpin                = 15;

const byte heartbeatLED           = 13;

const byte outputArray[]          = {19, 18, 5, 17, 16, 4, 2, 15, 13};
const byte outputSize             = sizeof(outputArray) / sizeof(outputArray[0]);

//========================
# else

const byte muxPin                 = A0;
const byte referencePin           = A1;

const byte s0TXpin                = 4;
const byte s1TXpin                = 5;
const byte s2TXpin                = 6;
const byte s3TXpin                = 7;

const byte s0RXpin                = 8;
const byte s1RXpin                = 9;
const byte s2RXpin                = 10;
const byte s3RXpin                = 11;

const byte heartbeatLED           = 13;

const byte outputArray[]          = {4, 5, 6, 7, 8, 9, 10, 11, 13};
const byte outputSize             = sizeof(outputArray) / sizeof(outputArray[0]);

# endif

//INPUTS
//================================================
//
# ifdef ESP32
const byte startSw                = 35;

# else
const byte startSw                = 2;

# endif

//========================
makeInput startSwitch =
{
  //.pin, .lastState
  startSw, OPENED
};


//VARIABLES
//================================================
//
const byte numberOfPins           = 16;           //number of pins in our cable connector
const int loadResistance          = 973;          //973 ohms, 1% tolerance
const int muxResistance           = 74 * 2;       //resistance for each mux

byte standardCableWireCount       = numberOfPins;
byte numWires                     = numberOfPins;
byte txCounter                    = 0;
byte rxCounter                    = 0;

long Vref;
long Vmux;

unsigned long shortPushTime       = 1 * 1000ul;   //less than 1 second
unsigned long longPushTime        = 3 * 1000ul;   //greater than 3 second


//                                      c l a s s   C a b l e
//================================================^================================================
//
//a Class to define Cable Objects
//
//cables are made up of a maximum of 16 wires, there can only be a 1:1 connection
//i.e. TX pin #1 pin can only be connected to one RX pin
//Example:    zero indexed
//            ~~~~~^~~~~~~
// ??[ 0].rxPin =  1, .connection = 0 (ohms) i.e. Tx pin  1 goes to RX pin 2
// ??[ 1].rxPin =  0, .connection = 0 (ohms) i.e. TX pin  2 goes to RX pin 1
// ??[ 2].rxPin =  2, .connection = 0 (ohms) i.e. TX pin  3 goes to RX pin 3
// . . .
// ??[15].rxPin = 15, .connection = 0 (ohms) i.e. TX pin 16 goes to RX pin 16

//================================================
class Cable
{
  private:
  public:

    //Object members
    byte rxPin;
    byte resistance;   //255 is open circuit, otherwise 0-254 ohms

    //========================
    //overload the comparison == operator so we can compare two Cable Objects
    bool operator == (const Cable &_target) const
    //bool operator == (Cable _target)              //this works too
    {
      //============
      //do the members agree ?
      if (rxPin == _target.rxPin && resistance == _target.resistance)
      {
        return true;
      }

      return false;
    }

}; //END of   class Cable


//                                C a b l e   D e f i n i t i o n s
//================================================^================================================
//
//make our cable Objects

//========================
Cable  standardCable[numberOfPins]  = {};

//========================
Cable targetCable[numberOfPins]     = {};


//                                            D e b u g
//================================================^================================================
//
unsigned long startTime;
//Serial.println(micros() - startTime);
//startTime = micros();


//                                           s e t u p ( )
//================================================^================================================
//
void setup()
{
  Serial.begin(115200);

  //========================
  //set pinModes
  for (byte x = 0; x < outputSize; x++)
  {
    digitalWrite(outputArray[x], LOW);
    pinMode(outputArray[x], OUTPUT);
  }

  mState = STARTUP;

} //END of   setup()


//                                            l o o p ( )
//================================================^================================================
//
void loop()
{
  //========================================================================
  //Print the time it takes to return to this same spot.
  //Comment the next 3 lines when no longer needed
  //static unsigned long startTime;
  //Serial.println(micros() - startTime);
  //startTime = micros();

  //========================================================================  T I M E R  heartbeatLED
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to toggle the heartbeat LED ?
  if (heartbeatTIMER.checkTIMER() == EXPIRED)
  {
    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

  //========================================================================  T I M E R  switches
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to check our switches ?
  if (switchesTIMER.checkTIMER() == EXPIRED)
  {
    checkSwitches();
  }

  //========================================================================  T I M E R  machine
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to service our State Machine ?
  if (machineTIMER.checkTIMER() == EXPIRED)
  {
    checkMachine();
  }


  //================================================
  //       Other non blocking code goes here
  //================================================


} //END of   loop()


//                                    c h e c k M a c h i n e ( )
//================================================^================================================
//
void checkMachine()
{
  //================================================
  //service the current "state"
  switch (mState)
  {
    //========================
    case STARTUP:
      {
        txCounter = 0;
        rxCounter = 0;
        updateMuxes();

        clearCableStruture(standardCable);

        clearCableStruture(targetCable);

        Serial.println("Tester Ready\n");
        Serial.println("Long press reads the cable as our standard");
        Serial.print("Adjust wire count in the Standard Cable: 2-16 = ");
        Serial.println(numWires);

        mState = POWERUP_WAITING;
      }
      break;
    //========================
    case POWERUP_WAITING:
      {
        //============
        //was the counter adjusted ?
        if (numWires != standardCableWireCount )
        {
          Serial.print("Wires in the Standard cable = ");
          Serial.println(standardCableWireCount);

          //update to the new value
          numWires = standardCableWireCount;
        }
      }

      break;

    //========================
    case WAITING:
      {
        //do nothing
      }
      break;

    //========================
    case TEST_STANDARD:
      {
        Serial.println("\nReading Standard Cable");

        //we assume there is no connection
        clearCableStruture(standardCable);

        txCounter = 0;
        rxCounter = 0;

        updateMuxes();

        //this is a micro second TIMER
        //TIMER set to 5ms
        commonTIMER.setInterval(5 * 1000ul);

        //start this TIMER
        commonTIMER.enableRestartTIMER();

        mState = READ_STANDARD;
      }
      break;

    //========================
    case READ_STANDARD:
      {
        //condition returned: STILLtiming, EXPIRED or TIMERdisabled
        //is it time to test the next cable wire ?
        if (commonTIMER.checkTIMER() == EXPIRED)
        {
          //unsigned long startTime;
          //startTime = micros();

          //a 16 wire cable takes 256(tests) * 5ms(TIMER interval) = 1.28 seconds to test
          //
          //a call takes about 550us(per wire)* 256(tests) = ~140ms per 16 wire cable (worst case)
          checkConnection(standardCable);

          //Serial.println(micros() - startTime);

          //============
          //are we finished testing this cable ?
          if (txCounter == 0 && rxCounter == 0)
          {
            //we are finished with the TIMER
            commonTIMER.disableTIMER();

            Serial.println("\nStandard cable is read");

            mState = DISPLAY_STANDARD;
          }
        }
      }
      break;


    //========================
    case DISPLAY_STANDARD:
      {
        displayObject(standardCable);

        Serial.println("\nShort press compares a target cable to the standard");

        mState = WAITING;
      }
      break;

    //========================
    case TEST_TARGET:
      {
        txCounter = 0;
        rxCounter = 0;

        updateMuxes();

        //this is a micro second TIMER
        //TIMER set to 5ms
        commonTIMER.setInterval(5 * 1000ul);

        //start this TIMER
        commonTIMER.enableRestartTIMER();

        mState = READ_TARGET;
      }
      break;

    //========================
    case READ_TARGET:
      {
        //============
        //condition returned: STILLtiming, EXPIRED or TIMERdisabled
        //is it time to test the next cable wire ?
        if (commonTIMER.checkTIMER() == EXPIRED)
        {
          //a "call" takes about 500us per wire, 16 wire cable takes 1.28 seconds to test
          checkConnection(targetCable);

          //============
          //are we finished testing this cable ?
          if (txCounter == 0 && rxCounter == 0)
          {
            //we are finished with the TIMER
            commonTIMER.disableTIMER();

            Serial.println("\nTarget cable is read");

            mState = DISPLAY_TARGET;
          }
        }
      }
      break;

    //========================
    case DISPLAY_TARGET:
      {
        displayObject(targetCable);

        mState = COMPARE_CABLES;
      }
      break;

    //========================
    case COMPARE_CABLES:
      {
        Serial.println("\nComparing a Standard Cable to a Target Cable");

        //============
        //check the two cables
        for (byte x = 0; x < standardCableWireCount; x++)
        {
          Serial.print("Wire #");

          //============
          //add a space if the number is < 10
          if (x + 1 < 10)
          {
            Serial.print(" ");
          }

          Serial.print(x + 1);

          //============
          //is this wire the same ?
          if (standardCable[x] == targetCable[x])
          {
            Serial.println(" is Good");
          }

          //============
          else
          {
            Serial.println(" is Bad");
          }
        }

        Serial.println("Comparison Finished\n");
        Serial.println("Short press compares a target cable to the standard");

        //we are finished with the comparison
        mState = WAITING;
      }
      break;

    //========================
    case UPDATE_LCD:
      {
      }
      break;

    //========================
    case FINISHED:
      {
        //Do something
      }
      break;

  } //END of  switch/case

} //END of   checkMachine()


//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
//
//we have access to:
//object.validChange()    - checks to see if there was a valid state change
//object.pin              - input hardware pin number
//object.lastState        - the state the input was/is in
//object.switchTime       - unsigned long variable where we can save millis()

void checkSwitches()
{
  //========================================================================  startSwitch
  //was there a valid input change ?
  //condition returned: NOTvalidated (0), VALIDATED (1) or NOchange (2)
  if (startSwitch.validChange() == VALIDATED)
  {
    //========================
    //was this switch closed ?
    if (startSwitch.lastState == CLOSED)
    {
    }

    //========================
    //this switch was opened
    else
    {
      //================================================  Short Push
      //was this a short push ?
      if (millis() - startSwitch.switchTime <= shortPushTime)
      {
        //============
        if (mState == POWERUP_WAITING)
        {
          standardCableWireCount++;

          //============
          //have we reached the maximum ?
          if (standardCableWireCount > numberOfPins)
          {
            standardCableWireCount = 2;
          }
        }

        //============
        else
        {
          //initialize the target structure
          clearCableStruture(targetCable);

          mState = TEST_TARGET;
        }
      }

      //================================================  Long Push
      //was this a long push ?
      else if (millis() - startSwitch.switchTime >= longPushTime)
      {
        mState = TEST_STANDARD;
      }
    }
  } //END of   startSwitch

} //END of   checkSwitches()


//                                c h e c k C o n n e c t i o n (  )
//================================================^================================================
//
void checkConnection(Cable Object[])
{
  //================================================
  //         {-------------- R1 ---------------}
  // Vref ---[muxResistance]---[crimpResistance]---.--- Vmux
  //                                               |
  //                                               |
  //                                        [loadResistance]  R2  1% tolerance
  //                                               |
  //                                              GND
  //
  // Note: crimpResistance is actually the sum of two crimps,
  //       one on TX side and one on RX side
  //
  //voltage divider math:
  //R1 = ((R2 * Vref) / Vmux) - R2

  Vref = analogRead(referencePin);  //read twice for settling time
  Vref = analogRead(referencePin);

  Vmux = analogRead(muxPin);        //read twice for settling time
  Vmux = analogRead(muxPin);

  //we need an unsigned variable to detect a negative result
  long R1 = ((loadResistance * Vref) / Vmux) - loadResistance;

  //remove the mux resistance
  long crimpResistance = R1 - muxResistance;

  //========================
  //update the target structure
  Object[txCounter].rxPin = rxCounter;

  //========================
  if (crimpResistance < 15 )
  {
    //save a resistance of 0 ohms
    Object[txCounter].resistance = 0;

    //back to the first RX pin
    rxCounter = 0;

    //next wire to test
    txCounter++;

    //============
    //are we finished with this cable ?
    if (txCounter >= standardCableWireCount)
    {
      txCounter = 0;
    }

    updateMuxes();

    return;
  }

  //========================
  //resistance is between 0 and 255
  else if (crimpResistance < NOconnection)
  {
    //update resistance
    Object[txCounter].resistance = crimpResistance;

    //back to the first RX pin
    rxCounter = 0;

    //next wire to test
    txCounter++;

    //============
    //are we finished with this cable ?
    if (txCounter >= standardCableWireCount)
    {
      txCounter = 0;
    }

    updateMuxes();

    return;
  }

  //========================
  //next RX pin
  rxCounter++;

  //============
  //have we finished all the RX pins ?
  if (rxCounter >= standardCableWireCount)
  {
    //back to the first RX pin
    rxCounter = 0;

    //next TX pin
    txCounter++;

    //============
    //are we finished with this cable ?
    if (txCounter >= standardCableWireCount)
    {
      //back to the first TX pin
      txCounter = 0;
    }
  }

  updateMuxes();

} //END of   checkConnection()


//                                      u p d a t e M u x e s ( )
//================================================^================================================
//
void updateMuxes()
{
  //TX mux
  digitalWrite(s0TXpin, bitRead(txCounter, 0));
  digitalWrite(s1TXpin, bitRead(txCounter, 1));
  digitalWrite(s2TXpin, bitRead(txCounter, 2));
  digitalWrite(s3TXpin, bitRead(txCounter, 3));

  //RX mux
  digitalWrite(s0RXpin, bitRead(rxCounter, 0));
  digitalWrite(s1RXpin, bitRead(rxCounter, 1));
  digitalWrite(s2RXpin, bitRead(rxCounter, 2));
  digitalWrite(s3RXpin, bitRead(rxCounter, 3));

} // END of   updateMuxes()


//                              c l e a r C a b l e S t r u t u r e ( )
//================================================^================================================
//
void clearCableStruture(Cable Object[])
{
  //========================
  //update target structure with defaults
  for (byte x = 0; x < numberOfPins; x++)
  {
    //defaults to no connection
    Object[x].rxPin = x;
    Object[x].resistance = NOconnection;
  }

} //END of   clearCableStruture()


//                                   d i s p l a y O b j e c t ( )
//================================================^================================================
//
void displayObject(Cable Object[])
{
  Serial.println("TX                    RX");

  //========================
  //summarize what was read from the target cable
  for (byte x = 0; x < standardCableWireCount; x++)
  {
    Serial.print(x + 1);

    //============
    //should we add a space ?
    if (x + 1 < 10)
    {
      Serial.print(" ");
    }

    Serial.print(" ");

    //============
    //is this wire connected ?
    if (Object[x].resistance == NOconnection)
    {
      Serial.println("Not connected");
    }

    //============
    //this wire is connected
    else
    {
      Serial.print("Connected to  ---> ");

      //============
      //should we add a space ?
      if (Object[x].rxPin + 1 < 10)
      {
        Serial.print(" ");
      }

      Serial.print(Object[x].rxPin + 1);
      Serial.print(" Resistance ");
      Serial.println(Object[x].resistance);
    }
  }

} //END of   displayObject()


//
//================================================^================================================
//
1 Like

thanks and sorry ,i will delete the french section one

That looks super neat and organized! So Connector A is like the main spot where all the wires start, and the other two connectors (B and C) each get 2 wires. That’s just right for 4 wires total.

I don't think you can delete. I have locked the one in the French category.

2 Likes

@sahbi97 ,

Your two or more topics on the same or similar subject have been merged.

Also noted that you created the same topic in the French section of the forum, which @sterretje has locked.

Please do not duplicate your questions as doing so wastes the time and effort of the volunteers trying to help you as they are then answering the same thing in different places.

Please create one topic only for your question and choose the forum category carefully. If you have multiple questions about the same project then please ask your questions in the one topic as the answers to one question provide useful context for the others, and also you won’t have to keep explaining your project repeatedly.

Repeated duplicate posting could result in a temporary or permanent ban from the forum.

Could you take a few moments to Learn How To Use The Forum

It will help you get the best out of the forum in the future.

Thank you.

1 Like

Thank you for your message, and I sincerely apologize for the confusion and duplicate posts.

This is actually my first time using the Arduino forum, and I wasn’t fully aware of the proper way to post questions or how the forum works. I really appreciate your patience and the guidance you’ve provided.

1 Like

Hello everyone,
I’d like to thank you all for your help in bringing my project to life—it’s almost complete now.
I’ve successfully completed the prototyping phase of my "Cable Harness Tester" and will soon move on to designing the PCB.

However, I’m still facing an issue with the I2C 16x2 LCD display.
It works at 3.3V, but the display is too dim. I searched online and found that some people use a TXS0108E level shifter to convert the voltage to 5V. I tried that, but I'm still only seeing a row of solid blocks on the screen.



with TXS0108E level shifter


without level shifter TXS0108E , using 3.3V esp32 pin

  • Show us your current sketch as it is now.

  • Show us your current schematic.

1 Like
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// MUX Control Pins
const int SENDER_S0 = 18, SENDER_S1 = 19, SENDER_S2 = 23, SENDER_S3 = 5;  // Sender MUX
const int RECV_S0 = 12, RECV_S1 = 13, RECV_S2 = 14, RECV_S3 = 15;         // Receiver MUX

// Signal Pins
const int SENDER_SIG = 25;   // Sender MUX COM pin
const int RECEIVER_SIG = 26; // Receiver MUX COM pin

// Button Pins
const int START_BUTTON_PIN = 4;
const int RESET_BUTTON_PIN = 27;

// Test Parameters
const int NUM_WIRES = 4;     // Adjust based on your harness
const int DELAY_µS = 500;    // Signal stabilization delay

bool testRunning = false;
bool testCompleted = false;

// LCD setup: I2C address is usually 0x27 or 0x3F — adjust if needed
LiquidCrystal_I2C lcd(0x27, 16, 2);

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

  // MUX control pins
  pinMode(SENDER_S0, OUTPUT); pinMode(SENDER_S1, OUTPUT);
  pinMode(SENDER_S2, OUTPUT); pinMode(SENDER_S3, OUTPUT);
  pinMode(RECV_S0, OUTPUT); pinMode(RECV_S1, OUTPUT);
  pinMode(RECV_S2, OUTPUT); pinMode(RECV_S3, OUTPUT);

  // Signal pins
  pinMode(SENDER_SIG, OUTPUT);
  pinMode(RECEIVER_SIG, INPUT_PULLDOWN);

  // Button pins
  pinMode(START_BUTTON_PIN, INPUT_PULLDOWN);
  pinMode(RESET_BUTTON_PIN, INPUT_PULLDOWN);

  // LCD init
  Wire.begin(21, 22);  // ESP32 I2C pins
  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Cable Tester");
  lcd.setCursor(0, 1);
  lcd.print("Ready");
  
  Serial.println("\nCable Harness Tester Ready\n");
}

void setMuxChannel(int channel, int s0, int s1, int s2, int s3) {
  digitalWrite(s0, bitRead(channel, 0));
  digitalWrite(s1, bitRead(channel, 1));
  digitalWrite(s2, bitRead(channel, 2));
  digitalWrite(s3, bitRead(channel, 3));
}

void testHarness() {
  int faults = 0;

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Testing...");

  for (int wire = 0; wire < NUM_WIRES; wire++) {
    setMuxChannel(wire, SENDER_S0, SENDER_S1, SENDER_S2, SENDER_S3);
    digitalWrite(SENDER_SIG, HIGH);
    delayMicroseconds(DELAY_µS);

    bool connectionFound = false;

    for (int recvChan = 0; recvChan < NUM_WIRES; recvChan++) {
      setMuxChannel(recvChan, RECV_S0, RECV_S1, RECV_S2, RECV_S3);
      delayMicroseconds(100);

      if (digitalRead(RECEIVER_SIG)) {
        if (wire == recvChan) {
          connectionFound = true;
        } else {
          Serial.printf("SHORT/CROSS-WIRING: Wire %d → %d\n", wire + 1, recvChan + 1);
          faults++;
        }
      }
    }

    if (!connectionFound) {
      Serial.printf("OPEN: Wire %d\n", wire + 1);
      faults++;
    }

    digitalWrite(SENDER_SIG, LOW);
  }

  lcd.setCursor(0, 1);
  if (faults == 0) {
    Serial.println("\nHARNESS OK - ALL CONNECTIONS GOOD");
    lcd.print("Status: OK");
  } else {
    Serial.printf("\nTEST FAILED - %d FAULTS DETECTED\n", faults);
    lcd.print("Faults: ");
    lcd.print(faults);
  }
}

void loop() {
  if (digitalRead(START_BUTTON_PIN) == HIGH && !testRunning) {
    testRunning = true;
    Serial.println("\nStarting new test...");
    testHarness();
    testCompleted = true;
    testRunning = false;
  }

  if (digitalRead(RESET_BUTTON_PIN) == HIGH && testCompleted) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Cable Tester");
    lcd.setCursor(0, 1);
    lcd.print("Ready");
    Serial.println("\nSystem reset. Ready for new test.");
    testCompleted = false;
    delay(500); // debounce
  }

  delay(100); // debounce
}



Hey everyone, I tested it again today and ran the I2C scanner. This time, it showed different results. However, it did work when I powered the device with 3.3V and used address 0x27.

  • Highly recommend you use an OLED Display Module 128x64 Pixel SH1106 with the ESP.

1 Like

Thanks Larry , i will get the SSD1306, it support 3.3v i reckon

Yes

1 Like
  • Are you getting the SH1106 or the SSD1306 ?

Example for the SH1106

//********************************************^************************************************
//  OLED_Counter.ino
//
//  LarryD
//  Version   YY/MM/DD     Comments
//  =======   ========     ===============================================
//  1.00      22/04/21     Running code
//
//

//https://lastminuteengineers.com/oled-display-arduino-tutorial/

#include <Wire.h>
//#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>

/* Uncomment the initialize the I2C address , uncomment only one, If you get a totally blank screen try the other*/
#define i2c_Address 0x3c //initialize with the I2C addr 0x3C Typically eBay OLED's
//#define i2c_Address 0x3d //initialize with the I2C addr 0x3D Typically Adafruit OLED's


//********************************************^************************************************

const byte heartbeatLED     = 13;

#define SCREEN_WIDTH         128   //OLED display width,  in pixels
#define SCREEN_HEIGHT         64   //OLED display height, in pixels
#define OLED_RESET            -1   //QT-PY / XIAO

Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

unsigned long counter       = 0;

//timing stuff
unsigned long heartbeatTime;
unsigned long displayTime;


//                                       s e t u p ( )
//********************************************^************************************************
//
void setup()
{
  Serial.begin(115200);

  pinMode(heartbeatLED, OUTPUT);

  delay(250); // wait for the OLED to power up
  display.begin(i2c_Address, true); // Address 0x3C default

  //display.display();
  //delay(2000);

  display.clearDisplay();

  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(0, 0);

} //END of   setup()


//                                        l o o p ( )
//********************************************^************************************************
//
void loop()
{
  //*********************************                         heartbeat TIMER
  //is it time to toggle the heartbeatLED (every 500ms)?
  if (millis() - heartbeatTime >= 250ul)
  {
    //restart this TIMER
    heartbeatTime = millis();

    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

  //*********************************                         displayTime TIMER
  //is time to update the display ?
  if (millis() - displayTime >= 1000ul)
  {
    //restart this TIMER
    displayTime = millis();

    //clear SSD1306 display
    display.clearDisplay();

    //************************************
    display.setTextSize(1);
    display.setCursor(0, 0);

    //Size 1 line  000000000111111111122
    //21 chars     123456789012345678901
    //Example      ..OLED Counter Demo..
    display.print("  OLED Counter Demo  ");

    //************************************
    //display.setCursor(0,9);
    //Size 1 line is  000000000111111111122
    //21 characters   123456789012345678901
    //display.print ("ABCDEFGHIJKLMNOPQRSTU");

    //************************************
    display.setTextSize(2);
    //2 = current text size, 16 is the pixel row we want to postion to
    display.setCursor(centering(counter, 2), 16);

    //Size 2 line     0000000001
    //10 characters   1234567890
    //Example           100000
    display.print(counter++);

    //************************************
    //degree symbol
    //using CP437 ASCII
    //display.cp437(true);
    //display.write(248);

    //************************************
    //  display.setTextSize(2);
    //  display.setCursor(0,16);
    //  display.setCursor(0,32);
    //  //temperature
    //  //Size 2 line  0000000001
    //  //10 chars     1234567890
    //  //Example      ABCDEFGHIJ
    //  display.print("ABCDEFGHIJ");

    //************************************
    display.display();
  }

  //************************************
  //other non blocking code goes here
  //************************************

} //END of   loop()


//                                 c o u n t D i g i t s ( )
//********************************************^************************************************
//return the number of digits in a number
byte countDigits(int num)
{
  byte count = 0;

  while (num)
  {
    num = num / 10;
    count++;
  }

  return count;

} //END of   countDigits()


//                                    g e t D i g i t ( )
//********************************************^************************************************
//return the selected digit
byte getDigit(unsigned int number, int digit)
{
  for (int i = 0; i < digit - 1; i++)
  {
    number = number / 10;
  }

  return number % 10;

} //END of   getDigit()


//                                   c e n t e r i n g ( )
//********************************************^************************************************
//return the position to print the MSD
byte centering(unsigned long number, byte textSize)
{
  byte count = 0;
  byte charaterCellWidth = 0;

  //a basic character is 5X7, we must scale for this text size
  charaterCellWidth = (5 * textSize) + 1;

  //number of digits in our number
  while (number)
  {
    number = number / 10;
    count++;
  }

  //center location where the MSD character will be displayed
  return (SCREEN_WIDTH / 2 - (charaterCellWidth * count / 2));

} //END of   centering()

//

Example for the SSD1306

//********************************************^************************************************
//  OLED_Counter.ino
//
//  LarryD
//  Version   YY/MM/DD     Comments
//  =======   ========     ===============================================
//  1.00      22/04/21     Running code
//
//

//https://lastminuteengineers.com/oled-display-arduino-tutorial/

#include <Wire.h>
//#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//********************************************^************************************************

const byte heartbeatLED     = 13;

#define SCREEN_WIDTH          128   //OLED display width,  in pixels
#define SCREEN_HEIGHT          64   //OLED display height, in pixels


//SSD1306 display
//size 1 is 5X7   pixels therefore, 6X8   to acount for spacing,  21 characters per line
//size 2 is 10X14 pixels therefore, 11X15 to account for spacing, 10 characters per line
//size 4 is 20X28 pixels therefore, 21X29 to account for spacing,  5 characters per line

//Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

unsigned long counter       = 0;

//timing stuff
unsigned long heartbeatTime;
unsigned long displayTime;


//                                       s e t u p ( )
//********************************************^************************************************
//
void setup()
{
  Serial.begin(115200);

  pinMode(heartbeatLED, OUTPUT);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
  {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }

  display.clearDisplay();

  //display.setTextColor(textColor, backgroundColor);
  display.setTextColor(WHITE, BLACK);

} //END of   setup()


//                                        l o o p ( )
//********************************************^************************************************
//
void loop()
{
  //*********************************                         heartbeat TIMER
  //is it time to toggle the heartbeatLED (every 500ms)?
  if (millis() - heartbeatTime >= 500ul)
  {
    //restart this TIMER
    heartbeatTime = millis();

    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

  //*********************************                         displayTime TIMER
  //is time to update the display ?
  if (millis() - displayTime >= 250ul)
  {
    //restart this TIMER
    displayTime = millis();

    //clear SSD1306 display
    display.clearDisplay();

    //************************************
    display.setTextSize(1);
    display.setCursor(0, 0);

    //Size 1 line is  000000000111111111122
    //21 characters   123456789012345678901
    //Example         ..OLED Counter Demo..
    display.print("  OLED Counter Demo  ");

    //************************************
    //display.setCursor(0,9);
    //Size 1 line is  000000000111111111122
    //21 characters   123456789012345678901
    //display.print ("ABCDEFGHIJKLMNOPQRSTU");

    //************************************
    display.setTextSize(2);
    //2 = current text size, 16 is the pixel row we want to postion to
    display.setCursor(centering(counter, 2), 16);

    //Size 2 line     0000000001
    //10 characters   1234567890
    //Example           100000
    display.print(counter++);

    //************************************
    //degree symbol
    //using CP437 ASCII
    //display.cp437(true);
    //display.write(248);

    //************************************
    //  display.setTextSize(2);
    //  display.setCursor(0,16);
    //  display.setCursor(0,32);
    //  //temperature
    //  //Size 2 line is  0000000001
    //  //10 characters   1234567890
    //  //Example         ABCDEFGHIJ
    //  display.print("ABCDEFGHIJ");

    //************************************
    display.display();
  }

  //************************************
  //other non blocking code goes here
  //************************************


} //END of   loop()


//                                 c o u n t D i g i t s ( )
//********************************************^************************************************
//return the number of digits in a number
byte countDigits(int num)
{
  byte count = 0;

  while (num)
  {
    num = num / 10;
    count++;
  }

  return count;

} //END of   countDigits()


//                                    g e t D i g i t ( )
//********************************************^************************************************
//return the selected digit
byte getDigit(unsigned int number, int digit)
{
  for (int i = 0; i < digit - 1; i++)
  {
    number = number / 10;
  }

  return number % 10;

} //END of   getDigit()


//                                   c e n t e r i n g ( )
//********************************************^************************************************
//return the position to print the MSD
byte centering(unsigned long number, byte textSize)
{
  byte count = 0;
  byte charaterCellWidth = 0;

  //a basic character is 5X7, we must scale for this text size
  charaterCellWidth = (5 * textSize) + 1;

  //number of digits in our number
  while (number)
  {
    number = number / 10;
    count++;
  }

  //center location where the MSD character will be displayed
  return (SCREEN_WIDTH / 2 - (charaterCellWidth * count / 2));

} //END of   centering()

//