# I2c bit banging, AT89C55WD, EEPROM BL24C04 interfacing

#### Vindhyachal Takniki

Joined Nov 3, 2014
583
1. I am using AT89C55WD(40 pin DIP), with 12Mhz crystal.
Have to interface BL24C04 with it.

2. System is +5V powered & I have made bit banging code in C for it.
I have kept 50kbps speed. Took refereence from here while making code ( http://saeedsolutions.blogspot.in/2012/10/pic16f84a-i2c-bit-banging-code-proteus.html )

3. I have simualted code in proteus & seems to be working as values shown in its I2c debugger.

4. One issue I think may be its very small delay. At 50kbps , 1 bit delay is 20us, half bit delay is 10us & quarter bit delay is 5us. In codeI have used nop statements for 10us & 5us.

5. In link above, blogger has used quarter(5us)/half(10us) but delay at many places. I dont know why quarter(5us) bit delay is used. Below is code. Is it ok?

C:
sbit sda = P1^7;
sbit scl = P1^6;

#define i2c_speed_kbps     50
#define half_bit_delay_us  10     /* ( (1*1000000) / (i2c_speed_kbps*1000) )/ 2  */
#define delay_10us()       _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();
#define delay_5us()       _nop_(); _nop_(); _nop_(); _nop_(); _nop_();

{
uint8_t temp;

i2c_init();

/* write to eeprom */
i2c_start();
while( 1U == i2c_write_byte(0xa0U) )
{
i2c_start();
}
i2c_write_byte(0x00U);
/* value */
i2c_write_byte(0x05U);
/* stop */
i2c_stop();

wait_delay_us(50000);
/* write to eeprom */
i2c_start();
while( 1U == i2c_write_byte(0xa0U) )
{
i2c_start();
}
i2c_write_byte(0x00U);
i2c_restart();
i2c_write_byte(0xa1U);
i2c_send_nack();

i2c_stop();

while(1);

} /* function ends here */

void i2c_init(void)
{
sda = 1U;
scl = 1U;
}

void i2c_start(void)
{
scl = 1U;
sda = 1U;

/* half bit delay for 50kbps which is 10us for 12Mhz it is 10 nops */
delay_10us();

sda = 0U;

/* half bit delay for 50kbps which is 10us for 12Mhz it is 10 nops */
delay_10us();
}

void i2c_restart(void)
{
scl = 0U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

sda = 1U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

scl = 1U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

sda = 0U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

}

void i2c_stop(void)
{
scl = 0U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

sda = 0U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

scl = 1U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

sda = 1U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

}

void i2c_send_ack(void)
{
scl = 0U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

sda = 0U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

scl = 1U;

/* half bit delay for 50kbps which is 10us for 12Mhz it is 10 nops */
delay_10us();

}

void i2c_send_nack(void)
{
scl = 0U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

sda = 1U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

scl = 1U;

/* half bit delay for 50kbps which is 10us for 12Mhz it is 10 nops */
delay_10us();

}

uint8_t i2c_write_byte(uint8_t byte)
{
uint8_t cnt;

for(cnt = 0U ; cnt < 8U ; cnt++)
{
scl = 0U;
/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

if( (byte << cnt) & 0x80U )
{
sda = 1U;
}
else
{
sda = 0U;
}

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

scl = 1U;

/* half bit delay for 50kbps which is 10us for 12Mhz it is 10 nops */
delay_10us();
}

/* get ack from slave */
scl = 0U;
sda = 1U;

/* half bit delay for 50kbps which is 10us for 12Mhz it is 10 nops */
delay_10us();

scl = 1U;

/* half bit delay for 50kbps which is 10us for 12Mhz it is 10 nops */
delay_10us();

return sda;
}

{
uint8_t cnt;
uint8_t rxdata = 0U;
uint8_t temp;

for(cnt = 0U ; cnt < 8U ; cnt++)
{
scl = 0U;
sda = 1U;

/* half bit delay for 50kbps which is 10us for 12Mhz it is 10 nops */
delay_10us();

scl = 1U;

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

temp = sda;
rxdata = rxdata | (temp << (7U - cnt));

/* half bit delay/2 for 50kbps which is 5us for 12Mhz it is 5 nops */
delay_5us();

}
return rxdata;
}

Last edited by a moderator:

#### Papabravo

Joined Feb 24, 2006
16,111
Without seeing the assembly language output from you compiler I can't say for sure that you code will or won't work. If the part is a variation of the stock 8051 with 1 μsec instruction time @ 12 MHz., then I seriously doubt that you can make this work -- reliably.

#### Vindhyachal Takniki

Joined Nov 3, 2014
583
yes it is a part variation of 8051, why would you think it will not work??

#### Papabravo

Joined Feb 24, 2006
16,111
yes it is a part variation of 8051, why would you think it will not work??
Because at 12 MHz. each instruction takes 1 μsec to execute. Your entire loop needs to be done in 10 instructions at that rate. There are some variations of the 8051 that take less than 12 clocks per instruction cycle, but I could not immediately tell if this was one of them.

#### Vindhyachal Takniki

Joined Nov 3, 2014
583
so will it work? i saw some code examples also put while loop for clock streching , do I need it here in interfacing bl24c04 eeprom?

#### Papabravo

Joined Feb 24, 2006
16,111
so will it work? i saw some code examples also put while loop for clock streching , do I need it here in interfacing bl24c04 eeprom?
I tried to answer that question, but I can not do that without more information. I need two things:
1. The assembly language output of the compiler
2. The timing of the instructions on the processor with a 12 MHz. clock
Without those two things your guess about working or not working is as good as mine.

#### Vindhyachal Takniki

Joined Nov 3, 2014
583
Attached is complete keil code named "test.rar".
Also listing file is in "main.rar" which has assembly output

1. Osc is 12Mhz. It has 12 cycles, so 1us single instruction timing. i.e single nop takes 1us

#### Attachments

• 56.1 KB Views: 4
• 3.3 KB Views: 4

#### Papabravo

Joined Feb 24, 2006
16,111
I don't know if I can handle a .rar file. I'll get back to you.

#### dannyf

Joined Sep 13, 2015
2,197
so will it work?
Why don't you try it on real hardware?

At this point, you have done all you could with simulation and all left to do is to put it on real hardware and see how / if it works.

#### John P

Joined Oct 14, 2008
1,897
If the processor timing really is 1usec per instruction I don't see how you can lose. For all the states and transitions listed, the timing criteria for the memory chip are slower than that. Looking in the other direction (meaning, how slow can the interface be) there seems to be no minimum speed for any operation.

But I2C involves open-collector lines with pullups. I'm a PIC guy and I know a PIC processor can work that way, by manipulating the bits in the TRIS register, but I don't know about this 8051 variant. You need to be able to make your processor pins be driven low, or go hi-Z. Can they do that, and are you controlling the chip correctly?

#### Papabravo

Joined Feb 24, 2006
16,111
Bit banging is hard enough to do in straight assembly language, but using a compiler is like tying both hands behind your back.

Looking at the timing I can tell you it won't work the way you expect. The NOP's you inserted are completely useless. If you take them out completely you might actually be closer to your performance goal. I estimate that your bit times with the NOPs will be in the neighborhood of 42 μsec, which is twice what you expected them to be. In addition, the time it takes to send a byte will depend on the number of 1's and 0's in the byte. This is a very bad business since you will never be able to use a scope to look at the timing because it will be all over the map as the data changes. You should at least be able to observe SCL as a stable trace from it's initial edge.

The listing was helpful, but the compiler is playing some games with conditional jumps. I think it is smart enough to use relative jumps over short ranges. The basic bit time is computed by counting instructions from ?C0014 to the xJNE instruction just above ?C0015. I'm also assuming that symbols like AR4 and AR7 refer to direct memory locations the compiler uses to hold function arguments.

You must have assumed that the actual instructions were executed in zero time, which is what I originally suspected.

The bit banging example you tried to emulate is for a situation where the bit time is orders of magnitude larger than the instruction time. It does not apply in this case.

#### Papabravo

Joined Feb 24, 2006
16,111
If the processor timing really is 1usec per instruction I don't see how you can lose. For all the states and transitions listed, the timing criteria for the memory chip are slower than that. Looking in the other direction (meaning, how slow can the interface be) there seems to be no minimum speed for any operation.

But I2C involves open-collector lines with pullups. I'm a PIC guy and I know a PIC processor can work that way, by manipulating the bits in the TRIS register, but I don't know about this 8051 variant. You need to be able to make your processor pins be driven low, or go hi-Z. Can they do that, and are you controlling the chip correctly?
@John P
The 8051 port output structure has an active pulldown output with a weak pullup (~40K-80K) which can be externally forced low. If you look at the I2C specification this pullup is too large to quickly pull SDA/SCL up from the low state, so an external pullup is a must. @cmartinez and I had an extensive discussion this about 3 or 4 months back. He specifically was interested in the magnitude of the 8051 "weak" pullup.

#### dannyf

Joined Sep 13, 2015
2,197
Code:
[*]#define delay_10us()       _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();
[*]#define delay_5us()       _nop_(); _nop_(); _nop_(); _nop_(); _nop_();
This type of macro writing is generally not preferred. Think about someone using this:

Code:
  if (true) delay_10us();
What do you think will happen?

Using a "do...while" loop around it to avoid that kind of mistakes.

#### Papabravo

Joined Feb 24, 2006
16,111
Code:
[*]#define delay_10us()       _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_();
[*]#define delay_5us()       _nop_(); _nop_(); _nop_(); _nop_(); _nop_();
This type of macro writing is generally not preferred. Think about someone using this:

Code:
  if (true) delay_10us();
What do you think will happen?

Using a "do...while" loop around it to avoid that kind of mistakes.
I agree, but this misses the point that no delay is required in order to achieve his performance objective. In fact it is killing any chance he has of making it.

#### cmartinez

Joined Jan 17, 2007
7,332
Some chips are sync-critical when performing SPI communications, in the sense that the clock has to have an almost perfect 50% duty cycle and a steady frequency. An example of this are some ADC's out there that perform their sampling cycle based on the SPI clock.
However, since you're talking about an EEPROM, most likely it's not sync-critical, and the only restriction might be the maximum clock frequency, and not it's minimum, as John_P has already mentioned. Also, external pull up resistors might be needed, as Papabravo has already stated too.
I would need to take a look at both chip's datasheets to tell you more.

#### Vindhyachal Takniki

Joined Nov 3, 2014
583
I have checked on hardware, MCU: AT89C55WD, 12Mhz crsytal & BL24C04.
It is working.

For me exact performance is not required, because I have to read it only once on startup & write only once.

#### Vindhyachal Takniki

Joined Nov 3, 2014
583
@Papabravo,
If perofrmance not required then is code correct?

#### dannyf

Joined Sep 13, 2015
2,197
f perofrmance not required then is code correct?
Didn't you just say that the code worked on real hardware?