//
// Interface to DS1621 
// By PEK '2005
//
// Compiler: GCC
// MCU: ATmega88
// Clock: 32768 Hz
//
// Notes:
// Thermostatic functions are not implemented
//
// The program sets the circuit in "one shot mode" and reads the temperature.
// Temperature is written on port D.
//

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

typedef unsigned char BYTE;

// Command set
#define DS1621_READTEMP  	0xAA
#define DS1621_SETCONFIG 	0xAC
#define DS1621_STARTCONVERT	0xEE
#define DS1621_STOPCONVERT	0x22

// Configuration bits
#define DS1621_CONF_DONE	7
#define DS1621_CONF_THF		6
#define DS1621_CONF_TLF		5
#define DS1621_CONF_NVB		4
#define DS1621_CONF_POL		1
#define DS1621_CONF_1SHOT	0

#define DEVICE_ADDR (0x48 | 0x01)	// Device selection code for DS1621 and address 1

// 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;	
}

// DS1621 Standalone Command
// Returns 1 if OK
int ds1621_standalone_cmd(BYTE ucDevice, BYTE ucCmd)
{
	int iRetVal = 0;
	
	if(!i2c_start())
		return 0;

	if(i2c_select_write(ucDevice))
	{
		if(i2c_write(&ucCmd, 1))
		{
			iRetVal = 1;
		}
	}
	
	i2c_stop();
	
	return iRetVal;
}

// DS1621 Write to Register
// Returns 1 if OK
int ds1621_write_reg(BYTE ucDevice, BYTE ucCmd, BYTE* ucReg, BYTE ucLen)
{
	int iRetVal = 0;
	
	if(!i2c_start())
		return 0;

	if(i2c_select_write(ucDevice))
	{
		if(i2c_write(&ucCmd, 1))
		{
			if(i2c_write(ucReg, ucLen))
			{
				iRetVal = 1;
			}
		}
	}
	
	i2c_stop();
	
	return iRetVal;
}

// DS1621 Read from Register
// Returns 1 if OK
int ds1621_read_reg(BYTE ucDevice, BYTE ucCmd, BYTE* ucReg, BYTE ucLen)
{
	int iRetVal = 0;

	if(!i2c_start())
		return 0;
		
	if(i2c_select_write(ucDevice))
	{
		if(i2c_write(&ucCmd, 1))
		{
			if(i2c_start())
			{
				if(i2c_select_read(ucDevice))
				{
					if(i2c_read(ucReg, ucLen))
					{
						iRetVal = 1;
					}
				}
			}
		}
	}
	
	i2c_stop();
	
	return iRetVal;
}

// DS1621 Read Temperature
// Returns 1 if OK
int ds1621_read_temp(BYTE ucDevice, BYTE* ucTemp)
{
	return ds1621_read_reg(ucDevice, DS1621_READTEMP, ucTemp, 2);
}

// DS1621 Set Configuration
// Returns 1 if OK
int ds1621_set_config(BYTE ucDevice, BYTE ucConfig)
{
	return ds1621_write_reg(ucDevice, DS1621_SETCONFIG, &ucConfig, 1);
}

// DS1621 Read Configuration
// Returns 1 if OK
int ds1621_read_config(BYTE ucDevice, BYTE* ucConfig)
{
	return ds1621_read_reg(ucDevice, DS1621_SETCONFIG, ucConfig, 1);
}

// DS1621 Start Conversion
// Returns 1 if OK
int ds1621_start_convert(BYTE ucDevice)
{
	return ds1621_standalone_cmd(ucDevice, DS1621_STARTCONVERT);
}

// DS1621 Stop Conversion
// Returns 1 if OK
int ds1621_stop_convert(BYTE ucDevice)
{
	return ds1621_standalone_cmd(ucDevice, DS1621_STOPCONVERT);
}

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

int main(void)
{
	BYTE ucTemp[2];

	// 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(!ds1621_set_config(DEVICE_ADDR, _BV(DS1621_CONF_1SHOT)))	// Set configuration to 1SHOT
	{
		PORTD |= 0x01;	// Configuration error
	}
	else
	{
		if(!ds1621_start_convert(DEVICE_ADDR))	// Start conversion
		{
			PORTD |= 0x02;	// Start error
		}
		else
		{
			delay(30000);	// Wait some time, till the conversion is finished
			
			if(!ds1621_read_temp(DEVICE_ADDR, ucTemp))
			{
				PORTD |= 0x04;	// Read error
			}
			else
			{
				PORTD = ucTemp[0];	// Show the MS Byte
			}
		}
	}
	
	while(1);	// Loop forever
	
	return 1;
}
