//*****************************************************************************
// File Name	: ds2450.c
// Title		: Dallas 1-Wire DS2450 A2D Sensor Library
// Revision		: 5
// Notes		: 
// Target MCU	: Atmel AVR series
// Editor Tabs	: 4
// 
//*****************************************************************************

//----- Include Files ---------------------------------------------------------
#include <string.h>			// include string support
#include "timer128.h"
#include "dallas.h"			// include dallas support
#include "ds2450.h"			// include ds2450 support
#include "rprintf.h"

//----- Functions ---------------------------------------------------------------

/*--------------------------------------------------------------------------
 * ds2450Chan2Addr: converts the channel to the address in RAM
 * input........... channel - the channel to get the address for [A-D]
 *                  page - the page in RAM that we are dealing with
 *                  address - where the address is stored
 * returns......... the corresponding error or DALLAS_NO_ERROR
 *-------------------------------------------------------------------------*/
static u08 ds2450Chan2Addr(u08 channel, u08 page, u16 *address);

void ds2450Init(void)
{
	// initialize the dallas 1-wire
	dallasInit();
}

u08 ds2450Setup(dallas_rom_id_T* rom_id, u08 channel, u08 resolution, u08 range)
{
	u08 error;
	u08 data[2];
	u16 address;

	// check resolution
	if (resolution > DS2450_RES_MAX)
		return DALLAS_RESOLUTION_ERROR;

	// check address
	error = dallasAddressCheck(rom_id, DS2450_FAMILY);
	if (error != DALLAS_NO_ERROR)
		return error;

	// get address
	strupr(&channel);
	error = ds2450Chan2Addr(channel, DS2450_SETUP_PAGE, &address); 	//find starting address
	if (error != DALLAS_NO_ERROR)
		return error;

	// convert to valid resolution - 16 bits = 0x00
	if (resolution == 16)
		resolution = 0x00;

	// read in current digital output settings
	error = dallasReadRAM(rom_id, address, 1, data);
	if (error != DALLAS_NO_ERROR)
		return error;

	// maintain digital output portion and add new resolution
	data[0] = (data[0] & 0xF0) | resolution;
	
	// maintain alarm states and add new range
	data[1] = (data[1] & 0xFE) | range;

	// actually write config, handles CRC too
	error = dallasWriteRAM(rom_id, address, 2, data);
	if (error != DALLAS_NO_ERROR)
		return error;

	// Normally, the DS2450 is designed to run off of parasite power from the data line
	// Typically the master (us) strongly pulls high long enough to power the conversion, so
	// there is inherintly a long () delay introduced. Since the A2D code is designed to
	// work for devices that use external power, we can elliminate this delay by writting
	// the following byte per the DS2450 datasheet.
	data[0] = DS2450_VCC_FLAG;
	error = dallasWriteRAM(rom_id, DS2450_VCC_ADDR, 1, &data[0]);
	if (error != DALLAS_NO_ERROR)
		return error;

	// verify the data
	error = dallasReadRAM(rom_id, address, 2, data);
	if (error != DALLAS_NO_ERROR)
		return error;

	if ((data[0] & 0x0F) != resolution)
		return DALLAS_VERIFY_ERROR;
	if ((data[1] & 0x01) != range)
		return DALLAS_VERIFY_ERROR;

	return DALLAS_NO_ERROR;
}

u08 ds2450Start(dallas_rom_id_T* rom_id, u08 channel)
{
	u08 mask;
	u08 error;
	u08 crc[2];

	// check address
	error = dallasAddressCheck(rom_id, DS2450_FAMILY);
	if (error != DALLAS_NO_ERROR)
		return error;

	// make sure the channel is a capital letter
	strupr(&channel);
	// convert to integer 0 to 3
	channel -= 'A';

	// make sure channel is a valid value
	if (channel > 3)
		return DALLAS_INVALID_CHANNEL;

	// shift over to construct input select mask
	mask = 0x01 << channel;

	// reset and select node
	error = dallasMatchROM(rom_id);
	if (error != DALLAS_NO_ERROR)
		return error;

	// send convert command
	dallasWriteByte(DS2450_CONVERT);
	// input select mask
	dallasWriteByte(mask);
	// shift over some more for "read-out" control
	mask = mask << channel;
	
	// set coresponding output buffer to zero	
	dallasWriteByte(mask);

	// we must read 2byte CRC16 to start the conversion:
	crc[0] = dallasReadByte();
	crc[1] = dallasReadByte();

	//replace with explicit CRC posibilities lookup table
//	if (crc[0] == 0xFF && crc[1] == 0xFF)
//		return DALLAS_DEVICE_ERROR;        //if CRC = all one's, no one is paying attention

	return DALLAS_NO_ERROR;
}

u08 ds2450Result(dallas_rom_id_T* rom_id, u08 channel, u16* result)
{
	u08 data[2];
	u08 error;
	u16 address;
	u08 resolution;

	// check address
	error = dallasAddressCheck(rom_id, DS2450_FAMILY);
	if( error != DALLAS_NO_ERROR)
		return error;

	// get the RAM address for the data for the channel
	strupr(&channel);
	error = ds2450Chan2Addr(channel, DS2450_DATA_PAGE, &address);
	if (error != DALLAS_NO_ERROR)
		return error;

	// read the RAM from the device to get the data
	error = dallasReadRAM(rom_id, address, 2, data);
	
	// get the address for the setup for the channel
	error = ds2450Chan2Addr(channel, DS2450_SETUP_PAGE, &address); 	//find starting address
	if (error != DALLAS_NO_ERROR)
		return error;
	
	// read the RAM from the device to get the resolution
	error = dallasReadRAM(rom_id, address, 1, &resolution);
	if (error != DALLAS_NO_ERROR)
		return error;

	// get the resultion part of the data
	resolution &=0x0F;

	// store the result by combining the 2 bytes
	// the result's MSB is always the same, so we may need to
	// shift the data over so that the LSB is at the first bit
	*result = 0;
	//*result = (((u16)data[1] << 8) | data[0]) >> (16 - resolution);
	*result = (((u16)data[1] << 8) | data[0]);

	return DALLAS_NO_ERROR;
}

u08 ds2450StartAndResult(dallas_rom_id_T* rom_id, u08 channel, u16 *result)
{
	u08 error;

	// start conversion
	error = ds2450Start(rom_id, channel);
	if (error != DALLAS_NO_ERROR)
		return error;
	
	// wait till conversion done
	dallasWaitUntilDone();
	
	// return results
	return ds2450Result(rom_id, channel, result);
}

u08 ds2450SetupAll(dallas_rom_id_T* rom_id, u08 resolution, u08 range)
{
	u08 i;
	u08 error;
	u08 data[8];
	u16 address;

	// check address
	error = dallasAddressCheck(rom_id, DS2450_FAMILY);
	if (error != DALLAS_NO_ERROR)
		return error;

	// check resolution
	if (resolution > DS2450_RES_MAX)
		return DALLAS_RESOLUTION_ERROR;

	// convert to valid resolution - 16 bits = 0x00
	if (resolution == 16)
		resolution = 0;

	// get address - start with channel A
	error = ds2450Chan2Addr('A', DS2450_SETUP_PAGE, &address);
	if (error != DALLAS_NO_ERROR)
		return error;

	// read in current settings so we can extract digital part
	error = dallasReadRAM(rom_id, address, 8, data);
	if (error != DALLAS_NO_ERROR)
		return error;

	// build up config data to write - increment by 2 b/c two bytes per channel
	for(i=0;i<8;i+= 2)
	{ 
		// maintain digital output settings
		data[i] &= 0xF0;	// extract digital output portion
		data[i+1] &= 0xFE;

		// write resolution byte and range
		data[i] |= resolution;
		data[i+1] |= range;
	}

	// actually write config - handles CRC too
	error = dallasWriteRAM(rom_id, address, 8, data);
	if (error != DALLAS_NO_ERROR)
		return error;

	// Normally, the DS2450 is designed to run off of parasite power from the data line
	// Typically the master (us) strongly pulls high long enough to power the conversion, so
	// there is inherintly a long () delay introduced. Since the A2D code is designed to
	// work for devices that use external power, we can elliminate this delay by writting
	// the following byte per the DS2450 datasheet.
	data[0] = DS2450_VCC_FLAG;
	error = dallasWriteRAM(rom_id, DS2450_VCC_ADDR, 1, &data[0]);
	if (error != DALLAS_NO_ERROR)
		return error;

	error = dallasReadRAM(rom_id,address,8,data);
	if (error != DALLAS_NO_ERROR)
		return error;

	for(i=0;i<8;i+=2)
	{
		if ((data[i] & 0x0F) != resolution)
			return DALLAS_VERIFY_ERROR;
		if ((data[i+1] & 0x01) != range)
			return DALLAS_VERIFY_ERROR;
	}

	return DALLAS_NO_ERROR;
}

u08 ds2450StartAll(dallas_rom_id_T* rom_id)
{
	u08 error;
	u16 crc;

	// check address
	error = dallasAddressCheck(rom_id, DS2450_FAMILY);
	if (error != DALLAS_NO_ERROR)
		return error;

	// reset and select node
	error = dallasMatchROM(rom_id);
	if (error != DALLAS_NO_ERROR)
		return error;

	dallasWriteByte(DS2450_CONVERT);			// send convert command
	dallasWriteByte(DS2450_CONVERT_ALL4_MASK);  	// select all 4 inputs
	dallasWriteByte(DS2450_CLEAR_ALL4_MASK);		// set all output buffers to zero

	// we must read 2byte CRC16 to start the conversion:
	crc = dallasReadByte() | ((u16)dallasReadByte() << 8);

	// replace with explicit CRC posibilities lookup table
//	if (crc == 0xFFFF)
//		return DALLAS_DEVICE_ERROR;        // if CRC = all one's, no one is paying attention
	
	return DALLAS_NO_ERROR;
}

u08 ds2450ResultAll(dallas_rom_id_T* rom_id, u16 result[4])
{
	//const u08 bytes_to_read = 10;		// read 10bytes = 2/ch*4ch + CRC
	u08 bytes_to_read = 10;
	u08 i;
	u08 error;
	u08 data[10];
	u08 resolution[10];
	u16 address;

	// check address
	
	error = dallasAddressCheck(rom_id, DS2450_FAMILY);
	if (error != DALLAS_NO_ERROR)
		return error;

	// start address with channel A
	error = ds2450Chan2Addr('A', DS2450_DATA_PAGE, &address);
	if (error != DALLAS_NO_ERROR)
		return error;

	// read the conversion data
	error = dallasReadRAM(rom_id, address, bytes_to_read, data);
	if (error != DALLAS_NO_ERROR)
		return error;

	//FUTURE: do a real CRC16 check

	// start address with channel A
	error = ds2450Chan2Addr('A', DS2450_SETUP_PAGE, &address);
	if (error != DALLAS_NO_ERROR)
		return error;

	// read the resolution data
	error = dallasReadRAM(rom_id, address, bytes_to_read, resolution);
	if (error != DALLAS_NO_ERROR)
		return error;

	// check crc?

	// store the result by combining the 2 bytes
	// the result's MSB is always the same, so we may need to
	// shift the data over so that the LSB is at the first bit
	error=0;
	for(i=0;i<8;i+=2)
	{
		resolution[i] &= 0x0F;
		if (!resolution[i])
			resolution[i] = 16;

		result[error] = 0;
		//result[error] = (((u16)data[i+1] << 8) | data[i]) >> (16 - resolution[i]);
		result[error] = (((u16)data[i+1] << 8) | data[i]);
		error++;
	}

	return DALLAS_NO_ERROR;
}

u08 ds2450StartAndResultAll(dallas_rom_id_T* rom_id, u16 result[4])
{
	u08 error;

	// start Conversion
	error = ds2450StartAll(rom_id);
	if (error != DALLAS_NO_ERROR)
		return error;
	
	// wait until conversion done
	dallasWaitUntilDone();
	
	// return any error - results passed by reference
	return ds2450ResultAll(rom_id, result);
}

void ds2450Print(u16 result, u08 range)
{
	u16 vscale;

	rprintfProgStrM(" 0x");
	rprintfu16(result);
	rprintf("  ");
	if(range)
		vscale = 12800;
	else
		vscale = 25600;

	rprintfNum(10, 4, TRUE , ' ', result/vscale);
	rprintf(".");
	rprintfNum(10, 4, FALSE, '0', (((u32)(result%vscale))*10000)/vscale );
	rprintfProgStrM(" Volts");
}

u08 ds2450DigitalOut(dallas_rom_id_T* rom_id, u08 channel, dallas_a2d_out_T state)
{
	u08 error;
	u08 old_resolution;
	u16 address;

	// check address
	error = dallasAddressCheck(rom_id, DS2450_FAMILY);
	if (error != DALLAS_NO_ERROR)
		return error;

	// get the address for the channel in the setup page
	error = ds2450Chan2Addr(channel, DS2450_SETUP_PAGE, &address);
	if (error != DALLAS_NO_ERROR)
		return error;

	// read in current resolution
	error = dallasReadRAM(rom_id, address, 1, &old_resolution);
	if (error != DALLAS_NO_ERROR)
		return error;
	
	// extract resolution portion
	old_resolution &= 0x0F;

	// write new setup byte
	state |= old_resolution;
	error = dallasWriteRAM(rom_id, address, 1, ((u08*)&state));
	if (error != DALLAS_NO_ERROR)
		return error;

	return DALLAS_NO_ERROR;
}

static u08 ds2450Chan2Addr(u08 channel, u08 page, u16 *address)
{
	// make sure the channel is a capital letter
	strupr(&channel);

	//convert to integer 0 to 3 and check to see if it is valid
	channel -= 'A';
	if (channel > 3)
		return DALLAS_INVALID_CHANNEL;

	// use corresponding memory address
	*address = (channel<<1) + page;			// channel<<1 == channel*2, but faster

	return DALLAS_NO_ERROR;
}
