1、项目背景

      一款2年前开发的无线网络通信软件在最近的使用过程中出现网络中传感器离线的问题,此软件之前已经使用的几年了,基本功能还算稳定。这次为什么出了问题。

       先派工程师去现场调试一下,初步的结果是网络信号弱,并且有个别主干网络设备离线不工作。这次网络的中的传感器数量与比前比相差不多,但是传感器数据的上报间隔较短。

2、临时办法

     网络信号弱导致的通信时有中断,需要通过加装主干网络设备增加信号覆盖解决。同时调整传感器数据的上报间隔变长,网络中设备离线的情况不再出现,看来是数据量上报大有关了。

3、产品软件问题

     虽然说现场问题临时解决了,传感器都上线了。但是这里面还是说明网络设备的软件有问题,这个问题是只有在大数量传输时会出现。那么就在公司搭建网络环境,发送大数量测试,经过长达3周的测试,复现出现场的问题。网络设备会出现内部MCU硬件定时器不工作和内存分配异常导致的无线网络发送空数据包两种问题。

3.1 定时器不工作

      在某种特定的网络情况下,网络中的设备内部的MCU硬件定时器会被连续调用两次启动,正常的情况下是调用1次启动,1次停止。可能由于数据量大,调用停止的操作未成功,就出现了连续两次调用启动的情况。经过分析定时器的驱动代码和实际跟踪发生问题时的情况,当第二次调用定时器启动时,HAL_TIM_Base_Start(&htim2);函数是返回失败,跟踪代码后发现定时器内部的状态是工作状态是BUSY状态,返回错误,因为定时还在运行状态。

          分析下面的代码,发现当调用StartWork函数-》InitTimer函数,此函数会通过寄存器操作把定时器关闭,之后再调用HAL_TIM_Base_Start函数启动,由于寄存器操作把定时器并不修改定时器的状态变量,导致再次调用启动会失败,正确的操作逻辑基于对外设备定时器的操作应统一方式,不能API函数调用和定时器操作混合使用,会导致异常情况驱动内部的状态不正常。

/*==========================================================================
brief  : 硬件定时器初始化
param  : None
retval : None
//==========================================================================*/
void InitTimer(TimeIsUpCb_t callback)
{HTimer2Timing.Status = eSltTiming_Idle;HTimer2Timing.CurSltIdx = 0;HTimer2Timing.StartCnt = 0;HTimer2Timing.EndtCnt = myAttributes.TotalSltSize_us - myAttributes.SltSize_us - 1;HTimer2Timing.NextSltTCnt = 0;HTimer2Timing.CallBackWhenTimeIsUp = callback;寄存器操作停止了定时器,不能修改定时器内部的状态变量__HAL_TIM_DISABLE(&htim2); //停止timier2计数htim2.Instance->CNT = 0;htim2.Instance->ARR = myAttributes.TotalSltSize_us - 1;__HAL_TIM_CLEAR_IT(&htim2, (TIM_IT_UPDATE | TIM_IT_CC1));__HAL_TIM_ENABLE_IT(&htim2, (TIM_IT_UPDATE | TIM_IT_CC1));
}/*==========================================================================
brief  : 开始工作命令处理函数,模块开始广播BCH,等待设备接入
param  : puartpkt: 串口帧数据指针,包含串口handle(用于说明串口数据是从哪个串口来的)
retval : None
//==========================================================================*/
void InitEachBuf(void);
void StartWork(UartFrame_t* puartpkt)
{//计算一次发送时不同时隙与最大传输字节数据对应表CalMaxBytesInOnePktBySlt(myAttributes.LoRaCfg.ModulationIndex, (myAttributes.SltSize_us),(myAttributes.BCHInfo.GP_Dphy * 100));InitAllTheadPt();InitEachBuf();BCHFrame_Init();InitTimer(Send_DL_Frame);调用定时器通过API函数调用 HAL_TIM_Base_Start(&htim2);myAttributes.WorkStatus = eModuleWorkSt_InNet;
}/*==========================================================================
brief  : 停止工作命令处理函数
param  : puartpkt: 串口帧数据指针,包含串口handle(用于说明串口数据是从哪个串口来的)
retval : None
//==========================================================================*/
void StopWork(UartFrame_t* puartpkt)
{myAttributes.WorkStatus = eModuleWorkSt_Idle;InitRadio();HAL_TIM_Base_Stop(&htim2);InitEachBuf();DevSltList_Init();DevRegList_Init();DevDRxList_Init();
}

     找到问题,解决办法很简单了,重新整理定时器的调用逻辑,全部通过API接口来调用定时器启动和停止 ,这样连续两次调用启动定时器也没有问题。

3.2 内存分配异常

        内存分配异常很难解决,前前后后增加了很多调试代码,一步一步跟踪分析,确定是内存分配导致的异常。

    


typedef struct 
{uint8_t  PktNum : 7;uint8_t  FlagRewind : 1;uint16_t Size;uint16_t Head;uint16_t Tail;uint16_t DataTail;//有效的数据结尾uint8_t* DataArr;
}StreamBuffer;内存申请代码:
//申请一个保留空间给外部使用,不支持多线程、多次调用
uint8_t* ApplyMemByStreamBuffer(StreamBuffer *ptStreamBuffer, uint16_t size)
{uint8_t* Result = NULL;do{//如果申请空间大于空间大小则放弃if(size > ptStreamBuffer->Size)break;if(ptStreamBuffer->PktNum == 0){ptStreamBuffer->Head = 0;ptStreamBuffer->Tail = 0;ptStreamBuffer->DataTail = 0;ptStreamBuffer->FlagRewind = 0;}uint16_t newtail = ptStreamBuffer->Tail + size;if(newtail <= ptStreamBuffer->Size){if(ptStreamBuffer->FlagRewind){   //第2种情况,Tail超过Head产生数据覆盖,不能分配出内存if(newtail > ptStreamBuffer->Head)break;}Result = (ptStreamBuffer->DataArr + ptStreamBuffer->Tail);}else //尾部空间浪费一些,不用了,这样处理简单{ptStreamBuffer->Tail = 0;ptStreamBuffer->FlagRewind = 1;if(ptStreamBuffer->PktNum){   //第2种情况,Tail超过Head产生数据覆盖,不能分配出内存if((ptStreamBuffer->Tail + size) > ptStreamBuffer->Head)break;}//从缓存区头部开始使用Result = ptStreamBuffer->DataArr;}}while(0);return(Result);
}
存在的问题:当可用内存为10240长度时,每次申请10个字节长度时,可申请出1024块内存,使用PktNum 7位表不了这么多的内存,当申请的1024块时,PktNum只能到127,无法表达内部存储了多少块?释放内存时,只能释放127块。申请0长度时,能返回可以申请,需要处理一下。

     根据以上内存代码,做了初步的代码逻辑分析,发现一些问题,但实际测试是这些问题点全部未发生,做了分配的原理分析图:内存分配基本逻辑正常。

      后面经过多次测试,发现一个共同的现场,当内部出问题时,内存块分配出去的计数PktNum与内存中已存储的有效数据不一样多。如下图,计数中有8块分配,根据内存指针来看已经没有有效数据了。

       此种问题多次复现,并且出现时PktNum都是8,很有规律,当时内存中申请的数据块长度为1225字节,整个总内存的长度为10240长度,计算下来正好能存储8*1225,即8块。说明内存在用完了后发生第二申请覆盖,所以只有计数增加了。

       跟踪内存分析的数据长度,发现如果内存中先分配8块1225长度块,再分配1个小块147长度,最后释放掉8块1225长度的块,再申请8块1225长度的块后,内存就满了,再申请1块1225长度的内存,内存出现的反转,开始占用已经申请出去的内存。解决办法,不允许此种情况下再分配出新的内存了。

uint8_t* ApplyMemByStreamBuffer(StreamBuffer *ptStreamBuffer, uint16_t size)
{uint8_t* Result = NULL;do{//如果申请空间大于空间大小则放弃/* 如果申请的空间是0或是内部存储的PktNum已经最大,申请的数量已超总内存大小,返回失败 zhaoshimin 20250429*/if((size > ptStreamBuffer->Size) || (size == 0) || (STREAM_BUFFER_PKTNUM_MAX == ptStreamBuffer->PktNum)){break;}    if(ptStreamBuffer->PktNum == 0){ptStreamBuffer->Head = 0;ptStreamBuffer->Tail = 0;ptStreamBuffer->DataTail = 0;ptStreamBuffer->FlagRewind = 0;}uint16_t newtail = ptStreamBuffer->Tail + size;if(newtail <= ptStreamBuffer->Size){if(ptStreamBuffer->FlagRewind){if(newtail > ptStreamBuffer->Head)break;}Result = (ptStreamBuffer->DataArr + ptStreamBuffer->Tail);}else //尾部空间浪费一些,不用了,这样处理简单{/*当内存中先放入1225的8包数据再放入147长度的包后,
释放掉8包数据后,再申请放入1225长度的8包数据后,内存满了,
发生第二次产生了反转  20250429*/if(ptStreamBuffer->FlagRewind){break;}else{ptStreamBuffer->Tail = 0;ptStreamBuffer->FlagRewind = 1;if(ptStreamBuffer->PktNum){if((ptStreamBuffer->Tail + size) > ptStreamBuffer->Head)break;}}   Result = ptStreamBuffer->DataArr;}}while(0);return(Result);
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React 第三十四节 Router 开发中 useLocation Hook 的用法以及案例详解

一、useLocation基础用法 作用&#xff1a;获取当前路由的 location 对象 返回对象结构&#xff1a; {pathname: "/about", // 当前路径search: "?namejohn", // 查询参数&#xff08;URL参数&#xff09;hash: "#contact", …

DeepSeek-Prover-V2-671B最新体验地址:Prover版仅适合解决专业数学证明问题

DeepSeek-Prover-V2-671B最新体验地址&#xff1a;Prover版仅适合解决专业数学证明问题 DeepSeek 团队于 2025 年 4 月 30 日正式在Hugging Face开源了其重量级新作 —— DeepSeek-Prover-V2-671B&#xff0c;这是一款专为解决数学定理证明和形式化推理任务而设计的超大规模语…

tornado_登录页面(案例)

目录 1.基础知识​编辑 2.脚手架&#xff08;模版&#xff09; 3.登录流程图&#xff08;processon&#xff09; 4.登录表单 4.1后&#xff08;返回值&#xff09;任何值&#xff1a;username/password &#xff08;4.1.1&#xff09;app.py &#xff08;4.1.2&#xff…

Android学习总结之自定义view设计模式理解

面试题 1&#xff1a;请举例说明自定义 View 中模板方法模式的应用 考点分析 此问题主要考查对模板方法模式的理解&#xff0c;以及该模式在 Android 自定义 View 生命周期方法里的实际运用。 回答内容 模板方法模式定义了一个操作的算法骨架&#xff0c;把一些步骤的实现延…

【Scrapy】简单项目实战--爬取dangdang图书信息

目录 一、基本步骤 1、新建项目 &#xff1a;新建一个新的爬虫项目 2、明确目标 &#xff08;items.py&#xff09;&#xff1a;明确你想要抓取的目标 3、制作爬虫 &#xff08;spiders/xxspider.py&#xff09;&#xff1a;制作爬虫开始爬取网页 4、存储内容 &#xff08;p…

开源CMS系统的SEO优化功能主要依赖哪些插件?

在当今互联网时代&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;是网站获取流量的核心手段之一。开源内容管理系统&#xff08;CMS&#xff09;因其灵活性和丰富的插件生态&#xff0c;成为许多开发者和企业的首选。本文将以主流开源CMS为例&#xff0c;深入解析其SEO优…

在 JMeter 中使用 BeanShell 获取 HTTP 请求体中的 JSON 数据

在 JMeter 中&#xff0c;您可以使用 BeanShell 处理器来获取 HTTP 请求体中的 JSON 数据。以下是几种方法&#xff1a; 方法一&#xff1a;使用前置处理器获取请求体 如果您需要在发送请求前访问请求体&#xff1a; 添加一个 BeanShell PreProcessor 到您的 HTTP 请求采样器…

在 WSL (Windows Subsystem for Linux) 中配置和安装 Linux 环境

在 WSL (Windows Subsystem for Linux) 中配置和安装 Linux 环境 WSL 允许你在 Windows 上运行 Linux 环境&#xff0c;以下是详细的配置和安装指南。 1. 安装前的准备工作 系统要求 Windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)或 Windows 11 64 位系统 虚…

AlphaFold蛋白质结构数据库介绍

AlphaFold Protein Structure Database (AlphaFold DB) 是 DeepMind + EMBL-EBI 合作开发的公开蛋白质结构预测数据库,是利用 AlphaFold2/AlphaFold3 AI模型 预测的全基因组级蛋白质三维结构库。 网址: https://alphafold.ebi.ac.uk 项目内容主办单位DeepMind + EMBL-EBI上线…

3.2goweb框架GORM

GORM 是 Go 语言中功能强大的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;支持 MySQL、PostgreSQL、SQLite、SQL Server 等主流数据库。以下是 GORM 的核心概念和用法详解&#xff1a; ​​一、基础入门​​ 1. 安装 go get -u gorm.io/gorm go get -u gorm.io…

第三部分:特征提取与目标检测

像边缘、角点、特定的纹理模式等都是图像的特征。提取这些特征是许多计算机视觉任务的关键第一步&#xff0c;例如图像匹配、对象识别、图像拼接等。目标检测则是在图像中找到特定对象&#xff08;如人脸、汽车等&#xff09;的位置。 本部分将涵盖以下关键主题&#xff1a; …

Canvas基础篇:图形绘制

Canvas基础篇&#xff1a;图形绘制 图形绘制moveTo()lineTo()lineTo绘制一条直线代码示例效果预览 lineTo绘制平行线代码示例效果预览 lineTo绘制矩形代码示例效果预览 arc()arc绘制一个圆代码实现效果预览 arc绘制一段弧代码实现效果预览 arcTo()rect()曲线 结语 图形绘制 在…

瑞芯微芯片算法开发初步实践

文章目录 一、算法开发的一般步骤1.选择合适的深度学习框架2.对于要处理的问题进行分类&#xff0c;是回归问题还是分类问题。3.对数据进行归纳和整理4.对输入的数据进行归一化和量化&#xff0c;保证模型运行的效率和提高模型运行的准确度5.在嵌入式处理器上面运行模型&#x…

计算机毕业设计--基于深度学习(U-Net与多尺度ViT)的模糊车牌图像清晰化复原算法设计与实现(含Github代码+Web端在线体验链接)

基于深度学习的U-Net架构下多尺度Transformer车牌图像去模糊算法设计与实现 如果想对旧照片进行模糊去除&#xff0c;划痕修复、清晰化&#xff0c;请参考这篇CSDN作品&#x1f447; 计算机毕业设计–基于深度学习的图像修复&#xff08;清晰化划痕修复色彩增强&#xff09;算…

(Go Gin)Gin学习笔记(四)Gin的数据渲染和中间件的使用:数据渲染、返回JSON、浅.JSON()源码、中间件、Next()方法

1. 数据渲染 1.1 各种数据格式的响应 json、结构体、XML、YAML类似于java的properties、ProtoBuf 1.1.1 返回JSON package mainimport ("github.com/gin-gonic/gin""net/http" )func main() {r : gin.Default()r.POST("/demo", func(res *gi…

实验:串口通信

/************************************************* * AT89C52 串口通信实验&#xff08;实用修正版&#xff09; * 特点&#xff1a; * 1. 解决所有编译警告 * 2. 保持代码简洁 * 3. 完全功能正常 ************************************************/ #include <re…

智驾赛道的诺曼底登陆,Momenta上海车展雄起

作者 |芦苇 编辑 |德新 今年的上海车展依旧热闹非凡&#xff0c;但火热的车市背后也是暗流涌动。尤其对智驾供应商而言&#xff0c;「智驾平权」带动了解决方案大量上车&#xff0c;各大主机厂纷纷选定各自的主要供应商&#xff0c;这也意味着赛道机会越发收敛。 正如汽车品牌…

Java 事务详解

目录 一、事务的基本概念1.1 什么是事务?1.2 事务的 ACID 特性二、Java 事务管理的实现方式2.1 JDBC 事务管理2.2 Spring 事务管理2.2.1 添加 Spring 依赖2.2.2 配置 Spring 事务管理2.2.3 使用 Spring 事务注解三、事务隔离级别四、最佳实践4.1 尽量缩小事务范围4.2 合理选择…

DirectX12(D3D12)基础教程七 深度模板视图\剔除\谓词

本章主要讲遮挡&#xff0c;作者认为比较复杂有难度的知识点&#xff0c;作为基础教程不会深入讲解。 GPU渲染管线 主要包括以下阶段 输入装配&#xff08;IA&#xff09;&#xff1a;读取顶点数据 &#xff0c;定义顶点数据结构顶点着色&#xff08;VS&#xff09;&#xf…

温补晶振(TCXO)稳定性优化:从实验室到量产的关键技术

在现代通信、航空航天、5G基站等对频率稳定性要求极高的领域&#xff0c;温补晶振&#xff08;TCXO&#xff09;扮演着不可或缺的角色。其稳定性直接影响系统的性能与可靠性&#xff0c;因此&#xff0c;对TCXO稳定性优化技术的研究与实践至关重要。 一、温度补偿算法&#xff…