communicating with an IME 393 through i2c

I have been trying to communicate with an IME 393 using a PIC16F887 over i2c. Each time I send the device address, I keep getting a NACK at the end. This is under the assumption that a master code is not necessary. My question is, do I have to send the master code first before I send the device address? Also, the FAQ stated that the device, upon start up, has a device address of 0x60, is that after shifting left 1 bit, or before? Thank you.

I moved this to the Technical Discussion sub-forum because I believe you will get an answer faster here than in the official VEX tech forum. OP, let me know if you want this moved back.

Rick is correct, you would have received an answer from VEX but it might have taken some time.

So I assume you have seen the details of the I2C comms that JVN posted here.

https://vexforum.com/showthread.php?p=255691

There was also some discussion of the details in this thread.

https://vexforum.com/t/new-integrated-encoder-module/20595/1

Not too interesting as it was before there was any official support from ROBOTC and EasyC so we were trying to reverse engineer the details.

The default address of 0x60 is after (in your language) shifting left 1 bit as far as I remember. In other words, 0x60 for write and 0x61 for read. When I was playing with this earlier in the year the first thing to do is reprogram the default address to a new address, I was using 0x20, your code will be different for the PIC but the code I used looked as follows.

/*---------------------------------------------------------------------------*/
/*                                                                           */
/*      Set new Address in IME                                               */
/*                                                                           */
/*---------------------------------------------------------------------------*/

int
VexIMESetAddr( int *device, unsigned char newDevice )
{
    char    buf[4];
    int     result;

    //
    buf[0] = IME_SETDEV_REG;
    buf[1] = newDevice;

    result = I2C_transmit_block( *device, buf, 2 );
    I2C_release();

    *device = newDevice;

    return(result);
}

and called as follows.



#define DEFAULT_DEVICE  0x60
#define IME_VERSION_ADDR    0x00
#define IME_VENDOR_ADDR     0x08
#define IME_DEVICEID_ADDR   0x10
#define IME_STATUS_ADDR     0x20
#define IME_DATA_ADDR       0x40

#define IME_SCRATCHR_ADDR   0x60
#define IME_SCRATCHW_ADDR   0xA0

#define IME_SETDEV_REG      0x4D
#define IME_RESET_REG       0x4E

static unsigned char MyAddress = DEFAULT_DEVICE;

... other code

    // Reset
    VexIMEResetAll();

    // Set new address
    VexIMESetAddr( &MyAddress, 0x20 );

As you can see, I’m also sending a reset before setting the new address but that is probably optional, reset code is.

/*---------------------------------------------------------------------------*/
/*                                                                           */
/*      Reset IME                                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

int
VexIMEResetAll( )
{
    char    buf[4];
    int     result;

    //
    buf[0] = IME_RESET_REG;
    buf[1] = 0xCA;
    buf[2] = 0x03;

    result = I2C_transmit_block( 0, buf, 3 );
    I2C_release();

    // give it some time to reset
    // probably don't need it really
    RTX_Sleep_Time(100);

    return(result);
}

probably not that helpful as it’s for a different processor with a different I2C API I would expect.

It works best at 100KHz although for one device 400KHz will also work.

Can you give us some more details of your code, perhaps post something we can look at? Are you getting the ACK after sending the first 8 bits or after the new address that is sent?

here is the code I was using to test this out:


  I2C1_Init(100000);                                 // initialize I2C communication
  I2C1_Start();                                      // issue I2C start signal
  I2C1_Wr(slave_address);
  I2C1_Wr(0x4D);                                   // send byte (address of reg)
  I2C1_Wr(new_slave_addr);                                   // send data (data to be written)
  I2C1_Stop();                                       // issue I2C stop signal

where slave_Address is 0x60 and new_slave_addr is 0x20. This was written in MikroC PRO using their i2c library. I2C1_Wr function sends the byte that is the argument for it. I still get a ~ACK after the slave address byte is sent. Also, sorry for the late reply, I have been a bit busy for the past day or so.

I have also tried another program that I wrote to communicate with the IME through i2c:


#include <htc.h>

#define SCL TRISC3
#define SDA TRISC4
#define SCL_IN RC3
#define SDA_IN RC4
#define INIT_WRITE 0x60
#define INIT_REG 0x4D
#define RESET_REG 0x4E

/*
*initializes ports and registers needed for the chip to do i2c
*/
void init(void){
	SCL = 1;
	SDA = 1;
	SCL_IN = 0;
	SDA_IN = 0;
	SSPCON = 0x28;
	SSPADD = 0x09;
}

/*
*causes the chip to do nothing(except count) for the specified
*amount of time in milliseconds
*/
void delay_ms(unsigned int time){
	int i, j, k;
	k = 0;
	for(i = 0; i < time; i++){
		for(j = 0; j < 10000; j++){
			k++;
		}
	}
}

/*
*if the bus is idle return 1, if the bus is busy return 0
*/
int i2cIdle(void){
	if((SSPCON2 & 0x1F) || (SSPSTAT & 0x04)){
		return 0;
	}else{
		return 1;
	}
}

/*
*starts the i2c sequence by sending the start bit
*/
void i2cStart(void){
	while(!i2cIdle());
	SEN = 1;
	SSPIF = 0;
	while(SEN);
}

/*
*ends the i2c sequence by sending the stop bit
*/
void i2cStop(void){
	while(!i2cIdle());
	PEN = 1;
	SSPIF = 0;
	while(PEN);
}

/*
*transmits the supplied byte value over i2c
*/
void i2cTransmit(unsigned char b){
	while(!i2cIdle());
	SSPBUF = b;	//puts value into the send buffer
	while(SSPIF != 1);	//wait for the bus to be done sending
	SSPIF = 0;	//reset the interrupt flag
	WCOL = 0;	//reset the write collision bit so that we don't get into trouble
	while(ACKSTAT != 0);	//wait for an acknowledge from the slave
	ACKSTAT = 1;	//reset the acknowledge bit
}

/*
*performs a random write over the i2c bus
*/
void i2cRandWrite(unsigned char dev_addr, unsigned char reg_addr, unsigned char value){
	i2cStart();	//send the start condition
	i2cTransmit(dev_addr);	//send the device address
	i2cTransmit(reg_addr);	//send the register on the device address
	i2cTransmit(value);		//send the value to be written to the register
	i2cStop();				//send the stop condition
}

/*
*resets the IME 393
*/
void i2cResetDevice(unsigned char gen_call, unsigned char reg_addr, unsigned char msb_value, unsigned char lsb_value){
	i2cStart();		//send the start condition
	i2cTransmit(gen_call);		//send the general call address
	i2cTransmit(reg_addr);		//send the register address
	i2cTransmit(msb_value);		//send the command byte
	i2cTransmit(lsb_value);		//send the reset device byte
	i2cStop();		//send the stop condition
	delay_ms(1000);	//delay for approximately 1 second
}

int main(void){
	init();
	i2cResetDevice(0x00, RESET_REG, 0xCA, 0x03);
	i2cRandWrite(INIT_WRITE, INIT_REG, 0x20);
	return(0);
}

with some chunks of the code drawing inspiration from jpearman’s snippets of easy c. This piece of code is written in MPLAB and compiled using hi-tech C for PIC16s. I am still not getting the IME to change it’s device address, anyone have any suggestions, or spot any errors in my code? Thank you.

I don’t see any obvious errors, I will have a closer look next week when I’m back in my office.

Here is some working code for a PIC16F887. This was compiled in MPLAB X using the HI-TECH compiler. You may have to play with the delay routine for whatever board you have and also the initialization of the SSPADD register.

Make sure you have suitable pullup resistors on the SCL and SDA lines, 4.7K should work. That may have been your problem before, which board are you using to develop this on ? Your code was more or less correct, I only rewrote some as I’m not that familiar with PIC’s and wanted to start with the code I had before. The I2C functions are a mixture of what you had written and some other examples I found.

Here is the I2C code.

#include <stdio.h>
#include <stdlib.h>
#include <htc.h>

#define SCL TRISC3
#define SDA TRISC4
#define SCL_IN RC3
#define SDA_IN RC4

// I2C Init
void i2c_Init(void)
{
    SCL    = 1;
    SDA    = 1;
    SCL_IN = 0;
    SDA_IN = 0;
    SSPCON = 0x28;  // Configure as I2C master
    SSPADD = 9;     // 100KHz on my board
}

// i2c_Wait - wait for I2C transfer to finish
void i2c_Wait(void){
    while ( ( SSPCON2 & 0x1F ) || ( SSPSTAT & 0x04 ) );
}

// i2c_Start - Start I2C communication
void i2c_Start(void)
{
    i2c_Wait();
    SEN=1;
    while(SEN);
}

// i2c_Restart - Re-Start I2C communication
void i2c_Restart(void)
{
    i2c_Wait();
    RSEN=1;
    while(RSEN);
}

// i2c_Stop - Stop I2C communication
void i2c_Stop(void)
{
    i2c_Wait();
    PEN=1;
    while(PEN);
}

// i2c_Write - Sends one byte of data
void i2c_Write(unsigned char data)
{
    i2c_Wait();
    SSPBUF = data;

    //wait for the bus to be done sending
    while(SSPIF != 1);	

    //reset the interrupt flag
    SSPIF = 0;

    //reset the write collision bit so that we don't get into trouble
    //WCOL = 0;

    // Wait for ACK
    while(ACKSTAT != 0);
    //reset the acknowledge bit
    ACKSTAT = 1;	
}

// i2c_WriteBlock - send a buffer of data
void i2c_WriteBlock( unsigned char *data, int len )
{
    int     i;
    unsigned char *p = data;
    
    for(i=0;i<len;i++)
        i2c_Write( *p++ );
}

// i2c_Address - Sends Slave Address and Read/Write mode
// mode is either I2C_WRITE or I2C_READ
void i2c_Address(unsigned char address, unsigned char mode)
{
    unsigned char l_address;

    l_address=address + mode;

    i2c_Write( l_address );
}

// i2c_Read - Reads a byte from Slave device
unsigned char i2c_Read(unsigned char ack)
{
    // Read data from slave
    // ack should be 1 if there is going to be more data read
    // ack should be 0 if this is the last byte of data read
    unsigned char i2cReadData;

    i2c_Wait();
    RCEN=1;
    i2c_Wait();
    i2cReadData = SSPBUF;
    i2c_Wait();
    if ( ack ) ACKDT=0;	        // Ack
    else       ACKDT=1;	        // NAck
    ACKEN=1;                    // send acknowledge sequence

    return( i2cReadData );
}

// i2c_ReadBlock - read data from the slave device
int
i2c_ReadBlock( unsigned char *data, int len )
{
    int     i;
    unsigned char *p = data;

    // do for all buffer except last
    for(i=0;i<(len-1);i++)
        *p++ = i2c_Read( 1 );

    // last char - send nack
    *p++ = i2c_Read( 0 );

    // return value not used
    return(1);
}

and the main control code, this is just an adaptation of what I did before.

/* 
 * File:   main.c
 * Author: James Pearman
 *
 * Created on February 27, 2012, 11:33 AM
 */

#include <stdio.h>
#include <stdlib.h>
#include <htc.h>

#if defined(WDTE_OFF)
__CONFIG(WDTE_OFF & LVP_OFF);
#elif defined (WDTDIS)
__CONFIG(WDTDIS & LVPDIS);
#endif


// address and rtegister definitions for the VEX IME
#define DEFAULT_DEVICE      0x60

#define IME_VERSION_ADDR    0x00
#define IME_VENDOR_ADDR     0x08
#define IME_DEVICEID_ADDR   0x10
#define IME_STATUS_ADDR     0x20
#define IME_DATA_ADDR       0x40

#define IME_SCRATCHR_ADDR   0x60
#define IME_SCRATCHW_ADDR   0xA0

#define IME_SETDEV_REG      0x4D
#define IME_RESET_REG       0x4E

#define I2C_WRITE           0
#define I2C_READ            1

static unsigned char MyAddress = DEFAULT_DEVICE;

unsigned char tmp_buf[16];
unsigned char version_buf[16];
unsigned char vendor_buf[16];
unsigned char deviceid_buf[16];

void i2c_Init(void);
void i2c_Wait(void);
void i2c_Start(void);
void i2c_Restart(void);
void i2c_Stop(void);
void i2c_Write(unsigned char data);
void i2c_WriteBlock( unsigned char *data, int len );
void i2c_Address(unsigned char address, unsigned char mode);
unsigned char i2c_Read(unsigned char ack);
int i2c_ReadBlock( unsigned char *data, int len );

/*---------------------------------------------------------------------------*/
/*                                                                           */
/*  Dumb wait routine                                                        */
/*                                                                           */
/*---------------------------------------------------------------------------*/

void delay_ms(unsigned int time)
{
    int i, j;

    for(i = 0; i < time; i++)
        {
        for(j = 0; j < 50; j++)
            ;
        }
}

/*---------------------------------------------------------------------------*/
/*                                                                           */
/*      Reset IME                                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

int
VexIMEResetAll( )
{
    char    buf[4];
    int     result;

    //
    buf[0] = IME_RESET_REG;
    buf[1] = 0xCA;
    buf[2] = 0x03;

    i2c_Start();
    i2c_Address( 0, I2C_WRITE );
    i2c_WriteBlock( buf, 3 );
    i2c_Stop();

    // give it some time to reset
    // probably don't need it really
    delay_ms(100);

    return(1);
}

/*---------------------------------------------------------------------------*/
/*                                                                           */
/*      Set new Address in IME                                               */
/*                                                                           */
/*---------------------------------------------------------------------------*/

int
VexIMESetAddr( int *device, unsigned char newDevice )
{
    char    buf[4];
    int     result;

    //
    buf[0] = IME_SETDEV_REG;
    buf[1] = newDevice;

    i2c_Start();
    i2c_Address( *device, I2C_WRITE );
    i2c_WriteBlock(buf, 2);
    i2c_Stop();

    *device = newDevice;

    return(result);
}

/*---------------------------------------------------------------------------*/
/*                                                                           */
/*      Read vendor from IME                                                 */
/*                                                                           */
/*---------------------------------------------------------------------------*/

int
VexIMEGetVendor( int device, unsigned char *buf )
{
    int     result;

    // Send register to read
    i2c_Start();
    i2c_Address( device, I2C_WRITE );
    i2c_Write( IME_VENDOR_ADDR );
    i2c_Stop();

    // read data
    i2c_Start();
    i2c_Address( device, I2C_READ );
    i2c_ReadBlock( buf, 8 );
    i2c_Stop();

    return(result);
}

/*---------------------------------------------------------------------------*/
/*                                                                           */
/*      Read encoder data from IME                                           */
/*                                                                           */
/*---------------------------------------------------------------------------*/

int
VexIMEGetData( int device, unsigned char *buf )
{
    int     result;

    // Send register to read
    i2c_Start();
    i2c_Address( device, I2C_WRITE );
    i2c_Write( IME_DATA_ADDR );
    i2c_Stop();

    // read data
    i2c_Start();
    i2c_Address( device, I2C_READ );
    i2c_ReadBlock( buf, 8 );
    i2c_Stop();

    return(result);
}

/*---------------------------------------------------------------------------*/
/*                                                                           */

int main(void)
{
    // We have 8 leds on port D
    TRISD = 0;

    // Init I2C
    i2c_Init();

    // Reset IME
    VexIMEResetAll();

    // Set new address
    VexIMESetAddr( &MyAddress, 0x20 );

    // Get vendor
    VexIMEGetVendor( MyAddress, vendor_buf );

    // Do forever
    while (1)
        {
        // read encoder data
        VexIMEGetData( MyAddress, tmp_buf );

        // put low byte of encoder on the leds
        PORTD = tmp_buf[1];

        // Wait 10mS
        delay_ms(10);
        }

    return 0;
}

It’s not very robust and needs some error recovery code adding but this should get you started.

Thank you so much jpearman, I am using the EasyPIC6 board to do the prototyping. I will play around with the code and see if I can get it to work.

It works now, thank you jpearman and others for your help. It seemed to be some minor tweaking of the code that jpearman posted(added a few delays), and also that the pins of the i2c cables did not line up correctly with the i2c cables we set up(we were interfacing the i2c cable of the encoder through a bread board). I can now communicate with the encoder through i2c now. Thank you!