#include "config.h"
#include "sht11.h"

#define SHT11_CMD_TEMP	0x03
#define SHT11_CMD_HUMID	0x05
#define SHT11_CMD_WSTAT	0x06
#define SHT11_CMD_RSTAT	0x07
#define SHT11_CMD_RESET	0x1E

/////////////////////////////////////////////////////////////////////////////
// This version needs external pullups on SDA!
/////////////////////////////////////////////////////////////////////////////

static void	delay(void)	{ _delay_us(2); }

static void	scl_hi(void)    { setBits(PORT(SHT11_PORT), SHT11_SCL); }
static void	scl_lo(void)    { clrBits(PORT(SHT11_PORT), SHT11_SCL); }
static void	sda_hi(void)    { clrBits(DDR(SHT11_PORT), SHT11_SDA); }
static void	sda_lo(void)    { setBits(DDR(SHT11_PORT), SHT11_SDA); }
static void	scl_pulse(void) { scl_hi(); delay(); scl_lo(); }
static uint8_t	sda_val(void)   { return (PIN(SHT11_PORT) & SHT11_SDA) != 0; }

/////////////////////////////////////////////////////////////////////////////

static uint8_t crc_value;

static void
crc8(uint8_t b)
{
    for (uint8_t i = 0; i < 8; ++i) {
	if ((crc_value ^ b) & 0x80) {
	    crc_value <<= 1;
	    crc_value ^= 0x31;
	} else
	    crc_value <<= 1;
	b <<= 1;
    }
}

/////////////////////////////////////////////////////////////////////////////

static uint8_t
send(uint16_t b)
{
    crc8(b);
    
    // data
    for (uint8_t i = 0; i < 8; ++i) {
	if (b & 0x80)
	    sda_hi();
	else
	    sda_lo();
	b <<= 1;
	delay();
	scl_pulse();
    }

    // acknowledge
    sda_hi();
    delay();
    uint8_t ack = sda_val();
    scl_pulse();
    return ack;
}

static uint8_t
recv_data(void)
{
    // data
    uint8_t b = 0;
    for (uint8_t i = 0; i < 8; ++i) {
	// data is transmitted MSB first
	b <<= 1;
	if (sda_val())
	    b |= 1;
	scl_pulse();
	delay();
    }

    // lo acknowledge
    sda_lo();
    delay();
    scl_pulse();
    sda_hi();
    delay();

    crc8(b);
    return b;
}

static uint8_t
recv_crc(void)
{
    // data
    uint8_t b = 0;
    for (uint8_t i = 0; i < 8; ++i) {
	// CRC is transmitted LSB first
	b >>= 1;
	if (sda_val())
	    b |= 0x80;
	scl_pulse();
	delay();
    }

    // hi acknowledge
    sda_hi();
    delay();
    scl_pulse();
    delay();

    return b;
}

static void
start(void)
{
    clrBits(PORT(SHT11_PORT), SHT11_SCL | SHT11_SDA);	// SCK output low, SDA input/high
    setBits(DDR(SHT11_PORT),  SHT11_SCL);
    clrBits(DDR(SHT11_PORT),  SHT11_SDA);
    delay();

    // reset communication
    for (uint8_t i = 0; i < 10; ++i) {
	scl_pulse();
	delay();
    }

    // "start" sequence
    scl_hi(); delay();
    sda_lo(); delay();
    scl_lo(); delay();
    scl_hi(); delay();
    sda_hi(); delay();
    scl_lo(); delay();
}

/////////////////////////////////////////////////////////////////////////////
// Measurement sequence.

uint8_t
sht11_start_temp(void)
{
    crc_value = SHT11_LOWRES << 7; // bit-reversed
    start();
    return send(SHT11_CMD_TEMP) == 0;
}

uint8_t
sht11_start_humid(void)
{
    crc_value = SHT11_LOWRES << 7; // bit-reversed
    start();
    return send(SHT11_CMD_HUMID) == 0;
}

uint8_t
sht11_ready(void)
{
    return sda_val() == 0;
}

static int16_t
result(void)
{
    if (!sht11_ready())
	return SHT11_UNAVAIL;
    int16_t v = recv_data() << 8; v |= recv_data();
    uint8_t crc = recv_crc();
    if (crc != crc_value)
	return SHT11_CRC_FAIL;
    return v;
}

int16_t
sht11_result_temp(void)
{
    int16_t v = result();
    if (sht11_valid(v)) {
#if SHT11_LOWRES
	v = v * 4 - 4000;
#else
	v -= 4000;
#endif
    }
    return v;
}

int16_t
sht11_result_humid(void)
{
    int16_t v = result();
    if (sht11_valid(v)) {
#if SHT11_LOWRES
	// inspired by Werner Hoch, modified for low resolution mode
	const int32_t C1 = (int32_t)(-4.0 * 100);
	const int32_t C2 = (int32_t)(0.648 * 100 * (1L<<24));
	const int32_t C3 = (int32_t)(-7.2e-4 * 100 * (1L<<24));
	v = (int16_t)((((C3 * v + C2) >> 7) * v + (1L<<16)) >> 17) + C1;
#else
	// inspired by Werner Hoch
	const int32_t C1 = (int32_t)(-4.0 * 100);
	const int32_t C2 = (int32_t)(0.0405 * 100 * (1L<<28));
	const int32_t C3 = (int32_t)(-2.8e-6 * 100 * (1L<<30));
	v = (int16_t)((((((C3 * v) >> 2) + C2) >> 11) * v + (1L<<16)) >> 17) + C1;
#endif
    }
    return v;
}

/////////////////////////////////////////////////////////////////////////////
// Initialize.

void
sht11_init(void)
{
    start();
    send(SHT11_CMD_RESET);
    _delay_ms(11);

    start();
    send(SHT11_CMD_WSTAT);
    send(SHT11_LOWRES);
}

