/****************************************************************************
i2c.c
v1.0 (16/04/2003)
Software implementation of the I2C protocol
Low level I2C I/O functions for HI-TECH PIC C - master mode only.
Input and output may be on the same resister. You need to edit i2c.h to
define SCL (clock) and SDA (data) pin.
Written by Philippe Corbes <philippe.corbes@laposte.net>
This code is free for personal use.
You need my agreement for a commercial use.
****************************************************************************/
#include <pic.h>
#include "delay.h"
#include "i2c.h"
signed char
i2c_start(void)
{
/*---------------------------------------------------------------------------
* Send (re)start condition
* Check clock lines at each states
* Return: - I2C_BUSY on bus busy
* - I2C_OK if bus free
* ____________
* scl / \
* _____/ \
* __________
* sda \/ \
* / \_______
* x x x Check bus, return I2C_BUSY on error
*---------------------------------------------------------------------------
*/
DelayUs(I2C_TBUF); /* bus free time before a restart */
SDA_HIGH(); /* data is high */
DelayUs(I2C_TSU_DAT);
SCL_HIGH(); /* clock is high */
if(i2c_SCLTimeout() || (!SDA)) /* wait for clock and data released */
return I2C_BUSY; /* bus busy by an other node */
DelayUs(I2C_TSU_STA);
if((!SCL) || (!SDA)) /* Check if bus always free */
return I2C_BUSY; /* bus busy by an other node */
SDA_LOW(); /* data high->low transition */
DelayUs(I2C_THD_STA);
if(!SCL) /* Check only clock */
return I2C_BUSY; /* bus busy by an other node */
SCL_LOW(); /* clock low */
return I2C_OK;
}
void
i2c_stop(void)
{
/*---------------------------------------------------------------------------
* Send stop condition
* It's possible to begin a new transaction just after.
* ______________
* scl \ /
* /\_________/
* ___ _________
* sda \ /
* ___/\___________/
*---------------------------------------------------------------------------
*/
SCL_LOW(); /* cock is low */
DelayUs(I2C_TSU_DAT);
SDA_LOW(); /* data is low first */
DelayUs(I2C_TLOW); /* clock low time */
SCL_HIGH(); /* cock is high */
DelayUs(I2C_TSU_STO);
SDA_HIGH(); /* float data high */
return;
}
unsigned char
i2c_SCLTimeout(void)
{
/*---------------------------------------------------------------------------
* wait for the clock line to be released by slow slaves
* - returns TRUE if SCL was not released after the
* time out period.
* - returns FALSE if and when SCL released
*---------------------------------------------------------------------------
*/
if(!SCL) {
DelayUs(I2C_TTR);
/* if the clock is still low -> bus error */
if(!SCL)
return 1;
}
return 0;
}
signed char
i2c_write(unsigned char abyte)
{
/*---------------------------------------------------------------------------
* Send a byte to the slave and acknowledges the transfer
* Return: - I2C_ERROR on bus error
* - I2C_ACK_TIMEOUT on ack timeout
* - I2C_OK if ack received from slave
*
*---------------------------------------------------------------------------
* SEND a byte to the slave
* Exit with I2C_ERROR on bus error
* ________ _____ .. __
* scl / \ / \
* ____/ \________/ \___
* __________________ __________ .. _______
* sda / \/
* \__________________/\__________ .. _______
* Write Bit 7 Write Bit 6 .. bit0
* x x Check clock, return I2C_ERROR on error
*
* THEN If Byte sent, Check for an acknowledge
* ________
* scl / \
* ______/ \
* ________________
* sda /
* /
* X Read Ack
* x Check clock, return I2C_ERROR on error
*---------------------------------------------------------------------------
*/
volatile int i;
/* SEND a byte to the slave */
for(i=7; i>=0; i--) {
/* data hold time = 0, send data now */
if ((abyte>>i)&0x01) { /* bit to send */
SDA_HIGH();
} else {
SDA_LOW();
}
DelayUs(I2C_TSU_DAT);
SCL_HIGH(); /* clock high */
if(i2c_SCLTimeout()) /* wait for clock release */
return I2C_ERROR; /* bus error */
DelayUs(I2C_THIGH); /* clock high time */
SCL_LOW(); /* clock low */
DelayUs(I2C_TLOW - I2C_TSU_DAT);
}
/* THEN If Byte sent, Check for an acknowledge */
SDA_HIGH(); /* disable data line - listen for ack */
DelayUs(I2C_TSU_DAT); /* SCL low to data out valid */
SCL_HIGH(); /* float clock high */
if(i2c_SCLTimeout()) /* wait for clock release */
return I2C_ERROR;
DelayUs(I2C_THIGH);
if (!SDA) { /* read the acknowledge */
i = I2C_OK;
} else {
i = I2C_ACK_TIMEOUT;
}
SCL_LOW(); /* clock low */
return i;
}
int
i2c_read(unsigned char more)
{
/*---------------------------------------------------------------------------
* Read a byte from the slave and acknowledges the transfer
* Return: - the byte
* - I2C_ERROR on bus error
*---------------------------------------------------------------------------
* READ a byte from the slave
* - returns the byte, or I2C_ERROR if a bus error
* ________ ____ .. __
* scl / \ / \
* _____/ \________/ \___
* _____________________________ .. _______
* sda /
* / X Read bit 7 X Read bit 0
* x x Check clock, return I2C_ERROR on error
*
* THEN Send an (~)acknowledge to the slave
* - status of I2C_LAST implies this is the last byte to be sent
* ________ ________
* scl / \ / \
* ____/ \ ____/ \
* OR ______________
* sda \ /
* \______________ /
* Write Ack OR Write Nack
* x x Check clock, return I2C_ERROR on error
*---------------------------------------------------------------------------
*/
volatile unsigned char i;
volatile unsigned char abyte;
/* READ a byte from the slave */
SDA_HIGH(); /* release data line */
for(i=0; i<8; i++) {
DelayUs(I2C_TSU_DAT); /* min clock low period */
SCL_HIGH(); /* float clock high */
if(i2c_SCLTimeout())
return I2C_ERROR;
DelayUs(I2C_THIGH);
abyte = abyte << 1; /* read the next bit */
abyte |= SDA;
SCL_LOW();
DelayUs(I2C_TLOW - I2C_TSU_DAT);
}
/* THEN Send an (~)acknowledge to the slave */
if ( more & 0x01) {
SDA_LOW(); /* drive line low -> more to come */
} else {
SDA_HIGH();
}
DelayUs(I2C_TSU_DAT);
SCL_HIGH(); /* float clock high */
if(i2c_SCLTimeout())
return I2C_ERROR;
DelayUs(I2C_THIGH);
SCL_LOW();
return (int)abyte;
}