#include <stdlib.h>
#include <avr/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>

#include "DHT11+22.h"


/******************************************************************************
* cpp definitions
******************************************************************************/

// IO-port pin the DHT-sensor is connected to (which is PB0 since the Timer/Counter1 Input Capture Feature will be used)
#define SENSOR_PIN	PB0

// define constants matching to SENSOR_PIN
#if SENSOR_PIN == PB0
#	define SENSOR_PORT		PORTB
#	define SENSOR_DDR		DDRB
#	define SENSOR_DD_BIT	DDB0
#else
#	error SENSOR_PIN not supported
#endif

// timing tolerance for the data transmission protocol of the sensor (microseconds)
#define TOLERANCE	15						// tested and found to be unavoidable (got errors with a value of 10 :( )

// Prescaler settings for Timer1:
// choose prescaler so that T <= 1us (to ensure resolution) and T >= 0.092us (to cover at least 6ms data acquisition time with the 16bit counter)
//							f >= 1MHz							f <= 10.9MHz
//	Prescaler = 1	->		F_CPU >= 1MHz						F_CPU <= 10.9MHz
//	Prescaler = 8	->		F_CPU >= 8MHz						F_CPU <= 87.2MHz
#if	  ( F_CPU >= 1000000UL ) && ( F_CPU <= 10900000UL )
#	define PRESCALER_VALUE		1
#	define PRESCALER_BITMASK	(0<<CS12) | (0<<CS11) | (1<<CS10);
#elif ( F_CPU >= 8000000UL ) && ( F_CPU <= 87200000UL )
#	define PRESCALER_VALUE		8
#	define PRESCALER_BITMASK	(0<<CS12) | (1<<CS11) | (0<<CS10);
#else
#	error F_CPU not supported
#endif


// Mnemonics
#define stopDataTransfer()	stopInputCaptureTimer()


/******************************************************************************
* constants and variables
******************************************************************************/

const uint16_t		CONST_DIVISOR	= ( F_CPU / PRESCALER_VALUE / 1000 );		// § needs to be a constant for unknown reasons, the §§-comments further below

uint8_t			data0, data1, data2, data3, data4;					// sensor transmits 5 bytes per acquisition

volatile bool	signalEdgeNeedsProcessing;	// flags whether the Input Capture provided new data which has not been processed yet
volatile bool	signalEdgeMissed;			// flags whether the Input Capture provided new data before the preceding one has been processed
bool			dataIsValid;				// set true, if the last data acquisition was successful


/******************************************************************************
* function prototypes
******************************************************************************/

extern void		startInputCaptureTimer	( void);
extern void		stopInputCaptureTimer	( void);


/******************************************************************************
* Interrupt Service Routines
******************************************************************************/

ISR( TIMER1_CAPT_vect )
{
	TCCR1B ^= (1<<ICES1);					// toggle the Input Capture Edge Selection (because there is no "match any edge" mode)

	if ( signalEdgeNeedsProcessing )		// damn, we are too late: the last edge has not been processed yet in the data-acquisition-loop
		signalEdgeMissed = true;
	else
		signalEdgeNeedsProcessing = true;	// flag the detection of the edge to the processing loop
}


// only reached in case of an error in the data reading/data processing (e.g. when the sensor fails or is not present at all)
ISR( TIMER1_OVF_vect )
{
	signalEdgeMissed = true;				// just a trick to force the data processing loop to exit
}


/******************************************************************************
* Function Definitions
******************************************************************************/

void startInputCaptureTimer()
{
	// mode 0: Clear Timer on Compare (CTC) mode with TOP=0xFFFF
	TCCR1A	= (0<<WGM11) | (0<<WGM10);					// mode 0
	TCCR1B	= (0<<WGM13) | (0<<WGM12);
	TCCR1B |= (1<<ICNC1);								// activate noise control for input capture
	TCCR1B |= (0<<ICES1);								// falling edge mode (note: the first edge to be detected is a falling one, the ISR ought to toggle that bit for each detected edge)
	// enables the Input Capture Interrupt
	// (as well as the Timer Overflow Interrupt in order be able to detect a missing sensor signal, which otherwise would block execution)
	TCCR1B |= PRESCALER_BITMASK;						// starts the timer/counter running
	TIMSK1 |= (1<<ICIE1) | (1<<TOIE1);
	cli();	// §§§ muss spätestens vor dem letzten Befehl stehen, dieser ist allerdings beliebig (setting TCCR1B, TIMSK1, TCNT1)
	TCNT1	= 0;										// reset counter value
	ICR1	= 0;										// reset input capture register (neccessary!)
	// sei();	// §§§ induces an error if placed here, but after ret it's fine :-O
}


void stopInputCaptureTimer( void)
{
	TIMSK1 = 0;											// disable all Timer1 related interrupts
	TCCR1B &= ~( (1<<CS12) | (1<<CS11) | (1<<CS10) );	// stop timer/counter
}


// acquires data transfer from the DHT-sensor
bool acquire_DHT()
{
	uint8_t		iEdge = 0;							// counts signal level changes
	uint16_t	last_time = 0;						// time of previous signal edge (in microseconds)
	uint16_t	current_time = 0;					// time of current	signal edge (in microseconds)
	uint16_t	duration = 0;						// time between the last two signal edges
	
	volatile uint16_t	var_divisor = ( F_CPU / PRESCALER_VALUE / 1000 );

	// ensure High level for some time
	SENSOR_PORT |=	(1<<SENSOR_PIN	 );
	SENSOR_DDR	|=	(1<<SENSOR_DD_BIT);
	_delay_ms( 1);									// § how long do we have to wait at least?

	// send start condition (18ms low)
	SENSOR_PORT &= ~(1<<SENSOR_PIN	 );
	_delay_ms( 18);									// note: in case of the DHT22, 800us would be enough (which can be used to distinguish both models)

	// prepare and start listening
	data0=0; data1=0; data2=0; data3=0; data4=0;
	dataIsValid = false;
	iEdge = 0;
	signalEdgeNeedsProcessing = false;
	signalEdgeMissed = false;
	cli();											// §§ why exactely is this needed?
	startInputCaptureTimer();
	sei();

	// (actively driven = fast) raising edge, then set IO as input with pull-up activ
	SENSOR_PORT |=	(1<<SENSOR_PIN	 );
	SENSOR_DDR	&= ~(1<<SENSOR_DD_BIT);
	
	// process data provided from IRQ-driven sensor reading
	while ( ! signalEdgeMissed	)					// note: besides this the loop may be left by "break;"
	{
		if ( signalEdgeNeedsProcessing )			// set by ISR each time a signal level change is detected and its time stored in ICR1
		{
			iEdge++;

			// get current timer tick counter (Timer1 Input Capture value) and scale it to microseconds
			// AVOID 64bit integer arithmetic like "current_time = ICR1 * 1000000ULL / ( F_CPU / PRESCALER_VALUE );"
			// §§§ The following assignment induces VERY STRANGE ERRORNEOUS BEHAVIOUR:
			// current_time = 1000L * ICR1 / ( F_CPU / PRESCALER_VALUE / 1000 );	// §§§ error
			// §§	 The only solution I have found is to put the term ( F_CPU / PRESCALER_VALUE / 1000 ) into a constant.
			//	   Not into a variable, this will fail too ;)
			current_time = 1000L * ICR1 / CONST_DIVISOR;							// okay
			// current_time = 1000L * ICR1 / var_divisor;       // §§§ error	<-------------------- BÖSER FEHLER -------------------->
			duration	 = current_time - last_time;
			last_time	 = current_time;
			
			// at this point, the Input Capture may be triggered again without loss of data, so tell this to be okay now
			asm volatile ("" : : : "memory");				// "compiler barrier" which ensures the compiler does not move the code around
			signalEdgeNeedsProcessing = false;

			if ( iEdge == 1 )								// begin of start response: 80us low, then 80us high
			{}												// the current time is recorded as last_time, nothing else is to be done here
			else if ( iEdge <= 3 )							// end of 80us low (iEdge==2) or end of 80us high (iEdge==3)
			{
				if (  duration < 80-TOLERANCE || duration > 80+TOLERANCE )
					{ stopDataTransfer(); break; }			// error
			}
			else											// sensor transmits 40 data bits (LSB first)
			{
				if ( ! ( iEdge % 2 ) )						// even number -> rising edge after 50us
				{
					if (  duration < 55-TOLERANCE || duration > 55+TOLERANCE )
						{ stopDataTransfer(); break; }		// error
				}
				else										// odd number -> falling edge after 26-28us (representing logic 0) or 70us (representing logic 1)
				{
					uint8_t iByte = (iEdge-5) / 2 / 8;		// 5: first data edge, 2: edges per bit, 8: bits per byte
					uint8_t iBit  = 7 - ( (iEdge-5) / 2 % 8 );		// LSB first
					if (  duration > 26-TOLERANCE/2 && duration < 28+TOLERANCE/2 )
					{
						// data[iByte] &= ~(1<<iBit);			// bit = 0
						switch ( iByte ) { case 0: data0 &= ~(1<<iBit); break; case 1: data1 &= ~(1<<iBit); break; case 2: data2 &= ~(1<<iBit); break; case 3: data3 &= ~(1<<iBit); break; case 4: data4 &= ~(1<<iBit); break; }
					}
					else if (  duration > 75-TOLERANCE && duration < 75+TOLERANCE )
					{
						// data[iByte] |=	(1<<iBit);			// bit = 1
						switch ( iByte ) { case 0: data0 |= (1<<iBit); break; case 1: data1 |= (1<<iBit); break; case 2: data2 |= (1<<iBit); break; case 3: data3 |= (1<<iBit); break; case 4: data4 |= (1<<iBit); break; }
					}
					else
						{ stopDataTransfer(); break; }		// error
					if ( iEdge == 83 )						// final bit edge
					{
						dataIsValid = true;					// SUCCESS
						stopDataTransfer(); break;
					}
				}
			}	// if ( iEdge <= 3 ) ... else
		}	// if ( signalEdgeNeedsProcessing )
	}	// while ( ! signalEdgeMissed ) - or left by "break;"

	// stop timer/counter1 and reset port pin
	stopInputCaptureTimer();
	SENSOR_PORT |=	(1<<SENSOR_PIN	 );				// high
	SENSOR_DDR	|=	(1<<SENSOR_DD_BIT);				// out

	// check checksum
	uint8_t sum = (uint8_t)(data0 + data1 + data2 + data3);
	if ( sum != data4 )
		dataIsValid = false;

	return	dataIsValid;
}	// bool acquire_DHT()


#if DHT_TYPE == DHT_TYPE_DHT11

	uint8_t get_humidity( void)
	{
		if ( dataIsValid )
			return data0;
		else
			return 0;
	}

	int8_t get_temperature( void)
	{
		if ( dataIsValid )
			return data2;
		else
			return 0;	// § may be better to use an impossible value too
	}

#elif DHT_TYPE == DHT_TYPE_DHT22

	float get_humidity( void)
	{
		if ( dataIsValid )
			return ( data0<<8 | data1 ) / 10.0;
		else
			return 0;
	}

	float get_temperature( void)
	{
		if ( dataIsValid )
		{
			uint16_t rawtemperature = data2<<8 | data3;
			if ( rawtemperature & 0x8000 )
				return -(rawtemperature & 0x7FFF) / 10.0;
			else
				return rawtemperature / 10.0;
		}
		else
			return 0;	// § may be better to use an impossible value too
	}

#else
	#error DHT_TYPE not supported
#endif
