I2CCommander 2 motors

Hi,

I am having some trouble with I2CCommander running 2 motors.
I am testing this with only MCUs attached (skipping motor calibration).

This is how I initialized I2CCommander on the controlling MCU (ESP32)

    commander.addI2CMotors(SLAVE2_ADDRESS, 2);
    commander.init();

This is how I send the commands to both motors from the controlling MCU

  float targetSpeed = RPMtoRADS(req_rpm.motor1);
  Serial.print(targetSpeed);
  Serial.print("\t");
  if (commander.writeRegister(0, REG_TARGET, &targetSpeed, 4)!=4) { 
	  Serial.println("Error Motor 0");
  }

  targetSpeed = RPMtoRADS(req_rpm.motor2);
  Serial.println(targetSpeed);
  if (commander.writeRegister(1, REG_TARGET, &targetSpeed, 4)!=4) { 
  	Serial.println("Error Motor 1");
  }

This is how I add motors on the MCU running simpleFOC algorithm to drive the motors

    commander.addMotor(&motor1);     // add a motor
    commander.addMotor(&motor2);  // you could add more than one motor
    commander.init(i2c_addr);       // initialize commander
    Wire2.onReceive(onReceive);      // connect the interrupt handlers
    Wire2.onRequest(onRequest);

To get some information what’s going on, I modified I2CCommander.cpp to provide me some output

        case REG_TARGET:
            Serial.print("Motor ");
            Serial.print(motorNum);
            Serial.print(":\t");
            Serial.println(motors[motorNum]->target);
            readBytes(&(motors[motorNum]->target), 4);

These are console outputs from both MCU’s - left is the MCU driving the motors, right is the controlling MCU

Either I am doing something wrong, or there is an issue with motorNum variable.

@runger I’d appreciate it if you had some time to look into this.

I will look into it!

I am currently on vacation, so I can only check the code, I don’t have any motors with me here to test with…

Thank you.

I am not sure, but I think the motor number is not being sent by the I2CCommanderMaster. It’s just used to get the I2C address to begin transmission.

int I2CCommanderMaster::writeRegister(int motor, I2CCommander_Register registerNum, void* data, uint8_t size){
    motors[motor].wire->beginTransmission(motors[motor].address);
    motors[motor].wire->write((uint8_t)registerNum);
    motors[motor].wire->write((uint8_t*)data, size);
    motors[motor].wire->endTransmission();
    return size;
};

Soooo… I have to apologize, I think this is a case of really bad documentation. The code in the drivers repository is not as well documented, and not as well tested as the main library.

In this case, I think I did not make really clear how I2CCommander actually works.

From the Target device point of view, the target MCU runs I2CCommander, and can control multiple motors attached to the same target.
The currently controlled motor, i.e. the motor that should receive the commands, is one of the registers that can be set.
So you can set the register “REG_MOTOR_ADDRESS” to point at the current motor you want to command, and then send a bunch of read or write register commands which will all affect that motor.
In other words, the selected motor on the target side is stateful, and should be set in advance of sending the other commands.

On the I2CCommanderMaster side, the model is that there can be one or more target devices, each with its own I2C address, on the same or different I2C busses. For each target device, if you want to use it, first set the motor current number on that target device, and then send commands to it.
The argument “motor” is there only as a hint to the I2CCommanderMaster which target you want to speak to, but it does not actually make that motor the current one on the target… :frowning:
This is clearly a very bad and confusing way for the API to work. I apologize for it.

So to achieve what you want, using a single target with 2 motors, you would do the following:

  uint8_t currMotor = 0;
  if (commander.writeRegister(0, REG_MOTOR_ADDRESS, &currMotor, 1)!=1) { 
	  Serial.println("Error Motor 0");
  }
  if (commander.writeRegister(0, REG_TARGET, &targetSpeed, 4)!=4) { 
	  Serial.println("Error Motor 0");
  }
  currMotor = 1;
  if (commander.writeRegister(0, REG_MOTOR_ADDRESS, &currMotor, 1)!=1) { 
	  Serial.println("Error Motor 1");
  }
  if (commander.writeRegister(0, REG_TARGET, &targetSpeed, 4)!=4) { 
	  Serial.println("Error Motor 1");
  }

Perhaps it makes sense to add a REG_TARGET_SAME register, for when you want to set both motors to the same value? Or a REG_TARGET_ALL register, which sets targets for each motor attached to a target device (and therefore data is one float per motor)?

— EDIT—

curMotor has to be a byte-sized variable, updated the code just now to reflect that.

1 Like

I’m happy you wrote it, and I am grateful - you saved me from writing my own communication layer with simpleFOC.

It sure would make the final code cleaner with fewer lines in some use cases (like mine), but I don’t see this as a huge thing.

Again, thank you for writing I2CCommander, and especially thank you for helping out while on vacation.