基于TC397的Bootloader开发过程中遇到的问题记录

问题1

  1. 现象描述

ECU收到34服务指令(Erase flash)后会开始擦除指定区间的flash,如果目标flash的size较小,擦除这个动作占用的时间不长,这种情况下不会有问题;但如果flash的size较大,那么擦flash将会占用较长的时间,一旦这个擦除时间超过了S3_client,会导致上位机超时,这就意味着刷新过程会被中断,因此当目标flash的size较大时,不再整块的擦flash,而是在for循环里以sector(flash的最小单位)为单位擦flash,这样每擦几个sector,ECU可以这个间隙里给上位机回复0x78来告诉上位机“我还活着”,上位机就不会超时了。

这种擦除方式虽然不会让上位机超时,但是弊端也很明显,以sector为单位擦除比一次性擦整块flash需要更多时间,而且会有部分报文漏处理。这里就是要记录一下如何处理报文漏处理的情况

在CAN的驱动配置中,CAN报文的接收和发送都配置为中断,收到报文后在中断服务函数中将硬件buffer中的报文拷贝到实现定义好的全局变量message[8][8]中,并以writePtr指示最新报文所处的位置,同时将CAN_Msg_Received这个标记量置为TRUE;

而报文的处理则是在HAL_E_CAN_Service_Message()中,函数会根据CAN_Msg_Received是否为TRUE决定是否处理报文,readPtr则用于指示当前报文处理到哪一帧了。

     /* Save data in CAN Rx ISR */
    for (i = 0;i < 8;i++)
    {
        message[writePtr][i] = data[i];
    }
    writePtr++;

    if(writePtr > 7u)
    {
        writePtr = 0u;
    }
    CAN_Msg_Received = TRUE;
     /* handle message */
     if(CAN_Msg_Received == TRUE)
     {
        /*Get msg from buffer & Do the service*/
        in_fun(&message[readPtr][0]);
    
        readPtr++;
    
        if(readPtr > 7u)
        {
            readPtr = 0u;
        }
        CAN_Msg_Received = FALSE;
     }
  1. 分析思路

这次博客记录的报文漏处理问题有好几层,一层一层分析。

第一层:

中断保护问题。CAN_Msg_Received是指示主程序是否收到了报文以及有没有报文需要处理,这个标志位是在接收中断服务程序中置位、报文处理函数中复位,那么就存在一个问题,假如在处理报文A的过程中来了一帧报文B,此时触发接收中断,在中断服务程序中将CAN_Msg_Received置位,然后退出中断继续处理报文A,处理结束后CAN_Msg_Received被复位,那么当while(1)再次轮询到该函数时发现CAN_Msg_Received为FALSE,虽然报文B也放入了message[8][8]数组中,但报文B永远不会被处理。

因此,在实际执行报文处理函数前需要将接收中断关闭,处理完成之后再打开接收中断,这样在处理报文过程中就不会触发接收中断,也不会漏处理报文了。

第二层:

接收报文数量超出缓存区空间。在报文处理函数前后加了中断的开关之后,还是会有报文漏处理的现象发生。继续分析,上面提到现在擦flash是在for循环中以sector为单位来擦,这样的弊端是会占用大量时间,实际测试下来擦除4m的空间需要大约45s,那么在这45s的时间里上位机会一直给ECU发送02 3e 80,虽然在擦sector的时候会关闭接收中断,但是在退出擦写过程、打开接收中断后依然会触发接收中断,将3e 80放到message[8][8]中。假设在执行擦除动作前writePtr和readPtr都等于4,而经过了45s的擦除动作,由于message里存放了很多的3e 80,可能writePtr已经变成了8,readPtr依然是4,等到flash擦完之后开始执行报文处理函数,此时readPtr仍然等于4,处理完存储在第4行的报文之后会将CAN_Msg_Received为FALSE,readPtr等于5,这种情况下第6帧和第7帧报文就得不到处理了。

分析到这里,办法就很清楚了,手动的给message中的每帧报文都添加了一个flag,用来记录当前这帧报文有没有被处理过。message[8][8]就变成了如下结构体。

typedef struct
{
    uint8 message[8][8];
    uint8 flag[8];
}
Msg_Rcv_Buffer;
Msg_Rcv_Buffer msg_rcv_buffer;

相应的,代码就修改成:

    SRC_CAN0INT0.B.SRE = 0u;
    if(msg_rcv_buffer.flg[readPtr] == TRUE)
    {
        /*Get msg from buffer & Do the service*/
        msg_rcv_buffer.flg[readPtr] = FALSE;
    
        in_fun(&msg_rcv_buffer.message[readPtr][0], msg_rcv_buffer.rx_datalength[readPtr]);
    
        readPtr++;
    
        if(readPtr > BUFFER_LENGTH - 1)
        {
            readPtr = 0u;
        }
    }
    SRC_CAN0INT0.B.SRE = 1u;

加了flag以后,是否处理报文不再是以CAN_Msg_Received是否置位为判断依据,而是以该报文自己的flag是否置位为依据,理论上就不会有报文漏处理了。

第三层:

接收报文数量超出缓存区空间一轮。在擦除过程中如果上位机发送了超过8帧报文,此时msg_rcv_buffer里的8帧报文各自的flag都置为TRUE了,假设在擦除指令前writePtr和readPtr都等于4,擦完之后writePtr等于7,readPtr等于4,那么退出擦除动作后由于flag都是TRUE,那么程序会从第4帧开始(因为readPtr等于4)将8帧报文都处理一遍,处理完成之后readPtr还是等于4,而此时writePtr等于7,即便此时来了第8帧,由于第5帧的flag为FALSE,那么程序将无法再处理到后面的所有报文,因此后面的报文就都漏处理了。

分析到这里,解决办法也很简单,增加一个MsgCount作为整个擦写过程中收到的所有报文的计数。

代码如下:

     /* Save data */
    for (i = 0; i < length; i++)
    {
        msg_rcv_buffer.message[writePtr][i] = data[i];
    }
    msg_rcv_buffer.rx_datalength[writePtr] = length;

    msg_rcv_buffer.flg[writePtr] = TRUE;

    MsgCount++;

    writePtr++;

    if(writePtr > BUFFER_LENGTH - 1)
    {
        writePtr = 0u;
    }
    SRC_CAN0INT0.B.SRE = 0u;
    if (MsgCount > 8U)
    {
        readPtr = writePtr % 8U;
        MsgCount = 8U;
    }
    if(msg_rcv_buffer.flg[readPtr] == TRUE)
    {
        /*Get msg from buffer & Do the service*/
        msg_rcv_buffer.flg[readPtr] = FALSE;

        in_fun(&msg_rcv_buffer.message[readPtr][0], msg_rcv_buffer.rx_datalength[readPtr]);

        if (MsgCount > 0U)
        {
            MsgCount--;
        }

        readPtr++;

        if(readPtr > BUFFER_LENGTH - 1)
        {
            readPtr = 0u;
        }
    }
    SRC_CAN0INT0.B.SRE = 1u;

MsgCount用于所有报文的计数,并且在正常情况下会保持为0(接收中断服务函数中会加1,处理函数中减1)。当有报文累积时,MsgCount用于指示累积报文是否超过8,依然用举例说明,

若没有超过8,即没有出现第二层描述里的情况,假设writePtr = 5,readPtr = 2,MsgCount = 3,那么程序在处理报文时从第2帧报文开始,一直遍历到第4帧报文,遍历完成之后readPtr = 5,MsgCount = 0,readPtr = writePtr;

若超过了8,即累积报文超过了buffer的最大容量,假设writePtr = 5,readPtr = 2,MsgCount =11请注意此时buffer里的第2、3、4帧都已经被覆盖,而且存放的是最新收到的报文,此时报文处理会从第5帧(readPtr = writePtr % 8 = 5)开始,且MsgCount = 8,遍历一轮后readPtr = writePtr = 5,MsgCount = 0,这种情况下就不会有报文漏处理了。

  1. 实际操作

  1. 复盘分析

代码改到这里,经过测试基本没有报文漏处理的现象发生了。即便如此,在实测中发现擦除flash的过程中上位机发了很多条3e 80,而软件buffer的size只有8,因此还是有很多3e 80没有被成功接收到。

理论上来讲如果擦除flash的时间足够长,那么即便准备的硬件buffer足够大,也会有报文丢失,那么3e 80丢失了会对程序的S3_Server造成影响吗?答案是否定的,原因有两点。

一、软件中S3_Server的计时是依赖于GPT模块产生的1ms中断,在擦除flash过程中关闭了中断,这样1ms的中断也就进不去了,那么S3_Server自然就不会超时;

二、ECU在收到请求报文后会关闭S3_Server的计时,在发出响应报文之后才会打开S3_Server的计时,对于34服务指令而言同理,因此收到34服务指令后开始擦除flash的这段过程,哪怕持续时间再长,ECU端的S3_Server都不会超时。

问题2

  1. 现象描述

刷新流程结束之后上位机通过22服务AFFC读取刷新计数时,刷新计数会偶发地置1

  1. 分析思路

尝试用单步调试的方法复现该现象,程序中涉及到刷新计数的更新有两处,一是在34服务中擦flash前,二是在31服务中擦flash前,由于下载过程不涉及31服务擦flash,着重模拟34服务擦flash。

  1. 实际操作

在CANoe中多次发送34 00 44 80 10 00 00 00 02 00 00,发现刷新计数可以正常的加1,也就是说每执行一次擦除flash的操作,刷新计数都可以正常的更新。接下来用上位机执行刷新操作,刷新计数也可以加1,但是第二次执行刷新操作时,刷新计数就置为1了。

经过多次尝试,终于稳定复现:在用上位机执行两次刷新操作后,刷新计数会置1,两次刷新操作本身没有太多差异,只能通过debug找问题。代码如下:

boolean UDS_SRV_I_34_Update_AppProgramming_Counter_Flag(void)
{
    uint16 Programming_Cnt = 0;
    boolean ck_status = TRUE;
    /* Check App programming counter */
    Flash_Read_ProgramCounter(&Programming_Cnt);

    if(Programming_Cnt == MAX_APP_PROGRAMMING_COUNTER)
    {
        ck_status = FALSE;
    }
    else
    {
        /* Update number of times programming on NVM */
        ck_status = TRUE;
        if(
             ((Programming_Cnt & 0x00FF) == (FLASH_FILLING_BYTE << 0)) &&
             ((Programming_Cnt & 0xFF00) == (FLASH_FILLING_BYTE << 8))
          )
        {
            Programming_Cnt = 1;
        }
        else
        {
            Programming_Cnt++;
        }
    }
    if(ck_status == TRUE)
    {
        Flash_Write_ProgramCounter(Programming_Cnt);
    }
    return ck_status;
}

将断点打在第6行Flash_Read_ProgramCounter(&Programming_Cnt)处,在上位机第二次刷新执行34服务时会停在这里

点进去,这里主要做的事是从FLASH_DATA_BASE(0x80040000)这块地址读取存好的物流数据到FLASH_RAM_mirror中,然后从FLASH_RAM_mirror中特定位置读取计数

在读取刷新计数之前先瞅一眼FLASH里的状态

看到这里,果然是出了问题,在读flash里的值之前,flash里的值本身已经被修改了,往前找哪个地方涉及到flash的擦写

/* Erase Application Valid Flag */
FlashVal_Status = Flash_Write_ApplicationValidFlag(0x00);

/*Update the programming integrity and compatibility status to 0xFF ---awoys*/
FlashInt_Status = Flash_Write_ProgramIntegrityState(0xFF);

FlashCom_Status = Flash_Write_ProgramCompatibilityState(0xFF);

FlashCount_Status = UDS_SRV_I_34_Update_AppProgramming_Counter_Flag();

任意挑一个函数点进去

boolean Flash_Write_ApplicationValidFlag(uint8 ApplicationValidFlag)
{
    boolean return_code;
    FLASH_RAM_Mirror[RAM_AppValidflag_Base] = ApplicationValidFlag;
    return_code = Flash_Update_Data();
    return return_code;
}

这几个更新标志位的函数实现的逻辑是:更改对应的FLASH_RAM_mirror里存放的标志位,然后将整个数组通过Flash_Update_code()函数写入到FLASH_DATA_BASE中(这就要求在初始化函数中需要对这块ram进行初始化)

看到这里,立马去看FLASH_RAM_mirror这个数组里存放的值,果然全是0,

回头到初始化函数里,果然

void NvM_Flash_ReadAllData(void)
{
    Copy_Bytes_NvM(FLASH_RAM_Mirror,  (uint32)FLASH_DATA_BASE, FLASH_RAM_Mirror_Size);
}

这个初始化函数并没有被调用,所以RAM中全是0

  1. 复盘分析

问题3

  1. 现象描述

刷新流程结束之后上位机通过31子服务FF01更新兼容码时,程序会卡死

  1. 分析思路

31服务通过CANoe测试过没有问题,直接DEBUG看问题出在哪

  1. 实际操作

把断点打在31子服务FF01前

然后单步调试

当走到第1691行时,if(*(uint8*)BOOT_HEADER_APP_DEPEND == *(uint8*)APP_HEADER_DEPEND)

程序报错。点进去看,BOOT_HEADER_APP_DEPEND是boot中存放兼容码的flash,地址是0x80000000,再去看这块flash中存放的内容,发现是空的。

所以当程序访问这段地址后报错了。

再看下为什么这段地址是空的,这段地址是用来放boot兼容码及F183的,原本是用#pragma语句定义

#pragma section ".BootHeader" a
const typedef struct
{
    uint8 App_Dependencies;
    uint8 Boot_Software_Number[10];
} BootHeader bootheader = {0xB5, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}};
#pragma section

后面为了将bootheader声明成extern类型的变量,因此讲变量的定义放到了头文件中,那这个地方犯了一个错误,误以为把类型的声明套上#pragma,就可以将用该类型定义的变量定义到目标flash中

#pragma section ".BootHeader" a
typedef struct
{
    uint8 App_Dependencies;
    uint8 Boot_Software_Number[10];
} BootHeader;
#pragma section

BootHeader bootheader = {0xB5, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}};

这样的写法无法将bootheader定义到.BootHeader区域中,所以0x80000000开始的这块地址是空的,当程序访问这块地址时,就会报错了。

  1. 复盘分析

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值