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 编译器自己的问题导致的。

结语

其实问题虽然是解决了,但是我还是不知道是因为什么原因,如果有小伙伴知道,还望多多指教,记得留言哦 。

当然出了这个问题,也给我们提了个醒,算法最好是封装成函数,虽然说,函数调用会有一定的开销,但是这样做不仅可读性和维护性强, 在重用,调试等方面也更有优势, 最重要的可以避免编译器对我们的程序进行不理想的优化导致的一些错误 。

那么本文就到这里,谢谢大家!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32-HAL库开发快速入门

注:本文主要记录一下STM32CubeMX软件的使用流程,记录内容以STM32外设&#xff08;中断、I2C、USART、SPI等配置&#xff09;在STM32CubeMX中的设置为主&#xff0c;对驱动代码编写不做记录&#xff0c;所以阅读本文最好有标准库开发经验。除第2节外&#xff0c;使用的都是韦东山…

C++的流提取(>>)(输入) 流插入(<<)(输出)

什么是输入和输出流 流提取&#xff08;<<&#xff09;(输入) 理解&#xff1a;我们可以理解为&#xff0c;输入到io流里面&#xff0c;比如是cin&#xff0c;然后从输入流中读取数据 流插入&#xff08;<<&#xff09;&#xff08;输出&#xff09; 理解&#xff…

网络协议头分析

目录 数据的传输与封装过程 以太网完整帧 以太网头部 IP头 TCP头 数据的传输与封装过程 以太网完整帧 ● 对于网络层最大数据帧长度是1500字节 ● 对于链路层最大数据长度是1518字节&#xff08;150014CRC&#xff09;● 发送时候&#xff0c;IP层协议栈程序检测到发送数…

前端 + 接口请求实现 vue 动态路由

前端 接口请求实现 vue 动态路由 在 Vue 应用中&#xff0c;通过前端结合后端接口请求来实现动态路由是一种常见且有效的权限控制方案。这种方法允许前端根据用户的角色和权限&#xff0c;动态生成和加载路由&#xff0c;而不是在应用启动时就固定所有的路由配置。 实现原理…

路由器的固定ip地址是啥意思?固定ip地址有什么好处

‌在当今数字化时代&#xff0c;‌路由器作为连接互联网的重要设备&#xff0c;‌扮演着举足轻重的角色。‌其中&#xff0c;‌路由器的固定IP地址是一个常被提及但可能让人困惑的概念。‌下面跟着虎观代理小二一起将深入探讨路由器的固定IP地址的含义&#xff0c;‌揭示其背后…

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:下(人物动画)

上个博客我们做出了人物的动画机和人物移动跳跃&#xff0c;接下来我们要做出人物展现出来的动画了 我们接下来就要用到动画机了&#xff0c;双击我们的动画机&#xff0c;进入到这样的页面&#xff0c;我这是已经做好的页面&#xff0c;你们是没有这些箭头的 依次像我一样连接…

【Python】Windows下python的下载安装及使用

文章目录 下载安装检测 使用环境搭建下载PycharmPycharm安装 下载 进入官网下载&#xff1a;https://www.python.org/ 点击下载 64位电脑下载该项 安装 勾选 添加至环境变量 使用自定义安装 检测 安装成功后&#xff0c;打开命令提示符窗口&#xff08;winR,输入cmd回车…

红海云 × 紫光同芯 | 数字化驱动芯片领军企业人力资源管理新升级

紫光同芯微电子有限公司&#xff08;以下简称“紫光同芯”&#xff09;是新紫光集团汽车电子与智能芯片板块的核心企业。专注于汽车电子与安全芯片领域&#xff0c;累计出货超过230亿颗&#xff0c;为亚洲、欧洲、美洲、非洲的二十多个国家和地区提供产品和服务。 为进一步提升…

VSC++: 十转十六进制

void 十转十六进制(int 数) {//缘由https://ask.csdn.net/questions/1089023string 十六模 "0123456789ABCDEF", 进制 "";int j 0;cout << 数 << ends; if (!数)cout << "0";while (数)进制.push_back(十六模[数 % 16]), j…

LCS—最长公共子序列

最长公共子序列问题就是求出两个字符串的LCS长度&#xff0c;是一道非常经典的面试题目&#xff0c;因为它的解法是典型的二维动态规划。 比如输入 str1 "babcde", str2 "acbe"&#xff0c;算法应该输出3&#xff0c;因为 str1 和 str2 的最长公共子序列…

【大模型基础】P2 Bag-of-Words

目录 词袋模型 概述词袋模型 实例第1步 构建语料库第2步 对句子进行分词第3步 创建词汇表第4步 转换词袋表示第5步 计算余弦相似度 词袋模型的局限性 词袋模型 概述 词袋模型&#xff0c;Bag-of-Words&#xff0c;是一种简单的文本表示方法&#xff0c;也是 NLP 中的一个经典模…

[数据集][目标检测]血细胞检测数据集VOC+YOLO格式2757张4类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2757 标注数量(xml文件个数)&#xff1a;2757 标注数量(txt文件个数)&#xff1a;2757 标注…

因MathType导致word复制粘贴失败,显示:运行时错误‘53’

问题&#xff1a;运行时错误‘53’&#xff1a;文件未找到&#xff1a;MathPage.WLL 解决方法&#xff1a;打开MathType所在文件夹 右击MathType图标->点击“打开文件所在位置”->找到MathPage.WLL文件。 然后&#xff0c;把这个文件复制到该目录下&#xff1a;C:\Progr…

jenkins工具的介绍和gitlab安装

使用方式 替代手动&#xff0c;自动化拉取、集成、构建、测试&#xff1b;是CI/CD持续集成、持续部署主流开发模式中重要工具&#xff1b;必须组件 jenkins-gitlab&#xff0c;代码公共仓库服务器&#xff08;至少6G内存&#xff09;&#xff1b;jenkins-server&#xff0c;需…

论文解读:利用大模型进行基于上下文的OCR校正

论文地址&#xff1a;https://arxiv.org/pdf/2408.17428 背景概述 研究问题&#xff1a;这篇文章要解决的问题是如何利用预训练的语言模型&#xff08;LMs&#xff09;来改进光学字符识别&#xff08;OCR&#xff09;的质量&#xff0c;特别是针对报纸和期刊等复杂布局的文档。…

Jmeter_循环获取请求接口的字段,并写入文件

通过JSON提取器、计数器、beanshell&#xff0c;循环读取邮箱接口的返回字段&#xff0c;筛选出flag为3的收件人&#xff0c;并写入csv文件。 1、调用接口&#xff0c;获取所有的邮件$.data.total.count&#xff1b; 2、beanshell后置处理total转换成页码&#xff0c;这里是227…

纵切车床和走心机的区别

纵切车床和走心机在机床加工领域中各自扮演着重要的角色&#xff0c;它们在多个方面存在显著的差异。下面&#xff0c;我将从基本概念、工作原理、应用领域以及加工能力等方面来详细阐述这两者的区别。 一.基本概念 ‌纵切车床‌&#xff1a;纵切车床&#xff0c;也被称为自动纵…

NFTScan | 09.02~09.08 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2024.09.02~ 2024.09.08 NFT Hot News 01/ 数据&#xff1a;NFT 8 月销售额跌破 4 亿美元&#xff0c;创年内新低 9 月 2 日&#xff0c;数据显示&#xff0c;8 月 NFT 的月销售额仅为 …

ozon免费选品工具,OZON免费选品神器

在跨境电商的浩瀚海洋中&#xff0c;寻找那片属于自己的盈利蓝海&#xff0c;是每个商家梦寐以求的目标。随着俄罗斯电商市场的迅速崛起&#xff0c;Ozon平台以其庞大的用户基数和不断增长的市场份额&#xff0c;成为了众多跨境卖家眼中的“香饽饽”。然而&#xff0c;面对琳琅…

vue-router + el-menu

1. el-menu的router属性 在el-menu中有一属性&#xff1a;router&#xff0c;默认是false 1.1 使用默认配置&#xff0c;即false 这时候需要自己在点击子菜单的时候进行导航&#xff0c;在el-menu添加方法&#xff0c;里边有三个参数 index: 选中菜单项的 index,indexPath…