比尔云BierYun--阿里云最新优惠活动
阿里云优惠码丨阿里云代金券

基于CAN总线的汽车诊断协议UDS (网络层 ISO 15765)

基于CAN总线的汽车诊断协议UDS (网络层 ISO 15765)http://www.bieryun.com/1311.html

上个月一个同事Z跳槽去了德赛西威,Z之前是完全不懂诊断的MCU工程师,去德赛后做诊断开发,让我感觉到,汽车嵌入式行业,CAN和诊断工程师还是比较稀缺的。之前我和Z共同负责一个项目,我负责CAN网络和诊断部分,经过4个多月的奋战,我一个人把汽车诊断UDS的系统搭建出来,自认为,完成度很高,代码质量也极好。他跳槽去德赛做诊断开发,我想多少有点受益于我开发的诊断代码,另外我也悉心指导他,讲解相关的知识,他确实也学到不少,即便是现在,他有问题也会打电话向我求助。

前两年的工作和学习,我了解到汽车CAN网络和诊断还是比较难以学习的,网上资料参差不齐,我花了很大功夫才把这部分掌握,所以考虑写几篇相关的文章,以帮助后来者。

网络层的国际标准是ISO 15756-2,该标准详细规定了协议的具体细节。CAN总线是一帧8个字节,该协议可以使CAN总线高效的传输大约8个字节(up to 4095 bytes)的命令和数据。基于该标准文档,我开发出了一个独立性良好的协议栈,工作在上层诊断协议之下和下层CAN驱动之上,下面详解开发协议栈时需要实现的部分(基于 ISO 15765-2:2004(E))

    4 Network layer overview

    4.2 Services provided by network layer to higher layers

4.2小节是描述网络层协议提供给上层的服务

    (a) Communication services  (通信服务)

有四个,其中第1个是发送消息的服务,我实现为一个外部函数,提供给上层调用,第2,3,4是上层获取协议栈发送和接收状态的服务,我按照回调函数的方式实现,于是变成了上层提供给网络层的接口。如果转成C++代码,可以用虚函数来实现。

1) N_USData.request

是网络层提供给上层的发送消息的服务,5.2.1小节对其有详细的描述,我只实现了两个参数,msg_buf和msg_dlc,发送时根据消息长度判断是单帧发送还是多帧发送,

[cpp] view plain copy

  1. extern void network_send_udsmsg (uint8_t msg_buf[],uint16_t msg_dlc)
  2. {
  3.   if (msg_dlc==0|| msg_dlc> UDS_FF_DL_MAX)return;
  4.   if (msg_dlc<= UDS_SF_DL_MAX)
  5.   {
  6.     send_singleframe (msg_buf, msg_dlc);
  7.   }
  8.   else
  9.   {
  10.     nwl_st = NWL_XMIT;
  11.     send_multipleframe (msg_buf, msg_dlc);
  12.   }
  13. }

 

2)N_USData_FF.indication

该服务用来通知上层,网络层收到了首帧,5.2.3小节对其有详细的描述,我实现了一个参数msg_dlc,该函数通过回调实现,具体细节在上层代码中,按下不表。

函数原型声明如下

typedef void (*ffindication_func) (uint16_t msg_dlc);

网络层接收到首帧后调用该服务。

3)N_USData.indication

该服务把接收到的完整消息传递给上层,5.2.4小节对其有详细的描述,我实现了3个参数,msg_buf,msg_dlc和n_result,该函数通过回调实现,具体细节在上层代码中,按下不表。

函数原型声明如下

typedef void (*indication_func) (uint8_t msg_buf[], uint16_t msg_dlc, n_result_t n_result);

该函数调用较多:

1.接收到单帧,with N_OK

2.接收连续帧,如果sn错误,with N_WRONG_SN

3.接收连续帧,如果长度正确,with N_OK

4.网络层主循环中,如果CR定时器超时,with N_TIMEOUT_Cr

5.接收到首帧和单帧,如果网络层状态异常,with N_UNEXP_PDU

4)N_USData.confirm

该服务用来通知上层,消息发送已经完成,并返回成功与否,5.2.2小节对其有详细的描述。我实现了1个参数n_result,该函数通过回调实现。具体细节在上层代码中,按下不表。

函数原型声明如下

typedef void(*confirm_func)(n_result_t n_result);

该函数调用如下:

1.接受到流控帧,如果流状态>= FS_RESERVED, with N_INVALID_FS

2.接收到流控帧,如果流状态== FS_OVERFLOW, with N_BUFFER_OVFLW

3.网络层主循环中,如果BS定时器超时,with N_TIMEOUT_Bs

    b) Protocol parameter setting services (协议参数控制服务)

协议参数控制服务有两个,我没有实现,具体用处我还不明白,但是不影响实现协议栈功能。

    6 Network layer protocol

第6节描述网络层协议内容

    6.1-6.4小节简要说明

当消息长度小于等于6(扩展地址和混合地址)或者7(普通地址)个字节时,是通过一个N_PDU(数据单元)发送完成,叫做SF(单帧)。

当消息长度较大时,是通过多个N_PDUs(数据单元)发送完成,这种数据单元叫做FF(首帧,第一个N_PDU)和CF(连续帧,后续的N_PDUs)。

FF(首帧)包括前面5个(扩展地址和混合地址)或者6个(普通地址)字节的内容,1个或者多个CF(连续帧),每个CF包括后续的6个(扩展地址和混合地址)或者7个(普通地址)字节的内容,当然也可以少于6个或者7个字节。消息长度信息在FF(首帧)中发送,所有的CF(连续帧)在发送端被编号,以帮助接收者按顺序重组

消息。(最后一句话没什么卵用)

接收者通过Flow control(流控帧)的机制,告知发送者自己有多大的接收能力。(其实就是每两个FC之间允许连续发送多少个CF,每两个CF之间的时间不能过快)

Flow control 包含三个字段:

Flow status(FS),流状态,用来控制发送方接下来的行为,总共有三个定义,分别是FC.CTS(继续发送),FC.WAIT(继续等待),FC_OVFLW(缓存溢出,此时应该终止发送)。

Block Size (BS),每次收到流控帧之后,发送者最大可发送的连续帧的个数。

SeparationTimeMin (STmin),两个连续帧之间的最小间隔。

综上所述,网络层共有4中数据单元类型:SF N_PDU,FF N_PDU, CF N_PDU, FC N_PDU。详细说明在6.4节,不再赘述。

Tale 2 是N_PDU format (数据单元格式),每个N_PDU由三个域组成。

在使用普通地址时,地址域仅由CAN ID组成,CAN消息数据的第一个字节(或前两字节)为N_PCI Bytes。N_PCI(Protocol control information)标识了一条消息的类型和附加信息。

6.5 Protocol control information specification

Table 3描述各种类型的N_PDU 的N_PCI bytes的定义。

N_PCI byte的第一个字节的高4位为N_PCItype,标识该N_PDU(数据单元)的类型。

0,SF(单帧)

1,FF(首帧)

2,CF(连续帧)

3,FC(流控帧)

4-F,保留定义

我在程序中接收到一条诊断报文后,通过一条宏定义获取N_PCItype

[cpp] view plain copy

  1. #define NT_GET_PCI_TYPE(n_pci) (n_pci>>4)
  2. pci_type = NT_GET_PCI_TYPE (frame_buf[0]);

然后根据pci_type进行不同的处理。

(1)单帧的情况下,N_PCI byte第一个字节的低4位为SF_DL(消息长度),范围在1-6(扩展地址和混合地址)或者1-7(普通地址)之间,如果SF_DL错误,网络层应该忽略这条N_PDU

(2)首帧的情况下,N_PCI bytes 第一个字节的低4位和第二个字节共同组成FF_DL(消息长度),范围在8-FFF(扩展地址和混合地址)或者7-FFF(普通地址)之间,如果FF_DL大于接收者的接收缓存,网络层应该丢弃这条消息,并且发送FC with  FlowStatus = Overflow

(3)连续帧情况下,N_PCI byte第一个字节的低4位为SN(SequenceNumber),

在每开始发送一段数据的时候SN必须从零开始,FF(首帧)没有SN字段,但应该被认为是SN = 0,

FF之后的第一个CF的SN应该为1,

每发送一个新的CF,SN都应该增加1,

CF的值不应该受FC的影响,

当SN的值达到15的时候,下次发送的CF,SN应被重置为0,

如果SN出错,网络层应该丢弃已接收到的消息,并且调用N_USData.indication服务,with N_WRONG_SN

(4)流控帧情况下,

N_PCI bytes第一个字节的低4位为FS(Flow status),FS有4个定义,

0, CTS     ,代表发送者可以正常发送

1, WT       ,代表发送者应该再等待下一个FC,并且重启N_BS timer

2, OVFLW,代表接收方缓存溢出,发送方收到此FS后,应该终止发送,调用N_USData.confirm 服务,with N_BUFFER_OVFLW

3-F, Reserved

如果发送者收到的FS出错,网络层应该停止消息发送,并且调用 N_USData.confirm 服务,with  N_INVALID_FS

N_PCI bytes的第2个字节为BS(BlockSize),BS代表发送方在收到下一个FC之前,应发送的CF的数量,只有最后一块数据,其CF的数量可以少于BS,BS的值分两个情况,

0,      代表没有BS限制,发送方不必等待FC,把所有的FC一次发送。

1-FF,代表发送方发送BS数量的CF后,需等待FC,

N_PCI bytes的第3个字节为STmin,发送方收到FC后,应该把STmin保存下来,该值表明两个CF之间的最小间隔,STmin的值定义如下图

如果发送方收到一个FC,其STmin的值是Reserved,则发送方应默认STmin为7F(127ms)

STmin参数体现在程序中就是一个定时器,发送完一帧CF后,应该立即启动STmin timer

timer超时之后才能发送下一个CF,我的实现方式如下,nt_timer_run(TIMER_STmin) < 0 代表STmin timer超时。

[cpp] view plain copy

  1. if (nt_timer_run (TIMER_STmin) < 0)
  2. {
  3.     g_xcf_sn++;
  4.     if (g_xcf_sn > 0x0f)
  5.         g_xcf_sn = 0;
  6.     OSMutexPend(UdsMutex,0,&err);
  7.     send_len = send_consecutiveframe (&remain_buf[remain_pos], remain_len, g_xcf_sn);
  8.     remain_pos += send_len;
  9.     remain_len -= send_len;
  10.     if (remain_len > 0)
  11.     {
  12.         if (g_rfc_bs > 0)
  13.         {
  14.             g_xcf_bc++;
  15.             if (g_xcf_bc < g_rfc_bs)
  16.             {
  17.                 nt_timer_start (TIMER_STmin);
  18.             }
  19.             else
  20.             {
  21.                 /**
  22.                  * start N_Bs and wait for a fc.
  23.                  */
  24.                 g_wait_fc = TRUE;
  25.                 nt_timer_start (TIMER_N_BS);
  26.             }
  27.         }
  28.         else
  29.         {
  30.             nt_timer_start (TIMER_STmin);
  31.         }
  32.     }
  33.     else
  34.     {
  35.         clear_network ();
  36.     }
  37.     OSMutexPost(UdsMutex);
  38. }

 

6.6 Maximum number of FC.Wait frame transmissions (N_WFTmax)

6.6节,最大FC.Wait次数,是本地(local)的参数,不包含在FC中,
        指明接收方最大能连续发送多少个FC.Wait,
        这个上限参数应该在系统规划的时候由用户定义,
        该参数只在接收消息的时候使用,
        该参数如果为0,则接收方应该禁用FC.Wait,即不发送FS = WT的流控帧。
我实现的时候,默认了该参数为0,实际是根本没定义该参数,也不使用FC = WT的流控帧,

6.7 Network layer timing

6.7.1 Timing parameters

    Table 16 定义了网络层的时间参数值,以及各个时间参数的开始和结束点,这些体现通信性能的值,通信双方都应该满足,每个程序都可以定义具体的值,但是要在Table 16的范围内。(实际上,车厂会给一个文档,叫做诊断规范,会规定这些参数的值)
    通常,将超时值定义为高于性能要求的值,以确保系统能在特殊情况下工作。指定的超时值应被视为任何给定实现的下限。真正的超时值应不晚于指定的超时值 + 50%。(这是一堆废话,按照车厂的诊断规范确定超时值)

6.7.2 Network layer timeouts

Table 17 定义了网络层定时器超时产生原因和超时后的处理行为
    (1)N_As和N_Ar
    N_As和N_Ar可以认为是同一个timer,是发送者本地的定时器,从网络层发出request(网络层调用CAN消息发送函数)开始,到网络层收到confirm(CAN消息发送成功或失败)结束,如果超时就丢弃消息,并调用N_USData.confirm 服务,with N_TIMEOUT_A。
    N_Ar超时的Action描述应该有误,我认为应该跟N_As一样,然而我并没有实现这两个timer,这两个timer是为了规避本地CAN消息阻塞而引入的。如果系统中不会出现阻塞或者出现阻塞也不会影响到后续消息发送,则不需要实现。
    (2)N_Bs
    N_Bs是发送者用来监控对端的定时器,如Table 16 中的描述,“Time until reception of the next FlowControl N_PDU. ” ,N_Bs是指到接收到下一个FC的最大时间,具体实现方法如下:
    发送者发送完FF或者BS(发送者每次连续发送的CF个数)个CF后,启动定时器TIMER_N_BS,如果定时器超时,则应该丢弃目前正在发送的消息,并且调用N_USData.confirm 服务,with N_TIMEOUT_Bs。如果发送者收到了FC,则应该检查TIMER_N_BS定时器是否超时,如果没有超时,则关闭该定时器,继续处理FC N_PDU,然后如果该FC携带的FS = WT,则应该重新启动TIMER_N_BS,继续等待下一个FC;如果发现TIMER_N_BS已经超时,则应该丢弃该FC。
    (3)N_Br
    N_Br是接收者本地的时间参数,如Table 16中描述“Time until transmission of the next FlowControl N_PDU ”,B_Br是指到发送下一个FC的时间。后面的Start说明,我认为有问题,Start中L_Data.indication (FF) 是指接收者收到FF(首帧),是没问题的,这一条是为了保证接收者接收到FF后要尽快发送FC;Start中的L_Data.confirm (FC) 是指接收者成功发送FC,这就没道理了,首先下一个FC的发送时间不应该参考上一个FC,即便是参考上一个FC,时间参数也不该跟接收到FF之后发送FC的时间参数相同。(有点绕口)。实际代码中,我没有显式的实现这一个参数,但是却满足该参数的要求,因为我收到FF之后没有做特殊延时,立即回复FC,另一个情况是收到BS个CF之后,也是立即回复FC。
    (4)N_Cs
    N_Cs是发送者本地的时间参数,如Table 16中描述“Time until transmission of the next ConsecutiveFrame N_PDU L”,N_Cs是指从收到FC或者发送完一个CF后,到发送下一个CF的时间,同样,实际代码中,我没有显式的实现这一参数,但是却满足该参数的要求,我收到FC之后立即发送一个CF,而发送完成一个CF之后,我等待STmin时间后立即发送下一个CF,并未做其他特殊延时。
    (5)N_Cr
    N_Cr是接收者用来监控对端的定时器,如Table 16中描述“Time until reception of the next ConsecutiveFrame N_PDU ”,N_Cr是指到接收到下一个CF的最大时间,Table 16中描述的Start是发送完FC或者接收到CF,然而我在实现时稍微变通了一下,Start变成了接收到FF或者CF后(因为收到FF后会立即发送FC,收到BS个CF后也会立即发送FC,所以不如直接在每次收到FF或者CF后启动TIMER_N_CR),具体实现方法如下:
    接收者收到一个FF或者CF之后,启动定时器TIMER_N_CR,如果定时器超时,则应该丢弃目前正在接收的消息,并且调用N_USData.confirm 服务with N_TIMEOUT_Cr。如果接收者收到一个CF,则应该检查TIMER_N_CR是否超时,如果没有超时,则应该关闭定时器,继续处理CF N_PDU;如果发现TIMER_N_CR已经超时,则应该丢弃该CF。
    (6)STmin
    STmin没有在Table 16和Table 17中描述,但它却是协议栈需要实现的一个定时器,不同于前面几个定时参数限制最大时间,这个定时器是限制最小时间间隔的,当发送完一个CF后,发送者需要延时STmin才能发送下一帧CF,具体实现方式如下:
    发送者发送完一个CF之后,如果连续发送的CF数量没有达到BS个,则立即启动定时器TIMER_STmin,在网络层主循环中运行TIMER_STmin,当定时器超时后,立即发送下一个CF。前面介绍流控帧时也有说明。

6.7.3 Unexpected arrival of N_PDU

    6.7.3小节介绍意外的数据单元
    如果通信的一方收到了不符合正常顺序的数据单元,就叫做unexpected N_PDU。unexpected N_PDU 可能是SF,FF,CF,FC或者不被本文档识别的类型。网络层的设计决定其支持全双工或者半双工通信,unexpected N_PDU的判断跟全双工和半双工有关,两者不同。
半双工是指:同一时间,两个节点之间只允许一个方向进行通信,(A向B发送SF,FF,CF,B向A回复FC,这叫做一个方向)
全双工是指:同一时间,两个节点之间能允许两个方向进行通信,
In addition to the network layer design decision, the possibility has to be considered that a reception or transmission from/to a node with the same address information (N_AI), as contained in the received unexpected N_PDU, is in progress.
英语太渣,这句话理解不了。
普遍情况下,除了SF和包含物理地址的FF,收到的其他unexpected N_PDU都应该被忽略;包含功能寻址的FF也应该被忽略。忽略的意思是指网络层不用告知上层它收到了这个N_PDU。
Table 18 描述了网络层在收到unexpected N_PDU时的行为,跟网络层当前的内部状态(NWL status)和全双工/半双工有关。并且默认收到的unexpected N_PDU的地址信息跟正常接收和发送的地址信息一致。
我开发的系统是属于半双工的,按照半双工来解读。
Idle是指空闲状态,起始默认状态,此时如果收到SF或者FF,都当作是新的接收时序的开始,收到其他的类型的N_PDU都应该忽略;
在接收状态下(Segmented Receive in progress),如果收到了SF或者FF,都当作新的接收时序的开始,并且要调用N_USData.indication服务通知上层,with N_UNEXP_PDU,
在接收状态下,如果正在等待CF的情况下收到了CF,则按正常CF处理,如果当前没有等待CF,则应该忽略这条CF。
在接收状态下,如果收到了FC,半双工系统应直接忽略。
在接收状态下,如果收到不识别的N_PDU,应直接忽略。
可以看出来,表格并未说明在接收状态下是否允许发送,我觉得发送应该有更高优先级,所以我在开发网络层的时候,任何时候都能发送,并且如果是多帧传输,立即把NWL status置为发送状态。所以关于发送,我的详细实现方式如下:
在任何状态下,如果上层请求发送数据,则立即进行发送,并丢弃当前正在发送的数据(但是不丢弃接收),如果请求发送是多帧传输,则把发送状态置为发送。
在发送状态下,(Segmented Transmit in progress),收到SF,FF,CF都应该忽略,
在发送状态下,收到FC,如果当前正在等待FC,则正常处理该FC,否则忽略,
在发送状态下,如果收到不识别的N_PDU,应直接忽略。
这一部分功能我在网络层接收数据的入口进行处理,变量nwl_st指示当前网络层状态。

[cpp] view plain copy

  1. extern void
  2. network_recv_frame (uint8_t func_addr, uint8_t frame_buf[], uint8_t frame_dlc)
  3. {
  4.     uint8_t err;
  5.     uint8_t pci_type; /* protocol control information type */
  6.     /**
  7.      * The reception of a CAN frame with a DLC value
  8.      * smaller than expected shall be ignored by the 
  9.      * network layer without any further action
  10.      */
  11.     if(frame_dlc != UDS_VALID_FRAME_LEN) return;
  12.     if (func_addr == 0)
  13.         g_tatype = N_TATYPE_PHYSICAL;
  14.     else
  15.         g_tatype = N_TATYPE_FUNCTIONAL;
  16.     OSMutexPend(UdsMutex,0,&err);
  17.     pci_type = NT_GET_PCI_TYPE (frame_buf[0]);
  18.     switch(pci_type)
  19.     {
  20.         case PCI_SF:
  21.             if (nwl_st == NWL_RECV || nwl_st == NWL_IDLE)
  22.             {
  23.                 clear_network ();
  24.                 if (nwl_st == NWL_RECV)
  25.                     N_USData.indication (recv_buf, recv_len, N_UNEXP_PDU);
  26.                 recv_singleframe (frame_buf, frame_dlc);
  27.             }
  28.             break;
  29.         case PCI_FF:
  30.             if (nwl_st == NWL_RECV || nwl_st == NWL_IDLE)
  31.             {
  32.                 clear_network ();
  33.                 if (nwl_st == NWL_RECV)
  34.                     N_USData.indication (recv_buf, recv_len, N_UNEXP_PDU);
  35.                 if (recv_firstframe (frame_buf, frame_dlc) > 0)
  36.                     nwl_st = NWL_RECV;
  37.                 else
  38.                     nwl_st = NWL_IDLE;
  39.             }
  40.             break;
  41.         case PCI_CF:
  42.             if (nwl_st == NWL_RECV && g_wait_cf == TRUE)
  43.             {
  44.                 if (recv_consecutiveframe (frame_buf, frame_dlc) <= 0)
  45.                 {
  46.                     clear_network ();
  47.                     nwl_st = NWL_IDLE;
  48.                 }
  49.             }
  50.             break;
  51.         case PCI_FC:
  52.             if (nwl_st == NWL_XMIT && g_wait_fc == TRUE)
  53.                 if (recv_flowcontrolframe (frame_buf, frame_dlc) < 0)
  54.                 {
  55.                     clear_network ();
  56.                     nwl_st = NWL_IDLE;
  57.                 }
  58.             break;
  59.         default:
  60.             break;
  61.     }
  62.     OSMutexPost(UdsMutex);
  63. }

6.7.4 Wait frame error handling

    6.7.4小节介绍,如果接收者连续发送等待流控帧(FC N_PDU WT)到最大次数,并且仍然不能正常接收,这时候接收者应该丢弃已经收到的消息,并且调用
 N_USData.indication 服务,with  N_WFT_OVRN ,告知上层。
    这一功能我并没有实现,因为我开发的网络层不使用(FC N_PDU WT),任何情况下都能正常接收。

6.8 Interleaving of messages

    6.8小节的内容是,网络层应该有并行传输不同地址的诊断消息的能力,
    这一功能我也没有实现,现状是我们的系统诊断地址都是固定的,由车厂分配,可能这一功能对于网关是有必要的,这里就不再赘述。

7 Data link layer usage

    第7节讲的是链路层的设计,以及扩展地址情况下,数据单元的格式,
    我们普遍使用的都是普通地址,这一部分也不再详细介绍了。
未经允许不得转载:比尔云 » 基于CAN总线的汽车诊断协议UDS (网络层 ISO 15765)
分享到: 更多 (0)

相关推荐

  • 暂无文章

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

强烈推荐

高性能SSD云服务器ECS抗攻击,高可用云数据库RDS