外设驱动库开发笔记55:MAX31865热电阻变送器驱动
热敏电阻(RTD)作为一种高精度的温度传感器,在工业控制、医疗设备和实验室测量等领域有着广泛的应用。MAX31865是Maxim Integrated推出的一款专为RTD设计的信号调理器,能够简化RTD的温度测量过程。本文将详细介绍如何设计一个简洁易用的MAX31865驱动程序,帮助开发者快速上手并集成到自己的项目中。
1、功能概述
MAX31865是简单易用的热敏电阻至数字输出转换器,优化用于铂电阻温度传感器(RTD)。外部电阻设置所用RTD的灵敏度,高精度Σ-Δ ADC将RTD电阻与参考阻值之比转换成数字输出。MAX31865输入具有高达±45V的过压保护,提供可配置的RTD及电缆开路/短路条件检测。
MAX31865 主要用于测量 PT100/PT1000 热电阻的阻值变化。热电阻的阻值会随着温度的变化而发生线性变化,芯片通过内部电路将热电阻的阻值变化转换为电压信号。有两种封装,器引脚定义如下:
MAX31865通过对寄存器的读写实现相应的操作,总共有八个8位寄存器,其中包括:转换、状态和配置数据,所有设置均通过选择相应寄存器单元的地址完成,如下表所示为寄存器地址。
存取寄存器时,地址0Xh为读操作,地址8Xh为写操作。读写数据时,寄存器MSB在前。
配置寄存器选择转换模式(自动模式或单次转换命令触发模式)、使能和禁止BIAS引脚输出VBIAS、发送单次转换命令、选择RTD连接方式(3线或2/4线)、启动一次故障检测、清空故障状态寄存器,以及选择滤波器陷波频率。配置寄存器各位的定义如下:
其他寄存器设置或获取MAX31865的各种信息及数据,这两就不一一列举了。
2、驱动设计与实现
前面我们了解了MAX31865热电阻温度变送器的基本情况,接下来我们考虑如何实现MAX31865热电阻温度变送器的驱动程序。
2.1、对象定义
我们依然是基于对象的概念来实现驱动程序的设计。所以我们首先来考虑对象类型的定义。
作为一个对象至少包含有属性和操作。我们先来分析一下MAX31865热电阻温度变送器对象的属性有哪些。MAX31865热电阻温度变送器拥有8个寄存器,这些寄存器标识了MAX31865热电阻温度变送器当前时刻所处的状态,所以我们将它们定义为属性。同时考虑到记录当前时刻读取的电阻转换值和根据物理量程转换的温度值,所以我们将相应的ADC转换值及物理量值作为MAX31865热电阻温度变送器对象的属性。
除了属性,我们还要考虑一下MAX31865热电阻温度变送器对象需要实现哪些操作。我们只考虑与具体平台依赖性较强的操作。对于MAX31865热电阻温度变送器对象,当其完成AD转换回给出一个就绪指示,我们需要实时的检测这个信号,并且这个过程依赖于具体的软硬件平台,所以我们将检测过程设计为对象的操作。我们使用MAX31865热电阻温度变送器需要对其进行读写,这一过程也同样依赖于具体的软硬件平台,所以我们也将其作为对象的操作。另外MAX31856用一个片选信号,在实现总线操作时我们需要以此来选择目标器件,所以我们也将其作为对象的操作。根据前述对属性和操作的分析,我们可以定义对象类型如下:
/*定义MAX31865对象类型*/
typedef struct Max31865Object {uint8_t regValues[8];uint32_t adcCode;float Rref; //参考电阻的值float R0; //RTD在0度时的值float rtdValue; //RTD电阻值float temperature; //温度值void (*ReadData)(uint8_t *rData,uint16_t rSize); //读数据操作void (*WriteData)(uint8_t *wData,uint16_t wSize); //写数据操作void (*ChipSelcet)(Max31865CSType cs); //片选信号
}Max31865ObjectType;
我们已经定义了对象类型,可以使用对象类型来声明类型变量,但类型变量必须要初始化才能使用,所以我们还需要设计一个对象的初始化函数。在这个初始化函数中,我们需要将对象变量以及具体应用相关的属性及操作作为参数传入,并对对象的各个属性及操作函数指针赋初值。具体实现如下:
/*初始化MAX31865对象*/
void Max31865Initialization(Max31865ObjectType *max, //MAX31865对象变量float Rref, //参考电阻的值float R0, //RTD在0度时的值void (*ReadData)(uint8_t *rData,uint16_t rSize), //读MAX31865函数指针void (*WriteData)(uint8_t *wData,uint16_t wSize), //写MAX31865函数指针void (*ChipSelcet)(Max31865CSType cs) //片选操作函数指针)
{if((max==NULL)||(ReadData==NULL)||(WriteData==NULL)||(ChipSelcet==NULL)){return;}max->ReadData=ReadData;max->WriteData=WriteData;max->ChipSelcet=ChipSelcet;
}
2.2、对象操作
我们定义了对象类型并实现了初始化函数,接下来我们需要考虑要对MAX31865热电阻温度变送器执行怎样的操作,毕竟得到检测数据才是我们的目的。我们考虑到需要设置相应的寄存器以实现相应功能,同时也需要获取寄存器的值以得到设备状态,或者从MAX31865热电阻温度变送器获取测量数据。
我们先来看怎么读取寄存器的值。我们读取寄存器的值用于判断MAX31856当前的运行状态,配置参数以及测量数据等。前面我们已经叙述过寄存器的地址及功能,而读寄存器的时序要求如下:
根据前述对寄存器通讯的描述以及读寄存器的时序图,我们可以设计读寄存器的函数如下:
/*读寄存器*/
static void ReadRegister(Max31865ObjectType *max,uint8_t regAdd,uint16_t regNumber)
{uint8_t rDatas[8];if(regAdd>MAX31865_FAULT){return;}if((regAdd+regNumber)>8){regNumber=8-regAdd;}max->ChipSelcet(Max31865CS_Enable);max->WriteData(®Add,1);max->ReadData(rDatas,regNumber);max->ChipSelcet(Max31865CS_Disable);for(int i=0;i<regNumber;i++){max->regValues[regAdd+i]=rDatas[i];}
}
有读寄存器,自然也需要写寄存器。接下来我们再来看一看写寄存器的相关操作。写寄存器实际就是对相应的参数进行设置。而写寄存器的时序要求如下:
根据前述对寄存器通讯的描述以及读寄存器的时序图,我们可以设计写寄存器的函数如下:
/*写寄存器*/
static void WriteRegister(Max31865ObjectType *max,uint8_t regAdd,uint16_t regNumber,uint8_t *regValue)
{uint8_t wDatas[8];uint16_t wSize=0;if(((MAX31865_CFG<regAdd)&&(regAdd<MAX31865_HIGH_MSB))||(regAdd>MAX31865_LOW_LSB)){return;}wDatas[wSize++]=0x80+regAdd;for(int i=0;i<regNumber;i++){wDatas[wSize++]=regValue[i];}max->ChipSelcet(Max31865CS_Enable);max->WriteData(wDatas,wSize);max->ChipSelcet(Max31865CS_Disable);
}
有些情况下,我们可能会一次读或者写多个寄存器,比如我们可以一次性将2个测量值寄存器的值读出,还有我们设置报警限制寄存器时都是可以读写多个寄存器的。读写多个寄存器的时序图如下:
根据前述我们对读写寄存器的相关描述和上述时序图,我们可以读写多个寄存器和读写1个寄存器在本质上并无不同,只是数量问题。上述的读写寄存器函数中,我们已经添加了读写的寄存器数量这个参数就是为了处理读写所个寄存器的问题。
3、驱动的使用
我们已经实现了MAX31865热电阻温度变送器的驱动程序,这一节我们来使用该驱动程序实现一个简单应用,以验证驱动程序的正确性。
3.1、声明并初始化对象
首先我们需要使用前面定义的MAX31865热电阻温度变送器对象类型声明一个对象变量。在我们的系统中,SPI总线上挂载了1个MAX31865,所以我们声明如下:
Max31865ObjectType max31865;
声明的对象变量需要先初始化方可使用,而初始化函数有6个参数。第一个参数是需要初始化的对象变量的指针,而余下参数中,有两个是电阻相关常数设定,另外的3个参数则是平台相关的操作函数指针。这些函数的原型定义如下:
//读MAX31865函数指针
void (*ReadData)(uint8_t *rData,uint16_t rSize);
//写MAX31865函数指针
void (*WriteData)(uint8_t *wData,uint16_t wSize);
//片选操作函数指针
void (*ChipSelcet)(Max31865CSType cs);
这几个函数则是我们需要根据具体的软硬件平台来实现的。由于是在同一总线上,所以读写函数只需统一定义就好,但偏选信号和就绪信号则需根据模块单独定义。具体的函数实现如下:
*SPI1读数据操作*/
static void BmtcReadData(uint8_t *rData,uint16_t rSize)
{HAL_SPI_Receive(&hspi1, rData, rSize, 1000);
}/*SPI1读数据操作*/
static void BmtcWriteData(uint8_t *wData,uint16_t wSize)
{HAL_SPI_Transmit(&hspi1, wData, wSize, 1000);
}/*SPI1片选操作函数*/
static void BmtcChipSelcet(Max31865CSType cs)
{if(Max31865CS_Enable == cs){HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_RESET);return;}HAL_GPIO_WritePin(SPI2_CS_GPIO_Port, SPI2_CS_Pin, GPIO_PIN_SET);
}
完成这些函数的定义后我们就可以初始化对象变量了!将对象变量的指针以及这些函数的指针作为参数传递给初始化函数,具体实现如下:
/*初始化MAX31865对象*/Max31865Initialization(&max31865, //MAX31865对象变量402, //参考电阻的值100, //RTD在0度时的值BmtcReadData, //读MAX31865函数指针BmtcWriteData, //写MAX31865函数指针BmtcChipSelcet //片选操作函数指针);
至此我们就完成了对象变量的声明及初始化,在后续操作中就可以使用对象变量对对应的MAX32856热偶温度变送器进行各种操作。
3.2、基于对象进行操作
现在我们就可以在应用中使用驱动程序完成我们想要对MAX31865进行的操作了!在这个例子中我们分别读取4个MAX31856对象去测量值,并对这个测量值进行滤波处理。
/*温度数据检测*/
Max31865Temperature(&max31865);
tFilter.newValue=Power3Polyfit(max31865.temperature,aPara.phyPara.tempFactorA,aPara.phyPara.tempFactorB,aPara.phyPara.tempFactorC,aPara.phyPara.tempFactorD);
aPara.phyPara.temperature=BandSmoothingFilter(&tFilter);
到这里我们就完成了整个测试程序的编写,运行后能够正确读取温度数据,说明我们设计的驱动程序是正确的。
4、应用总结
在这一篇中我们设计并实现了MAX31865热电阻温度变送器的驱动程序,也编写了测试应用来验证这一驱动程序,测试的结果符合我们的预期。事实上,这一测试应用是从我们的实际项目中提取出来的,我们设计的MAX31865热电阻温度变送器驱动程序在实际项目中运行也完全符合要求。
在使用驱动程序时需要注意,在我们的应用中是一条SPI总线挂载了4个MAX31856模块,所以需要偏选信号。如果在应用中MAX31856是硬件设定的偏选信号,则可以在初始化时使用NULL或者空函数替代。