- 易迪拓培训,专注于微波、射频、天线设计工程师的培养
基于VB6.0的串行通信中的错误预防方法
1 引言
随着计算机技术特别是单片机技术的发展,通信在诸多领域得到了广泛的应用,技术人员可以通过计算机的串口来获取设备的各种数据,然后利用计算机强大的运算和分析处理功能进行处理,再根据处理的结果发送数据来控制设备。通常通信的型式可以分为两种,即并行通信(parallel commuNIcation)和串行通信(serial communication)。就通信速度来看,并行通信一次的传输量为8个位(1个字节),而串行通信一次只传输1个位(也就是一个标准电位状态),很明显,并行通信的传输速度要快得多。但就传输的安全性来说,由于并行通信在传输过程中更容易产生电压衰减和信号间相互串音干扰等问题而使得传输的数据发生 错误,相对而言,串行通信一次只传输处理的数据电压只有一个标准电位,因此不容易把数据丢失。
不论是采用哪种通信方式,在数据的传输过程中,数据都有可能受到干扰而使得原来的数据信号发生扭曲,此时的接收到的数据当然是错误的,为了检测数据在发送过程中发生的错误,发送与接受必须对数据进行进一步的确认工作。最简单的方式就是使用校验码(checksum),其次是使用crc(cyclic redundancy check CODe,循环冗余校验码)。
2 串行通信
2.1 通信步骤
通常情况下,串行通信过程就是交换字符串的数据过程,而数据的交换必须有一定的格式。通信的双方才能根据一定的数据格式针对所传输的数据进行解析,以工业上最常用的PLC为例,计算机与plc进行通信时,任何厂牌的机器均会定义通信协议,所谓的通信协议就是服务器端与客户端的通信语言及定义。每一次计算机与设备的数据交换都会有3个步骤,
(1) 设备要求发送数据时,计算机会发送一组字符串过去
通常该字符串的第一个字符就是前导码,设备根据前导码辨认是否应该读取该字符串、该字符属于哪一个命令集,以及用什么格式去解读该字符串等。前导码不会是一般的字符,通常是一些不可见字符(位于ascii码的前30个)或很少被使用的符号字符,这是因为避免数据字符与前导码一样而发生错误判断。在前导码之后是后站号,通常是以两个字符代表,单纯以rs-232连接的单一设备也许不需要站号的设置,不过如果以rs-485进行网络连接,就需要用站号来辨别命 令是属于哪一个设备。站号之后就是要设备解读的命令或数据。
(2) 当设备收到要求字符串,并经过判断确定后,便会送出计算机所要求的数据
同 样,数据被送出时会在数据之前加上前导码与站号,计算机也以此前导码与站号判别数据来自何处。
(3) 握手
计算机端接收到设备发送回来的字符串即进行解读检查的操作,当检查完成后,便再送一个确定的字符串给设备,用以说明计算机端已成功收到字符串;而发送失 败,计算机也在该回送的字符串中要求设备重新发送数据。
以上3个步骤就是计算机与设备数据的发送交换的一般情况,对于不同的设备可能会有不同的数据发送流程,应视具体情况而定。另外,在数据发送时,一般都会加上错误检查机制,最常用的方法是将字符进行计算,而在字符串的最后加上checksum字符。发送的双方利用checksum字符的检查而判断出字符串的 正确性。
2.2 串行通信方法
就工业上最常用的串行通信来说,我们在用vb开发串行通信程序中通常用到两种方法:
(1) 一是利用windows的通信api函数;
(2) 另一种是采用vb标准控件mscomm来实现。
本文采用第二种方法,其实,mscomm控件的通信功能实现,实际上也是调用了api函数,而api函数是由comm.drv解释并传给设备驱动程序的,对于vb程序开发者只需知道mscomm控件的属性和事件的用法就可以实现操作。在该控件的setting属性设置中, 如:mscomm.setting=9600,n,8,1就表示所使用的通信端口是以每秒9600为的速率进行传输,不进行奇偶校验位的检查,每个数据单 元是8个位,而停止位是1个位。
3 checksum的使用
前面提到过的奇偶校验位的使用在于避免传输过程错误的发生,导致数据的不正确;不过,由于在串行通信中每一组的传输过程最多只有一个字节,而奇偶校验位的检查也是针对一个字节。当传输的数据量增大时,如何确保数据的正确性 一般的做法也是类似奇偶校验位的方式,将检查码放到所发送的字符串中同时发送到接收 端,接收端再对数据进行一次检查码的确认,而检查码的使用最常见的就是checksum。
checksum的实际做法因设备的不同而异,主要的做法是将双方发送的字符串中的字符一一地进行相加(以对应的ascii码),再对255或者128取 余数,所得的余数即为checksum。部分的做法是将该余数当成一个字节含在原来发送的字符串尾端送出;还有一种是将该余数转成两个字符,并含在原来的 字符串尾端送出,现在我们对255取余数示范第二种情况。
针对checksum的计算,首先必须将所要发送的字符串中的字符一个个地转换为acsii码中所对应的号码。在visual basic中,将一个字符转换为ascii码的函数就是asc函数例如:
no1=asc{“$”}
所返回的结果就是字符”$”在ascii码中的号码(结果是36)。根据该号码将全部发送字符的ascii码相加,所得的数值取十六进制,并取该十六进制 中的最末端两位,即为checksum的结果。程序的部分代码如下:
checksum = 0 '初始化
for i = 1 to buflen
buf = mid(inbuf, i, 1) '取出字符
'和&hff进行and运算可以将结果限定在&hff以内,即取255的余数
checksum = (checksum + asc(buf)) and &hff
next i
上述程序的最后,checksum变量即为进行完计算的数值,为了使其可以同时和原字符串一起发送,只要在程序中加入hex(checksum)即可形成 字符。hex函数会将要转换的数值转成字符串形式,因此checksum数值就会变成了字符串。
4 程序实现
由于checksum的使用必须使得通信的双方都同意,所以除了发送方必须进行checksum的计算外,接收方通常也必须针对对方传来的字符串再进行一 次确认的操作,该确认的操作也必须再执行一次计算的工作,若为了方便,最好的方式就是开发两个子程序,一个专门进行输出字符串时的checksum计算及 合并原字符串的工作;另一个则是在接收到对方传来的字符串后,再一次计算其checksum的结果,并和字符串尾端的checksum结果进行比较,确认结果的正确性。因此笔者分别就这两个部分开发了两个函数。
4.1 发送端函数outchecksum
用于处理所要送出的字符串,只要将所要送出的字符串传入,返回的结果字符串即已经过checksum处理,其流程图如图1所示:
图1 输出命令时的checksum流程图
而程序编写则如下:
function outchecksum(inbuf as string) as string
dim buflen as integer, buf as string
dim i as integer
dim checksum as long
buflen = len(inbuf) '取得传入字符串的长度
checksum = 0 '初始化
for i = 1 to buflen
buf = mid(inbuf, i, 1) '取出字符
'和&hff进行and运算可以 将结果限定在&hff以内,即取255的余数
checksum = (checksum + asc(buf)) and &hff
next i
'字符串重组,并加上结尾字符cr
buf = iif(len(hex(checksum)) = 1, "0" & hex(checksum), hex(checksum))
outchecksum = inbuf & buf & vbcr
end function
上述的函数中使用了iif函数的第1个参数是判断式,当该判断式成立时,第2个参数结果返回;当该判断式不成立时,则返回第3个参数。我们用来判断所形成 的checksum是一位数还是两位数,一般均要求checksum字符串必须是两位数,故不足的位数前面必须加上一个0。
4.2 接收端函数inchecksum
用于处理接收到的返回字符串,只要将所接收到的字符串传入,返回的结果字符串即已经过checksum检查,并删除checksum及结尾字符,其流程如 图2所示:
图2 检查返回结果的checksum流程
而程序编写如下:
function inchecksum(inbuf as string) as string
dim buflen as integer, buf as string
dim i as integer
dim checksum as long
buflen = len(inbuf) '取得传入字符串的长度
checksum = 0 '初始化
for i = 1 to buflen - 3
'需扣掉两个checksum字符和结尾字符
buf = mid(inbuf, i, 1) '取得字符
'和&hff进行and运算可以将结果限定在&hff以内
checksum = (checksum + asc(buf)) and &hff
next i
if checksum = val("&h" & mid(inbuf, buflen - 3 + 1, 2)) then
inchecksum = mid(inbuf, 1, buflen - 3)
else
inchecksum = ""
end if
end function
有了以上的两个函数,只要在输出时先调用outcheck sum将检查码计算出;而接收到数据后,送入inchecks