Manuel Orsini
Manuel Orsini

Reputation: 41

Can't solve this synchronization SPI problem

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

Answers (2)

Spektre
Spektre

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...

  1. 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

  2. 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

pmacfarlane
pmacfarlane

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.

Figure 7

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

Related Questions