外设驱动库开发笔记53:MAX31856热偶变送器驱动

news2024/12/22 20:30:42

  在我们的产品中经常有需要温度检测的地方,而热电偶温度检测电路是我们常用的。热电偶温度检测的方法很多,有时出于简单方便的考虑我们会选择热偶温度变送器来实现,这一篇我们就来讨论使用MAX31856热电偶温度变送器实现温度的检测。

1、功能概述

  MAX31856可以对任何类型热电偶的信号进行冷端补偿和数字转换,输出数据以摄氏度为单位。转换器温度分辨率达0.0078125°C,允许读取最高+1800°C、最低-210°C (取决于热电偶类型)的温度读数,热电偶电压测量精度达±0.15%。热电偶输入端提供±45V过压保护。

  MAX31856内部的查找表(LUT)储存不同类型热电偶(K、J、N、R、S、T、E和B)的线性修正数据。而且MAX31856还具备50H和60Hz电网频率滤波,也是热电偶故障检测频率。SPI兼容接口允许选择热电偶类型并设置转换和故障检测过程。
热电偶功能是检测热电偶线两端的温度差。可在热电偶的额定工作温度范围内测量其检测端(常称为“热”端),关于热电偶测温范围:

  MAX31856将冷端温度数据储存在寄存器0Ah和0Bh。使能冷端温度检测时,这些寄存器为只读,其中包含实测冷端温度加冷端失调寄存器的数值。冷端温度检测使能时,读取寄存器操作将DRDY引脚复位为高电平。应通过多字节传输读取该寄存器的两个字节,以确保两个字节的数据来自同一次温度更新。禁止冷端温度检测时,这些寄存器为可读/写寄存器,其中包含最新的实测温度值。如果需要,禁止内部冷端检测时,可将来自外部温度传感器的数据写入这些传感器。最大冷端温度箝位在128°C,最小温度箝位在-64°C。

  由于所有热电偶都具有非线性,必须对冷端补偿后的原始值进行线性修正,并转换为温度值。为实现这一处理,利用LUT产生经过线性化和冷端补偿的温度值;每次转换后,将其作为19位数据储存在线性化热电偶温度寄存器(0Ch、0Dh和0Eh)中。应通过多字节传输读取全部三个字节,以确保所有数据来自于同一次数据更新。关于线性化热电偶温度数据格式,

  与MAX31856的通信通过16个包含转换、状态和配置数据的8位寄存器实现,全部设置均通过选择相应寄存器单元的对应地址完成,寄存器存储器映射所示为温度、状态和配置寄存器的地址。存取寄存器时,使用地址0Xh为读操作,地址8Xh为写操作。读写数据时,寄存器MSB在前。如果对只读寄存器执行写操作,不改变该寄存器的值。 

2、驱动设计与实现

  我们了解了MAX31856热偶温度变送器的基本情况,接下来我们考虑如何实现MAX31856热偶温度变送器的驱动程序。

2.1、对象定义

  我们依然是基于对象的概念来实现驱动程序的设计。所以我们首先来考虑对象类型的定义。
  作为一个对象至少包含有属性和操作。我们先来分析一下MAX31856热偶温度变送器对象的属性有哪些。MAX31856热偶温度变送器拥有16个寄存器,这些寄存器标识了MAX31856热偶温度变送器当前时刻所处的状态,所以我们将它们定义为属性。同时考虑到记录当前时刻读取的温度转换值和根据物理量程转换的温度值,所以我们将目标温度及冷端温度的ADC转换值及物理量值作为MAX31856热偶温度变送器对象的属性。
  接下来我们考虑一下MAX31856热偶温度变送器对象需要实现哪些操作。我们只考虑与具体平台依赖性较强的操作。对于MAX31856热偶温度变送器对象,当其完成AD转换回给出一个就绪指示,我们需要实时的检测这个信号,并且这个过程依赖于具体的软硬件平台,所以我们将检测过程设计为对象的操作。我们使用MAX31856热偶温度变送器需要对其进行读写,这一过程也同样依赖于具体的软硬件平台,所以我们也将其作为对象的操作。另外MAX31856用一个片选信号,在实现总线操作时我们需要以此来选择目标器件,所以我们也将其作为对象的操作。根据前述对属性和操作的分析,我们可以定义对象类型如下:

/*定义MAX31856对象类型*/
typedef struct Max31856Object {
    uint8_t regValue[16];
    uint32_t mDataCode;
    uint32_t rDataCode;
    float mTemperature;     //TC测量温度
    float rTemperature;      //冷端温度
    uint8_t (*Ready)(void);
    void (*ReadData)(uint8_t *rData,uint16_t rSize);
    void (*WriteData)(uint8_t *wData,uint16_t wSize);
    void (*ChipSelcet)(Max31856CSType cs);     //片选信号
}Max31856ObjectType;

  我们已经定义了对象类型,使用对象类型l可以声明类型变量,但类型变量必须要初始化才能使用,所以我们还需要设计一个对象的初始化函数。在这个初始化函数中,我们需要将对象变量以及具体应用相关的属性及操作作为参数传入,并对对象的各个属性及操作函数指针赋初值。具体实现如下;

/*初始化MAX31855对象*/
void Max31856Initialization(Max31856ObjectType *tc,         //MAX31856对象变量
                            Max31856Ready ready,            //就绪信号
                            Max31856ReadDataType read,      //读MAX31856函数指针
                            Max31856WriteDataType write,    //写MAX31856函数指针
                            Max31856ChipSelcetType cs       //片选操作函数指针
                                )
{
    uint8_t regValue=0;
    uint8_t rData[16]={0};
    
    if((tc==NULL)||(ready==NULL)||(read==NULL)||(write==NULL))
    {
        return;
    }
    
    tc->Ready=ready;
    tc->ReadData=read;
    tc->WriteData=write;
    
    if(cs!=NULL)
    {
        tc->ChipSelcet=cs;
    }
    else
    {
        tc->ChipSelcet=DefaultChipSelect;
    }
    
    tc->mDataCode=0;
    tc->rDataCode=0;
    tc->mTemperature=0.0;
    tc->rTemperature=0.0;
    
    tc->ChipSelcet(Max31856CS_Disable);
    
    regValue=0x81;
    
    WriteRegister(tc,REG_CR0,regValue);
    
    ReadRegister(tc,REG_CR0,rData,16);
    for(int i=0;i<16;i++)
    {
        tc->regValue[i]=rData[i];
    }
}

2.2、对象操作

  我们定义了对象类型并实现了初始化函数,接下来我们需要考虑要对MAX31856热偶温度变送器执行整么样的操作,毕竟得到数据才是我们的目的。我们考虑到需要设置相应的寄存器以实现相应功能,同时也需要获取寄存器的值以得到设备状态,或者从MAX31856热偶温度变送器获取测量数据。
  我们先来看怎么读取寄存器的值。我们读取寄存器的值用于判断MAX31856当前的运行状态,前面我们已经叙述过寄存器的地址及功能,而读寄存器的时序要求如下:

  根据前面的描述及上述时序图的要求可以编写读寄存器的函数如下:

/*读寄存器操作*/
static void ReadRegister(Max31856ObjectType *tc,uint8_t regAddr,uint8_t *rData,uint8_t rSize)
{
    uint8_t wData=regAddr;

    if(rSize<1)
    {
        return;
    }
    
    tc->ChipSelcet(Max31856CS_Enable);
    
    tc->WriteData(&wData,1);
    
    tc->ReadData(rData,rSize);
    
    tc->ChipSelcet(Max31856CS_Disable);

}

  同样我们写寄存器时,我们根据前述寄存器的相关描述和写寄存器的时序图来实现。写寄存器的时序图如下:

  根据上述描述我们可以实现写寄存器值的函数如下:

/*写寄存器操作*/
static void WriteRegister(Max31856ObjectType *tc,uint8_t regAddr,uint8_t value)
{
    uint8_t wData[2];
    
    if(regAddr>11)
    {
        return;
    }
    
    
    wData[0]=regAddr+0x80;
    wData[1]=value;
    
    tc->ChipSelcet(Max31856CS_Enable);
    tc->WriteData(wData,2);
    tc->ChipSelcet(Max31856CS_Disable);
}

  我们使用MAX31856的目的就是为了得到温度测量数据,所以我们来看看如何读取温度数据。温度转换值可以一次读取测量数据和冷端数据,其时序图如下:

  根据上述描述我们一次性读取6个字节的数据,具体实现如下:

/*获取MAX31855测量数据*/
void Max31856GetDatas(Max31856ObjectType *tc)
{
    uint8_t rData[6]={0};

    if(tc->Ready())
    {
        if((tc->regValue[REG_CR0]&0x80) != 0x80)
        {
            WriteRegister(tc,REG_CR0,0x81);
            ReadRegister(tc,REG_CR0,rData,1);
            tc->regValue[REG_CR0]=rData[0];
        }
        return;
    }
    
    ReadRegister(tc,REG_CJTH,rData,6);

    tc->rDataCode=(rData[0]<<8)+rData[1];
    
    tc->mDataCode=(rData[2]<<16)+(rData[3]<<8)+rData[4];

    tc->regValue[REG_SR]=rData[5];
    
    tc->mTemperature=CalcMeasureTemperature(tc->mDataCode);
    tc->rTemperature=CalcColdEndTemperature(tc->rDataCode);

}

3、驱动的使用

  我们已经实现了MAX31856热偶温度变送器的驱动程序,这一节我们来使用该驱动程序实现一个简单应用,以验证驱动程序的正确性。

3.1、声明并初始化对象

  首先我们需要使用前面定义的MAC31856热偶温度变送器对象类型声明一个对象变量。在我们的系统中,总线上挂载了4个MAX31856,所以我们声明如下:

Max31856ObjectType tcObj[4];

  声明的对象变量需要先初始化方可使用,而初始化函数有5个参数。第一个参数是需要要初始化的对象变量的指针,而余下的4个参数则是平台相关的操作函数指针。这些函数的原型定义如下:

typedef uint8_t (*Max31856Ready)(void);
typedef void (*Max31856ReadDataType)(uint8_t *rData,uint16_t rSize);
typedef void (*Max31856WriteDataType)(uint8_t *wData,uint16_t wSize);
typedef void (*Max31856ChipSelcetType)(Max31856CSType cs);     //片选信号

  这几个函数则是我们需要根据具体的软硬件平台来实现的。由于是在同一总线上,所以读写函数只需统一定义就好,但偏选信号和就绪信号则需根据模块单独定义。具体的函数实现如下:

/*温度模块1就绪操作函数*/
static uint8_t Tc1Ready(void)
{
    uint8_t result=1;
    result=HAL_GPIO_ReadPin(TC1_RDY_GPIO_Port,TC1_RDY_Pin);
    return result;
}

/*温度模块2就绪操作函数*/
static uint8_t Tc2Ready(void)
{
    uint8_t result=1;
    result=HAL_GPIO_ReadPin(TC2_RDY_GPIO_Port,TC2_RDY_Pin);
    return result;
}

/*温度模块3就绪操作函数*/
static uint8_t Tc3Ready(void)
{
    uint8_t result=1;
    result=HAL_GPIO_ReadPin(TC3_RDY_GPIO_Port,TC3_RDY_Pin);
    return result;
}

/*温度模块4就绪操作函数*/
static uint8_t Tc4Ready(void)
{
    uint8_t result=1;
    result=HAL_GPIO_ReadPin(TC4_RDY_GPIO_Port,TC4_RDY_Pin);
    return result;
}

/*SPI1写数据操作*/
static void WriteData(uint8_t *wData,uint16_t wSize)
{
    HAL_SPI_Transmit(&hspi1, wData, wSize, 1000);
}

/*温度模块1片选操作函数*/
static void Tc1ChipSelcet(Max31856CSType cs)
{
    if(Max31856CS_Enable == cs)
    {
        HAL_GPIO_WritePin(TC1_CS_GPIO_Port, TC1_CS_Pin, GPIO_PIN_RESET);
        return;
    }
    HAL_GPIO_WritePin(TC1_CS_GPIO_Port, TC1_CS_Pin, GPIO_PIN_SET);
}

/*温度模块2片选操作函数*/
static void Tc2ChipSelcet(Max31856CSType cs)
{
    if(Max31856CS_Enable == cs)
    {
        HAL_GPIO_WritePin(TC2_CS_GPIO_Port, TC2_CS_Pin, GPIO_PIN_RESET);
        return;
    }
    HAL_GPIO_WritePin(TC2_CS_GPIO_Port, TC2_CS_Pin, GPIO_PIN_SET);
}

/*温度模块3片选操作函数*/
static void Tc3ChipSelcet(Max31856CSType cs)
{
    if(Max31856CS_Enable == cs)
    {
        HAL_GPIO_WritePin(TC3_CS_GPIO_Port, TC3_CS_Pin, GPIO_PIN_RESET);
        return;
    }
    HAL_GPIO_WritePin(TC3_CS_GPIO_Port, TC3_CS_Pin, GPIO_PIN_SET);
}

/*温度模块4片选操作函数*/
static void Tc4ChipSelcet(Max31856CSType cs)
{
    if(Max31856CS_Enable == cs)
    {
        HAL_GPIO_WritePin(TC4_CS_GPIO_Port, TC4_CS_Pin, GPIO_PIN_RESET);
        return;
    }
    HAL_GPIO_WritePin(TC4_CS_GPIO_Port, TC4_CS_Pin, GPIO_PIN_SET);
}

  完成这些函数的定义后我们就可以初始化对象变量了!将对象变量的指针以及这些函数的指针作为参数传递给初始化函数,具体实现如下:

/*初始化MAX31856对象*/
    Max31856Initialization(&tcObj[3],
                           Tc4Ready,
                           ReadData,
                           WriteData,
                           Tc4ChipSelcet
                               );
    Max31856Initialization(&tcObj[0],
                           Tc1Ready,
                           ReadData,
                           WriteData,
                           Tc1ChipSelcet
                               );
    Max31856Initialization(&tcObj[1],
                           Tc2Ready,
                           ReadData,
                           WriteData,
                           Tc2ChipSelcet
                               );
    Max31856Initialization(&tcObj[2],
                           Tc3Ready,
                           ReadData,
                           WriteData,
                           Tc3ChipSelcet
                               );

  至此我们就完成了对象变量的声明及初始化,在后续操作中就可以使用对象变量对对应的MAX32856热偶温度变送器进行各种操作。

3.2、基于对象进行操作

  现在我们就可以在应用中使用驱动程序完成我们想要对MAX31856进行的操作了!在这个例子中我们分别读取4个MAX31856对象去测量值,并对这个测量值进行滤波处理。

Max31856GetDatas(&tcObj[0]);
    tFilter[0].newValue=tcObj[0].mTemperature;
    if(tcObj[0].mTemperature<0.0)
    {
        kF[0]=1.125;
    }
    aPara.phyPara.tc1Temp=Power3Polyfit(BandSmoothingFilter(&tFilter[0]),0.0,0.0,kF[0],0.0);
    
    Max31856GetDatas(&tcObj[1]);
    tFilter[1].newValue=tcObj[1].mTemperature;
    if(tcObj[1].mTemperature<0.0)
    {
        kF[1]=1.126;
    }
    aPara.phyPara.tc2Temp=Power3Polyfit(BandSmoothingFilter(&tFilter[1]),0.0,0.0,kF[1],0.0);
    
    Max31856GetDatas(&tcObj[2]);
    tFilter[2].newValue=tcObj[2].mTemperature;
    if(tcObj[2].mTemperature<0.0)
    {
        kF[2]=1.125;
    }
    aPara.phyPara.tc3Temp=Power3Polyfit(BandSmoothingFilter(&tFilter[2]),0.0,0.0,kF[2],0.0);
    
    Max31856GetDatas(&tcObj[3]);
    tFilter[3].newValue=tcObj[3].mTemperature;
    if(tcObj[3].mTemperature<0.0)
    {
        kF[3]=1.125;
    }
    aPara.phyPara.tc4Temp=Power3Polyfit(BandSmoothingFilter(&tFilter[3]),0.0,0.0,kF[3],0.0);

  到这里我们就完成了整个测试程序的编写,运行后能够正确读取温度数据,说明我们设计的驱动程序是正确的。

4、应用总结

  在这一篇中我们设计并实现了MAX31856热偶温度变送器的驱动程序,也编写了测试应用来验证这一驱动程序,测试的结果符合我们的预期。事实上,这一测试应用是从我们的实际项目中提取出来的,我们设计的MAX31856热偶温度变送器驱动程序在实际项目中运行也完全符合要求。
  在使用驱动程序时需要注意,在我们的应用中是一条SPI总线挂载了4个MAX31856模块,所以需要偏选信号。如果在应用中MAX31856是硬件设定的偏选信号,则可以在初始化时使用NULL或者空函数替代。

欢迎关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/477683.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PCL】—— 点云配准ICP(Iterative Closest Point)算法

文章目录 数学原理问题定义计算平移计算旋转 案例实现参考 ​     由于三维扫描仪设备受到测量方式和被测物体形状的条件限制&#xff0c;一次扫描往往只能获取到局部的点云信息&#xff0c;进而需要进行多次扫描&#xff0c;然后每次扫描时得到的点云都有独立的坐标系&…

第十六章 预制件prefab(上)

本章节我们介绍一下“预制件”&#xff0c;也有人叫“预制体”&#xff0c;也就是Prefab。在游戏世界中&#xff0c;那些自然环境的游戏对象&#xff0c;我们可以提前创建在场景中&#xff0c;这个大家能够理解。但是&#xff0c;有些游戏对象&#xff0c;需要根据游戏逻辑来通…

20230430 ICFD学习笔记 管道流动

三个边界: (1) 速度入口 (2) 压力出口 (3) 非滑移边界 一、先利用workbench进行网格的划分,导出K文件。 二、利用Ultraeidt进行K文件的修改 (或者是在lspp中直接删除也行) K文件开头是*Keyword Part *Define coordinate system后面到*Database binary D3prop全部删除 &…

Dubbo总结

目录 什么是分布式系统 单机架构、集群架构、分布式架构 Dubbo的概念 Dubbo的核心组件 Dubbo的常用注解 Dubbo的高级特性&#xff1a; 序列化特性安全 地址缓存 超时机制 重试机制 多版本灰度发布 负载均衡 集群容错 服务降级 服务限流 结果缓存 Dubbo实战&#xff1a;…

40.java-Set集合(HashSet,LinkedHashSet,TreeSet)

Set集合 1.Set集合特点2.Set集合实现类3. HashSet3.1 底层原理3.1.1 哈希表组成3.1.2 哈希值3.1.3 对象的哈希值特点 3.2 数据添加元素的过程3.3 HashSet的三个问题3.4 实例&#xff1a;去除重复元素 4. LinkedHashSet5. TreeSet5.1 特点5.2 集合默认规则5.3 例子5.4 两种比较规…

JavaScript 知识总结上篇(更新版)

1. 为什么 JS 是单线程的&#xff1f; 因为JS里面有可视的Dom&#xff0c;如果是多线程&#xff0c;这个线程正在删除DOM节点&#xff0c;另一个线程正在编辑Dom节点&#xff0c;导致浏览器不知道该听谁的 2.如何理解同步和异步&#xff1f; 同步&#xff1a;按照代码书写顺…

Linux——中断和时间管理(下)

目录 延时控制 定时操作 低分辨率定时器 高分辨率定时器 练习 延时控制 在硬件的操作中经常会用到延时&#xff0c;比如要保持芯片的复位时间持续多久、芯片复位后要至少延时多长时间才能去访问芯片、芯片的上电时序控制等。为此&#xff0c;内核提供了一组延时操作函数。…

DDD系列:三、Repository模式

为什么需要Repository&#xff1f; ​ Anemic Domain Model&#xff08;贫血领域模型&#xff09;特征&#xff1a; 有大量的XxxDO对象&#xff1a;这里DO虽然有时候代表了Domain Object&#xff0c;但实际上仅仅是数据库表结构的映射&#xff0c;里面没有包含&#xff08;或…

kafka整理

kafka整理 一、kafka概述 kafka是apache旗下一款开源的顶级的消息队列的系统, 最早是来源于领英, 后期将其贡献给apache, 采用语言是scala.基于zookeeper, 启动kafka集群需要先启动zookeeper集群, 同时在zookeeper记录kafka相关的元数据 kafka本质上就是消息队列的中间件产品…

Codeforces Round 867 (Div. 3)(A-G2)

文章目录 A. TubeTube Feed1、题目2、分析3、代码&#xff0c; B. Karina and Array1、题目2、分析3、代码 C. Bun Lover1、问题2、分析&#xff08;1&#xff09;观察样例法&#xff08;2&#xff09;正解推导 3、代码 D. Super-Permutation1、问题2、分析&#xff08;1&#…

力扣第343场周赛

第一次力扣&#xff0c;等大二寒暑假&#xff0c;有时间再来系统刷题 目录 &#x1f33c;前言 &#x1f33c;一&#xff0c;6341.保龄球游戏的获胜者 &#x1f33c;二&#xff0c;6342.找出叠涂元素 &#x1f333;第一次 -- 超时 &#x1f333;第二次 -- AC &#x1f33c…

二叉树相关的简单递归oj

二叉树相关的简单递归oj 前言题目二叉树的前序遍历相同的树判断单值二叉树对称二叉树另一棵树的子树创建二叉树并遍历 前言 这篇博客主要是博主感觉对二叉树oj题目不太熟悉&#xff0c;随便整理的一下题目和解答&#xff0c;方便复习&#xff0c;所以讲题部分主要以我自己以及为…

Java 基础入门篇(二)——— Java 基础语法

文章目录 一、注释二、字面量三、变量3.1 变量概述3.2 变量在计算机中的底层原理 四、数据类型五、关键字、标志符六、类型转换6.1 自动类型转换6.2 表达式的自动类型转换6.3 强制类型转换 七、运算符7.1 基本算数运算符7.2 符号做连接符7.3 自增自减运算符7.4 赋值运算符7.5 …

【C++技能树】类的六个成员函数Ⅰ --构造、析构、拷贝构造函数

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法…感兴趣就关注我吧&#xff01;你定不会失望。 本篇导航 0.this指针1.Class默认成员函数2.构造函数调用规则: 3.析构函数4.拷贝构造函数 0.this指针 在开始本章内容之前&#xff0c;先浅…

Channel-wise Knowledge Distillation for Dense Prediction(ICCV 2021)原理与代码解析

paper&#xff1a;Channel-wise Knowledge Distillation for Dense Prediction official implementation&#xff1a;https://github.com/irfanICMLL/TorchDistiller/tree/main/SemSeg-distill 摘要 之前大多数用于密集预测dense prediction任务的蒸馏方法在空间域spatial…

(求正数数组的最小不可组成和,养兔子)笔试强训

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 目录 文章目录 一、选择题1 二、[编程题]养兔子 三、[编程题]求正数数组的最小不可组成和 一、选择题1 reflection是如何工作的__牛客网 (nowcoder.com) 考虑下面这个简单的例子&…

大数据Doris(八):Broker部署和集群启停脚本

文章目录 Broker部署和集群启停脚本 一、Broker部署 1、准备Broker 安装包 2、启动 Broker

PyQt6剑指未来-日期和时间

前言 时间和日期是软件开发中非常重要的概念。在PyQt6中&#xff0c;时间和日期模块提供了处理日期、时间和日期时间的类和函数&#xff0c;以及管理时区和夏令时的特性。这些模块提供了可靠和易于使用的工具&#xff0c;使得在PyQt6中处理和呈现时间和日期的操作变得轻松起来…

Java中Lambda表达式(初学到精通)

目录 一、Lambda表达式是什么&#xff1f;什么场景下使用Lambda&#xff1f; 1.Lambda 表达式是什么 2.函数式接口是什么 第二章、怎么用Lambda 1.必须有一个函数式接口 2.省略规则 3.Lambda经常用来和匿名内部类比较 第三章、具体使用举例&#xff08;&#xff09; 1.案…

跳跃游戏类题目 总结篇

一.跳跃游戏类题目简单介绍 跳跃游戏是一种典型的算法题目&#xff0c;经常是给定一数组arr&#xff0c;从数组的某一位置i出发&#xff0c;根据一定的跳跃规则&#xff0c;比如从i位置能跳arr[i]步&#xff0c;或者小于arr[i]步&#xff0c;或者固定步数&#xff0c;直到到达某…