This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

I2C discussion

Hi,

I am trying to use the I2C bus with a 89LV55 MCU. I have 2 devices : a 24LC16B serial eeprom and a digital pot DS1844. The MCU has no built-in facilities for the I2C bus so I created the low level routines for the communications. I believed, from the I2C bus specifications, that these functions should be adequate, but not the way I use them to access the devices. A first question would be how do you put them together ? or how to construct a program... I know I am in trouble ! I have two files : I2C.c with the I2C functions and a definition file I2C.h. The I2C functions are :

void I2C_Start(void) // start I2C communication
void I2C_Write(unsigned char data_out) // write a byte
unsigned char I2C_Read(void) // read a byte
bit I2C_Ack (void) // read acknowledge
void I2C_NoAck (void) // no acknowledge
void I2C_Delay(void) // 5 us delay for low speed (100kHz)
void I2C_Stop(void) // stop I2C communication

-----------------------------------------------------------------------------------------------------------------------
void I2C_Start(void)
{
SDA = HIGH;			/* Bus idle when SDA and SCK at level HIGH */
SCK = HIGH;
I2C_Delay();
SDA = LOW;			/* Start condition */
I2C_Delay();
//SCK = LOW;		/* I2C_READ and I2C_WRITE */
}

-----------------------------------------------------------------------------------------------------------------------
void I2C_Write(unsigned char data_out)
{
unsigned char position;
for (position = 0; position < 8; position++)
	{
	SCK = LOW;
	SDA = ((data_out & 0x80) ? 1 : 0); /* Mask to check the MSB and send 0 or 1 */
	SCK = HIGH;
	I2C_Delay();
	data_out <<= 1; /* Shift data */
	}
}

-----------------------------------------------------------------------------------------------------------------------

unsigned char I2C_Read(void)
{
unsigned char data_in, position;
SCK = LOW;
SDA = HIGH;			/* Set data pin in read mode  */
data_in = 0x00;
for (position = 0; position < 8; position++)
	{
	I2C_Delay();
	data_in <<=1;
	SCK = HIGH;
	I2C_Delay();
	data_in |= SDA;	/* Read pin state */
	SCK = LOW;
	}
return (data_in);
}
-----------------------------------------------------------------------------------------------------------------------

bit I2C_Ack (void)
{
bit ack_bit;
SCK = LOW;			/* Wait for high state of SCK for the 9th clock cycle */
I2C_Delay();
SCK = HIGH;
ack_bit = SDA; 	/* Read the acknowledge bit */
I2C_Delay();
return (ack_bit);
}
-----------------------------------------------------------------------------------------------------------------------

void I2C_NoAck (void)
{
SCK = LOW;			/* Wait for high state of SCK for the 9th clock cycle */
SDA = HIGH; 		/* Set the acknowledge bit */
I2C_Delay();
SCK = HIGH;
I2C_Delay();
}

-----------------------------------------------------------------------------------------------------------------------

void I2C_Delay(void)	// 5 us delay
{
_nop_();
}
-----------------------------------------------------------------------------------------------------------------------

void I2C_Stop(void)
{
SCK = LOW;
SDA = LOW;
SCK = HIGH;			/* Generate stop condition */
I2C_Delay();
SDA = HIGH;			/* Generate stop condition */
I2C_Delay();
}
-----------------------------------------------------------------------------------------------------------------------

  • Sorry, add to split my message into 2 parts


    Basically, I only use the 24LC16B to test my functions. The 24LC16B is a 16K serial eeprom, consisting of 8 blocks of 256 bytes (16 pages, each page consists of 16 bytes). In main.c, I want to fill the 16K, so I use a loop.

    ... // initialisation LCD, variables :command,address, value[]={0x12,0xDA,0xE4,…}...
    error = ERR_ACK_NULL; // error variable to determine if error occurred, and where (display on LCD)

    for (block=0; block<=7; block++)
    	{
    	temp = (block<<1) | (CMDWR & MASK_BLOCK);	// select block (0 to 7)
    	for (page=0; (page<=0xF0) && (!error); page += 0x10)	// select page (0 to 15)
    		{
    		I2C_Start();
    		I2C_Write(temp);
    		if (I2C_Ack())
    			{
    			error = ERR_ACK_CONTROL;
    			break;
    			}
    		I2C_Write(page);
    		if (I2C_Ack())
    			{
    			error = ERR_ACK_ADDRESS;
    			break;
    			}
    		for (i=0; i< sizeof(value) && (i<=0x0F) && (!error); i++)	//
    			{
    			I2C_Write(value[i]);
    			if (I2C_Ack())
    				{
    				error = ERR_ACK_DATA;
    				break;
    				}
    			}
    		I2C_Stop();
    		Delay(400);	// delay to avoid polling end of internal write for seeprom
    		}
    	}
    
    
    Now when I test the program, I have no error messages at all, but the first block of the seeprom is filled, others blocks are blank. LCD is working fine, since I write a message to warn me of end of I2C write. I have no oscilloscope (annoying but LCD should replace it to some extend).

    Problems and questions :

    I have changed the block origin (to1, 2, 3, 4, 5, 6, 7) and the first block is always filled but not the next ones… I added a delay to wait between each block (100ms…) still the same. When I debug the for loop, block is never incremented…I have checked the inner loop and brackets, I don't understand why. I had to add the Delay(400) to give time to the seeprom to do the internal write and to avoid the ack polling, otherwise only 16 bytes were written (page 0). I do not understand how to put in place the ack polling routine. I should send the 16 bytes, then send the

    1) I2C_Start();
    2) I2C_Write(temp);
    3) if (I2C_Ack())
    no ack from seeprom, internal write cycle so loop again to 1)
    4) I2C_Write(address);
    5) …

    When I wrote the test routine to fill the device, I used the error value to exit the loop and to tell me where it failed (no need to try to write to the seeprom if it does not receive the data, whatever the problem, and the error value give me a hint of what might be the problem) The problem with the ack polling is that if the device does not respond (ie internal write cycle) the master should keep sending a start an write (command) sequence until device acknowledges. Now I have 2 opposite behaviours : during a page write sequence, I use the I2C_Ack() condition to exit, since the device did not receive the data, but if I successively wrote a page I need to wait for the device to generate a acknowledge (and not exit …)

    Also, when checking the ack bit (9th pulses for read or write byte) in I2C_Ack() function, I checked the state of the SDA lines (should I write the bit I2C_Ack (void) as followed )

    bit I2C_Ack (void)
    {
    bit ack_bit;
    SDA=HIGH; // set pin in read mode
    SCK = LOW;	/* Wait for high state of SCK for the 9th clock cycle */
    I2C_Delay();
    SCK = HIGH;
    ack_bit = SDA; 	/* Read the acknowledge bit */
    I2C_Delay();
    return (ack_bit);
    }
    
    
    What I do not understand is the no ack (for read)
    Let say I want to read 6 bytes, at address 0x00 (whatever block, page…) . The sequence will be as followed

    I2C_start()
    I2C_write(control) //set to write and which block
    Check I2C_ack from device (should be 0)
    I2C_write(address); //set to address 0x00
    Check I2C_ack from device (should be 0)
    I2C_write(control); //set to read and which block
    Loop as long as needed (6 times)
    Check I2C_ack from de

  • the philips (owner of IIC) website has oodles and bunches of bit bang routines.

    Go there and use that as a base

    Erik

    One note:
    forget about making delay loops in C, they must be in assembler.

  • Hi Erik,

    what difference does it make in my case to have a C function or a asm function to waste 5us (@12Mhz). Since I know it takes 2 mc for a call, 1 nop and 2 mc to go back to caller, it is a known delay...
    I have seem others programs about bit bang routines, but they are normally for low level routines (start, stop condition byte read and write) but not really how to handle a device (and if things go wrong...) but I guess it is a more complex discussion..

  • "Since I know it takes 2 mc for a call, 1 nop and 2 mc to go back to caller,"

    So you think you know, do you...

    But what memory model are you using?
    What Optimisation setting?
    Even, what compiler version?!

    All of these do affect the generated code!

    With a high-level language like 'C' you have absolutely no guarantee whatsoever that any particular source construct will generate any specific sequence of machine instructions!

  • "With a high-level language like 'C' you have absolutely no guarantee whatsoever that any particular source construct will generate any specific sequence of machine instructions!"

    Unless you use Keil's language extension _nop_(), which is guaranteed to translate to a single nop. For the delay required in this case this would seem ideal, but do inline them (possibly by encapsulating them in a macro) rather than using a function.

    Stefan