Please help explain this software I2C code!

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
Hi all,

I am trying to do a software implementation of the I2C protocol on an Atmega 1284 because the dedicated pins for hardware I2C are being used. I tried writing my own software I2C library, got stuck, but found a previously written one.

I had some questions regarding how the code (attached) works:

1) In the header file, the lines:
#define SCL PD0 //PORTD.0 PIN AS SCL PIN
#define SDA PD1 //PORTD.1 PIN AS SDA PIN

PD0 and PD1 aren't predefined pin values in iom1284p.h nor is it a type that the user #typedef, what exactly do they refer to? According to the user comment, shouldn't it be PORTD0 and PORTD1 instead?

2)
I don't understand how the user defined SDA_LOW, SDA_HIGH, SCL_LOW, SCL_HIGH...
Taking SDA_HIGH for instance, I think the code tells us it's HIGH when the DDRD register at location SDA is 0? As I understand, DDRD register bits set to 0 simply means that the PORT at location SDA is set to input and it doesn't mean that the PORT at the location has a high or low value...

Anyhow, clarifications would be greatly appreciated!
 

Attachments

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
C:
/**********************************************************

Software I2C Library for AVR Devices.

Copyright 2008-2012
eXtreme Electronics, India
www.eXtremeElectronics.co.in
**********************************************************/


#ifndef _I2CSOFT_H
#define _I2CSOFT_H

/*
I/O Configuration
*/

#define SCLPORT    PORTD    //TAKE PORTD as SCL OUTPUT WRITE
#define SCLDDR    DDRD    //TAKE DDRB as SCL INPUT/OUTPUT configure

#define SDAPORT    PORTD    //TAKE PORTD as SDA OUTPUT WRITE
#define SDADDR    DDRD    //TAKE PORTD as SDA INPUT configure

#define SDAPIN    PIND    //TAKE PORTD TO READ DATA
#define SCLPIN    PIND    //TAKE PORTD TO READ DATA

#define SCL    PD0        //PORTD.0 PIN AS SCL PIN
#define SDA    PD1        //PORTD.1 PIN AS SDA PIN


#define SOFT_I2C_SDA_LOW    SDADDR|=((1<<SDA))
#define SOFT_I2C_SDA_HIGH    SDADDR&=(~(1<<SDA))

#define SOFT_I2C_SCL_LOW    SCLDDR|=((1<<SCL))
#define SOFT_I2C_SCL_HIGH    SCLDDR&=(~(1<<SCL))


/**********************************************************
SoftI2CInit()

Description:
    Initializes the Soft I2C Engine.
    Must be called before using any other lib functions.
  
Arguments:
    NONE
  
Returns:
    Nothing

**********************************************************/
void SoftI2CInit();  

/**********************************************************
SoftI2CStart()

Description:
    Generates a START(S) condition on the bus.
    NOTE: Can also be used for generating repeat start(Sr)
    condition too.
  
Arguments:
    NONE
  
Returns:
    Nothing

**********************************************************/
void SoftI2CStart();

/**********************************************************
SoftI2CStop()

Description:
    Generates a STOP(P) condition on the bus.
    NOTE: Can also be used for generating repeat start
    condition too.
  
Arguments:
    NONE
  
Returns:
    Nothing

**********************************************************/
void SoftI2CStop();

/**********************************************************
SoftI2CWriteByte()

Description:
    Sends a Byte to the slave.
  
Arguments:
    8 bit date to send to the slave.
  
Returns:
    non zero if slave acknowledge the data receipt.
    zero other wise.

**********************************************************/
uint8_t SoftI2CWriteByte(uint8_t data);

/**********************************************************
SoftI2CReadByte()

Description:
    Reads a byte from slave.
  
Arguments:
    1 if you want to acknowledge the receipt to slave.
    0 if you don't want to acknowledge the receipt to slave.
  
Returns:
    The 8 bit data read from the slave.

**********************************************************/
uint8_t SoftI2CReadByte(uint8_t ack);


#endif
C:
/**********************************************************

Software I2C Library for AVR Devices.

Copyright 2008-2012
eXtreme Electronics, India
www.eXtremeElectronics.co.in
**********************************************************/
#include <avr/io.h>
#include <util/delay.h>

#include "i2csoft.h"

#define Q_DEL _delay_loop_2(3)
#define H_DEL _delay_loop_2(5)

void SoftI2CInit()
{
    SDAPORT&=(1<<SDA);
    SCLPORT&=(1<<SCL);
  
    SOFT_I2C_SDA_HIGH;  
    SOFT_I2C_SCL_HIGH;  
      
}
void SoftI2CStart()
{
    SOFT_I2C_SCL_HIGH;
    H_DEL;
  
    SOFT_I2C_SDA_LOW;  
    H_DEL;    
}

void SoftI2CStop()
{
     SOFT_I2C_SDA_LOW;
     H_DEL;
     SOFT_I2C_SCL_HIGH;
     Q_DEL;
     SOFT_I2C_SDA_HIGH;
     H_DEL;
}

uint8_t SoftI2CWriteByte(uint8_t data)
{
   
     uint8_t i;
       
     for(i=0;i<8;i++)
     {
        SOFT_I2C_SCL_LOW;
        Q_DEL;
      
        if(data & 0x80)
            SOFT_I2C_SDA_HIGH;
        else
            SOFT_I2C_SDA_LOW;  
      
        H_DEL;
      
        SOFT_I2C_SCL_HIGH;
        H_DEL;
      
        while((SCLPIN & (1<<SCL))==0);
          
        data=data<<1;
    }
   
    //The 9th clock (ACK Phase)
    SOFT_I2C_SCL_LOW;
    Q_DEL;
      
    SOFT_I2C_SDA_HIGH;      
    H_DEL;
      
    SOFT_I2C_SCL_HIGH;
    H_DEL;  
  
    uint8_t ack=!(SDAPIN & (1<<SDA));
  
    SOFT_I2C_SCL_LOW;
    H_DEL;
  
    return ack;
   
}
uint8_t SoftI2CReadByte(uint8_t ack)
{
    uint8_t data=0x00;
    uint8_t i;
          
    for(i=0;i<8;i++)
    {
          
        SOFT_I2C_SCL_LOW;
        H_DEL;
        SOFT_I2C_SCL_HIGH;
        H_DEL;
          
        while((SCLPIN & (1<<SCL))==0);
      
        if(SDAPIN &(1<<SDA))
            data|=(0x80>>i);
          
    }
      
    SOFT_I2C_SCL_LOW;
    Q_DEL;                        //Soft_I2C_Put_Ack
  
    if(ack)
    {
        SOFT_I2C_SDA_LOW;  
    }
    else
    {
        SOFT_I2C_SDA_HIGH;
    }  
    H_DEL;
  
    SOFT_I2C_SCL_HIGH;
    H_DEL;
  
    SOFT_I2C_SCL_LOW;
    H_DEL;
          
    return data;
  
}
Moderators note: used code=c for annotation
 
Last edited by a moderator:

JohnInTX

Joined Jun 26, 2012
4,787
PD0 and PD1 aren't predefined pin values in iom1284p.h nor is it a type that the user #typedef, what exactly do they refer to? According to the user comment, shouldn't it be PORTD0 and PORTD1 instead?
2)
I don't understand how the user defined SDA_LOW, SDA_HIGH, SCL_LOW, SCL_HIGH...
Taking SDA_HIGH for instance, I think the code tells us it's HIGH when the DDRD register at location SDA is 0? As I understand, DDRD register bits set to 0 simply means that the PORT at location SDA is set to input and it doesn't mean that the PORT at the location has a high or low value...
PD0 and PD1 are PORT/BIT declarations. Different compilers/programmers have different ways to do the same thing - welcome to embedded programming! Just re-define SCL and SDA to fit your circuit. Don't forget to also define the data direction bits for both of them. The reason follows.

In I2C, high/low on SCL/SDA are generated by changing the port from input to output. To generate a high, the port is set to INPUT and the external pullup resistor pulls the bus high. For a low, the port direction is set to OUTPUT and the data bit is set to 0. A port never actively drives a '1'. This arrangement allows I2C arbitration i.e. if a device outputs a high (by setting its port to input) it then reads that port back to see if indeed it is high. If not, some other device is pulling the port low. Depending on where you are in the I2C byte sequence, that could be an ACK, a lost arbitration etc.. When more than one device is on the bus, the device sending a '1' loses to the device sending a '0' and must get off the bus.

If you haven't already done so, check out the I2C spec attached.

Good luck.
 

Attachments

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
The embedded world is pretty confusing/frustrating at times haha.

Another question I had is in the C file, there isn't anything related to setting the SCL frequency as I have seen done in the hardware implementation of the I2C in Atmega's TWI protocol.

Does bit-banging disregard the need for a constant SCL clock frequency? Rather it outputs an appropriate SCL pulse whenever read/write/start/stop happens? I have a hunch that's where the _delay_loop calls come in...

Why are the delay loops specified as they are in this code, were they, perhaps, experimentally acquired? Also, I looked into the delay.h file and they defined F_CPU at 1 Mhz whereas the Atmega1284 clocks in at 20Mhz...would these delay calls be incorrect for applications on the Atmega1284?

Thanks! :)
 

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
Also, in the function SoftI2CWriteByte,

while((SCLPIN & (1<<SCL))==0);

I was under the assumption that only the master generates the clock signal thus a PORT definition is needed and a PIN wont be...if so what is the significance of this line? It seems to say if the SCLPIN is high then write the MSB of data...isn't this redundant since you have the line

SOFT_I2C_SCL_HIGH;

preceding it which already sets the SCL high?
 

JohnInTX

Joined Jun 26, 2012
4,787
Also, in the function SoftI2CWriteByte,

while((SCLPIN & (1<<SCL))==0);

I was under the assumption that only the master generates the clock signal thus a PORT definition is needed and a PIN wont be...if so what is the significance of this line? It seems to say if the SCLPIN is high then write the MSB of data...isn't this redundant since you have the line

SOFT_I2C_SCL_HIGH;

preceding it which already sets the SCL high?
Correct, only the (current) master generates the SCL signal. The master raises SCL by making the pin an input (SOFT_I2C_SCL_HIGH) but then it must monitor the SCL line to see when it actually goes high. The slave may be holding SCL low temporarily (called 'clock stretching') while it completes some operation or is otherwise busy. When the slave is ready for data to resume, it releases SCL, SCL gets pulled up, the master detects that ( while((SCLPIN & (1<<SCL))==0); ) then it knows that the slave is ready to receive more data. The particular 'C' construct used is frequently seen to select one bit on an input port, other compilers/chips may do it differently but the result is the same i.e. it waits until SCL goes high before continuing.

A common use for clock stretching is during ACK/NAK. After the 8 data bits are received, it may take the slave some processing to decide ACK or NAK. The slave changes its SCL input to an output set to '0', causing the master to wait. The master releases SCL to make it '1' (to clock in the ACK), but the slave holds SCL low until it is ready, causing the master to wait. When its ready, the slave sets the ACK/NAK on SDA then releases SCL. The master detects SCL finally at '1' and proceeds.

And remember, the master never actively outputs a '1', it only releases the port pin (by making it an input) and the '1' is generated by the pullup resistor on SCL/SDA. That is what allows a slave to stretch SCL by holding it low without having to pull down an actively sourced '1' on the master's SCL pin. The routine you excerpt correctly does that.

Note that I said 'current' master, I2C can have multiple masters on the bus but only the current one (the one that won the last arbitration) is allowed to run SCL. If you have only master, then it can clock SCL whenever it wants as long as it respects the possible clock-stretching by the slave.
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
The embedded world is pretty confusing/frustrating at times haha.

Another question I had is in the C file, there isn't anything related to setting the SCL frequency as I have seen done in the hardware implementation of the I2C in Atmega's TWI protocol.

Does bit-banging disregard the need for a constant SCL clock frequency? Rather it outputs an appropriate SCL pulse whenever read/write/start/stop happens? I have a hunch that's where the _delay_loop calls come in...

Why are the delay loops specified as they are in this code, were they, perhaps, experimentally acquired? Also, I looked into the delay.h file and they defined F_CPU at 1 Mhz whereas the Atmega1284 clocks in at 20Mhz...would these delay calls be incorrect for applications on the Atmega1284?

Thanks! :)
Sorry, missed that post.
In the code, the SCL timing is indeed set by the various H_DEL, Q_DEL etc. These are macros that in turn evaluate to calling _delay_loop_2 with various parameters to enforce minimum times. The delay times are unknown - at least with the source you posted. Presumably, you set things up in delay.h. I think your hunch is correct that these are software delay loops since you but specify F_CPU. If they are properly written, the routines should use F_CPU to calculate how many cycles to count off the delay (faster CPU-more cycles to count for the same time). I haven't seen the delay code but usually software delays in C like this are experimentally determined. Sometimes, blocking delays like this use some interrupt driven counter to keep time but mostly they usually just count an integer down from some setting. Inspecting the delay code would tell the tale.

Bit Banging I2C doesn't require a constant SCL speed, width etc. as long as minimum cycle times, data setup and hold times are observed and in practice are likely to be pretty sloppy. Fortunately, I2C doesn't care as long as those specifics are met. When you use an onboard I2C master peripheral (like Microchip's MSSP modules) you need to specify the clock speed such that its within the limits of the various slaves etc. This spec is the maximum clock speed. Slower slaves that stretch the clock can slow down throughput quite a bit. Something like F_CPU would be used with the I2C master peripheral but would be used to load values into a set of configuration registers.

Have fun.
 

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
Good news! I've gotten the start, stop, and I2C write functions working correctly (observed from oscilloscope).

For the I2C_read function, we once again have the line
while((SCLPIN & (1<<SCL)) == 0);
but with an empty statement...why do we want the function to stay in limbo if the SCL is read as high in the read function?

Also,
if(SDAPIN & (1<<SDA))
{
data |= (0x80>>i);
}

is saying if the statement is true (aka when the SDAPIN is set as low) then we change the bit at the ith location of data to 1...shouldn't it be

if(!(SDAPIN & (1<<SDA)))
{
data |= (0x80>>i);
}

instead since we want the data bit at the ith position to be 1 only when the SDA line is high, which happens when the SDAPIN at location SDA is 0, which changes the pin to input (HIGH).
 

JohnInTX

Joined Jun 26, 2012
4,787
For the I2C_read function, we once again have the line
while((SCLPIN & (1<<SCL)) == 0);
but with an empty statement...why do we want the function to stay in limbo if the SCL is read as high in the read function?
The way I read it its waiting (possibly forever) for SCL to go high - presumably after releasing it (by setting SCL to an input). Recall that the slave may stretch the clock by holding SCL low. The master can't proceed until the clock is released by the slave and it detects a high on SCL. Unfortunately, the way this is written means that if you have a stuck slave holding the clock or faulty pullup resistor (seen both), the master will hang there forever until the situation clears up or the watchdog timer (if enabled) times out. Not a particularly good implementation but not atypical of the 'Xtreme' stuff you see on these the intertubes. In my bitbanged implementations, I set a guard timer that I watch while waiting for SCL and the like. If the bus gets stuck, I can then break out and perform a recovery procedure. That's more work but keeps things from hanging..

if(SDAPIN & (1<<SDA))
{
data |= (0x80>>i);
}
That is saying that if SDA is read as '1' (test is TRUE) than it sets the current bit in the data register. It shifts 80h by 'i' - the bit number being received - then sets the bit by ORing 'data' with the shifted result. If SDA is read as low, 'data' is unchanged i.e. the current bit is left at the default value of '0'. Its not uncommon to do bit-diddling that way i.e. start with a known default value 00000000, just set the '1' bits and leave the '0's alone. Its correct as written since a '1' on SDA means that the data is '1' as well. Your way would compliment the data.

Looks like you are making progress!
 

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
That is saying that if SDA is read as '1' (test is TRUE) than it sets the current bit in the data register. It shifts 80h by 'i' - the bit number being received - then sets the bit by ORing 'data' with the shifted result.
Okay, this is where I got a bit confused. I understand that if 1, test is TRUE. However, doesn't 1 also signify that SDAPIN is currently set as an output so it is technically LOW, not HIGH and thus the data should not be or'd with a 1?

Or does the 1 = output (low) and 0 = input (high) only pertain to PORTs and not PINs?

Which kind of rounds back to

The way I read it its waiting (possibly forever) for SCL to go high - presumably after releasing it (by setting SCL to an input).
Doesn't the line go into an infinite loop when the SCL is high because of the == 0, which is weird since high the SCL HIGH means that the slave isn't pulling the line low so the data transmission should be good to go. o_Oo_O
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
Okay, this is where I got a bit confused. I understand that if 1, test is TRUE. However, doesn't 1 also signify that SDAPIN is currently set as an output so it is technically LOW, not HIGH and thus the data should not be or'd with a 1?
Don't feel bad. I had to print it out and see for myself. The lack of comments in the source makes it harder to see what's going on. But, I think its functional. Here we go:

Generating a 1 on SCL/SDA is done by setting the port bit to an input and letting the external resistor pull the line to a '1'.
Generating a 0 on SCL/SDA is done by setting the port bit to an output and using the power up value of 0 in the port flip/flop to pull the line down. (I don't see any specific PORTx=0 so I assume they take the default - hopefully its guaranteed a 0!).
SDA starts as an input from the SOFT_I2C_SDA_HIGH macro in init. After that, it may be an output in write_byte or as the ACK in read_byte but winds up an input. So the answer to your question is no, its an input.

Doesn't the line go into an infinite loop when the SCL is high because of the == 0, which is weird since high the SCL HIGH means that the slave isn't pulling the line low so the data transmission should be good to go.
No, after releasing SCL, it waits for SCL to go high (in the infinite loop). When it does it proceeds.

The way this is written, you have to do things in the specific order required by the I2C slave to persorm a complete I2C transaction. For example, a 24C01 EEPROM slave (7 bit addressing) requires the following:

We assume the bus is idle for starters (SCL/SDA == 1) - this code does not make provision for that but its OK for now.

Writes:
Send the S(tart) condition using SoftI2CStart()
Send the control byte using SoftI2CWriteByte (1010xxx0) LSbit==0 to indicate WRITE.
Inspect the return value from SoftI2CWriteByte for ACK. If its is NAK, quit and send the stoP condition. That meas there is no slave listening. ACK/NAK should be checked after each call to SoftI2CWriteByte. If at any time a NAK is returned, terminate the transmission and send stoP.
Send the address to write to using SoftI2CWriteByte (address) - check ACK
Send the data to write using SoftI2CWriteByte(data)
Inspect the return value for ACK from the slave (meaning it can accept more data).
Repeat for the bytes to write - mindful of the buffer limits of the EEPROM.
Send stoP condition using SoftI2CStop();
Bus is now idle (whew! that was easy..)

Reads:
To READ from the EEPROM it is necessary to first write the address to read from then restart and read:
Send the S(tart) condition using SoftI2CStart().
Write the address that you want to read from by sending the control byte using SoftI2CWriteByte (1010xxx0) LSbit==0 to indicate WRITE. - check ACK as above.
Send the address to read from using SoftI2CWriteByte (address) - check ACK
Send an rS (repeatStart) condition (rS starts a new operation without releasing the bus)
Send the control byte again SoftI2CWriteByte(data with LSbit==1 to read)
Call SoftI2cReadByte(ACK/NAK) for as many bytes as you want to read. Use NAK on the last byte to tell the slave that its over.
Send stoP using SoftI2cStop() to terminate the transaction.

In short, this 'library' is a few useful routines for I2C but they are low level. You have to implement the protocol that your device requires yourself (the protocol described is for a small EEPROM - others may differ). Its not well commented which is causing you some problems but hopefully we are getting those resolved. If it were me, I would use these as low level stuff and write higher level routines that combine them to do what you want.

In EEPROM example, write two routines (ReadEE, WriteEE) that take as parameters the EEPROM address, number of bytes and the source/target address for the data to be written/read. I would punch it up a bit to return at least a general error (and not lock up on a stuck bus line!) that told you if things worked out.

bit WriteEE(unsigned int EEaddress, unsigned char ByteCount, unsigned char *source_data);
bit ReadEE(unsigned int EEaddress, unsigned char ByteCount, unsigned char *data_target);

The 'bit' return is a boolean value that just says 'it worked' or 'it didn't'. That's the important stuff. You can post error codes etc. for more info but your main routines likely only want to know if things are OK..

For an EEPROM, the R/W routines will have to know how many bytes the EE can take at one time. Other devices may have other limitations. Your read/write to a device routines have to take limitations, errors etc into account. The idea is that the main routine is isolated from the low level hardware limitations.

I attached the datasheet for the EEPROM in the example.
Have fun!

EDIT: fixed r/w sequence.
 

Attachments

Last edited:

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
Thanks John for the detailed input! I see where I went wrong, I basically read the definition of 1 as output, aka low and 0 as input, aka high which drove my thought process the opposite of what the code intended to do haha.

I think your suggestion of writing higher level routines is a good way to go so I began to write them. As for a read function, I have it written like this.

C:
uint8_t I2C_write_regis(uint8_t addr, uint8_t regis, unit8_t data)
{
    uint8_t error; //1 = no error, 0 == error
    SoftI2CStart(); //initiates I2C start condition
    if(uint8_t SoftI2CWriteByte(uint8_t addr)) //writes to the slave devices with address addr, if ack then
    {
        if(uint8_t SoftI2CWriteByte(uint8_t regis)) //writes to the register of the slave device, if ack then
        {
            if(uint8_t SoftI2CWriteByte(uint8_t regis)) //writes data to register of slave device, if ack then no error
            {
                error = 1;              
            }
            else
            {
                error = 0;
            }
        }
        else
        {
            error = 0;
        }
    }
    else
    {
        error = 0;
    }
    SoftI2CStop(); //intiates I2C stop condition
    return error;
}

uint8_t I2C_read_regis(unsigned int addr, unsigned int regis)
{
    uint8_t data; //0 == error, all other values is data
    SoftI2CStart(); //initiates I2C start condition
    if(uint8_t SoftI2CWriteByte(uint8_t addr)) //writes to the slave devices with address addr as read operation, if ack then
    {
        if(uint8_t SoftI2CWriteByte(uint8_t regis)) //writes to the register of the slave device as read operation, if ack then
        {
            data = I2C_read(ack); //reads the register data
        }
        else
        {
            data = 0;
        }
    }
    else
    {
        data = 0;
    }
    SoftI2CStop(); //intiates I2C stop condition
    return data;
}
I'm not sure if the way I wrote it is overkill with all the if's. Is a repeated start for read required? Since for my application, there is only 1 master I thought read would simply be:
1) start
2) write to slave address as read op
3) write to register on slave as read op
4) read in data from the register
5) stop

Thanks!

Moderators note: Used code=c tags
 
Last edited by a moderator:

ErnieM

Joined Apr 24, 2011
8,377
Is a repeated start for read required?
Yeah it usually is required, see John's READ description above. I2C is pedantic with read and write, so if you write an address for a register you must then restart to read the data. You also must send the address both times too.

Exactly what is needed will be documented in the data sheets for the specific slave you are using.
 

JohnInTX

Joined Jun 26, 2012
4,787
Is a repeated start for read required?
It depends on the slave. In the EEPROM example, reads come from the EE's current address, whatever that is. If you want to read from random addresses, you have to first write the EE's address to set the current address. Reads after that will come from that current address, auto-incrementing after each byte. You can do this in two separate transactions with Start and stoP on each in a single master system. The repeatStart (rS) is to allow multiple operations without sending stoP and releasing the bus halfway through your operation. In a multi-master system, stoP means bus is idle and another master can jump in, maybe messing with the same slave.

Of course, different slaves may require different sequences of reads and writes to internal registers etc. The low level I2C is the same.

As far as writing code with overkill, that's just fine - especially during development. I like to keep things open and breezy while debugging and tighten up as necessary later.

EDIT: ErnieM beat me to it.
Have fun!
 

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
Hi all,

After some work trying to get the communication between my MCU and peripheral talking, I still haven't been successful in attaining the correct data.

I've taken a screen shot of the O-scope during a write function. The data written is 10111010, from the waveform I can clearly see the start, stop, and the data correctly transmitted. However, the part that seems to not be correct is ACK phase. According to the write function, I am expecting SDA to go high before SCL goes high...not after.

However, technically what I am seeing is an ACK since SDA is "low" during a high SCL. Could it be possible that the slave was pulling the SDA line low even before the SCL went high?
 

Attachments

JohnInTX

Joined Jun 26, 2012
4,787
@indeedisuper
Could it be possible that the slave was pulling the SDA line low even before the SCL went high?
That's exactly what it did.

What you have circled is OK and not unusual. To send ACK, the slave must grab SDA after the 8th SCL and hold it low through the 9th SCL- it can release SDA anytime after the 9th SCL goes back low.

In your shot, on the 9th clock pulse, the slave has held SDA low for the ACK. The slave releases SDA on the falling edge of SCL. The spike you see is SDA being pulled up by the bus resistor after the slave releases it but before the master takes over and re-asserts SDA=0. Spikes like this on SDA are not uncommon in bit banged I2C, as long as a SCL=0.

What you see here is Start, 101 1101 (7 bit address) and the 8th bit = 0 (write). The slave ACKS by pulling SDA low for the 9th SCL then releases the bus (the spike). The master then takes over SCL and generates the stoP condition.

Since the slave ACK'ed, its time to continue the slave write (before sending stoP).

Looks OK from here so far.

EDIT: I don't know what your budget is but I wouldn't be without my Beagle I2C Analyser from TotalPhase. Excellent tool!
 
Last edited:

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
Okay thanks, that gives me some ease of mind knowing that the oddity is acceptable for bit-banging. Just saw a Youtube video of the Beagle, it sure would save time...but dang $300+ dollars! Maybe sometime in the future!
 

JohnInTX

Joined Jun 26, 2012
4,787
Okay thanks, that gives me some ease of mind knowing that the oddity is acceptable for bit-banging. Just saw a Youtube video of the Beagle, it sure would save time...but dang $300+ dollars! Maybe sometime in the future!
Understand. Oh well, just keep posting those scope shots. I've looked at so much I2C on a scope I can read it like text. Looks like you will too. If not now, soon!

Carry on!
 

Thread Starter

indeedisuper

Joined Oct 14, 2015
21
I delved a little more into some O-scope captures to see what's going on with the I2C communication. I couldn't attain correct temperature data from the LPS25H sensor chip so I went and did a simple test to see if the I2C_read is working (I2C_write is working fine).

There is a register with address 0x0F in the sensor that contain's the device ID called the WHO_AM_I register with the value of 0xBD as the device ID. I wanted to see if I can read out the device ID...which it did! However, looking at the O-scope capture, there seems to be a lot of mistiming between clock pulses and the SDA pulses during the read part of the protocol...it's a wonder how it still read out the correct ID? I circled some of the parts that worried me...should this be a concern?


Code:
unsigned char I2C_read_regis(unsigned char addr, unsigned char regis)
{
    unsigned char data; //0 = error, all other values is data
    I2C_start(); //initiates I2C start condition
    if(I2C_write(addr) == 1) //writes to the slave device with address addr as write operation, if ack then
    {
        if(I2C_write(regis)) //writes to the particular register of the slave device as write operation, if ack then
        {
            I2C_start(); //repeat start
            if(I2C_write(addr + read_op) == 1)
            {
                data = I2C_read(0); //reads the register data
            }
            else
            {
                data = 0;
            }
        }
        else
        {
            data = 0;
        }
    }
    else
    {
        data = 0;
    }
    I2C_stop(); //intiates I2C stop condition
    return data;
}
 

Attachments

JohnInTX

Joined Jun 26, 2012
4,787
Yeah! Interesting. A couple of things jump out:

First, you're well on your way to being an I2C badass. Nice work so far.

As to your question:
I agree. I took a look at your first circled timing relationship after the rS and that's a stoP. By spec.

But a lot of simple slaves, including EEPROMs and the others I've used that don't have their own clock source, use the rising edge of SCL to click their internal state machines (in this case to the next data bit), relying on the fact that most I2C masters clock the data on the falling edge of SCL. Weird, yes. I actually took another look at the I2C spec and (without spending a lot of time on it) see your point... The spec says no SDA transitions when SCL is high. That would be a Start or stoP. But for lots of simple slaves, its not the case if the transition is in the data. I found it out on an LM75 implementation way back when.

And.. you are getting correct data and that's good! I think you can carry on.

A couple of more things:
Define NAK as 0 and ACK as 1 in the lib. code so that you can easily specify it when you call readI2C. I'd appreciate it and you will too when you revisit your code later and want to know what the heck you did :)

Simple slaves like this are notorious for counting clocks before looking for things like stoP. Your code should take that into account. For example, suppose you were in the middle of a byte when your processor crashed. You'd resume after resetting but your I2C slave(s) would still be looking for bit 6 before accepting a stoP sequence. If you have bad luck (and you WILL), you can have a slave holding SDA thinking its outputting bit 6 or 4 or whatever. The solution which you should implement is to 1) detect a bus problem and 2) clock the bus until both SCL and SDA are 1 then generate a stoP. Be prepared to do it several times.

What? Not in the xTREME library?? That's why I write my own...

You are doing great. Well done! Carry on.
 
Last edited:
Top