/*
*********************************************************************************************************************
*												I2C BUS LOW LEVEL FUNCTIONS
*
* Description:  Low level IIC functions
* File:			iic.c
* Software:		Keil C (C51) compiler, eval version 7.09 under uVision2 IDE v2.40
* Programmer: 	John Leung, TechToys Co. (www.TechToys.com.hk)
* Hardware:		gLCD-STK1
* Date:			14th May 2006
* Version:		1.0
*
*********************************************************************************************************************
*/
#include <REGX52.h>
#include <intrins.h>
#include "iic.h"

/*
*********************************************************************************************************************
*													LOCAL DEFINITIONS
*********************************************************************************************************************
*/
#define HIGH				1
#define LOW					0
#define Delay5Us	{_nop_();_nop_();_nop_();_nop_();_nop_();}	//for 12MHz xtal, generic 8051

/*
*********************************************************************************************************************
*											IIC START CONDITION GENERATION
* Function:	Generate an I2C bus start condition
* Argument:	none
* Return:	none
* Note:		Hardware-dependent for direct I/O operation on SDA and SCL required.
*			Delay5Us is a macro assuming the system clock (xtal) is 12MHz or less for 12-clock 8051
*********************************************************************************************************************
*/
void i2c_Start(void)
{
	SDA = HIGH;
	SCL = HIGH;
	Delay5Us;
	SDA = LOW;		
	Delay5Us;
	SCL = LOW;
}

/*
*********************************************************************************************************************
*											IIC STOP CONDITION GENERATION
* Function:	Generate an I2C stop condition
* Argument:	none
* Return:	none
* Note:		Hardware-dependent for direct I/O operation on SDA and SCL required.
*			Delay5Us is a macro assuming the system clock (xtal) is 12MHz or less for 12-clock 8051
*********************************************************************************************************************
*/
void i2c_Stop(void)
{
	SDA = LOW;
	SCL = HIGH;
	Delay5Us;
	SDA = HIGH;
	Delay5Us;
}

/*
*********************************************************************************************************************
*											IIC ACK CONDITION GENERATION
* Function:	This function is called to generate an ACK or noACK.
* Argument:	'reply'		ACK if an acknowledge is required, i.e. SDA pulled low
*						noACK if an acknowledge not required, i.e. SDA pulled high
* Return:	none
* Warning:	The argument reply of type 'bit', which is KEIL C specific
* Note:		ACK and noACK constants defined under iic.h, public constants
*			Hardware-dependent for direct I/O operation on SDA and SCL required.		
*			Delay5Us is a macro assuming the system clock (xtal) is 12MHz or less for 12-clock 8051
*********************************************************************************************************************
*/
void i2c_Ack(bit reply)
{
	(reply==ACK)?(SDA=LOW):(SDA=HIGH);
	_nop_();_nop_();_nop_();
	SCL = HIGH;
	Delay5Us;
	SCL=LOW;
	_nop_();_nop_();
}

/*
*********************************************************************************************************************
*										IIC LOW LEVEL SEND BYTE FUNCTION
* Function:	This function is called to write a byte to the i2c bus in MBS first.
*			The function caller(master) sends a byte, and in the 9th bit, waits for an ACK or noACK
*			from the slave. The slave may reply either ACK or noACK.
*			Waiting for ACK signal is NOT infinite to avoid program hang-up.
*			Statement 'while((SDA==HIGH)&&(i++<0x80))' employed for ACK waiting with timeout
* Argument:	'c'		the byte to send
* Return:	ACK 	if the slave does acknowledge
*			noACK 	if the slave didn't acknowledge
*			ACK and noACK defined under iic.h, public constants
* WARNING : the type 'bit' is KEIL C specific
* Note:		Hardware-dependent for direct I/O operation on SDA and SCL required.
*			Delay5Us is a macro assuming the system clock (xtal) is 12MHz or less for 12-clock 8051
*********************************************************************************************************************
*/
bit i2c_SendByte(unsigned char c)
{
	unsigned char i;

	for(i=0;i<8;i++)
	{
		((c<<i)&0x80)? (SDA = HIGH):(SDA = LOW);
		_nop_();
		SCL = HIGH;
		Delay5Us;	//for data on SDA to be valid for at least 4.7usec
		SCL = LOW;
	}
	i=0;
	SDA = HIGH;		//release data line to read ACK/noACK signal from slave
	SCL = HIGH;		//take clock line high for a stable data line read
	while((SDA==HIGH)&&(i++<0x80));
	(SDA==LOW)? (i = ACK):(i = noACK);
	SCL = LOW;
	_nop_();_nop_();
	return (i);
}

/*
*********************************************************************************************************************
*											IIC LOW LEVEL READ BYTE FUNCTION
* Function:	This function is called to read a byte from the i2c bus.
* Argument:	none
* Return:	The byte read from the IIC bus
* Note:		Hardware-dependent for direct I/O operation on SDA and SCL required.
*			Delay5Us is a macro assuming the system clock (xtal) is 12MHz or less for 12-clock 8051
*********************************************************************************************************************
*/
unsigned char i2c_RcvByte(void)
{
	unsigned char val=0;
	unsigned char i;
	
	SDA = HIGH;				//release data line for input

	for(i=0; i<8; i++)		//bit counter from 0 to 7 (inclusive)
	{
		SCL = LOW;
		Delay5Us;
		SCL = HIGH;
		_nop_();_nop_();
		val = val<<1;
		if (SDA==HIGH) val = val|1;
	}

	SCL = LOW;
	_nop_();_nop_();
	return (val);
}

/*
*********************************************************************************************************************
*												SEQUENTIAL WRITE BYTES
* Function:	This function is called to sequentially write an array/string of a certain length
*			to an iic slave. STOP bit is not generated at the end of every byte written except
*			the last byte.
* Argument:	'device'	iic device address, e.g. 0xA0 for 24LC02 EEPROM, 0xA2 for PCF8563 RTC etc
*			'addrh'		internal address's high byte, if any. If address is 8-bit, 
*						this should be passed a NONE defined under iic.h
*			'addrl'		internal address's low address byte.
*			'*s'		pointer to the array/string to write
*			'length'	length of the array or string to write
* Return:	'noACK' if any of the iic write action passes a bus collision error
*			'ACK'	if all of the iic write action successful
* WARNING : the type 'bit' is KEIL C specific
* Note:		Example:
*			s[0] = 0x00; s[1] = 0x12;
*			i2c_SeqWr(0xA2, NONE, 0x00, &s[0], 2);	//This is to initialize PCF8563 rtc
*********************************************************************************************************************
*/
bit i2c_SeqWr(unsigned char device, unsigned char addrh, unsigned char addrl, unsigned char *s, unsigned char length)
{
	unsigned char i;

	i2c_Start();
	if(i2c_SendByte(device)==noACK) return (noACK);
	if(addrh!=NONE) 
	{
		if(i2c_SendByte(addrh)==noACK) return (noACK);
	}
	if(i2c_SendByte(addrl)==noACK) return (noACK);
	for (i=0;i<length;i++)
	{
		if(i2c_SendByte(*s)==noACK) return (noACK);
		s++;
	}
	i2c_Stop();
	return (ACK);
}

/*
*********************************************************************************************************************
*												SEQUENTIAL READ BYTES
* Function:	This function is called to sequentially read an array/string of a certain length
*			from an iic slave.
* Argument:	'device'	iic device address, e.g. 0xA0 for 24LC02 EEPROM, 0xA2 for PCF8563 RTC etc
*			'addrh'		internal address's high byte, if any. If address is 8-bit, 
*						this should be passed a NONE defined under iic.h
*			'addrl'		intermal address's low address byte.
*			'*s'		pointer to the array/string to read
*			'length'	length of the array or string to write
* Return:	'noACK' if any of the iic write action passes a bus collision error
*			'ACK'	if all of the iic write action successful
* WARNING : the type 'bit' is KEIL C specific. Array/string passed to *s should be large enough to hold the 
*			total data length.
* Note:		
*********************************************************************************************************************
*/
bit i2c_SeqRd(unsigned char device, unsigned char addrh, unsigned char addrl, unsigned char *s, unsigned char length)
{
	unsigned char i;

	i2c_Start();
	if(i2c_SendByte(device)==noACK) return (noACK);
	if(addrh!=NONE) 
	{
		if(i2c_SendByte(addrh)==noACK) return (noACK);
	}
	if(i2c_SendByte(addrl)==noACK) return (noACK);
	i2c_Start();
	//RTC_ADDR+1 = 1010 001R/W, R/W = 1 for READ, R/W = 0 for WRITE
	if(i2c_SendByte(device+1)==noACK) return (noACK);
	
	for(i=0; i<length-1; i++)
	{
		*s = i2c_RcvByte();
		i2c_Ack(ACK);
		s++;
	}

	*s = i2c_RcvByte();
	i2c_Ack(noACK);
	i2c_Stop();

	return (ACK);
}
