In this article, we’ll explore how to integrate STM32 with the MAX31856 Thermocouple for precise temperature sensing in embedded applications.
Things used in this project
Software apps and online services:
1-Â STMicroelectronics STM32CubeMX
2-Â STMicroelectronics STM32CubeIDE
3-Â Proteus 8
Integrating STM32 with MAX31856 Thermocouple for Advanced Temperature Monitoring
In this project, we dive into the integration of the STM32 microcontroller with the MAX31856 thermocouple-to-digital converter, aiming to revolutionize temperature monitoring in embedded systems.
Understanding MAX31856 Thermocouple Applications with STM32:
The MAX31856, equipped with a 19-bit ADC and robust thermocouple support, ensures precise temperature measurements while its fault detection features bolster system reliability, rendering it well-suited for embedded systems. Seamlessly integrating the STM32 microcontroller with the MAX31856 enhances temperature monitoring efficiency, primarily through SPI communication, optimizing data exchange between the components and paving the way for highly efficient embedded system applications.
Key Features and Capabilities of STM32 with MAX31856 Thermocouple Integration:
The MAX31856 thermocouple-to-digital converter offers robust temperature sensing capabilities through advanced features like cold-junction and thermocouple out-of-range detection. This ensures accurate readings even in challenging conditions. With a serial interface (SPI), it seamlessly communicates with microcontrollers, facilitating efficient data transfer. Address and data bytes are managed effectively, allowing for precise control over data exchange. Additionally, the DRDY output signals when new conversion results are ready, streamlining the data retrieval process for enhanced system responsiveness and reliability.
To kickstart this project, our first task is configuring the STM32 to operate in SPI Full-Duplex Master mode. This sets the stage for seamless communication with the MAX31865, laying a solid foundation for data exchange. Next, we’ll fine-tune the PA4 and PA3 pins to OUTPUT mode, granting us precise control over peripheral devices, crucial for system performance. Wrapping up, we’ll integrate UART communication, facilitating the accurate transmission of temperature readings and real-time system updates.
STM32CubeMX Configuration:
- Open CubeMX & Create New Project Choose The Target MCU STM32F103C6 & Double-Click Its Name
- Go To The Clock Configuration & Set The System Clock To 8MHz
- Configure The GPIO Pin PA4 as Output Pin (CSPin)
- In the Categories tab, select the SPI1 & Full-Duplex Master Enable USART1 Module (Asynchronous Mode)
- Set the USART1 communication parameters (baud rate = 9600, parity=NON, stop bits = 1, and word length =8bits)
- Generate The Initialization Code & Open The Project In CubeIDE
STM32CubeIDE Configuration :
- Write The Application Layer Code
- Max31856.h & Max31856.c
- main.c
#ifndef MAX31856_H #define MAX31856_H #include "main.h" #include <stdio.h> #include <stdbool.h> #include <math.h> #define MAX31856_CR0_REG 0x00 ///< Config 0 register #define MAX31856_CR0_AUTOCONVERT 0x80 ///< Config 0 Auto convert flag #define MAX31856_CR0_1SHOT 0x40 ///< Config 0 one shot convert flag #define MAX31856_CR0_OCFAULT1 0x20 ///< Config 0 open circuit fault 1 flag #define MAX31856_CR0_OCFAULT0 0x10 ///< Config 0 open circuit fault 0 flag #define MAX31856_CR0_CJ 0x08 ///< Config 0 cold junction disable flag #define MAX31856_CR0_FAULT 0x04 ///< Config 0 fault mode flag #define MAX31856_CR0_FAULTCLR 0x02 ///< Config 0 fault clear flag #define MAX31856_CR1_REG 0x01 ///< Config 1 register #define MAX31856_MASK_REG 0x02 ///< Fault Mask register #define MAX31856_CJHF_REG 0x03 ///< Cold junction High temp fault register #define MAX31856_CJLF_REG 0x04 ///< Cold junction Low temp fault register #define MAX31856_LTHFTH_REG \ 0x05 ///< Linearized Temperature High Fault Threshold Register, MSB #define MAX31856_LTHFTL_REG \ 0x06 ///< Linearized Temperature High Fault Threshold Register, LSB #define MAX31856_LTLFTH_REG \ 0x07 ///< Linearized Temperature Low Fault Threshold Register, MSB #define MAX31856_LTLFTL_REG \ 0x08 ///< Linearized Temperature Low Fault Threshold Register, LSB #define MAX31856_CJTO_REG 0x09 ///< Cold-Junction Temperature Offset Register #define MAX31856_CJTH_REG 0x0A ///< Cold-Junction Temperature Register, MSB #define MAX31856_CJTL_REG 0x0B ///< Cold-Junction Temperature Register, LSB #define MAX31856_LTCBH_REG 0x0C ///< Linearized TC Temperature, Byte 2 #define MAX31856_LTCBM_REG 0x0D ///< Linearized TC Temperature, Byte 1 #define MAX31856_LTCBL_REG 0x0E ///< Linearized TC Temperature, Byte 0 #define MAX31856_SR_REG 0x0F ///< Fault Status Register #define MAX31856_FAULT_CJRANGE \ 0x80 ///< Fault status Cold Junction Out-of-Range flag #define MAX31856_FAULT_TCRANGE \ 0x40 ///< Fault status Thermocouple Out-of-Range flag #define MAX31856_FAULT_CJHIGH \ 0x20 ///< Fault status Cold-Junction High Fault flag #define MAX31856_FAULT_CJLOW 0x10 ///< Fault status Cold-Junction Low Fault flag #define MAX31856_FAULT_TCHIGH \ 0x08 ///< Fault status Thermocouple Temperature High Fault flag #define MAX31856_FAULT_TCLOW \ 0x04 ///< Fault status Thermocouple Temperature Low Fault flag #define MAX31856_FAULT_OVUV \ 0x02 ///< Fault status Overvoltage or Undervoltage Input Fault flag #define MAX31856_FAULT_OPEN \ 0x01 ///< Fault status Thermocouple Open-Circuit Fault flag #define SPI_DELAY 0xff extern bool initialized; /** Noise filtering options enum. Use with setNoiseFilter() */ typedef enum { MAX31856_NOISE_FILTER_50HZ, MAX31856_NOISE_FILTER_60HZ } max31856_noise_filter_t; /** Multiple types of thermocouples supported */ typedef enum { MAX31856_TCTYPE_B = 0b0000, MAX31856_TCTYPE_E = 0b0001, MAX31856_TCTYPE_J = 0b0010, MAX31856_TCTYPE_K = 0b0011, MAX31856_TCTYPE_N = 0b0100, MAX31856_TCTYPE_R = 0b0101, MAX31856_TCTYPE_S = 0b0110, MAX31856_TCTYPE_T = 0b0111, MAX31856_VMODE_G8 = 0b1000, MAX31856_VMODE_G32 = 0b1100, } max31856_thermocoupletype_t; /** Temperature conversion mode */ typedef enum { MAX31856_ONESHOT, MAX31856_ONESHOT_NOWAIT, MAX31856_CONTINUOUS } max31856_conversion_mode_t; bool begin(void); bool conversionComplete(void); uint8_t readFault(void); uint8_t readRegister8(uint8_t addr); uint16_t readRegister16(uint8_t addr); uint32_t readRegister24(uint8_t addr); max31856_conversion_mode_t getConversionMode(void); max31856_thermocoupletype_t getThermocoupleType(void); float readCJTemperature(void); float readThermocoupleTemperature(void); void writeRegister8(uint8_t addr, uint8_t reg); void setConversionMode(max31856_conversion_mode_t mode); void setThermocoupleType(max31856_thermocoupletype_t type); void triggerOneShot(void); void setTempFaultThreshholds(float flow, float fhigh); void setColdJunctionFaultThreshholds(int8_t low, int8_t high); void setNoiseFilter(max31856_noise_filter_t noiseFilter); void writeRegister8(uint8_t addr, uint8_t reg); void readRegisterN(uint8_t addr, uint8_t buffer[], uint8_t n); #endif // MAX31856_H
#include "Max31856.h" extern SPI_HandleTypeDef hspi1; max31856_conversion_mode_t conversionMode; bool initialized = false; bool begin(void) { if (HAL_SPI_Init(&hspi1) == HAL_OK) {initialized = true;} else {initialized = false;} // assert on any fault writeRegister8(MAX31856_MASK_REG, 0x0); // enable open circuit fault detection writeRegister8(MAX31856_CR0_REG, MAX31856_CR0_OCFAULT0); // set cold junction temperature offset to zero writeRegister8(MAX31856_CJTO_REG, 0x0); // set Type K by default setThermocoupleType(MAX31856_TCTYPE_K); // set One-Shot conversion mode setConversionMode(MAX31856_ONESHOT); return initialized; } max31856_conversion_mode_t getConversionMode(void) { return conversionMode; } void setConversionMode(max31856_conversion_mode_t mode) { conversionMode = mode; uint8_t t = readRegister8(MAX31856_CR0_REG); // get current register value if (conversionMode == MAX31856_CONTINUOUS) { t |= MAX31856_CR0_AUTOCONVERT; // turn on automatic t &= ~MAX31856_CR0_1SHOT; // turn off one-shot } else { t &= ~MAX31856_CR0_AUTOCONVERT; // turn off automatic t |= MAX31856_CR0_1SHOT; // turn on one-shot } writeRegister8(MAX31856_CR0_REG, t); // write value back to register } void setThermocoupleType(max31856_thermocoupletype_t type) { uint8_t t = readRegister8(MAX31856_CR1_REG); t &= 0xF0; // mask off bottom 4 bits t |= (uint8_t)type & 0x0F; writeRegister8(MAX31856_CR1_REG, t); } max31856_thermocoupletype_t getThermocoupleType(void) { uint8_t t = readRegister8(MAX31856_CR1_REG); t &= 0x0F; return (max31856_thermocoupletype_t)(t); } uint8_t readFault(void) { return readRegister8(MAX31856_SR_REG); } void setColdJunctionFaultThreshholds(int8_t low,int8_t high) { writeRegister8(MAX31856_CJLF_REG, low); writeRegister8(MAX31856_CJHF_REG, high); } uint8_t readRegister8(uint8_t addr) { uint8_t ret = 0; readRegisterN(addr, &ret, 1); return ret; } void setNoiseFilter(max31856_noise_filter_t noiseFilter) { uint8_t t = readRegister8(MAX31856_CR0_REG); if (noiseFilter == MAX31856_NOISE_FILTER_50HZ) { t |= 0x01; } else { t &= 0xfe; } writeRegister8(MAX31856_CR0_REG, t); } void setTempFaultThreshholds(float flow, float fhigh) { int16_t low, high; flow *= 16; low = flow; fhigh *= 16; high = fhigh; writeRegister8(MAX31856_LTHFTH_REG, high >> 8); writeRegister8(MAX31856_LTHFTL_REG, high); writeRegister8(MAX31856_LTLFTH_REG, low >> 8); writeRegister8(MAX31856_LTLFTL_REG, low); } void triggerOneShot(void) { if (conversionMode == MAX31856_CONTINUOUS) return; uint8_t t = readRegister8(MAX31856_CR0_REG); // get current register value t &= ~MAX31856_CR0_AUTOCONVERT; // turn off autoconvert t |= MAX31856_CR0_1SHOT; // turn on one-shot writeRegister8(MAX31856_CR0_REG, t); // write value back to register // conversion starts when CS goes high } bool conversionComplete(void) { if (conversionMode == MAX31856_CONTINUOUS) return true; return !(readRegister8(MAX31856_CR0_REG) & MAX31856_CR0_1SHOT); } float readCJTemperature(void) { return readRegister16(MAX31856_CJTH_REG) / 256.0; } float readThermocoupleTemperature(void) { // for one-shot, make it happen if (conversionMode == MAX31856_ONESHOT) { triggerOneShot(); uint32_t start = HAL_GetTick(); while (!conversionComplete()) { if (HAL_GetTick() - start > 250) //250 return NAN; HAL_Delay(10); } } // read the thermocouple temperature registers (3 bytes) int32_t temp24 = readRegister24(MAX31856_LTCBH_REG); // and compute temperature if (temp24 & 0x800000) { temp24 |= 0xFF000000; // fix sign } temp24 >>= 5; // bottom 5 bits are unused return temp24 * 0.0078125; } uint16_t readRegister16(uint8_t addr) { uint8_t buffer[2] = {0, 0}; readRegisterN(addr, buffer, 2); uint16_t ret = (uint16_t)buffer[0]; // Cast to uint16_t before left shift ret <<= 8; ret |= buffer[1]; return ret; } uint32_t readRegister24(uint8_t addr) { uint8_t buffer[3] = {0, 0, 0}; readRegisterN(addr, buffer, 3); uint32_t ret = (uint32_t) buffer[0]; ret <<= 8; ret |= buffer[1]; ret <<= 8; ret |= buffer[2]; return ret; } void readRegisterN(uint8_t addr, uint8_t buffer[], uint8_t n) { addr &= 0x7F; // MSB=0 for read, make sure top bit is not set HAL_GPIO_WritePin(GPIOA , SPI1_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, &addr, 1, SPI_DELAY); HAL_SPI_Receive(&hspi1, buffer, n, SPI_DELAY); HAL_GPIO_WritePin(GPIOA , SPI1_CS_Pin, GPIO_PIN_SET); } void writeRegister8(uint8_t addr, uint8_t data) { addr |= 0x80; // MSB=1 for write, make sure top bit is set uint8_t buffer[2] = {addr, data}; HAL_GPIO_WritePin(GPIOA, SPI1_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, buffer, 2, SPI_DELAY); // Corrected size to 2 HAL_GPIO_WritePin(GPIOA, SPI1_CS_Pin, GPIO_PIN_SET); }
#include "main.h" #include "Max31856.h" #include<stdio.h> #include<string.h> /* Private variables ---------------------------------------------------------*/ SPI_HandleTypeDef hspi1; UART_HandleTypeDef huart1; /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_SPI1_Init(void); static void MX_USART1_UART_Init(void); int main(void) { /* USER CODE BEGIN 1 */ char MSG[100]; char MSG0[100]; char MSG1[100]="Could not initialize thermocouple\n\r"; char MSG3[100]="$$***************************************$$\n\r"; char MSG2[100]="Thermocouple type: "; char MSG4[100]= "B Type\n\r"; char MSG5[100]= "E Type\n\r"; char MSG6[100]= "J Type\n\r"; char MSG7[100]= "K Type\n\r"; char MSG8[100]= "N Type\n\r"; char MSG9[100]= "R Type\n\r"; char MSG10[100]= "S Type\n\r"; char MSG11[100]= "T Type\n\r"; char MSG12[100]= "Voltage x8 Gain mode\n\r"; char MSG13[100]= "Voltage x8 Gain mode\n\r"; char MSG14[100]= "Unknown\n\r"; char MSG15[100]= "Cold Junction Range Fault\n\r"; char MSG16[100]= "Thermocouple Range Fault\n\r"; char MSG17[100]= "Cold Junction High Fault\n\r"; char MSG18[100]= "Cold Junction Low Fault\n\r"; char MSG19[100]= "Thermocouple High Fault\n\r"; char MSG20[100]= "Thermocouple Low Fault\n\r"; char MSG21[100]= "Over/Under Voltage Fault\n\r"; char MSG22[100]= "hermocouple Open Faultn\r"; HAL_Init(); SystemClock_Config(); MX_GPIO_Init() MX_SPI1_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ if (!begin()) { HAL_UART_Transmit(&huart1,(uint8_t*)MSG1,sizeof(MSG1), 100); while (1) HAL_Delay(10); } setThermocoupleType(MAX31856_TCTYPE_K); HAL_UART_Transmit(&huart1,(uint8_t*)MSG2,sizeof(MSG2), 100); switch (getThermocoupleType() ) { case MAX31856_TCTYPE_B: HAL_UART_Transmit(&huart1,(uint8_t*)MSG4,sizeof(MSG4), 100); break; case MAX31856_TCTYPE_E: HAL_UART_Transmit(&huart1,(uint8_t*)MSG5,sizeof(MSG5), 100); break; case MAX31856_TCTYPE_J: HAL_UART_Transmit(&huart1,(uint8_t*)MSG6,sizeof(MSG6), 100); break; case MAX31856_TCTYPE_K: HAL_UART_Transmit(&huart1,(uint8_t*)MSG7,sizeof(MSG7), 100); break; case MAX31856_TCTYPE_N: HAL_UART_Transmit(&huart1,(uint8_t*)MSG8,sizeof(MSG8), 100); break; case MAX31856_TCTYPE_R: HAL_UART_Transmit(&huart1,(uint8_t*)MSG9,sizeof(MSG9), 100); break; case MAX31856_TCTYPE_S: HAL_UART_Transmit(&huart1,(uint8_t*)MSG10,sizeof(MSG10), 100); break; case MAX31856_TCTYPE_T: HAL_UART_Transmit(&huart1,(uint8_t*)MSG11,sizeof(MSG11), 100); break; case MAX31856_VMODE_G8: HAL_UART_Transmit(&huart1,(uint8_t*)MSG12,sizeof(MSG12), 100); break; case MAX31856_VMODE_G32:HAL_UART_Transmit(&huart1,(uint8_t*)MSG13,sizeof(MSG13), 100); break; default: HAL_UART_Transmit(&huart1,(uint8_t*)MSG14,sizeof(MSG14), 100);break; } HAL_Delay(2000); /* USER CODE END 2 */ while (1) { sprintf(MSG,"1-Cold Junction Temp: %.2f degrees Celsius\n\r",readCJTemperature()); sprintf(MSG0,"2-Thermocouple Temp: %.2f degrees Celsius\n\r", readThermocoupleTemperature()); HAL_UART_Transmit(&huart1,(uint8_t *) MSG, sizeof(MSG), 100); HAL_UART_Transmit(&huart1,(uint8_t *) MSG0, sizeof(MSG0), 100); HAL_UART_Transmit(&huart1,(uint8_t *) MSG3, sizeof(MSG3), 100); uint8_t fault = readFault(); if (fault) { if (fault & MAX31856_FAULT_CJRANGE) HAL_UART_Transmit(&huart1,(uint8_t*)MSG15,sizeof(MSG15), 100); if (fault & MAX31856_FAULT_TCRANGE) HAL_UART_Transmit(&huart1,(uint8_t*)MSG16,sizeof(MSG16), 100); if (fault & MAX31856_FAULT_CJHIGH) HAL_UART_Transmit(&huart1,(uint8_t*)MSG17,sizeof(MSG17), 100); if (fault & MAX31856_FAULT_CJLOW) HAL_UART_Transmit(&huart1,(uint8_t*)MSG18,sizeof(MSG18), 100); if (fault & MAX31856_FAULT_TCHIGH) HAL_UART_Transmit(&huart1,(uint8_t*)MSG19,sizeof(MSG19), 100); if (fault & MAX31856_FAULT_TCLOW) HAL_UART_Transmit(&huart1,(uint8_t*)MSG20,sizeof(MSG20), 100); if (fault & MAX31856_FAULT_OVUV) HAL_UART_Transmit(&huart1,(uint8_t*)MSG21,sizeof(MSG21), 100); if (fault & MAX31856_FAULT_OPEN) HAL_UART_Transmit(&huart1,(uint8_t*)MSG22,sizeof(MSG22), 100); } HAL_UART_Transmit(&huart1,(uint8_t *) MSG, sizeof(MSG), 100); HAL_Delay(1000); } /* USER CODE END 3 */ }
Proteus Configuration :
- Open Proteus & Create New Project and click next
- Click on Pick Device
- Search for STM32F103C6 & TCK & MAX31856
- Click on Virtual Instrumets Mode then choose VIRTUAL TERMINAL & SPI DEBUGGER
- Click on Terminal Mode then choose (DEFAULT & POWER &GROUND)
- finally make the circuit below and start the simulation
That’s all!
If you have any questions or suggestions don’t hesitate to leave a comment below