/******************************************************************************/
// 
//  temp_recorder
//
//  This routine configures the F2013 to run at minimum power and 
//  and record temperature to FLASH memory. A 32.786 kHz Xtal is required.
//
//  The temperature is saved in FLASH in run-length encoded words. Upon
//  first execution, five samples are collected and a Gausian filter
//  used to smooth the data. The first samples flash the LED on and
//  off. The three buffer, run-length encording minimizes 
//  oscillations in the output data when the temperature changes.
//
//  There are about 128 words of FLASH memory and in the current
//  release, up to 42 hours of a constant temperature could be recorded.
//  Temperature changes reduce the duration. When FLASH is exhausted, the
//  LED glows. To flush the current run-length buffers, just warm or cool
//  the part by 3F.
//
//  Upon second power up, the firmware detects the FLASH data and report
//  each temperature interval to the debugger terminal window. With the
//  required 32.786 kHz Xtal, each sample interval is 9.5 seconds. The
//  temperature accuracy is 0.8F and the temperatures are reported in
//  10*F degress (aka., 720 -> 72.0 degrees). Use excel to normalize the
//  data.
//
//  There are several simple changes that could increase the available
//  FLASH storage and improve the run-length encoding efficiency.
//
//  Bob Wilson, August 31, 2006, bwilson4use@hotmail.com
//
/******************************************************************************/

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

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 runs out of RAM memory in part because
//  it is so slow to write. This following 'flash' module is
//  copied over into RAM, fcode[] and then called with the data to be written
//  and location.
//
void flash(unsigned int stemp)
{
  union rlen rle;                       // Local data
  //
  //  Range testing
  //
  if ( stemp > 32687 )              // Did we measure a negitive temp?
  {
    stemp = 0;                      // Lowest temp is 0
  }
  //
  //  Shut yourself off if TOO HOT!
  //
  if ( stemp > 2120 )               // Have we reached boiling?
  {
    _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 < 126)
    {
      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 < 126)
    {
      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 < 126 )
    {
      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
    SD16BUF0 +                //   input buffer, slow speed
      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
    {
      printf("%d,", sec);     // Lets see the new second start
      rle.temp = temp_begin[i]; // Grab the value
      sec += rle.rb[1];       // Grab the seconds to new end time
      rle.rb[1] = 0;          // Clear out what was left
      ltemp = rle.temp << 3; // Restore temperature
      printf("%d\n,%d,%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?