/*
 * onewire.c
 *
 * A OneWire interface implementation utilizing Timer16B0
 *
 *  Created on: Jan 15, 2011
 *      Author: James Harwood
 */

#include "LPC17xx.h"                                   // LPC17xx definitions
#include "type.h"
#include "onewire.h"
#include "crc8.h"

#ifdef ROM_SEARCH
#include <stdio.h>
#endif

// Timer clock enable bit positions
#define CT16B0 7
#define CT16B1 8
#define CT32B0 9
#define CT32B1 10

/*----------------------------------------------------------------------------*
 * System configuration section
 *
 * Select a port pin and a timer
 *
 *----------------------------------------------------------------------------*/

// Define which GPIO pin the OneWire data line is connected to
#define OW_PORT 1
#define OW_PIN 18

// Define which timer to use (change all three defines below)
#define TIMER_EN (1 << CT16B0)
#define TIMER_IRQn TIMER0_IRQn
#define LPC_TMR LPC_TIM0

/*----------------------------------------------------------------------------*
 * end of System configuration section
 *----------------------------------------------------------------------------*/

// GPIO Helper macros
static LPC_GPIO_TypeDef(* const LPC_GPIO[5]) = { LPC_GPIO0, LPC_GPIO1, LPC_GPIO2, LPC_GPIO3, LPC_GPIO0 };

#define OW_OUTPUT (LPC_GPIO[OW_PORT]->FIODIR |= (1 << OW_PIN))
#define OW_INPUT (LPC_GPIO[OW_PORT]->FIODIR &= ~(1 << OW_PIN))
#define OW_LOW (LPC_GPIO[OW_PORT]->FIOPIN &= ~(1 << OW_PIN))
#define OW_HIGH (LPC_GPIO[OW_PORT]->FIOPIN |= (1 << OW_PIN))
#define OW_SENSE (LPC_GPIO[OW_PORT]->FIOPIN & (1 << OW_PIN))

// Match Control register bit positions
#define MR0I 0
#define MR1I 3
#define MR2I 6

// prescale timer to 1uS interval
#define PRESCALE	((SystemCoreClock/4)/(1000000ul * 2))

typedef enum {
	RES,
	RESET_COMPLETE,
	WRITE,
	WR_COMPLETE,
	READ,
	RD_COMPLETE,
#ifdef ROM_SEARCH
	WRITE_BIT,
	WR_BIT_COMPLETE,
	READ_BIT,
	RD_BIT_COMPLETE,
#endif
	WAIT_CONV,
	CONV_COMPLETE
} TOWState;

// Globals used by both the ISR and the main thread
static volatile TOWState ow_state;
static volatile uint8_t presence;
static volatile uint8_t *data_ptr;
static volatile uint16_t data_len;
static volatile uint16_t data_index;
static volatile uint8_t cur_byte;
static volatile uint8_t bit_pos;
static volatile uint8_t single_bit;
static volatile uint8_t wait_conv_state;

/*****************************************************************************
 ** Function name:		ow_init
 **
 ** Description:			Initialize the OneWire interface. Must be called by
 ** 						the application before any of the other functions can
 ** 						be used.
 **
 ** parameters:			None
 ** Returned value:		None
 **
 **
 *****************************************************************************/
void ow_init() {
	/* Enable AHB clock to the GPIO domain. */
	//LPC_SC->SYSAHBCLKCTRL |= (1 << 6);

	/* Enable AHB clock to Timer16B0. */
	//LPC_SC->SYSAHBCLKCTRL |= TIMER_EN;

	LPC_TMR->PR = PRESCALE;
	LPC_TMR->TCR = 0x02; // timer stopped and reset

	/* Enable the TIMER0 Interrupt */
	NVIC_EnableIRQ(TIMER_IRQn);

}

/*****************************************************************************
 ** Function name:		ow_reset
 **
 ** Description:			Initiate a bus reset operation.
 ** 						Blocks the calling thread until complete.
 **
 ** parameters:			None
 ** Returned value:		0 if no devices present, 1 if devices are present
 **
 *****************************************************************************/
uint8_t ow_reset() {
	presence = 0;
	ow_state = RES;
	LPC_TMR->MR0 = 480; // reset drive duration
	LPC_TMR->MR1 = 480 + 80; // presence pulse detection
	LPC_TMR->MR2 = 960; // reset finished
	LPC_TMR->MCR = (1 << MR0I) | (1 << MR1I) | (1 << MR2I);
	OW_OUTPUT; // set to output
	OW_LOW; // drive low
	LPC_TMR->TCR = 0x01; // start timer
	while (ow_state != RESET_COMPLETE)
		; // wait for reset completed
	return presence;
}

/*****************************************************************************
 ** Function name:		ow_write
 **
 ** Description:			Initiate a multi-byte write operation.
 ** 						Blocks the calling thread until complete.
 **
 ** parameters:			data - pointer to a buffer holding the data to write
 ** 						len - the number of bytes to write
 **
 ** Returned value:		None
 **
 *****************************************************************************/
void ow_write(uint8_t *data, uint16_t len) {
	data_ptr = data;
	data_len = len;
	data_index = 0;
	bit_pos = 0;
	cur_byte = data[data_index];
	ow_state = WRITE;
	LPC_TMR->MR0 = 2; // end of write pulse
	LPC_TMR->MR1 = 60; // end of write cycle
	LPC_TMR->MR2 = 62; // ready to start next write cycle
	LPC_TMR->MCR = (1 << MR0I) | (1 << MR1I) | (1 << MR2I);
	OW_OUTPUT; // set to output
	OW_LOW; // drive low
	LPC_TMR->TCR = 0x01; // start timer
	while (ow_state != WR_COMPLETE)
		; // wait for write completed
}

/*****************************************************************************
 ** Function name:		ow_read
 **
 ** Description:			Initiate a multi-byte read operation.
 ** 						Blocks the calling thread until complete.
 **
 ** parameters:			data - pointer to a buffer to store the data read
 ** 						len - the number of bytes to read
 **
 ** Returned value:		None
 **
 *****************************************************************************/
void ow_read(uint8_t *data, uint16_t len) {
	data_ptr = data;
	data_len = len;
	data_index = 0;
	bit_pos = 0;
	cur_byte = 0;
	ow_state = READ;
	LPC_TMR->MR0 = 2; // end of read pulse
	LPC_TMR->MR1 = 12; // time to sample line
	LPC_TMR->MR2 = 62; // ready to start next read cycle
	LPC_TMR->MCR = (1 << MR0I) | (1 << MR1I) | (1 << MR2I);
	OW_OUTPUT; // set to output
	OW_LOW; // drive low
	LPC_TMR->TCR = 0x01; // start timer
	while (ow_state != RD_COMPLETE)
		; // wait for read completed

}

#ifdef ROM_SEARCH
/*****************************************************************************
 ** Function name:		ow_write_bit
 **
 ** Description:		Writes a single bit to the bus.
 ** 					Blocks the calling thread until complete.
 **
 ** parameters:			bit - value to be written
 **
 **
 ** Returned value:		None
 **
 *****************************************************************************/
void ow_write_bit(uint8_t bit) {
	single_bit = bit;
	ow_state = WRITE_BIT;
	LPC_TMR->MR0 = 2; // end of write pulse
	LPC_TMR->MR1 = 60; // end of write cycle
	LPC_TMR->MR2 = 62; // ready to start next write cycle
	LPC_TMR->MCR = (1 << MR0I) | (1 << MR1I) | (1 << MR2I);
	OW_OUTPUT; // set to output
	OW_LOW; // drive low
	LPC_TMR->TCR = 0x01; // start timer
	while (ow_state != WR_BIT_COMPLETE)
		; // wait for write completed
}


/*****************************************************************************
 ** Function name:		ow_read_bit
 **
 ** Description:		Reads a single bit from the bus.
 ** 					Blocks the calling thread until complete.
 **
 ** parameters:			None
 **
 ** Returned value:		Received bit value
 **
 *****************************************************************************/
uint8_t ow_read_bit() {
	ow_state = READ_BIT;
	single_bit = 0;
	LPC_TMR->MR0 = 2; // end of read pulse
	LPC_TMR->MR1 = 12; // time to sample line
	LPC_TMR->MR2 = 62; // ready to start next read cycle
	LPC_TMR->MCR = (1 << MR0I) | (1 << MR1I) | (1 << MR2I);
	OW_OUTPUT; // set to output
	OW_LOW; // drive low
	LPC_TMR->TCR = 0x01; // start timer
	while (ow_state != RD_BIT_COMPLETE)
		; // wait for read bit completed
	return single_bit;
}

#endif // ROM_SEARCH


/*****************************************************************************
 ** Function name:		ow_wait_conv
 **
 ** Description:			If a DS18S20 is externally powered, it (may) pull the
 ** 						bus low on read bit cycles until the temperature
 ** 						conversion is complete.
 ** 						Blocks the calling thread until complete.
 **
 ** parameters:			None
 ** Returned value:		None
 **
 *****************************************************************************/
void ow_wait_conv() {
	ow_state = WAIT_CONV;
	wait_conv_state = 0;
	LPC_TMR->MR0 = 2; // end of read pulse
	LPC_TMR->MR1 = 12; // time to sample line
	LPC_TMR->MR2 = 62; // ready to start next read cycle
	LPC_TMR->MCR = (1 << MR0I) | (1 << MR1I) | (1 << MR2I);
	OW_OUTPUT; // set to output
	OW_LOW; // drive low
	LPC_TMR->TCR = 0x01; // start timer
	while (ow_state != CONV_COMPLETE)
		; // wait for read completed

}

/*****************************************************************************
 ** Function name:		TIMER16_0_IRQHandler
 **
 ** Description:			Handles the Match Register interrupts during each
 ** 						of the reset, write bit or read bit cycles
 **
 ** parameters:			None
 ** Returned value:		None
 **
 *****************************************************************************/
void TIMER0_IRQHandler(void) {

	switch (ow_state) {
	case RES:
		switch (LPC_TMR->IR) {
		case 0x01:
			LPC_TMR->IR = 0x01; // clear interrupt
			OW_HIGH; // set pin high
			OW_INPUT; // set to input
			break;
		case 0x02:
			LPC_TMR->IR = 0x02; // clear interrupt
			if (OW_SENSE == 0)
				presence = 1;
			break;
		case 0x04:
			LPC_TMR->IR = 0x04; // clear interrupt
			LPC_TMR->TCR = 0x02; // timer stopped and reset
			LPC_TMR->MCR = 0; // clear match control flags
			ow_state = RESET_COMPLETE;
			break;
		}
		break; // end of reset case

	case WRITE:
		switch (LPC_TMR->IR) {
		case 0x01: // end of write pulse
			LPC_TMR->IR = 0x01; // clear interrupt
			if ((cur_byte >> bit_pos) & 0x01) {
				OW_HIGH; // set pin high
			} // else keep the line low
			break;
		case 0x02: // end of write cycle
			LPC_TMR->IR = 0x02; // clear interrupt
			OW_HIGH; // set pin high
			break;
		case 0x04: // start next write cycle
			LPC_TMR->IR = 0x04; // clear interrupt
			bit_pos++;
			if (bit_pos == 8) {
				// end of byte
				bit_pos = 0;
				data_index++;
				if (data_index == data_len) {
					// end of data
					ow_state = WR_COMPLETE;
					OW_INPUT; // allow line to be pulled up
					LPC_TMR->TCR = 0x02; // timer stopped and reset
					LPC_TMR->MCR = 0; // clear match control flags
					return;
				} else {
					// next byte
					cur_byte = data_ptr[data_index];
				}
			}
			LPC_TMR->TCR = 0x02; // reset timer
			OW_OUTPUT; // set to output
			OW_LOW; // drive low
			LPC_TMR->TCR = 0x01; // restart timer
			break;
		}
		break; // end of write case

	case READ:
		switch (LPC_TMR->IR) {
		case 0x01: // end of read pulse
			LPC_TMR->IR = 0x01; // clear interrupt
			OW_HIGH; // set pin high
			OW_INPUT; // set to input
			break;
		case 0x02: // time to sample line
			LPC_TMR->IR = 0x02; // clear interrupt
			if (OW_SENSE) {
				cur_byte |= (1 << bit_pos);
			}
			break;
		case 0x04: // start next read cycle
			LPC_TMR->IR = 0x04; // clear interrupt
			bit_pos++;
			if (bit_pos == 8) {
				// end of byte
				data_ptr[data_index] = cur_byte;
				cur_byte = 0;
				bit_pos = 0;
				data_index++;
				if (data_index == data_len) {
					// end of data
					ow_state = RD_COMPLETE;
					LPC_TMR->TCR = 0x02; // timer stopped and reset
					LPC_TMR->MCR = 0; // clear match control flags
					return;
				}
			}
			LPC_TMR->TCR = 0x02; // reset timer
			OW_OUTPUT; // set to output
			OW_LOW; // drive low
			LPC_TMR->TCR = 0x01; // restart timer
			break;
		}
		break; // end of read case

#ifdef ROM_SEARCH
		case WRITE_BIT:
			switch (LPC_TMR->IR) {
			case 0x01: // end of write pulse
				LPC_TMR->IR = 0x01; // clear interrupt
				if (single_bit) {
					OW_HIGH; // set pin high
				} // else keep the line low
				break;
			case 0x02: // end of write cycle
				LPC_TMR->IR = 0x02; // clear interrupt
				OW_HIGH; // set pin high
				break;
			case 0x04: // end of write bit cycle
				LPC_TMR->IR = 0x04; // clear interrupt
				ow_state = WR_BIT_COMPLETE;
				OW_INPUT; // allow line to be pulled up
				LPC_TMR->TCR = 0x02; // timer stopped and reset
				LPC_TMR->MCR = 0; // clear match control flags
				break;
			}
			break; // end of write bit case

		case READ_BIT:
			switch (LPC_TMR->IR) {
			case 0x01: // end of read pulse
				LPC_TMR->IR = 0x01; // clear interrupt
				OW_HIGH; // set pin high
				OW_INPUT; // set to input
				break;
			case 0x02: // time to sample line
				LPC_TMR->IR = 0x02; // clear interrupt
				if (OW_SENSE) {
					single_bit = 1;
				}
				break;
			case 0x04: // end of read bit cycle
				LPC_TMR->IR = 0x04; // clear interrupt
						ow_state = RD_BIT_COMPLETE;
						LPC_TMR->TCR = 0x02; // timer stopped and reset
						LPC_TMR->MCR = 0; // clear match control flags
				break;
			}
			break; // end of read bit case
#endif // ROM_SEARCH

	case WAIT_CONV:
		switch (LPC_TMR->IR) {
		case 0x01: // end of read pulse
			LPC_TMR->IR = 0x01; // clear interrupt
			OW_HIGH; // set pin high
			OW_INPUT; // set to input
			break;
		case 0x02: // time to sample line
			LPC_TMR->IR = 0x02; // clear interrupt
			if (OW_SENSE) {
				// received a one bit
				if (wait_conv_state == 1)
					wait_conv_state++;
			} else {
				// received a zero bit
				if (wait_conv_state == 0)
					wait_conv_state++;
			}
			break;
		case 0x04: // start next read cycle
			LPC_TMR->IR = 0x04; // clear interrupt
			if (wait_conv_state == 2) {
				ow_state = CONV_COMPLETE;
				LPC_TMR->TCR = 0x02; // timer stopped and reset
				return;
			}
			LPC_TMR->TCR = 0x02; // reset timer
			OW_OUTPUT; // set to output
			OW_LOW; // drive low
			LPC_TMR->TCR = 0x01; // restart timer
			break;
		}
		break; // end of wait convert case
	default:
		break;
	}

	return;
}

/*-----------------------------------------------------------------------------------------------
 *		Below are some examples of using the OneWire interface.
 *		Only the DS18S20 temperature sensor has been tested so far.
 *
 *
 */

//#define PARASITIC 1			// affects the implementation of temperature conversion delay
// OneWire command definitions
#define CMD_ROM   0x033		// read rom
#define CMD_CONV  0x044		// convert temperature
#define CMD_MATCH 0x055		// match rom
#define CMD_PWR   0x0b4		// read power
#define CMD_READ  0x0be		// read scratch pad
#define CMD_SKIP  0x0cc		// skip rom
// SysTick timer count
extern volatile uint32_t msTicks;

static uint8_t read_buf[10];

/*****************************************************************************
 ** Function name:		ow_read_rom
 **
 ** Description:			Read the Rom address of the device on the bus. Can only
 ** 						be used if there is only a single device present.
 **
 ** parameters:			addr - Rom Address pointer to be filled with Rom value
 ** Returned value:		0 if success, 1 on crc failure
 **
 *****************************************************************************/
uint8_t ow_read_rom(RomType *addr) {
	uint8_t crc;
	uint8_t cmd = CMD_ROM;

	ow_reset();
	ow_write(&cmd, 1);
	ow_read((*addr).b, 8);
	crc = crc8((*addr).b, 8);
	return crc;
}

/*****************************************************************************
 ** Function name:		ow_read_power
 **
 ** Description:			Detect if any of the devices on the bus are
 ** 						parasitic powered.
 **
 ** parameters:			None
 ** Returned value:		0 if any parasitic powered devices are present
 ** 						1 if all devices are externally powered
 **
 *****************************************************************************/
uint8_t ow_read_power() {
	uint8_t cmd = CMD_PWR;
	ow_reset();
	ow_write(&cmd, 1);
	ow_read(read_buf, 1); // only the lsb is significant
	if (read_buf[0] & 0x01) {
		return 1; // ext power
	} else {
		return 0; // parasitic power
	}
}

/*****************************************************************************
 ** Function name:		ow_read_temp
 **
 ** Description:			Read current temperature from DS18x20 sensor
 ** 						Blocks the calling thread until complete.
 **
 ** parameters:			result - address of signed 16 bit word to be filled with
 ** 							temperature value. The format depends on the device
 ** 							part number (DS18S20 vs DS18B20), and user configuration.
 **
 **
 ** 						addr - Rom Address pointer to select a specific device
 ** 							May be NULL if there is only one device present
 ** 							the on OW bus.
 **
 ** 						For example:
 **							RomType my_sensor;
 my_sensor.l = 0x07100080042ecdf10ull;
 **							result = ow_read_temp(&temp, &my_sensor);
 **
 ** Returned value:		0 on success,
 ** 						1 if crc error,
 ** 						2 if no devices present
 **
 *****************************************************************************/
uint8_t ow_read_temp(uint16_t *result, RomType *addr) {
	static uint8_t wr_buf[10];
	uint8_t crc, i;

	if (!ow_reset())
		return 2;

	// convert temperature
	if (addr == NULL) {
		wr_buf[0] = CMD_SKIP;
		wr_buf[1] = CMD_CONV;
		ow_write(wr_buf, 2);
	} else {
		wr_buf[0] = CMD_MATCH;
		for (i = 0; i < 8; i++) {
			wr_buf[i + 1] = (*addr).b[i];
		}
		wr_buf[9] = CMD_CONV;
		ow_write(wr_buf, 10);
	}

#if PARASITIC
	// the bus cannot be used during temperature conversion
	// so the device cannot signal 'not-ready' bits to the host.
	// Use the SysTick timer instead to wait 750mS
	uint32_t done = msTicks + 750;
	while (msTicks != done)
		;
#else
	ow_wait_conv();
#endif

	if (!ow_reset())
		return 2;

	// read scratch pad
	if (addr == NULL) {
		wr_buf[1] = CMD_READ;
		ow_write(wr_buf, 2);
	} else {
		wr_buf[9] = CMD_READ;
		ow_write(wr_buf, 10);
	}
	ow_read(read_buf, 9);
	crc = crc8(read_buf, 9);
	if (crc == 0) {
		*result = read_buf[0];
		*result |= (read_buf[1] << 8);
		return 0;
	} else {
		return 1;
	}
}

/* ---------------------------------------------------------------------------------------
 *  ROM Search functions below taken from Maxim Application Note 162
 *
 */
#ifdef ROM_SEARCH
static uint8_t lastDiscrep;
static uint8_t doneFlag;



/*****************************************************************************
 ** Function name:		ow_srch_next
 **
 ** Description:		Searches for the next device on the 1-wire bus.
 **
 **
 ** parameters:			rom_ptr - address of a RomType variable to hold the result
 ** Returned value:		0 if no more ROM codes were found
 ** 					1 if a ROM code was found
 **
 *****************************************************************************/
uint8_t ow_srch_next(RomType *rom_ptr) {
	uint8_t m = 1;// ROM Bit index
	uint8_t n = 0;// ROM Byte index
	uint8_t k = 1;// bit mask
	uint8_t x = 0;
	uint8_t discrepMarker = 0;// discrepancy marker
	uint8_t g;// Output bit
	uint8_t nxt;// return value
	uint8_t cmd = 0xF0; 			// SearchROM command
	uint8_t crc;

	nxt = FALSE;// set the next flag to false

	if ((!ow_reset()) || doneFlag)// no parts -> return false
	{
		lastDiscrep = 0;// reset the search
		return FALSE;
	}
	ow_write(&cmd, 1);// send SearchROM command
	do
	// for all eight bytes
	{
		x = 0;
		if (ow_read_bit() == 1)
			x = 2;

		if (ow_read_bit() == 1)
			x |= 1;// and its complement
		if (x == 3)// there are no devices on the 1-wire
			break;

		else {
			if (x > 0)// all devices coupled have 0 or 1
				g = x >> 1;// bit write value for search
			else {
				// if this discrepancy is before the last
				//  discrepancy on a previous Next then pick
				//  the same as last time
				if (m < lastDiscrep)
					g = (((*rom_ptr).b[n] & k) > 0);
				else
					// if equal to last pick 1
					g = (m == lastDiscrep);// if not then pick 0
				// if 0 was picked then record
				//position with mask k
				if (g == 0)
					discrepMarker = m;
			}
			if (g == 1)// isolate bit in ROM[n] with mask k
				(*rom_ptr).b[n] |= k;
			else
				(*rom_ptr).b[n] &= ~k;
			ow_write_bit(g);// ROM search write
			m++;// increment bit counter m
			k = k << 1;// and shift the bit mask k
			if (k == 0)// if the mask is 0 then go to new ROM
			{ // byte n and reset mask
				n++;
				k++;
			}
		}

	} while (n < 8);//loop until through all ROM bytes 0-7
	crc = crc8((*rom_ptr).b, 8);
	if (m < 65 || (crc != 0) )// if search was unsuccessful then
		lastDiscrep = 0;// reset the last discrepancy to 0
	else {
		// search was successful, so set lastDiscrep,
		//lastOne, nxt
		lastDiscrep = discrepMarker;
		doneFlag = (lastDiscrep == 0);
		nxt = TRUE; // indicates search is not complete yet, more
		//parts remain
	}

	return nxt;
}

/*****************************************************************************
 ** Function name:		ow_srch_first
 **
 ** Description:		Resets the current state of a ROM search and calls
 ** 					ow_srch_next to find the first device on the 1-wire bus.
 **
 **
 ** parameters:			rom_ptr - address of a RomType variable to hold the result
 ** Returned value:		0 if no ROM code was found
 ** 					1 if a ROM code was found
 **
 *****************************************************************************/
uint8_t ow_srch_first(RomType *rom_ptr)
{
   lastDiscrep  = 0;// reset the rom search last discrepancy global
   doneFlag = FALSE;
   return ow_srch_next(rom_ptr);// call next and return its return value
}

/*****************************************************************************
 ** Function name:		ow_find_devices
 **
 ** Description:		Search for devices on the 1-wire bus and print out
 ** 					their ROM codes
 **
 **
 ** parameters:			None
 ** Returned value:		None
 **
 *****************************************************************************/
void ow_find_devices(void) {
	RomType ROM;
	if(ow_srch_first(&ROM)) {
		do {
			printf("found: %#0.16llx\n", ROM.l);
		} while (ow_srch_next(&ROM));
	}
}
#endif  // ROM_SEARCH

/******************************************************************************
 **                            End Of File
 ******************************************************************************/
