#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>


/** C99 library to convert a reading from a TMP102 sensor into a human-
 * readable temperature value.
 *
 * This code was written as a demonstration as to how to convert the raw
 * SPI readings into a value for display on a LCD panel. This code does
 * not take into account any stdio.h library code for the LCD; porting to
 * a microcontroller's I/O libraries is left as an exercise for the reader.
 *
 * Copyright (c) 2010 Ben Stewart. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY Ben Stewart ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL Ben Stewart OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of Ben Stewart.
 *
 */

/** Table to convert the 4 LSBs to a 1-decimal-place fractional temperature */
char conversion_table[16] =
    {'0', '1', '1', '2', '2', '3', '4', '4', '5', '6', '6', '7', '7', '8', '9',
    '9'}; 

/** Convert a raw 16-bit ADC count from the TMP102 sensor to degrees C and
 * fractional degrees in units of 0.0625C.
 *
 * @param raw_reading  Value straight from SPI, in native endianness
 * @param negFlag      Output, boolean flag to represent the sign of the value.
 *                     True is negative, false is positive or zero.
 * @param degreesC     Output, Integer part of temperature
 * @param fractional   Output, fractional part of temperature in units of
 *                     0.0625C = 1.
 *
 * @return             Returns true if conversion succeeds, and values have 
 *                     been placed in output variables.
 *
 * @return             Returns false if any output variable pointer is NULL.
 */ 
bool convertReading(const uint16_t raw_reading, bool *negFlag, int8_t *degreesC, 
    uint8_t *fractional)
{
    int16_t signedADCCounts;

    /* Check for null pointers */
    if ((NULL == degreesC) || (NULL == fractional) || (NULL == negFlag))
    {
        return false;
    }

    /* Treat raw_reading as a twos-complement signed 16-bit integer, 
     * then throw away the bottom 4 bits */
    signedADCCounts = ((int16_t)raw_reading) / 16; 

    /* ADC is in units of 0.0625C = 1 ADC count, so divide it by 16 to get the
     * integer component of the temperature */
    *degreesC = abs(signedADCCounts)/16;

    /* Grab out the sign flag */
    *negFlag = (signedADCCounts < 0);

    /* Are we operating within the design limits? */
    assert(*degreesC <= 150);
    assert(*degreesC >= -55);

    /* Now give the fractional degrees in unsigned form for easy LUTting */
    *fractional = abs(signedADCCounts) % 16;

    return true;
}


/** Run a test case through the temperature decoder.
 *
 * This routine will convert a reading from the raw SPI value, and compare the
 * expected values with those calculated by the routine.
 *
 * @return  Returns true if the test case succeeds.
 * @return  Returns false if the test case fails.
 */
bool testDecoder(uint16_t input_raw, bool exp_negflag, int8_t exp_deg, int8_t exp_frac)
{
    bool calc_negflag;
    int8_t calc_deg;
    int8_t calc_frac;

    if (convertReading(input_raw, &calc_negflag, &calc_deg, &calc_frac))
    {
        if ((exp_deg == calc_deg) && (exp_frac == calc_frac) && (calc_negflag == exp_negflag))
        {
            printf("PASS: 0x%04x == %c%3d.%cC\n", input_raw, calc_negflag ? '-' : ' ', calc_deg, conversion_table[calc_frac]);
            return true;
        }
        else
        {
            printf("FAIL: 0x%04x == %c%3d.%cC, instead calculated %c%3d%cC\n", input_raw, exp_negflag?'-':' ', exp_deg, conversion_table[exp_frac], calc_negflag?'-':' ', calc_deg, conversion_table[calc_frac]);
            return false;
        }
    }
    else
    {
        printf("FAIL: 0x%04x returned false.\n", input_raw);
        return false;
    }
}


/** The main entry point.
 *
 * Run a few tests from the entries in the data sheet.
 */
int main(int argc, char **argv)
{
    testDecoder(0x7FF0, false, 127, 0x0F); // 127.9375C
    testDecoder(0x6400, false, 100, 0);    // 100C
    testDecoder(0x5000, false, 80,  0);    // 80C
    testDecoder(0x4B00, false, 75,  0);    // 75C
    testDecoder(0x3200, false, 50,  0);    // 50C
    testDecoder(0x1900, false, 25,  0);    // 25C
    testDecoder(0x0A40, false, 10,  0x04); // 10C
    testDecoder(0x0040, false, 0,   0x04); // 0.25C
    testDecoder(0x0000, false, 0,   0);    // 0C
    testDecoder(0xFFC0, true,  0,   0x04); // -0.25C
    testDecoder(0xE700, true,  25,  0);    // -25C
    testDecoder(0xC900, true,  55,  0);    // -55C
    return 0;
}
