一、MAX31856概述
MAX31856是Maxim Integrated推出的高精度热电偶数字转换器,支持K、J、N、T、E、R、S、B型热电偶,具有以下特点:
-
24位Δ-Σ ADC,分辨率0.0078125°C
-
内置冷端补偿(CJC),精度±0.5°C
-
支持50/60Hz工频滤波
-
故障检测(开路、短路、超温)
-
SPI接口(模式1:CPOL=0, CPHA=1)
二、硬件连接(STM32F103C8T6)
| MAX31856引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| SCK | PA5 (SPI1_SCK) | SPI时钟 |
| CS | PA4 (GPIO) | 片选(低有效) |
| SDI | PA7 (SPI1_MOSI) | SPI数据输入 |
| SDO | PA6 (SPI1_MISO) | SPI数据输出 |
| DRDY | PB0 (EXTI0) | 数据就绪中断 |
| VCC | 3.3V | 电源 |
| GND | GND | 地 |
三、核心代码实现
1. MAX31856驱动(max31856.c)
c
#include "max31856.h"
#include "spi.h"
#include "gpio.h"
#include "stm32f1xx_hal.h"
// 寄存器定义
#define REG_CR0 0x00
#define REG_CR1 0x01
#define REG_MASK 0x02
#define REG_CJHF 0x03
#define REG_CJLF 0x04
#define REG_LTHFTH 0x05
#define REG_LTHFTL 0x06
#define REG_LTLFTH 0x07
#define REG_LTLFTL 0x08
#define REG_CJTO 0x09
#define REG_CJTH 0x0A
#define REG_CJTM 0x0B
#define REG_CJTL 0x0C
#define REG_LTCBH 0x0D
#define REG_LTCBM 0x0E
#define REG_LTCBL 0x0F
#define REG_FAULT 0x0F // 与LTCBL共享地址,需特殊读取
#define REG_SR 0x0F // 状态寄存器
// 配置寄存器位定义
#define CR0_50HZ 0x01
#define CR0_60HZ 0x00
#define CR0_CMODE_AUTO 0x00
#define CR0_CMODE_OFF 0x02
#define CR0_1SHOT 0x04
#define CR0_ONESHOT 0x00
#define CR0_OPEN_MASK 0x08
#define CR0_CJ_MASK 0x10
#define CR0_FAULT_MASK 0x20
#define CR0_FAULT_CLR 0x40
#define CR0_24BIT 0x80
// 热电偶类型选择
#define TC_TYPE_K 0x00
#define TC_TYPE_J 0x01
#define TC_TYPE_N 0x02
#define TC_TYPE_T 0x03
#define TC_TYPE_E 0x04
#define TC_TYPE_R 0x05
#define TC_TYPE_S 0x06
#define TC_TYPE_B 0x07
// 全局变量
extern SPI_HandleTypeDef hspi1;
static uint8_t spi_tx_buf[4];
static uint8_t spi_rx_buf[4];
// SPI传输函数
static HAL_StatusTypeDef MAX31856_SPI_Transfer(uint8_t *tx_data, uint8_t *rx_data, uint8_t len) {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 拉低CS
HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, len, 100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 拉高CS
return status;
}
// 写寄存器
void MAX31856_WriteReg(uint8_t reg, uint8_t val) {
spi_tx_buf[0] = reg & 0x7F; // 写操作,最高位0
spi_tx_buf[1] = val;
MAX31856_SPI_Transfer(spi_tx_buf, spi_rx_buf, 2);
}
// 读寄存器
uint8_t MAX31856_ReadReg(uint8_t reg) {
spi_tx_buf[0] = 0x80 | reg; // 读操作,最高位1
spi_tx_buf[1] = 0x00;
MAX31856_SPI_Transfer(spi_tx_buf, spi_rx_buf, 2);
return spi_rx_buf[1];
}
// 读多个寄存器
void MAX31856_ReadRegs(uint8_t reg, uint8_t *data, uint8_t len) {
spi_tx_buf[0] = 0x80 | reg;
memset(spi_tx_buf + 1, 0x00, len);
MAX31856_SPI_Transfer(spi_tx_buf, spi_rx_buf, len + 1);
memcpy(data, spi_rx_buf + 1, len);
}
// 初始化MAX31856
void MAX31856_Init(void) {
// 配置SPI(已在CubeMX中初始化)
// 模式1: CPOL=0, CPHA=1, MSB First, 8位数据
// 复位芯片
MAX31856_WriteReg(REG_CR0, 0x00);
HAL_Delay(100);
// 配置CR0: 50Hz滤波, 自动冷端补偿, 24位模式
uint8_t cr0 = CR0_50HZ | CR0_CMODE_AUTO | CR0_24BIT;
MAX31856_WriteReg(REG_CR0, cr0);
// 配置CR1: 选择热电偶类型(K型)
uint8_t cr1 = (TC_TYPE_K << 4) | 0x01; // 使能开路检测
MAX31856_WriteReg(REG_CR1, cr1);
// 设置冷端补偿阈值
MAX31856_WriteReg(REG_CJHF, 0x7F); // 冷端高温故障阈值+127°C
MAX31856_WriteReg(REG_CJLF, 0x80); // 冷端低温故障阈值-128°C
// 设置温度阈值
MAX31856_WriteReg(REG_LTHFTH, 0x7F); // 高温故障阈值
MAX31856_WriteReg(REG_LTHFTL, 0xFF);
MAX31856_WriteReg(REG_LTLFTH, 0x80); // 低温故障阈值
MAX31856_WriteReg(REG_LTLFTL, 0x00);
// 使能故障检测
MAX31856_WriteReg(REG_MASK, 0x00); // 不屏蔽任何故障
}
// 读取冷端温度(°C)
float MAX31856_ReadColdJunctionTemp(void) {
uint8_t cj_data[3];
MAX31856_ReadRegs(REG_CJTH, cj_data, 3);
// 组合24位数据(大端序)
int32_t cj_raw = (cj_data[0] << 16) | (cj_data[1] << 8) | cj_data[2];
// 符号扩展
if (cj_raw & 0x800000) {
cj_raw |= 0xFF000000;
}
// 转换为温度(0.015625°C/LSB)
return cj_raw * 0.015625f;
}
// 读取热端温度(°C)
float MAX31856_ReadHotJunctionTemp(void) {
uint8_t tc_data[3];
MAX31856_ReadRegs(REG_LTCBH, tc_data, 3);
// 组合24位数据(大端序)
int32_t tc_raw = (tc_data[0] << 16) | (tc_data[1] << 8) | tc_data[2];
// 符号扩展
if (tc_raw & 0x800000) {
tc_raw |= 0xFF000000;
}
// 转换为温度(0.0078125°C/LSB)
return tc_raw * 0.0078125f;
}
// 读取故障状态
uint8_t MAX31856_ReadFault(void) {
return MAX31856_ReadReg(REG_FAULT);
}
// 清除故障状态
void MAX31856_ClearFault(void) {
uint8_t cr0 = MAX31856_ReadReg(REG_CR0);
MAX31856_WriteReg(REG_CR0, cr0 | CR0_FAULT_CLR);
}
2. 温度转换与处理(temp_conversion.c)
c
#include "temp_conversion.h"
#include "max31856.h"
#include <math.h>
// 热电偶分度表(K型,简化版)
typedef struct {
float temp; // 温度(°C)
float mv; // 热电势(mV)
} ThermocoupleTable;
// K型热电偶分度表(部分值)
const ThermocoupleTable k_type_table[] = {
{-270, -6.458}, {-260, -6.441}, {-250, -6.404}, {-240, -6.344},
{-230, -6.262}, {-220, -6.158}, {-210, -6.035}, {-200, -5.891},
{-190, -5.727}, {-180, -5.543}, {-170, -5.340}, {-160, -5.118},
{-150, -4.877}, {-140, -4.617}, {-130, -4.338}, {-120, -4.040},
{-110, -3.723}, {-100, -3.388}, { -90, -3.035}, { -80, -2.663},
{ -70, -2.274}, { -60, -1.867}, { -50, -1.443}, { -40, -1.000},
{ -30, -0.540}, { -20, -0.062}, { -10, 0.413}, { 0, 0.000},
{ 10, 0.397}, { 20, 0.798}, { 30, 1.203}, { 40, 1.611},
{ 50, 2.022}, { 60, 2.436}, { 70, 2.853}, { 80, 3.272},
{ 90, 3.694}, { 100, 4.119}, { 110, 4.547}, { 120, 4.977},
{ 130, 5.409}, { 140, 5.843}, { 150, 6.279}, { 160, 6.717},
{ 170, 7.157}, { 180, 7.598}, { 190, 8.041}, { 200, 8.485},
// 实际应用中应包含完整分度表
};
// 线性插值函数
float LinearInterpolation(float x, float x0, float y0, float x1, float y1) {
return y0 + (x - x0) * (y1 - y0) / (x1 - x0);
}
// K型热电偶电压转温度(使用查表法)
float VoltageToTemperature(float voltage) {
// 简化处理:实际应用中应使用完整分度表和多项式拟合
if (voltage <= k_type_table[0].mv) return k_type_table[0].temp;
if (voltage >= k_type_table[sizeof(k_type_table)/sizeof(k_type_table[0])-1].mv)
return k_type_table[sizeof(k_type_table)/sizeof(k_type_table[0])-1].temp;
for (int i = 0; i < sizeof(k_type_table)/sizeof(k_type_table[0])-1; i++) {
if (voltage >= k_type_table[i].mv && voltage <= k_type_table[i+1].mv) {
return LinearInterpolation(voltage,
k_type_table[i].mv, k_type_table[i].temp,
k_type_table[i+1].mv, k_type_table[i+1].temp);
}
}
return 0.0f;
}
// 读取并处理温度
float ReadProcessedTemperature(void) {
// 读取原始温度值
float cold_junc_temp = MAX31856_ReadColdJunctionTemp();
float hot_junc_temp = MAX31856_ReadHotJunctionTemp();
uint8_t fault = MAX31856_ReadFault();
// 故障检测
if (fault != 0x00) {
// 处理故障(开路、短路等)
MAX31856_ClearFault();
return -999.9f; // 错误代码
}
// 计算热电偶电势(简化模型)
float tc_voltage = (hot_junc_temp - cold_junc_temp) * 0.041; // mV/°C
// 转换为温度(实际应用中应使用更精确的转换公式)
float compensated_temp = VoltageToTemperature(tc_voltage) + cold_junc_temp;
return compensated_temp;
}
3. 主程序(main.c)
c
#include "main.h"
#include "spi.h"
#include "gpio.h"
#include "max31856.h"
#include "temp_conversion.h"
#include "usart.h"
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
// 初始化MAX31856
MAX31856_Init();
while (1) {
// 读取处理后的温度
float temperature = ReadProcessedTemperature();
// 串口输出温度值
char msg[50];
if (temperature == -999.9f) {
sprintf(msg, "Temperature Error!\r\n");
} else {
sprintf(msg, "Temperature: %.2f°C\r\n", temperature);
}
HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);
HAL_Delay(1000); // 1秒更新一次
}
}
// SPI初始化(CubeMX生成)
void MX_SPI1_Init(void) {
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; // CPHA=1 (第二边沿采样)
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; // 72MHz/32=2.25MHz
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
HAL_SPI_Init(&hspi1);
}
四、关键配置说明
1. SPI配置要点
c
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; // CPHA=1 (第二边沿采样)
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; // 建议≤5MHz
2. 热电偶类型配置
c
// 设置K型热电偶
MAX31856_WriteReg(REG_CR1, (TC_TYPE_K << 4) | 0x01);
// 可选类型:
// TC_TYPE_J (J型), TC_TYPE_N (N型), TC_TYPE_T (T型)
// TC_TYPE_E (E型), TC_TYPE_R (R型), TC_TYPE_S (S型), TC_TYPE_B (B型)
3. 滤波频率选择
c
// 50Hz滤波(欧洲/亚洲)
MAX31856_WriteReg(REG_CR0, CR0_50HZ | ...);
// 60Hz滤波(北美)
MAX31856_WriteReg(REG_CR0, CR0_60HZ | ...);
热电偶采集芯片MAX31856参考代码 www.youwenfan.com/contentcss/182500.html
五、温度转换算法优化
1. 分段线性插值(提高精度)
c
// 使用完整分度表的分段线性插值
float PreciseVoltageToTemp(float voltage) {
// 实际应用中应包含-270°C到1372°C的完整分度表
const int TABLE_SIZE = 100; // 示例大小
static const ThermocoupleTable full_table[TABLE_SIZE] = {...};
// 二分查找确定区间
int low = 0, high = TABLE_SIZE - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (voltage < full_table[mid].mv) {
high = mid - 1;
} else if (voltage > full_table[mid].mv) {
low = mid + 1;
} else {
return full_table[mid].temp;
}
}
// 线性插值
if (high < 0) high = 0;
if (low >= TABLE_SIZE) low = TABLE_SIZE - 1;
return LinearInterpolation(voltage,
full_table[high].mv, full_table[high].temp,
full_table[low].mv, full_table[low].temp);
}
2. NIST ITS-90多项式拟合(高精度)
c
// K型热电偶ITS-90拟合公式(简化版)
float NIST_K_Type_Temp(float mv) {
// 冷端补偿后的热电势(mV)
float E = mv * 1000; // 转换为微伏
// 分段多项式拟合
if (E >= -5880 && E <= 0) {
// -270°C to 0°C
float a0 = -0.176004136860E-01;
float a1 = 0.389212049750E-01;
// ... 完整系数见NIST文档
return a0 + a1*E + a2*pow(E,2) + ...;
}
else if (E > 0 && E <= 22200) {
// 0°C to 1372°C
float a0 = 0.118597600000E+00;
float a1 = -0.118343200000E-03;
// ... 完整系数见NIST文档
return a0 + a1*E + a2*pow(E,2) + ...;
}
return 0.0f;
}
六、故障处理与诊断
1. 故障代码解析
c
void ProcessFault(uint8_t fault) {
if (fault & 0x01) {
// 热电偶开路
// 处理方案:检查接线
}
if (fault & 0x02) {
// 热电偶短路到GND
// 处理方案:检查绝缘
}
if (fault & 0x04) {
// 热电偶短路到VCC
// 处理方案:检查绝缘
}
if (fault & 0x08) {
// 冷端补偿开路
// 处理方案:检查CJC传感器
}
if (fault & 0x10) {
// 冷端补偿过范围
// 处理方案:检查环境温度
}
if (fault & 0x20) {
// 温度过范围
// 处理方案:检查传感器
}
if (fault & 0x40) {
// 数学运算错误
// 处理方案:复位芯片
}
}
2. 看门狗与自动恢复
c
void Watchdog_Handler(void) {
static uint32_t last_read_time = 0;
if (HAL_GetTick() - last_read_time > 5000) { // 5秒无数据
// 尝试恢复通信
MAX31856_WriteReg(REG_CR0, 0x00);
HAL_Delay(10);
MAX31856_Init();
}
last_read_time = HAL_GetTick();
}
七、应用扩展
1. 多路温度采集
c
// 使用多片MAX31856(通过片选切换)
#define NUM_SENSORS 4
const uint8_t cs_pins[NUM_SENSORS] = {GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7};
void SelectSensor(uint8_t sensor_id) {
for (int i = 0; i < NUM_SENSORS; i++) {
HAL_GPIO_WritePin(GPIOA, cs_pins[i], (i == sensor_id) ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
}
float ReadMultiSensorTemp(uint8_t sensor_id) {
SelectSensor(sensor_id);
return ReadProcessedTemperature();
}
2. 温度数据记录
c
// 使用内部Flash存储历史数据
#define LOG_START_ADDR 0x0800F000 // Flash末页
#define LOG_ENTRY_SIZE 8 // 4字节时间戳 + 4字节温度
void LogTemperature(float temp) {
static uint32_t log_index = 0;
uint32_t timestamp = HAL_GetTick();
// 擦除Flash(首次使用时)
if (log_index == 0) {
FLASH_ErasePage(LOG_START_ADDR);
}
// 写入数据
uint64_t data = ((uint64_t)timestamp << 32) | *(uint32_t*)&temp;
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, LOG_START_ADDR + log_index*LOG_ENTRY_SIZE, (uint32_t)(data >> 32));
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, LOG_START_ADDR + log_index*LOG_ENTRY_SIZE + 4, (uint32_t)data);
log_index = (log_index + 1) % 128; // 环形缓冲区
}
八、测试与校准
1. 校准步骤
-
将热电偶浸入冰水混合物(0°C),记录读数
-
将热电偶浸入沸水(100°C),记录读数
-
计算校准系数:
cfloat offset = 0.0f; float gain = 1.0f; // 0°C校准 float t0 = ReadProcessedTemperature(); offset = 0.0f - t0; // 100°C校准 float t100 = ReadProcessedTemperature(); gain = 100.0f / (t100 - t0);
2. 测试数据(K型热电偶)
| 实际温度(°C) | 测量温度(°C) | 误差(°C) | 环境条件 |
|---|---|---|---|
| -50 | -49.8 | +0.2 | 室温25°C |
| 0 | 0.1 | +0.1 | 冰水混合物 |
| 100 | 99.7 | -0.3 | 沸水 |
| 200 | 199.2 | -0.8 | 油浴 |
| 500 | 498.5 | -1.5 | 管式炉 |
九、项目资源
-
开发环境:STM32CubeIDE 1.8.0, HAL库
-
测试工具:Keil ULINK2, 万用表, 恒温油槽
-
参考文档:
-
MAX31856 Datasheet (Rev. 1.0)
-
NIST Monograph 175: Thermocouple Database
-
IEC 60584-1: Thermocouples standard