Arduino master to arduino slave code not working correctly

i have a one master four slave arduino code, communicating over i2c.

communication goes as follows:
master transmits <command,datain>
master requests from slave
slave responds <success, dataout>

however, this doesn't work. i have weird issues where they lock up, slave doesnt recieve command, slave doesnt transmit command, etc. i have removed all non-essential code and it still doesnt work reliably. it is not a hardware issue, as i can simply transmit and have slave recieve for hours without issue. the problem seems to be the request from (atleast i think?). here are the transmit and receive functions.
my question is: what is wrong/could be wrong with my code to cause this issue
EDIT EDIT EDIT:
New Problem:
so I've narrowed my problem down to an issue with trying to have the device have both master and slave capabilities. so i took a new approach, and now i can get it to work perfectly as either master or slave, but not both. i know you cant do both at the same time, but i had assumed you could switch between them, in an OR configuration. however, i am having issues with that. is supposed to select between master and slave mode. however Wire.onReceive(receiveEvent) & Wire.onRequest(requestEvent) only work when initialized in the setup routine, and once the device is switched into master mode, it ceased ever being able to function as a slave (until it is reset). does anyone know a workaround to this? microcontroller is a SAMD21G18A. Its on a custom board, so using a second sercom is not possible without a revised board.

EDIT EDIT EDIT:

Modified a module for:

#define slaveSDA 11
#define slaveSCL 13

still doesnt work as slave. tried both 6K and 1K pullups. seems to just lockup after a short while.

so i tried another approach: hardcoding the interface into the variant file... and it works?! so i think i have pretty much proven that onReceive and onRequest only work in the hard defined i2c pins?? why might this be? is there a workaround? (modified varient file in comments)

here is the full code, not just code snippets.

SLAVE:

/*********************************************************************************************************************

List of accepted commands:

<matrix,set1,set2,set3,set4> - sets the matrix. returns either <success> or <fail,status1,status2,status3,status4>

<matrixscalpel,pin,seto> - sets a single channel of the matrix. returns either <success> or <fail>

<powerset,float voltage> - sets the module voltage. also turns off voltage. returns either <success,difference> or <fail>

<powertoggle,bool> - toggles power on or off. returns either <success> or <fail>

<currmeasure> - measures the current draw of the V_ADJ supply. returns either <success, float current> or <fail>

<*idn?> - module identification. returns < firmare_version, hardware_version>

<adcread,pin> - reads the ADC value of a pin. returns either <success, float adcvalue> or <fail>

<dacset,pin,float voltage> - sets the DAC voltage level of a pin. can be 0 to 5.5V. returns either <success> or <fail>

<selftest,status> - this returns the self test status, either <success> or <fail>, simply returns the status of a previous self test.

<selftest,runtest> - this returns the self test status, either <success> or <fail>, but also runs a full new self test of the module.

<resetbreaker> - resets the MIC2045 circuit breaker. returns either <success> or <fail>
*********************************************************************************************************************/
#include <Wire.h>
#include <Adafruit_SleepyDog.h>

#define FIRMWARE_VERSION "2.0"
#define HARDWARE_VERSION "B"

//enable the digital pins
#define en_dio 7

//error LED
#define faultLED A5

//i2c data interupt
#define I2C_DATA_INCOMING_INTERUPT 24

bool selfTest = true; //default as passed self test.

//#define Serial SerialUSB

String responseToSend; // This will hold the response to be sent

String command = "";

/*********************************************************************************************************************
Setup and initilization function
*********************************************************************************************************************/

void setup() {

  Watchdog.enable(10000); // turn on watchdog timer with 10 seconds

  Serial.begin(9600);
//  while (!Serial) delay(10);     // pause the serial port

  //set pinmode of fault LED to output
  pinMode(faultLED,OUTPUT);

  // Initial state of selfTest is true
  selfTest = true;

  // Call main self testing function
  //just calls all the individual self test and initilization functions
  //this function and call is mandantory. remove it, and the code wont work.
  selfTestMain();

  switchI2CMode(false); //as slave

  //Wire.begin(0x20);                // join i2c bus with address 0x20
  //Wire.onReceive(receiveEvent); // register event
  //Wire.onRequest(requestEvent); // Register the onRequest event handler

  //pinMode(I2C_DATA_INCOMING_INTERUPT,INPUT);

  pinMode(en_dio,OUTPUT);
  digitalWrite(en_dio,HIGH);

}

void loop() {

  Watchdog.reset(); //resets the watchdog timer. basically, this makes sure the code doesnt lock up.

}

void switchI2CMode(bool asMaster) {
  Wire.end(); // End current I2C operation to reconfigure
  
  if (asMaster) {
    // Configure as I2C master
    Wire.begin(); // Start I2C as master
 }
  
  else {
    // Configure as I2C slave
    Wire.begin(0x20); // join i2c bus with address 0x20 as a slave 
    Wire.onReceive(receiveEvent); // register event
    Wire.onRequest(requestEvent); // Register the onRequest event handler
  }
}

/*********************************************************************************************************************
just runs the setup and self test functions.
this function MUST be called in setup() otherwise the rest of the code wont work
this is because this code also initilizes all the ADC DAC ETC objects in the code
*********************************************************************************************************************/

void selfTestMain(){
  // Call setup functions and check their return values
  if (setupPinMatrix() != 0) selfTest = false;  // Set selfTest to false if setupPinMatrix fails

  if (setupVoltage() != 0) selfTest = false;  // Set selfTest to false if setupVoltage fails

  if (adcPinSetup() != 0) selfTest = false;  // Set selfTest to false if adcPinSetup fails
        
  if (dacPinSetup() != 0) selfTest = false;  // Set selfTest to false if dacPinSetup fails

  // Call the selfTestPinMatrix and check its return value
  //this pretty much is the main self test routine
  if (selfTestPinMatrix() != 0) selfTest = false;  // Set selfTest to false if selfTestPinMatrix fails

//  Serial.println("testy");

  digitalWrite(faultLED,!selfTest); //turn fault LED ON if there is an error
}

/*********************************************************************************************************************
i2c Slave Request and Recieve functions
*********************************************************************************************************************/

// Function to run when data requested from master
void requestEvent() {

  Serial.println("In Request");

    Serial.print("Command Sent:  ");
    Serial.println(responseToSend);

    String responseWithTermination = responseToSend + "\n";
    Wire.write(responseWithTermination.c_str(), responseWithTermination.length()); // Send the response
}


// I2C communication function for receiving data
void receiveEvent(int howMany) {

  Serial.println("In Recieve");

    if (Wire.available() > 0) {
        command = "";
        while (Wire.available()) {
            char c = Wire.read();
            command += c;
        }

        Serial.print("Command Reieved:  ");
        Serial.println(command);

        responseToSend = processCommand(command); // Process the command and store the response
    }
}



/*********************************************************************************************************************
i2c command handling functions
*********************************************************************************************************************/

// Process received command
String processCommand(String command) {

  delay(10); // Adjust this delay based on the expected processing time

  //return "<success,1.0>";

    //give priority to selftest commands. this is important as the next condition voids the main command selection if self test is in error.
    if (command.startsWith("<selftest,")) return processSelfTestCommand(command);

    //if it has failed a self test, do not proceed. theoretically the master should not have proceeded with a module failing a self test, so this is basically redundant.
    //however, with the watchdog, it could pass an initial self test and fail the next without master knowing. this is an edge case but worth noting.
    if (selfTest == false) return "<fail,selftest>";

    //process which command was recieved and call the appropriate function to interpret.
    if (command.startsWith("<matrix,")) return processSetMatrixCommand(command);
        
    else if (command.startsWith("<matrixscalpel,")) return processScalpelMatrixCommand(command);
        
    else if (command.startsWith("<powerset,")) return processSetPowerCommand(command);
        
    else if (command.startsWith("<powertoggle,")) return processTogglePowerCommand(command);

    else if (command.startsWith("<dacset,")) return processDacSetCommand(command);

    else if (command.startsWith("<adcread,")) return processAdcReadCommand(command);

    else if (command == "<currmeasure>") return processCurrentMeasureCommand();

    else if (command == "<resetbreaker>") return processCircuitBreakerCommand();
    
    else if (command == "<*idn?>") return processIdnCommand();

    else return "<fail,Invalid Command>";

}

/*********************************************************************************************************************
Set Matrix Command
<matrix,set1,set2,set3,set4> - sets the matrix. returns either <success> or <fail,status1,status2,status3,status4>
*********************************************************************************************************************/

// Command processing functions
String processSetMatrixCommand(String command) {
    // Parse the command
    //int matrix = command.charAt(8) - '0'; // assuming single digit matrix number
    int matrix = 1;
    int lastComma = command.indexOf(',', 7); // Start after "<matrix,"
    int nextComma;
    String settings[4];
    uint8_t status[4];

    for (int i = 0; i < 4; i++) {
      nextComma = command.indexOf(',', lastComma + 1);
    
      if (nextComma == -1) { // If it's the last setting
        settings[i] = command.substring(lastComma + 1, command.length() - 1); // Exclude '>'
      } 
      else {
        settings[i] = command.substring(lastComma + 1, nextComma);
      }

      status[i] = setPinMatrix(matrix, 1, settings[i]); //1 means external mode

      lastComma = nextComma;
   //   nextComma = command.indexOf(',', lastComma + 1);
      matrix++;
    }

    // Check status of each call and form response
    String response = "<fail,";
    bool success = true;
    for (int i = 0; i < 4; i++) {
        if (status[i] != 0) {
            success = false;
            response += String(status[i]) + ",";
        }
    }
    response = success ? "<success>" : response.substring(0, response.length() - 1) + ">";
    return response;
}

/*********************************************************************************************************************
Set Matrix Scalpel Command
<matrixscalpel,pin,seto> - sets a single channel of the matrix. returns either <success> or <fail>
*********************************************************************************************************************/

String processScalpelMatrixCommand(String command) {
    // Remove command identifier and split the remaining part
    String commandBody = command.substring(15, command.length() - 1); // Remove "<matrixscalpel," and '>'

    // Find the comma separating the values
    int commaIndex = commandBody.indexOf(',');

    // Extract PIN and SETO
    int pin = commandBody.substring(0, commaIndex).toInt();
    String seto = commandBody.substring(commaIndex + 1);

    // Call setPinMatrix function
    int status = setPinMatrix(pin, 1, seto); //1 means external mode

    // Form the response based on the status
    String response = (status == 0) ? "<success>" : "<fail>";
    return response;
}

/*********************************************************************************************************************
Set Voltage Level Command
<powerset,float voltage> - sets the module voltage. also turns off voltage. returns either <success,difference> or <fail>
*********************************************************************************************************************/

String processSetPowerCommand(String command) {
    // Parse the command
    int commaIndex = command.indexOf(',');
    float voltage = command.substring(commaIndex + 1).toFloat();

    // Call setVoltage and check the status
    float status = setVoltage(voltage);
    String response = (status == -1) ? "<fail>" : "<success," + String(status) + ">";
    return response;
}

/*********************************************************************************************************************
Toggle Power Command
<powertoggle,bool> - toggles power on or off. returns either <success> or <fail>
*********************************************************************************************************************/

String processTogglePowerCommand(String command) {
    // Extract the boolean part of the command
    int commaIndex = command.indexOf(',');
    String boolStr = command.substring(commaIndex + 1, command.length() - 1); // Exclude '>'

    // Convert the extracted string to boolean
    bool state = boolStr == "true";

    // Call voltageToggle and check the status
    int status = voltageToggle(state);

    // Form the response based on the status
    String response = (status == 0) ? "<success>" : "<fail>";
    return response;
}

/*********************************************************************************************************************
Reset Circuit Breaker Command
<resetbreaker> - resets the MIC2045 circuit breaker. returns either <success> or <fail>
*********************************************************************************************************************/

String processCircuitBreakerCommand() {
    // Call the voltageResetCircuitBreaker function
    int status = voltageResetCircuitBreaker(); //voltageResetCircuitBreaker() doesn't require arguments

    // Form the response based on the status
    String response = (status == 0) ? "<success>" : "<fail>";
    return response;
}

/*********************************************************************************************************************
ADC Read Command
<adcread,pin> - reads the ADC value of a pin. returns either <success, float adcvalue> or <fail>
*********************************************************************************************************************/

String processAdcReadCommand(String command) {
    // Parse the command
    uint8_t pin = command.substring(command.indexOf(',') + 1).toInt();

    // Call readAdcValue and check the status
    float adcValue = adcGetPinValue(pin);
    String response = (adcValue == -1) ? "<fail>" : "<success," + String(adcValue) + ">";
    return response;
}

/*********************************************************************************************************************
DAC Voltage Set Command
<dacset,pin,float voltage> - sets the DAC voltage level of a pin. can be 0 to 5.5V. returns either <success> or <fail>
*********************************************************************************************************************/

String processDacSetCommand(String command) {
    // Extract pin number and voltage from the command
    int firstCommaIndex = command.indexOf(',');
    int secondCommaIndex = command.lastIndexOf(',');

    int pin = command.substring(firstCommaIndex + 1, secondCommaIndex).toInt();
    float voltage = command.substring(secondCommaIndex + 1, command.length() - 1).toFloat();

    // Call dacSetPinValue function and check the status
    float status = dacSetPinValue(pin, voltage);
    String response = (status == -1) ? "<fail>" : "<success>";
    return response;
}

/*********************************************************************************************************************
Identify Command
<*idn?> - module identification. returns < firmare_version, hardware_version>
*********************************************************************************************************************/

String processIdnCommand() {
  return "<" + String(FIRMWARE_VERSION) + "," + String(HARDWARE_VERSION) + ">";
}

/*********************************************************************************************************************
Current Measure Command
<currmeasure> - measures the current draw of the V_ADJ supply. returns either <success, float current> or <fail>
*********************************************************************************************************************/

String processCurrentMeasureCommand() {
    // Call the currentGetPinValue function
    float current = currentGetPinValue(); //currentGetPinValue() doesn't require arguments

    // Form the response based on the status
    String response = (current == -1) ? "<fail>" : "<success," + String(current) + ">";
    return response;
}

/*********************************************************************************************************************
Self Test Command

Accepts two commands:
<selftest,status> - this returns the self test status, either <success> or <fail>, simply returns the status of a previous self test.
<selftest,runtest> - this returns the self test status, either <success> or <fail>, but also runs a full new self test of the module.
*********************************************************************************************************************/

String processSelfTestCommand(String command) {
    
  //Return the current selfTest status
  if (command == "<selftest,status>") return selfTest ? "<success>" : "<fail>";

  //Run a new self test 
  else if (command == "<selftest,runtest>") {  
    selfTestMain();
    // Return the updated selfTest status
    return selfTest ? "<success>" : "<fail>";
  }

  //Handle invalid self test command
  else return "<fail,Invalid Command>";
}
#include <Adafruit_ADS1X15.h>
#include <Wire.h>

//ads1115 address
#define adcaddr 0x48

//create the ADC object
Adafruit_ADS1115 ads;

/*********************************************************************************************************************
ADC Setup Function. Initilizes the ADC and sets the gain value.
*********************************************************************************************************************/

int adcPinSetup(){

  switchI2CMode(true); //as master

  // Try to initialize!
  if (!ads.begin(adcaddr)) {
    Serial.println("Failed to initialize ADS.");
    switchI2CMode(false); //as slave
    return 1; //error
  }

  ads.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 0.1875mV (default)

  switchI2CMode(false); //as slave

  return 0; //no error

}

/*********************************************************************************************************************
Main ADC Get Value Function. Gets the ADC value on the pin you want. accpets pin values 1 - 4.
*********************************************************************************************************************/

float adcGetPinValue(int pin){

  int16_t adc = 1;

  float adcValue;

  switchI2CMode(true); //as master

  switch (pin){
    case 1:
    adcValue = ads.computeVolts(ads.readADC_SingleEnded(0));
    break;

    case 2:
    adcValue = ads.computeVolts(ads.readADC_SingleEnded(1));
    break;

    case 3:
    adcValue = ads.computeVolts(ads.readADC_SingleEnded(2));
    break;

    case 4:
    adcValue = ads.computeVolts(ads.readADC_SingleEnded(3));
    break;

    default:
    break;
  }

  switchI2CMode(false); //as slave

  return adcValue; //return the read value

}
//MCP4728 (default address 0x60) or MCP4728A4 (default address 0x64)

#include <Adafruit_MCP4728.h>
#include <Wire.h>

//mcp4728 address
#define dacaddr 0x60

//create the DAC object
Adafruit_MCP4728 mcp;

/*********************************************************************************************************************
DAC Setup Function. Initilizes the DAC and sets the VREF value.
*********************************************************************************************************************/

int dacPinSetup(){

  switchI2CMode(true); //as master

  // Try to initialize!
  if (!mcp.begin(dacaddr)) {
    Serial.println("Failed to find MCP4728 chip");
    switchI2CMode(false); //as slave
    return 1; //error
  }

  ads.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 0.1875mV (default)

  //initilize to known state
  //VDD = 5.5V
  // Vref = MCP_VREF_VDD, value = 0, 0V
  mcp.setChannelValue(MCP4728_CHANNEL_A, 0);
  mcp.setChannelValue(MCP4728_CHANNEL_B, 0);
  mcp.setChannelValue(MCP4728_CHANNEL_C, 0);
  mcp.setChannelValue(MCP4728_CHANNEL_D, 0);

  mcp.saveToEEPROM();

  switchI2CMode(false); //as slave

  return 0; //no error

}

/*********************************************************************************************************************
Main DAC SET Value Function. Sets the DAC value on the pin you want. accpets pin values 1 - 4.
*********************************************************************************************************************/

float dacSetPinValue(int pin, float voltage){

  // Calculate the voltage increment per level for a 12-bit DAC with VREF = 5.5V
  float increment = 5.5 / 4096;

  // Convert the desired voltage to the corresponding 12-bit value
  int voltageAdjusted = voltage / increment;

  // Ensure voltageAdjusted is within the 12-bit range
  if (voltageAdjusted < 0) {
    voltageAdjusted = 0;
  } else if (voltageAdjusted > 4095) {
    voltageAdjusted = 4095;
  }

  switch (pin){
    case 1:
    switchI2CMode(true); //as master
    mcp.setChannelValue(MCP4728_CHANNEL_A, voltageAdjusted);
    switchI2CMode(false); //as slave
    return 0;
    break;

    case 2:
    switchI2CMode(true); //as master
    mcp.setChannelValue(MCP4728_CHANNEL_B, voltageAdjusted);
    switchI2CMode(false); //as slave
    return 0;
    break;

    case 3:
    switchI2CMode(true); //as master
    mcp.setChannelValue(MCP4728_CHANNEL_C, voltageAdjusted);
    switchI2CMode(false); //as slave
    return 0;
    break;

    case 4:
    switchI2CMode(true); //as master
    mcp.setChannelValue(MCP4728_CHANNEL_D, voltageAdjusted);
    switchI2CMode(false); //as slave
    return 0;
    break;

    default:
    return 1;
  }
}
#include <Adafruit_MCP23X17.h>

//mcp23017 address
#define mcpioaddr 0x22

//create the IO Expander object
Adafruit_MCP23X17 mcpio;

//switch matrix pins low-power (microcontroller)
#define switch1 A4
#define switch2 8
#define switch3 A3
#define switch4 4
#define switch5 A2
#define switch6 3
#define switch7 A1
#define switch8 9

//switch matrix pins high-power (MCP23017)
#define annodeSwitch1 0 //out enable channel 1
#define annodeSwitch2 1 //gnd out channel 1
#define annodeSwitch3 2 //v_adj out channel 1
#define annodeSwitch4 3 //out enable channel 2
#define annodeSwitch5 4 //gnd out channel 2
#define annodeSwitch6 5 //v_adj out channel 2
#define annodeSwitch7 6 //out enable channel 3
#define annodeSwitch8 7 //gnd out channel 3
#define annodeSwitch9 8 //v_adj out channel 3
#define annodeSwitch10 9 //out enable channel 4
#define annodeSwitch11 10 //gnd out channel 4
#define annodeSwitch12 11 //v_adj out channel 4

/*********************************************************************************************************************
Pin Matrix Setup Function. Initilizes the Pin Matrix and sets the Default value (all OFF)
*********************************************************************************************************************/

int setupPinMatrix(){

  //setup pinmodes for the microcontroller controlled switches
  pinMode(switch1,OUTPUT);
  pinMode(switch2,OUTPUT);
  pinMode(switch3,OUTPUT);
  pinMode(switch4,OUTPUT);
  pinMode(switch5,OUTPUT);
  pinMode(switch6,OUTPUT);
  pinMode(switch7,OUTPUT);
  pinMode(switch8,OUTPUT);

  switchI2CMode(true); //as master

  // Try to initialize!
  if (!mcpio.begin_I2C(mcpioaddr)) {
    Serial.println("Failed to initialize IO Expander.");
    switchI2CMode(false); //as slave
    return 1; //error
  }

  mcpio.pinMode(annodeSwitch1, OUTPUT);
  mcpio.pinMode(annodeSwitch2, OUTPUT);
  mcpio.pinMode(annodeSwitch3, OUTPUT);
  mcpio.pinMode(annodeSwitch4, OUTPUT);
  mcpio.pinMode(annodeSwitch5, OUTPUT);
  mcpio.pinMode(annodeSwitch6, OUTPUT);
  mcpio.pinMode(annodeSwitch7, OUTPUT);
  mcpio.pinMode(annodeSwitch8, OUTPUT);
  mcpio.pinMode(annodeSwitch9, OUTPUT);
  mcpio.pinMode(annodeSwitch10, OUTPUT);
  mcpio.pinMode(annodeSwitch11, OUTPUT);
  mcpio.pinMode(annodeSwitch12, OUTPUT);

  //set the pin matrix to safe values. ADC is considered "safe" as it is high impedence and wont let too much current flow.
  //we want to guerentee that a overcurrent condition cannot happen. the switches can only handle 400mA RMS 600mA PEAK
  setPinMatrix(1,0,"off"); //0 for internal mode
  setPinMatrix(2,0,"off");
  setPinMatrix(3,0,"off");
  setPinMatrix(4,0,"off");

  switchI2CMode(false); //as slave

  return 0; //no error
  
}

/*********************************************************************************************************************
sets the pin matrix. accepts pin value 1-4 and string values VADJ GND DIO ADC DAC NULL OFF

For safety, default of everything is ADC mode, i.e. when set to V_ADJ mode, due to the switching archetecture, ADC must also be ON

mode. 0 = internal 1 = external. 0 is used exclusively for self-test.

truth table:

SW1	SW2	Anode1	Anode2	Anode3		Output
0	   1	  0	      0	      0		     off
0	   1	  1	      0	      0		     adc
1	   x	  1	      0	      0		     dac
0	   0	  1	      0	      0		     dio
0	   1	  1	      1	      0		     gnd
0	   1	  1	      0	      1		    v_adj

*********************************************************************************************************************/


int setPinMatrix(int pin, int mode, String seto){
  if (pin < 1 || pin > 4) return 1; //make sure pin is in range of 1-4

  uint8_t sw1;
  uint8_t sw2;
  uint8_t an1;
  uint8_t an2;
  uint8_t an3;


  if (seto == "null") return 0; //if pin value null exit function

  //set the 5 switch values according to the truth table.
  //these will then be applied to the appropriate switch pins.
  //Determine the switch states based on the mode
  if (seto == "off") {
      sw1 = 0; sw2 = 1; an1 = 0; an2 = 0; an3 = 0;
  } else if (seto == "adc") {
      sw1 = 0; sw2 = 1; an1 = 1; an2 = 0; an3 = 0;
  } else if (seto == "dac") {
      sw1 = 1; sw2 = 0; an1 = 1; an2 = 0; an3 = 0;
   } else if (seto == "dio") {
      sw1 = 0; sw2 = 0; an1 = 1; an2 = 0; an3 = 0;
  } else if (seto == "gnd") {
      sw1 = 0; sw2 = 1; an1 = 1; an2 = 1; an3 = 0;
  } else if (seto == "vadj") {
      sw1 = 0; sw2 = 1; an1 = 1; an2 = 0; an3 = 1;
  } else {
      return 1; // Invalid mode
  }

  if (mode == 0) an1 = 0;

  switchI2CMode(true); //as master

  switch (pin){
    case 1:
    //First, put the high-power switches in safety OFF
    mcpio.digitalWrite(annodeSwitch1, LOW);
    mcpio.digitalWrite(annodeSwitch2, LOW);
    mcpio.digitalWrite(annodeSwitch3, LOW);
    //Next, switch the low-power switches to desired position
    digitalWrite(switch1,sw1);
    digitalWrite(switch2,sw2);
    //Finally, switch the high-power switches to desired position
    mcpio.digitalWrite(annodeSwitch1, an1);
    mcpio.digitalWrite(annodeSwitch2, an2);
    mcpio.digitalWrite(annodeSwitch3, an3);
    switchI2CMode(false); //as slave
    return 0;
    break;

    case 2:
    //First, put the high-power switches in safety OFF
    mcpio.digitalWrite(annodeSwitch4, LOW);
    mcpio.digitalWrite(annodeSwitch5, LOW);
    mcpio.digitalWrite(annodeSwitch6, LOW);
    //Next, switch the low-power switches to desired position
    digitalWrite(switch3,sw1);
    digitalWrite(switch4,sw2);
    //Finally, switch the high-power switches to desired position
    mcpio.digitalWrite(annodeSwitch4, an1);
    mcpio.digitalWrite(annodeSwitch5, an2);
    mcpio.digitalWrite(annodeSwitch6, an3);
    switchI2CMode(false); //as slave
    return 0;
    break;

    case 3:
    //First, put the high-power switches in safety OFF
    mcpio.digitalWrite(annodeSwitch7, LOW);
    mcpio.digitalWrite(annodeSwitch8, LOW);
    mcpio.digitalWrite(annodeSwitch9, LOW);
    //Next, switch the low-power switches to desired position
    digitalWrite(switch5,sw1);
    digitalWrite(switch6,sw2);
    //Finally, switch the high-power switches to desired position
    mcpio.digitalWrite(annodeSwitch7, an1);
    mcpio.digitalWrite(annodeSwitch8, an2);
    mcpio.digitalWrite(annodeSwitch9, an3);
    switchI2CMode(false); //as slave
    return 0;
    break;

    case 4:
    //First, put the high-power switches in safety OFF
    mcpio.digitalWrite(annodeSwitch10, LOW);
    mcpio.digitalWrite(annodeSwitch11, LOW);
    mcpio.digitalWrite(annodeSwitch12, LOW);
    //Next, switch the low-power switches to desired position
    digitalWrite(switch7,sw1);
    digitalWrite(switch8,sw2);
    //Finally, switch the high-power switches to desired position
    mcpio.digitalWrite(annodeSwitch10, an1);
    mcpio.digitalWrite(annodeSwitch11, an2);
    mcpio.digitalWrite(annodeSwitch12, an3);
    switchI2CMode(false); //as slave
    return 0;
    break;

    default:
    switchI2CMode(false); //as slave
    return 1;
  }
}

/*********************************************************************************************************************
Pin Matrix Self Test Function.

Does the following test steps:

-turns output OFF
-puts low-power matrix into ADC mode
-puts high-power matrix into vadj mode
-sets voltage to 2.0V, turns supply ON, measures voltage and current.
-sets voltage to 3.0V, turns supply ON, measures voltage and current.
-sets voltage to 4.0V, turns supply ON, measures voltage and current.
-puts high-power matrix into GND mode
-measures GND value. should be 0.0

if all these are within expected values self test passed (return 0) if not its a fail (return 1)
*********************************************************************************************************************/

#define TOLERANCE 0.10  // Define the tolerance level
#define MAXCURRENT 0.05 //define max current. should be 0 as there is no load.

bool selfTestPinMatrix() {

    // Step 1: Turn output OFF
    voltageToggle(false);

    // Step 2 & 3: Set high-power matrix to VADJ mode, set low-power matrix into ADC mode
    setPinMatrix(1, 0, "vadj"); //0 for internal mode
    setPinMatrix(2, 0, "vadj");
    setPinMatrix(3, 0, "vadj");
    setPinMatrix(4, 0, "vadj");

    // Helper function to perform voltage test
    auto voltageTest = [](float testVoltage) {
        setVoltage(testVoltage);  // Set voltage
        voltageToggle(true);      // Turn power ON
        delay(1000);              // Wait for stabilization

        bool allChannelsPass = true;
        for (int channel = 1; channel <= 4; ++channel) {
            float measuredVoltage = adcGetPinValue(channel);  // Measure voltage on each channel

//            Serial.print("measuredVoltage:  ");
//            Serial.println(measuredVoltage);

            if (abs(measuredVoltage - testVoltage) > TOLERANCE) {
                allChannelsPass = false; // If any channel is out of tolerance, mark test as failed
                break;
            }
        }
        
        float measuredCurrent = currentGetPinValue();  // Measure current

//        Serial.print("measuredCurrent:  ");
//        Serial.println(measuredCurrent);

        voltageToggle(false);  // Turn power OFF

        // Check if all channels are within acceptable range and current is within limit
        return allChannelsPass && (measuredCurrent < MAXCURRENT);
    };

    // Step 4, 5, 6: Test with 2.0V, 3.0V, and 4.0V
    if (!voltageTest(2.0) || !voltageTest(3.0) || !voltageTest(4.0)) {
        return false;  // Test failed
    }

    // Step 7: Put high-power matrix into GND mode and measure
    setPinMatrix(1, 0, "gnd"); //0 for internal mode
    setPinMatrix(2, 0, "gnd");
    setPinMatrix(3, 0, "gnd");
    setPinMatrix(4, 0, "gnd");

    bool groundTestPass = true;
    for (int channel = 1; channel <= 4; ++channel) {
        float groundValue = adcGetPinValue(channel);  // Measure GND value on each channel
        if (abs(groundValue) > TOLERANCE) {
            groundTestPass = false; // If any channel is out of tolerance, mark test as failed
            break;
        }
    }

    // If all tests passed
    return groundTestPass && voltageTest(3.3) && voltageTest(3.0) && voltageTest(4.0);
}
#include "isl28022DL.h"

//power supply mosfets
#define mfet1 22
#define mfet2 2
#define mfet3 5
#define mfet4 11
#define mfet5 13
#define mfet6 10
#define mfet7 12
#define mfet8 6

//LOW = OFF
#define en_psu 23

//io expander + MIC2045-1 pins
#define pwrgdpsu 12 //this is basically not used / redundant. if we wanted to know if power is good, just use the ISL28022
#define en_psu_mic2045 13
#define faultpsu 14

//power supply monitor address i2c
//TB008 HWRev.A requires modified PCB for this address.
//without PCB modification address is 0x4F
#define addrPwrMonitor 0x40

//create power monitor object
power_isl28022 power_monitor(addrPwrMonitor);

const float VoltageLookupTable[][9] = {
    {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.2},
    {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.2600660066006601},
    {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.86006600660066},
    {0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.35},
    {0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.9499999999999997},
    {0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 2.01006600660066},
    {0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.61006600660066},
    {0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.44985835694051},
    {0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 2.04985835694051},
    {0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 2.10992436354117},
    {0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 2.7099243635411696},
    {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 2.1998583569405095},
    {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 2.799858356940509},
    {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 2.859924363541169},
    {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 3.45992436354117},
    {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.5933774834437084},
    {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 2.1933774834437085},
    {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 2.2534434900443685},
    {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 2.8534434900443686},
    {0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 2.3433774834437084},
    {0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 2.9433774834437085},
    {0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 3.003443490044368},
    {0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 3.603443490044368},
    {0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 2.4432358403842187},
    {0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 3.0432358403842183},
    {0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 3.103301846984878},
    {0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 3.703301846984878},
    {0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 3.1932358403842183},
    {0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 3.7932358403842192},
    {0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 3.8533018469848788},
    {0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.453301846984879},
    {0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.7881188118811882},
    {0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.3881188118811885},
    {0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 2.448184818481848},
    {0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 3.048184818481848},
    {0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.538118811881188},
    {0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 3.138118811881189},
    {0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 3.1981848184818484},
    {0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 3.798184818481849},
    {0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.637977168821698},
    {0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 3.2379771688216987},
    {0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 3.2980431754223587},
    {0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 3.8980431754223583},
    {0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 3.3879771688216973},
    {0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 3.9879771688216987},
    {0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 4.048043175422359},
    {0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 4.648043175422358},
    {0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 2.7814962953248967},
    {0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 3.381496295324897},
    {0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 3.4415623019255577},
    {0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 4.041562301925557},
    {0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 3.5314962953248967},
    {0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 4.131496295324898},
    {0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 4.191562301925557},
    {0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 4.791562301925557},
    {0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 3.6313546522654074},
    {0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 4.231354652265408},
    {0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 4.291420658866067},
    {0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 4.891420658866066},
    {0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 4.381354652265407},
    {0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 4.981354652265407},
    {0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 5.041420658866067},
    {0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.074201474201474},
    {0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.674201474201474},
    {0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 2.7342674808021337},
    {0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 3.334267480802134},
    {0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.8242014742014745},
    {0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 3.424201474201475},
    {0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 3.484267480802133},
    {0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 4.084267480802134},
    {0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.9240598311419834},
    {0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 3.5240598311419844},
    {0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 3.5841258377426435},
    {0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 4.184125837742644},
    {0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 3.674059831141983},
    {0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 4.274059831141983},
    {0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 4.3341258377426435},
    {0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 4.934125837742645},
    {0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 3.0675789576451824},
    {0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 3.6675789576451834},
    {0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 3.727644964245844},
    {0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 4.327644964245844},
    {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 3.8175789576451824},
    {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 4.417578957645183},
    {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 4.477644964245843},
    {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 5.077644964245844},
    {0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 3.9174373145856927},
    {0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 4.517437314585693},
    {0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 4.577503321186351},
    {0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 5.177503321186355},
    {0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 4.667437314585692},
    {0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.262320286082663},
    {0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 3.862320286082662},
    {0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 3.922386292683322},
    {0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 4.522386292683323},
    {0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 4.012320286082662},
    {0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 4.612320286082661},
    {0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 4.672386292683322},
    {0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 4.112178643023173},
    {0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 4.712178643023171},
    {0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 4.772244649623832},
    {0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 4.862178643023172},
    {0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 4.255697769526371},
    {0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 4.8556977695263726},
    {0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 4.915763776127032},
    {0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 5.00569776952637},
    {0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 5.1055561264668805},
    {1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.541747572815534},
    {1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 3.1417475728155333},
    {1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 3.2018135794161933},
    {1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 3.801813579416195},
    {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 3.291747572815534},
    {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 3.891747572815533},
    {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 3.951813579416194},
    {1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 4.5518135794161925},
    {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 3.391605929756045},
    {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 3.9916059297560453},
    {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 4.051671936356703},
    {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 4.6516719363567045},
    {1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 4.141605929756045},
    {1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 4.7416059297560444},
    {1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 4.801671936356704},
    {1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 3.5351250562592416},
    {1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 4.135125056259241},
    {1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 4.1951910628599025},
    {1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 4.795191062859902},
    {1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 4.2851250562592424},
    {1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 4.885125056259242},
    {1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 4.9451910628599025},
    {1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 4.384983413199751},
    {1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 4.9849834131997515},
    {1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 5.045049419800411},
    {1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 5.134983413199753},
    {1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.729866384696722},
    {1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 4.329866384696722},
    {1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 4.389932391297382},
    {1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 4.989932391297382},
    {1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 4.479866384696723},
    {1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 5.0798663846967225},
    {1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 5.139932391297382},
    {1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 4.579724741637232},
    {1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 5.179724741637232},
    {1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 4.723243868140431},
    {1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.015949047017008},
    {1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 4.6159490470170095},
    {1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 4.676015053617669},
    {1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 4.765949047017008},
    {1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 4.86580740395752},
    {1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 5.009326530460719},
};

/*********************************************************************************************************************
Programable PSU Setup Function. Initilizes the supply control pins & power monitor.
*********************************************************************************************************************/

int setupVoltage(){
  // Initialize MOSFET pins as outputs
  pinMode(mfet1, OUTPUT);
  pinMode(mfet2, OUTPUT);
  pinMode(mfet3, OUTPUT);
  pinMode(mfet4, OUTPUT);
  pinMode(mfet5, OUTPUT);
  pinMode(mfet6, OUTPUT);
  pinMode(mfet7, OUTPUT);
  pinMode(mfet8, OUTPUT);
  pinMode(en_psu,OUTPUT);

  switchI2CMode(true); //as master

  //io expander + MIC2045-1 pins set pinmode
//  mcpio.pinMode(pwrgdpsu, INPUT); //this is basically not used / redundant. if we wanted to know if power is good, just use the ISL28022
  mcpio.pinMode(en_psu_mic2045, OUTPUT);
  mcpio.pinMode(faultpsu, INPUT_PULLUP);

  //disable PSU
  digitalWrite(en_psu,LOW);

  //set voltage to a known value
  setVoltage(2.0);

  // Start up the ISL28022 Power monitor ICs
  power_monitor.begin();

  switchI2CMode(false); //as slave

  return 0;
}

/*********************************************************************************************************************
Voltage Set Function. 
Accepts a float value of the desired voltage. then, tries to find a combination of resistors that get close to the desired value.
This function also turns the supply OFF. it does not turn it back ON.
Returns the difference between the set and expected values, or -1 for error.
*********************************************************************************************************************/

float setVoltage(float desiredVoltage) { //no direct i2c

  digitalWrite(en_psu,LOW);


    if (desiredVoltage < 1.2 || desiredVoltage > 5.2) {
        Serial.println("Desired voltage out of range.");
        return -1;
    }

    int bestIndex = -1;
    float minDifference = 9999;

    for (int i = 0; i < sizeof(VoltageLookupTable) / sizeof(VoltageLookupTable[0]); ++i) {
        float thisVoltage = VoltageLookupTable[i][8];
        float thisDifference = abs(desiredVoltage - thisVoltage);

        if (thisDifference < minDifference && thisVoltage >= 1.2 && thisVoltage <= 5.2) {
            bestIndex = i;
            minDifference = thisDifference;
        }
    }

    if (bestIndex == -1) {
        Serial.println("No suitable resistor combination found.");
        return -1;
    }

    // Turn on/off MOSFETs based on the best combination
    digitalWrite(mfet1, VoltageLookupTable[bestIndex][0]);
    digitalWrite(mfet2, VoltageLookupTable[bestIndex][1]);
    digitalWrite(mfet3, VoltageLookupTable[bestIndex][2]);
    digitalWrite(mfet4, VoltageLookupTable[bestIndex][3]);
    digitalWrite(mfet5, VoltageLookupTable[bestIndex][4]);
    digitalWrite(mfet6, VoltageLookupTable[bestIndex][5]);
    digitalWrite(mfet7, VoltageLookupTable[bestIndex][6]);
    digitalWrite(mfet8, VoltageLookupTable[bestIndex][7]);

    return desiredVoltage - VoltageLookupTable[bestIndex][8];
}

/*********************************************************************************************************************
Voltage Toggle Function.
Performs a check to see if the PSU circuit breaker is in an error state (i.e. overcurrent, short circuit, etc.).
Then toggles the power supply according to the bool toggle state.
*********************************************************************************************************************/

//the minimum is always 32mS (MIC2045 limited)
#define wait_for_settle 100

int voltageToggle(bool toggle) {

    switchI2CMode(true); //as master

    // Check the fault status of the PSU
    if (mcpio.digitalRead(faultpsu) == LOW) {
        // Disable the PSU and return an error code
        digitalWrite(en_psu, LOW);
        mcpio.digitalWrite(en_psu_mic2045, LOW);
        switchI2CMode(false); //as slave
        return 1; // Error code
    }

    // Set the PSU on or off according to the toggle value
    if (toggle == true) {
        digitalWrite(en_psu, HIGH);
        mcpio.digitalWrite(en_psu_mic2045, HIGH);

        // Wait for PSU to settle
        delay(wait_for_settle);

        // Perform additional checks here after settling
        // Check if the PSU is still okay
        if (mcpio.digitalRead(faultpsu) == LOW) {
            // Disable the PSU and return an error code
            digitalWrite(en_psu, LOW);
            mcpio.digitalWrite(en_psu_mic2045, LOW);
            switchI2CMode(false); //as slave
            return 1; // Error code
        }
    } else {
        digitalWrite(en_psu, LOW);
        mcpio.digitalWrite(en_psu_mic2045, LOW);
    }

    switchI2CMode(false); //as slave

    return 0; // No error
}

/*********************************************************************************************************************
Reset Circuit Breaker Function

this function resets the circuit breaker (i.e. MIC2045) if there is overcurrent condition.
basically useless, however sudden capacitive loads can trigger the circuit breaker in error, so may be usefull.

simply does the following:
-turns ON (high) en_psu which is the switchers native enable
-toggles en_psu_mic2045 on - off - on
-waits wait_for_settle mS to let everything settle
-checks faultpsu for fault. LOW = error

if error, return 1

if no error, return 0
*********************************************************************************************************************/

int voltageResetCircuitBreaker() {

    switchI2CMode(true); //as master

    // Turn on the native PSU enable
    digitalWrite(en_psu, HIGH);

    // Toggle the MIC2045 enable pin
    mcpio.digitalWrite(en_psu_mic2045, HIGH);
    delay(10); // Short delay to ensure the state change is registered
    mcpio.digitalWrite(en_psu_mic2045, LOW);
    delay(10); // Short delay
    mcpio.digitalWrite(en_psu_mic2045, HIGH);

    // Wait for the system to settle
    delay(wait_for_settle);

    // Check for a fault condition
    if (mcpio.digitalRead(faultpsu) == LOW) {
        // If there's a fault, disable the PSU and return error
        digitalWrite(en_psu, LOW);
        mcpio.digitalWrite(en_psu_mic2045, LOW);
        switchI2CMode(false); //as slave
        return 1; // Error code
    }

    switchI2CMode(false); //as slave

    // If no fault, return 0 indicating success
    return 0;
}

/*********************************************************************************************************************
Current Measure Command. Simply measures the output of the PSU. does not care if PSU is on or not. use wisely and in context
perhaps add checks in the future. returning a -1 means it has failed, so we could use that. use the "circuit breaker" IC?
*********************************************************************************************************************/

float currentGetPinValue(){

  switchI2CMode(true); //as master

  float current = power_monitor.readRegister(CURRENTREG);

  switchI2CMode(false); //as slave

  return current;
  
}

MASTER CODE (simplified for testing, normally runs a GUI, but this is the actual code im using):

#include <Adafruit_GFX.h>    // Core graphics library
#include <SPI.h>             // This is needed for display
#include <Adafruit_ILI9341.h>
#include <Wire.h>            // This is needed for FT6206
#include <Adafruit_FT6206.h>
#include "bitmaps.h"
#include "isl28022DL.h"
#include <Adafruit_SleepyDog.h>

// Define the pin States
enum PinSetting { PIN_3V3, PIN_5V, PIN_GND, PIN_ADC, PIN_DAC, PIN_DIO_INPUT, PIN_DIO_OUTPUT, PIN_NULL, PIN_OFF };

//DIO Pins
#define DIO1 7
#define DIO2 6
#define DIO3 12
#define DIO4 13
#define DIO5 11
#define DIO6 5
#define DIO7 2
#define DIO8 0
#define DIO9 1
#define DIO10 3
#define DIO11 4
#define DIO12 A5
#define DIO13 8
#define DIO14 A3
#define DIO15 A2
#define DIO16 A1
const byte DIO_PINS[16] = {DIO1, DIO2, DIO3, DIO4, DIO5, DIO6, DIO7, DIO8, DIO9, DIO10, DIO11, DIO12, DIO13, DIO14, DIO15, DIO16};

//fan pins
#define fan_foo 43 //PA08
#define pwm_fan 44 //PA09 requires you to modify the line in variant.cpp in order to use PWM { PORTA, 9, PIO_COM, PIN_ATTR_PWM_E, No_ADC_Channel, TC0_CH1, NOT_ON_TIMER, EXTERNAL_INT_9 },

//power supply monitor address i2c
#define addr9v 0x4f
#define addr3v3 0x4e
#define addr5v5 0x4d

//create power monitor objects
power_isl28022 power_monitor_9v(addr9v);
power_isl28022 power_monitor_3v3(addr3v3);
power_isl28022 power_monitor_5v5(addr5v5);

//USB vs WALL selector pins (LTC4415 IC)
//IN1 is VJACK IN2 is VUSB. VJACK has priority and is allowed to supply more current. 2 Amp USB 4 Amp VJACK.
//stat indicators are low when conducting, i.e. if VJACK & USB plugged in, stat1 will be LOW, stat2 will be HIGH.
//WARN Pulls down when diode current exceeds its current limit or die temperature is close to thermal shutdown.
//PB31 and PB23 requires you to add the lines in variant.cpp in order to use. Add them at the bottom of the pin definitions.
#define warn1 47 //PB31 VJACK { PORTB, 31, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_15 },
#define warn2 48 //PB23 VUSB { PORTB, 23, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_7 },
#define stat1 46 //PA11 VJACK { PORTA, 11, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_11 },
#define stat2 41 //PB10 VUSB { PORTB, 10, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_10 },

/*********************************************************************************************************************
Setup and initilization function
*********************************************************************************************************************/

// Initialize the MCP23017
void setup() {

  delay(5000);

  pinModulesSetup();

//  Watchdog.enable(60000); // turn on watchdog timer with 60 seconds

  Wire.begin(); // Start I2C

  Serial.begin(9600);
//  while (!Serial) delay(10);     // pause the serial port
 
  Serial.println(F("master test"));

  // Start up the ISL28022 Power monitor ICs
  power_monitor_9v.begin();
  power_monitor_3v3.begin();
  power_monitor_5v5.begin();

  pinMode(pwm_fan,OUTPUT);
  analogWrite(pwm_fan,100);

  //setup USB vs WALL selector inputs
  pinMode(warn1,INPUT);
  pinMode(warn2,INPUT);
  pinMode(stat1,INPUT);
  pinMode(stat2,INPUT);

}

/*********************************************************************************************************************
Loop Function. Main code.
*********************************************************************************************************************/

void loop() {
//    Watchdog.reset();

    delay(2500);

    //Serial.println(setPinScalpel(1,"gnd"));

    //delay(2500);


    //Serial.println(setPinScalpel(2,"vadj"));

    //delay(2500);


    Serial.println(setModulePower(3.3,1));

    //delay(2500);

    Serial.println(toggleModulePower("true",1));

    delay(2500);


}
/*********************************************************************************************************************

List of accepted commands:

<matrix,set1,set2,set3,set4> - sets the matrix. returns either <success> or <fail,status1,status2,status3,status4>

<matrixscalpel,pin,seto> - sets a single channel of the matrix. returns either <success> or <fail>

<powerset,float voltage> - sets the module voltage. also turns off voltage. returns either <success,difference> or <fail>

<powertoggle,bool> - toggles power on or off. returns either <success> or <fail>

<currmeasure> - measures the current draw of the V_ADJ supply. returns either <success, float current> or <fail>

<*idn?> - module identification. returns < firmare_version, hardware_version>

<adcread,pin> - reads the ADC value of a pin. returns either <success, float adcvalue> or <fail>

<dacset,pin,float voltage> - sets the DAC voltage level of a pin. can be 0 to 5.5V. returns either <success> or <fail>

<selftest,status> - this returns the self test status, either <success> or <fail>, simply returns the status of a previous self test.

<selftest,runtest> - this returns the self test status, either <success> or <fail>, but also runs a full new self test of the module.

<resetbreaker> - resets the MIC2045 circuit breaker. returns either <success> or <fail>
*********************************************************************************************************************/

#include <Wire.h>

/*********************************************************************************************************************
Pin Modules Setup Code
*********************************************************************************************************************/
//all four pin modules share an address. They are selected using a digital pin going "HIGH". This triggers an interupt in the selected slave and lets it recieve commands.

uint8_t pinmoduleaddress;

#define pinmoduleaddress1 0x20
#define pinmoduleaddress2 0x21
#define pinmoduleaddress3 0x22
#define pinmoduleaddress4 0x23

#define pinmodule3 49 //PA07 { PORTA, 7, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_7 },
#define pinmodule2 50 //PA15 { PORTA, 15, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_7 },
#define pinmodule1 51 //PB00 { PORTB, 0, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_7 },
#define pinmodule4 52 //PB01 { PORTB, 1, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_7 },

void pinModulesSetup(){
  //Wire.begin(); // Start I2C
  pinMode(pinmodule1,OUTPUT);
  pinMode(pinmodule2,OUTPUT);
  pinMode(pinmodule3,OUTPUT);
  pinMode(pinmodule4,OUTPUT);
  selectModule(0); //deselect all modules
}

/*********************************************************************************************************************
Module Select Function
*********************************************************************************************************************/

int selectModule(int selection) {
  // Set all module pins to LOW initially
  digitalWrite(pinmodule1, LOW);
  digitalWrite(pinmodule2, LOW);
  digitalWrite(pinmodule3, LOW);
  digitalWrite(pinmodule4, LOW);

  // Select the appropriate module based on the input
  switch (selection) {
    case 1: digitalWrite(pinmodule1, HIGH); pinmoduleaddress = pinmoduleaddress1; break;
    case 2: digitalWrite(pinmodule2, HIGH); pinmoduleaddress = pinmoduleaddress2; break;
    case 3: digitalWrite(pinmodule3, HIGH); pinmoduleaddress = pinmoduleaddress3; break;
    case 4: digitalWrite(pinmodule4, HIGH); pinmoduleaddress = pinmoduleaddress4; break;
    case 0: return 0; // Deselection
    default: return 1; // Invalid selection, return error
  }
  return 0; // Successful selection
}

/*********************************************************************************************************************
Module Initilize Function
*********************************************************************************************************************/

String pinINITILIZE(){
  //this function will query and initilize all four pinmodules, checking for errors.
  //it will then return a message that shows the errors, if any.
  //perhaps should return "good" if all good, so that the program knows to halt if not good.
  String message = "Dummy Pin Initilize Pass";
  return message;
}

/*********************************************************************************************************************
ADC Read Command
<adcread,pin> - reads the ADC value of a pin. returns either <success, float adcvalue> or <fail>
*********************************************************************************************************************/

float pinADC(int pin) {

  if (pin < 1 || pin > 16) {
    Serial.println("Invalid pin number");
    return -1; // Early return on invalid pin
  }

  int moduleNumber = (pin - 1) / 4 + 1; // Determine module number
  int pinToSend = (pin - 1) % 4 + 1; // Normalize pin number for module
  selectModule(moduleNumber);

  // Send ADC read command to the correct slave module
  String command = "<adcread," + String(pinToSend) + ">";
  Wire.beginTransmission(pinmoduleaddress);
  Wire.write(command.c_str());
  Wire.endTransmission();

  delay(200); // Increase the delay to ensure the slave has time to prepare the response

  // Request and read response from slave
  Wire.requestFrom(pinmoduleaddress, 32); // Request enough bytes to cover the expected response
  String response = "";
  while (Wire.available()) {
    char c = Wire.read();
    response += c;
    if (c == '\n') break; // Stop reading if termination character is found
  }

  response.trim(); // Remove any extraneous whitespace or non-printable characters

  // Validate and parse the response
  if (response.length() > 0 && response.startsWith("<") && response.endsWith(">")) {
    if (response.startsWith("<success,")) {
      int startIndex = response.indexOf(',') + 1;
      int endIndex = response.indexOf('>', startIndex);
      String adcValueStr = response.substring(startIndex, endIndex);
      selectModule(0); //deselect all modules
      return adcValueStr.toFloat(); //return the ADC value for the pin
    } 
    else {
      selectModule(0); //deselect all modules
      return -1; //return error
    }
  } 
  else {
    selectModule(0); //deselect all modules
    return -1; //return error
  }
}

/*********************************************************************************************************************
Current Measure Command
<currmeasure> - measures the current draw of the V_ADJ supply. returns either <success, float current> or <fail>
*********************************************************************************************************************/

float pinCURRENT(int module){
  if (module < 1 || module > 4) {
    Serial.println("Invalid module number");
    return -1;
  }

  selectModule(module);

  Wire.beginTransmission(pinmoduleaddress);
  Wire.write("<currmeasure>");
  Wire.endTransmission();

  delay(200);

  Wire.requestFrom(pinmoduleaddress, 32);
  String response = "";
  while (Wire.available()) {
    char c = Wire.read();
    response += c;
    if (c == '\n') break;
  }

  response.trim();

  selectModule(0);

  if (response.startsWith("<success,")) {
    int startIndex = response.indexOf(',') + 1;
    int endIndex = response.indexOf('>', startIndex);
    return response.substring(startIndex, endIndex).toFloat();
  } else {
    return -1;
  }
}

/*********************************************************************************************************************
Pin Self Test Code

accepts two commands:
<selftest,status> - this returns the self test status, either <success> or <fail>, simply returns the status of a previous self test.
<selftest,runtest> - this returns the self test status, either <success> or <fail>, but also runs a full new self test of the module.
*********************************************************************************************************************/

int pinModuleSelfTest(int module, String command) {
  // Check if the module number is valid
  if (module < 1 || module > 4) {
    Serial.println("Invalid module number");
    return -1; // Error code for invalid module number
  }

  // Validate the command parameter
  if (command != "status" && command != "runtest") {
    Serial.println("Invalid command");
    return -2; // Error code for invalid command
  }

  // Prepare the self-test command
  String selfTestCommand = "<selftest," + command + ">";

  //transmit command to slave. recieve response.
  String response = requestRecieve(selfTestCommand, module);

  // Interpret the response
  if (response.startsWith("<success")) {
    return 0; // 0 for successful operation
  } else if (response.startsWith("<fail")) {
    return 1; // 1 for failed operation
  } else {
    // If the response is not as expected
    Serial.println("Unexpected response: " + response);
    return -3; // Error code for unexpected response
  }
}

/*********************************************************************************************************************
Pin DAC Code
<dacset,pin,float voltage> - sets the DAC voltage level of a pin. can be 0 to 5.5V. returns either <success> or <fail>
*********************************************************************************************************************/

int pinDacSet(int pin, float voltage){
  if (pin < 1 || pin > 16 || voltage < 0 || voltage > 5.5) {
    Serial.println("Invalid parameters for DAC set");
    return -1;
  }

  int moduleNumber = (pin - 1) / 4 + 1;
  int pinToSend = (pin - 1) % 4 + 1;
  selectModule(moduleNumber);

  String dacCommand = "<dacset," + String(pinToSend) + "," + String(voltage) + ">";
  Wire.beginTransmission(pinmoduleaddress);
  Wire.write(dacCommand.c_str());
  Wire.endTransmission();

  delay(200);

  if (!checkModuleResponse()) {
    selectModule(0);
    return -1;
  }

  selectModule(0);
  return 0;
}

/*********************************************************************************************************************
Pin Matrix All Set Code
<matrix,set1,set2,set3,set4> - sets the matrix. returns either <success> or <fail,status1,status2,status3,status4>

Also sets the voltage level of each module.
*********************************************************************************************************************/

// Convert PinSetting to string command for I2C communication
String convertPinSettingToCommand(PinSetting setting) {
  switch(setting) {
    case PIN_3V3:
    case PIN_5V: return "vadj";
    case PIN_GND: return "gnd";
    case PIN_ADC: return "adc";
    case PIN_DAC: return "dac";
    case PIN_DIO_INPUT:
    case PIN_DIO_OUTPUT: return "dio";
    case PIN_NULL: return "null";
  }
  return "";
}

float voltageForSetting(PinSetting setting) {
  switch(setting) {
    case PIN_3V3: return 3.3;
    case PIN_5V: return 5.0;
    default: return 0.0; // GND, ADC, DIO_INPUT, DIO_OUTPUT
  }
}

int setPins(PinSetting pinConfig[16]) {

  //first, set the pinmodes of the DIO pins locally
  for (int i = 0; i < 16; i++) {
    if (pinConfig[i] == PIN_DIO_INPUT) {
      pinMode(DIO_PINS[i], INPUT);
    }
    else if (pinConfig[i] == PIN_DIO_OUTPUT) {
      pinMode(DIO_PINS[i], OUTPUT);
    }
  }


  //Next, set the matrix switches and power supply voltages
  String matrixCommands[4] = {"", "", "", ""};
  float voltageSet[4] = {0.0, 0.0, 0.0, 0.0}; // Store the voltage for each module

  // Generate matrix commands and check for voltage conflicts
  for (int i = 0; i < 16; i++) {
    int moduleIndex = i / 4;
    matrixCommands[moduleIndex] += convertPinSettingToCommand(pinConfig[i]);
    if (i % 4 != 3) matrixCommands[moduleIndex] += ",";

    float pinVoltage = voltageForSetting(pinConfig[i]);
    if (pinVoltage > 0) {
      if (voltageSet[moduleIndex] == 0) {
        voltageSet[moduleIndex] = pinVoltage;
      } else if (voltageSet[moduleIndex] != pinVoltage) {
        Serial.println("Voltage conflict detected");
        return -1; // Error due to voltage conflict
      }
    }
  }

  bool errorFlag = false;
  for (int module = 0; module < 4; module++) {
    // Set the matrix
    String matrixCommand = "<matrix," + matrixCommands[module] + ">";
    selectModule(module + 1);
    Wire.beginTransmission(pinmoduleaddress);
    Wire.write(matrixCommand.c_str());
    Wire.endTransmission();
    if (!checkModuleResponse()) {
      Serial.print("Bad response from module matrix: ");
      Serial.println(module + 1);
      errorFlag = true;
      break;
    }

    // Send the powerset command only if voltage is greater than 0
    if (voltageSet[module] > 0) {
      String powerCommand = "<powerset," + String(voltageSet[module]) + ">";
      Wire.beginTransmission(pinmoduleaddress);
      Wire.write(powerCommand.c_str());
      Wire.endTransmission();
      if (!checkModuleResponse()) {
        Serial.print("Bad response from module power: ");
        Serial.print(module + 1);
        errorFlag = true;
        break;
      }
    }
  }

  selectModule(0); // Deselect all modules

  // Set pin modes for DIO pins
  for (int i = 0; i < 16; i++) {
    if (pinConfig[i] == PIN_DIO_INPUT) {
      pinMode(DIO_PINS[i], INPUT);
    } else if (pinConfig[i] == PIN_DIO_OUTPUT) {
      pinMode(DIO_PINS[i], OUTPUT);
    }
  }

  return errorFlag ? -1 : 0; 
}

/*********************************************************************************************************************
Pin Matrix Single Set Code
<matrixscalpel,pin,seto> - sets a single channel of the matrix. returns either <success> or <fail>

Sets the matrix setting, but not the voltage. vadj not PIN_3V3 ETC.
Voltage must be set seperately.
*********************************************************************************************************************/

int setPinScalpel(int pin, String seto) {
  if (pin < 1 || pin > 16) {
    Serial.println("Invalid pin number");
    return -1;
  }

  int module = (pin - 1) / 4 + 1;
  int pinToSend = (pin - 1) % 4 + 1;

  // Create the pin scalpel command
  String matrixScalpelCommand = "<matrixscalpel," + String(pinToSend) + "," + seto + ">";

  //transmit command to slave. recieve response.
  String response = requestRecieve(matrixScalpelCommand, module);

  return requestRecieveCheckResponse(response) ? 0 : -1;
}

/*********************************************************************************************************************
Power supply set voltage function
Calling this function turns the supply OFF. Must be turned back on seperatly using toggleModulePower.
<powerset,float voltage> - sets the module voltage. also turns off voltage. returns either <success,difference> or <fail>
*********************************************************************************************************************/

int setModulePower (float voltage, int module){
  if (module < 1 || module > 4 || voltage < 0 || voltage > 5.5) {
    Serial.println("Invalid parameters for setModulePower");
    return -1;
  }

  // Create the power set command
  String powerSetCommand = "<powerset," + String(voltage) + ">";

  //transmit command to slave. recieve response.
  String response = requestRecieve(powerSetCommand, module);

  return requestRecieveCheckResponse(response) ? 0 : -1;
}

/*********************************************************************************************************************
Power supply toggle function
<powertoggle,bool> - toggles power on or off. returns either <success> or <fail>
*********************************************************************************************************************/

int toggleModulePower(bool state, int module) {
  bool errorFlag = false;

  if (module < 1 || module > 4) {
    Serial.println("Invalid parameters for toggleModulePower");
    return -1;
  }

  // Create the toggle power command
  String powerToggleCommand = "<powertoggle," + String(state ? "true" : "false") + ">";

  //transmit command to slave. recieve response.
  String response = requestRecieve(powerToggleCommand, module);

  return requestRecieveCheckResponse(response) ? 0 : -1;
}

/*********************************************************************************************************************
Power supply reset circuit breaker function
Calling this function resets the circuit breaker of the selected module. the MIC2045 automatically "resets" aswell, however this can be usefull for capacitive loads.
<resetbreaker> - resets the MIC2045 circuit breaker. returns either <success> or <fail>
*********************************************************************************************************************/

int resetModuleBreaker (int module){
  if (module < 1 || module > 4) {
    Serial.println("Invalid module number");
    return -1;
  }

  selectModule(module);

  Wire.beginTransmission(pinmoduleaddress);
  Wire.write("<resetbreaker>");
  Wire.endTransmission();

  delay(200); // Adjust delay as necessary

  return checkModuleResponse() ? 0 : -1;
}

/*********************************************************************************************************************
Module IDN function
<*idn?> - module identification. returns < firmare_version, hardware_version>
*********************************************************************************************************************/

String queryModuleIdn (int module){
  if (module < 1 || module > 4) {
    Serial.println("Invalid module number");
    return "Invalid module";
  }

  selectModule(module);

  Wire.beginTransmission(pinmoduleaddress);
  Wire.write("*idn?");
  Wire.endTransmission();

  //delay(200); // Adjust delay as necessary

  Wire.requestFrom(pinmoduleaddress, 32);
  String response = "";
  while (Wire.available()) {
    char c = Wire.read();
    response += c;
    if (c == '\n') break;
  }

  response.trim();
  selectModule(0);
  return response.startsWith("<") ? response : "Invalid response";
}

/*********************************************************************************************************************
Response from slave verification function
*********************************************************************************************************************/

bool checkModuleResponse() {
  Wire.requestFrom(pinmoduleaddress, 32);
  String response = "";
  while (Wire.available()) {
    char c = Wire.read();
    response += c;
    if (c == '\n') break;
  }

  response.trim();

  if (response.length() > 0 && response.startsWith("<") && response.endsWith(">")) {
    if (response.startsWith("<success")) {
      selectModule(0);
      return true;
    } else {
      selectModule(0);
      return false;
    }
  } else {
    selectModule(0);
    return false;
  }
}

/*********************************************************************************************************************
requestrecieve function
*********************************************************************************************************************/

String requestRecieve(String message, int module){

  // Select the appropriate module
  selectModule(module);

  delay(10);

  // Send self-test command to the module
  Wire.beginTransmission(pinmoduleaddress);
  Wire.write(message.c_str(), message.length());
  Wire.endTransmission();

  // Wait for the module to process the command
  delay(500); // Adjust this delay based on the expected processing time

  // Check for response from the module
  Wire.requestFrom(pinmoduleaddress, 32, true); // Assuming 32 is the maximum length of response
  String response = "";
  while (Wire.available()) {
    char c = Wire.read();
    response += c;
    if (c == '\n') break; // End of response
  }

  // Wait for the module to process the command
  //delay(200); // Adjust this delay based on the expected processing time

  response.trim(); // Remove any extraneous whitespace

  // Deselect all modules
  selectModule(0);

  return response;
}


//basic function to check if module responded correctly
bool requestRecieveCheckResponse(String response){

  response.trim();

  if (response.length() > 0 && response.startsWith("<") && response.endsWith(">")) {
    if (response.startsWith("<success")) {
      return true;
    } else {
      return false;
    }
  } else {
    return false;
  }
}

Are you starting the Serial?
like Serial.begin(9600); ?

yes. the full code isnt shown. i didnt want to overwhelm with a bunch of code thats not used. only the actual i2c implementation is shown. the main loop of the slave is actually empty, and it simply responds with dummy values.

could you by chance show the crash report, or the message the log says.

Which Arduino is your master?

The String type does not work on small 8-bit controllers.

the code marked as master is the master. only one of the four slave code exerts is shows, they are all the same, just different addresses.

all five are samd51g19a microcontrollers.

the log really says nothing inmortant. heres an exert anyways from the slave:

11:38:14.764 -> Command Sent:  <success,0.00>
11:38:14.808 -> In Recieve
11:38:15.185 -> In Recieve
11:38:15.185 -> Command Reieved:  <powertoggle,true>
11:38:28.579 -> In Recieve
11:38:28.579 -> Command Reieved:  <powerset,3.30>
11:38:28.736 -> In Request
11:38:28.782 -> Command Sent:  <success,0.00>
11:38:28.782 -> In Recieve
11:38:29.189 -> In Recieve
11:38:29.189 -> Command Reieved:  <powertoggle,true>
11:38:42.544 -> In Recieve
11:38:42.544 -> Command Reieved:  <powerset,3.30>
11:38:42.717 -> In Request
11:38:42.717 -> Command Sent:  <success,0.00>
11:38:42.763 -> In Recieve
11:38:43.157 -> In Recieve
11:38:43.157 -> Command Reieved:  <powertoggle,true>

In the On...Event handlers do required things (read/write msg) first, log later.

@ DrDiettrich i do not understand what you mean. can you please elaborate?

so I've narrowed my problem down to an issue with trying to have the device have both master and slave capabilities. so i took a new approach, and now i can get it to work perfectly as either master or slave, but not both. i know you cant do both at the same time, but i had assumed you could switch between them, in an OR configuration. however, i am having issues with that.

Basically, this function on the slave:

void switchI2CMode(bool asMaster) {
  Wire.end(); // End current I2C operation to reconfigure
  
  if (asMaster) {
    // Configure as I2C master
    Wire.begin(); // Start I2C as master
 }
  
  else {
    // Configure as I2C slave
    Wire.begin(0x20); // join i2c bus with address 0x20 as a slave 
    Wire.onReceive(receiveEvent); // register event
    Wire.onRequest(requestEvent); // Register the onRequest event handler
  }
}

is supposed to select between master and slave mode. however Wire.onReceive(receiveEvent) & Wire.onRequest(requestEvent) only work when initialized in the setup routine, and once the device is switched into master mode, it ceased ever being able to function as a slave (until it is reset). does anyone know a workaround to this? microcontroller is a SAMD21G18A. Its on a custom board, so using a second sercom is not possible without a revised board.

Are all the Slaves on the custom board as well ?
Why do you need so many processors ? Can you start all over without the I2C bus ?

Tell us how far the I2C bus is extended. It was designed as a within-one-circuit-board interprocessor/peripheral interface.
You will find that very often on this forum, the problem users present is solved by fixing something in the code that was not originally presented. Hence, it is much preferred* that you provide the entire code, in code tags, so that we may import your code into our IDE to observe compilation, structure, etc. It is of course up to you, but most often providing complete code and as much design info as you can will result in expeditious resolution of your problem.
*- to the extent that many helpers simply 'hang back', or walk away, when a new posting simply contains snippets.
YMMV, of course, but hey, you asked for help.

That's wrong :frowning:

Each slave also can act as a master of the I2C bus at any time.

1 Like

PLEASE FIND THE FULL CODE IN THE FIRST POST OF THIS THREAD.

so the architecture is four slave modules communicating with a master. The device does need separate processors. they are slave modules that plug into a master device. i have four. the BOM cost each was around 250$. ideally i wouldn't rev it again, but its not out-of-the-question.

the slave modules each have 4-channel DAC, 4-Channel ADC, IO expander, and Power Monitor on their internal i2c bus. the master turns on a switch to physically add and remove the slave modules from the main i2c bus. However, all the testing i have done so far has only been with one slave module.

i have heard calls for the whole code. ok. here it is. its a few thousand lines so i didn't think it necessary, however like most things i'm probably wrong.

@camsysca i looked at the trace-lengths and it seems worst case 5-6 inches of PCB traces, routed correctly away from noise sources. All my testing indicates its a software not hardware issue. I have updated the first post with all the code.

@DrDiettrich what i meant by "i know you cant do both at the same time" is that it has to be OR, i.e. Wire.begin(); OR Wire.begin(0x20), but you cant just do Wire.begin(0x20) and then have the micro control a ADC as master. is this correct? my testing shows it is, but perhaps i'm doing something else wrong?

1 Like

Each module has a number of I2C devices that it controls. When a module is communicating with such a I2C device, then the module is a Master on the I2C bus. At that moment the I2C bus is occupied.
Is there a "main" and a "internal" I2C bus ? How can I see the difference in the source code ?

What is the file "isl28022DL.h" ?

I'm sorry, but I think that you have no idea how much trouble you are in.

for the sake of simplicity, lets focus on 1 master 1 slave, so everything is on one i2c bus. however, in practice, the master "physically flips a switch" (dpst analog switch) to put the desired slave onto and off of the main i2c bus.

my bad. here they are:

#pragma once

#include <Wire.h>
#include <Arduino.h>

#define SHUNTVOLTREG  0x01  
#define BUSVOLTREG    0x02
#define CURRENTREG    0x04
#define POWERREG      0x03

class power_isl28022 {
  private:
    int _address; // Private variable for the address

  public:
    power_isl28022(int address_ic); // Constructor
    void begin();
    float readRegister(int reg);

};

#include "isl28022DL.h"

power_isl28022::power_isl28022(int address_ic) {
  _address = address_ic;
}

void power_isl28022::begin()
{
  // Set up ISL28022 Power Monitor IC
  // Test the ISL28022
  Wire.begin();  // Join the bus as Master
  delay(20);  // Short settling delay
  // Reset the Configuration Register

  Serial.print("Start: ");
  Serial.println(_address);

  Wire.beginTransmission(_address);  // Start write to address 0b1000000
  Wire.write(0x00);    // Write to the calibration register

  // Set up everything:
  // RST/BRNG1/BRNG0/PG1/PG0/BADC3/BADC2/BADC1/BADC0/SADC3/SADC2/SADC1/SADC0/MODE2/MODE1/MODE0
  // Set it to:
  // 0b1000000000000000
  // Perform a system reset:
  Wire.write (0b10000000);
  Wire.write (0b00000000);
  Wire.endTransmission();

  // Set up the Configuration Register
  Wire.beginTransmission(_address);  // Start write to address 0b1000000
  Wire.write(0x00);    // Write to the calibration register

  // Set up everything:
  // RST/BRNG1/BRNG0/PG1/PG0/BADC3/BADC2/BADC1/BADC0/SADC3/SADC2/SADC1/SADC0/MODE2/MODE1/MODE0
  // Shunt with 320mV range so need to set PG1 = 1 and PG0 = 1 (from datasheet)
  // Set it to:
  // 0b 0 11 11 0011 0011 111
  Wire.write (0b01111001);
  Wire.write (0b10011111);
  Wire.endTransmission();

  // Set up the Calibration Register
  // This allows the Power and Current registers to be used
  Wire.beginTransmission(_address);  // Start write to IC address 
  Wire.write(0x05);                 // Write to the calibration register

  // OLD values were:
  // 4474 = 117A in HEX
  // Binary: 0001 0001 0111 1010

  // 5mOhm Shunt with 320mV range
  // Calibration register needs to be set to 4194 (dec) = 1062 (hex)
  Wire.write (0x10);
  Wire.write (0x62);

  Wire.endTransmission();
}

// Read data from a specified register and apecified IC address:
float power_isl28022::readRegister(int _reg)
{
  // This subroutine reads data from device with I2C address ADDRESS
  // It reads the data from the device register ISL28022register
  byte firstByte = 0;
  byte secondByte = 0;
  int16_t valueInt16;
  float  valueFloat;

  Wire.beginTransmission(_address);  // Start write to address 0b1000000
  Wire.write(_reg);     // Read the Bus Voltage register
  Wire.endTransmission();
  Wire.requestFrom(_address, 2);    // Read two bytes of data

  while (!Wire.available())          // Wait until there is data returned
  {
  }
  firstByte = Wire.read();          // Read and store the data
  secondByte = Wire.read();

  if (_reg == SHUNTVOLTREG)
  {
    //Serial.print(F("SV: "));
    // ****THIS DEPENDS UPON THE SADC BITS*******
    // ****RANGE = 11 (full scale up to +/-160mV)
    // The bus voltage is a 15 bit value which needs to be converter.
    // Bit 16 is a sign bit (2's complement?)
    // Need to add them together, with the correct weighting factor (256)
    valueInt16 = firstByte * 256 + secondByte; // Convert to a value
    // Each bit is (160mV / 2^15)
    // So value * 0.00001V = shunt voltage
    // This is too small, so keep number in milli Volts
    valueFloat = ((valueInt16 * 320.0) / 32768.0);
  }

  else if (_reg == BUSVOLTREG)
  {
    //Serial.print(F("V: "));
    // ****THIS DEPENDS UPON THE RANGE BITS*******
    // ****RANGE = 11 (up to 60V)
    // Need to add them together, with the correct weighting factor (256)
    valueInt16 = firstByte * 256 + secondByte; // Convert to a value
    valueInt16 = valueInt16 >> 2; // Shift the value across by 2 (14 bit number)

    // 60V = max bus range This is 14 bit number
    // Each LSB is 4mV so multiply up
    valueFloat = valueInt16 * 0.004;
  }
  else if (_reg == POWERREG)
  {
    //Serial.print(F("P: "));
    // Only has meaning if calibration register (0x05) is programmed.
    valueInt16 = firstByte * 256 + secondByte; // Convert to a value
    // From ISL28022 datasheet
    // Actual Power = reading * Power LSB * 5000 * 2
    // Power LSB = Current LSB x Voltage LSB
    // Power LSB = 1.953125 mA x 4 mV = 7.8125 uW per LSB
    // 1 LSB = 7.8125uW
    valueFloat = (valueInt16 * 7.8125 * 5000.0 * 2.0) / 1000000.0;
  }
  else if (_reg == CURRENTREG)
  {
    //Serial.print(F("I: "));
    // Only has meaning if calibration register (0x05) is programmed.
    valueInt16 = firstByte * 256 + secondByte; // Convert to a value
    // From ISL28022 datasheet
    // Actual current = ((reading / 2^15) * Max Shunt Voltage (mV)) / Shunt resistance(mOhm)
    valueFloat = ((valueInt16 / 32768.0) * 320.0) / 140.0 ;
  }
  //Serial.print(valueFloat, 2);
  //Serial.print(" ");

  return valueFloat;
}

Right :frowning:

A single Wire.begin(slaveID) in setup() is sufficient to start a slave that also can act as a master at any time. An Arduino becomes the I2C bus master during Wire.endTransmission().

Can you show a schematic ?
I'm curious how this analog switch is working and if you have enough pullup resistors.

its a very low power switch, perfect for the application. it is both powered and switched on from a microcontroller pin.

each slave module is two boards sandwiched, and the master consists of two boards. its a relatively complex system, that would take some understanding to get the big picture, so i don't think there is any point sharing the full schematics.

pullups on the input are from master, so maybe 5" away. the switch is located 130mills from the micro, and has 1K pullups on the slave side.

im 99.9% sure its not a hardware issue. i have left it running for hours as either a slave OR master. i just cant get it to do both/switch between the two.

130mills, is that 3.3 cm ?
5", is that 12 cm ?

The maximum length of the I2C bus is 10 cm if you do it wrong according to the official standard UM10204.pdf paragraph 7.5 page 54.
The maximum current (sink current to pull a signal low) is 3 mA. Therefor your 1kΩ is too low. I prefer to see the schematic and see that resistor myself.

I wrote before: "Can you start all over without the I2C bus ?" and I wrote "... I think that you have no idea how much trouble you are in".
I was not joking when I wrote that. You do not have a lot experience with the I2C bus, and everywhere we look there is something wrong.

I'm also 99.9% sure it is not a hardware issue. But that does not mean that you can look away.

Let's start at the very begin. Do you know if the SAMD21G18A processor can be a reliable I2C Slave when using Arduino ?

Thanks.

"Do you know if the SAMD21G18A processor can be a reliable I2C Slave when using Arduino ?" if all i wanted was slave only, i have left it running for hours without issue, so i assume it can.

yeah 1K is low, however it should work fine, especially considering i can have it work exclusively as a master or slave and never have an issue that i can notice.

so through some butchering, i broke out PA12 and PA13, sercom2, sercomalt4, cut the existing lines to the switch, and soldered the wires. i verified with an oscilloscope that it should be OK. I soldered 1K pullups directly to the microcontroller pins. i alsoe removed all extra code from the slave. it will now exclusively respond success. however, this new setup doesnt seem to work, the slave never enters receiveEvent or requestEvent.

doing this doesnt help either.
#define slaveSDA PIN_PA12 //22 PA12
#define slaveSCL PIN_PA13 //38 PA13

SIMPLIFIED SLAVE CODE:

#include <Wire.h>
#include <Adafruit_SleepyDog.h>
#include "wiring_private.h" // pinPeripheral() function

#define slaveSDA 22 //22 PA12
#define slaveSCL 38 //38 PA13

TwoWire slaveWire(&sercom4, slaveSDA, slaveSCL);

String responseToSend; // This will hold the response to be sent

String command = "";

/*********************************************************************************************************************
Setup and initilization function
*********************************************************************************************************************/

void setup() {

  Watchdog.enable(10000); // turn on watchdog timer with 10 seconds

  Serial.begin(9600);
  while (!Serial) delay(10);     // pause the serial port

  Serial.println(F("testing"));

  Wire.begin();

  // Assign pins to SERCOM functionality
  pinPeripheral(slaveSDA, PIO_SERCOM_ALT);
  pinPeripheral(slaveSCL, PIO_SERCOM_ALT);

  slaveWire.begin(0x20);                // join i2c bus with address 0x20

  slaveWire.onReceive(receiveEvent); // register event
  slaveWire.onRequest(requestEvent); // Register the onRequest event handler

}

void loop() {

  Watchdog.reset(); //resets the watchdog timer. basically, this makes sure the code doesnt lock up.

}

void switchI2CMode(bool asMaster) {

}

/*********************************************************************************************************************
i2c Slave Request and Recieve functions
*********************************************************************************************************************/

// Function to run when data requested from master
void requestEvent() {

  Serial.println("In Request");

    Serial.print("Command Sent:  ");
    Serial.println(responseToSend);

    String responseWithTermination = responseToSend + "\n";
    slaveWire.write(responseWithTermination.c_str(), responseWithTermination.length()); // Send the response
}


// I2C communication function for receiving data
void receiveEvent(int howMany) {

  Serial.println("In Recieve");

    if (slaveWire.available() > 0) {
        command = "";
        while (slaveWire.available()) {
            char c = slaveWire.read();
            command += c;
        }

        Serial.print("Command Reieved:  ");
        Serial.println(command);

        responseToSend = processCommand(command); // Process the command and store the response
    }
}



/*********************************************************************************************************************
i2c command handling functions
*********************************************************************************************************************/

// Process received command
String processCommand(String command) {

  return "<success,1.0>";

}