通讯程序理解全解.docx
《通讯程序理解全解.docx》由会员分享,可在线阅读,更多相关《通讯程序理解全解.docx(8页珍藏版)》请在冰豆网上搜索。
通讯程序理解全解
通讯程序理解
本程序中的通讯部分以103规约为规则,通过C语言编程实现控制系统与DSP控制板之间的通讯,并且将控制系统(即上位机操作)作为链路层主站,DSP控制板为链路层子站,遵照103规约,建立通讯联系,这是一种非平衡的传输方式,即控制系统一般作为启动站,而DSP控制板一般作为从动站。
在通讯过程中,数据信息以帧的形式传递,按照帧长是否可变分为固定长帧和可变长帧,一般情况下,复位,请求,响应帧(确认帧,忙帧,无所请求的数据帧等)都为固定帧长,在103规约中,每一个固定帧都有其特定的格式,详情请参考103规约。
固定帧长帧的格式如下图:
其中启动字符和结束字符为固定值,控制域和地址域携带本次上传或下传的简单信息;传输顺序为先低后高,另外需要注意的是,帧与帧的传输之间线路空闲间隔至少为33位,而字符之间不需要线路空闲间隔;可变帧长一般用来传输数据信息,其帧长不固定,其中的控制域和地址域与固定长帧的含义并无差别。
但是需要注意的是,可变长帧的报文头是固定的,这对于校验非常重要,这是可变帧长帧校验必不可少的校验对象,其之所以可变就是就是指的链路用户数据。
其格式如下图所示:
应用服务数据为可变长度的信息集合,信息集合的应用场合由类型标志,命令类型等确定,应用服务数据单元的格式如下,关于详细内容请参考103公约:
地址域长度为一个字节,其高四位是发送方的地址,低四位为接收方的地址。
控制域中在上层向下层和下层向上层的过程中所表示的含义有差别,具体参照103公约,其控制与的格式如下图:
注:
其中分为两部分的空格中,上面的一部分表示从上层到下层传输时的含义,下面的一部分表示下层到上层传输是的含义。
个人认为FCB较难理解,这里稍加阐释。
上层向下层传输报文时,将FCB取相反的的值,上层为下层保存一个FCB的备份,若超时未收到所期望的报文,或出现差错,则上层控制系统不改变FCB的状态,重传该报文,最多3次,如果3次以内上层收不到下层的确定或响应信息,则表示通讯故障。
ASDU的类型有很多,每一种类型对应着一种数据格式,这在103规约中有明确规定,这里只介绍下一般命令型。
个人认为一般命令型较为复杂,其余较易理解,这里就不在一一解释。
一般命令型的格式如下:
序号
说明
1
类型标志TYP:
01H
2
传送原因COT(见4.1.2)
3
命令类型FUN(见4.1.3)
4
附加信息SIN
其中附加信息在这里要详加解释,其每种命令类型对应的含义如下表:
命令类型FUN
附加信息SIN含义
查询定值<20H>
27定值区域编号20
定值区域切换<30H>
27切换后的定值区号20
请求启动记录说明<51H>
27记录表中的序号20
请求故障记录说明<52H>
27记录表中的序号20
其他
00H无意义
以下是对我们的程序的分析:
1.变量定义和结构体构造
变量包括通讯口状态,链路层通讯地址,校验信息,错误码定义,命令,功能码,类型标志,传送原因等,
对于结构体,首先建立数据应用层结构,应用服务数据单元接收部分具体结构如下:
一个完整的数据包由多个ASDU(应用服务数据单元)组成,每个ASDU包含在一个数据帧中
structinasdu_data
{
unsignedcharuAsduType;//ASDU类型标志
unsignedcharuCmdType;//命令类型
unsignedcharuFrmCount;//组成一个数据包的帧总数
unsignedcharuFrmIndex;//数据包中的当前帧序号
unsignedcharuData[10][255];//构成当前ASDU的字符数组,其中uData[帧序号][0]作为有效数据计数器使用
unsignedcharuValidFlag;//0ASDU不可用;1ASDU可用;
};
应用服务数据单元发送部分与之类似。
紧接着是链路层结构:
串口状态数据,数据发送之前全部放置在该结构体的数据中
structcom_data{
unsignedcharuComState;//串口状态
unsignedcharuTimeOut;//通讯超时次数
unsignedcharuDataIndex;//数据区位置索引。
接收状态时候该值为0,程序中将其加1,表示从第一个数开始存放,存放1个就将该寄存器加1,最后放入在COM.uInData[0]中;发送的时候该值为1,表示直接发需要传送的数据,发完一个数再加1
unsignedcharuInData[255];//输入数据区,其中COM.uInData[0]作为有效数据计数器使用
unsignedcharuOutData[255];//输出数据区,其中COM.uOutData[0]作为有效数据计数器使用
unsignedcharuACDFlag;//故障时间标志位
unsignedcharuFCBFlag;//帧计数位
unsignedcharuRadFlag;//广播标志位
unsignedcharuSourceAddr;//请求源地址
unsignedcharuDestinAddr;//目的地址
};
2.分析各个重要子函数:
●voidLPDU_Analyze();此子函数的功能是链路层数据分析及提取,也就是com数组中的一帧数据进行处理判断。
首先提取这个数据帧的长度,进而判断该数据帧是固定帧还是可变帧长帧,在103规约中,固定帧的帧长是5个字节,所以很容易判断是否为固定帧(因为可变帧长的报文头加上各种标志就已经7个字节,所以可以排除5个字节的可变帧长帧的干扰),根据判断结果提取出对应的控制域信息,COM.uInData[2]对应的是固定帧的控制域数据,而COM.uInData[5]对应着可变帧长帧的控制域信息,这个由103公约决定,上述格式中已经说明。
紧接着判断对应控制域的计数有效位是否有效,至于为什么将控制域数据与0x10进行&运算就可以判断是否有效?
请参照前文所述的控制域格式。
在计数有效位即FCV位有效后,((uCtlCode&0x20)!
=0)用来判断计数位的值,将声明的FCB标志位即uFCBFlag取与之相反的值,至于原因,前文已经讲到。
在取到uFCBFlag的值以后,在判断DSP是否将计数位变位,如果变位则表示数据传输成功,进而提取功能码,否则重传数据。
uFrmType=uCtlCode&0x0F;即提取到了功能码;然后利用switch语句对功能码进行判断;每个功能码的数值由103公约决定,在变量初始化中已经用define语句进行定义。
Switch中的语句都较为简单,在理解上文的基础上且在源程序中已经进行详细解释,在这里就不在详加赘述。
在该子程序的最后有判断数据是否有效的程序,分别是对故障标志位和ACD位进行判断,如果条件成立则进行事件记录。
●voidASDU_Extract();这个子函数的功能是从接收的数据中提取应用服务数据。
首先要得到应用服务数据的总长,至于为什么减2?
是因为L即总长包括控制域信息和地址域信息,这个上文中的可变帧长帧格格式中可以清楚地看到。
紧接着得到ASDU类型标志和命令类型。
然后利用switch语句对各种类型的ASDU进行相对应的操作。
switch语句中的大部分语句容易理解,就不再解释,但我认为以下部分需要深刻理解:
caseASDU3_SETSLIST:
//用户定值
InAsdus.uFrmCount=COM.uInData[10];InAsdus.uFrmIndex=COM.uInData[11];
看到这个我想很多人不明白,为什么COM数组中的第10和第11个跟别表示帧总数和当前帧数呢?
其实如果仔细看前文,这个问题已经解决过了,在链路层数据的前6个字节是各种链路规约的信息,而服务层中如果是整定值列表则第4个和第5个字节分别表示总帧数和当前帧数,详见链路层结构体。
至此我想已经解释清楚这个问题了。
在switch语句执行完之后,接着就是保存刚刚提取出来的当前帧数,然后保存应用服务数据的总长,紧接着利用for循环,将接收到的数据提取出来。
接下来就是判断当前帧数是否为总帧数,若成立则置位ASDU有效位,否则清零该位。
●voidASDU_Analyze();这个子函数的功能是分析提取出来的数据信息。
首先提取应用服务数据信息中的传送原因和命令类型,至于为什么是数组中的第2和第3个数据,上文中的应用服务单元的格式已说明。
然后就是利用switch语句对ASDU的每一种类型进行相对应的处理,InAsdus.uAsduType在voidASDU_Extract()中已经提取出来。
对于每个ASDU类型都有与其相对应的数据格式,详情请看103规约,这里的switch语句内的程序有几个地方较为复杂,现在就来一一分析。
caseASDU1_COMMAND:
uSIN=InAsdus.uData[0][4];
ASDU_01_Command_Execute(uFUN,uSIN);
如果是一般命令类型,则进入到命令类型的详细程序中去。
uSIN得到的是附加信息,用来判断该命令是一般命令类型中的哪一种。
其对应含义在前文中已经讲明。
caseASDU3_SETSLIST:
整定值下载命令
首先提取定值区号,然后提取总帧数。
在将计数变量归零之后利用for循环提取全部定值。
最后组装整定值,为后续程序做准备。
caseASDU11_CFMCMD:
定值与控制频率修改执行
首先判断InAsdus.uData[0][4]的值,根据值的不同进行相对应的操作,在这种ASDU类型下,InAsdus.uData[0][4]表示的含义在103规约中已做规定。
其余部分较易理解。
注:
未提到的数据类型较易理解,就不在详加赘述。
在该子程序最后另InAsdus.uValidFlag=0,即清零标志位。
防止该数据再被利用。
●charLPDU_Check();此子函数的功能是链路层数据的校验。
首先利用switch语句判断是固定帧还是可变帧长帧,分别对应必备的校验内容,其校验内容由103规约决定。
对于可变帧长帧:
先检验起始符是否正确,103规约中规定可变帧长帧的起始符为0x68。
接着判断帧的第二个字节与第三个字节是否相等,在程序中就是:
if(COM.uInData[2]!
=COM.uInData[3])
在可变帧长帧的格式中第二个和第三个字节都是总帧长的地址,所以判断这两个字节是否相等可起到校验作用。
紧接着是提取用户数据的总长度,uAsduLen=COM.uInData[2]-2,后面的减2是因为L包括一个字节的地址域数据和一个字节的控制域数据。
紧接着提取目的地址和源地址,地址域数据的高4位是源地址即主站地址,低4位是目的地址即子站地址,
提取之后判断子站地址是否为DSP板的地址。
紧接着校验校验和,校验和包括地址域,控制域数据及所有数据之和,可变帧的最后一个字节所存位校验和,将得到的校验和与之相比较,相等则正确,否则数据错误。
最后校验结束支付是否正确,可变帧长帧的结束字符是0x16。
至此可变帧长帧的校验已经结束。
下面开始讨论固定帧的校验,首先提取主站地址和子站地址,地址域数据的结构与可变帧长帧的结构相同。
接着判断子站地址是否为我们的DSP控制板的地址。
固定帧长的校验和只有地址域数据和控制域数据的和,将所得的校验和与已经存在的较验和进行校验,判断数据是否有效。
最后判断固定帧的结束符是否正确,103规约中规定固定帧的结束符为0x16。
在这里我想解释一下固定帧长为什么没有校验起始符?
其实起始符已经利用switch语句进行判断,如果起始符不正确根本进不来校验其他部分,而可变帧长帧之所以有校验其起始符的步骤是因为可变帧长帧的格式规定第一个字节和第4个字节都是起始符即0x68,在switch中已经检验第一个起始符了,而固定帧长帧的起始符只有1一个,即第一个字节,其实switch(COM.uInData[1]){}是优先级最高的校验。
总之一句话,对于不同帧的校验内容由其特殊格式决定。
到这里我想这个子函数已经解释清楚了。
●voidCommunicate();此子函数的功能是中断中使用的通讯程序。
利用switch语句判断端口状态并对其各个状态进行相对应的得操作。
当端口状态处于发送状态时,判断发送缓冲寄存器是否准备就绪,当SCICTL2寄存器中的最高位即TXRDY为1时,表示SCITXBUF准备好接收下一组发送的数据。
这里需要注意的是,该模式下串口一次只能发送一个数据,这样for语句应该就可以理解为什么是只能循环一次了。
将要发送的数据送给串口缓冲区,然后利用if语句进行发送过程是否结束的判断,如果数据发送完毕,或者发送到最后一个就结束,其判断语句用C语言的或运算符表示如下:
if(COM.uDataIndex>=COM.uOutData[0]||COM.uDataIndex>=254)
COM.uOutData[0]中存放的是一帧数据中的字节总数。
如果发送完毕则将端口状态置为等待状态,否则字节计数变量自加1。
如果端口状态为等待状态则直接跳出switch语句。
下面来讨论端口状态为接收状态的情况,首先提取位置区索引的值,当处于接收状态时,该值为0。
紧接着判断接收缓冲寄存器是否准备就绪,SCIRXST寄存器中的次高位即RXRDY为1时表示新数据可读。
然后在判断有数据的前提下,将串口缓冲区数据取出,放入通讯数组中,计数变量加1,并且通讯超时次数变量和用来表示通讯错误的变量都置0。
当数据接收完毕后,判断计数变量是否与程序一开始提取的计数变量相等,若相等则表示数据接收失败,并且将异常计数变量加1。
最后判断通讯异常变量的值是否大于3,如果大于3成立,则表示三个中断都没有接收到数据,表示此次通讯结束。
将已经记录的数据总数保存到[0]的数据单元。
并且将通讯状态改为等待状态。
注:
此程序中还未理解的部分:
最后那个计数判断和相对应的操作。
一次只能发送一个帧由什么决定?
接收?