Max31856 and SPI problems

I'm trying to combine a few libraries I have to save space, and do a little optimizing in the process with direct port access instead of digitalReads, etc...

The library provided for the Max31856 works, but it doesn't use the SPI library at all, so to start with, I'm trying to just do the bare bones... Write to the registers, and read back from them...

Datasheet here https://datasheets.maximintegrated.com/en/ds/MAX31856.pdf

The long and the short of it is if bit 7 of the first byte is set, you have a write to the address at bits 6:0.. if it's unset, you will read from that address

What am I doing wrong here? The hardware side works fine, but using this code I'm only getting 0xFF back when I read from any register

Here's the code

#include "SPI.h"

#define Pin0  22  //Cable select pins for each channel
#define Pin1  24
#define Pin2  26
#define Pin3  28

#define NumPins 4
int Pins[] = {Pin0, Pin1, Pin2, Pin3};

#define NumRegisters 12
byte RegisterValues[] =
{0x90, 0x03, 0xFF, 0x7F, 0xC0, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00};
//Values I want to put in each register



void setup() {
  // put your setup code here, to run once:
 
  InitializePins(); //EDITED TO ADD, fixed 0xFF output, but still not getting what I put into the registers 

  SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0));
  Serial.begin(115200);

  for (int i = 0; i < 1; i++) { //only using one channel for now
    InitializeChannel(Pins[i]);
    VerifyData(Pins[i]);
  }
}

void InitializePins() {
  for (int i = 0; i < NumPins; i++) {
    pinMode(Pins[i], OUTPUT);
    digitalWrite(Pins[i], HIGH);
  }
}

void InitializeChannel(int Pin) {
  digitalWrite(Pin, LOW);
  Serial.print("Set pin to low.. #");
  Serial.println(Pin);
  SPI.transfer(0x80); //Write command starting at Register 0x00
  
  for (int i = 0; i < NumRegisters; i++) {
    SPI.transfer(RegisterValues[i]);
    Serial.print("Writing to register 0x");
    Serial.print(i, HEX);
    Serial.print(" value 0x");
    Serial.println(RegisterValues[i], HEX);
  }
  digitalWrite(Pin, HIGH);

}
void VerifyData(int Pin) {
  digitalWrite(Pin, LOW);
  SPI.transfer(0x00); //Read command starting at Register 0x00
  for (int i = 0; i < NumRegisters; i++) {
    byte RegVal = SPI.transfer(0);
    Serial.print("Register has 0x");
    Serial.print(RegVal, HEX);
    Serial.print(" and should have 0x");
    Serial.println(RegisterValues[i], HEX);
  }
  digitalWrite(Pin, HIGH);

}
void loop() {
  // put your main code here, to run repeatedly:

}

And here's the result of when I run it

Set pin to low.. #22
Writing to register 0x0 value 0x90
Writing to register 0x1 value 0x3
Writing to register 0x2 value 0xFF
Writing to register 0x3 value 0x7F
Writing to register 0x4 value 0xC0
Writing to register 0x5 value 0x7
Writing to register 0x6 value 0xFF
Writing to register 0x7 value 0x80
Writing to register 0x8 value 0x0
Writing to register 0x9 value 0x0
Writing to register 0xA value 0x0
Writing to register 0xB value 0x0
Register has 0xFF and should have 0x90
Register has 0xFF and should have 0x3
Register has 0xFF and should have 0xFF
Register has 0xFF and should have 0x7F
Register has 0xFF and should have 0xC0
Register has 0xFF and should have 0x7
Register has 0xFF and should have 0xFF
Register has 0xFF and should have 0x80
Register has 0xFF and should have 0x0
Register has 0xFF and should have 0x0
Register has 0xFF and should have 0x0
Register has 0xFF and should have 0x0

I must just be missing something and I can't see what it is

OK.. One blunder found.. I never called the "InitializePins" function so all the CS pins were low..

What did it fix? well, I'm getting real data now, not just 0xFF, but I'm not getting the data I sent to it

Have you tried single-byte transfers where you'll need to set the CS pin high between each byte transferred?

What's your SPI clock rate? The MAX31856 has SCK = 5MHz max ... try SPI speed of 1MHz for testing.

Thanks for the ideas...

Whenever I use SPI.beginTransaction... everything goes south.. delay() doesn't work anymore, and I get only 0xFF back, regardless of my mode or SPI speed (I just tried 1mhz)

If I use SPI.begin, I can at least read my registers, and the read seems to work fine, I just can't write what I want.

I just tried setting the CS high between each register.. no difference

I'll keep digging... it's getting frustrating, I'll post a revised version of the sketch soon when I clean it up a little.

Here's a simple loopback test you can do. Note that SCK needs to default HIGH as per datasheet.

// For Loopback test, jumper MOSI to MISO (pin 11 to pin 12 on UNO)

#include <SPI.h>
byte x;

void setup (void)
{
  //SCK defaults HIGH for SPI_MODE2 or SPI_MODE3
  SPI.setDataMode(SPI_MODE2);
  SPI.setClockDivider(SPI_CLOCK_DIV16); //1 MHz
  Serial.begin(115200);
}

void loop (void)
{
  digitalWrite(SS, HIGH);  // ensure SS stays high

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on

  SPI.begin ();

  // enable Slave Select
  digitalWrite(SS, LOW);  // SS is pin 10

  // send test byte
  x = SPI.transfer (0xAB);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  Serial.print("Response: ");
  Serial.println(x, HEX);
  Serial.print("SCK:      ");
  Serial.println(digitalRead(SCK));

  SPI.end ();  // turn SPI hardware off

  delay(3000);  //wait
}

Results:

Reference: Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino

OK, on register CR0, one with no reserved bits, and that is read/write, I ran a little test on it

Write a byte, read it back,.. increment write byte, repeat...

Very strange results, and the results changed if I put a delay between registers, which leads me to believe there is a timing problem.

Theres a serious lack of documentation for the SPI library.. the digital pot control only writes, and the barometric pressure sensor doesn't use SPI.beginTransaction, and any attempts for me to use beginTransaction fail in utter glory.

dlloyd.. I'll try that

OK, dlloyd, that works just fine

So back to the drawing board, i've fiddled and fiddled, it's been 4 hours of it now and I'm nowhere.. I get data, and it's approximately right, but not quite.

So I enlisted the help of my Uno, and made an SPI snooper.. and it's not helping.. doesn't seem to be fast enough to do any kind of math on the input before the next one comes along, even with a 128 clock divider, so I'm getting garbage there too..

I'm bugeyed and burnt out! Calling it a night.

And just in case anyone is curious, here's what I tried for the SPI snooping on the Uno

#define MISO_PIN 12
#define MOSI_PIN 11
#define SS_PIN 3    //must have an interrupt enabled pin
#define CLK_PIN 2   //must have an interrupt enabled pin

#define CLOCK_ISR_MODE CHANGE
#define SLAVE_ISR_MODE CHANGE
#define CPOL 0  //0 is active high, 1 is active low
#define DPOL 0  //0 is active high, 1 is active low

volatile unsigned long MisoData;  //Data received on MISO line
volatile unsigned long MosiData;  //Data received on MOSI line
volatile byte ClockCount; //clock counts between the time Slave goes low to the time it goes high again
volatile boolean SlaveActive;

void setup()
{
	Serial.begin(115200);
	Serial.println("Snooping SPI port");
	pinMode(MISO_PIN, INPUT);
	pinMode(MOSI_PIN, INPUT);
	pinMode(SS_PIN, INPUT);
	pinMode(CLK_PIN, INPUT);

	attachInterrupt(digitalPinToInterrupt(CLK_PIN), ClockISR, CLOCK_ISR_MODE);
	attachInterrupt(digitalPinToInterrupt(SS_PIN), SlaveISR, SLAVE_ISR_MODE);

}

void ClockISR(){
	if(SlaveActive){  //if slave select is pulled low for active
		if(digitalRead((CLK_PIN) ^ CPOL)){
			byte MOSIvalue = digitalRead(MOSI_PIN);
			byte MISOvalue = digitalRead(MISO_PIN);
			MisoData = (MisoData<<1) | (MISOvalue); //bitfiddle
			MosiData = (MosiData<<1) | (MOSIvalue);
			ClockCount++; //increment clock counts
		}
	}
}

void SlaveISR(){
	//Slave got deselected, print everything out

	if(digitalRead(SS_PIN)){
		Serial.print("\nMISO = 0x");
		Serial.print(MisoData, HEX);
		Serial.print("     MOSI = 0x");
		Serial.println(MosiData, HEX);
		Serial.print("Clock counts = ");
		Serial.println(ClockCount);
		MisoData = 0;
		MosiData = 0;
		ClockCount = 0;
		SlaveActive = false;
	}
	else{ //slave deselected
		SlaveActive = true;
	}
}

void loop()
{
}

Try this single byte transfer test (you may need to use SPI_MODE3):

// MAX31856 SINGLE BYTE TRANSFER TEST

#include <SPI.h>
byte addressValues[] = {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B};
byte writeValues[] = {0x90, 0x03, 0xFF, 0x7F, 0xC0, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00};
byte readValues[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

void setup (void)
{
  SPI.setDataMode(SPI_MODE2); //also test using SPI_MODE3
  SPI.setClockDivider(SPI_CLOCK_DIV16); //1 MHz
  Serial.begin(115200);
  SPI.begin ();
  digitalWrite(SS, HIGH);
}

void loop (void)
{
  // See Figure 5. SPI Single-Byte Write
  for (int i = 0; i < 12; i++) {
    digitalWrite(SS, LOW);
    SPI.transfer (addressValues[i]);
    SPI.transfer (writeValues[i]);
    digitalWrite(SS, HIGH);
    delayMicroseconds(100);
  }

  // See Figure 4. SPI Single-Byte Read
  for (int i = 0; i < 12; i++) {
    digitalWrite(SS, LOW);
    readValues[i] = SPI.transfer (addressValues[i]);
    digitalWrite(SS, HIGH);
    delayMicroseconds(100);
  }

  // Print Results
  for (int i = 0; i < 12; i++) {
    Serial.print("Register ");
    Serial.print(addressValues[i], HEX);
    Serial.print(" has value ");
    Serial.println(readValues[i], HEX);
  }

  Serial.println();
  delay(10000); //repeat every 10 seconds
}

OK, I tried that.. here's the results

Register 80 has value 0
Register 81 has value 0
Register 82 has value 0
Register 83 has value 0
Register 84 has value 0
Register 85 has value 0
Register 86 has value 0
Register 87 has value 0
Register 88 has value 0
Register 89 has value 0
Register 8A has value 0
Register 8B has value 0

Register 80 has value FF
Register 81 has value FF
Register 82 has value FF
Register 83 has value FF
Register 84 has value FF
Register 85 has value FF
Register 86 has value FF
Register 87 has value FF
Register 88 has value FF
Register 89 has value FF
Register 8A has value FF
Register 8B has value FF

I think there's a little something wrong with that code, the read/write bit is the high bit of the address you send... so you'd have to zero it in addressValues[]..
Also, You can't be reading the address you're sending at the same time, right?.. so you'd have to send the read command, then transfer 0 and read from that.. That's how I understand it..

Either way, and with either SPI mode, the results aren't changing..

Here's the last edition of the code I ran with what I mentioned above 'fixed' (at least from my understanding)

// MAX31856 SINGLE BYTE TRANSFER TEST

#include <SPI.h>
byte addressValues[] = {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B};
byte writeValues[] = {0x90, 0x03, 0xFF, 0x7F, 0xC0, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00};
byte readValues[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

void setup (void)
{
  SPI.setDataMode(SPI_MODE3); //also test using SPI_MODE3
  SPI.setClockDivider(SPI_CLOCK_DIV16); //1 MHz
  Serial.begin(115200);
  SPI.begin ();
  digitalWrite(SS, HIGH);
}

void loop (void)
{
  // See Figure 5. SPI Single-Byte Write
  for (int i = 0; i < 12; i++) {
    digitalWrite(SS, LOW);
    SPI.transfer (addressValues[i]);
    SPI.transfer (writeValues[i]);
    digitalWrite(SS, HIGH);
    delayMicroseconds(100);
  }

  // See Figure 4. SPI Single-Byte Read
  for (int i = 0; i < 12; i++) {
    digitalWrite(SS, LOW);
    SPI.transfer (addressValues[i] & 0x7F); //unset bit 7 for a read command
    readValues[i] = SPI.transfer(0);
    digitalWrite(SS, HIGH);
    delayMicroseconds(100);
  }

  // Print Results
  for (int i = 0; i < 12; i++) {
    Serial.print("Register ");
    Serial.print(addressValues[i], HEX);
    Serial.print(" has value ");
    Serial.println(readValues[i], HEX);
  }

  Serial.println();
  delay(10000); //repeat every 10 seconds

Found another thing that was forgotten.. Forgot to set all other slave selects to high, so all 4 units were 'talking' at the same time..

It seems to be reading the default values for the registers correctly..

Here's the updated version

// MAX31856 SINGLE BYTE TRANSFER TEST

#include <SPI.h>
byte addressValues[] = {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B};
byte writeValues[] = {0x90, 0x03, 0xFF, 0x7F, 0xC0, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00};
byte readValues[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};


void setup (void)
{
  //set all channels high
  pinMode(22, OUTPUT);
  pinMode(24, OUTPUT);
  pinMode(26, OUTPUT);
  pinMode(28, OUTPUT);
  
  digitalWrite(22, HIGH);
  digitalWrite(24, HIGH);
  digitalWrite(26, HIGH);
  digitalWrite(28, HIGH);

  SPI.setDataMode(SPI_MODE3); //also test using SPI_MODE3
  SPI.setClockDivider(SPI_CLOCK_DIV16); //1 MHz
  Serial.begin(115200);
  SPI.begin ();
  digitalWrite(22, HIGH);
}

void loop (void)
{
  // See Figure 5. SPI Single-Byte Write
  for (int i = 0; i < 12; i++) {
    digitalWrite(22, LOW);
    SPI.transfer (addressValues[i]);
    SPI.transfer (writeValues[i]);
    digitalWrite(22, HIGH);
    delayMicroseconds(100);
  }

  // See Figure 4. SPI Single-Byte Read
  for (int i = 0; i < 12; i++) {
    digitalWrite(22, LOW);
    SPI.transfer (addressValues[i] & 0x7F); //unset bit 7 for a read command
    readValues[i] = SPI.transfer(0);
    digitalWrite(22, HIGH);
    delayMicroseconds(100);
  }

  // Print Results
  for (int i = 0; i < 12; i++) {
    Serial.print("Register ");
    Serial.print(addressValues[i], HEX);
    Serial.print(" has value ");
    Serial.println(readValues[i], HEX);
  }

  Serial.println();
  delay(10000); //repeat every 10 seconds
}

And what it's currently outputting

Register 80 has value 90
Register 81 has value 3
Register 82 has value FF
Register 83 has value 7F
Register 84 has value C0
Register 85 has value 7
Register 86 has value FF
Register 87 has value 80
Register 88 has value 0
Register 89 has value 0
Register 8A has value 13
Register 8B has value D0

I'll have to see if I can write what I want to it now, and if I get that back

OK, everything is working now...

Here's a little sketch that reads 4 MAX31856's, Cable selects are intended for use on a Mega on pins 22, 24, 26, and 28... I will eventually change it to direct port access for my own use, but perhaps someone will find this handy. It does not check for invalid data or faults (yet)

#include "SPI.h"

#define Pin0  22  //Cable select pins for each channel
#define Pin1  24
#define Pin2  26
#define Pin3  28
//If there are other devices on SPI, make sure those Cable select pins are also set to HIGH, but if they aren't MAX31856's, they can't be part of the Pins[]


#define NumPins 4
int Pins[] = {Pin0, Pin1, Pin2, Pin3};

#define NumRegisters 10
byte RegisterValues[] =    {0x90,  0x03,   0xFC,   0x7F,   0xC0,   0x07,     0xFF,     0x80,     0x00,     0x00 };
String RegisterNames[] =   {"CR0", "CR1", "MASK", "CJHF", "CHLF", "LTHFTH", "LTHFTL", "LTLFTH", "LTLFTL", "CJTO"};
byte RegisterAddresses[] = {0x00,  0x01,   0x02,   0x03,   0x04,   0x04,     0x06,     0x07,     0x08,     0x09 };

#define NOP __asm__ __volatile__ ("nop");

void setup() {
	// put your setup code here, to run once:
	Serial.begin(921600);
	SPI.begin();
	SPI.setClockDivider(SPI_CLOCK_DIV16);
	SPI.setDataMode(SPI_MODE3);

	InitializePins();
	for (int i = 0; i < NumPins; i++) { //usually done for each channel..
		InitializeChannel(Pins[i]);		
		delay(10);
		VerifyData(Pins[i]);
	}
	delay(500);
}

void InitializePins() {
	Serial.print("Initializing pins");
	for (int i = 0; i < NumPins; i++) {
		Serial.print(", ");
		Serial.print(Pins[i]);
		pinMode(Pins[i], OUTPUT);
		digitalWrite(Pins[i], HIGH);
	}
	Serial.println("  Done");
}

void InitializeChannel(int Pin) {
	Serial.print("Initializing channel on pin ");
	Serial.println(Pin);
	for (int i = 0; i < NumRegisters; i++) {
		WriteRegister(Pin, i, RegisterValues[i]);
	}
}

void VerifyData(int Pin) {
    int ErrorCount = 0;
	for (int i = 0; i < NumRegisters; i++) {
		byte RegVal = ReadSingleRegister(Pin, i);
		if(RegVal != RegisterValues[i]){
        Serial.print(RegisterNames[i]);
		Serial.print("\t has 0x");
		Serial.print(RegVal, HEX);
		Serial.print(" and should have 0x");
		Serial.println(RegisterValues[i],HEX);
		ErrorCount++;
		}
	}
	if (ErrorCount == 0){
		Serial.println("No discrepancies found");
	}
	
}
void loop() {

    Serial.println("=================================================");
	for(int i = 0; i<NumPins; i++){
		Serial.print("\nChannel ");
		Serial.print(i);
		Serial.print("   Pin ");
		Serial.println(Pins[i]);

		double CJ_temp = ReadColdJunction(Pins[i]);
		double TC_temp = ReadTemperature(Pins[i]);

		Serial.print("\tCold junction is ");
		Serial.println(CJ_temp);
		Serial.print("\tThermocouple temp is ");
		Serial.println(TC_temp);
	}
	delay(1000);
}
	byte ReadSingleRegister(int Pin, byte Register) {
		digitalWrite(Pin, LOW);
		NOP;
		SPI.transfer(Register & 0x7F); //set bit 7 to 0 to ensure a read command
		NOP;
		byte data = SPI.transfer(0);
		digitalWrite(Pin, HIGH);
		return data;
	}

	unsigned long ReadMultipleRegisters(int Pin, byte StartRegister, int count) {
		//reads up to 4 sequential registers
		digitalWrite(Pin, LOW);
		unsigned  long data = 0;
		SPI.transfer(StartRegister & 0x7F); //force bit 7 to 0 to ensure a read command
		NOP;

		for (int i = 0; i<count; i++){
			data = (data<<8) | SPI.transfer(0);  //bitshift left 8 bits, then add the next register
		}
		digitalWrite(Pin, HIGH);
		return data;
	}

	void WriteRegister(int Pin, byte Register, byte Value) {
		byte Address = Register | 0x80; //Set bit 7 high for a write command
		digitalWrite(Pin, LOW);
		NOP;
		SPI.transfer(Address);
		NOP;
		SPI.transfer(Value);
		digitalWrite(Pin, HIGH);
	}

	double ReadColdJunction(int Pin){
		
		double temperature;

		long data, temperatureOffset;

		data = ReadMultipleRegisters(Pin, 0x08, 4);

		// Register 9 is the temperature offset
		temperatureOffset = (data & 0x00FF0000) >> 16;

		// Is this a negative number?
		if (temperatureOffset & 0x80)
		temperatureOffset |= 0xFFFFFF00;

		// Strip registers 8 and 9, taking care of negative numbers
		if (data & 0x8000)
		data |= 0xFFFF0000;
		else
		data &= 0x0000FFFF;

		// Remove the 2 LSB's - they aren't used
		data = data >> 2;

		// Add the temperature offset to the temperature
		temperature = data + temperatureOffset;

		// Convert to Celsius
		temperature *= 0.015625;
		

		// Return the temperature
		return (temperature);


	}
	double ReadTemperature(int Pin){

		double temperature;
		long data;

		data = ReadMultipleRegisters(Pin,0x0C, 4);

		// Strip the unused bits and the Fault Status Register
		data = data >> 13;

		// Negative temperatures have been automagically handled by the shift above :-)

		// Convert to Celsius
		temperature = (double) data * 0.0078125;
		

		// Return the temperature
		return (temperature);

	}

I just spotted this thread after spending some time trying to port the bit-banged version to SPI (and it not quite working)

I got some strange results where the temperature reading was 1/2 what it was supposed to be, which I presume is because the SPI was dropping a bit

Also it seemed inconsistent on different boards. (I'm using a couple of different ARM boards and it almost worked on one of them, but hardly every got a valid reading on another board)

But looking at your code it looks like the issue is that a time delay is required between the command to read registers and also between CS and the SPI transfers

I also had a go at using SPI.transfer(*bufferPointer, length) but that didnt work at all, even when the buffer was initially filled with zeros

So I'll have another try with your code, but add a delayMicroseconds(1); instead of NOP; as I don't have NOP on the board I'm using.

Edit. Well I suppose I do have nop, but it may be too short as I'm using an ARM board, so I'll happily wait 1 microsecond each time :wink:

Edit.

Just tested your code on a 72Mhz Arm board (STM32 Maple mini) and it worked straight away

I have however changed the NOP to delayMicroseconds(1); because I some other much faster boards - Specifically the STM Nucleo STM32F446, which is a 180Mhz board, and also a 120MHz GD32 (which runs code from shadow RAM and is also very fast) and I think a single nop may not be enough time, as the spec on the MAX31856 says 100nS between CS and transfer and a nop at 120Mhz is only 8.3nS

In fact, I'm surprised a nop worked on the 72Mhz device, as even allowing for its 2 wait states to access its program (flash) that would only equate for about a 40nS delay.

Oh,

One question, have you noticed that the time for each reading seems to vary.

I tried triggering the reading of data from the DRDY signal and I get 3 conversions at around 118mS, but then I get one at around 130mS, then it goes back to taking 118mS for the next few readings.

This isn't a serious problem, if you are only taking readings at least every 130ms (or preferably per the spec which seems to be 180mS worst case)

But it may effect some systems which expect the conversion time to be consistent each time.

I just wanted to thank the people that started this thread. I was having a hard time getting my Mega2560 to talk to a Dual MAX31856 board I got on Ebay. I did find one software error in the program of a register setup which I have fixed. I have also added some comments to the code so it helps explain the MAX31856 setup. I also changed the NOP's to delayMicroseconds(1) so it would be more universal for various boards.
For those people trying to get your MAX31856 board to work, remember it also helps to have the correct SPI pins hooked up for your board type.
Using this guide helps :

I had my Dual MAX31856 wired for a UNO pin out instead of a Mega2560 pin out.
It works much better wired correctly.
I have attached the modified code below.

Test_DualMax31856.cpp.ino (6.46 KB)