I2C data read of multiple bytes gives wrong value

I am trying to control an encoded DC servo motor( https://robokits.co.in/automation-control-cnc/integrated-dc-servo-motors/nema23-high-torque-encoder-dc-servo-motor-10rpm-with-uart-i2c-ppm-drive?cPath=364_366_376 ) with I2c communication using Arduino UNO. According to the data sheet the position to which the motor is to be moved is controlled using a control 4 byte signed control byte. The position of the encoder can also be red from another 4 byte signed control byte. When I tried moving the motor to a particular location and reading it's corresponding encoder values, the first byte always seem to be different from the actual position the motor was moved to. Can anyone point out if anything is wrong with the program.

The program, the output and the actual output i should have got is attached. Thanks in advance.

#include "Wire.h"
#define I2C_ADDRESS 0x10 >> 1 //Default slave address of this drive is 0x10. Arduino works on 7-bit I2C Addressing, so the Address value is shifted to right by 1 bit.
#define MAX_SPEED 255  //Set Maximum Speed (0-255)
#define CB_0 0 // Command Byte "0" to Read/Write Motor Max Speed 
#define CB_1 1 // Command Byte "1" to Read/Write Motor Speed and Direction
#define CB_3 3 // Command Byte "3" to Read/Write encoder position
#define CB_4 4 // Command Byte "4" to Read/Write absolute go to position
#define CB_8 8 // Command Byte "8" to Read/Write Relative go to position

byte encoder;
void setup(){
  Wire.begin();
  Serial.begin(9600);
  delay(100);
  
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write(CB_0);
  Wire.write(255); // LSB Byte of Maximum Speed Value
  Wire.write(0); // MSB Byte of Maximum Speed Value
  Wire.endTransmission();
  delay(100);

  delay(3000);

  Wire.beginTransmission(I2C_ADDRESS); //reset encoder position as zero
  Wire.write(CB_3);
  Wire.write(0xE1); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.endTransmission();
  delay(100);

  Wire.beginTransmission(I2C_ADDRESS); //move to +45
  Wire.write(CB_4);
  Wire.write(0xE1); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.endTransmission();
  delay(100);

  Wire.beginTransmission(I2C_ADDRESS); //read encoder position
  Wire.write(CB_3);
  Wire.endTransmission(false);
  Wire.requestFrom(I2C_ADDRESS,4);

  if(4<=Wire.available())
  {
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
  }

  delay(1500);

  Wire.beginTransmission(I2C_ADDRESS); //move to zero position
  Wire.write(CB_4);
  Wire.write(0); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.endTransmission();
  delay(100);

  Wire.beginTransmission(I2C_ADDRESS); //read encoder position
  Wire.write(CB_3);
  Wire.endTransmission(false);
  Wire.requestFrom(I2C_ADDRESS,4);

  if(4<=Wire.available())
  {
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
  }

  delay(1500);

  Wire.beginTransmission(I2C_ADDRESS); //move to -45
  Wire.write(CB_4);
  Wire.write(0x1F); 
  Wire.write(0xFF); 
  Wire.write(0xFF); 
  Wire.write(0xFF); 
  Wire.endTransmission();
  delay(100);
  Wire.beginTransmission(I2C_ADDRESS); //read encoder position
  Wire.write(CB_3);
  Wire.endTransmission(false);
  Wire.requestFrom(I2C_ADDRESS,4);

  if(4<=Wire.available())
  {
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
  }

  delay(1500);

  Wire.beginTransmission(I2C_ADDRESS); //move to zero
  Wire.write(CB_4);
  Wire.write(0); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.endTransmission();
  delay(100);
  
  Wire.beginTransmission(I2C_ADDRESS); //read encoder position
  Wire.write(CB_3);
  Wire.endTransmission(false);
  Wire.requestFrom(I2C_ADDRESS,4);

  if(4<=Wire.available())
  {
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
  }
}


void loop(){

 
}

image

Actual output should have been : E1 00 00 00 00 00 00 00 1F FF FF FF 00 00 00 00

1. What is the Slave Address according to data sheet? Is it 0x10 (0010000 = 7-bit)? Yes! It is 0010000 (Fig-1). Then leave it like this. There is no reason to shift to the right by 1-bit position. If you do so, it will become 0001000 which is different from 0010000. //001000. edit


Figure-1:

Note that during data write operation (data goes from UNO to Slave), the UNO automatically shifts the 7-bit address (0100000) to the left by 1-bit position and then place a 0 (zero) to the right-most position to make it 8-bit I2C Address (00100000 = 0x20).

Note that during data read operation (data comes to UNO from Slave), the UNO automatically shifts the 7-bit address (0100000) to the left by 1-bit position and then place a 1 (one) to the right-most position to make it 8-bit I2C Address (00100001 = 0x21).

2. The following simple sketch should verify the I2C communication between UNO and the Slave.

#include <Wire.h>
#define slaveAddress 0x10 // >> 1 //Default slave address of this drive is 0x10. Arduino works on 7-bit I2C Addressing, so the Address value is shifted to right by 1 bit.
#define CB4 4 // Command Byte "4" to Read/Write absolute go to position

byte CB4Data[] = {0x78, 0x56, 0x34, 0x12};   //4-byte data lower byte = 0x78

void setup() 
{
  Wire.begin();
  Serial.begin(9600);

  Wire.beginTransmission(slaveAddress);
  Wire.write(CB4);
  Wire.write(CB4Data, sizeof CB4Data);
  Wire.endTransmission();

  Wire.beginTransmission(slaveAddress); //reset encoder position as zero
  Wire.write(CB4);   //Poinnting the relevant register with Command Byte 4
  Wire.endTransmission();

  Wire.requestFrom(slaveAddress, 4);
  {
    for(int i = 0; i<4; i++)
    {
      Serial.print(Wire.read());//shpuld show: 78 56 34 12
      Serial.print(' ');
    }
  }
}

void loop() 
{

}

The Arduino slave address is 8. The data sheet deserves special interpretation.

That's not zero!

The documentation is imprecise. E.g. what's the difference between Read Encoder Position (3) and Read Go To Position (4)? How to determine when a move has finished?

I'd start with writing and reading the encoder position what should work without a delay. Also check the motor position before and after an encoder change.

Is there any difference between "Slave Address" and "Slave Device Address"? The data sheets says "slave device address of 0x10)" which I think is the 7-bit "Slave Address (0010000)". The 8-bit I2C Address of a slave has two values -- Read Mode I2C (bus) Address (00100001) and Write Mode I2C (bus) Address (00100000).

If a slave is assigned the 7-bit address 0010000, then the read mode 8-bit I2C Address is: 00100001 and the write mode 8-bit I2C Address is: 00100000. We do not enter the 8-bit I2C Address in the instruction; we enter the 7-bit slave address which are automatically converted into 8-bit I2C Address corresponding to read/write mode.

and it adds 0 or 1 for read/write without shifting - see the example code in the data sheet.

The program could receive data only because the address was calculated correctly.

Actually, those are the same.

My bad! I was writing " If you do so, it will become 0001000 which is different from 0010000." //001000. edit

1. The OP will verify/announce the 7-bit slave address by doing read/after write operation by sending Command-4 and the associated 4-byte data. He may follow the sketch of post #2. Alternatively, he (@aravindshaji) can ran the following "I2C address scanner program" of Step-2, which will report the 7-bit address of the Slave.

2. I2C Address Scanner Program

#include <Wire.h>

void setup()
{
  Serial.begin(9600);
  Serial.println("I2C search");
  Serial.println("-------------------------");

  Wire.begin();
  int i2c = 0b0001000;
  for (int i = 0; i < 32; i++)
  {
    Serial.print("Search at [");
    Serial.print(i2c, HEX);
    Serial.print("]: ");
    Wire.beginTransmission(i2c);
    byte busStatus = Wire.endTransmission();
    if (busStatus == 0)
    {
      Serial.println("FOUND!");
      while(1);
    }
    else
    {
      Serial.println("not found");
    }
    i2c++;
  }
}

void loop() {}

3. The following Fig-1 clearly indicates that the 7-bit Slave Address is to be shifted left by 1-bit position to insert 1/0 (read-write/) to form 8-bit I2C Address. The I2C network is a byte oriented protocol; so, it must handle the 8-bit I2C Address and not the 7-bit Slave Address.
cbtwi-1
Figure-1:

@aravindshaji It does not matter how the I2C address is defined. Run a I2C Scanner sketch and use the address that is found.
I think you already have the correct I2C address.

The datasheet is indeed confusing.
For example, they say that it is "little-Endian byte ordering", but the example shows LSB is sent first.

Can you use UART ? The Arduino Leonardo has a spare serial port. It is possible to run a software serial port on the Arduino Uno with AltSoftSerial.

I suggest to make a small test sketch and try to make that work.
Read and write only the two bytes of command 0.
If that works for a few different values, then you can try to write and read a command with 4 bytes.

Data sheets says - "slave device address of 0x10." Is it 7-bit slave address (0010000) or 8-bit I2C address (00010000)? What is your opinion?

That is correct. The "Little-Endian" refers to the storage of lower byte into lower-address memory location.

Thanks for your response. I have used the I2C scanner and found the address to be 0x08 and writing to any command byte through I2C channel has never been an issue since the motor has given me the correct output everytime.

I was facing issue while reading the command bytes and the code which you have mentioned above is working perfectly (the I2C address had to be changed to 0x08 though). Your code seems to have all the things my code does. Could anyone point out the differences in the codes bellow which would make mine not give a correct output while reading the command bytes.

This is the codw which works well

#include <Wire.h>
#define slaveAddress 0x08 //Default slave address of this drive is 0x10. Arduino works on 7-bit I2C Addressing, so the Address value is shifted to right by 1 bit.
#define CB4 4 // Command Byte "4" to Read/Write absolute go to position

byte CB4Data[] = {0x78, 0x56, 0x34, 0x12};   //4-byte data lower byte = 0x78

void setup() 
{
  Wire.begin();
  Serial.begin(9600);

  Wire.beginTransmission(slaveAddress);
  Wire.write(CB4);
  Wire.write(CB4Data, sizeof CB4Data);
  Wire.endTransmission();

  Wire.beginTransmission(slaveAddress); //reset encoder position as zero
  Wire.write(CB4);   //Poinnting the relevant register with Command Byte 4
  Wire.endTransmission();

  Wire.requestFrom(slaveAddress, 4);
  {
    for(int i = 0; i<4; i++)
    {
      Serial.print(Wire.read());//shpuld show: 78 56 34 12
      Serial.print(' ');
    }
  }
}

void loop() 
{

}

the following is the code written by me which always give a wrong value while reading the LSB

#include "Wire.h"
#define I2C_ADDRESS 0x10 >> 1 //Default slave address of this drive is 0x10. Arduino works on 7-bit I2C Addressing, so the Address value is shifted to right by 1 bit.
#define MAX_SPEED 255  //Set Maximum Speed (0-255)
#define CB_0 0 // Command Byte "0" to Read/Write Motor Max Speed 
#define CB_1 1 // Command Byte "1" to Read/Write Motor Speed and Direction
#define CB_3 3 // Command Byte "3" to Read/Write encoder position
#define CB_4 4 // Command Byte "4" to Read/Write absolute go to position
#define CB_8 8 // Command Byte "8" to Read/Write Relative go to position

byte encoder;
void setup(){
  Wire.begin();
  Serial.begin(9600);
  delay(100);
  
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write(CB_0);
  Wire.write(255); // LSB Byte of Maximum Speed Value
  Wire.write(0); // MSB Byte of Maximum Speed Value
  Wire.endTransmission();
  delay(100);

  delay(3000);

  Wire.beginTransmission(I2C_ADDRESS); //reset encoder position as zero
  Wire.write(CB_3);
  Wire.write(0xE1); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.write(0); 
  Wire.endTransmission();
  delay(100);

  Wire.beginTransmission(I2C_ADDRESS); //read encoder position
  Wire.write(CB_3);
  Wire.endTransmission();
  Wire.requestFrom(I2C_ADDRESS,4);

  if(3<=Wire.available())
  {
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
    Serial.println(Wire.read(),HEX);
  }

}
void loop(){

 
}

Did you read and understand my #3?

Eventually you should set motor speed to 0 to prevent a permanently running motor.

I didn't really have to write the motor speed to 0 as the motor stops itself at the correct write go to position(4). It was exacly to avoid the issue where the encoder reads the value before reaching the set position that I included a delay which ensures that motor has sufficient time to reach the target position and only after that the encoder value is read.

Wire.write(CB_3); must be followed by 4-byte data (Fig-1), which you have forgotten to send. If you comply with this requirement, your sketch might work.


Figure-1:

If you set the speed to some non-zero value then the motor most probably runs continuously at that speed.

Only for a write operation, not for reading the register. You really should read the example code in the data sheet instead of wild guessing.

My bad! I did not notice the " Wire.requestFrom(I2C_ADDRESS,4);" command.

There is a processor inside and the encoder might be very sensitive. I agree with the others that it can change.

Do you have all the information to make working code ?

I suggest to remove the CB_0, CB_1 (and so on) definitions, just use number 0, 1, (and so on).
You could make functions to read 2 bytes, read 4 bytes, write 2 bytes, write 4 bytes.
A 2 bytes signed integer is a "int16_t" and a 4 byte signed integer is a "int32_t".

The datasheet suggest a repeated start.

  Wire.beginTransmission(slaveAddress); //reset encoder position as zero
  Wire.write(CB4);   //Poinnting the relevant register with Command Byte 4
  Wire.endTransmission(false);     // <-- false for a repeated start

  Wire.requestFrom(slaveAddress, 4);
  ...

The Wire.endTransmission(false) with parameter "false" omits the STOP condition. The next START condition by Wire.requestFrom() will be a repeated start. Use that parameter only just before calling the Wire.requestFrom(), not when writing data.

@GolamMostafa @DrDiettrich @Koepel @red_car Thanks everyone for helping out. I did try different ways of reading the code and the following code works the best.

#include <Wire.h>
#define slaveAddress 0x10 // >> 1 //Default slave address of this drive is 0x10. Arduino works on 7-bit I2C Addressing, so the Address value is shifted to right by 1 bit.
#define CB4 4 // Command Byte "4" to Read/Write absolute go to position

byte CB4Data[] = {0x78, 0x56, 0x34, 0x12};   //4-byte data lower byte = 0x78

void setup() 
{
  Wire.begin();
  Serial.begin(9600);

  Wire.beginTransmission(slaveAddress);
  Wire.write(CB4);
  Wire.write(CB4Data, sizeof CB4Data);
  Wire.endTransmission();

  Wire.beginTransmission(slaveAddress); //reset encoder position as zero
  Wire.write(CB4);   //Poinnting the relevant register with Command Byte 4
  Wire.endTransmission();

  Wire.requestFrom(slaveAddress, 4);
  {
    for(int i = 0; i<4; i++)
    {
      Serial.print(Wire.read());//shpuld show: 78 56 34 12
      Serial.print(' ');
    }
  }
}

void loop() 
{

}

Even by using this code there was slight variations in the go to position fed into the motor and encoder position read from the motor(+- 0.6 degrees from my experimentation) and i believe this is due to the inability of the motor to stop at the exact location which may be corrected using proper PI tuning.

I also tried the repeated start condition as suggested by one of you

Wire.beginTransmission(slaveAddress); //reset encoder position as zero
  Wire.write(CB4);   //Poinnting the relevant register with Command Byte 4
  Wire.endTransmission(false);     // <-- false for a repeated start

  Wire.requestFrom(slaveAddress, 4);
  ...

but that didn't prove to be much useful as it gave the same output as the code without repeated start enabled.

1 Like

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