KEIL中编译51程序 算法计算异常的疑问
KEIL开发 51 单片机程序 算法处理过程中遇到的问题 ...... by 矜辰所致
前言
因为产品的更新换代, 把所有温湿度传感器都换成 SHT40 ,替换以前的 SHT21。在 STM32 系列产品上的替换都正常,但是在一块 51 内核的无线产品上面,数据莫名其妙的总会遇到异常的情况,弯弯绕绕了好一阵子,最后才发现是程序在执行一个不算复杂的算法的时候会出错。
那么本文的目的就是说明这个问题,以及如何解决这个问题,同时也想向大家请教这个问题出现的原因。 因为到最后,没有花时间去过多的研究到底是怎么出的问题。
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!
目录
- 前言
- 一、 SHT40 温湿度读取
- 二、51 上的数据异常
- 2.1 程序移植
- 2.2 问题分析
- 2.3 问题解决
- 结语
一、 SHT40 温湿度读取
本来 SHT40 其实特别简单, 简单的 I2C 通讯,简单的计算公式,在 SHT40 数据手册上面,只要看一个地方基本就能把 SHT40 用起来了,如下图:
硬件电路,和算法手册都给出来了,实际上在使用 STM32 的时候确实是真简单就可以正确的读取到数据,代码如下:
/*
SHT40
地址和 SHT30 一样 0x44
*/
Readthstruct SHT40_read_result(u8 addr)
{
u16 tem,hum;
u16 buff[6] = {0};
float Temperature=0;
float Humidity=0;
Readthstruct sensordata;
I2C_Start();
I2C_Send_Byte(addr<<1 | write);//写7位I2C设备地址加0作为写取位,1为读取位
I2C_Wait_Ack();
I2C_Send_Byte(0xFD); // SHT 40 只需要一个 0xFD
I2C_Wait_Ack();
// I2C_Send_Byte(0x06);
// I2C_Wait_Ack();
I2C_Stop();
HAL_Delay(20);
I2C_Start();
I2C_Send_Byte(addr<<1 | read);//写7位I2C设备地址加0作为写取位,1为读取位
if(I2C_Wait_Ack()==0)
{
buff[0]=I2C_Read_Byte(1);
buff[1]=I2C_Read_Byte(1);
buff[2]=I2C_Read_Byte(1);
buff[3]=I2C_Read_Byte(1);
buff[4]=I2C_Read_Byte(1);
buff[5]=I2C_Read_Byte(0);
I2C_Stop();
}
tem = ((buff[0]<<8) | buff[1]);//温度拼接
hum = ((buff[3]<<8) | buff[4]);//湿度拼接
/*转换实际温度*/
Temperature= (175.0*(float)tem/65535.0-45.0) ;// T = -45 + 175 * tem / (2^16-1)
/*
*humi = (1.0 * 125 * (readData[3] * 256 + readData[4])) / 65535.0 - 6.0;
rh_pRH = -6 + 125 * rh_ticks/65535
*/
Humidity= (125.0*(float)hum/65535.0-6.0);
// sprintf(humiture_buff1,"%6.2f*C %6.2f%%",Temperature,Humidity);//111.01*C 100.01%(保留2位小数)
// printf("温湿度:%s\n",humiture_buff1);
if((Temperature>=-20)&&(Temperature<=80)&&(Humidity>=0)&&(Humidity<=100))//过滤错误数据
{
sensordata.tem_100 = (u16)(Temperature*100);
sensordata.hum_100 = (u16)(Humidity*100);
}
else{
//重新通讯读取一遍
}
// printf("温度100倍:%d\n湿度100倍:%d\n",sensordata.tem_100,sensordata.hum_100);
hum=0;
tem=0;
if(sensordata.tem_100>4000) sensordata.tem_100=4000; //A5-04-01 0~40du
else if(sensordata.tem_100<0) sensordata.tem_100=0;
if(sensordata.hum_100>10000) sensordata.hum_100=10000; //prevent temperature over-/underflow
else if(sensordata.hum_100<0) sensordata.hum_100=0;
return sensordata;
}
上面的代码是为了得到 温湿度实际数据的100倍的结果, 上面时候因为测试,中途需要打印浮点数,所以中途按照浮点数计算了结果,
其实可以在Temperature= (175.0*(float)tem/65535.0-45.0)
这地方直接得到 100 倍的数值,
而且还可以省去浮点数的计算。
反正到这里一切都是正常的,于情于理挺简单的应用当然不会有问题。
二、51 上的数据异常
2.1 程序移植
在 STM32 上替换很顺利,那么还有一款 51 内核的无线芯片也需要替换,那么其实也就是做了简单的移植,通讯逻辑基本和上面一样:
void SHT40THMeasure()
{
sint16 tem,hum;
uint16 buff[6] = {0};
float Temperature=0;
float Humidity=0;
time_wait(20);
i2c_start();
u8Ack = i2c_write(0x94); //SHT40_SOFT_RESET
i2c_stop();
time_wait(10);
i2c_start();
u8Ack = i2c_write(0X44<<1); //I2C_Send_Byte(0x44<<1 | 0);
u8Ack = i2c_write(0xFD);
i2c_stop();
time_wait(20); //HAL_Delay(20);
i2c_start();
u8Ack = i2c_write((0X44<<1) + 1);
if(u8Ack==I2C_ACK){
buff[0]= i2c_read(I2C_ACK);
buff[1]= i2c_read(I2C_ACK);
buff[2]= i2c_read(I2C_ACK);
buff[3]= i2c_read(I2C_ACK);
buff[4]= i2c_read(I2C_ACK);
buff[5]= i2c_read(I2C_NACK);
i2c_stop();
}
tem = ((buff[0]<<8) | buff[1]);//温度拼接
hum = ((buff[3]<<8) | buff[4]);//湿度拼接
Temperature= (175.0*(float)tem/65535.0-45.0) ;// T = -45 + 175 * tem / (2^16-1)
Humidity= (125.0*(float)hum/65535.0-6.0);
if((Temperature>=-20)&&(Temperature<=80)&&(Humidity>=0)&&(Humidity<=100))//过滤错误数据
{
aTemperature.value = (u16)(Temperature*100);
aHumidity.value = (u16)(Humidity*100);
}
else
{
//数据出错,再来一遍
i2c_start();
u8Ack = i2c_write(0x94); //SHT40_SOFT_RESET
i2c_stop();
// 这里就省略了,就是重复一遍
}
if(aTemperature.value>4000) aTemperature.value=4000; //prevent temperature over-/underflow
else if(aTemperature.value<0) aTemperature.value=0;
if(aHumidity.value>10000) aHumidity.value=10000; //prevent temperature over-/underflow
else if(aHumidity.value<0) aHumidity.value=0;
}
上面代码其实也不复杂,因为本来就很简单。 但是在测试的时候,总是会出现温湿度数据异常的情况,比如湿度为0 ,温度最最大值等等 。
2.2 问题分析
在最开始的时候,连硬件问题,然后还有因为低功耗需要给传感器断电问题都统统想到过,都先给一一排除了。
最终还是回到程序上来,也尝试过校验,软件复位,多次读取等等方式,发现依然存在问题。
各种可能的不可能的问题估计都想过,最后考虑了一下以前是 32 位的 ARM 内核,现在是 8 位 51 内核,是不是算法有点问题,然后看了下代码,主要集中在算法那两行代码 :
Temperature= (175.0*(float)tem/65535.0-45.0) ;// T = -45 + 175 * tem / (2^16-1)
Humidity= (125.0*(float)hum/65535.0-6.0);
想着在 51 单片机上进行浮点数运算比较 “困难” 的,如果可以尽量减少浮点数的运算 。
所以为了防止因为浮点数计算导致的问题,直接把浮点数运算也去掉,因为以前 SHT21 在 51 上数据也是正常的,所以参考了一下 以前 SHT21 的算法书写:
sint16 sht21_calcRH(uint16 u16RH)
{
sint16 humidityRH; // variable for result
u16RH &= ~0x0003; // clear bits [1..0] (status bits)
//-- calculate relative humidity [%RH] --
humidityRH = (sint16)(-600 + (12500*(sint32)u16RH)/65536 ); // RH = -6 + 125 * SRH/2^16
return humidityRH; // Return RH*100
}
// -------------------------------------------------------------------
sint16 sht21_calcTemperature(uint16 u16T)
{
sint16 temperature; // variable for result
u16T &= ~0x0003; // clear bits [1..0] (status bits)
//-- calculate temperature [癈] --
temperature= (sint16)(-4685 + (17572*(sint32)u16T)/65536); //T = -46.85 + 175.72 * ST/2^16
return temperature; //return T*100
}
把算法变成如下:
tem = ((buff[0]<<8) | buff[1]);//温度拼接
hum = ((buff[3]<<8) | buff[4]);//湿度拼接
aTemperature.value = (sint16) (17500*(sint32)tem/65535 - 4500) ;
aHumidity.value = (sint16) (12500*(sint32)hum/65535 - 600);
...
上面已经没有了浮点数运算,数据类型也注意到了,感觉应该没什么问题。
但是实际测试下来,结果还是和以前一样。
期间还测试发现当湿度大于接近 40% 的时候,湿度 aHumidity.value 就会变成 0 。
为了排除不是传感器通讯的问题,期间还读取过 tem 和 hum 的值观察,然后自己通过算法计算都能得到准确的数据。
然后自己给一个合理的 tem hum 的值,如下图:
tem = ((buff[0]<<8) | buff[1]);//温度拼接
hum = ((buff[3]<<8) | buff[4]);//湿度拼接
tem = 0x 78;//写文章的测试例子,当时是多少来着忘记了
tem = 0x 78;
aTemperature.value = (sint16) (17500*(sint32)tem/65535 - 4500) ;
aHumidity.value = (sint16) (12500*(sint32)hum/65535 - 600);
...
确实发现只要 aHumidity.value
正确计算结果在接近或者大于 4000 的时候,这个算是结果在程序中就会变成 0 。
…
对比了一下算法的书写,是在看不出哪里会溢出什么的。
…
再反复看了以前 SHT21 的算法,明明也是这样的算法,怎么就会不行了呢?
…
…
2.3 问题解决
最后实在是觉得还是不应该出问题,但是明明以前 SHT21 也是在同样的环境,同样的平台下,就是这么写的算式,不应该啊。
最后才看来看去,再看了下以前 SHT21 怎么处理的:
在文章上面其实我也给出了 SHT21 的这两个计算结果的函数,在以前 I2C 通讯完毕,是通过调用了计算结果的函数得到的数值没有问题。
于是乎,我把 SHT40 也尝试封装成了同样的函数,算法直接复制进去,如下图:
然后忽然就发现…… 好了…… 数据怎么样都正常了……
这真的是沙比问题,我确实有点懵了,反正最后自己也不知道是什么根本原因。
网上也没有明确的此类问题的,到处查阅了一些资料,有的说是因为编译器的代码优化问题,或者说是因为 51 编译器自己的问题导致的。
结语
其实问题虽然是解决了,但是我还是不知道是因为什么原因,如果有小伙伴知道,还望多多指教,记得留言哦 。
当然出了这个问题,也给我们提了个醒,算法最好是封装成函数,虽然说,函数调用会有一定的开销,但是这样做不仅可读性和维护性强, 在重用,调试等方面也更有优势, 最重要的可以避免编译器对我们的程序进行不理想的优化导致的一些错误 。
那么本文就到这里,谢谢大家!