Help communicating to a MAX31855 over hardware SPI

Hey everyone, I’m new to working with hardware and decided to learn by interfacing with some MAX31855 thermocouple sensors. I’ve successfully read temperatures from them using a software SPI, basically doing all of the CLK switching manually, and decided to give the hardware SPI a try but I’m unsure how to correctly interface with it.

I’m using the Circuits.SPI library. I’ve connected the MAX31855 pins according to this layout

DO → SPI_MISO
CLK → SPI_SCLK
CS → SPI_CE0

I’ve done Circuits.SPI.bus_name which returns ["spidev0.0", "spidev0.1"] I assume spidev0.0 corresponds to CE0, so I then proceeded to do SPI.open("spidev0.0", speed_hz: 5_000_000, delay_ns: 100) The opts I’ve added by looking at the MAX31855 datasheet (https://datasheets.maximintegrated.com/en/ds/MAX31855.pdf). I’m really not sure what to do from here, I’ve tried, as a test, SPI.transfer(ref, <<0>>) and it just returns {:ok, <<0>>}.

Any help would be appreciated as I’m pretty lost here since the example for Circuits.SPI is interfacing with an ADC.

I’ve never used this device myself, but couple things i notice here… SPI usually requires more wires.

The main signals are

  • MISO - Master In Slave Out
  • MOSI - Master Out Slave In
  • CLK - Clock

Optional signals will be

  • CS - Cable Select
  • CE - Chip Enable

And occasionally you will have
*INT - interups

As far as i know the Linux driver will not operate CS or CE themselves. You usually will need to use
Circuits.GPIO to toggle these. You will probably want to use spidev0.0, but it depends on the bus you used. If you are on Raspberry pi, the 0.0 bus is the top one. You will also want to look at your data sheet to see how to use CS or CE. (It looks like you have CS). CS needs to be set high or low for a SPI device to know that it is it’s turn to use the bus. if you have a CE, it tells the chip to be on or off. You will need to look at the datasheet to tell for sure. You also need to get another data line for MOSI (master out, slave in) this is the wire that will be transmitting data TO the device from Nerves

2 Likes

Hey thanks for the reply. This device has 6 pins total. The other 3 are 3.3v Vin, GND, and 3Vo. I don’t believe there is anything for MOSI. Here is a pin layout I’ve been able to find for connecing to a RPI, Hardware | MAX31855 Thermocouple Sensor Python Library | Adafruit Learning System.

I was under the impression that Circuits.SPI handled selecting the chip. From the Circuits.SPI example I can see that a CE pin from the RPI is connected to a CS pin and there is no manual toggling with GPIO.

After messing around with the wiring a bit on the breadboard, I’ve discovered that the +/- rails seem to have issues because by connecting the power and ground directly to the breakout board I’m now getting something else out of the transfer

{:ok, <<oc::1, scg::1, scv::1, _::1, ref::12, fault::1, _::1, temp::14>>} = SPI.transfer(t, <<0::32>>)
{:ok, <<1, 68, 20, 224>>}

I’ve been playing around with a breakout board (from Adafruit) that has a MAX31865 IC which, I think, is just a later iteration of the MAX31855 (I haven’t looked at the datasheet for the MAX31855 yet). Anyway, I also had some trouble figuring out how to communicate with the IC via SPI but I eventually figured it by reading the source code of the Adafruit python library and running the python library with lots of debug statements sprinkled around.

I have my breakout board wired to a Raspberry Pi Zero. On the breakout board I have I’m using the pin outs for power and ground, of course. For the SPI communication I’m using:

CLK → BCM 11 (SCLK)
SDO → BCM 9 (MISO)
SDI → BCM 10 (MOSI)
CS → BCM 8 (CE0)

To open a connection I use:

iex(gadget@nerves.local)1> {:ok, ref} = Circuits.SPI.open("spidev0.0", mode: 1, speed_hz: 500_000)

I set the mode to 1 because the datasheet for the MAX31865 says it works in mode 1 or 3. The speed_hz to 500_000 because that’s what’s used in the Adafruit documentation and source code (I’m kind of assuming I have the value set correctly).

At this point I can read from the config register (0x00 when reading).

iex(gadget@nerves.local)2> Circuits.SPI.transfer(ref, <<0x00, 0x00>>)
{:ok, <<0, 0>>}

And set the config register (0x80 when writing) to the values that correspond to my setup which is a 3-wire PT100 RTD.

iex(gadget@nerves.local)3> Circuits.SPI.transfer(ref, <<0x80, 0x90>>)
{:ok, <<0, 0>>}

And read from the config register again to make sure it is set correctly.

iex(gadget@nerves.local)4> Circuits.SPI.transfer(ref, <<0x00, 0x00>>)
{:ok, <<0, 144>>}

When reading from the data register of the MAX31865 (0x01) I will get back 3 bytes of binary data. The first byte is ignored (same applies when reading from the config register). Then next 2 bytes are the data that I want. The first 15 bits of that data is the actual resistance value of the RTD. The last bit is the fault bit that will be 0 or 1 (if there was a fault you can read from the fault registers to see what it was). To read the resistance from the chip I do a transfer/2 and pattern match.

First, I have to configure the chip for a 1-shot data transfer. This value is in the datasheet.

iex(gadget@nerves.local)5> Circuits.SPI.transfer(ref, <<0x80, 0xB0>>)

Then I can initiate a transfer and get the resistance value back in the binary.

iex(gadget@nerves.local)6> {:ok, <<_::size(8), resistance::size(15), fault_bit::size(1)>>} =
      Circuits.SPI.transfer(ref, <<0x01, 0x00, 0x00>>)

The first byte of the binary that I’m passing into the transfer\2 function is the hex value of the data register on the MAX31865. I also add 2 more bytes because the transfer function is going to return me the same amount of binary data that I pass in. Since I know that I’m going to get 2 bytes worth of data back, I add 2 empty bytes in my binary that I pass into the transfer function. That’s the part that really tripped me up.

Once I have the resistance value I can do the maths to convert it to a temperature value. I created a Raspbian build to run the python libraries for debugging and to also take a measurement of the resistance value so I could test that it was the same when using Circuits.SPI on a Nerves build. The values where the same and after converting the resistance value to a temperature value and comparing it to a normal thermometer the values are only off by 2 degrees. So, I think it’s working but this is all an experiment for me too.

Hope that helps.

After looking closer at the datasheet for the MAX31855 it doesn’t look like it uses registers like the MAX 31865. I think in your case you get all the data back in one full chunk of 32 (0-31) bits with bits 30 - 18 containing the temp data. Bit 31 is the first one returned. So after dropping the CS pin low you would initiate a transfer of 32 bits. Something like this:

Circuits.SPI.transfer(ref, <<0x00, 0x00, 0x00, 0x00>>)

Then I think you will need to wait a bit before raising the CS pin high again because, according to the datasheet,

A complete serial-interface read of the cold-junction compensated thermocouple temperature requires 14 clock cycles.

So the time you have to wait depends on the clock timing that you are applying at SCK.

Once the timing has completed check the values that you patterned matched on and see if you have data.

Haha I was just replying to your previous post when you posted again.

It looks like I’ve successfully been able to get data out by doing, {:ok, <<temp_sign::1, temp::13, _::1, fault::1, ref_sign::1, ref::11, _::1, scv::1, scg::1, oc::1>>} = SPI.transfer(t, <<0::32>>). I’m still unsure about the whole needing to manually drop the CS pin low because simply doing the transfer seems to be working. I’ll have to see what happens when I add in a second MAX31855.

Thanks for the help!

Yeah, I reread your post and it looks like you have it. I think you’re also correct about changing the CS pin value.

Funny because I was working on this when I found your post and thought I would do a brain dump to see if it would help. Like you, I haven’t found a lot of examples demonstrating how to talk to different devices through SPI. Hopefully someone else will find this helpful too.

1 Like

Just replying to say that I was able to use 2 sensors without issue, using the same method, with the second sensors CS pin connected to SPI_CS1. It seems like the Circuits.SPI library handles the CS/CE switching.

2 Likes

Thanks for posting this! I’m still trying to wrap my head around how to talk over an SPI interface and found it helpful to read through your posts

2 Likes