Saturday, 11 April 2020 18:25

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

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

网络层的国际标准是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,发送时根据消息长度判断是单帧发送还是多帧发送,

extern void network_send_udsmsg (uint8_t msg_buf[],uint16_t msg_dlc)
{

if (msg_dlc==0|| msg_dlc> UDS_FF_DL_MAX)return;

if (msg_dlc= UDS_SF_DL_MAX)
{
send_singleframe (msg_buf, msg_dlc);
}
else
{
nwl_st = NWL_XMIT;
send_multipleframe (msg_buf, msg_dlc);
}
}

 

    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由三个域组成。

 J15765 0001

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

6.5 Protocol control information specification
Table 3描述各种类型的N_PDU 的N_PCI bytes的定义。

 J15765 0002

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

    0,SF(单帧)

    1,FF(首帧)

    2,CF(连续帧)

    3,FC(流控帧)

    4-F,保留定义

 

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

 

#define NT_GET_PCI_TYPE(n_pci) (n_pci>>4)

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的值定义如下图

 J15765 0003

 

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

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

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

if (nt_timer_run (TIMER_STmin) 0)
{
g_xcf_sn++;
if (g_xcf_sn > 0x0f)
g_xcf_sn = 0;
OSMutexPend(UdsMutex,0,&err);
send_len = send_consecutiveframe (&remain_buf[remain_pos], remain_len, g_xcf_sn);
remain_pos += send_len;
remain_len -= send_len;

if (remain_len > 0)
{
if (g_rfc_bs > 0)
{
g_xcf_bc++;
if (g_xcf_bc g_rfc_bs)
{
nt_timer_start (TIMER_STmin);
}
else
{
/**
* start N_Bs and wait for a fc.
*/
g_wait_fc = TRUE;
nt_timer_start (TIMER_N_BS);
}
}
else
{
nt_timer_start (TIMER_STmin);
}
}
else
{
clear_network ();
}
OSMutexPost(UdsMutex);
}

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%。(这是一堆废话,按照车厂的诊断规范确定超时值)

 J15765 0004

6.7.2 Network layer timeouts
Table 17 定义了网络层定时器超时产生原因和超时后的处理行为

 J15765 0005

    (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的地址信息跟正常接收和发送的地址信息一致。

 J15765 0006

我开发的系统是属于半双工的,按照半双工来解读。
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指示当前网络层状态。

extern void
network_recv_frame (uint8_t func_addr, uint8_t frame_buf[], uint8_t frame_dlc)
{
uint8_t err;
uint8_t pci_type; /* protocol control information type */


/**
* The reception of a CAN frame with a DLC value
* smaller than expected shall be ignored by the
* network layer without any further action
*/
if(frame_dlc != UDS_VALID_FRAME_LEN) return;

if (func_addr == 0)
g_tatype = N_TATYPE_PHYSICAL;
else
g_tatype = N_TATYPE_FUNCTIONAL;

OSMutexPend(UdsMutex,0,&err);
pci_type = NT_GET_PCI_TYPE (frame_buf[0]);
switch(pci_type)
{
case PCI_SF:
if (nwl_st == NWL_RECV || nwl_st == NWL_IDLE)
{
clear_network ();
if (nwl_st == NWL_RECV)
N_USData.indication (recv_buf, recv_len, N_UNEXP_PDU);
recv_singleframe (frame_buf, frame_dlc);
}
break;
case PCI_FF:
if (nwl_st == NWL_RECV || nwl_st == NWL_IDLE)
{
clear_network ();
if (nwl_st == NWL_RECV)
N_USData.indication (recv_buf, recv_len, N_UNEXP_PDU);

if (recv_firstframe (frame_buf, frame_dlc) > 0)
nwl_st = NWL_RECV;
else
nwl_st = NWL_IDLE;
}
break;
case PCI_CF:
if (nwl_st == NWL_RECV && g_wait_cf == TRUE)
{
if (recv_consecutiveframe (frame_buf, frame_dlc) = 0)
{
clear_network ();
nwl_st = NWL_IDLE;
}
}
break;
case PCI_FC:
if (nwl_st == NWL_XMIT && g_wait_fc == TRUE)
if (recv_flowcontrolframe (frame_buf, frame_dlc) 0)
{
clear_network ();
nwl_st = NWL_IDLE;
}
break;
default:
break;
}
OSMutexPost(UdsMutex);
}

 

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节讲的是链路层的设计,以及扩展地址情况下,数据单元的格式,
    我们普遍使用的都是普通地址,这一部分也不再详细介绍了。

 

文档下载:

ISO 15765[1].1(2004)道路车辆——控制局域网络诊断——第1部分:总体信息

ISO 15765[1].2(2004)道路车辆——控制局域网络诊断——第2部分:网络层服务

中文版

10 ISO 15765.3(2004)道路车辆——控制局域网络诊断——第3部分:一元化诊断服务实施(CAN的UDS)

中文版

ISO 15765[1].4(2005)道路车辆——控制局域网络诊断——第4部分:排放相关系统要求

中文版

————————————————
原文链接:https://blog.csdn.net/qq_28086637/article/details/73699677

Read 23844 times Last modified on Saturday, 11 April 2020 19:22

GPS singal acquisition,replay and test equipment

Portable singal acquisition and replay

AI intelligent tongue imager

Tongue imager rafavi
 
Please support our site by viewing this advertisement.

Please support our site by viewing this advertisement

Free Content