//******************************************************************************
/******************************************************************************/
// 
//  temp_recorder
//
//  Interrupt driven, after power-on initialization, the software checks 
//  the first free FLASH word to see if run-length encoded data is present. 
//  If not, the watchdog timer is configured as a watch crystal controlled 
//  interval timer and each 8 second interrupt measures the temperature. 
//  But if FLASH has run-length encoded temperature data, it dumps the 
//  temperature time and values to the USB debugger terminal window and 
//  log file.
//
//  The 16-bit ADC measures the temperature using a built-in thermister 
//  device. The built-in 'short' interface is used to provide a relative 
//  zero. By happy accident, the low-power, 32 oversampling values are a 
//  simple offset and shift from 1/10th degree, farenheiht values. However, 
//  the low order three bits suffer from thermal noise and are shifted out.
//
//  To smooth the data, a five-element, Gaussian filter is use on each 
//  data point. This minimizes noise yet preserves most local peak events.
//
//  The data from the Gaussian filter is stored in a three level, 
//  run-length encoded array. The three levels are the high, middle and 
//  low temperatures saved as a byte value and a byte counter. When a new 
//  high or new low temperature reading occurs, the last array element is 
//  saved as a run-length word and the temperature arrays shifted. The 
//  run-length word is written to FLASH and the part goes to sleep again.
//
//  IAR Project -> Options
//     General Options
//        Target -> MSP430F2013
//        Library Configuration -> CLIB
//        Library Options -> printf -> Small
//     C/C++ compiler
//        Optimization -> size -> High (smallest)
//     FET Debugger
//        Erase main and information memory -> to initialize part
//        Retain unchanged memory -> to dump temperature record
//     
//    
//  Bob Wilson, September 19, 2006, bwilson4use@hotmail.com
//
/******************************************************************************/

#include "msp430x20x3.h"
#include "intrinsics.h"
#include "stdio.h"
#include "string.h"
#include "limits.h"

//
//  The maximum temperature in 1/10th degrees, should be set by 
//  the battery chemistry. As for the FLASH itself, the limit is just
//  above 100C.
//
#define MAX_TEMP 2200

union rlen {
  unsigned int temp;                        // The integer
  unsigned char rb[2];                     // The byte values
};

unsigned int * temp_begin;                // Lowest data address
static unsigned int temp_cnt = 0;         // Highest code address
static unsigned int temp_cur = 0;         // Current index

static unsigned char lcnt = 0;              // Count of low values
static unsigned char mcnt = 0;              // Count of medium value
static unsigned char hcnt = 0;              // Count of high values
static unsigned int ltemp = 0;              // Low temperature value
static unsigned int mtemp = 0;              // Medium temperature value
static unsigned int htemp = 0;              // High temperature value


static unsigned int wdt_cnt=0;                   // Interrupt counters
static unsigned int sd16_cnt=0;

#define tcnt 6
static unsigned int temp[tcnt];                 // Temp measurements

//
//  FLASH operation code is supposed to run out of RAM memory in part because
//  FLASH is so slow to write. However, testing suggests this is not
//  always the case. 
//
void flash(unsigned int stemp)
{
  union rlen rle;                       // Local data
  //
  //  Range testing
  //
  if ( stemp > 32687 )              // Did we measure a negative temp?
  {
    stemp = 0;                      // Lowest temp is 0
  }
  //
  //  Shutoff if TOO HOT!
  //
  if ( stemp > MAX_TEMP )               // Have we reached limit?
  {
    _BIC_SR(GIE);                   // Stop interrupts
    _BIS_SR(LPM4_bits);             // Save yourself, try to shutoff
  }
  //
  //    Run-length encoding code
  //
  rle.temp = 0;                 // Nothing for now
  if ( (lcnt == 0) & (hcnt == 0) & (mcnt == 0) ) // First entry?
  {
    htemp = stemp;                    // Use first temp for both
    mtemp = stemp;                    // Medium temperature
    ltemp = stemp;                    //   so we have a start
    lcnt = 1;                         // Count this one
    return;                         // Done for now
  }
  //
  //  See if we have an ltemp match
  //
  if ( stemp == ltemp )
  {
    if (lcnt < UCHAR_MAX)           // Still within count range?
    {
      lcnt++;                       // Count this low temp
      return;
    }
    rle.temp = ltemp >> 3;      // Shift into a byte
    rle.rb[1] = lcnt;           // take the count
    lcnt = 1;                   // Keep temp, restart counter
  }
  //
  //  See if we have an mtemp match
  //
  if ( stemp == mtemp )
  {
    if (mcnt < UCHAR_MAX)           // Still within count range?
    {
      mcnt++;                       // Count this low temp
      return;
    }
    rle.temp = mtemp >> 3;      // Shift into a byte
    rle.rb[1] = mcnt;           // take the count
    mcnt = 1;                   // Keep temp, restart counter
  }  
  //
  //  See if we have an htemp match
  //
  if ( stemp == htemp )
  {
    if ( hcnt < UCHAR_MAX )         // Still within count range?
    {
      hcnt++;                         // Count this high temp
      return;
    }
    rle.temp = htemp >> 3;      // Shift into a byte
    rle.rb[1] = hcnt;           //  take the count
    hcnt = 1;                   // Keep temp, restart counter
  }
  //
  //    See if a new low has arrived
  //
  if (stemp < ltemp)              // New cold temperature?
  {                         // YES - dump the last high
    rle.temp = htemp >> 3;        // Bring temp to a byte value
    rle.rb[1] = hcnt;             // New RTL is ready
    htemp = mtemp;                // Shift up ltemp
    mtemp = ltemp;                //  save the new mtemp
    hcnt = mcnt;                  // and counter
    mcnt = lcnt;                  //  save in new mtemp
    ltemp = stemp;                // Save the current
    lcnt = 1;                     // Start counting
  }
  //
  //  See if a new high has arrived
  //
  if (stemp > htemp)            // New hot temp?
  {                       // YES - dump the last low
    rle.temp = ltemp >> 3;        // Bring temp to a byte value
    rle.rb[1] = lcnt;             // New RTL is ready
    ltemp = mtemp;                // Shift down htemp
    mtemp = htemp;                //   into the medium
    lcnt = mcnt;                  //   and counter
    mcnt = hcnt;                  //   including medium counter
    htemp = stemp;                // Save the current temp
    hcnt = 1;                     // Start counting
  }
  //
  //  Halt the watchdog, write flash, restart watchdog
  //
  WDTCTL = WDTPW + WDTHOLD;       // Turn off the watchdog timer, for now, no recursion
  temp_begin[temp_cur] = rle.temp; // Save the run length encode value
  temp_cur += 1;                // Point to next
  
  if ( temp_cur <= temp_cnt )
  {
    WDTCTL = WDTPW +            // Makes the watchdog work as a timer
      WDTSSEL +                 // use 32,768 kHz clock, / 32768, ~9.5 sec
        WDTTMSEL ;              // Puts in interval time mode  
  }
  else
  {
    P1OUT = 0x01;               // Enable the LED, done.
  }
}

void gausian(void)
{
  unsigned long sum;      // Start the conversion
  sum = (6 * temp[3]) + (4 * (temp[2]+temp[4])) + (temp[1]+temp[5]);
  sum /= 16;              // Normalize
  sum += 4;               // Add half of shift
  sum = sum >> 3;         // Shift out lower bits
  sum = sum << 3;         // Shift back to normal, *8
  flash( sum );         // Write the flash value
}

void start_adc(int sd16inctl0)
{
  //
  //  Configure the SD16_A to read the thermister voltage
  //
  SD16AE = 0;                 // No external interfaces
  SD16INCTL0 = sd16inctl0;    // Input control register
  SD16CTL =                  // Control register
    0 +                      //   clock divider
      SD16LP +                 //   low power mode 
        0 +                      //   clock divider
          SD16SSEL1 +              //   use ACLK 
            0 +                      //   V(MID) buffer off (not in 2013?)
              SD16REFON +              //   reference on
                SD16OVIE ;               //   enable overflow interrupt
  SD16CCTL0 =                 // Channel 0 control register
    SD16UNI +                 //   unipolar mod
      //SD16XOSR +                //   extended mode, 1024 sampling
      SD16SNGL +                //   single conversion
        SD16OSR1 + SD16OSR0 +    //   32 oversampling
          0 +                       //   don't toggle on SD16MEM0 reads
            SD16LSBACC +              //   read the low order 16-bits
              0 +                       //   offset binary, not 2s complement
                SD16IE +                  //   interrupt enable
                  SD16SC ;                  //   start conversion
}

//
//  This is entered via a Power On Reset and the 'glue' logic includes
//  an invisible routine to clear the RAM. By default, the watchdog
//  timer re-enters this way, thus wiping out all flags and counters.
//
void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;       // Turn off the watchdog timer, for now, no recursion
  //
  //  Setup the clocks for all functions
  //
  BCSCTL3 = 0;                     // Use Xtal, lowest 1 pf stablity
  BCSCTL2 = SELM1+SELM0 + DIVM1+DIVM0 + DIVS1; // Xtal drives MCLK, /8, DCO -> SCLK
  //
  //  We use DCO at 1 mHz, divided by 4, for FLASH writes.
  //
  BCSCTL1 = CALBC1_1MHZ;      // Set the calibrated clock value
  BCSCTL1 |= DIVA1+DIVA0;     // 1 mHz, Divide Xtal / 8 for ACLK
  DCOCTL = CALDCO_1MHZ;       // Set DCO step and modulation
  //
  //  Find the available FLASH memory for data storage.
  //
#pragma segment="DATA16_C"
#pragma segment="INTVEC"
  temp_cnt = (unsigned int) __segment_end("DATA16_C"); // Grab the last byte
  temp_cnt += 1;              // Move up one
  temp_cnt &= 0xfffe;         // Round to word boundry
  temp_begin = (unsigned int *) temp_cnt; // Point to first FLASH word
  temp_cnt = (unsigned int) __segment_begin("INTVEC") - temp_cnt; // Remember the end
  temp_cnt /= 2;              // Make it a word count
  
  if ( temp_begin[0] == 0xffff ) // Is first flash still unused?
  {
    FCTL1 = FWKEY + WRT;        // Make flash writable
    FCTL2 = FWKEY + FSSEL_3 + FN2;  // Use SCLK, / 4
    FCTL3 = FWKEY ;           // Turn off the locks, 
    //
    //  Setup the watchdog timer as an interval timer
    //
    WDTCTL = WDTPW +            // Makes the watchdog work as a timer
      WDTSSEL +                 // use 32,768 kHz clock, / 32768, ~9.5 sec
        WDTTMSEL ;              // Puts in interval time mode
    IE1 |= WDTIE;               // Enable timer interrupts when GIE enabled
    //
    //  Initialize the output LED port and go to sleep
    //
    _BIS_SR(GIE);               // Enable the interrupts
    //
    //  Enter the lowest possible, wait state pending interrupt(s).
    //
    P1DIR |= 0xFF;              // Avoid floating pins drain, set unused to output
    P1OUT = 0x01;               // Enable LED
    _BIS_SR(LPM3_bits);         // Turn off CPU in idle loop
    //   MCLK, SMCLK, DCO osc disabled
    //   DC gen. disabled
    //   ACLK remains active
    //   LED still flashes
    //
  }
  else                        // No - report the values
  {
    int i = 0;                // Counter for flash memory
    unsigned long sec = 0;    // Counter for elapsed time
    union rlen rle;           // Local decoder for run length
    unsigned int ltemp=0;     // Local time
    
    while ( temp_begin[i] != 0xffff ) // Pass through data
    {
      rle.temp = temp_begin[i]; // Pickup compressed into two bytes
      if ( rle.rb[1] > 0 )     // Is this a counted temperature?
      {
        printf("%ld,", sec);     // Lets see the last second start
        sec += rle.rb[1];       // Add the seconds byte to new end time
        rle.rb[1] = 0;          // Clear out the temp bits
        ltemp = rle.temp << 3; // Restore temperature
        printf("%d\n%ld,%d\n", 
               ltemp, sec, ltemp); // Lets see the temperature
      }
      i += 1;                    // Point to next
    }
  }
}

// 0xFFF4 Watchdog Timer - start temperature measurement
#pragma vector=WDT_VECTOR
__interrupt void wdt_vector(void)
{
  wdt_cnt +=1;                // Count ISR
  if ( wdt_cnt >= tcnt )      // Have we reached limit of temp array?
  {
    gausian();                 // Convert the gausian array
  }
  start_adc(SD16INCH2 + SD16INCH1);   // Begin temperature recording
  if ( temp_begin[0] == 0xffff ) // Are we still getting started?
  {
    P1OUT ^= 0x01;            // Flash LED to other state
  }
  else
  {
    P1OUT = 0;                // Everything off
  }
}

// 0xFFEA Sigma Delta ADC
#pragma vector=SD16_VECTOR
__interrupt void sd16_vector(void)
{
  sd16_cnt +=1;                  // Count ISR
  int i;                        // Counter for data shifts
  
  SD16IV = 0;                    // Suppress recursive interrupt
  i = SD16INCTL0 & (SD16INCH2+SD16INCH1+SD16INCH0) ; // Get the source bits
  
  if ( i == (SD16INCH2+SD16INCH1+SD16INCH0) ) // Was this a zero request?
  {
    temp[0] = temp[0] - SD16MEM0;     // Adjust for zero
    temp[0] /= 4;                    // Scale to 0.1F
    temp[0] -= 4516;                 // Offset to base for 32 F.
    temp[0] += 4;               // Add half of shift
    temp[0] = temp[0] >> 3;         // Shift out lower bits
    temp[0] = temp[0] << 3;         // Shift back to normal, *8
    
    for (i=tcnt-1; i > 0; i--)     // Pass through existing array
    {
      temp[i] = temp[i-1];         // Shift all values up
    }
    
    temp[0] = 0;                  // Clear the low
  }
  else                            // No, assume a data request
  {
    temp[0] = SD16MEM0;           // Get the value
    start_adc(SD16INCH2 + SD16INCH1 + SD16INCH0); // Get the zero to adjust
  }
}

// End of code segment?