//
// I2C Master Example
// Interface to M24256 EEPROM 
// By PEK '2005
//
// Compiler: GCC
// MCU: ATmega88
// Clock: 32768 Hz
//
// Notes:
// The program shows how to handle I2C Master writes and reads. First the
// program writes two bytes at a specific address in the EEPROM. Then the
// program reads two bytes at the same address and presents the result on
// port D.
//

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>

typedef unsigned char BYTE;

#define EEPROM_ADDR 0x50	// Device selection code for M24256

// I2C Start
// Returns 1 if OK
int i2c_start(void)
{
	TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);	// Send start condition
	
	while(!(TWCR & _BV(TWINT)));	// Wait
	
	return((TWSR & 0xF8) == 0x08 || (TWSR & 0xF8) == 0x10);	// Return status
}

// I2C Stop
void i2c_stop(void)
{
	TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTO);	// Send stop condition
}

// I2C select device and read mode
// Returns 1 if OK
int i2c_select_read(BYTE ucDevice)
{
	TWDR = (ucDevice << 1) | 0x01;
	TWCR = _BV(TWINT) | _BV(TWEN);	// Begin transmission of address
	
	while(!(TWCR & _BV(TWINT)));	// Wait

	return((TWSR & 0xF8) == 0x40);	// Return status
}

// I2C select device and write mode
// Returns 1 if OK
int i2c_select_write(BYTE ucDevice)
{
	TWDR = ucDevice << 1;
	TWCR = _BV(TWINT) | _BV(TWEN);	// Begin transmission of address
	
	while(!(TWCR & _BV(TWINT)));	// Wait

	return((TWSR & 0xF8) == 0x18);	// Return status
}

// I2C write bytes
// Returns 1 if OK
int i2c_write(BYTE* ucData, BYTE ucLen)
{
	while(ucLen)
	{
		ucLen--;
		
		TWDR = *ucData;
		ucData++;
		TWCR = _BV(TWINT) | _BV(TWEN);	// Begin transmission of data
	
		while(!(TWCR & _BV(TWINT)));	// Wait

		if((TWSR & 0xF8) != 0x28)	// Check status
		{
			return 0;
		}
	}
	
	return 1;
}

// I2C read bytes
// Returns 1 if OK
int i2c_read(BYTE* ucData, BYTE ucLen)
{
	while(ucLen)
	{
		ucLen--;
		
		if(ucLen)
		{
			TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);	// Enable new incoming data, ack
		}
		else
		{
			TWCR = _BV(TWINT) | _BV(TWEN);	// Enable new incoming data, nack (last byte)
		}
		
		while(!(TWCR & _BV(TWINT)));	// Wait

		switch(TWSR & 0xF8)
		{
			case 0x50:
				*ucData = TWDR;
				ucData++;
				break;				
			
			case 0x58:
				if(ucLen)
				{
					return 0;	// Error
				}
				else
				{
					*ucData = TWDR;
					ucData++;					
				}
				break;
				
			default:
				return 0;
		}
	}
	
	return 1;	
}

// EEPROM Read (M24256)
// Returns 1 if OK
int ee_read(BYTE ucDevice, BYTE* ucAddr, BYTE* ucData, BYTE ucLen)
{
	int iRetVal = 0;

	if(!i2c_start())
		return 0;
		
	if(i2c_select_write(ucDevice))
	{
		if(i2c_write(ucAddr, 2))
		{
			if(i2c_start())
			{
				if(i2c_select_read(ucDevice))
				{
					if(i2c_read(ucData, ucLen))
					{
						iRetVal = 1;
					}
				}
			}
		}
	}
	
	i2c_stop();
	
	return iRetVal;
}

// EEPROM Write (M24256)
// Returns 1 if OK
int ee_write(BYTE ucDevice, BYTE* ucAddr, BYTE* ucData, BYTE ucLen)
{
	int iRetVal = 0;
	
	if(!i2c_start())
		return 0;

	if(i2c_select_write(ucDevice))
	{
		if(i2c_write(ucAddr, 2))
		{
			if(i2c_write(ucData, ucLen))
			{
				iRetVal = 1;
			}
		}
	}
	
	i2c_stop();
	
	return iRetVal;	
}

// Delay of (uiDelay*4 + 8) clock cycles
void delay(unsigned int uiDelay)
{
	while(--uiDelay);
}

int main(void)
{
	BYTE ucDataWrite[2] = {0x50, 0x05};	// Bytes to write
	BYTE ucDataRead[2] = {0x00, 0x00};
	BYTE ucAddr[2] = {0x00, 0x10};	// EEPROM address (0x00:0x10)

	// Initiate Ports
	PORTD = 0x00;
	DDRD = 0xFF;	// PortD as output

	// Initiate TWI
	PRR = ~_BV(PRTWI);	// Only enable the TWI unit
	TWSR = 0;	// Prescaler = 1
	TWBR = 10;	// Give us a bitrate of 910.2 bps
	
	if(!ee_write(EEPROM_ADDR, ucAddr, ucDataWrite, 2))	// Write byte
	{
		PORTD |= 0x01;	// Error write
	}
	else
	{
		delay(1000);	// Wait for EEPROM to finish the write
		
		if(!ee_read(EEPROM_ADDR, ucAddr, ucDataRead, 2))	// Read byte
		{
			PORTD |= 0x02;	// Error read
		}
		else
		{
			PORTD = ucDataRead[0] | ucDataRead[1];	// Show the written bytes (0x55)
		}
	}
	
	while(1);	// Loop forever
	
	return 1;
}
