Using
the
MAX31855
Thermocouple Converter with ATmega168
(Last
updated 1 Jan 2013)
I needed a wide-range A/D for an oven PID controller I was
making.  I chose the MAX31855 thermocouple converter and matching
K-type thermocouple.  I purchased both devices from Adafruit.  But I found issues
with
the code samples provided by Adafruit on their git repository and ended
up rolling my own.
For the record, the issues I had with their code were:
1.  Bit-banged SPI rather than hardware SPI.
2.  The bit-banged SPI was really slow, as in a 500 Hz SPI clock,
when the 31855 device can run MUCH faster
3.  The conversion code used floating point operations.  This
is probably good for most applications, but I knew my app only needed
integer math and I didn't want to drag in the extra 4K of code to
support float operations that were only going to be done as an
intermediate step.
4.  The code was wrong for negative raw values from the
MAX31855.  I wasn't expecting to see negative values in my oven
temperature controller, but that error still needed fixing.
5.  The code was written in C++, as it was presented as an Arduino
sketch.  Sorry, not interested.
Revised 1 Jan 2013
It is possible that item 4 above is actually compiler-dependent. 
The gcc-avr compiler does not sign-extend on right-shift, but perhaps
the Arduino compiler does.  I don't use Arduino so can't test.
The updated MAX31855 code
Here is my updated code. 
Note that this is NOT a finished program and will not
compile.  This is a set of subroutines that you can drop into your
project, adjust the SPI literals as needed, and talk to the MAX31855
via SPI.  In my implementation, this code compiles using the
WinAVR gcc tool suite (WinAVR-20100110) and runs on an ATmega168
using a 20 MHz crystal.
=============================================================================
/*
 *  Define literals for the SPI port accesses and the
thermocouple chip
 *  select line.
 */
#define  PORT_THERMO_CS       
   PORTD
#define  DDR_THERMO_CS       
    DDRD
#define  BIT_THERMO_CS       
    7
#define  MASK_THERMO_CS       
   (1<<BIT_THERMO_CS)
#define  PORT_SPI       
         PORTB
#define  DDR_SPI       
          DDRB
#define  BIT_SPI_SCK       
      5
#define  MASK_SPI_SCK       
     (1<<BIT_SPI_SCK)
#define  BIT_SPI_SS       
       2
#define  MASK_SPI_SS       
      (1<<BIT_SPI_SS)
#define  BIT_SPI_MISO       
     4
#define  MASK_SPI_MISO       
    (1<<BIT_SPI_MISO)
/*
 *  ThermoInit      set up hardware
for using the MAX31855
 *
 *  This routine configures the SPI as a master for exchanging
 *  data with the MAX31855 thermocouple converter.  All
pins
 *  and registers for accessing the various port lines are
 *  defined at the top of this code as named literals.
 */
static void  ThermoInit(void)
{
    PORT_THERMO_CS |= MASK_THERMO_CS;   
    // start with CS high
    DDR_THERMO_CS |= MASK_THERMO_CS;   
     // now make that line an output
    PORT_SPI |= MASK_SPI_SS;   
             // SS* is not
used but must be driven high
    DDR_SPI |= MASK_SPI_SS;   
              // SS*
is not used but must be driven high
    PORT_SPI &= ~MASK_SPI_SCK;   
           // drive SCK low
    DDR_SPI |= MASK_SPI_SCK;   
             // now
make SCK an output
    SPCR = (1<<SPE) | (1<<MSTR) |
(1<<SPR0) | (1<<SPR1) | (1<<CPHA);
            
           
           
        // enable SPI as master, slowest
clock,
            
           
           
        // data active on trailing edge
of SCK
}
/*
 *  ThermoReadRaw      return 32-bit
raw value from MAX31855
 *
 *  This routine uses a four-byte SPI exchange to collect a
 *  raw reading from the MAX31855 thermocouple
converter.  That
 *  value is returned unprocessed to the calling routine.
 *
 *  Note that this routine does NO processing.  It does
not
 *  check for error flags or reasonable data ranges.
 */
static int32_t  ThermoReadRaw(void)
{
    int32_t       
              d;
    unsigned char       
        n;
    PORT_THERMO_CS &=
~MASK_THERMO_CS;    // pull thermo CS low
    d = 0;       
           
            // start with
nothing
    for (n=3; n!=0xff; n--)
    {
        SPDR = 0;   
           
         // send a null byte
        while ((SPSR &
(1<<SPIF)) == 0)  ;    // wait until transfer
ends
        d = (d<<8) +
SPDR;           
    // add next byte, starting with MSB
    }
    PORT_THERMO_CS |=
MASK_THERMO_CS;     // done, pull CS high
/*
 *                            
Test
cases
 *
 *  Uncomment one of the following lines of code to return
known values
 *  for later processing.
 *
 *  Test values are derived from information in Maxim's
MAX31855 data sheet,
 *  page 10 (19-5793 Rev 2, 2/12).
 */
//  d = 0x01900000;       
    // thermocouple = +25C, reference = 0C, no faults
//  d = 0xfff00000;       
    // thermocouple = -1C, reference = 0C, no faults
//  d = 0xf0600000;       
    // thermocouple = -250C, reference = 0C, no faults
//  d = 0x00010001;       
    // thermocouple = N/A, reference = N/A, open fault
//  d = 0x00010002;       
    // thermocouple = N/A, reference = N/A, short to GND
//  d = 0x00010004;       
    // thermocouple = N/A, refernece = N/A, short to VCC
    return  d;
}
/*
 *  ThermoReadC      return
thermocouple temperature in degrees C
 *
 *  This routine takes a raw reading from the thermocouple
converter
 *  and translates that value into a temperature in degrees
C.  That
 *  value is returned to the calling routine as an integer
value,
 *  rounded.
 *
 *  The thermocouple value is stored in bits 31-18 as a
signed 14-bit
 *  value, where the LSB represents 0.25 degC.  To
convert to an
 *  integer value with no intermediate float operations, this
code
 *  shifts the value 20 places right, rather than 18,
effectively
 *  dividing the raw value by 4 and scaling it to unit
degrees.
 *
 *  Note that this routine does NOT check the error flags in
the
 *  raw value.  This would be a nice thing to add later,
when I've
 *  figured out how I want to propagate the error
conditions...
 */
static int  ThermoReadC(void)
{
    char       
           
    neg;
    int32_t       
             d;
    neg = FALSE;       
        // assume a positive raw value
    d = ThermoReadRaw();   
    // get a raw value
    d = ((d >> 18) & 0x3fff);   //
leave only thermocouple value in d
    if (d & 0x2000)   
         // if thermocouple reading
is negative...
    {
        d = -d &
0x3fff;        // always work with
positive values
        neg = TRUE;   
         // but note original value
was negative
    }
    d = d + 2;       
          // round up by 0.5 degC (2
LSBs)
    d = d >> 2;   
             // now
convert from 0.25 degC units to degC
    if (neg)  d = -d;   
       // convert to negative if needed
    return  d;       
          // return as integer
}
/*
 *  ThermoReadF      return
thermocouple temperature in degrees F
 *
 *  This routine takes a reading from the thermocouple
converter in
 *  degC and converts it to degF.
 *
 *  Note that this routine simply calls ThermoReadC and
converts
 *  from degC to degF using integer math.  This routine
does not
 *  see the raw converter value and cannot do any error
checking.
 */
static int  ThermoReadF(void)
{
    int       
           
      t;
    t = ThermoReadC();   
       // get the value in degC
    t = ((t * 90) / 50) + 32;    //
convert to degF
    return  t;       
           // all done
}
=====================================================================
Home