Using a MAX6675 temperature sensor without a library

When you are using a sensor, or other type of peripheral, it is always handy to use one of the available libraries. Sometimes though there is no library, or the instructions for the peripheral are simple enough to not use a library, or the library gives a lot of overhead.
In an earlier article, I showed how the (excellent) datasheet of the good old BMP180 offers a blueprint on using the sensor without library on an attiny85.

Purely as a learning moment, I will now do the same for an SPI sensor, the MAX6675 temperature sensor on an ESP8266. Yes I know, there is a library available, but consider this just as an exercise in dealing with SPI peripherals.
The MAX6675 datasheet states the following:

Force CS low and apply a clock signal at SCK to read the results at SO. Forcing CS low immediately stops any conversion process. Initiate a new conversion process by forcing CS high. Force CS low to output the first bit on the SO pin. A complete serial interface read requires 16 clock cycles.Read the 16 output bits on the falling edge of the clock.

The first bit, D15, is a dummy sign bit and is always zero. Bits D14–D3 contain the converted temperature in the order of MSB to LSB. Bit D2 is normally low and goes high when the thermocouple input is open. D1 is low to provide a device ID for the MAX6675 and bit D0 is three-state.

There is another piece of information hidden in the Datasheet, saying that the 12 Bit output is in steps of 0.25 degrees.
Another piece of info that would come in handy, but that is void in the datasheet is the signal timings. Yes, there are some timing diagrams in the datasheet, but they mention no numbers, so we have to just do some trial and error. The speed of the SPI-bus itself is half of the CPU processor speed so in the ESP8266 it runs at 40Mhz. We willl just leave it at that.

In the table of the SO output register we see that apart from MSB (bit15) we also do not really need the 3 LSB’s, but we will read them anyway and deal with them. Bit 2 does have some function as it indicates whether the Sensor is connected or not.
Finally, we have to multiply the 12 bit value we found with 0.25 because  the raw value found gives the number of quarter degrees.

So in order to get the temperature we need to do the following things:

  • Begin with CS low to stop any conversion. To start fresh so to say
  • Then make CS high, to start the conversion
  • Pull CS low to output the bits to MISO on falling edge of clock.
  • Cycle the clock to read all the 16 incoming bits on the MISO pin
  • Ignore the MSB (bit 15, remember, we count from 0)
  • Discard the 3 LSB’s by a right shift 3
  • Multiply the found value with 0.25 (or divide by four).

In Software that goes like this:
Define the pins and variables we will use

#define cs D8 //cs
#define clk D5 //sck
#define miso D6 // so
//define variables
int v = 0;
float tempC;//for temperature in degrees Celsius
float tempF;//for temperature in degrees Fahrenheit

In the setup, we define the pinModes and the begin states of the pins

void setup() {
pinMode(cs, OUTPUT);
pinMode(clk, OUTPUT);
pinMode(miso, INPUT);
digitalWrite(cs, HIGH);
digitalWrite(clk, LOW);

In the loop, we  call a procedure that reads and returns the MISO bits

void loop() {
v = spiRead();
if (v == -1) {
  Serial.print("Sensor not found");
else {
  tempC = v * 0.25; //Celsius (read on,the 0.25 will become clear)
  tempF= tempC * 9.0/5.0 + 32;//Fahrenheit

Then in the spiRead procedure, we read the  the first MSB (bit15) and discard it.  Then in a loop we read the remaining 15 bits (bit 0-bit 14) and add them to a variable, we then discard bit 0,1 and 2, so we have a 12 bit number left. This number than still needs to be multiplied by 0.25 to give the result in degrees celsius

int spiRead() 
int value = 0;

//Deal with bit 15 first
digitalWrite(cs,LOW);//Pull CS low to start conversion
digitalWrite(clk,LOW);// bit is read on downflank

//Read bits 14-0 from MAX6675 and store in 'rawTmp'
for (int i=14; i>=0; i--) 
  rawTmp += digitalRead(miso) << i;   digitalWrite(clk,LOW); } // if bit D2 is HIGH no sensor connected   if ((rawTmp & 0x04) == 0x04) return -1; //remaining 14 bits read, now discard bits 0,1 and 2 //we do that with a shift right three places return rawTmp >> 3;

The section

// if bit D2 is HIGH no sensor connected
  if ((rawTmp & 0x04) == 0x04) return -1;

needs a bit of explaining. Once we read all 15 bytes, we can check bit 2 to see if that is set or not, to see if the sensor is detached or connected. the hex value 0x04 is equal to 0b00000100, so no matter what the value of our 15 bit raw temperature is, if we ‘AND’ it with 0b0b00000100, only bit number 2 will matter. If that bit is set, the result is 0b00000100 again, which is 0x04. So if we AND the raw temperature with 0x04 and the result is 0x04, we know that bit was HIGH, meaning the sensor was not connected.

Once that is done, we remove the  3 unnecessary bits (2,1 and 0) by the rightshift with 3.

It will probably be clear that if  we would have defined the command:

for (int i=14; i>=0; i--)


for (int i=14; i>=2; i--)

we would only need to do a rightshift 1 like so

return rawTmp >> 1;

As we have to multiply the rawTmp value with 0.25, or divide it by 4. You might be enticed to do that with  a bitshift because after all, division by 4 is a rightshift with 2.
So, in theory it is possible to combine:
v=rawTmp >>3

as :
v=rawTmp >>5
Yet that is not a good idea.
Suppose we find the value “101001000” (=328)
with a rightshift 3 that becomes “101001” (=41)
divided by 4 that is 10.25 degrees.
However, if we would have combined it in a rightshift 5, we would have gotten “1010” which is 10.

Once it is all done we can convert the degrees Celsius to degrees Fahrenheit by the known formula: *9/5 +32
Download the code here.
In a next article, I’ll hook up the MAX6675 to an ESP8266-01 and will make a monitoring system for a fireplace or cooktop