/******************************************************************************/
//
// 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?