汽车电子笔记之-014:一场FIFO的思考引发将汽车电子DTC相关 - 故障发生前后关键数据记录并回读的功能浅研发

目录

1、概述

2、故障发生前数据记录

2.1、环形数组C语言实现

2.2、FIFO的C语言实现

3、故障发生后数据记录

4、数据存储

4.1、数据进FIFO设计思路

4.2、数据出FIFO设计思路

5、数据回读


1、概述

        工作中DTC的冻结帧与扩展数据功能一般用于存储故障发生时刻的一些关键数据,例如电压、电流、传感器信息等。快照只能存储单点信息,也就是故障发生时刻一组信息,但是故障发生前应该有一定趋势,并不是突变的,分析故障发生前例如100组数据能够较好的定位问题原因,那么怎么去记录并回放呢,网上查了一下,不知道是不是自己不会查,没查到。

        专利网站翻翻很多,关键词搜索:故障发生前。

        实现思路:下面章节会逐步分解步骤

        将故障发生前后的数据记录,存储到NVM中并通过CAN回读出来。

2、故障发生前数据记录

        故障发生前数据记录进一个数组里面,此处假设记录100个数据,当数组100记录满之后,需要实现数据的有效排序,也就是第101个数据填充进100,那么原本的100应该挪到99,99挪到98以此类推第一个数据需要舍弃,以此实现数据的有效排列,怎么实现呢?首先想到的额是环形数组与FIFO,两种方法的C语言代码如下:

2.1、环形数组C语言实现

        直接上代码-发车!代码可以直接使用。

       优点:非常好理解,并且省内存空间

        缺点:buff满之后,每次都要for循环数组深度次数进行数据排序,时间花费巨大,对于类似DCDC或者电机控制器这种,有主中断执行时间长的零部件不适用(本次不选用)。

#define MAX_DATA_POINTS     30    /*数组深度*/
#define BUFFREADENABLE      0     /*测试使能开关*/typedef struct
{uint16 R1x[MAX_DATA_POINTS];uint16 R2x[MAX_DATA_POINTS];uint16 R3x[MAX_DATA_POINTS];uint16 R4x[MAX_DATA_POINTS];// 其他参数...uint16 count; // 当前数据个数
} FreezeFrameFlowData;void InitCirBuffer(FreezeFrameFlowData* FlowData)
{FlowData->count = 0;
}#if BUFFREADENABLE
uint16 R1xRead[MAX_DATA_POINTS] = {0,0};
uint16 R2xRead[MAX_DATA_POINTS] = {0,0};
uint16 R3xRead[MAX_DATA_POINTS] = {0,0};
uint16 R4xRead[MAX_DATA_POINTS] = {0,0};
#endif
boolean writeBuffer(FreezeFrameFlowData* FlowData, uint16 data1, uint16 data2,uint16 data3, uint16 data4){if (FlowData->count < MAX_DATA_POINTS){// 如果缓存区未满,直接写入FlowData->R1x[FlowData->count] = data1;FlowData->R2x[FlowData->count] = data2;FlowData->R3x[FlowData->count] = data3;FlowData->R4x[FlowData->count] = data4;
#if BUFFREADENABLER1xRead[FlowData->count] =  data1;R2xRead[FlowData->count] =  data2;R3xRead[FlowData->count] =  data3;R4xRead[FlowData->count] =  data4;
#endifFlowData->count++;}else{// 缓存区已满,依次向后移动数据for (int i = 1; i < MAX_DATA_POINTS; i++){FlowData->R1x[i - 1] = FlowData->R1x[i];FlowData->R2x[i - 1] = FlowData->R2x[i];FlowData->R3x[i - 1] = FlowData->R3x[i];FlowData->R4x[i - 1] = FlowData->R4x[i];
#if BUFFREADENABLER1xRead[i - 1] = R1xRead[i];`R2xRead[i - 1] = R2xRead[i];R3xRead[i - 1] = R3xRead[i];R4xRead[i - 1] = R4xRead[i];
#endif}// 将新数据写入最后一个位置FlowData->R1x[MAX_DATA_POINTS - 1] = data1;FlowData->R2x[MAX_DATA_POINTS - 1] = data2;FlowData->R3x[MAX_DATA_POINTS - 1] = data3;FlowData->R4x[MAX_DATA_POINTS - 1] = data4;
#if BUFFREADENABLER1xRead[MAX_DATA_POINTS - 1] =data1;R2xRead[MAX_DATA_POINTS - 1] =data2;R3xRead[MAX_DATA_POINTS - 1] =data3;R4xRead[MAX_DATA_POINTS - 1] =data4;
#endif}return 1;
}/*注意初始化的时候 FreezeFrameFlowData FreeDataLocal; InitCirBuffer(&FreeDataLocal);定义了之后是个取地址符号*/

2.2、FIFO的C语言实现

        直接上代码-发车!代码可以直接使用。

       优点:调用入队列出队就可以,节省时间,可以异步操作(本次选用)。

       缺点:内存开销大。

#define FIFO_MAX_DEPTH 			5   // FIFO深度typedef struct
{uint16    data[FIFO_MAX_DEPTH];sint16    redIndex;      // 读索引(读地址)sint16    writeIndex;    // 写索引(写地址)
} FIFO_Queue;void Fifo_Init (FIFO_Queue *queue)
{queue->redIndex 	= -1;queue->writeIndex 	= -1;
}
/*写索引的下一个位置是读索引,说明FIFO已满,实际也就是判断写索引的下一个位置是否等于FIFO深度*/
int isFull(FIFO_Queue *queue)
{return (queue->writeIndex + 1) % FIFO_MAX_DEPTH == queue->redIndex;// 也可以写成如下形式// return queue->writeIndex + 1 == FIFO_MAX_DEPTH;
}
/*判断写索引是否为-1*/
int isEmpty(FIFO_Queue *queue)
{return queue->writeIndex == -1;
}
/*元素入队*/
void enqueue(FIFO_Queue *queue, uint16 element)
{// 判断FIFO是否已满if (isFull(queue)){// 提示FIFO已满return;}// 判断FIFO是否为空if (isEmpty(queue)){// 第一次存储数据需要更新一下读索引queue->redIndex 				= 0;}// 存储数据,更新写索引queue->writeIndex 					= (queue->writeIndex + 1) % FIFO_MAX_DEPTH;queue->data[queue->writeIndex] 		= element;}
/*元素出队*/
uint16 dequeue(FIFO_Queue *queue)
{// 判断FIFO是否为空uint16 ReturnValue = 0;if (isEmpty(queue)){// 提示FIFO为空return 0xFF;}// 读出数据ReturnValue				= queue->data[queue->redIndex];// 判断是否读到最后一个数据if (queue->redIndex == queue->writeIndex){// 全部读取结束,重新初始化queue->redIndex 	= -1;queue->writeIndex 	= -1;}else{queue->redIndex 	= (queue->redIndex + 1) % FIFO_MAX_DEPTH;}return ReturnValue;
}
/*测试代码如下,下面是伪代码,不要字节调用*/
FIFO_Queue queue;
volatile uint32 _100msRuning  				= 0;
volatile uint16 RunningCountw 				= 0;
uint8    Flagsss 							= 0;
uint16	 ReturnFiFoBuff[FIFO_MAX_DEPTH]		= {0,0};
void main(void)
{Fifo_Init(&queue);while(1){_100msRuning++;int i = 0;if((_100msRuning % 4) == 0){RunningCountw++;if(Flagsss == 0){enqueue(&queue,RunningCountw);if (isFull(&queue))/*防止FIFO满,满了丢弃最先进入的那个*/{ReturnFiFoValue = dequeue(&queue);}}if(Flagsss == 1){for(i = 1; i < FIFO_MAX_DEPTH; i++){ReturnFiFoBuff[i] = dequeue(&queue);}ReturnFiFoBuff[0] = ReturnFiFoValue;Flagsss = 2;}}
}

       下面是FIFO里面好好多个数据的方式:

        上述代码结构体里面数据只有一个,有时候需要实现多个,例如结构体是这样的

typedef struct
{uint16    data1[FIFO_MAX_DEPTH];uint16    data2[FIFO_MAX_DEPTH];uint16    data3[FIFO_MAX_DEPTH];uint16    data4[FIFO_MAX_DEPTH];uint16    data5[FIFO_MAX_DEPTH];uint16    data6[FIFO_MAX_DEPTH];uint16    data7[FIFO_MAX_DEPTH];uint16    data8[FIFO_MAX_DEPTH];sint16    redIndex;      // 读索引(读地址)sint16    writeIndex;    // 写索引(写地址)
} FIFO_Queue;

        那么函数就要变如下,(TIP:当然也可以多次调用入队与出队,但是耗时间,毕竟代码执行花费不少时间的)

/*元素入队*/
void enqueue
(FIFO_Queue *queue,uint16 element1,uint16 element2,uint16 element3,uint16 element4,uint16 element5,uint16 element6,uint16 element7,uint16 element8
)
{// 判断FIFO是否已满if (isFull(queue)){// 提示FIFO已满return;}// 判断FIFO是否为空if (isEmpty(queue)){// 第一次存储数据需要更新一下读索引queue->redIndex 				= 0;}// 存储数据,更新写索引queue->writeIndex 					= (queue->writeIndex + 1) % FIFO_MAX_DEPTH;queue->data1[queue->writeIndex] 		= element1;queue->data2[queue->writeIndex] 		= element2;queue->data3[queue->writeIndex] 		= element3;queue->data4[queue->writeIndex] 		= element4;queue->data5[queue->writeIndex] 		= element5;queue->data6[queue->writeIndex] 		= element6;queue->data7[queue->writeIndex] 		= element7;queue->data8[queue->writeIndex] 		= element8;
}
/*元素出队,注意出队的数据只能用指针(uint16 *element1),不能是(uint16 element1)程序认为赋值不对*/
uint16 dequeue(FIFO_Queue *queue, uint16 *element1)
{// 判断FIFO是否为空if (isEmpty(queue)){// 提示FIFO为空element1[0] = 0xFFFF;element1[1] = 0xFFFF;element1[2] = 0xFFFF;element1[3] = 0xFFFF;element1[4] = 0xFFFF;element1[5] = 0xFFFF;element1[6] = 0xFFFF;element1[7] = 0xFFFF;return 0;}// 读出数据element1[0]				= queue->data1[queue->redIndex];element1[1]				= queue->data2[queue->redIndex];element1[2]				= queue->data3[queue->redIndex];element1[3]				= queue->data4[queue->redIndex];element1[4]				= queue->data5[queue->redIndex];element1[5]				= queue->data6[queue->redIndex];element1[6]				= queue->data7[queue->redIndex];element1[7]				= queue->data8[queue->redIndex];// 判断是否读到最后一个数据if (queue->redIndex == queue->writeIndex){// 全部读取结束,重新初始化queue->redIndex 	= -1;queue->writeIndex 	= -1;}else{queue->redIndex 	= (queue->redIndex + 1) % FIFO_MAX_DEPTH;}return 1;
}

3、故障发生后数据记录

        故障发生后的记录,直接在一个buff里面赋值就好,假设记录20个故障发生后数据,简单代码如下

uint16 buffer[20] = {0,0};
int i = 0;
for(i = 0; i < 20; i++)
{buffer[i] = data;
}

4、数据存储

        数据存储之前需要进行数据的处理,什么时候填充FIFO,什么时候停止记录等等,下面是一种策略。

4.1、数据进FIFO设计思路

        故障在老化之前只记录一次,也就是假设老化次数为20,那么老化前只记录第一次,为了节省程序执行时间,假设有历史故障不再执行FIFO记录,假设故障尚未发生过,FIFO一直记录数据,一旦故障发生,停止FIFO记录并开始记录故障发生后的数据(故障发生后记录不再使用FIFI,而是一个buffer,上文程序有体现),假设记录30个数据之后,停止记录并置位数据记录完成标志位,等待数据读取。

        进FIFO队列流程图如下:

4.2、数据出FIFO设计思路

        出队列时,是在入队完成之后才启动的,此时故障标志位要维持住,也没有出队列过(防止队列为空的现象)才进行for循环读取FIFO数据,只读取一次,读取完成后存储到NVM里面,NVM下次上电Readall的数据,故障数据就在了。

        进FIFO队列流程图如下:

5、数据回读

        数据回读比较简单了,

        第一种:

        NVM的的RAM数据在上电的时候已经Readall了,将NVM的RAM地址给到CAN就行了,不过要设置一个触发发送的启动才行,否则你也不知道那个是最先发生的数据。

        第二种: 

        设置一个触发标志位,用XCP的10ms去读取,一旦触发标志位置位,按照10ms将NVM Readall出来对应Block 的RAM从首地址一个一个赋值给某个全局变量进行读取即可。

        存在哪里不重要的,可以是EEPROM、PF、DF、做好存储逻辑就好,例如故障清除再次存储的时候,上电要找到,另外多个DTC存储也要规划好。

记录点:进FIFO可以放置到任何任务里面,出FIFO可以放置到其他任务里面,设计上进FIFO执行时间非常快都行,一旦故障发生了,将数据锁起来,异步出FIFO函数慢慢存储,这样可以较小的影响MCU性能。

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

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

相关文章

Unity Apple Vision Pro 保姆级开发教程 - Simulator 模拟器使用

教程视频 Apple VisionPro Simulator 模拟器使用教程 Unity Vision Pro 中文课堂教程地址&#xff1a; Unity3D Vision Pro 开发教程【保姆级】 | Unity 中文课堂 ​ VsionOS Simulator 简介 visionOS Simulator 是一个用于开发和测试 visionOS 应用程序的工具。它模拟 Appl…

数仓模型规范设计

模型架构设计 数仓架构一般从宏观上分为三层&#xff1a;操作数据层ODS、公共维度模型层CDM和数据应用层ADS。其中CDM又包含明细数据层DWD、汇总数据层DWS&#xff0c;维度层DIM、根据生产经验这里可在加入数据临时层TMP。架构图如下&#xff1a; ODS 把操作系统的数据几乎无…

高中数学:立体几何-外接球的外心法

文章目录 一、外心法定义二、习题1、例题一2、例题二3、例题三4、例题四 一、外心法定义 依然以三棱锥为例 即&#xff0c;找到三棱锥的外接球的球心&#xff0c;从而可以确定出外接球的半径R。 而三棱锥有四个顶点&#xff0c;这四个顶点必然都在外接球的球面上。 寻找思路…

海蓝色主题移动端后台UI作品集模板源文件分享 figmasketch格式

页面数量&#xff1a;30页 页面尺寸&#xff1a;1920*1080px 发给你的文件&#xff1a;作品集Figma源文件、作品集sketch源文件、部字体文件、高质量作品集包装psd样机文件&#xff08;含手机和电脑样机&#xff09;

设计模式概览

设计模式是一种解决常见编程问题的经验总结&#xff0c;提供了代码的可重用性、可扩展性和可维护性。常见的设计模式有23个&#xff0c;主要分为三大类&#xff1a;创建型模式、结构型模式和行为型模式。下面是这三类设计模式的详细分类和讲解&#xff1a; 一、创建型模式 创建…

linux多窗口调试一些常用命令

在 vim 或 neovim 中使用分屏移动光标的方式&#xff1a; 希望光标从左窗口移动到右侧窗口&#xff1a; 按 Ctrlw 然后按 l&#xff08;小写的 L&#xff09;&#xff0c;光标就会从左边窗口移动到右边窗口。 其它分屏操作&#xff1a; Ctrlw h&#xff1a;移动到左边的窗…

【我的 RT 学习手札】信息收集

相关笔记整理自B站up主泷羽sec全栈渗透测试教学&#xff08;免费&#xff09; 视频链接为泷羽sec的个人空间-泷羽sec个人主页-哔哩哔哩视频 笔记只是方便师傅学习知识&#xff0c;以下网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0…

11 图书借阅功能实现(Vue3+element plus +Spring Boot)

目录 1 功能描述2 接口地址3 后端代码4 api/book.js中编写借阅图书的接口代码5 BookResourcesVue.vue组件中完成点击事件borrowBook6 功能演示 1 功能描述 普通用户借阅图书&#xff0c;点击借阅按钮&#xff0c;修改图书状态&#xff0c;最多能够借阅3本图书。 2 接口地址 …

保证缓存一致性的常用套路

缓存更新的套路 看到好些人在写更新缓存数据代码时&#xff0c;先删除缓存&#xff0c;然后再更新数据库&#xff0c;而后续的操作会把数据再装载的缓存中。然而&#xff0c;这个是逻辑是错误的。试想&#xff0c;两个并发操作&#xff0c;一个是更新操作&#xff0c;另一个是…

[MyBatis-Plus]扩展功能详解

代码生成 使用MP的步骤是非常固定的几步操作 基于插件, 可以快速的生成基础性的代码 安装插件安装完成后重启IEDA连接数据库 mp是数据库的名字?serverTimezoneUTC 是修复mysql时区, 不加会报错 生成代码 TablePrefix选项是用于去除表名的前缀, 比如根据tb_user表生成实体类U…

恒定电流下有功率密度,功率密度体积分就是恒定电流的功率

体积趋于0时&#xff0c;体积的功率就叫功率密度 恒定电流的 电场乘距离等于电压 电流面密度*面积等于电流注意&#xff1a;电流面密度不是电荷线面体密度&#xff0c;电荷线面体密度用在静电场中&#xff0c;即电荷不运动这种

redo文件误删除后通过逻辑备份进行恢复

问题描述 开发同事让在一个服务器上查找下先前库的备份文件是否存在&#xff0c;如果存在进行下恢复。翻了服务器发现备份文件存在&#xff0c;多愁了一眼竟翻到了该备份文件于2024.6.17日恢复过的日志&#xff0c;赶紧和开发沟通说2024.6.17号已经恢复过了为啥还要恢复&#x…

ESP32_S3驱动舵机servor sg90

ESP32_S3驱动舵机servor sg90 硬件连接图硬件外观[^1]硬件引脚功能图硬件连接引脚对照表硬件接线图 Arduino IDE添加ESP32_S3开发板[^2]安装SERVO3舵机驱动库[^3]下载库ZIP包安装库 ESP32_S3程序下载方式源代码SERVO库自带例程方式二 参考文献 调试ESP32_S3舵机发现舵机不动。查…

多线程编程

使用多线程完成两个文件的拷贝&#xff0c;分支线程1&#xff0c;拷贝前一半&#xff0c;分支线程2拷贝后一半&#xff0c;主线程用于回收分支线程的资源 #include<myhead.h>typedef struct sockaddr_in addr_in_t; typedef struct sockaddr addr_t; typedef struct soc…

Redis --- 第四讲 --- 常用数据结构 --- Hash、List

一、Hash哈希类型的基本介绍。 哈希表&#xff1a;之前学过的所有数据结构中&#xff0c;最最重要的。 1、日常开发中&#xff0c;出场频率非常高。 2、面试中&#xff0c;非常重要的考点。 Redis自身已经是键值对结构了。Redis自身的键值对就是通过哈希的方式来组织的。把…

【MySQL 保姆级教学】数据类型全面讲解(5)

数据类型 1. 数据类型分类1.1 数值类型1.2 文本和二进制类型1.3 日期类型 2 数值类型2.1 TINYINT 类型2.1.1 默认有符号类型2.1.2 无符号类型 2.2 INT 类型2.2.1 默认有符号类型2.2.2 无符号类型 2.3 BIT 类型2.3.1 语法2.3.2 举例 2.4 FLOAT 类型2.4.1 语法2.4.2 默认有符号类…

OpenCV高级图形用户界面(20)更改窗口的标题函数setWindowTitle()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在OpenCV中&#xff0c;cv::setWindowTitle函数用于更改窗口的标题。这使得您可以在程序运行时动态地更改窗口的标题文本。 函数原型 void cv::…

keepalived(高可用)+nginx(负载均衡)+web

环境 注意&#xff1a; (1) 做高可用负载均衡至少需要四台服务器&#xff1a;两台独立的高可用负载均衡器&#xff0c;两台web服务器做集群 (2) vip&#xff08;虚拟ip&#xff09;不能和物理ip冲突 (3) vip&#xff08;虚拟ip&#xff09;最好设置成和内网ip同一网段&#xf…

【Vulnhub靶场】Kioptrix Level 3

目标 本机IP&#xff1a;192.168.118.128 目标IP&#xff1a;192.168.118.0/24 信息收集 常规 nmap 扫存活主机&#xff0c;扫端口 根据靶机IP容易得出靶机IP为 192.168.118.133 nmap -sP 192.168.118.0/24nmap -p- 192.168.118.133 Getshell 开放22端口和80 端口 访问web…

Git极速入门

git初始化 git -v git config --global user.name "" git config --global user.email "" git config --global credential.helper store git config --global --list省略(Local) 本地配置&#xff0c;只对本地仓库有效–global 全局配置&#xff0c;所有…