Reputation: 41
So, I am integrating a MAX31865 temperature sensor in a NUCLEO-F103RB board using SPI communication. It doens't seem to be working well, since I just can't send a single Write Transmission: enter image description here
I am just using this function:
void INIT_SPI_MAX(void)
{
uint8_t spi_reg =0b11000000; //V_BIAS ON, Auto off, 4-wire, returns all fault status bits in register, 50Hz
uint16_t low_fault = (100) & 0x7FFF;;
uint8_t threshold[2] = {0x85, 0x86}; //RTD resistance data address registers
SPI_Write1Byte(0x80);
SPI_Write1Byte(spi_reg);
SPI_Write1Byte(threshold[0]);
SPI_Write1Byte((uint8_t)((low_fault & 0xFF00) >> 8));
SPI_Write1Byte(threshold[1]);
SPI_Write1Byte((uint8_t)((low_fault & 0x00FF) << 1));
}
and also this one
void SPI_Write1Byte(uint8_t data)
{
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
HAL_Delay(1);
}
I just did this in a PIC microcontroller in MPLAB and it worked perfectly. I am using SPI mode 3 and my STM32 Cube IDE settings are "CPOL: HIGH" and "CPHA: 2 Edge", and I also am sure that GPIOs are configurated correctly (CS included, using it as a GPIO_OUTPUT).
I don't seem to find the error in this, can it be system configuration? Or maybe SPI Clock parameters (which are 32 prescaler (2MBits/s, since sensor can handle 5M)? Hope someone can help me, thank you guys!
Upvotes: 4
Views: 534
Reputation: 51873
I only recently moved to STM32 platform (using STM32H723ZG MCU) and also needed to interface SPI MAX31856 thermocouple ADC/driver and facing troubles due to HW errors of the SPI module...
SPI NSS signal is bugged
due to its glitches the communication between MCU and MAX is not working at all so I switched to GPIO /CS signal instead of HW SPI one. So just set some GPIO pin as output and call it MAX31856_CS
my SPI transfers worked only once after powerup and then not at all
this was resolved by the answer provided by pmacfarlane and in the following code its encapsulated in functions MAX31856_read
and MAX31856_write
So I ended up with this code:
//--------------------------------------------------------------------------------------------------------
//--- Spekres STM32 MAX31856 SPI Thermocouple 19bit ADC driver ver: 1.000 -------------------------------
//--------------------------------------------------------------------------------------------------------
#ifndef _MAX31856_SPI_h
#define _MAX31856_SPI_h
//--------------------------------------------------------------------------------------------------------
SPI_HandleTypeDef *MAX31856_SPI=NULL; // used SPI max 5MHz
#define MAX31856_SPI_timeout_ms 10
#define MAX31856_SPI_CS_time_us 100
// RW reg addr
#define MAX31856_reg_CR0 0
#define MAX31856_reg_CR1 1
#define MAX31856_reg_MASK 2
#define MAX31856_reg_CJHF 3
#define MAX31856_reg_CJLF 4
#define MAX31856_reg_LTHFTH 5
#define MAX31856_reg_LTHFTL 6
#define MAX31856_reg_LTLFTH 7
#define MAX31856_reg_LTLFTL 8
#define MAX31856_reg_CJTO 9
#define MAX31856_reg_CJTH 10
#define MAX31856_reg_CJTL 11
// R reg addr
#define MAX31856_reg_LTCBH 12
#define MAX31856_reg_LTCBM 13
#define MAX31856_reg_LTCBL 14
#define MAX31856_reg_SR 15
#define MAX31856_regs_W 12
#define MAX31856_regs 16
// Register masks and values
#define MAX31856_CR0_AUTOMATIC_CONVERSION 0x80
#define MAX31856_CR0_ONE_SHOT 0x40
#define MAX31856_CR0_OPEN_CIRCUIT_FAULT_TYPE_K 0x10
#define MAX31856_CR0_COLD_JUNCTION_DISABLED 0x08
#define MAX31856_CR0_FAULT_INTERRUPT_MODE 0x04
#define MAX31856_CR0_FAULT_CLEAR 0x02
#define MAX31856_CR0_NOISE_FILTER_50HZ 0x01
#define MAX31856_CR1_AVERAGE_1_SAMPLE 0x00
#define MAX31856_CR1_AVERAGE_2_SAMPLES 0x10
#define MAX31856_CR1_AVERAGE_4_SAMPLES 0x20
#define MAX31856_CR1_AVERAGE_8_SAMPLES 0x30
#define MAX31856_CR1_AVERAGE_16_SAMPLES 0x40
#define MAX31856_CR1_THERMOCOUPLE_TYPE_B 0x00
#define MAX31856_CR1_THERMOCOUPLE_TYPE_E 0x01
#define MAX31856_CR1_THERMOCOUPLE_TYPE_J 0x02
#define MAX31856_CR1_THERMOCOUPLE_TYPE_K 0x03
#define MAX31856_CR1_THERMOCOUPLE_TYPE_N 0x04
#define MAX31856_CR1_THERMOCOUPLE_TYPE_R 0x05
#define MAX31856_CR1_THERMOCOUPLE_TYPE_S 0x06
#define MAX31856_CR1_THERMOCOUPLE_TYPE_T 0x07
#define MAX31856_CR1_VOLTAGE_MODE_GAIN_8 0x08
#define MAX31856_CR1_VOLTAGE_MODE_GAIN_32 0x0C
#define MAX31856_MASK_COLD_JUNCTION_HIGH_FAULT 0x20
#define MAX31856_MASK_COLD_JUNCTION_LOW_FAULT 0x10
#define MAX31856_MASK_THERMOCOUPLE_HIGH_FAULT 0x08
#define MAX31856_MASK_THERMOCOUPLE_LOW_FAULT 0x04
#define MAX31856_MASK_VOLTAGE_UNDER_OVER_FAULT 0x02
#define MAX31856_MASK_THERMOCOUPLE_OPEN_FAULT 0x01
#define MAX31856_SR_FAULT_COLD_JUNCTION_OUT_OF_RANGE 0x80
#define MAX31856_SR_FAULT_THERMOCOUPLE_OUT_OF_RANGE 0x40
#define MAX31856_SR_FAULT_COLD_JUNCTION_HIGH 0x20
#define MAX31856_SR_FAULT_COLD_JUNCTION_LOW 0x10
#define MAX31856_SR_FAULT_THERMOCOUPLE_HIGH 0x08
#define MAX31856_SR_FAULT_THERMOCOUPLE_LOW 0x04
#define MAX31856_SR_FAULT_UNDER_OVER_VOLTAGE 0x02
#define MAX31856_SR_FAULT_OPEN 0x01
//--------------------------------------------------------------------------------------------------------
void MAX31856_read(U8 reg,U8 *dat,U8 siz) // read siz-1 registers starting from reg
{ // dat[0] is reserved for MAX31856 address
HAL_GPIO_WritePin(MAX31856_CS_GPIO_Port,MAX31856_CS_Pin,1); // set /CS=High (off)
HAL_Delay(1); //wait_us(MAX31856_SPI_CS_time_us);
HAL_SPI_Transmit(MAX31856_SPI,dat,1,MAX31856_SPI_timeout_ms); // send dummy byte to sync SPI clk
HAL_GPIO_WritePin(MAX31856_CS_GPIO_Port,MAX31856_CS_Pin,0); // set /CS=Low (on)
HAL_Delay(1); //wait_us(MAX31856_SPI_CS_time_us);
dat[0]=reg; HAL_SPI_TransmitReceive(MAX31856_SPI,dat,dat,siz,MAX31856_SPI_timeout_ms);
HAL_GPIO_WritePin(MAX31856_CS_GPIO_Port,MAX31856_CS_Pin,1); // set /CS=High (off)
}
//--------------------------------------------------------------------------------------------------------
void MAX31856_write(U8 reg,U8 *dat,U8 siz) // write siz-1 registers starting from reg
{ // dat[0] is reserved for MAX31856 address
HAL_GPIO_WritePin(MAX31856_CS_GPIO_Port,MAX31856_CS_Pin,1); // set /CS=High (off)
HAL_Delay(1); //wait_us(MAX31856_SPI_CS_time_us);
HAL_SPI_Transmit(MAX31856_SPI,dat,1,MAX31856_SPI_timeout_ms); // send dummy byte to sync SPI clk
HAL_GPIO_WritePin(MAX31856_CS_GPIO_Port,MAX31856_CS_Pin,0); // set /CS=Low (on)
HAL_Delay(1); //wait_us(MAX31856_SPI_CS_time_us);
dat[0]=reg|0x80; HAL_SPI_Transmit(MAX31856_SPI,dat,siz,MAX31856_SPI_timeout_ms);
HAL_GPIO_WritePin(MAX31856_CS_GPIO_Port,MAX31856_CS_Pin,1); // set /CS=High (off)
}
//--------------------------------------------------------------------------------------------------------
void MAX31856_init()
{
// U8 RX_dat[MAX31856_regs +1]={ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
U8 TX_dat[MAX31856_regs_W+1]={ 0x80, 0x00,0x03,0xFF,0x7F,0xC0,0x7F,0xFF,0x80,0x00,0x00,0x00,0x00}; // default values
// TX_dat[MAX31856_reg_CR0+1]|=MAX31856_CR0_AUTOMATIC_CONVERSION;
TX_dat[MAX31856_reg_CR0+1]=MAX31856_CR0_AUTOMATIC_CONVERSION|MAX31856_CR0_NOISE_FILTER_50HZ|MAX31856_CR0_OPEN_CIRCUIT_FAULT_TYPE_K;
TX_dat[MAX31856_reg_CR1+1]=MAX31856_CR1_THERMOCOUPLE_TYPE_K|MAX31856_CR1_AVERAGE_1_SAMPLE;
MAX31856_write(0,TX_dat,MAX31856_regs_W+1);
// MAX31856_read (0,RX_dat,MAX31856_regs +1);
}
//--------------------------------------------------------------------------------------------------------
S32 MAX31856_getTC_fixed_20_12() // Thermo-Couple temperature in [C] 20.12 fixed int
{
S32 t;
U8 dat[5]={0,0,0,0,0};
MAX31856_read(MAX31856_reg_LTCBH,dat,5);
// extract SR
t=dat[4];
if ((t&MAX31856_SR_FAULT_OPEN)!=0) return 0.0;
if ((t&MAX31856_SR_FAULT_UNDER_OVER_VOLTAGE)!=0) return 0.0;
// extract temperature
t =dat[1];
t<<=8; t|=dat[2];
t<<=8; t|=dat[3]; t&=0x00FFFFFF;
if ((t&0x00800000)!=0) t|=0xFF000000;
return t;
}
//--------------------------------------------------------------------------------------------------------
S32 MAX31856_getCJ_fixed_20_12() // IC reference Cold-Junction temperature in [C] 20.12 fixed int
{
S32 t,to;
U8 dat[4]={0,0,0,0};
MAX31856_read(MAX31856_reg_CJTO,dat,4);
// extract temperature offset
to=dat[1]; to&=0xFF;
if ((to&0x80)!=0) to|=0xFFFFFF00;
// extract temperature
t =dat[2];
t<<=8; t|=dat[3]; t&=0x0000FFFF;
if ((t&0x8000)!=0) t|=0xFFFF0000;
// Add the temperature offset to the temperature
t+=to; t<<=4;
return t;
}
//--------------------------------------------------------------------------------------------------------
float MAX31856_getTC(){ return ((float)(MAX31856_getTC_fixed_20_12()))/4096.0; }
float MAX31856_getCJ(){ return ((float)(MAX31856_getCJ_fixed_20_12()))/4096.0; }
//--------------------------------------------------------------------------------------------------------
#endif
//--------------------------------------------------------------------------------------------------------
Where MAX31856_CS
is my GPIO output used for the SPI NSS (or MAX /CS). I am using my own timinig routines where wait_us(n)
waits n [us] based on 32 bit timer2 running on 1MHz clock. I replaced them with HAL_Delay(1);
so its compilable without it but note it wastes ~2ms per transfer ...
Usage is simple just on init do this:
// SPI thermocouple
HAL_SPI_Init(&hspi1);
MAX31856_SPI=&hspi1;
MAX31856_init();
just change the spi index to one you use and then when you need the temps just call MAX31856_getTC()
and MAX31856_getCJ()
...
In case of some error detected you can call the init again to reset the registers...
Also do not forget to change the init register values to ones you need (I used K type thermocouple)
Also You will need some datatypes definitions (I am used to from AVR32):
#define false 0
#define true 1
typedef char bool;
typedef char S8;
typedef short int S16;
typedef int S32;
typedef unsigned char U8 ;
typedef unsigned short int U16;
typedef unsigned int U32;
as CUBE IDE (at least mine) environment in most cases/places does not knows standard C/C++ datatypes (and defining them is not possible due to conflicts with used compiler) I stick to AVR32 ones so I can also reuse my old AVR32 libs ...
Upvotes: 0
Reputation: 4317
It's a known issue (at least to me) that the STM32 SPI peripheral starts out with its clock signal possibly in the wrong state. The solution is to do a dummy transaction on the bus (without asserting chip-select). After that, the clock line will be correct. So you could just do this (before doing anything else):
SPI_Write1Byte(0x00);
I think you have more problems. You're asserting and de-asserting chip-select around every byte that you send. I'm pretty sure that's not how it is meant to be used. From the MAX31865 datasheet on page 17:
For a single-byte transfer, 1 byte is read or written and then CS is driven high (see Figure 6 and Figure 7). For a multiple-byte transfer, multiple bytes can be read or written after the address has been written (see Figure 8). The address continues to increment through all memory locations as long as CS remains low.
So you should probably remove the control of chip-select from your SPI_Write1Byte()
function:
void SPI_Write1Byte(uint8_t data)
{
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
}
Then, to write the Configuration Register (address 0x00) you'd want something like:
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
SPI_Write1Byte(0x80);
SPI_Write1Byte(spi_reg);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
To write the two Fault Threshold registers at register 0x05 and 0x06 you'd probably need:
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
SPI_Write1Byte(threshold[0]);
SPI_Write1Byte((uint8_t)((low_fault & 0xFF00) >> 8));
SPI_Write1Byte((uint8_t)((low_fault & 0x00FF) << 1));
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
taking advantage of the fact that the address naturally increments. (i.e. you don't need to write the address of both registers, just the first one.)
You could make all this easier by making a function like so:
void write_consecutive_registers(uint8_t start_reg, uint8_t num_reg, uint8_t *buff)
{
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
SPI_Write1Byte(start_reg);
for (uint8_t i = 0u; i < num_reg; ++i)
{
SPI_Write1Byte(buff[i]);
}
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
}
Upvotes: 3