#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);
}