Bit-banged i2c

Go To Last Post
29 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm using CVAVR for a ATtiny414 project. I have copied the i2c bit-banged code for AT24C08 EEPROM from the help section. By observing the SDA and SCL lines on an oscilloscope, I see that the new start-condition after the second address byte is missing. When i duplicate the i2c_start() at this point in the code, it works.

 

Is this a bug in the CVAVR bit-banged i2c library or have I missed something?

 

/* read a byte from the EEPROM */

unsigned char eeprom_read(unsigned int address) 
{
    unsigned char data;

    i2c_start();
    i2c_write(EEPROM_BUS_ADDRESS | 0);
    i2c_write(address >> 8);
    i2c_write((unsigned char) address);
    i2c_start();

    i2c_start();                           // <<<< Extra i2c_start to make the code run correctly
    i2c_write(EEPROM_BUS_ADDRESS | 1);
    data = i2c_read(0);
    i2c_stop();
    return data;
}

This topic has a solution.
Last Edited: Fri. May 7, 2021 - 10:50 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You have intrigued me.   Your code looks wrong to me.

 

From datasheet:

Write Operations BYTE WRITE: A write operation requires an 8-bit data word address following the
device address word and acknowledgment. Upon receipt of this address, the EEPROM
will again respond with a zero and then clock in the first 8-bit data word. Following
receipt of the 8-bit data word, the EEPROM will output a zero and the addressing
device, such as a microcontroller, must terminate the write sequence with a stop condition.
At this time the EEPROM enters an internally timed write cycle, tWR, to the
nonvolatile memory. All inputs are disabled during this write cycle and the EEPROM will
not respond until the write is complete (see Figure 8 on page 11).

The 24C08 chips only have 1024 bytes of EEPROM.   The EEPROM internal array location is set partly by the Slave Address and partly by an 8-bit byte.

 

Your example shows a "bigger" 24Cxx chip.  i.e. with 16-bit location involving two 8-bit bytes.

 

This is not very "legal".   You are writing "address & 0xFF" to location "address >> 8" in the Page Buffer.

The Page-Write would be initiated by an i2c_stop()

 

However you do a Repeated-Start instead.

I would expect the i2c_read() to work but the location might have incremented.

 

Pure speculation.   I don't own a 24C08 chip.  But I can try a similar sequence on a bigger 24Cxx e.g. AT24C32

I will post the TWI.h version and the I2C.h version Logic Analyser sequences.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You're right, David. I actually use  "bigger" 24Cxx chip", namely the M24C64. But that aside, the i2c_start() should anyway work and show up on the oscilloscope.

 

It doesn't unless I duplicate the call.

 

Edit: BTW, the code snippet is a 1:1 copy of the example library (apart from the extra i2c_start()).

Last Edited: Thu. May 6, 2021 - 07:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Yes,  the example should say 24C32, 64, 128, 256, 512, ...

 

Here is the CV Help example running on a Uno with a 24C32 EEPROM (Slave address 0x57)

 

The write sequence  i.e write 0x55 to location 0x00AA.

 

 

 

 

The read sequence   read location 0x00AA

 

So everything seems to work just fine.

 

David.

 

Last Edited: Thu. May 6, 2021 - 09:13 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

leifindr wrote:

  i2c_start();

  i2c_start();   

 

Hello,

 

It should be:

i2c_stop();

i2c_start();

 

I'm also use 24C256. 

 

/* read one byte from eeprom with address */
uint8_t AT24C_RandomReadbyte(uint16_t adr)
{
    uint8_t res, adrL, adrH;
    adrH = (uint8_t)(adr >> 8);
    adrL = (uint8_t)(adr & 0xFF);
    I2C_Start();
    I2C_Write(AT24C_writeadr);
    I2C_Write(adrH);
    I2C_Write(adrL);
    I2C_Stop();
    I2C_Start();
    I2C_Write(AT24C_readadr);
    res = I2C_Read_NACK();
    I2C_Stop();
    return res;
}

 

 

regards EllenR

 

Senior Electric Engineer
--------------------------
I use Atmega128, AS7, GNU GCC, Atmel ICE

Last Edited: Thu. May 6, 2021 - 10:00 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No.  The Stop is unnecessary.   You can just do a Repeated Start.  i.e. just like the example.

 

Different I2C Slaves behave differently.   The 24Cxx EEPROMs are equally happy with Stop-Start as with a RepeatedStart

Different I2C Slaves respond to Stop differently.   e.g. 24Cxx initiates a Page-Write if there have been any writes to the Page-Buffer.

 

Some I2C Slaves will only accept a RepeatedStart for some operations.

 

The I2C protocol is defined.   Individual device behaviour varies.   Study the specific datasheet for your Slave chip.

 

Oh.   Any respectable I2C library returns status values for most operations.   Always use the return value.

Many example programs blindly ignore the return values e.g. #1 and #5

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Dave,

 

Is this from a simulator or from a physical implementation? In theory there is an i2c_start() in the code where it should be, but on the i2c-lines there are no falling-SDA-while-SCL-is high where it should be. Only the duplicated i2c_start() works.

 

I find it strange that the sample code from Pavel's library is erroneous.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


 

No,  as I said.   It is from a real 24C32.

 

You can click on the image and view it in another tab.    You will see that the Start is perfectly formed.   i.e. SDA was released after the ACK.  Then pulled LOW when SCL is HIGH.

 

After I posted those images from v1.2.18 I foolishly installed v2.3.26.  Which is horrible.   Or at least I need to get used to it.

I can't see how to take a window snapshot.

 

Edit.  I got this from my old Laptop.  You can see how the ACK is read at +63us and a Start is formed at +66us and executed at 73us.

 

 

And a zoom into the Start formation.   SDA is released about 125ns after SCL is pulled LOW.   This is to ensure that the SDA is only changed when SCL is LOW.

 

 

David.

 

 

 

Last Edited: Thu. May 6, 2021 - 12:07 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

You can see how the ACK is read at +63us and a Start is formed at +66us and executed at 73us

 

Yes, no problem to see that it's OK on your screenshot. But on my scope the new start is missing until I add the 2nd i2c_start(). My hope was that somebody had experienced the same phenomenon and had an explanation.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I used a mega328P.   I don't have a Tiny414.   But I would assume that CV bit-bashes both in exactly the same way.

 

I do have a Tiny817.   I can run the program on that chip if you like.

 

Can you post a Saleae capture?   Other Logic Analyser software is available.

Can you post a scope trace?    I do find scope traces difficult to read (compared to LA traces)

 

Why are you bit-bashing?   The tiny414 has a hardware TWI peripheral.   I guess that CV supports the TWI hardware.   I can investigate with the Tiny817 if you like.

 

David.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wonder if it's something to do with the '328 being classic AVR8, and the '414 being AVR8x?

 

Comparing the .lst file of the same project compiled for two different chips would be the way to check. 

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As I said in #10.   I am quite prepared to test both CV I2C and CV TWI code on a tiny817.   Even test the CodeWizard too.

 

But I will wait until I hear back from leifindr

 

David.

 

p.s.  I know that I joined in 2005.

leifindr seems to have joined in 2002.

Brian Fairchild joind in 2001.

 

None of us are Spring Chickens !

Last Edited: Thu. May 6, 2021 - 04:49 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

But I will wait until I hear back from leifindr

 

Sorry for my absence, left work and now at home.

 

Yes, please have a check with the tiny817 part. I would gladly share a screenshot with you, but the environment where I have both the scope and my HW is under strict confidentiality and I am neither allowed to take photos nor remove anything from the premises. And I won't risk my job for this.

 

Of course I could use the TWI, but I prefer bashing for now. I feel I have more control, and since bashing is a built-in feature with CVAVR I thought I would give it a shot.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Here is 8MHz tiny817 with bit-bang I2C on PA1, PA2 (SDA, SCL)

 

 

Here is a close up.

 

 

It all looks much the same to me.

If in doubt,  this is the same C code for both mega328p and for tiny817

 

/* include the I2C bus functions
   The I2C bus connections and bit rate must be specified in the
   Project|Configure|C Compiler|Libraries|I2C menu */
#include <i2c.h>

/* function declaration for delay_ms */
#include <delay.h>

#define I2C_7BIT_DEVICE_ADDRESS 0x57  //.kbv 24C32 on breakout board
#define EEPROM_BUS_ADDRESS (I2C_7BIT_DEVICE_ADDRESS << 1)

/* read a byte from the EEPROM */
unsigned char eeprom_read(unsigned int address)
{
    unsigned char data;
    i2c_start();
    i2c_write(EEPROM_BUS_ADDRESS | 0);
    /* send MSB of address */
    i2c_write(address >> 8);
    /* send LSB of address */
    i2c_write((unsigned char) address);
    i2c_start();
    i2c_write(EEPROM_BUS_ADDRESS | 1);
    data = i2c_read(0);
    i2c_stop();
    return data;
}

/* write a byte to the EEPROM */
void eeprom_write(unsigned int address, unsigned char data)
{
    i2c_start();
    i2c_write(EEPROM_BUS_ADDRESS | 0);
    /* send MSB of address */
    i2c_write(address >> 8);
    /* send LSB of address */
    i2c_write((unsigned char) address);
    i2c_write(data);
    i2c_stop();
    /* 10ms delay to complete the write operation */
    delay_ms(10);
}

void main(void)
{
    unsigned char i;
    /* initialize the I2C bus */
    i2c_init();
    /* write the byte 55h at address AAh */
    eeprom_write(0xaa, 0x55);
    /* read the byte from address AAh */
    i = eeprom_read(0xaa);
    while (1); /* loop forever */
}

 

Edit.   Oops.  I made no attempt to set the CPU CLK registers.    The bus is only operating at 35kHz and has a visible Repeated_Start.

 

When I set clocks properly the Repeated_Start disappears.   So I built another program with the Wizard.   I will post it later.  Possibly tomorrow.

Last Edited: Thu. May 6, 2021 - 08:45 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

I get a Repeated_Start with default 16MHz div6 = 2.66MHz

Also with 16MHz div2 = 8MHz.  Note that the bus is 84kHz and not 100kHz that I expected.

 

 

 

 

 

 

 

But I lose it with 16MHz div1 = 16MHz.   Bus speed=103kHz which is as expected.

 

 

Hey-Ho.  I have no idea why.

 

David.

 

Last Edited: Thu. May 6, 2021 - 09:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The wizard generated code contains quite a few calls to inbuilt delay functions of varying length controlled by wizard calculated magic numbers. I wonder if something is wrong there? Certainly worth making a reliable example and sending it to Pavel.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

Hey-Ho.  I have no idea why.

 

What a relief! I also use "16MHz div. 1". I think I'll just switch to another clock setting and continue my implementation.

 

Thanx David for your efforts in finding the cause for this behaviour. For a while I started doubting myself ;)

 

Leif 

Last Edited: Fri. May 7, 2021 - 07:33 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I intend to investigate this this morning.   Everything is visible in the .ASM file.   All the internal delay calls are constructed, invoked in the expected way.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see that bit banged I2C for AVR8x was only added in v3.41, two versions ago. Maybe it still has a few wrinkles that need ironing out,

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I compared the ASM for 4MHz versus the ASM for 16MHz.   The user functions are identical.   The optimisations are identical.   My only suspicion is with:

_i2c_start:
	cbi  __i2c_dir_sda,__sda_bit
	cbi  __i2c_dir_scl,__scl_bit
	clr  r30
	nop
	sbis __i2c_pin_sda,__sda_bit
	ret
	sbis __i2c_pin_scl,__scl_bit
	ret
	rcall __i2c_delay1
	sbi  __i2c_dir_sda,__sda_bit
	rcall __i2c_delay1
	sbi  __i2c_dir_scl,__scl_bit
	ldi  r30,1
__i2c_delay1:
	ldi  r22,2
	rjmp __i2c_delay2l

Note that CBI and SBI are 1-cycle on AVR8X whereas they are 2-cycle on AVR.    So it is possible that the SDA, SCL lines are released but the subsequent SBIS instructions are "too soon".   i.e. require some NOPs

 

A Logic Analyser is nice to use but does not capture the slope of the shark-fin.   I will take the board downstairs and look with an oscilloscope.   Or perhaps I will just add some stronger pullups.    Last night I posted an 8MHz trace that seemed to have the Repeated_Start.   This morning 8MHz fails.   4MHz shows a well constructed Repeat_Start.

 

However,  a cup of tea comes first.

 

David.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am pretty sure that this was the problem.  So I tried an "extra delay" after releasing SDA, SCL

void i2c_start_asm(void)   //.kbv add an amended version of i2c_start()
{
#asm
	cbi  __i2c_dir_sda,__sda_bit
	cbi  __i2c_dir_scl,__scl_bit
	clr  r30
	;nop   ;CV just uses one NOP
        rcall __i2c_delay1_asm    ;use half-period (5us) is safer
	sbis __i2c_pin_sda,__sda_bit
	ret
	sbis __i2c_pin_scl,__scl_bit
	ret
	rcall __i2c_delay1_asm
	sbi  __i2c_dir_sda,__sda_bit
	rcall __i2c_delay1_asm
	sbi  __i2c_dir_scl,__scl_bit
	ldi  r30,1
__i2c_delay1_asm:
	ldi  r22,5
	rjmp __i2c_delay2l
#endasm
}

/* read a byte from the EEPROM */
unsigned char eeprom_read(unsigned int address)
{
	unsigned char data;
	i2c_start();
	i2c_write(EEPROM_BUS_ADDRESS | 0);
	/* send MSB of address */
	i2c_write(address >> 8);
	/* send LSB of address */
	i2c_write((unsigned char) address);
	i2c_start_asm();  //.kbv use the amended function
	i2c_write(EEPROM_BUS_ADDRESS | 1);
	data = i2c_read(0);
	i2c_stop();
	return data;
}

I suspect that the i2c_start() might fail in the same way with a regular AVR too.   e.g. if there is high capacitance and inadequate pullups.

 

David.

Last Edited: Fri. May 7, 2021 - 08:47 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


Hmmm...

 

 

 

...does that first cbi/cbi combination meet the i2c spec? Even with a 2-cycle opcode it seems tight.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
A Logic Analyser is nice to use but does not capture the slope of the shark-fin.  

yes

 

See also: https://www.avrfreaks.net/commen...

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would guess that few people bit-bang I2C on a fast AVR.

 

Yes,  the two CBI instructions would be tSU:STA = 62.5ns which is well short of 600ns.   However I bet that 24Cxx will cope ok.

 

The real problem is reading SDA to check whether it has been released.  i.e.  risen up the shark fin from the previous ACK state (low).

 

David.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


david.prentice wrote:

Yes,  the two CBI instructions would be tSU:STA = 62.5ns which is well short of 600ns.   However I bet that 24Cxx will cope ok.

 

Maybe, but still out of spec...

 

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Yes,  I agree that it is out of spec.    I am just saying that it "works" on a real 24C32.

 

Anyway,  I ran the same program but with TWI on the same tiny817 @ 16MHz with a bus frequency = 100kHz.   Note that PA1, PA2 are the alternate TWI0 pins.

 

The Repeated_Start is perfectly formed and executed.    Since you have a choice of TWI0 pins it seems unlikely that anyone would choose to bit-bang when TWI is available.  And considerably simpler:

unsigned char eeprom_read(unsigned int address)
{
    unsigned char data, txbuf[2], ret;
    txbuf[0] = address >> 8;
    txbuf[1] = address;
    ret = twi_master_trans(&twi0_master,EEPROM_TWI_BUS_ADDRESS,txbuf,2,&data,1);
    return data;
}

 

Last Edited: Fri. May 7, 2021 - 10:11 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think I will switch to TWI, probably the best "fix" :)

 

Credit to David for taking the time to sort out the issue!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

time to mark the solution, then?

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:

time to mark the solution, then?

 

Done!