手搓UDS Bootloader系列——网络层(TP层)

前言

  在介绍UDS服务之前,首先需要介绍一下网络层(传输层TP层),这是UDS与控制器之间的中间层,否则控制器收到的就是普通的CAN报文,无法实现对应的诊断服务,为什么需要TP层呢?因为有些数据不是一帧报文就能发送完的,需要发送很多帧,网络层需要做的就是数据的解包、打包和传输。


一、网络层(TP层)

1、网络层(TP层)作用

  在汽车诊断协议中,UDS(Unified Diagnostic Services,统一诊断服务)基于ISO 14229标准,其协议栈遵循OSI模型。其中,‌网络层(Network Layer),也称为‌传输协议层(Transport Protocol Layer,TP层),主要负责将应用层(如UDS服务)的长消息拆分成适合底层总线(如CAN、CAN FD、FlexRay等)传输的帧,并在接收端重新组装。网络层的核心标准是 ‌ISO 15765-2(适用于CAN总线)。

  当应用层消息长度超过底层总线单帧容量(如CAN帧的8字节)时,网络层将消息拆分为多个帧发送,接收端重新组合为完整消息,例如:UDS的`0x36服务传输数据时,数据可能长达数KB,需分多帧传输;它还可以用于流控制,管理发送端与接收端的数据传输速率,避免接收缓冲区溢出。这里只列举了简单的功能,有关网络层的详细内容,还请阅读ISO 15765-2协议。

2、网络层帧类型

  在UDS的网络协议控制中一共有4种类型的帧,分别为:单帧,首帧,续帧,流控帧。它们通过通信帧的第一字节的高4位来区分。
在这里插入图片描述

3、网络层长消息传输流程

  以34服务为例,34服务需要传输块大小和块长度,一般情况下会超过7个字节,因此需要多帧传输。

a. 发送首帧(FF)

  发送端发送首帧(FF),包含消息总长度。例如:10 0b 34 00 44 08 00 50,其中第一个字节10的高4位为1,表示这是首帧;数据长度是第一个字节的低4位和第三个字节组成,在这里值是0x00b,11个字节。

b. 接收端回复流控帧(FC)

  接收端回复流控帧(FC),指定传输参数,包括:

  流控状态(FS):0x0(继续发送)、0x1(等待)、0x2(错误);

  块大小(BS):发送端每次连续发送的帧数(如BS=0表示无限制);

  帧间隔时间(STmin):连续帧之间的最小时间间隔(单位ms)。例如30 00 00 CC CC CC CC CC,表示接收端允许发送端继续发送,块大小不限制,帧间隔时间不限制。

c. 发送端发送连续帧(CF)
  发送端按流控帧指定的参数发送连续帧(CF),每帧包含顺序编号和数据块。顺序号:从0x01开始递增,循环范围(0x00-0x0F)。例如:21 00 00 00 37 60 AA AA,其中21表示多帧的第一帧,后面如果还有内容,第一个字节的低4位会继续递增,21,22,23,24,~2F。

d. 循环直至传输完成,重复步骤b和c,直至所有数据发送完毕。
在这里插入图片描述
  注:上面图中的各个时间参数可以阅读ISO15765-2,后面我们在升级如果由于时间参数的问题导致bug,再回头来仔细分析。

帧类型格式示例

a. 单帧(SF),以CAN为例
  02 10 01 00 00 00 00 00,02的高4位为0,表示单帧,10表示会话服务,子服务是01,意思就是请求默认会话。
在这里插入图片描述
b.多帧(FF+FC)
  10 0b 34 00 44 08 00 50即表示首帧,首帧的前两个字节,byte0的高4位是1。

  byte0的低4位和byte2组合是数据长度,这里我们可以看到最大的长度是0xfff(4095),这也是为什么基于CAN的UDS最大允许传输长度是4095个字节;
在这里插入图片描述
  34表示请求传输服务:

  30 00 00 CC CC CC CC CC表示接收端发送来的流控帧。

  30表示该帧是流控帧,第1个字节30的低4位为0,表示FS为0,继续发送后续帧;

  第2个字节00为BS = 0,块大小值,控制连续帧的连续传输的个数。例如:当BS设置成8时,表示接收流控帧之后,当发送完8个连续帧就需要再次接受流控帧。当值为0时,表示后续连续帧的发送不受流控帧的控制。

  第3个字节00表示Stmin连续帧之间最小时间间隔不限制,如果要实现间隔是10ms,则第3个字节应为0A。
在这里插入图片描述
c. 连续帧(CF)
  21 00 00 00 37 60 AA AA表示该帧是连续帧,连续帧与首帧组合起来表示34服务要传输的内容为:这一个block要传输的块的内存地址是0x08005000,该块的长度是0x00003760(14176)。
在这里插入图片描述

二、代码实现

1.CAN接口

  网络层与CAN接口通过全局变量UDS_Data来实现,HAL_CAN_RxFifo0MsgPendingCallback函数是HAL库自带的CAN接收中断函数,每次收到CAN数据都会进入中断。并且我们测试过,通过中断接收不会导致丢帧问题。

代码如下:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan1) 
{
  CAN_RxHeaderTypeDef rxHeader;
  uint8_t Rec_Data[8];
  uint8_t i;
  // 从FIFO0读取数据
  if (HAL_CAN_GetRxMessage(hcan1, CAN_RX_FIFO0, &rxHeader, Rec_Data) == HAL_OK) 
  {
      // 处理接收到的数据
      if (rxHeader.StdId == PhyCANID_Rx) 
      {
          // 解析rxData数组中的数据
          for(i = 0;i < 8;i++)
          {
            UDS_Data[i] = Rec_Data[i]; /*将接收到的数据赋给全局变量UDS_Data*/            
          }
      }
  }
}

2.首帧(FF)数据处理

  这里每次处理完数据都会把UDS_Data数据clear掉,因为我将处理函数放在了while(1)循环内,会不断监控这个全局变量,如果不清空,如果后面没有数据会导致一直卡在一个分支。

代码如下:

typedef struct /*定义buffer存储结构体,包括数据,长度,有无错误等*/
{   
  uint8_t 	Data[UDS_MAX_DATA_LEN]; 
  uint16_t 	Length; 
  uint8_t 	N_PCI;
  uint8_t		N_Result;
  bool        RecvComplete;
  bool		MFTxEnFlg;
  uint8_t		MFTxLength;
  uint8_t		*MFTxDataBuffPtr;
  uint8_t 	NTX_PCI;           
}N_UDSData;

下面展示一些 内联代码片

N_PCI = (UDS_Data[0] >> 4);/*byte0的高4位,bit4~bit7*/
DataPtr->RecvComplete = FALSE;/*接收完成标志位置false*/
case PCI_FF: /*10 0b 34 00 44 08 00 50*/
	DataLen = (uint16_t)(((UDS_Data[0] & 0x0F) << 8) | UDS_Data[1]);/*多帧的长度*/
	if (DataLen > UDS_MAX_DATA_LEN)/*大于4095,报错*/
	{
		DataPtr->N_Result = N_ERROR;
	}
	else if(DataLen <= 7)/*小于等于7,报错,因为如果小于等于7个字节,没必要通过多帧传输*/
	{
		DataPtr->N_Result = N_ERROR;
	}
	else
	{
		/*长度正确,将UDS_Data拷贝至一个N_UDSData类型的Data buffer,这个buffer长度是4095,所有的UDS处理数据都通过这个buffer*/
    	DataPtr->Length = DataLen;
        Rx_Index = 0; /*清空缓冲区*/
        DataPtr->Data[Rx_Index++] = UDS_Data[2];
        DataPtr->Data[Rx_Index++] = UDS_Data[3];
        DataPtr->Data[Rx_Index++] = UDS_Data[4];
        DataPtr->Data[Rx_Index++] = UDS_Data[5];
        DataPtr->Data[Rx_Index++] = UDS_Data[6];
        DataPtr->Data[Rx_Index++] = UDS_Data[7];
        DataPtr->N_PCI = PCI_SF;
        DataPtr->N_Result = N_OK;
        DataPtr->RecvComplete = TRUE;
        FF_RecFlag = 1;/*接收到了首帧*/
        DataLen = DataLen - 6;/*长度减去6,因为首帧数据只有后面6个字节是数据*/
        DiagTimer_Set(&ReciveTimerCr, N_Cr);/*开启Cr定时器*/
        UDS_FC_TxData();/*自动回复流控帧*/
        for(i = 0;i < 8;i++)
        {
            UDS_Data[i] = 0x00;/*clear 临时buffer*/
        }
    }
    break;	

2.连续帧(CF)数据处理

  每次收到连续帧,都要把N_Cr定时器打开,然后这个定时器会不断地被更新覆盖,因为ReciveTimerCr1是一个全局变量。


    case PCI_CF:  /*接收连续帧,21 00 00 00 37 60 AA AA*/
        DiagTimer_Set(&ReciveTimerCr1, N_Cr);/*开启Cr定时器*/
        if(FF_RecFlag == 1)
        {
            if(DataLen > 7)
            {
                /*数据长度大于一帧的长度*/
                DataPtr->Data[Rx_Index++] = UDS_Data[1];
                DataPtr->Data[Rx_Index++] = UDS_Data[2];
                DataPtr->Data[Rx_Index++] = UDS_Data[3];
                DataPtr->Data[Rx_Index++] = UDS_Data[4];
                DataPtr->Data[Rx_Index++] = UDS_Data[5];
                DataPtr->Data[Rx_Index++] = UDS_Data[6];
                DataPtr->Data[Rx_Index++] = UDS_Data[7];
                
                DataLen = DataLen - 7;/* 成功接收了7个长度数据 */
            }
            else
            {
                /* 剩余长度最后一帧 */          
                for(i = 0;i < DataLen;i++)
                {
                    DataPtr->Data[Rx_Index++] = UDS_Data[i+1];
                }
                DataLen = 0;                
                DataPtr->RecvComplete = TRUE;               
                FF_RecFlag = 0;              //本次发完,可以继续接受首帧了   
            }
            for(i = 0;i < 8;i++)
            {
                UDS_Data[i] = 0x00;
            }
            DataPtr->N_PCI  = PCI_CF;
            DataPtr->N_Result = N_OK;
        }
        else
        {
            DataPtr->N_Result = N_ERROR;
        }
        break;
    default:
        break;

三、测试

  测试首帧时,先把自动回复流控帧注释掉,单片机收到首帧不回复连续帧。上位机使用的是TSmaster,它可以自己创建Bootloader的升级上位机,后面会通过一篇内容进行介绍。
在这里插入图片描述
在这里插入图片描述
  自动回复流控帧打开,单片机收到首帧回复连续帧,TSmaster收到流控帧会自动发送下一个连续帧。
在这里插入图片描述

四、总结

  这一篇内容,主要讲解了UDS的网络层,以及代码如何实现首帧和连续帧,并且对功能进行了简单的测试。下一篇内容将先实现34服务,本着先实现再完美的原则,通过搭积木的方式实现UDS Bootloader的开发。

感兴趣的小伙伴可以加我微信公众号:行至汽车电子(XZJZLN),会及时推送文章

### UDS Bootloader 实现概述 UDS(Unified Diagnostic Services)协议是基于ISO-14229标准的诊断服务协议,广泛应用于汽车嵌入式系统中。在嵌入设系统中实现UDS Bootloader通常涉及以下几个关键方面: #### 1. **UDS Bootloader 的功能定义** UDS Bootloader 的主要功能是通过诊断服务接口对目标设备进行固件更新或配置初始化。其核心功能包括: - 接收和解析 UDS 请求消息。 - 验证接收到的固件数据完整性。 - 将固件数据写入目标存储器。 - 执行必要的校验和启动新固件。 这些功能可以通过 DCM(Diagnostic Communication Manager)模块实现,DCM 是 UDS 协议栈的一部分,负责处理和检查诊断消息[^3]。 #### 2. **UDS Bootloader 的协议栈实现** UDS Bootloader 的实现依赖于底协议栈的支持。常见的实现方式包括: - **CANTP 模块**:负责传输协议帧的分割与重组,适用于长于 8 字节的 CAN I-PDUs[^3]。 - **CANIF 模块**:在数据链路提取基于 CAN ID 的消息。 - **MCAL 配置**:通过 MCAL(Microcontroller Abstraction Layer)配置 CANFD 波特率和采样时间等参数[^3]。 以下是一个简单的 UDS Bootloader 实现流程图: ```plaintext +-------------------+ | CAN Interface | +-------------------+ | v +-------------------+ | CANTP Layer | +-------------------+ | v +-------------------+ | DCM Layer | +-------------------+ | v +-------------------+ | Bootloader Core | +-------------------+ ``` #### 3. **Bootloader 核心代码示例** 以下是一个简化的 UDS Bootloader 核心代码示例,展示如何接收和处理诊断请求: ```c #include "CanTp.h" #include "Dcm.h" void Uds_Bootloader_Process(void) { uint8_t rxBuffer[64]; // 接收缓冲区 uint8_t txBuffer[64]; // 发送缓冲区 uint8_t serviceId; // 服务ID uint8_t subFunction; // 子功能ID // 从 CANTP 接收数据 CanTp_Receive(rxBuffer); // 解析 UDS 请求 serviceId = rxBuffer[0]; if (serviceId == 0x2E) { // 写数据服务 (Write Data By Identifier) subFunction = rxBuffer[1]; if (subFunction == 0x01) { // 示例子功能 // 执行写操作 WriteToFirmware(rxBuffer + 2, rxBuffer[2]); } } // 构造 UDS 响应 txBuffer[0] = 0x6E; // 正常响应服务ID txBuffer[1] = subFunction; CanTp_Send(txBuffer); } void WriteToFirmware(uint8_t *data, uint8_t length) { // 将数据写入目标存储器 for (uint8_t i = 0; i < length; i++) { Flash_Write(data[i], TARGET_ADDRESS + i); } } ``` #### 4. **UDS Bootloader 的测试与验证** 为了确保 UDS Bootloader 的可靠性,需要进行全面的测试,包括但不限于: - **功能性测试**:验证所有支持的服务是否按预期工作。 - **性能测试**:评估固件下载速度和资源占用。 - **鲁棒性测试**:模拟网络延迟、丢包等异常情况下的行为。 测试可以参考 AIgraphX ACC 系统开发需求文档中的验证方法[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值