基于CAN总线的汽车诊断协议UDS(⽹络层ISO15765)
上个⽉⼀个同事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) quest
是⽹络层提供给上层的发送消息的服务,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)firm
该服务⽤来通知上层,消息发送已经完成,并返回成功与否,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
h5论坛
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
#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后,应该终⽌发送,调⽤firm 服务,with N_BUFFER_OVFLW
3-F, Reserved
如果发送者收到的FS出错,⽹络层应该停⽌消息发送,并且调⽤ firm 服务,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超时。
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%。(这是⼀堆废话,按照车⼚的诊断规范确定超时值)