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(){
}
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
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.
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.
@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.
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(){
}
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.
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.
#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.