在我们的产品中,经常需要检测温湿度数据。检测温湿度的方法和模块有很多,包括SHT1x系列温湿度传感器是一种成本低、使用方便的温湿度检测模块。我们来谈谈如何实现它。SHT1x驱动系列温湿度传感器。
SHT1x包括 SHT10, SHT11 和 SHT15 属于Sensirion温湿度传感器家族的贴片包装系列。传感器将传感器元件和信号处理电路集成在微电路板上,输出完全标定的数字信号。
SHT1x传感器包括一个电容聚合物湿度敏感元件,一个由能量间隙材料制成的温度测量元件,在同一芯片上,与14 位的A/D 无缝连接转换器和串行接口电路。引脚定义如下:
SHT1x温湿度传感器使用的2线通信类似于I2C总线,但不一样,用普通的GPIO通信可以实现。这次使用。STM32F103VET6来操作SHT具体连接方式如下:
SCK 用于微处理器和SHT1x 通信同步。由于界面包含完全静态逻辑,因此没有最小值SCK 频率。
DATA 引脚是读取传感器数据的三态结构 . 当命令发送到传感器时, DATA 在 SCK 上升沿有效且在 SCK 平时高电必须保持稳定。 DATA 在 SCK 下降边后变化。微处理器应驱动以避免信号冲突DATA 在低电平。需要外部上拉电阻(例如:10kΩ)将信号提升到高电平。微处理器通常包含上拉电阻I/O 电路中。
选择供电电压后,传感器通电,上电速率不得低于1V/ms。传感器通电后需要11ms 在此之前,不允许向传感器发送任何命令进入休眠状态。
SHT1x温湿度传感器采用一组启动传输时序,完成数据传输的初始化。后续命令包括三个地址位(目前只支持000)和五个命令位。SHT1x 会以下方式表示正确接收指令:第八 个SCK 时钟下降后,DATA 下拉为低电平(ACK 位)。在第9 个SCK 时钟下降后,释放DATA(恢复高电平)。SHT1x温湿度传感器的令表如下:
我们后续开发SHT1x当温湿度传感器驱动时,通过这些操作命令实现不同的操作。
湿度测量数据不是线性变化过程中湿度的非线性。为了获得更准确的测量数据,我们通常使用非线性补偿公式进行信号转换。湿度的非线性补偿公式和参数如下:
一般来说,传感器湿度的校准是在一定的参考温度下进行的,但在我们的使用过程中,实际温度和测试参考温度是25℃ (~77℉)这显然是不同的,所以我们需要补偿实际的湿度数据。湿度的温度补偿公式和系数如下:
SHT1x系列温湿度传感器的温度传感器采用能隙材料PTAT。而能隙材料PTAT它通常与绝对温度成正比,因此温度传感器具有优异的线性。数字输出可以使用以下公式(SOT)温度转换系数如下:
SHT1x 露点不能直接测量,但露点可以通过读取温度和湿度来计算.。在同一集成电路上测量温度和湿度,SHT1x露点可以测量。露点的计算方法很多,大部分都很复杂。 – 50°C 通过以下公式测量温度范围可以获得更好的精度。
可以通过上述公式计算SHT1x监测的温度、湿度及露点数据。
我们已经明白了SHT1x接下来,我们将进一步考虑如何设计和实现系列温湿度传感器的基本技术特性SHT1x驱动系列温湿度传感器。
在使用一个对象之前,我们需要得到一个对象。同样,我们的东西SHT1x系列温湿度传感器需要先定义SHT1x系列温湿度传感器的对象。
我们要得到SHT1x一系列温湿度传感器对象需要首先分析其基本特性。一般来说,一个对象至少包含两个特征:属性和操作。接下来,让我们从这两个方面来思考SHT1x系列温湿度传感器的对象。
首先考虑属性,因为属性必须用来识别或记录对象的特征。我们来考虑SHT1x系列温湿度传感器对象属性。首先SHT1x系列温湿度传感器有一个状态寄存器,用于表示状态和配置操作特性,所以我们将读取的状态寄存器的数据作为标识SHT1x一系列温湿度传感器对象的属性。根据前面SHT1x系列温湿度传感器的数据计算公式可知,温度单位和工作电压对温度测量结果的计算有直接影响,所以我们将温度单位和工作电压也作为SHT1x用于区分计算过程中系列温湿度传感器对象的属性。此外,我们将温度、湿度和露点数据作为记录当前状态的属性。
然后我们还需要考虑SHT1x系列温湿度传感器对象的操作。我们是使用GPIO模拟数字通信,所以SCK引脚和DATA引脚需要控制输出,控制函数的实现与特定硬件有关,因此我们将控制这两个引脚输出的函数作为对象。对于DATA引脚也可能需要控制方向和读取输入,因为同样的原因,我们也使用它作为对象。另外,我们在和SHT1X通信时需要控制时钟,操作等待是与硬件相关的时间操作,因此我们也将其作为操作对象。
以上我们是对的SHT1x我们可以定义温湿度传感器的分析SHT1x温湿度传感器的对象类型如下:
/*定义SHT1x对象类型*/typedefstructSht1xObject{
uint8_tstatusReg;//状态寄存器uint32_tperiod;//SCK时钟周期SHT1xTempUnitTypetempUnit;///温度单位floatvdd;//工作电压floattemperature;//温度floathumidity; //湿度floatdewPoint;//露点SHT1xSetBusPin*SetBusPin; ///总线操作函数uint8_t(*ReadSDABit)(void); ///阅读数据总线函数void(*SDADirection)(SHT1xIODirectionTypedirection);////数据总线方向控制函数 void (*Delayus)(volatile uint32_tperiod); //微秒延时函数 void (*Delayms)(volatile uint32_tnTime); //毫秒秒延时函数}Sht1xObjectType;
我们知道,一个对象仅作声明是不能使用的,我们需要先对其进行初始化,所以这里我们来考虑SHT1x系列温湿度传感器对象的初始化函数。一般来说,初始化函数需要处理几个方面的问题。一是检查输入参数是否合理;二是为对象的属性赋初值;三是对对象作必要的初始化配置。据此我们设计SHT1x系列温湿度传感器对象的初始化函数如下:
/* 初始化SHT1x对象 */voidSHT1xInitialization(Sht1xObjectType *sht, uint32_t sck, float vdd, SHT1xTempUnitType uint, SHT1xHeaterType heater, SHT1xOTPType otp, SHT1xResolutionType resolution, SHT1xSetBusPin setSckPin, SHT1xSetBusPin setDataPin, SHT1xReadSDABit readSDA, SHT1xSDADirection direction, SHT1xDelay delayus, SHT1xDelay delayms){
uint8_t regSetup=0x00; uint8_t heaterSet[]={ONCHIPHEATERDISABLE,ONCHIPHEATERENABLE}; //是否启用片内加热配置集 uint8_t otpSet[]={OTPENABLE,OTPDISABLE}; //是否加载OTP配置集 uint8_t dpiSet[]={HIGH_RESOLUTION_DATA,LOW_RESOLUTION_DATA}; //数据分辨率配置集 if((sht==NULL)||(setSckPin==NULL)||(setDataPin==NULL) ||(readSDA==NULL)||(delayus==NULL)||(delayms==NULL)) {
return; } setBusPin[0]=setSckPin; setBusPin[1]=setDataPin; sht->SetBusPin=setBusPin; sht->ReadSDABit=readSDA; sht->Delayus=delayus; sht->Delayms=delayms; if(direction!=NULL) {
sht->SDADirection=direction; } else {
sht->SDADirection=DefaultSDADirection; } /*初始化速度,默认100K*/ if((sck>0)&&(sck<=500)) {
sht->period=500/sck; } else {
sht->period=5; } sht->temperature=0.0; sht->humidity=0.0; sht->dewPoint=0.0; sht->vdd=vdd; sht->tempUnit=uint; regSetup=regSetup|heaterSet[heater]|otpSet[otp]|dpiSet[resolution]; WriteStatusRegister(sht,®Setup); sht->Delayms(10); ReadStatusRegister(sht);}
我们已经完成了SHT1x系列温湿度传感器对象类型的定义和对象初始化函数的设计。但我们的主要目标是获取对象的信息,接下来我们还要实现面向SHT1x温湿度传感器的各类操作。
每次发起与SHT1x温湿度传感器的通讯都需要用一组“启动传输”时序,来完成数据传输的初始化。它包括:当SCK时钟高电平时DATA翻转为低电平,紧接着SCK变为低电平,随后是在SCK时钟高电平时DATA翻转为高电平。启动通讯时序如下图:
根据上述时序图我们可以实现启动通讯的操作函数如下:
/*SHT1X启动时序操作*/static voidStartSHT1XOperation(Sht1xObjectType *sht){
/*将data线设置为输出模式*/ sht->SDADirection(Out); sht->SetBusPin[DataPin](SHT1xSet); sht->SetBusPin[SckPin](SHT1xReset); sht->Delayus(sht->period); sht->SetBusPin[SckPin](SHT1xSet); sht->Delayus(sht->period); sht->SetBusPin[DataPin](SHT1xReset); sht->Delayus(sht->period); sht->SetBusPin[SckPin](SHT1xReset); sht->Delayus(sht->period); sht->SetBusPin[SckPin](SHT1xSet); sht->Delayus(sht->period); sht->SetBusPin[DataPin](SHT1xSet); sht->Delayus(sht->period); sht->SetBusPin[SckPin](SHT1xReset);}
如果与SHT1x通讯中断,可通过下列信号时序复位:当DATA保持高电平时,触发SCK时钟9 次或更多。接着发送一个“传输启动”时序。这些时序只复位串口,状态寄存器内容仍然保留。具体的时序图如下:
根据上述的时序图,我们设计通讯复位操作函数如下:
/*SHT1X通讯复位*/void ResetSHT1XCommunication(Sht1xObjectType*sht){
/*将data线设置为输出模式*/ sht->SDADirection(Out); sht->Delayms(1); sht->SetBusPin[DataPin](SHT1xSet); sht->SetBusPin[SckPin](SHT1xReset); for(int i=0;i<9;i++) {
sht->SetBusPin[SckPin](SHT1xSet); sht->Delayus(sht->period); sht->SetBusPin[SckPin](SHT1xReset); sht->Delayus(sht->period); } StartSHT1XOperation(sht);}
在前面我们已经了解了SHT1x通讯命令,根据命令定义,我们发送命令“00000101”就表示相对湿度RH测量,发送命令“00000011”就表示温度T的测量。测量过程需要大约20/80/320ms,分别对应8/12/14bit分辨率。SHT1x通过下拉DATA至低电平并进入空闲模式,表示测量的结束。控制器在再次触发SCK时钟前,必须等待这个“数据备妥”信号来读出数据。检测数据可以先被存储,这样控制器可以继续执行其它任务在需要时再读出数据。
接着传输2个字节的测量数据和1个字节的CRC奇偶校验(可选择读取)。控制器需要通过下拉DATA为低电平,以确认每个字节。所有的数据从MSB 开,右值有效(例如:对于12bit 数据,从第5个SCK时钟起算作MSB;而对于8bit 数据,首字节则无意义)。
在收到CRC的确认位之后,表明通讯结束。如果不使用CRC-8 校验,控制器可以在测量值LSB后,通过保持ACK高电平终止通讯。在测量和通讯完成后,SHT1x自动转入休眠模式。数据测量时序图如下所示:
根据上述描述和时序图,我们可以实现温湿度数据的获取函数如下:
/*获取SHT1X的湿度值*/float GetSht1xHumidityValue(Sht1xObjectType *sht){
float humiValue=0.0; uint16_t sorh=0; uint8_t err=0; uint8_t humiCode[2]={
0,0}; uint8_t checkSum=0; StartSHT1XOperation(sht); WriteByteToSht1x(sht,HUMI_MEAS_COMMAND); sht->SDADirection(In); if((sht->statusReg&0x01)==0x01) {
sht->Delayms(20); } else {
sht->Delayms(80); } if(sht->ReadSDABit() == 1) {
err += 1; } humiCode[0]=ReadByteFromSht1x(sht,Ack); humiCode[1]=ReadByteFromSht1x(sht,Ack); checkSum=ReadByteFromSht1x(sht,noAck); if(CheckCRC8ForSHT1x(humiCode,2,checkSum)) {
sorh=(humiCode[0]<<8)|humiCode[1]; } else {
err+= 1; } if(err != 0) {
ResetSHT1XCommunication(sht); } else {
humiValue=ConvertHumidityData(sht,sorh); } return humiValue;}
SHT1x的某些高级功能可以通过给状态寄存器发送指令来实现,如选择测量分辨率,电量不足提醒,使用OTP加载或启动加热功能等。SHT1x的状态寄存器可以读或者写。其实写状态寄存器就是配置设备的一些特性,一般情况下在初始化时完成即可。读写状态寄存器的格式如下:
/*读状态寄存器*/static uint8_tReadStatusRegister(Sht1xObjectType *sht){
uint8_t err=0; uint8_t status; uint8_t checkSum; StartSHT1XOperation(sht); err=WriteByteToSht1x(sht,READ_STATUS_REGISTER); status=ReadByteFromSht1x(sht,Ack); checkSum=ReadByteFromSht1x(sht,noAck); if(CheckCRC8ForSHT1x(&status,1,checkSum)) {
sht->statusReg=status; } else {
err+=1; } return err;} /*写状态寄存器*/static uint8_tWriteStatusRegister(Sht1xObjectType *sht,uint8_t *pValue){
uint8_t err=0; StartSHT1XOperation(sht); err +=WriteByteToSht1x(sht,WRITE_STATUS_REGISTER); err +=WriteByteToSht1x(sht,*pValue); err+=ReadStatusRegister(sht); return err;}
我们已经设计并实现了SHT1x温湿度传感器驱动,接下来我们还需要对这一驱动进行验证,所以我们要基于此驱动设计一个简单的应用。
使用基于对象的操作我们需要先得到这个对象,所以我们先要使用前面定义的SHT1x温湿度传感器对象类型声明一个SHT1x温湿度传感器对象变量,具体操作格式如下:
Sht1xObjectTypesht1x;
声明了这个对象变量并不能立即使用,我们还需要使用驱动中定义的初始化函数对这个变量进行初始化。这个初始化函数所需要的输入参数如下:
Sht1xObjectType*sht,SHT1X对象变量
uint32_t sck,SCK时钟频率
float vdd,工作电压
SHT1xTempUnitTypeuint,温度单位
SHT1xHeaterTypeheater,是否启用加热器设置
SHT1xOTPType otp,是否加在OTP设置
SHT1xResolutionTyperesolution,测量分辨率设置
SHT1xSetBusPinsetSckPin,SCK引脚操作函数
SHT1xSetBusPinsetDataPin,DATA引脚操作函数
SHT1xReadSDABitreadSDA,读DATA引脚函数
SHT1xSDADirectiondirection,DATA引脚方向配置函数
SHT1xDelaydelayus,微秒延时函数
SHT1xDelaydelayms,毫秒延时函数
对于这些参数,对象变量我们已经定义了。时钟频率根据实际输入,以k为单位,默认为100k。工作电压根据实际情况输入。温度单位、加热设置、OTP配置、分辨率配置均为枚举,根据实际情况选择就好了。主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型如下:
/* 定义GPIO引脚输出操作的函数指针 */typedef void(*SHT1xSetBusPin)(SHT1xPinValueTypevalue);/* 读数据总线函数 */typedef uint8_t(*SHT1xReadSDABit)(void);/* 数据总线方向控制函数 */typedef void(*SHT1xSDADirection)(SHT1xIODirectionType direction);/* 微秒延时函数 */typedef void(*SHT1xDelay)(volatile uint32_t period);
对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。片选操作函数用于多设备需要软件操作时,如采用硬件片选可以传入NULL即可。具体函数定义如下:
/*操作SCK引脚,设置高低操作*/static voidOperationSckPin(SHT1xPinValueType value){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,(GPIO_PinState)value);} /*操作DATA引脚,设置高低操作*/static voidOperationDataPin(SHT1xPinValueType value){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,(GPIO_PinState)value);} /*读取DATA引脚位*/uint8_t ReadDataPinBit(void){
return (uint8_t)HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_9);} /*将DATA线设置为输入输出方向模式*/voidSetDataPineDirection(SHT1xIODirectionType direction){
GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_9; if(direction) { GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; } else { GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; } HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);}
对于延时函数我们可以采用各种方法实现。我们采用的STM32平台和HAL库则可以直接使用HAL_Delay()函数。于是我们可以调用初始化函数如下:
SHT1xInitialization(&sht1x,100,3.3,DegreeCentigrade,SHT1xHeaterDisable,SHT1xOTPEbable,SHT1xHighResolution,OperationSckPin,OperationDataPin,ReadDataPinBit,SetDataPineDirection,Delayus,HAL_Delay);
这里我们将SHT1x对象初始化为速度100k,3.3伏工作电压,采用摄氏温度单位,禁用片上加热器,加载OTP并使用高分辨率。
我们定义了对象变量并使用初始化函数给其作了初始化。接着我们就来考虑操作这一对象获取我们想要的数据。我们在驱动中已经将获取数据并转换为转换值的比例值,接下来我们使用这一驱动开发我们的应用实例。
这里我们设计一个简单应用,使用SHT1X温湿度传感器获取温度、湿度及露点数据,具体实现如下:
/* 获取SHT1X数据 */void GetSHT1xData(void){
float temperature=0.0; float humidity=0.0; float dewPoint=0.0; GetSht1xMeasureValue(&sht1x); temperature=sht1x.temperature; humidity=sht1x.humidity; dewPoint=sht1x.dewPoint;}
我们实现了SHT1X温湿度传感器的驱动,并使用这一驱动开发了简单的验证应用。所得到的结果与我们预期的结果是一致的,这说明我们的驱动开发没有问题。
在使用驱动程序时需要注意一点,对象有一个控制DATA总线引脚输入输出方向的操作。对于一般情况下我们编写引脚的输入输出方向控制函数,在初始化函数中将函数指针作为参数传入即可。如果硬件上可以配置为开漏输出,则可以不用单独控制引脚的输入输出方向。在初始化函数中以NULL作为参数输入。
关于通讯速率问题需要注意。在不同工作电压时所支持的最大通讯速率是不同的,但不论如何我都能支持到1MHz,所以没有特殊要求,电压的影响可以不用考虑。在我们的驱动中,最多能支持到500kHz,这主要是考虑到SHT1X的典型速度只有100k,而且大多数应用中不会有高速要求。
完整的源代码可在GitHub下载:https://github.com/foxclever/ExPeriphDriver