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 codeNote 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