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

时间:2024-10-21 09:36:24

目录

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 BUFFREADENABLE
        R1xRead[FlowData->count] =  data1;
        R2xRead[FlowData->count] =  data2;
        R3xRead[FlowData->count] =  data3;
        R4xRead[FlowData->count] =  data4;
#endif
        FlowData->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 BUFFREADENABLE
            R1xRead[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 BUFFREADENABLE
        R1xRead[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性能。