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