Visual C++串口通信开发入门与编程实践(txt+pdf+epub+mobi电子书下载)


发布时间:2020-08-26 10:15:38

点击下载

作者:周韧研,商斌

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Visual C++串口通信开发入门与编程实践

Visual C++串口通信开发入门与编程实践试读:

前言

计算机的体系接口是计算机体系中的重要组成部分,体系接口的发展也是计算机技术发展的一个重要标志。计算机体系接口复杂多样,在它的发展过程中,总有一些接口在慢慢消失,又有一些接口在不断出现。在当代,主流的体系接口有PCI/PCI-E接口、USB 1.0/2.0、百兆位以太网等,新兴接口还有蓝牙、USB 3.0、吉位以太网、光纤等。这些体系接口都能够提供通用计算机与包括嵌入式系统在内的非计算机电子设备之间进行通信的功能。

无论是主流的体系接口还是新兴的体系接口,其传输速度和传输可靠性都使得传统体系接口(并口、串口)不能望其项背。但与此同时,这些接口的复杂性也是传统体系接口所不能比拟的。这体现在两个方面:一个是下位设备,也就是与通用计算机通信的设备的复杂性。通常,为了支持诸如以太网或者USB的接口,要么需要专业的硬件或者嵌入式软件开发者投入大量的精力进行开发、验证和维护,要么花费资金购买相应的硬件授权(IP核)或者接口芯片,硬件(嵌入式软件)的复杂性必然带来各种硬件开销(硬件量、功耗等)的增加。另一个是通用计算机上软件的复杂性。对支持以太网或者USB通信的软件的开发,由于体系本身的复杂性,程序员需要拥有相当的知识储备和过硬的调试能力。

考察在科研和生产中所使用的电子电气设备和与通用计算机的交互状态,我们发现,有相当一部分设备并不需要主流或者新兴计算机体系接口所提供的大吞吐量,而是往往对通信的实时性有特别的要求。显然,对于这样的情况,使用主流或者新兴计算机体系接口就不合适。嵌入式设备,特别是嵌入式计算机系统,都希望能够通过一种简单、可靠、高实时性的接口与通用计算机通信,而且,在通用计算机上运行的软件也要易于开发和调试。作者认为,正是由于这个需求的存在,是通用计算机上的异步串行通信接口(UART)能够保留到现在的原因。

针对通用计算机上的异步串行通信接口开发的特点,结合目前较为新颖的开发思路,我们组织编写了本书。归结起来,本书具有如下特色:

●理论和实践的结合。针对目前接口开发教程对通信理论、硬件技术介绍不足的缺点,本书着重介绍了一般的通信理论和软硬件相连接部分的技术细节。例如,当我们回答为什么要在软件层面也实现某种协议的时候,在理论和硬件实现的原理上就能够找到答案。同样,在软件中启动一个操作,在硬件层面上会发生什么变化,本书也有详细的说明。

●软件技术的新颖。在本书中,我们抛弃了Windows下串口通信中经常使用,但又被不恰当使用的MSCOMM控件,而是从基本的Windows API函数讲起,然后介绍它的类封装。在图形界面开发的部分,我们不仅介绍主流的MFC技术,而且对目前非常流行的Qt库中如何使用串口通信进行了详细说明。

●工程实例的新颖。在工程实例介绍的环节,我们介绍了DSP系统、串口到以太网通信等目前嵌入式领域发展迅猛的技术,供有相似工程需求的读者进行参考。

全书共分12章,分别介绍如下:

第1章——串行通信的基本概念。介绍与串口通信密切相关的理论和技术概念,首先从通信问题的数学模型切入,在此基础之上,解释EIA RS-232的基本原则、规范,以及高层次协议设计的必要性。

第2章——异步串行通信接口电路简介。介绍在通用计算机和嵌入式设备中兼容串口电路的实现,以及软硬件之间的体系分界。

第3章——在Windows NT中搭建开发环境。本章中介绍平台和开发工具,首先说明进行串口程序开发所需要的硬件和软件环境,然后结合第一个串口程序Hello World介绍Visual C++的使用。我们把重点放在开发流程上,因为有一个良好的流程是非常重要的。

第4章——使用Windows API串口编程。Windows以SDK串口通信函数的方式提供了应用程序对设备的操作接口。Windows API是串口通信开发的基本。本章将结合示例详细介绍与串口通信相关的Windows API的使用方法。

第5章——使用CSerial类。使用非面向对象的SDK编程不利于程序的维护和代码复用。本章将介绍一种基于C++的通信API封装——CSerial类。CSerial类适用于文本程序界面和图形程序界面,代码量少,使用方便。我们以CSerial类为基础,介绍它的使用方法和在图形界面程序设计中的应用。

第6章——使用Qt进行串口编程。当今,跨平台的快速应用开发已经成为主流。Qt是一款高性能、跨平台的C++应用程序开发框架,包含一个类库和一系列的工具。在官方的Qt库中没有提供串口编程的类,本章以开源项目QextSerialPort为例,介绍如何使用Qt开发串口应用程序。

第7章——Windows下双机点到点串行通信系统设计与开发。Windows下双机的串行通信系统是一个典型的通信系统,它是我们为了实现计算机底层的工作,以及为了用户更好地和系统能够直接相连而提出来的,它不但可以广泛地应用于各个领域中,还可以在比较艰苦或者不方便的情况下如没有网线的时候进行双机通信。

第8章——16位高速DSP增强型同步串口的设计。本章主要针对DSP芯片外设电路中的增强型同步串口及其他一些外设模块开展工作。外设电路对于提高整个芯片的性能起着非常重要的作用,它是内核和片外电路的接口,负责外部电路的数据交换,外设电路性能的好坏直接影响整个芯片的工作。

第9章——串口与以太网数据传输实现。串口和以太网口数据转换模块可以应用在串口设备需要远程传输文件的场合,如数控机床控制文件的远程传输等。本章首先介绍该类模块在国内外的现状;然后经过分析比较,选取了Rabbit公司的RCM2200模块来实现以太网口和串口的数据传输;接下来介绍RCM2200微控制器核心模块以及Dynamic C软件开发环境,在此基础上选择并实现了串口和以太网传输协议,即XMODEM和TFTP协议;最后介绍实现文件传输的整体方案,通过计算机的实验演示和数控机床的实际应用,都达到了比较好的效果。

第10章——基于串口的DNC信息采集系统的开发。数据采集是分布式数字控制(DNC)系统的一个重要功能。在自动化制造中,无论是信息检测、测试与监控、物流和设备管理,还是设备诊断与维修,均是以数据采集技术作为支撑的。可以说,先进合理的数据采集技术是实现DNC、MES、MRPII、ERP和制造自动化的重要基础。DNC信息采集系统作为DNC的子系统,是DNC获得底层信息的主要来源,在DNC系统中占有重要地位,是DNC的核心内容之一。

第11章——Windows XP下USB转RS-232桥接器驱动程序开发。本章利用Windows XP DDK、Visual C++6.0、DbgView、SoftICE等开发和调试工具开发出了以Windows XP为平台的桥接器驱动程序,创建了和真实串口功能基本相同的虚拟串口,为桥接器的使用提供了软件保证。首先对桥接器硬件设计进行了分析,对WDM(Windows Driver Mode)驱动模式进行了说明;然后设计了桥接器驱动模型,确立了开发方案,明确并设计了所需的各种机制,设计了重点例程的实现策略,用驱动程序中的典型用例来说明具体实现;最后对驱动测试、安装过程进行了说明,对整个设计和应用做了总结,并提出了进一步的完善思路。

第12章——串口通信在机器人实时控制中的应用开发。主要以MOTOMAN UP6机器人为载体,介绍Visual C++环境下机器人软件控制系统的开发过程。本项目主要是通过PC来实现机器人各种运动的控制。PC和下位机之间通过一条RS-232C串口线进行串口通信。本项目采用了多线程编程技术,并将各种不同的运动模块封装起来,使其具有可移植性,便于以后开发工作的进一步深入。本控制系统界面友好,在测试过程中实现了对UP6机器人的运动控制,并且能够对机器人的动作进行实时监控与图像记录,还能够对机器人进行单步控制,实现了对UP6功能的扩展。

全书讲解由浅入深、通俗易懂、注重实践,是一本不可多得的程序员手册。本书实例源代码和项目设计代码请到www.fecit.com.cn的“下载专区”中下载。

本书适合于对电子电路和计算机体系有初步了解,但对软件开发掌握不多的读者作为自学用书,也可作为自动化、电气、工控、机械等领域有相关需求的工程人员的参考书。

由于作者水平有限,书中疏漏和不当之处在所难免,欢迎读者指正。联系信箱:DevEmb@gmail.com,详情请垂询http://books.vcer.net/vcserial。编著者联系方式

咨询电话:(010)6813454588254160

电子邮件:support@fecit.com.cn

服务网址:http://www.fecit.com.cn http://www.fecit.net

通用网址:计算机图书、飞思、飞思教育、飞思科技、FECIT第1章 串行通信的基本概念

作为计算机接口软件的开发者,非常有必要对硬件相关的原理和协议有一定的了解。本章将介绍与串行通信密切相关的理论和技术概念。首先从通信问题的数学模型切入,在此基础之上,解释EIA RS-232的基本原则、规范,以及高层次协议设计的必要性。我们还将介绍串口通信接口——一种在计算机体系当中几十年来一直保持不变动的通信构架。

串口通信是串行通信实现的一种方式,也是最古老的方式之一。然而,时至今日,尽管速度快、稳定性高的串行通信体系层出不穷,但其底层在实现上与串口通信并无实质性的差别。1.1 从电路到通信系统

电子电路与电气电路在目的上首先就有很大的不同。电气电路强调所传递的能量,而电子电路则关注在一定的能量下,能够传递多少信息。

通信系统是复杂的电子电路。如果将通信系统狭义地用到计算机数据接口通信,则它仍然是一个复杂的电子电路。那么,在电子电路到通信系统的演化过程中,复杂程度的增加难道仅仅意味着电子器件数量的变化,或者电路拓扑结构的复杂化?

一个系统一旦涉及到信息的传递,则经典的电路分析理论就显得不足了,这时需要更高层的数学工具对系统的性态和行为进行分析。在这些工具中,首先用到的就是信息论。1.1.1 应用信息论简要

在信息论中,首先被定义的是信息。

信息是与事件的随机性密切相关的。简单地说,如果某个事物或者过程有若干不确定的状态,那么我们就说这个事物或者过程含有一定量的信息。也就是说,事物对于不了解它的人来说,含有信息。对于事物进行了解的过程也就是消除一些不确定性的过程,是信息的传递。

信息论就是这样一个讨论信息本身以及信息传递过程的理论。为了表述信息以及信息的传递过程,信息论使用熵和互信息这两个概念。

熵是事物所含信息的度量,其实质是概率分布到正实数的一个映射关系。但并非所有的映射关系都能够称做熵。信息论给出了一系列的条件,这些条件在今天被看做是一组公理。

使用概率负对数的期望作为熵,是可以被证实满足熵公理的。

假设这样一种情况:我们取一枚均匀的硬币投向空中,硬币在空中翻滚的过程中,我们不能够预测落地后的硬币哪一面朝上。此时我们说这个事件含有信息。

虽然我们不知道硬币最终哪一面朝上,但是我们知道的是,硬币最终的状态只可能有三种,一种是正面朝上,一种是反面朝上,一种是恰好竖立在地面上。经验表明,第三种情况的可能性微乎其微,我们称之为小概率事件,或者称为不可能事件。前两种情况则各占一半的可能性。

我们知道,在求期望的过程中,小概率事件的成分可以被忽略(这个结论不是显然的,而是因为无穷小与无穷小的对数的乘积趋于0)。因此,可以认为,前两种情况各占50%的概率。那么这个分布就是(50%,50%),我们求这个分布的概率负对数的期望。其求法是,对于每一个概率,先求对数,然后求相反数,再与概率本身相乘,最后把每个概率算得的积相加。

很容易得知,如果以2为底,计算上面所示分布的负对数期望,其结果应该是1。这也是硬币翻滚时,以熵作为度量的所含有的信息。当所选对数的底为2时,我们把熵的单位称为比特(bit)。换句话说,翻滚中的硬币含有1比特的信息。

这里的比特和计算机系统中度量信息的比特的含义完全相同。这是因为,任何存储系统均以两种对立的状态存储信息。在未知处于何种状态的情况时,可以认为存储的数据和翻滚中的硬币没有区别。

已经确定状态的事件含有多少信息?由于确定事件可以看做(100%,0,0,0……)这样的分布,那么无论以何为底求对数,其期望结果都为0。也就是说,确定的事件不含有任何信息。

我们看到,信息论对于事件本身处于某种状态,以及事件产生某个结果,并不关心。信息论分析的重点在于事件所处的若干种状态各自的可能性,也就是所谓的概率分布。概率分布的确定决定了信息量的确定,而事件状态的确定过程意味着信息量坍缩为0。

信息的传递如何描述?刚才说过,消除不确定性就是传递了信息,但是,消除不确定性是个后验的,如何通过统计规律确定信息的传递?

信息论认为,如果一个事件X不同的状态会导致另外一个事件Y发生概率分布的变化,就认为有信息进行了传递,反之亦然。事件X称为信源,事件Y称为信宿。

传递的信息量是通过互信息进行表述的。说到互信息,就不得不涉及条件熵。什么是条件熵?先取X的一个状态,观察Y的概率分布。由于X的状态已经确定,所以此时Y的概率分布也是确定的。我们来计算这个熵,这个熵因为和X的某个特定状态有关联,我们不妨称为在X状态下的状态关联熵(不同的论著对这个熵的命名各有不同)。X的不同状态有不同的概率,我们把状态关联熵以其所关联的状态概率进行加权求和,这个和就是已知X分布的条件下,Y的条件熵。状态关联熵所表述的模型称为信道。

总的来看,事件Y含有多少信息?我们可以想像这样一种场景:一个小孩放学回家。他有75%的可能性会回自己家,有25%的可能性去同学家。不碰巧的是,如果他去了同学家,妈妈很有可能打电话叫他立即回家,这个事情发生的可能性是80%。问题是,总的来说,这个小孩最终回到自己家的可能性有多大?不难计算,应该是75%+25%×80%。这个算法称为贝叶斯公式,其计算的依据是排列与组合中的加法和乘法原理。那么小孩回家这件事含有多少信息?实际上相当于计算一个(75%+25%×80%,1-75%+25%×80%)分布的熵。信宿Y的熵也这样计算。

互信息就定义为信宿的熵与已知信源分布的条件下信宿的条件熵之差。想像一种极端情况:无论X处于何种状态,Y的概率分布均相同,此时所计算的条件熵和Y本身的信息量相同,互信息为0,这就是说,在此过程中传递了量为0的信息,即,一个事件X不同的状态没有导致事件Y发生概率分布的变化,就会传递量为0的信息。同时可以严格证明,互信息总是正数。

信源、信宿与信道是通信中的基本要素。通常,信源和信宿具有一致性,即由信源的特性决定信宿的特性,也就是我们常说的收发机协议匹配。信道常常被独立考虑。

以经典的概率与统计理论为基础,信息论通过严格的数学证明,发展了一系列结论,其中最著名的就是编码理论,这是现代通信技术的理论基础。

编码理论主要讨论在点对点的信息传输中,应该如何对信息进行表达,以获得特定评估方法范围内的最优化。

如图1-1所示为一个点对点的通信系统。图1-1 点对点的通信系统

编码理论分为三个方向,即无损信源编码、有损信源编码和信道编码。信息论的创立者建立了一整套严格的数学体系对这三种编码进行了描述。

信源就是产生事件的个体,事件的不同结果称做信源的不同状态,每一个状态称为一个符号(Symbol)。如果把信源看做概率分布含有时间参数的随机事件,即随机过程,则无损信源编码就需要解决以下若干问题:

●如何用一定长的码(Code)序列表达信源的符号(编码)。

●如何评价编码的有效性。

●接收端恢复信源符号的复杂性如何。

我们以投骰子为例,投骰子结果的可能性有6种。如果将投骰子看做信源,则6种结果就是这个信源的6个符号。假设这样一个情景,一个人A在不停地投骰子(随机过程),另外一个人B通过电报机把每一次投掷的结果告诉相距甚远的朋友C。

如果按照最一般的方法使用电报机,那么电报机每一次只能发长和短两个码。不妨称长码为1,短码为0。用一段0和1的序列表示投骰子的结果,这就是二进制信源编码。“二进制”的意思是每一个码有两种取值。

采用多少进制的码要取决于信道。如果信道只允许传送两个状态,就必须用二进制的码。由于一个码最多代表两个符号,所以要使用多个码表示更多的符号。由于要这些码依次穿过信道,所以就有了码串(Word,也叫码字)的概念。编码就是用不同的码串代表不同的符号,码串之间的互斥保证接收端准确地还原符号。有了码串之后,最关键的是信源的符号如何与码串建立起一一对应的关系。

在骰子问题中如何编码?首先想到的是采取十进制转二进制编码的方法。码串0对应点数1,1对应点数2,10对应点数3,11对应点数4,100对应点数5,101对应点数6。信息论采用了平均码长描述编码方案的效率。平均码长就是每一个信源符号对应的码串长度与信源符号概率的加权和。按照平均码长的定义,这种编码的平均码长是1/6×1+1/6×1+1/6×2+1/6×2+1/6×3+1/6×3=1.5(bit)。

使用这种编码的前提是,每一个编码之间的时间间隔要足够长,否则接收端将不能够区分不同的码串。例如,一个4bit的串0100,接收端既可以解释为0、10、0,也可以解释为0、100。所谓“间隔”,就是指除了0和1之外的“第三态”。在电报通信中,可以利用无消息表示间隔,然而在数字通信中,二进制非0即1,不定态是不允许的。

一个简单的解决方案是等长编码。在不足3bit的编码前补0使之达到3bit,令接收端每三个比特截取一次码串并译码。这样就避免了码间模糊的问题,但是这无形之中增加了码长。3bit等长编码的平均码长是3bit。

等长编码在大规模的符号量面前会带来码串长度的指数级暴涨。比如,16bit的等长码可以对65536个符号进行编码,但是若要对65537个符号编码,就必须使用17bit的码长,17bit的码串能够对2的17次方(也就是131072个符号)进行编码,所以编码后,65535个码串被浪费掉了。为了解决这个问题,人们找到了前缀码编码方法。所谓前缀码,是指在编码方案里,没有码串是其他码串的前缀。前缀码的译码不需要回溯(立即译出,而无须借助后续的码字信息),所以也叫即时码。(0,10,110,1110,1111)就是一组能够代表5个符号的前缀码,假设接收端收到序列01011111010,则可以立即断句为0,10,1111,10,10这5个符号,并且不存在其他的译码结果。使用Kraft树可以方便地对任意多的符号集合进行前缀码编码。一般来说,前缀码比等长码短。假设符号是等概率的,它的平均码长为2.8bit,要比平均编码的4bit短。

前缀码编码同时带来了码本身的节约和编解码的便利,虽然绝大多数情况下前缀码的码长都要比等长编码短,但是前缀码所谓的最优性仍然没有得到确定。这一点很明确:在一个非等概率分布的符号集合中,把一个长的码串分配给出现概率大的符号是非常不明智的做法。

定性地说,一个最优的码应该把较短的码串分给出现可能性较多的符号。仍然以骰子为例,我们将出现点1、点2、点3的事件进行合并,称为“小点”,这样,投骰子就变成了一个不等概率分布,即(小点,4点,5点,6点)=(1/2,1/6,1/6,1/6)。如果用等长编码,那么平均码长是2bit;使用前缀码(0,10,110,111)作为码串集,并指定小点为111,4点为0,5点为10,6点为110,则平均码长为3×1/2+1×1/6+2×1/6+3×1/6=2.5bit;若指定小点为0,4点为10,5点为110,6点为111,则平均码长为1×1/2+2×1/6+3×1/6+3×1/6=1.83bit。相比较而言,最后一种编码方案具有最优性。

那么,对于给定的信源符号概率分布,使用前缀码编码,其码长的最优性(即最小码长)能够达到多少?香农第一定理给出了这个问题的解答。这个定理指出,最优前缀码编码的平均码长的可取范围与信源的熵有关。D进制信源编码的最优码平均码长不低于以D为底的信源概率分布的熵。

由于发送信息所消耗的能量以及存储信息所占用的空间都是以比特为单位,因此,大平均码长意味着系统开销的增大。考虑二进制编码,对于一个平均分布的信源而言,若有N个符号,则这个信源的熵就是log(N),这与二进制等长编码的平均码长是相同的。因此,平2均分布信源的等长编码是最优码。著名的ASCII码就是基于这样的前提而确定的。

可是,对于一个非平均分布的信源,又如何编码?香农第一定理并没有提供解决方案。可以证明的最优的编码方案是Huffman于1952年提出的,这个编码方案就是著名的Huffman编码,如图1-2所示。图1-2 Huffman编码

以骰子的小点合并信源为例,它的符号分布为(1/2,1/6,1/6,1/6)。按照Huffman编码的方案,首先将两个1/6(即最小的概率)分别标记为1和0,再将它们合并。新的概率分布为(1/2,1/6,1/6+1/6)=(1/2,1/6,1/3),将1/6记为0,1/3记为1,将它们合并,得到(1/2,1/2),分别记为0和1。最后进行回溯。“小点”对应的编码就是自己的标记,为0;倒数第二次的1/6,即4点对应的编码,为1/2的标记与它自己标记的联合,即10;倒数第三次的两个1/6,即5点和6点的编码,为1/2的标记、1/3的标记与它自己的标记的联合,为110和111。这样就得到了(0,10,110,111)一组前缀码,它和刚才提到的那组前缀码相同,不仅在其他两种比较方案中是最优的,而且在其他任何前缀码方案中都是最优的。

有了Huffman编码,则对于确定概率分布的信源总可以找到最佳的前缀码编码。然而,包括语言在内的绝大多数信源的概率分布不能够直接确定,需要大量的实验进行估计。这也是无损编码算法(比如大家熟悉的ZIP、RAR)能够百花齐放的原因。

前面主要讨论的是无损编码。有时候我们并不需要100%的无损。在声音和图像压缩领域,在信源编码时损失一部分数据,解码后并不过分影响视听效果。这就是有损压缩。

在信息论中,有损压缩的核心理论是保证度准则下的率失真编码。即给定一个编码到解码的失真度量,探讨码率(单位比特所含信息量)的极限。香农第三定理的一系列推论能够给出码率的极限,但是没有给出具体的编码方法。一种行之有效的失真度量和编/解码方法仍然是当今音频和视频处理领域的热门话题。本书讨论的话题属于无失真编码的范畴,因此,失真编码到此不再展开。

接下来我们谈一谈信道编码。

在前面已经介绍了信道是信源向信宿施加影响的通道。信源和信宿的本质都是具有一定状态概率分布的事件。信源的分布变化通过信道向信宿施加影响。

信道的概念实际上非常宽泛。通过条件概率分布的逆命题我们知道,如果事件A的分布对事件B的分布具有一定的影响,那么事件A和B之间就存在信道。也就是说,在人们有意识地建立信道之前,一些信道就有可能存在了。例如,天降雨和河水泛滥就有一定的关系,那么就可以认为两件事情之间存在信道。反复强调,信道是数学上的模型概念,而不是具体的电话线、网线之类。这一点在失真信源编码理论里有很深刻的体现。

信道的一个最大的特点是会受到噪声的干扰。这里的噪声也是一个宽泛的概念。实际上,噪声源也是一种信源,不过,噪声源的状态通常符合某种特定的分布,最典型的是高斯分布,这样的噪声也叫高斯噪声。

在信息论中使用信道转移概率矩阵来描述一个信道。假设信源形成M个符号(注意,这个符号可能是信源的原始符号,也可能是经过编码后的码串。不管怎样,在编码的过程中,需要处理的事件状态总是称为“符号”,而生成的字符叫做码。请读者在阅读过程中注意上下文),而信宿会接受到N个符号。如果信道能够忠实地传递信息,则M和N应该相等。然而,由于噪声的原因,码字在传输过程中,有一定的可能性会变成另外一个符号。尽管噪声是随机的,但是噪声造成的符号转移的概率通常是可以确定的。如果噪声造成的符号转义概率是确定的,那么这个信道的转移概率矩阵也是确定的。

信道转移矩阵是一个M行N列的矩阵,处在第i行第j列的数是信源产生第i个码字与信宿接收到第j个符号之间的条件转移概率q(j|i),即发送符号i的前提下接收到符号j的概率。对于理想无噪声信道而言,M=N,当i=j时,q(j|i)=1,否则q(j|i)=0。

当信源和信宿连接到信道上后,一次传输过程中所传递的信息可以使用互信息来表达。但是从互信息的概念上来说,它的取值不仅和信道转移概率有关,而且与信源的状态分布有关。这对于描述信道而言,并不是一个好消息。我们需要一种和信源无关的量,对信道的信息传输进行描述。考虑到互信息的有界性,信息论规定,对于信道概率转移矩阵给定的信道,穷举所有的信源分布,并找出其中最大的互信息,将其定义为信道的信道容量。即C=max(I(X,Y)),对所有X。

给定信道概率转移矩阵,计算信道容量并不是一件容易的事。对于简单的信道概率转移矩阵,可以通过求函数极值的方法确定信道容量;对于复杂的信道概率转移矩阵,需要用优化理论的相关知识。

噪声的出现引入了信道传输过程中的错误,在数学上,这个错误是使用信源符号最大错误概率确定的,称做误码率。为了检查和纠正错误,需要提供额外的信息。因此,需要将信源符号进行处理。

一个最直观的方法是额外的传输。例如,约定每个符号传3次。信宿接收到符号后,每三个分为一组,选出每组中出现最多的那个符号,认定为所传递的符号。这样,即便是传输过程中有一个符号发生了变化,仍然可以获取正确的信息。

重复法是最简便易行的抵抗信道错误的方法。例如,在打电话的时候,如果听得不清楚,再说一遍往往就能解决问题。

从这个简单的例子里我们还能够看出:如果不加冗余的进行信道编码(实际上没有编码),则每个信道码承载了符号的全部信息。信道编码后,码变多了,符号的这些信息被分担。对于信道而言,每个码出错的概率是一定的。如果没有信道编码,则码信息的流失就意味着符号信息的流失;如果进行了编码,那么即便是丢失了几个码,信息仍然可以得到恢复。

信息论将这种分担的过程定义为码率,即信源符号的个数的对数与码串长的比值,也叫平均码信息量。例如,3次重复法,对于一个000~111的8个符号的信源来说,码串的长度为3×3=9,那么码率就是(log8)/9=1/3(bit)。如果不进行编码,码率为1。2

信道每传送一个码,同样意味着能量的消耗。所以码长越小越好,这就意味着码率应该尽可能大。那么,一个给定的信道,它允许的最大码率是多少?

香农第二定理通过证明给出了码率和信道容量之间的关系。这个定理指出,超过信道容量的码率,不存在无差错传输的信道编码方案。

香农第二定理虽然给出了码率的上限,但是没有给出具体编码的方案。逼近信道容量的编码方法至今还在寻找当中。当然,一个接近信道容量的信道编码可能实现起来非常复杂。现在常用的信道编码有重复码和汉明码。重复码的特点是误码率趋于0的时候,码率也趋于0,但是由于实现简单,通常用于简易的传输;汉明码可以查错和检错,效率较高。所谓校验值,也属于信道编码。校验值是将一个较长的符号序列通过一定的算法生成一个较短的序列,并附加在原来序列的后面一起发送。两个不同的符号序列具有相同校验值的概率很小,因此,如果接收端计算得到的校验值与接收到的校验值不同,则有很大可能在传输过程中出现了错误。

在信道编码理论中还有几个著名的结论,一个是香农公式,这个公式说明了高斯加性连续信道中的信道容量与采样率和信噪比之间的关系,并暗示足够低的噪声功率可以实现足够高的信道容量;其次,就是关于反馈信道的问题,信道编码理论指出,反馈信道不增加信道容量。这看起来有些不可思议。

上面是关于信息论核心内容的简要回顾。我们只讨论离散的信源和信道,并假设信道是无记忆的。我们忽略了很多细节的内容,特别是渐进等同分割等香农定理的证明基础,也一并略过。有兴趣的读者可以参考相关的文献。

在本质上,由于串行通信属于信道编码的框架,高层协议又多多少少涉及到信源编码,所以这些内容对理解串行通信中的某些核心的问题是非常有帮助的。1.1.2 串行通信的信源特性

信息论作为一种普适的理论,并没有特别指定信息传输实现的方式。但是几十年技术发展的历史表明,有线电通信和无线电通信是信息论得以应用的最广泛的技术领域。

广义地讲,所有的通信方式都是串行的。因为任何一种通信都要涉及到时间参数。并行通信实际上可以看做多个串行通信的排列。

本书所侧重的串行通信具有狭义性,特指利用有线电信号进行数据的传输。最简单的通信模型如图1-3所示。图1-3 简单电报模型

在信源一方,有一个触点开关,触点开关的通和断代表码0和1。注意,必须有一个操作者控制这个开关,而操纵者的任务就是将欲表达的信息转化成开关的0和1,这就是信源编码。如果考虑到一定的冗余,就要使用信道编码。无论是信源编码还是信道编码,从电路的角度看,编码之后都是一串0和1的组合,而0和1的数量对于电路而言是不可预测的,这也是信息的本质。

在现代数字集成电路中,触点开关实际上是晶体管。在数字电路中,使用不同的电平代表1和0,从而构成了二值数字逻辑。

使用数字电路可以完成复杂的数据处理工作。不管怎样,数据最终是要被输出的。数字处理电路的输出段具有一定的电平特性。如果用一个数字示波器进行观察,就会发现数字电路的输出与理想的方波近似。

在单位时间发送的码的数量较低的情况下,信源的输出可以直接接到信宿,控制信宿的电路以完成信息的传递。

然而,我们知道,方波陡峭的边沿在频域上具有广泛的分布,如图1-4所示为一种升余弦滚降信号。通常的信道不能够容忍这种频域特性。在现代基带传输中,通常要将波形整理成圆滑的滚降信号。图1-4 升余弦滚降信号

在计算机串口通信中,不使用基带传输的波形整形。对于近距离的传输,则对信道有一定的要求。限制于信道,因此,传送率不可能做得很高。对于远距离传输,需要进行调制。一般把调制器也看做信源的一部分。调制器的作用是生成较高频率的固定信号,例如,正弦信号作为载波,然后用数字信号对载波进行作用,或改变载波的振幅,或改变载波的频率。在接收端进行解调,即数字信号的还原。使用调制后的信号具有一定的抗干扰能力,因此,可以进行长距离的传输。

总之,串行通信的信源特性依赖于对串行通信质量本身的要求。对于计算机串口而言,尽管使用了非标准的数字电压,但其信源特性与计算机内部的电路特性保持一致。1.1.3 串行通信的信道特性

设备之间的信号传输方式多种多样,比较常见的有低频无线传输、射频无线传输,以及各种电缆、光缆。一般所说的有线传输均指电缆。

使用两根导体线即可以完成串行传输。传输过程中存在噪声,使得输出端采样得到的数据与发送段提供的数据不同。

有线传输的噪声主要来源于两个方面,一是外部噪声。长的导体的电感对于高频的信号来说不可忽略。这些高频信号可能来自运行中的电子设备,也可能来自供电系统中电力的跳变。这些高频信号遇到电感就会产生耦合,从而对导线上的信号产生干扰。

噪声的另外一个来源是信号线之间的互相干扰,这和无线电磁干扰不同。一般来说,传输信号的导线会被封装在绝缘皮中,这样导线的距离就会很近。

我们知道,靠得很近的导体之间会存在明显的电容,对于主观设计时未涉及的电容,通常叫做寄生电容。电容的大小与导线间的距离成反比,与导体的规模成正比。也就是说,导线靠得越近、导线越长,寄生电容越大。导线间寄生电容的存在使得一条导线上的高频信号会通过电容而影响到另外一条导线。

此外,导线的电阻与导线上所传输电平的信号频率有关。这是由电磁学中的趋肤效应决定的。在高频信号流过导线时,电流由于自感电动势的作用趋于导体的表面,这样,导线实际通过电流的“横截面积”就要减小,导致电阻的升高。这有可能在数据的接收端产生错误的采样值。

使用高质量的电缆可以很好地屏蔽无线干扰,因为这些电缆的外皮内侧都有金属网屏蔽层。所以,电缆上的噪声主要来源于信号线的相互干扰。

如果只考虑简单电平通信(这也是串口通信的绝大多数情况),那么导线间的寄生电容占主要地位,特别是在插接件的连接处以及电路的接口,更有较大的寄生电容存在。

我们不妨进行模拟实验,看一看寄生电容的影响。

如图1-5所示为实验电路,左侧是信号源,产生电平信号,不考虑导线的电阻,仅考虑电容,右侧连接一个示波器。图1-5 实验电路

假设信号源产生正负电平信号,如图1-6所示,它表示一个信源码串。图1-6 输入信号

当通过电容后,输出由于电容的短路漏电,从而发生了电平的下降。除此之外,在示意图中由于地线没有可靠地接地,因此,地线上的电平也受到了影响,如图1-7所示。图1-7 地线电压

注意,为了使输出图像能够得到清晰的表达,我们夸张了电路中电容的值。因此,输出的结果只是示意图。

如图1-8所示为典型的电容冲激响应。电容越大,其充电时间就越长,输出信号的反应越迟钝。在寄生电容相同的情况下,信号间隔周期越短,输出信号发生误采样的可能性就越大。而误采样通常发生在连续的码输入之时。图1-8 输出信号

描述这种物理传输可以使用信息论中的二元对称信道模型。这个信道模型如图1-9所示。注意这个模型与电路中的两根线之间的区别。

由于电路中噪声的存在,传送“1”就会有一定的可能接收到“0”,传送“0”就会有一定的可能接收到“1”。对于寄生电容而言,充电和放电的时间常数计算方法相同,因此,0→1和1→0的错误概率是一样的,记为P。P可以通过观测、统计的方法得到。

这个信道是无记忆的吗?严格地说不是。从图1-7可以看到,当连续传送同一个码时,误码概率增加。但是为了简化考虑,仍然认为信道是无记忆的。图1-9 二进制对称信道

经过计算表明,这个信道的信道容量是1减去分布(P,1-P)的熵(通常这个熵写做H[(P,1-P)],或者直接写做H(P),表示这是一个关于P的函数)。我们把C~P的曲线绘制于图1-10。图1-10 二元对称信道的信道容量与错误概率之间的关系

从图1-10可以看出,当P小于0.5时,错误概率增加,则信道容量减小;当P大于0.5时,错误概率增加,信道容量却增加,这说明接收端可以认为接收“1”,则说明发送端发送了“0”,反之亦然。注意,信道并不是电信号的通道,信道中传递的是信息而不是具体的码。

当P为0.5时,输出端等概率地接收到0和1,发送端的信息彻底淹没在噪声之中,信道的容量为0。

在已知信道容量的情况下,就可以选择合适的编码方案,使得误码率小到可以承受的程度。1.2 计算机的数据接口

在讨论串口通信的具体细节之前,我们先介绍一下计算机的数据接口。本书中所说的计算机,是狭义的概念,也就是我们日常接触到的IBM兼容机,包括各种服务器、台式机以及笔记本。至于Sun工作站以及苹果电脑等,其具体构架与IBM兼容机稍有区别,但是概念和思路是相通的。

总的来说,计算机是由中央处理器(CPU)、存储器和输入/输出设备组成的。计算机整体的控制工作和大部分的运算工作由CPU来完成。参与到计算机体系中的各种设备通过数据接口与CPU完成控制和数据的交换。除了存储器之外的所有设备,在CPU看来都是输入/输出(I/O)设备,因此,数据接口也称做I/O接口。本书对这个概念不加以区分。

为什么要使用数据接口?常用的数据接口都有哪些?所谓并口、串口是怎么回事?这些都是本节所要讨论的问题。1.2.1 使用数据接口

可以说,CPU的设计和制造是集成电路技术发展水平的标志。目前,计算机主流CPU的制造商都是国际一流水平的技术企业,它们拥有大量的资金和技术储备,并且以几何级数的扩张力更新着它们的产品。

CPU的运行速度非常快,而且拥有并行处理的能力。目前,CPU的主频为2GHz左右,主流的CPU具有2个甚至4个核心,支持数据位宽已经达到64bit。

在计算机中,没有任何其他的设备能够与CPU的工作频率以及数据吞吐量相匹配(某些高端视频加速处理器除外),这就带来一个问题,如何做到CPU和外围设备的协调工作?

一个简单的方案是,令外围设备同样达到CPU相近的水平,整个计算机犹如铁板一块,协调工作。事实上,当今的小型计算设备采用的就是这种方式,将处理器和外围设备尽可能地集成,形成所谓的片上系统。然而,在计算机工业中,这是行不通的。对于外围设备的制造商而言,达到CPU制造商的技术水平非常困难。使用统一的架构不利于计算机的功能扩展,这不仅会使计算机的造价变得昂贵,而且也不可能形成计算机在工业和社会范围的广泛应用。

必须提供一种方案,使得与CPU有不同差距的电子设备也能加入到计算机的体系结构中去,而且要发挥这些设备的最大功能。除了解决速度的差异,还要解决以下几类问题。(1)信号表达

计算机体系需要接纳来自于不同领域的电子设备,这些电子设备在电气特性上就可能存在不兼容。例如,使用多高的电压代表1,不同的设备就不一样,一些设备则用相反的方式表达信息,例如,0电平代表1等。有些设备使用了模拟信号(例如无线网络),这些模拟的信号不能够直接被处理。(2)时序

即使是低速的时钟,在众多的设备当中达到步调一致也是非常困难的,这是因为在时钟信号的传送过程中,存在干扰和延时,导致信号采样的失败。因此,在计算机这样一个复杂的系统当中,时钟应该是短距离、分布式的,即使用异步电路模型。异步电路如何进行有效的规划,必须得以解决。(3)信息的格式

CPU的对外接口是并行的,而且不同时代的CPU,其输出位宽也不一样。早期的CPU只有8位,到现在的64位。因此,直接使用CPU的数据端口是不明智的。首先,CPU的数据端口在同一个时代往往保持固定不变,因此,当外围设备发生变化时,必须将接口进行固定,这对一些外围设备来说比较困难;另外,当CPU升级换代时,又要求外围设备的接口发生变化。这两类变化的相忤使得计算机系统的发展受到影响。因此,非常有必要确定一种或几种对CPU和设备都无关的规则,实现不同设备和不同CPU的无缝连接。

于是,设备数据接口就这样被引进。数据接口就是CPU同外围设备进行数据交换的规范。设备数据接口的一个思想就是,制定一套相对稳定的规范,规范的两边是CPU和设备,以及相应的接口适配器。适配器负责提供数据缓冲,以满足速度上的差异,此外,适配器还要进行信号的转换、编码的转换等。而无论规范的两边发生何种变化,只需要对各自的适配器进行改动。适配器的变化代价往往很小,这样就能够保证数据接口的稳定性。

这样做还有一个好处,我们知道,有很多设备是数据接口规范推出之后才被设计和制造出来的。典型的例子是USB-Flash大容量存储器(也就是闪盘)。在设计新设备的时候,设计者可以参考计算机所提供的众多数据接口,然后根据速度、信号类型、物理特性等,挑选适合所设计设备的数据接口规范进行设计。这样直接推出的产品就能够很顺利地融入到计算机体系结构中。1.2.2 计算机数据接口的发展

在数字计算机出现的早期,CPU通过简单的读写接口访问存储器中的数据。对于非存储器的设备,CPU仍然将其看做一个存储器,并采用访问存储器相同的方法对这些设备进行访问,而不是在CPU内部对每一个设备单独设立一个接口。因为只有这样,才能保证CPU的通用性以及软件的可移植性。

我们现在使用的所有的数据接口来自20世纪80年代IBM为其生产的计算机所制定的体系结构PC/AT。IBM以80286处理器为核心,选择了一些计算机中应配备的设备,例如,键盘、鼠标、显示器等,并为每一个设备制定了物理层次和信号层次的接口。IBM实现了这些接口,并慢慢形成了标准,即IBM兼容机构架,它是现代计算机体系构架的鼻祖。几十年来,一些设备离开了这个体系,一些设备和设备的接口得到改进,而且,不断有新的接口和设备加入到体系中,但是,这个体系整体的层次构架思想并没有发生质的变化。

PC/AT构架(如图1-11所示)引入了总线的概念。图1-11 PC/AT构架

总线是CPU外部信号的自然延伸,总线上的信号同时也是CPU的引脚信号。不过,在总线规范中,将这些信号按照CPU的功能逻辑划分为地址、数据、中断等功能束,并对每一个功能束制定了相应的标准。所有的I/O设备必须拥有一个总线接口,绝大多数I/O设备拥有总线数据接口的所有功能束。特别需要说明的是,PC/AT构架中,内存储器和其他I/O设备一样,并列接入到CPU总线上。

在PC/AT时代,主要的I/O设备有键盘、鼠标、单色显示器、串口、软盘机、磁带机等。这些设备主要有两个特点,一是速度不太快,并且设备之间的速度差异较小;二是这些设备往往不是个人计算机所专用的。因此,在数据接口的设计过程中,存在计算机体系对外部设备的“迎合”。

80386/80486-AT构架是1985年提出的。这个构架首次引入了分层次总线的概念。这是由于CPU和存储器的速度得到了显著的提升,而I/O设备的速度几乎没有发生变化。CPU需要更多地与内存储器进行数据交换。我们知道,在同一个总线上,每个CPU指令周期只能访问到所有设备之一。如果较慢的I/O设备与相对较快的内存储器处于同一条总线,则CPU在访问I/O设备时势必会浪费很多的指令周期。为了解决这个问题,次级总线和次级总线适配器由此诞生。次级总线适配器面向CPU的总线接口,与内存储器一样快。而次级总线适配器引出的次级总线速度与I/O设备相同。总线适配器上有缓存和仲裁。这样,与I/O设备进行交流的数据经过适配器的缓冲,节约了CPU的指令周期。总线适配器相对于指令集体系结构来说是透明的,对于软件而言,仍然相当于I/O设备与CPU、内存储器处于同样一个层次。

80386/80486-AT构架(如图1-12所示)所引入的次级总线就是ISA总线,它的全称是“工业标准体系结构”(Industry Standard Architecture)。

ISA总线在计算机上存在了有20余年的历史,2007年3月,微软才明确表示放弃对ISA总线设备的支持。然而,ISA总线并没有因此而消失,包括SATA在内的一系列I/O接口都是由ISA总线演化而来的。

进入奔腾时代,CPU的速度越来越快,计算机的I/O设备越来越多,它们之间的差距也在慢慢拉大。Pentium AT构架(如图1-13所示)正式引入了PCI(Peripheral Component Interconnect,外围设备互连)总线。PCI总线应用于需要资源较多、速度较快的设备,例如,网络适配器、多媒体声卡、视频加速器等。ISA总线依然适用于速度较慢的设备。在Pentium AT构架中,ISA总线适配器成为了一个普通的PCI设备。图1-12 80386/80486-AT构架       图1-13 Pentium AT构架

Pentium AT构架实质上保持了相当长的稳定时期,PCI总线发生了几次小的改动。1997年,Pentium II AT构架(如图1-14所示)推出,这个构架只是在Pentium AT上增加了AGP总线,以加强图形加速器的数据吞吐要求。AGP总线并不是独立的,而是CPU总线的PCI总线适配器上的一个附加模块。图1-14 Pentium II AT构架

在1997年至2004年间,Pentium II AT构架保持稳定,各个总线在保持整体构架不变动的前提下升级。2004年,含有PCI-E的总线构架被提出,这种构架成为了当代计算机的主流构架(如图1-15所示)。图1-15 当代计算机体系构架

当代计算机构架的特点是设备繁多,设备之间的差别非常大。这使得计算机的数据接口变得庞杂,一方面体现在其层次关系上,另一方面,对于某种特定的总线接口,尤其是新一代的高速数据接口,其实现也非常复杂。以PCI-E总线为例,它引入了层次化的体系结构,通过高速串行的接口实现了设备的点对点传输。因此,PCI-E设备需要相对复杂的总线接口,这与20世纪80年代PC/AT简单的寄存器接口已经不能够同日而语了。

从计算机的数据接口的发展来看,有以下4个趋势。(1)分层细化

分层细化是指总线数据接口更加贴近于设备的实际需求,整个体系构架得到精心的分层,使得每一个终端设备匹配到理想的服务,总线的时序浪费(设备等待)以及设备接口的缓存数量能够得到减少。

即使是同一种数据接口内部,也分为若干种子类型,并规定了兼容关系。例如,PCI-E接口,就同时存在若干种形状不同的插槽,以便于节省设备尺寸。又如USB协议,就同时存在USB 1.1和USB 2.0两种版本的传输率接口。两个版本速率不同,但有兼容性,即USB 1.1的设备接入USB 2.0的适配器可以发挥全部性能,USB 2.0的设备接入USB 1.1的适配器也能够使用,只不过速度被降级。USB 1.1设备实现相对简单,软件开发也较容易。这样就能够满足不同类型的需求。(2)接口通用化

当代计算机体系构架要求功能需要相似的设备,使用相同或者相同类的数据接口。例如,在新的构架中,取消了AGP总线(在一些过渡时期的主板上还保留了AGP接口),图形加速器与PCI-E网卡等设备一样,使用PCI-E总线接口。又如,周边设备键盘和鼠标等,多建议使用USB总线,以代替原有的、不互相通用的鼠标和键盘接口。(3)数据传输串行化

并行传输意味着宽的数据接口,意味着空间的浪费和稳定性变差,因为多条数据线更容易受到干扰。如今,计算机数据接口有串行化的趋势。PCI-E、USB以及图形显示接口、SATA都是串行接口的代表。(4)接口协议化

PCI-E、USB是典型的协议化总线接口。当代计算机的数据接口包含了众多的连线、编码乃至软件协助,非常复杂,因此,需要有效的逻辑表示。这些协议化的总线接口引入了OSI模型的概念,对物理、编码、传输等各个环节作出了明确的规定。这样就非常利于工作在不同层次的开发者进行分工和合作。1.2.3 LPC总线与串口适配器

在图1-15中看到了LPC总线所处的位置。LPC总线的全称是“低脚位数总线”(Low Pin Count Bus)。它是由Intel公司于1998年引入的,目的是在废除ISA总线后提供给低速设备的通用接口。这些低速设备包括鼠标、键盘、串口和并口。与ISA总线一样,LPC总线对软件是透明的。

LPC总线适配器表现为一个单独的主板模块,也有一些芯片组的南桥也实现了LPC。LPC总线不引出通用的接口,而是为每一个所支持的设备引出单独的接口。实现LPC总线的芯片,通常称为LPC多功能芯片。主板厂商,特别是笔记本主板制造商通常自己实现LPC多功能芯片,以期望有选择地提供一些接口。

如图1-16所示是一种LPC多功能芯片IT8705F的功能和内部框图。在这款LPC多功能芯片中,集成了PC98/99/2001&ACPI适配器、增强的硬件监视器、风扇控制器、游戏柄接口、2个16C550 UART兼容串口控制器、MIDI接口、软盘控制器、键盘控制器、智能卡控制器、48个通用I/O接口、Flash ROM接口等。图1-16 IT8705F内部框图

LPC总线的信号比较少。必选信号中,有4位的综合地址数据传输信号(LAD)、帧标志信号(LFRAME)、重置信号(LRESET)和时钟(LCLK),此外还有6个可选信号,包括IRQ、DMA等控制信号。IT8705F使用全部的必选信号,以及可选信号中的3个,即DMA申请(LDRQ)、IRQ申请(SERIRQ)和电源事件管理(PME)。

LPC的总线信号属于同步的基于半字的并-串行传输。每一次传输一个帧。帧数据以半字为单位(因为综合地址数据传输信号只有4bit)。一个半字可以是帧控制信号,也可以是地址或者数据的一部分。

在LPC多功能芯片的内部,各种LPC设备挂置于LPC总线上,由适配器进行分配和仲裁。

在当代计算机构架中,尽管LPC总线所使用的芯片种类繁多,但是基本接口和功能定义大同小异。从图1-16可以看到,LPC总线仅提供了两路串口接口,比IBM PC/AT构架的4个串口有所减少。

挂载于LPC总线上的串口适配器负责将LPC的接口信号转换成串口接口信号。在这个过程中需要进行数据的缓存、并行到串行的转换,以及通信帧的形成,除此之外,串口适配器还要完成串口标准的其他信号的产生。

前面我们接触到了信号的调制。在串口适配器诞生的时候,主要作为调制解调器的接口,以便于相距较远的计算机能够通信。当然,现在这种通信方式已经被互联网所取代,而现在用于电话线拨号上网的窄带调制解调器,其接口也已经用到PCI总线上。对于串口来说,25条数据线被简化为9条。这里需要注意,早期的调制解调器对于软件而言,不是透明的。串口上被简化的大量信号与调制解调器的控制密切相关。在1.4.4节,我们将对这个问题进行进一步的探讨。

尽管几十年来计算机体系构架的复杂性提高,整体速度和兼容性也逐渐完善。但是有一点没有改变,那就是从CPU角度看过去的I/O体系结构。虽然,当代计算机体系结构和IBM PC/AT相差非常大,但是对于编程模型来说,没有发生变化。换句话说,尽管CPU访问串口,需要经历北桥集线器、南桥集线器、LPC总线、串口接口众多的设备,但是在软件看来,仿佛串口就是直接接到CPU的I/O总线上一样,在IBM PC/AT上进行串口编程的方法,在今天仍然适用(当然,这里面包含操作系统不发生改变的前提,这个前提通常是能够满足的,也就是说,IBM PC/AT时代的那一套软件,原则上可以不加修改地移植到现在的计算机上使用)。

在软件中使用串口,就应该了解在计算机构架中,程序应该如何与串口控制器进行交流,尽管现代操作系统为我们提供了舒适的虚拟环境,但是在嵌入式系统盛行的今天,学习在指令集体系结构下的软硬件接口是益处多多的。在第2章,我们将详细介绍如何针对串口适配器进行编程。1.3 串行通信协议

使用串口进行串行通信时,首先必须解决以下几个问题:

●需要哪些设备,这些设备应该怎样连接。

●如何使用软硬件之间的接口。

●如何保证数据传输的可靠性。

●如何使串行通信具有可扩展性。

本节首先介绍通用的OSI模型,然后讲述如何在硬件接口,特别是串行通信中构建一个模型。1.3.1 为什么制定协议

在上一节我们谈到,信源和信道如果能达到一定程度的匹配,则可以提高传输效率。为了使得这种匹配关系得到贯彻施行,必须对通信的双方以及信道的特性作一定程度上的约束。除此之外,在通信的高层还必须对二进制流的含义加以一系列的规定,以做到接收端能够对源端的信息作出符合源端本意的解释。

我们以打电话为例,通话的双方必须建立连接,那么建立连接的前提条件就是两端的电话机,以及通信网络符合一定的标准。如果电话已经接通,是不是就说明双方可以交流了呢?这不一定。通话的双方还必须通晓同一种语言,通信才能够建立。否则,如果给听不懂中文的人使用中文通话,那么你所说的所有内容对于对方而言都是噪声。

因此,在通信中制定协议是必要而且是首要的。所谓协议,实际上就是彼此都知道的东西,用信息论的观点说,协议包含的信息是必须在通信进行之前送达的,协议相对于所控制的通信而言,是先验的。1.3.2 OSI协议参考模型

在上面的论述中,您可能已经了解到协议和协议之间似乎有所不同。是的,说到协议,就不能不提及分层。那么,什么是分层?

如果协议A的实现需要借助于协议B,或者,实现协议A必须首先实现协议B,则称协议A处于协议B的上层。一个协议体系通常是一族协议的集合。这些协议以分层方式进行堆积,构成一个相对完整的体系结构。

那么,对于计算机通信的诸多协议,该以何种方式进行组织?1974年,国际标准化组织(ISO)和美国国家标准协会(ANSI)推荐了OSI(开放系统互连)模型。这个模型面向由计算机组成的网络体系,最初应用于有线局域网和无线通信网。

OSI定义了网络协议层次体系中每一层的位置和功能,并提供了同层透明的机制。有了OSI,网络设备实现了标准化的接口,这样,来自不同厂家和不同时代的设备可以进行相互通信,这使得大规模的网络应用得到了实现的保证。

尽管OSI模型定位于网络通信体系,然而,在硬件接口体系中,完全可以直接参照OSI的模型建议,制定具有相似构架的通信体系。如果这一体系得到遵从,那么就可以享受到OSI标准化带来的好处。1.3.3 一般模型与协议栈

OSI的模型定义不依赖于特定的硬件与软件,它如同语言中的语法,规定了互联通信的准则。

OSI模型主要规定了如下内容:

●通信设备之间如何联系,使用不同协议的设备如何通信。

●通信设备如何获知何时传输或不传输数据。

●如何安排、连接物理设备。

●确保数据被正确接收的方法。

●设备如何维持数据流的恒定速率。

●数据在介质上如何表示。

OSI模型由7个层组成,即物理层、链路层、网络层、传输层、会话层、表示层和应用层,如图1-17所示。图1-17 OSI模型

每一层都处理特定的通信任务,使用基于协议的通信来与下一层交换数据。两个设备间的通信就是通过在每一设备的协议实现中进行完成的。例如,有一工作站要与一服务器进行通信,任务从工作站的应用层开始,经由较低的层格式化信息,直至数据到达物理层,然后通过网络传输到服务器。服务器从协议栈的物理层获取信息,向上层发送信息以解释信息,直到到达应用层。每一层可用其名称称呼,也可用其在协议栈中的位置表明。例如,最底层可称为物理层或第1层。

最底层执行的功能与物理通信相关,如构建帧、传输含有包的信号;中间层协调结点间的网络通信,如确保通信会话无中断、无差错地持续进行。最高层的工作直接影响软件应用和数据表示,包括数据格式化、加密以及数据与文件传输管理。总括起来,这些层称为协议栈。

具体到某一协议,可以根据自身的需要实现其中的某几层。

协议栈的优势在于以下两点:

●负责制。每一层只为它的上层提供接口,并控制下层进行传输。隔层之间不进行串通。这样,在层的实现中,只对相邻的两层负责即可。

●层间透明。在一次传输中,相互进行对话的是同层次的层。例如,制作一个FTP客户端,仅需对FTP协议和下层调用方法进行了解即可,而不去理会网卡的工作原理。

1.物理层

OSI模型的最底层为物理层,包含以下各项:

●数据传输介质。

●设备的物理接口。

●网络拓扑结构。

●信令与编码方法。

●数据传输设备。

●网络接口。

●信令出错检验。

物理层使用的设备要传输、接收包含数据的信号,需负责产生、携带并检查电压。在物理层中,决定了使用何种电压模式(数字/模拟)传输信息,以及使用该种电压模式如何携带信息(基带传输、调制、解调)。

在信号传输中,物理层处理数据传输速率,监控数据出错频率,并处理电压电平。物理层是信道噪声产生的场所。在物理层的设计上,应该尽可能采取措施,保证信号的完整性。

物理层和链路层的“逻辑链接控制子层”一起完成数据的物理传输和比特流的生成。

2.链路层

链路层的主要工作是形成“帧”(Frame)。所谓“帧”,指具有一定格式的字符流。

链路层利用物理层形成的比特流,按照链路层的协议组成帧。在链路层之上,也有帧的概念,而链路层是形成帧的第一个环节,所以被称做“原始帧”。

链路层的帧结构比较简单。一般来说,设备层面的定位发生在这一层。通常的做法是,以事先约定的长串字符唯一地代表设备,并在原始帧当中指明数据的来源设备和数据的目的设备。此外,需要对帧数据进行完整性校验,校验和一般放置于帧尾。

链路层提供给上层的数据流是帧的主体,称为“有效负载”。链路层不控制有效负载的格式,而只是当做无格式的数据流进行处理。

对原始帧本身进行处理的链路层模块称为介质访问控制(Media Access Control,MAC)子层,而负责下层数据连接的模块称为逻辑链接控制(Logic Link Control,LLC)子层。

在网络通信中,常常把MAC子层与链路层相混淆。

3.网络层

网络层处理与网络拓扑相关的数据传输。“路由协议”就发生在这一层。

在网络层中传输的数据是“包”。包实际上是一系列原始帧的有效负载的组合。在网络层,有效负载先被组合成一个实体(打包),并由网络层决定合适的网络路径,即一个包应当通过哪些设备进行传送。

网络层的数据通信在数学上的模型非常复杂。至今,确定一个在各种条件下均行之有效的路由框架仍然是困难的。

向上层,网络层暴露了更加虚拟化的数据源和数据目标。但是,网络层不能够保证信息传递的正确性。

4.传输层

传输层解决的是给定源和目的之后的有效传输问题。有效传输包含两方面的意义:

首先是数据包本身的正确性。传输层协议常常采取“出错重传”的方式。所谓出错,一般是指下层反馈了校验错误的信息,或者超过一定的时间所期望的数据没有到达(连接超时)等。一般来说,校验错误的包会直接丢弃,导致连接超时,因此,两种出错方式可以进行合并。当出现错误时,传输层会发送简短的反馈包。这种反馈包由于数据量少、优先级高,因此,反馈包本身的出错概率可以设计得非常小。

其次是数据包顺序的正确性。由于路由可变性强,所以在源端按照一定顺序发送的数据包,在接收端可能不会按照期望的顺序收到,这对顺序性强的应用(在线视频点播)可能带来较大的问题。因此,传输层必须对这些数据包加以整理。

著名的TCP/IP协议就发生在这一层。

5.会话层、表示层和应用层

一般来说,这几个层由软件进行控制,对于性能要求高或者软件不便使用的场合,也可以用硬件实现。会话层负责建立有效的连接,并在下层发生错误时通过定时恢复的方式保证连接的通畅。表示层协议规定了高层数据的格式,高层次的数据加密发生在表示层。应用层就是我们熟知的各种面向应用的协议,如HTTP、FTP等。应用层的协议数量非常庞大。1.3.4 串行通信协议

在串行通信中,同样需要制定协议。实际上,如果我们使用OSI模型进行考虑的话,所谓串行,实际上只发生在链路层或者以应用层为基础的虚拟链路层。

什么叫做以应用层为基础的虚拟链路层?

事实上,在应用层,我们完全可以利用下层的协议实现单个比特的分时传输。只需要把所传送的数据打散,然后每个或每几个比特打成数据包进行传送。各种各样的协议转接器就是这样工作的,如图1-18所示。图1-18 协议转换

在链路层中,数据以帧为单位传送。一套链路层协议中,关于帧的定义应该至少包含以下内容:

1)帧的类型。所谓帧的类型,是指在一次传输中,可能用到格式不同的帧。总的来说,链路层协议应该包括命令帧和数据帧。命令帧比较简短,不带有有效负载,而是由协议定义的特殊数据;数据帧是携带有效负载的帧。一般来说,链路层会将命令帧置于较高的优先级和较严格的错误检验乃至加密措施,以保证命令帧准确无误地传输。对于一些简单的协议来说,可能没有命令帧。

2)帧内分组。帧内分组指各种帧内部的结构信息。通常把功能相似的数据放置在一起,形成“段”。例如,在数据帧内,把对该数据的描述和数据本身分开,形成不同的段。通常,段的开头都定义一定的标志位或标志字符。

宏观上看,如果一种协议需要较多、较复杂的帧,那么数据传输的实现也会很复杂。但是实际应用中往往会出现这样一种情况:甲需要传送大量数据给乙,而乙只需要给甲适当的反馈。这样,完全没有必要在甲乙双方都完全实现协议规定的要求。我们称这种传输为非平衡传输。实现控制的一端称为主机(Master),被控制的一端称为从机(Slave),传输的发起者和控制权在于主机,而从机只需要实现配合主机的一部分协议即可。在非平衡传输中,主机发给从机的帧一般称命令帧,从机发给主机的帧一般称响应帧。

作为一种点对点的通信方式,串行通信在数据流向中,同样可分为单工、半双工和双工3种。单工指在物理上只能够由一方向另一方传送数据;半双工指物理上双方可以互传数据,但是同一时刻只能够有一个方向的流;全双工指在同一时刻,任何一方都可以向另一方发送数据。注意,这三类流向不是串行通信所特有的。

较为流行的串行通信协议可以分为同步串行协议和异步串行协议,如果更细分,有起止式异步传输协议、面向字符的同步协议以及面向比特的同步协议。我们分别予以介绍。

1.起止式异步传输协议

所谓异步传输,是指发送方可以在任何时刻发送由若干比特组成的帧,而接收方不知道数据会在什么时候到达。帧和帧之间的间隔也是没有约定的,一般来说,由发送方控制。

那么,如果不采取一定的措施,接收端就会产生数据的丢失,即检测到数据并作出响应之前,第一个比特已经过去了。这就像有人出乎意料地从后面走上来说话,而你没来得及反应过来,漏掉了最前面的几个词。因此,每次异步传输的信息都以一个起始位开头,它通知接收方数据已经到达了,这就给了接收方响应、接收和缓存数据比特的时间;在传输结束时,一个停止位表示该次传输信息的终止。按照惯例,空闲(没有传送数据)的线路实际携带着一个代表二进制1的信号,异步传输的开始位使信号变成0,其他的比特位使信号随传输的数据信息而变化。最后,停止位使信号重新变回1,该信号一直保持到下一个开始位到达。例如,十六进制字符0x31,按照8比特位的扩展ASCII编码,将发送“10001100”,同时需要在8比特位的前面加一个起始位,后面一个停止位。

由于串行传输是面向比特的,因此,通常把一次发送的若干个比特称为一帧。一般来说,起始位、数据位是帧中不可或缺的部分。

计算机串口对于起止式异步通信有如下规定:

●在通道空闲时,接口保持正逻辑1,即高电平。

●发送端和接收端必须使用相同的采样率,即比特率。

●每一个物理帧以正逻辑0,即低电平开始。

●每一个物理帧的有效负载比特(数据位)为5~8个比特。低位首先发送。

●有效负载之后是奇偶校验位。是否采用校验和使用何种校验,由双方事先约定。

●校验位之后是停止位,为正逻辑1,使用长度多少的停止位需要事先约定。

从上述规定中可以看到,使用起止式异步通信必须实现在双端进行一系列的配置,如比特率、数据位长度、校验、停止位等。

如图1-19所示为一个异步帧,它有8位数据位、1位校验位、1位停止位。图1-19 起止式异步传输的一帧

异步传输协议中,只有数据帧而没有命令帧,且数据帧不分段。

异步传输的实现比较容易,由于每个信息都加上了“同步”信息,因此,计时的漂移不会产生大的积累,但却产生了较多的开销。在上面的例子中,每8个比特要多传送两个比特,总的传输负载就增加25%,如果加上奇偶校验位,则效率更低。对于数据传输量很小的低速设备来说问题不大,但对于那些数据传输量很大的高速设备来说,25%的负载增值就相当严重了。因此,异步传输常用于低速设备。

尽管如此,由于异步传输的可靠性非常高,所以在低速传输中有很广泛的应用。

2.面向字符的同步协议

所谓同步协议,指双方约定好采样率后,即开始数据的传输,线上的每个数据都是有效的,数据的协调过程不需要专门的起始信息。

有的同步协议需要物理层的支持。例如,自适应的同步传输就需要物理层提供时钟支持。但是,如果物理层比较简单,只提供基本的电平转换,则需要链路层增加额外的功能。

面向字符的同步协议就是这样一种。以IBM公司的二进制同步通信协议(BSC)为例,它的物理层是一般同步传输,物理层不断提供给链路层字符流。为了数据的有效传输,BSC采用了以不同字符作为数据分割的手段,这就是面向字符的同步协议。

BSC只有数据帧,没有命令帧,如图1-20所示。

BSC取10个特殊字符,作为帧的开头与结束标志以及整个传输过程的控制信息,它们也叫做通信控制字。由于被传送的数据块是由字符组成的,故被称做面向字符的协议。图1-20 BSC帧

控制字和有效负载组成帧。帧的开始,首先传递同步字符(Synchronous Character, SYN),如果每个帧只有一个SYN,称为单同步;如果有两个SYN,称为双同步。同步字符用于表达一帧的开始。接着的SOH是帧头字符(Start Of Header),它表示帧头的开始。帧头中包括源地址、目的地址和路由指示等信息。STX是文始字符(Start Of Text),它标志着有效负载的开始。有效负载由多个字符组成。数据块后面是组终字符ETB(End Of Transmission Block)或文终字符ETX(End Of Text),其中ETB用在正文很长、需要分成若干个分数据块、分别在不同帧中发送的场合,这时在每个分数据块后面用文终字符ETX。一帧的最后是校验码,它对从SOH开始到ETX(或ETB)字段进行校验,校验方式可以是奇偶校验或CRC。另外,在面向字符协议中还采用了一些其他通信控制字,它们的名称如表1-1所示。

面向字符的同步协议不像起止式异步协议那样需要在每个字符前后附加起始和停止位,因此,传输效率提高了。同时,由于采用了一些传输控制字,故增强了通信控制能力和校验功能。

但是对于同步传输来说,存在区别数据字符代码和特定字符代码的问题,因为在数据块中完全有可能出现与特定字符代码相同的数据字符,这就会发生误解。比如,正文有个与文终字符ETX的代码相同的数据字符,接收端就不会把它作为普通数据处理,而误认为是正文结束,因而产生差错。因此,同步协议应具有将特定字符作为普通数据处理的能力,这种能力叫做“数据透明”。为此,BSC协议中设置了转义字符DLE(Data Link Escape)。当把一个特定字符看做数据时,在它前面要加一个DLE,这样,接收器收到一个DLE就可预知下一个字符是数据字符,而不会把它当做控制字符来处理了。DLE本身也是特定字符,当它出现在数据块中时,也要在它前面加上另一个DLE。这种方法叫字符填充。这有点类似于C语言字符串的转义。我们知道字符串中的回车用\n来表示,这是因为C语言的字符串必须写在一行,所以不能够直接输入回车来表示回车,为了将回车转义与一般文本区分开,就必须使用转义符“\”。在BSC协议中,DLE就相当于“\”。

但是,字符填充依赖于字符本身的代码,且实现复杂。有时候甚至会由于转义字符的插入,降低了数据传输的效率。因此,为了改善这个问题,出现了面向比特的同步传输。

3.面向比特的同步协议

面向比特的同步协议不再以8bit为传输单位。在面向比特的同步协议中,所传输的一帧数据可以是任意位,依靠协议的约定进行组合,而不是靠特定字符来标志帧的开始和结束,故称“面向比特”的协议。

面向比特的同步协议中最具有代表性的是IBM的同步数据链路控制规程(Synchronous Data Link Control, SDLC)、国际标准化组织ISO的高级数据链路控制规程(High Level Data link Control, HDLC)、ANSI的先进数据通信规程(Advanced Data Communication Control Procedure, ADCCP)。

SDLC/HDLC的一帧信息包括几个场(Field),所有场都是从有效位开始传送的。

首先是标志场。SDLC/HDLC协议规定,所有信息的传输必须以一个标志字符开始,且以同一个字符结束。这个标志字符是01111110,称标志场(F)。从开始标志到结束标志之间构成一个完整的信息单位,称为一帧(Frame)。所有的信息是以帧的形式传输的,而标志字符提供了每一帧的边界。接收端可以通过搜索“01111110”来探知帧的开头和结束,以此建立帧同步。

在标志场之后,是地址场A(Address)。由于协议可能用于多路传输,所以地址场用来确定通信端地址。命令帧中的地址字段携带的是目标的地址,而响应帧中的地址字段所携带的地址是发送者的地址。此外,规定全1的地址为广播地址,即任何单位均可以接收含有广播地址的帧;规定全0的地址为测试保留,在应用中不应该为任何设备分配这个地址。

地址场后是控制场C(Control)。控制场可规定若干个命令。发送方利用控制字段来通知被寻址的接收方执行约定的操作;相反,从机用该字段作为对命令的响应,报告已完成的操作或状态的变化。SDLC规定A场和C场的宽度为8位或16位。接收方必须检查每个地址字节的第一位,如果为“0”,则后面跟着另一个地址字节;若为“1”,则该字节就是最后一个地址字节。同理,如果控制场第一个字节的第一位为“0”,则还有第二个控制场字节,否则就只有一个字节。

跟在控制场之后的是信息场I(Information)。I场包含有要传送的数据,并不是每一帧都必须有信息场。即数据场可以为0,当它为0时,则这一帧主要是控制命令。理论上,I场没有上限,实际中受到处理装置的缓冲区限制,一般为1~2KB。

紧跟在信息场之后的是两字节的帧校验,帧校验场称为FC(Frame Check)场或称为帧校验序列(Frame Check Squence, FCS)。SDLC/HDLC均采用16位循环冗余校验码(Cyclic Redundancy Code, CRC)。除了标志场和自动插入的“0”以外,所有的信息都参加CRC计算。

我们看到,面向比特的同步协议中,也有对帧的分组,只不过由于是比特流,因此,不称为组。但是基本的思想是一样的,即一帧当中包含有导引信息、控制信息和数据信息。同样,利用分组的不同可以定义多种帧。HDLC最常使用的是信息帧(I)、监控帧(S)和无编号帧(U)。信息帧主要携带数据进行传送,监控帧携带各种协议规定的控制信息,无编号帧提供了自定义命令的能力。

与面向字符的同步协议类似,面向比特的同步协议也必须解决数据透明的问题。如上所述,SDLC/HDLC协议规定以01111110为标志字节,但在信息场中也完全有可能有同一种模式的字符。为了把它与标志区分开,采取了“0”位插入和删除技术(也叫扰码)。具体做法是发送端在发送所有的信息(除标志字节外)时,只要遇到连续5个“1”,就自动插入一个“0”;当接收端在接收数据时(除标志字节),如果连续收到5个“1”,就自动将其后的一个“0”删除,以恢复信息的原有形式。这种“0”位的插入和删除过程是由硬件自动完成的,目的是防止在本不应该出现标志字节的地方出现与标志相同的字节。

SDLC/HDLC具备异常结束的规定:若在发送过程中出现错误,则SDLC/HDLC协议常用异常结束(Abort)字符,或称为失效序列使本帧作废。在HDLC规程中,7个连续的“1”被作为失效字符,而在SDLC中失效字符是8个连续的“1”。在发送失效序列时,不使用“0”位插入/删除技术。

在同步协议中,还有一种称做面向字符计数的同步协议。这种协议在帧起始的位置标明了帧数据的大小,因此,能够避免数据透明的诸多问题。

总的来看,串行通信协议面向链路层,分为异步通信协议和同步通信协议两大类。这两种协议的区别就是同步通信的物理层不提供帧起始的信息,而是把所有采样结果都提交给链路层,依靠链路层的状态转换机制,结合协议判断帧的起始。这样,帧与帧之间的空隙是稳定的,而不是在异步通信中是个不确定的长度。

我们还看到,协议之间并不是泾渭分明的,有很多的概念和思想都可以交叉在一起得到应用。在串口通信中,使用的是异步协议,而这里之所以介绍这么多同步协议的内容,是为了给接下来的高层次协议设计作铺垫。1.4 EIA RS-232串行接口标准介绍

在这一节我们讨论物理层的实现,包括EIA RS-232标准的简介,以及与计算机串口通信相关的特性描述。1.4.1 EIA RS-232标准概述

EIA RS-232是美国电子工业联盟(Electronic Industries Alliance, EIA)制定的串行通信标准,全称EIA RS-232C数据终端设备和数据通信设备应用串行数据交换标准(EIA Standard RS-232-C Interface Between Data Terminal Equipment and Data Communication Equipment Employing Serial Data Interchange)。EIARS-232标准主要定义了串行通信中以下几个方面的内容:

●数据终端设备(DTE)和数据通信设备(DCE)的定义。

●接口的模拟电子特性。

●接口的机械特性。

●子电路的结构和接口。

●用于调制传输的电路接口。

以下内容并不是EIA RS-232标准所关心的内容:

●数字编码。

●帧的格式。

●传输率(EIA RS-232标准只定义上限)。

●电源分配。

为什么称EIA RS-232为“标准”(Standard)而不是“协议”(Protocol)呢?

作为“标准”,实际上就说明了这套规范具有一定的完备性和强制性。在很多场合,对于一个协议而言,在不实行全部协议的条件下,也可以满足协议相应部分所规定的内容。例如,一套带有命令帧和数据帧的协议,完全可以只通过实现命令帧而达到控制。然而标准就不同了,标准的每一个细节必须严格地实现,否则整个标准所实现的目标就不能够达到。

在介绍EIA RS-232的细节之前,先回顾一下EIA RS-232标准的简单历史。

20世纪60年代末电子通信设备都非常简单。DTE设备通常是电传打字机,DCE设备就是调制解调器。为了能够使不同的设备能够遵从相同的通信标准,以避免产生混乱,EIA制定了RS-232串行通信标准,1969年进行了最后一次修订,这就是EIA RS-232C标准,此后,这个标准虽然历经更换名称,甚至出现了很多升级版,但实际上,如今,在计算机总线上的串口适配器遵循的仍然是1969年制定的EIA RS-232C标准。

需要注意的是,EIA RS-232最初并不是为计算机所制定的。在标准制定的时候,计算机、打印机、测试仪等设备均没有对标准的实现。那个时候,计算机的构架标准处于四分五裂的状态(而不是今天的IBM兼容构架一统天下),来自不同电脑制造商的工程师们于是匆忙地将EIA RS-232标准在自己的计算机上实现。遗憾的是,并不是每个人都能够很好地遵守EIA RS-232所规定的标准,各种各样的低级错误层出不穷,例如,连线错误、信号线丢失等,其中不乏一些知名厂商。除了故意或者不故意地篡改协议,一些人还故意对标准中的某些规定进行简化。例如,EIA RS-232指出,数字逻辑使用正负电平表示(而不是0为正电平),电压绝对值为12V,但是同时规定,对于绝对值为3V的电平,接收器也应该能够进行识别,这为一部分人钻空子提供了可乘之机。他们对电路进行简化,其电压绝对值只有5V,并将EIA RS-232兼容的标签打到了产品之上。这样,一个合乎EIA RS-232标准的发送器的12V高电压将很可能对这个设备的接收器产生不可逆转的损坏。

当个人计算机也包括大型商用服务器的基本构架得到一致化之后,EIA RS-232C就借助计算机构架的标准化而得到保持,并且由于计算机体系本身的巨大影响力,才使得EIA RS-232不至于消亡,或者被修正为另外的东西。

20世纪90年代,互联网开始兴起。互联网接入服务首先是通过通信网接入实现的。此时适用于计算机的调制解调器开始广泛应用。内置式的调制解调器是通过总线作为接口的,如ISA总线和PCI总线;外置式的调制解调器就使用串口作为接入口,这是因为,由于EIA RS-232标准的保证,以前在电传机上使用的调制解调器可以几乎原封不动地拿到计算机上用。这也许是这种原本不是为计算机使用的设备没有改变名称的原因之一。在宽带网普及之前所经历的一个漫长的调制解调器网络时代,为串口的存在提供了足够的理由。

在工业界,依然有大量的数控及程控设备需要使用串口及EIA RS-232兼容接口,一般来说,与信息技术界不同,工业界追求设备的稳定而不是新颖,这包括协议本身的稳定和维护的简易,EIA RS-232标准满足了这一点。

时至今日,串口及EIA RS-232有在个人计算机上被USB、IEEE1394等高速接口取代的优势。1997年,微软公司提出的PC97构架方案明确将属于IBM兼容体系(AT)的EIA RS-232标准排斥在外,也就是说,串口已经成为一种可选的设备。然而,串口作为为数不多的以电平为直接信号载体的计算机接口,还有它发挥作用的空间。

如果读者有兴趣,请到几个知名计算机配件报价网站进行浏览。关注各个厂商主板的接口方案,试着找出没有配备串口的若干款主板,并作出自己的结论。1.4.2 电气特性

DTE与DCE使用直接电缆进行连接。DTE之间也可以进行连接。

EIA RS-232规定,使用负电平代表逻辑1(Mark),使用正电平代表逻辑0(Space)。负电平电压为-12V,但至少在-12~3V之间能够检测为正确的逻辑。正电平使用12V电压,容忍下限为3V。注意,EIA RS-232的这种规定与现代大多数数据传送设备是相反的,其来源可能是1950年的电传打字机。在EIA RS-232的控制信号中,正电平和负电平的允许范围与逻辑信号相同,但是,正电平代表ON(许可),负电平代表OFF(禁止)。

由于使用正负电平,自行开发DTE设备(例如单片机串口通信)可能会遇到困难。因此,需要使用EIA RS-232电平转换器,将0~5V的数字电平转换为EIA RS-232规定的正负电平。

通常使用的转换器有很多,以比较流行的MAX232芯片为例,这款芯片能够同时提供两路EIA RS-232到数字电平的转换(包括收发)。其适配电平输出为10V,适配输入符合EIA RS-232的电平标准。使用一颗MAX232芯片(如图1-21所示)就能够满足一般的数据传送需求,完全实现EIA RS-232的DCE-DTE标准需要十余条信号线,此时可以使用MAX245、MAX248等多路电平转换器。MAX系列转换器的供电电压可以使用与数字电路相同的电压。此外,所需要的外围电路很简单,一般是几个钽电容或电解电容。注意,MAX232内建电平转换器,因此,它本身不需要10V电源的供电。图1-21 MAX220/232/232A

由于各个信号线使用了相对电平,所以EIA RS-232标准规定了公共地线。公共地线只有一条,时序、控制和数据信号的电平均以公共地线为电平参考。EIA RS-232标准不支持使用电气隔离的器件,例如光器件。

对于信号线的接口,需要符合EIA RS-232标准所规定的等效电路约束。所谓等效电路约束,是指信号线端口的电气特性与等效电路的电气特性相同。

在图1-22的左侧是输出等效电路。要求输出阻抗小于50Ω,电压源和电压源内阻需要仔细选择,使得短路电流不超过500mA;电容C0没有特殊规定,要求足够小且考虑寄生效应在内。右侧是输入等效电路,要求输入阻抗在3~7kΩ之间,输入电容小于2500pF,输入反馈电压小于±2V。图1-22 EIA RS-232电气特性等效电路

EIA RS-232标准规定数据发送端能够容忍短路。即将信号输出连接到地时,不会引起任何的电器损坏。同时规定,对于接收端来说,在±25V以内的电压都不能够造成任何破坏。值得注意的是,电容-电感混联电路可能会产生长时间、高电压的感生电动势,从而造成接收端的损坏。

在发送端掉电时,发送接口仍然可能送出电平,即失效信号。因此,EIA RS-232标准规定4条信号线在设备掉电时应该保持的状态。这4个信号线分别是RTS、RTS2、DTE-R、DCE-R,它们在设备掉电时保持1,即负电平。在由施密特触发器构成的电路中要特别注意失效信号的问题。

对于信号的交流特性(如图1-23所示),EIA RS-232标准同样作出了规定。细节如下:

●信号的电平一旦进入电平不确定区域,必须在足够短的时间内翻转,而不能返回到原来的电平。离开不确定区域后不能够返回不确定区域。

●控制信号的不确定区域渡越时间小于1ms。

●数据和时钟信号的不确定区域渡越时间由周期确定。

对于大于50ms的周期,渡越时间小于1ms。

对于125µs~50ms的周期,渡越时间不超过周期时间的4%。

对于小于125µs的周期,渡越时间小于5µs。

●信号的上升和下降速率不能高于30V/µs。图1-23 交流特性曲线

表1-2列出了常用的电气特性。1.4.3 机械特性

完整的EIA RS-232信号束有20多个信号接口,在计算机工业发展的过程中,这些信号线被简化为9个,并且慢慢成为了事实的标准。2000年之前出售的计算机多带有25根信号线的接口,但往往只是插头上的兼容,真正实现EIA RS-232完全信号的计算机很少。

EIA RS-232标准没有对接插件本身作强制性的规定。从实际应用上看,采用的是DB-25接插件和DE-9接插件。

读者也许会经常看到“DB-9接插件”、“RS-232接插件”,甚至“串口接头”等说法。希望阅读完这一部分后,读者能够自行纠正一些错误的说法。图1-24 D型迷你接插件

根据信号线数量不同,分为5个子类,它们的名称以英文字母A~E排列,分别代表不同的信号线数量。25线称为DB接口,9线称为DE接口,在字母的后面标注数字其实是多余的。然而,由于9线的DE口和25线的DB口在个人计算机上广泛流行,人们逐渐忘记了第二个字母表示的是接口针的数量,于是9线的接插件也被叫做DB-9接口。

1984年,IBM公司在制定个人计算机构架标准AT时,引入了DB-25和DE-9接口作为串口的接口。在计算机的主板上,DB-25使用的是母头,DE-9使用的是公头。当时,DE-9接口并不为串口所独有,例如,黑白显示器以及VGA的接口曾一度使用DE-9公头。现代微型计算机接口中,除了串口之外,其他设备均不使用DE-9接口。

D型接插件与具体设备无关,但由于缺乏协议级的支持,而且体积庞大,D型接插件没有走向今天USB接口等通用接口的道路。

对于设计一个基于D接口的仪器来说,了解接插件的尺寸尤为重要,例如,可能需要在外壳上凿孔,或者在绘制PCB板时预留足够的空间。

图1-25是D型插头的尺寸,图1-26是D型插座的尺寸。在这两张图中,尺寸单位是毫米(mm),括号中的数据单位是英寸。图中标注字母的尺寸,不同的子类是不一样的,具体到DB-25和DE-9的尺寸,见表1-3。

D型迷你接插件(D-subminiature)是ITT Cannon公司于1952年推出的接口标准序列。分为插头(Plug)和插座(Socket),一般称插头为公头(Mail),插座为母头(Female)。接插件外壳形状像英文字母的D,如图1-24所示,D插接件因此得名。

D型接插件的材料取决于接插件使用的范围,例如,耐磨和耐压程度。通常,D型接插件的外壳使用镀锡钢壳,绝缘体是塑料,连接头使用铜合金。一些要求较严格的场合还需要在铜合金上镀金。

D型接插件的壳缘有螺丝孔,一般要在活动的插件上配备螺杆(如何在这个螺丝孔上配螺丝螺母,目前的状况极度混乱,接头能够连接,但是螺丝无法契合,令人尴尬)。图1-26 D型插座尺寸注:*-P代表插头,-S代表插座。**此数据精度为±0.41。***此数据精度为±0.368。

D型接插件可以直接焊接在电路板上,也可以制作成电缆连接线。对于接插件尾部的要求并不统一。

有关串口通信的连接电缆,EIA RS-232标准没有作细致的要求,但是必须能够保证满足电气特性约束而不带来附加的寄生效应。1.4.4 信号线定义

前面已经提到过,EIA RS-232标准要求使用25根信号线,实际需要22根。关于信号的分配,EIA RS-232标准规定:对于数据终端设备(DTE),使用DB-25公头;对于数据传输设备(DCE),使用DB-25母头,DCE设备与DTE设备的连接使用一公一母的连接电缆。不经过DCE设备的直接通信,就应该使用双母头的电缆。

提示:

若有条件,请到电子元器件商场咨询有关“串口双公头线”的事宜。

一个完全的EIA RS-232标准信号线束不仅包括UART传输的必要信号,而且还具有相当多而细致的关于调制解调器的控制信号。经过简化的9信号束抛弃了大部分调制解调器的控制信号,仅保留具有基本功能的部分。

图1-27和图1-28是DB-25和DE-9接头中的引脚信号分布。由于D型接口的对称性,所以正确识别端口编号很重要。一个技巧是,面对DTE的公头接头,让接口的信号针指向自己。旋转DTE设备(如果台式机不好旋转,就假设它已经转过去了),让D型接口的长边朝左,那么,左下角的那个端子就是编号为1的端子。图1-27 公头引脚编号图1-28 母头引脚编号

表1-4列出了信号线的名称和对应的简写,以及在DB-25和DE-9接口之间的编号区别。

注意,信号线的编号是由信号线所处的位置决定的,因此,在表1-4的“名称”一栏中,有斜线分开的信号,例如RxD/TxD,表示对于DTE和DCE,这个接口有不同的名称。虽然坚持EIA RS-232标准的信号线名称很困难,但是本书中尽可能地使用EIA RS-232标准所规定的用法。

我们把信号线的缩写放到后面的括号里。在以后的叙述中经常会用到这些缩写。

EIA RS-232标准信号线束中绝大多数情况下假设DCE是一个调制解调器。对于终端设备之间的互联,信号线要少得多。

EIA RS-232标准信号线束的诸多信号大致可以分为保护地线、主异步串行传输控制线组、副异步串行传输控制线组、同步串行传输附加控制线组、调制解调器状态控制线组和测试线组;简化的9信号连接中只有保护地线、主异步串行传输控制线组以及部分调制解调器状态控制信号。深度简化的信号连接也就是三线连接法,只保留了主异步串行传输控制线组的TxD、RxD,以及信号地线这3条线。(1)保护地线

保护地线包括一个信号地线(SIGNAL-GND)和外壳接口。对于金属外壳的设备,D型接插件的外壳需要与设备外壳相连接。这样,当不同的设备相连接的时候,设备的金属外壳、D型接插件的外壳、连接电缆的屏蔽层就连接在了一起,形成对信号的有效屏蔽。信号地线是串口信号电平的参考电压相连接的设备,这个电平必须保持相同。信号地线与外壳可以连接,也可以不连接。(2)主异步串行传输控制线组

主异步串行传输控制线组包括4条信号线:TxD、RxD、RTS、CTS(由于SIGNAL-GND是共用的,因此不将它划为任何组中)。TxD和RxD用于数据的传输。这里我们看到,EIA RS-232标准支持“全双工”传输方案,也就是DTE和DCE在同一时刻可以互不干扰地互相传送数据。RTS和CTS属于“硬件互联控制信号”,DTE和DCE设备通过这两个信号进行额外的握手应答。当DTE试图发送给DCE数据时,首先将RTS信号置位(“0”有效)。当DCE准备好后,将CTS置位(“0”有效),代表DTE可以发送数据。(3)副异步串行传输控制线组

副异步串行传输控制线组的信号线与主异步串行传输控制线组信号线的含义相同。设置这组串行传输控制线组的目的是为了在远端设备之间建立可靠的连接,以便进行主异步串行传输控制线组波特率的协调、帧错误反馈等相关数据的传输。副异步串行传输控制线组依靠何种机制以保证传输的可靠性?答案很简单,就是降低数据的传输速率。(4)调制解调器状态控制线组

调制解调器状态控制线组负责有关调制解调器的状态和控制。传统上,由于计算机的通信要靠电话线完成(这个和今天的低速拨号上网原理并不同),因此,调制解调器不仅要有数-模转换的功能,而且还要提供电话接口。

调制解调器本身有拨号功能。当“电话打通”之后,调制解调器将DCE-READY(DSR)信号置为有效(“0”),告诉DTE设备可以进行传输。当调制解调器的数据通道发生错误时,这个信号置为无效。一个典型的错误就是有语音电话的连接。由于语音业务具有高的优先级,因此,数据传输会被打断。有趣的是,调制解调器会把振铃信号转化为数字电平,并通过RI信号线提供给DTE设备。

DTE设备也会告知调制解调器是否已经准备好。这个信号线就是DTE READY(DTR)。对于调制解调器来说,如果这个信号是无效的,则调制解调器不会进行远程连接,或者立即挂断已经建立的连接。非调制解调器设备中,有些会要求DTE使用这个信号,有些会忽略这个信号。因此,使用串口和某个设备进行通信时,如果设备没有反应,则需要首先检查这个信号是否有效。

CD信号的全称是Received Line Signal Detector,由于历史上称做“Carrier Detector”,因此,缩写为CD。这个信号是由DCE产生的。当远程的调制解调器没有应答时,这个信号被本地调制解调器置为无效。这是为主异步串行传输控制线组准备的。对于副异步串行传输控制线组,对应的信号是2nd CD。

此外,还有一个信号Data Signal Rate Selector,这个信号的作用是由DTE选择DCE的两种预先设定好的数据传送率,置位信号选择较高的那个传送率。(5)同步串行传输附加控制线组

这组信号包括TC、RC和ETC。

TC信号的全称是Transmitter Signal Element Timing,即传输时钟。当做为DCE的调制解调器工作在同步模式中时,会产生这个信号,并以这个信号为基准,对DTE的TxD信号线上的信号进行采样。

RC信号的全称是Receiver Signal Element Timing,即接收时钟。这个时钟同样是由调制解调器产生的。在同步传输的过程中,要求DTE设备以这个时钟为基准,对RxD信号进行采样。

ETC信号的全称是External Transmitter Signal Element Timing。这个信号是由DTE产生的。在同步传输中,DTE以这个时钟为基准对RxD信号线上的信号采样,并要求DCE按照这个时钟对TxD信号线上的信号采样。

需要注意的是,当代计算机的软硬件已经不再支持这种同步传输。(6)测试线组

测试线组用于回环测试,即自发自收,如图1-29所示。当测试信号(LL、RL)之一有效时,调制解调器进入测试模式,Test Mode信号被调制解调器置位为有效。

LL即Local Loopback信号,其含义是DTE要求本地调制解调器将DTE设备通过TxD发送的信号直接返到RxD信号中供DTE设备接收。

RL即Remote Loopback信号,其含义是DTE要求远程调制解调器将本地调制解调器发送的信号返给本地调制解调器,并提供给RxD。图1-29 测试模式

当代计算机的软硬件同样不再提供测试模式接口。

在简化的9信号连接里,只有S-GND、TxD、RxD、RTS、CTS、DSR、DTR、RI、Shield信号。为了简化,称TxD、RxD为串口数据线,CTS、RTS为串口控制线,DSR、DTR为串口状态线、S-GND为地线、RI为振铃。对于一般的UART传输,实际上只需要数据线和地线,这种三线接法就是所谓的“深度简化接口”。这里需要提醒读者,在市场上销售的串口电缆中,有一些只提供了三线,如果设备要求使用控制线和状态线,显然,这种电缆是不符合要求的。1.4.5 串口近距离通信

正是因为调制解调器的存在,才有了DTE和DCE的不同区分。但是自从9线串口通信成为事实上的标准之后,人们便不再理会调制解调器。两个设备进行点对点的直连才是天经地义的事情。

EIA RS-232标准的一个特点就是,如果某些方面不按标准执行,也不会出太大的问题。

我们还是希望无论是设计设备还是编写程序,都遵循一个固定的标准。EIA RS-232的建议并非是强制性的,但是某种程度的统一会带来很大的方便。

这一节讨论串口近距离通信的问题。我们的依据就是EIA RS-232标准推荐的设备命名与接口规范。

计算机无论何时都应该是一个DTE设备。那么外围设备应该如何设计接口?

当一个外围设备使用D型母头的时候,就暗示了它是一个能够向远方传递数据,并从远方接收数据的“中介”,也就是DCE。DTE与DCE之间应该采用D型延长线进行连接。当一个外围设备使用D型公头时,就说明它是一个DTE设备。DTE和DTE设备之间应该使用双头交叉母线进行连接。此外,为了照顾到使用DB-25的设备,还有DB-25转DE-9的延长连接与交叉连接(显然,在这种情况下,25线中的大部分信号线被忽略)。下面列出了这几种连接详细的接法。(1)DE-9 DTE设备自连接

如图1-30所示,自连接一般用于自检,即通过软件检测操作系统的串口接口是否通畅。数据线TxD和RxD相连,控制线RTS和CTS相连,状态线均接地。DTR线也可以悬空。图1-30 DE-9 DTE自连接(公头)

如果只检验数据本身,例如,进行一般UART的传输,则可以只连接TxD和RxD。(2)DE-9 DTE与DE-9 DCE延长连接

如图1-31所示,一个带有DE-9公头的DTE设备和一个带有DE-9母头的DCE设备可以不通过任何延长线直接相连。但是由于DE-9的外壳较短,因此,大多数情况下还是需要延长线。延长线也叫并行线、直连线等。使用延长线,实际上将DTE和DCE建立起了如图1-31所示的连接。图1-31 DE-9延长连接(3)DE-9 DTE与DE-9 DTE交叉连接

如图1-32所示,两个DTE设备可以借助双母头交叉线进行连接。在这种连接方式中,RI信号不使用,TxD和RxD、DTR和DSR、CTS和RTS这3对信号彼此互相连接。实际上,只需要TxD、RxD以及S-GND信号就能够完成通信。图1-32 DE-9交叉连接(4)DB-25 DTE与DE-9 DCE延长连接

不同接口之间的延长连接线有两种。由于计算机上提供的DB-25连接口是公头,所以在连接DE-9 DCE设备时,多采用图1-33和图1-34所示的连接方法。图1-33 DB-25-P与DE-9-S延长连接        图1-34 DE-9-P与DB-25-S延长连接(5)DB-25 DTE设备自连接

这种连接方案和DE-9 DTE设备自连接非常相近,如图1-35所示。图1-35 DB-25 DTE自连接(公头)1.4.6 串口通信的流控制

前面已经谈到了串行通信协议。历史上,EIA RS-232曾经支持过基于同步的串行传输。但是由于9信号接口的出现,目前,串口通信已经不再使用同步的方式进行。在下面的章节中,我们不再介绍有关同步传输的内容。

如今,串口通信采用起止式异步协议,也就是UART。因此,“UART”已经成为协议层次上的串口通信代名词。很多转换器以及应用软件都以UART作为标记。

我们知道,在起止式异步协议中,要求数据的发送方和接收方采用统一的电平采样率进行传输。这个采样率也就是我们常说的比特率。比特率在一次传输中是一个固定的值,而且一般要在传输之前约定好。

起止式异步协议每一次传输一帧。异步帧的格式在1.3.4节已有介绍。在理想状况下,帧和帧之间没有间隙,也就是说,某一帧的停止位的下一个比特就是下一帧的开始位。在这种情况下,单位时间的数据传输量就和比特率相一致,也就是比特率乘以有效负载的长度后,再除以帧的长度。此时的数据传送量也叫做吞吐率(Throughput Rate)。

然而,在实际的传输过程中,达到吞吐率的数据传送量往往不现实。由于信道存在干扰,因此,数据的校验和发生错误导致帧丢失。但是在串口通信中,最大的问题并不在信道,而是接收方的数据处理速度。

为了发送和接收数据,发送方需要准备好待发送的数据,放置于缓冲区中。所谓缓冲区,就是一块固定大小的存放数据的单元,一般采用寄存器等数据保持器件实现。发送方的数据生成装置负责填充缓冲区;当缓冲区有数据时,发送器便将数据组装成帧,并通过串口进行发送;对于接收一方,如果在收到数据之后能够在比一个比特对应时长小的时间内处理数据,则可以在处理完成后继续接收,否则,就需要一个接收缓冲区将数据进行暂存。由于向缓冲区填充数据的速度往往很快,这样就解决了速度匹配的问题。

假设数据发送方的数据生成装置填充发送缓冲区的速度很慢,则发送缓冲区会经常处于空(Hungry)的状态,数据的发送就不得不停止;对于接收方而言,接收缓冲区不是无穷大的,如果缓冲区被填满而接收器仍然没有处理完数据,则再接收数据,新的帧就会被丢弃。在这两种情况下,都不能够使数据传输达到最大吞吐率。在前一种情况下,信道没有得到充分的利用,在后一种情况下则问题较大,因为数据被丢失比信道空闲产生的后果更严重。

因此,串口通信仅仅做到能够传输数据是不够的。为了避免数据的丢失,有效利用信道,应该提供一种机制,使发送方和接收方有充分的交流。这就是串口通信引入流控制的起源之一,当然,使用流控制还有别的原因,例如,在传输的起始提供一次握手等。总之,所谓流控制,是通过数据通路额外的信号对数据通路的数据进行通断控制的方法。

流控制是串口通信的重要组成部分。在9信号接口中提供了流控制的信号接口,在计算机体系结构乃至操作系统的层面上,同样对流控制提供了支持。

流控制可分为硬件流控制和软件流控制。硬件流控制又可以分为基于控制线的流控制和基于状态线的流控制。由于基于状态线的流控制主要应用于调制解调器,不同调制解调器对流控制的接受程度和控制方法也大不相同,这里不再介绍。基于控制线的流控制现在有一定的应用,常常把基于控制线的硬件流控制直接称做硬件流控制。下面分别介绍硬件流控制和软件流控制。

1.硬件流控制(控制线)

硬件流控制的控制逻辑在发送器和接收器内部实现。发送器和接收器检查各自的缓冲区,并将缓冲区是否可用的信息通知给对方。

基于RTS/CTS的硬件流控制适用于半双工通信。DTE的RTS与DCE的CTS连接,DTE的CTS与DCE的RTS连接,RTS总是输出端,CTS总是输入端。

RTS的有效意味着连接方式的转变。这种转变可能是从不传输到开始传输,也可能是在半双工通信中改变传输的方向。无论如何,总是传输的发起者在自己的CTS无效的情况下,将RTS置为有效。当通信的任何一方检测到CTS置位有效时,就说明对方希望发起一次传输。此时,这一方需要检测自己是否已经准备好接收,如果已经准备好(缓冲区有空闲),则将自己的RTS置为有效,同时,想发送数据的一方检测到CTS有效,就说明对方已经作出了应答,数据传输开始。

当发送的一方打算停止传输时(例如发送缓冲区已空),要把RTS信号置为无效。接收方检测到CTS无效后,就知道发送方不想发送数据了,此时,接收方首先要把自己的RTS置为无效,然后进行其他收尾处理。若两个控制信号线上都是无效电平,表示一次传输结束。

在传送过程中,如果接收的一方不能够再接收数据(例如接收缓冲区将满)时,首先要把RTS信号置为无效,以告诉发送方停止发送数据。发送方当检测到CTS信号无效时,应该停止发送数据。此时,发送方的RTS信号是有效的,这意味着传输发生了暂停而不是终止。一般来说,在发送方设置一个连接超时,当CTS无效时间超过一定程度后,将RTS置为无效,终止传输。这是因为接收方可能遇到了异常情况,或者信号线被切断(此时CTS是“掉电电平”,即无效)。在接收方,假如仍然可以继续接收数据,并且发送方提供的CTS仍然是有效的,则可以重新置RTS为有效,此时,由于发送方提供的CTS已经有效,因此,这种重置不能够看做是创建新的连接,而是继续传送。

由于EIA RS-232标准中同一个信号线在不同的设备上有两个名字,为了便于理解,我们提供了一个类似四格漫画的说明,如图1-36和图1-37所示。图1-36 传输发起的过程

前面叙述的是EIA RS-232标准推荐的硬件流控制方案。这个方案的特点是,对RTS/CTS所控制状态的判断依赖于发送RTS和查询CTS之间的先后顺序;对超时连接的处理也不尽人意。更重要的是,RTS/CTS方案不能用于全双工通信,使得这两个额外的信号的适用范围受到约束。

EIA RS-232标准并没有强制要求传输采用硬件流控制,大多数使用串口通信的设备也不对上述的流控制方案进行硬件实现,而是留出寄存器接口,供软件写RTS或者读CTS。在调制解调器一边的情况类似,特别是Hayes公司推出的一系列调制解调器,从根本上修改了EIA RS-232标准所建议的流控制方案。图1-37 传输暂停和恢复

因此,如果读者想利用串口对一个已有的设备进行控制,请仔细阅读设备提供的控制流说明,弄清楚这个设备是否提供硬件流控制,如果提供了,那么它的RTS/CTS的信号是怎样定义的。如果读者想自己设计一个串口终端设备与计算机进行通信,则最好采用EIA RS-232标准所推荐的硬件流控制。

2.软件流控制

软件流控制指在全双工通信中,通过发送和检测特殊的帧(流控制字)来实现流控制的应答。如果使用软件流控制,则设备之间可以使用深度简化的三线接口。

进行软件流控制之前,需要设备之间约定好流控制字。流控制字一般采用负载有特定字符的一帧。这个“特定字符”用XON和XOFF表示,通常也不加区分地将流控制字称为XON和XOFF。

发送XON,相当于置RTS有效;接收到XON,相当于检测到CTS有效。发送XOFF,相当于置RTS无效。除此之外,握手和应答过程与硬件流控制相同。

对于XON和XOFF的选取,不同的设备有不同的设置。习惯上,使用0x17作为XON,使用0x19作为XOFF。这两个字符在ASCII表里都是控制字符,因此,如果使用串口传递文本数据,当然不会发生在文本中夹杂XON或XOFF的情况。然而,如果传送二进制数据,则码字冲突在所难免,因此,需要提供扰码的机制。这个和基于字符的同步协议非常类似。

软件流控制的优点是硬件连接少,配置方便;缺点是必须在全双工的场合使用,并需要进行扰码。

硬件流控制和软件流控制各有千秋,在实际应用中,可以在不同的环境中采用其中之一,或者不使用流控制。1.5 RS-422与RS-485串行接口标准

RS-232标准所规定的传输,其连线简单,易于使用,但同时也带来了传输距离短、抗干扰能力差等问题。RS-422由RS-232发展而来,它是为弥补RS-232之不足而提出的。为改进RS-232通信距离短、速率低的缺点,RS-422定义了一种平衡通信接口,将传输速率提高到10Mb/s,传输距离延长到4000英尺(速率低于100kb/s时),并允许在一条平衡总线上连接最多10个接收器。RS-422是一种单机发送、多机接收的单向、平衡传输规范,被命名为TIA/EIA-422-A标准。为扩展应用范围,EIA又于1983年在RS-422基础上制定了RS-485标准,增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,扩展了总线共模范围,后命名为TIA/EIA-485-A标准。由于EIA提出的建议标准都是以“RS”作为前缀,所以在通信工业领域,仍然习惯将上述标准以“RS”作前缀称谓。

RS-422、RS-485与RS-232不一样,前两个标准的数据信号采用差分传输方式,也称做平衡传输,它使用一对双绞线,将其中一线定义为A,另一线定义为B,如图1-38所示。图1-38 平衡传输模式

通常情况下,发送驱动器A、B之间的正电平在+2~+6V之间,是一个逻辑状态,负电平在−2~6V之间,是另一个逻辑状态。另有一个信号地C,在RS-485中还有一个“使能”端,而在RS-422中是可用可不用的。“使能”端是用于控制发送驱动器与传输线的切断与连接。当“使能”端起作用时,发送驱动器处于高阻状态,称做“第三态”,即它是有别于逻辑“1”与“0”的第三态。

接收器也作与发送端相对的规定,收、发端通过平衡双绞线将AA与BB对应相连,当在收端AB之间有大于+200mV的电平时,输出正逻辑电平,小于−200mV时,输出负逻辑电平。接收器接收平衡线上的电平范围通常在200mV~6V之间,如图1-39所示。图1-39 接收器接收平衡线上的电平范围1.5.1 RS-422电气规定

RS-422标准全称是“平衡电压电气特性数字接口电路”,它定义了接口电路的特性。如图1-40所示为典型的RS-422四线接口。实际上还有一根信号地线,即共5根线。图1-40 RS-422二线制单向通信

如图1-41所示为其DE-9连接器引脚定义,这与RS-232有所区别。图1-41 DE-9连接器引脚定义

由于接收器采用高输入阻抗和发送驱动器比RS-232更强的驱动能力,故允许在相同传输线上连接多个接收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Salve),从设备之间不能通信,所以RS-422支持点对多的双向通信。接收器输入阻抗为4kΩ,故发端最大负载能力是10×4kΩ+100Ω(终接电阻)。RS-422四线接口由于采用单独的发送和接收通道,因此,不必控制数据方向,各装置之间任何必需的信号交换均可以按软件方式(XON/XOFF握手)或硬件方式(一对单独的双绞线)。

RS-422的最大传输距离为4000英尺(约1219m),最大传输速率为10Mb/s。其平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能达到最大传输距离。只有在很短的距离下才能获得最高速率传输。一般100m长的双绞线上所能获得的最大传输速率仅为1Mb/s。

RS-422需要一个终接电阻,要求其阻值约等于传输电缆的特性阻抗。在短距离传输时可不需终接电阻,即一般在300m以下不需终接电阻。终接电阻接在传输电缆的最远端。

有关RS-422电气参数见表1-5。1.5.2 RS-485电气规定

由于RS-485是从RS-422基础上发展而来的,所以RS-485许多电气规定与RS-422相同,如都采用平衡传输方式,都需要在传输线上接终接电阻等。RS-485可以采用二线与四线方式,二线制可实现真正的多点双向通信,如图1-42所示。图1-42 RS-485二线制通信示意图

采用四线连接时,与RS-422一样只能实现点对多的通信,即只能有一个主(Master)设备,其余为从设备,但它比RS-422有改进,无论是四线还是二线连接方式,总线上可接多达32个设备,如图1-43所示。图1-43 RS-485四线制双向通信示意图

RS-232、RS-422与RS-485有关电气规定的比较参见表1-5。

RS-485与RS-422一样,其最大传输距离约为1219m,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能使用规定最长的电缆长度。只有在很短的距离下才能获得最高速率传输。一般100m长双绞线的最大传输速率仅为1Mb/s。

RS-485需要两个终接电阻,其阻值要求等于传输电缆的特性阻抗。在短距离传输时可不需终接电阻,即一般在300m以下不需终接电阻。终接电阻接在传输总线的两端。1.5.3 RS-422与RS-485的网络安装注意要点

RS-422可支持10个节点,RS-485支持32个节点,因此,多节点构成网络。网络拓扑一般采用终端匹配的总线型结构,不支持环形或星形网络。在构建网络时,应注意如下两点。(1)总线连接的方式

采用一条双绞线电缆作总线,将各个节点串接起来,从总线到每个节点的引出线长度应尽量短,以便使引出线中的反射信号对总线信号的影响最低。如图1-44所示为实际应用中常见的一些错误连接方式(a,b,c)和正确的连接方式(d,e,f)。a,b,c这3种网络连接尽管不正确,在短距离、低速率下仍可能正常工作,但随着通信距离的延长或通信速率的提高,其不良影响会越来越严重,主要原因是信号在各支路末端反射后与原信号叠加,会造成信号质量下降。(2)总线特性阻抗

应注意总线特性阻抗的连续性,在阻抗不连续点就会发生信号的反射。下列几种情况易产生这种不连续性:总线的不同区段采用了不同电缆,或某一段总线上有过多收发器紧靠在一起安装,再者是过长的分支线引出到总线。

总之,应该提供一条单一、连续的信号通道作为总线。图1-44 常见错误连接方式(a,b,c)和正确的连接方式(d,e,f)1.5.4 RS-422与RS-485传输线上匹配的一些说明

对于RS-422与RS-485总线网络,一般要使用终接电阻进行匹配。但在短距离与低速率下可以不用考虑终端匹配。那么在什么情况下不用考虑匹配呢?理论上,在每个接收数据信号的中点进行采样时,只要反射信号在开始采样时衰减到足够低就可以不考虑匹配。但实际上难以掌握,美国MAXIM公司有篇文章提到一条经验性的原则可以用来判断在什么样的数据速率和电缆长度时需要进行匹配:当信号的转换时间(上升或下降时间)超过电信号沿总线单向传输所需时间的3倍以上时就可以不加匹配。例如,具有限斜率特性的RS-485接口MAX483输出信号的上升或下降时间最小为250ns,典型的双绞线上的信号传输速率约为0.2m/ns(24AWG PVC电缆),那么只要数据速率在250kb/s以内,电缆长度不超过16m,采用MAX483作为RS-485接口时就可以不加终端匹配。

一般终端匹配采用终接电阻方法,前文已有提及,RS-422在总线电缆的远端并接电阻,RS-485则应在总线电缆的开始和末端都需并接终接电阻。终接电阻一般在RS-422网络中取100Ω,在RS-485网络中取120Ω。相当于电缆特性阻抗的电阻,因为大多数双绞线电缆特性阻抗大约为100~120Ω。这种匹配方法简单有效,但有一个缺点,即匹配电阻要消耗较大功率,对于功耗限制比较严格的系统不太适合。

另外一种比较省电的匹配方式是RC匹配,如图1-45所示。利用一只电容C隔断直流成分可以节省大部分功率。但电容C的取值是一个难点,需要在功耗和匹配质量间进行折中。图1-45 RC匹配

还有一种采用二极管的匹配方法,如图1-46所示。这种方案虽未实现真正的“匹配”,但它利用二极管的钳位作用能迅速削弱反射信号,达到改善信号质量的目的,其节能效果显著。图1-46 二极管的匹配方法1.5.5 RS-422与RS-485的接地问题

1.忽略信号地连接的原因

电子系统接地是很重要的,但常常被忽视。接地处理不当往往会导致电子系统不能稳定地工作,甚至危及系统安全。RS-422与RS-485传输网络的接地同样也是很重要的,因为接地系统不合理会影响整个网络的稳定性,尤其是在工作环境比较恶劣和传输距离较远的情况下,对于接地的要求更为严格,否则接口损坏率较高。很多情况下,连接RS-422、RS-485通信链路时只是简单地用一对双绞线将各个接口的A、B端连接起来。若忽略了信号地的连接,在许多场合虽然能正常工作,但却埋下了很大的隐患,这有下面两个原因。(1)共模干扰问题

正如前文已述,RS-422与RS-485接口均采用差分方式传输信号方式,并不需要相对于某个参照点来检测信号,系统只需检测两线之间的电位差就可以了。但人们往往忽视了收发器有一定的共模电压范围,如RS-422共模电压范围为-7~+7V,而RS-485收发器共模电压范围为-7~+12V,只有满足上述条件,整个网络才能正常工作。当网络线路中共模电压超出此范围时,就会影响通信的稳定,甚至损坏接口。以图1-47为例,当发送驱动器A向接收器B发送数据时,发送驱动器A的输出共模电压为V,由于两个系统具有各自独立的接地系OS统,存在着地电位差V。那么,接收器输入端的共模电压VCM就GPD会达到V=V+V。RS-422与RS-485标准均规定V≤3V,但CMOSGPDOSV可能会有很大幅度(十几伏甚至数十伏)的变化,并可能伴有GPD强干扰信号,致使接收器共模输入V超出正常范围,并在传输线路CM上产生干扰电流,轻则影响正常通信,重则损坏通信接口电路。图1-47 共模干扰问题(2)EMI(电磁干扰)问题

发送驱动器输出信号中的共模部分需要一个返回通路,如没有一个低阻的返回通道(信号地),就会以辐射的形式返回源端,整个总线就会像一个巨大的天线向外辐射电磁波。

2.解决方法

由于上述原因,RS-422、RS-485尽管采用差分平衡传输方式,但对整个RS-422或RS-485网络,必须有一条低阻的信号地。一条低阻的信号地将两个接口的工作地连接起来,使共模干扰电压VGPD被短路。这条信号地可以是额外的一条线(非屏蔽双绞线),或者是屏蔽双绞线的屏蔽层。这是最通常的接地方法。

值得注意的是,这种做法仅对高阻型共模干扰有效,由于干扰源内阻大,短接后不会形成很大的接地环路电流,对于通信不会有很大影响。当共模干扰源内阻较低时,会在接地线上形成较大的环路电流,影响正常通信。笔者认为,可以采取以下3种措施。(1)限流电阻

如果干扰源内阻不是非常小,可以在接地线上加限流电阻以限制干扰电流。接地电阻的增加可能会使共模电压升高,但只要控制在适当的范围内就不会影响正常通信。(2)采用浮地技术,隔断接地环路

这是较常用也是十分有效的一种方法,当共模干扰内阻很小时,限流电阻的方法已不能奏效,此时可以考虑将引入干扰的节点(例如,处于恶劣的工作环境的现场设备)浮置起来(也就是系统的电路地与机壳或大地隔离),这样就隔断了接地环路,不会形成很大的环路电流。(3)采用隔离接口

有些情况下,出于安全或其他方面的考虑,电路地必须与机壳或大地相连,不能悬浮,这时可以采用隔离接口来隔断接地回路,但是仍然应该有一条地线将隔离侧的公共端与其他接口的工作地相连,如图1-48所示。图1-48 采用隔离接口1.5.6 RS-422与RS-485的网络失效保护

RS-422与RS-485标准都规定了接收器门限为±200mV。这样规定能够提供比较高的噪声抑制能力。如前文所述,当接收器A电平比B电平高+200mV以上时,输出为正逻辑,反之,则输出为负逻辑。但由于第三态的存在,即主机在发端发完一个信息数据后,将总线置于第三态,即总线空闲时没有任何信号驱动总线,使AB之间的电压在−200~+200mV之间直至趋于0V,这带来了一个问题:接收器输出状态不确定。如果接收机的输出为0V,网络中从机将把其解释为一个新的启动位,并试图读取后续字节,由于永远不会有停止位,从而产生一个帧错误结果,不再有设备请求总线,网络陷于瘫痪状态。除上面所述的总线空闲会造成两线电压差低于200mV的情况外,开路或短路时也会出现这种情况。故应采取一定的措施避免接收器处于不确定状态。

通常是在总线上加偏置,当总线空闲或开路时,如图1-49所示,利用偏置电阻R1、R2将总线偏置在一个确定的状态(差分电压≥−200mV)。图1-49 总线上加偏置电阻

总线偏置电阻将A上拉到VCC,B下拉到GND,电阻的典型值是1kΩ,具体数值随电缆的电容变化而变化。

上述方法是比较典型的方法,但它仍然不能解决总线短路时的问题,有些厂家将接收门限移到−200~−50mV之间,可解决这个问题。例如,Maxim公司的MAX3080系列RS-485接口,不仅省去了外部偏置电阻,而且解决了总线短路情况下的失效保护问题。1.5.7 RS-422与RS-485的瞬态保护

前文提到的信号接地措施只对低频率的共模干扰有保护作用,对于频率很高的瞬态干扰就无能为力了。由于传输线对高频信号而言就相当于电感,因此,对于高频瞬态干扰,接地线实际上等同于开路。这样的瞬态干扰虽然持续时间短暂,但可能会有成百上千伏的电压。

实际应用环境下还是存在高频瞬态干扰的可能。一般在切换大功率感性负载如电机、变压器、继电器等或闪电过程中都会产生幅度很高的瞬态干扰,如果不加以适当防护,就会损坏RS-422或RS-485通信接口。对于这种瞬态干扰,可以采用隔离或旁路的方法加以防护。(1)隔离保护方法

这种方案实际上将瞬态高压转移到隔离接口中的电隔离层上,由于隔离层的高绝缘电阻不会产生损害性的浪涌电流,起到保护接口的作用。通常采用高频变压器、光耦等元件实现接口的电气隔离,已有器件厂商将所有这些元件集成在一片IC中,使用起来非常简便,如Maxim公司的MAX1480/MAX1490,隔离电压可达2500V。这种方案的优点是可以承受高电压、持续时间较长的瞬态干扰,实现起来也比较容易,缺点是成本较高。

53(2)旁路保护方法

这种方案利用瞬态抑制元件(如TVS、MOV、气体放电管等)将危害性的瞬态能量旁路到大地,优点是成本较低,缺点是保护能力有限,只能保护一定能量以内的瞬态干扰,持续时间不能很长,而且需要有一条良好的连接大地的通道,实现起来比较困难。

实际应用中是将上述两种方案结合起来灵活加以运用。在这种方法中,隔离接口对大幅度瞬态干扰进行隔离,旁路元件则保护隔离接口不被过高的瞬态电压击穿。一些商用的专用芯片已经集成了类似的功能,例如ADM2485等。1.6 本章小结

本章介绍了串行通信协议的基本知识,并详细讲解了串行数据接口标准RS-232、RS-422与RS-485的电气特性、机械特性、信号线定义、流控制等内容。其中,RS-232是PC与通信工业中应用最广泛的一种串行接口,以后各章将主要围绕该协议展开。第2章 异步串行通信接口电路简介

本章介绍串口接口电路的实现。在个人计算机上,所使用的串口模型电路是8250,而在各种串口兼容设备中,8250较为少见。在8086小系统以及Intel单片机系统中,多使用8251作为串口适配器。随着片上系统(SoC)的逐渐流行,许多数字芯片都内建了对串口通信的支持,那么,这些嵌入式的电路是如何实现的?在本章有较详细的介绍。2.1 8250兼容接口电路2.1.1 8250兼容接口电路概述

IBM PC/AT构架除了包含处理器之外,还有各种总线适配器。这些适配器的一端与处理器的I/O总线相连接,另一端连接各种外围设备。

IBM PC/AT构架中,每个总线适配器都使用独立的专门集成电路实现。在早期,实现串口接口的集成电路是美国国家半导体(National Semiconductor)推出的INS 8250。这种集成电路是出现较早的可编程电路之一。可编程电路含有寄存器及寄存器接口,能够通过运行在处理器上的软件对其行为进行控制。

由于8250的功能有限,因此,在8250之后,又推出了其他一些串口接口适配器。8250A、8250B是8250的修改版,其主要目的是修正8250当中的一些错误。

作为UART的8250,其最大的问题是传输速率有限。美国国家半导体推出了16450和16550系列芯片,从基本构架上增加了新的特性,包括多位缓冲区、自实现的传输率控制器和中断接口。

最后一款16550芯片是1995年推出的16550D。除在PC上使用之外,这款芯片还在调制解调器、串口打印机等设备上得到应用。而一些串口数据接口设备也经常使用16550芯片,只要在I/O接口的一端符合8086的I/O时序,16550就能够工作,完成并行数据到串行数据的转换。

除8250、16550之外,还有过渡类型的16450以及增强型的16750等。在1.2.3节已经介绍过,在当代计算机体系结构中,串口接口已经作为一个LPC总线设备集成到了多功能接口芯片中,这些接口芯片的串口接口与16550兼容。

每一种芯片各自的版本众多,而且8250和16550的早期版本存在比较严重的设计错误。在下面的叙述中,我们主要以8250A、16550D作为原型讨论,并在必要的时候兼顾16750,并且以“8250”代替8250A、“16550”代替16550D,以表示系列的主流产品以及叙述的方便。

我们知道,计算机的体系结构在发展过程中,最大限度地保持了向下兼容。在I/O接口设备中的向下兼容体现在总线的透明上。也就是说,无论计算机的物理结构如何,在程序看来并没有发生什么变化。在当代计算机体系结构中,在CPU是8086、串口适配器是8250这个假设的前提下进行分析和软件设计,是不存在任何逻辑问题的。因此,8250又称为UART编程模型。特别是在常用波特率(9600b/s)的条件下,无论实际的计算机采用何种芯片(8251、16550等),其编程模型与8250无大的区别。所以,本节均以8250作为原型讨论问题。其他的模型,例如8086、8259等,道理相同。

8250支持异步串行传输,其传输方式支持全双工。8250只实现了EIA RS-232中的9线简单接口。8250的输出电平不符合EIA RS-232的电平标准,需要使用专用电平转换电路(EIA-DRIVER)进行电平转换。在IBM PC/AT构架中,电平转换电路一般使用1488和1489集成电平转换器。

8250向8086总线引出了标准I/O接口,包括单向控制总线、双向的数据总线以及中断。8250和其他总线从电路一样,没有私有的地址解码,只有地址信号和片选信号。

如图2-1所示,INS8250使用3.072MHz的主时钟,并内建了波特率控制电路。其波特率信号还能够输出到片外。同样,INS8250也可以使用外部时钟信号作为波特率参考。图2-1 INS8250连接

NS16450等8250的后续版本也采取相同的信号定义和连接方案。2.1.2 8250的结构

串行接口的核心电路是移位寄存器(SHIFT REGISTER)阵列。

如图2-2所示为异步串行发送电路的一种实现方式。这个发送电路按照1bit起始位、8bit数据位、1bit校验位和1bit停止位的方式将数据转化为串行信号。当不进行发送时,输出多路选择器向TxD输出停止位信号(Stop bit);当开始发送时,首先要将起始位(比特“0”)、数据以及奇偶校验一起送到移位寄存器阵列中,发送开始后,第一个时钟周期内,首先送出0号寄存器的内容,同时,n号寄存器加载n+1号寄存器当中的内容,第二个时钟周期,送出的将是原先1号寄存器的内容,依此类推。发送电路的波特率由时钟进行控制。

如图2-3所示为异步串行接收电路的一种实现方式。在每个时钟周期,都检查RxD是否收到开始位信号。如果是,则在下一个时钟周期信号开始,就依次接收RxD上的信号,包括数据和校验位。接收完毕,接收电路不仅需要将接收到的数据存储于接收缓冲区,而且需要做必要的检查工作。这些检查工作至少包括帧的长度是否符合所设定的协议要求、奇偶校验是否正确等。图2-2 一种UART发送电路的实现图2-3 一种UART接收电路的实现

实际的发送电路和接收电路比上面提到的实现方式都要复杂。例如,为了提高效率,奇偶校验会在发送和接收的同时进行计算,而不是先计算再发送,或者先接收再计算。此外,作为计算机上的附属设备,需要有一定的通用性。发送电路和接收电路都需要配置成不同的数据位或者有无校验位。但无论如何,其最根本的原理是一致的。

8250以基本的UART发送和接收电路为主体,同时配有其他的辅助电路,不仅对总线提供了丰富的功能,而且配置灵活。

如图2-4所示为8250的功能框图。

从功能上,可以分为传输和波特率产生功能区、调制解调器接口功能区、中断接口功能区、总线控制接口功能区以及电源(未画出)。

从图中可以看出,每一个功能区都向总线引出数个寄存器,这些寄存器共享一条数据总线。总线控制接口负责将这些寄存器映射到总线地址空间中,这样,软件就可以通过执行x86指令集中的I/O指令,对这些寄存器进行读写操作。8250的这些寄存器统称编程接口,这是我们下一节讨论的问题。2.1.3 8250的编程方法

即使是讨论编程方法,我们也需要对计算机的总线结构以及必要的控制电路有足够的了解。尽管我们讨论的话题是8250的编程方法,但是除此之外,我们需要理解可编程中断控制器(8259)和处理器(8086)的编程方法。图2-4 8250的功能框图

与8250相同,8259和8086的接口电路在当代计算机体系中仍然保持了对它们的兼容。

对于现在的操作系统来说,其底层驱动程序已经为我们做好了所有的事情。其基本的出发点和这里讨论的问题没有差别。因此,我们把重点放在“如何使之工作”。然而,如果需要使用8250作为一个同计算机通信的接口(尽管多数人喜欢比较简单的8251A),那么,你就需要对整个问题有一个深入的理解和丰富的实践。

1.8086 的I/O接口

比8086更早的通用处理器是Intel 4004和Intel 8008。Intel 8008是第一款8位微处理器,也是Intel处理器相兼容的底线。从Intel 8008开始,有了字节(Byte)的概念。一个字节等注意:

于8比特。即使是以后的16位、32位,乃至今天的64位处理器中,仍然没有改变字节的宽度,而是将新的数据位宽定义为双字节、四字节等。Intel的向下兼容策略虽然导致了新的处理器必须拥有一套复杂的模拟机制,使得在8008时代的程序仍然可以无障碍地运行,但这也带来了稳定性的好处,即一成不变的编程方法可以沿用很长时间。

8086处理器假设所有的外围设备都拥有一个或者数个寄存器,无论这些外围设备多么复杂,8086处理器都只和这些寄存器打交道。8008提供了8位的地址线、8位的数据线以及读写信号线,这样,通过地址线的译码,处理器就能够访问256个寄存器当中之一,并进行一次一个字节的数据交换。这里的地址线、数据线和读写信号线就是处理器的I/O总线。

地址译码采用简单译码的方式。例如,地址0x03就代表了选中第3号寄存器。一般来说,将0x00到0xFF这一端连续的地址称为地址空间。对于设备而言,一般要占用多个寄存器,并且这些寄存器在地址空间中是连续的,也就是说,假如一个设备使用了3个寄存器,第一个寄存器的地址是0x05,则其余两个寄存器的地址就是0x06和0x07。我们将第一个寄存器的地址称为设备基址(BASE),也称做“设备的地址”;其他两个寄存器的地址称为偏移,即0x06记作+1、0x07记作+2,当然,第一个寄存器的偏移可以记作+0。

8008的下一代乃至下几代处理器,其地址空间的位数得到扩展。8086的I/O接口形式上是16位,但实际上只有10位,直到Pentium处理器,才完全使用16位的I/O接口。不过,当我们谈到串口适配器的I/O地址时,仍然使用10位的表示方法。

在x86的指令集中,使用OUT和IN指令进行I/O空间的读写。OUT指的是将数据从CPU内部的寄存器输出到I/O总线上的寄存器,IN指令与之相反。例如,有一个I/O寄存器,其地址是0x09,要将内部寄存器AH中的数据输出到这个I/O寄存器,所使用的指令是

要将这个I/O寄存器中的数据读入到内部寄存器AH,则使用指令

C语言的标准库使用inp()和outp()两个函数进行I/O操作,当编译时将转换成对应的汇编指令。inp()对应IN指令,outp()对应OUT指令。例如,将0x09寄存器写入数据0xFF,则对应的语句为

读入这个寄存器的数据时,使用下面的语句

OUT指令以及outp()函数是为数不多的可能将对计算机构成损坏的指令之一,这是由于如果不了解写入地址对应的寄存器的功能,则可能导致修改计算机运行所依赖的重要的数据。在80386以后的CPU中均提供了保护机制,配合现代操作系统中的权限保护措施,使得一般的应用程序无法执行OUT指令。并且,IN指令也被禁止,防止设备中的敏感数据被非法获取。

如果读者打算进行练习,可采取以下的方法之一。(1)使用DOS或者基于DOS的操作系统

DOS系统下,应用程序可以不加限制地访问I/O总线上的设备。对于基于DOS的操作系统,例如,Windows 3.2、Windows 95、Windows 98等,实践表明,在这些操作系统上直接访问I/O资源仍然通畅。(2)使用虚拟机

现代虚拟机软件能够很好地模拟处理器和串口。在虚拟机中可以安装DOS操作系统以及汇编器、编译器等。这种方法非常安全,但是不能够访问主计算机中的硬件资源。有一些虚拟机软件能够将主计算机上的物理串口映射为虚拟机上的串口,不过其效率有所折扣,并且不能够映射任意地址空间上的设备。(3)使用端口模拟软件

一些开发者设计了端口模拟软件(库),可以在不能直接访问I/O资源的操作系统上采用间接的方法对I/O资源进行访问。它们的原理是提供驱动程序以及驱动程序的接口。比较流行的模拟软件(库)有WinIO以及PortTalk等。

需要说明的是,I/O操作每一次同时对寄存器的8位进行操作,但是,一般来说,如果只针对其中的某一位进行操作,就需要使用AND指令和OR指令。例如,将某地址的寄存器的第2位设置为0(或1),首先需要读出这个寄存器的所有内容到内部寄存器(例如AL),下一步是将内部寄存器的值,与一个特殊的常数值进行AND(或OR)操作。这个值称做屏蔽值(Mask),代表欲进行操作位的位置。如果是对第2位(即右起第3位,注意位的序号从0开始)进行操作,那么这个屏蔽值就是二进制的11111011(对应于AND操作,而OR操作的屏蔽值是00000100)。我们知道,AND(或OR)的含义是“按位与”(或“按位或”),当AL中的一个位与1进行AND操作时(或与0进行OR操作时),其值不改变;当AL中的一个位与0进行AND操作时(或与1进行OR操作时),其值改变为0(或改变位1)。这样就可以通过选取特殊的屏蔽值进行操作,并重新写入到寄存器所在地址,达到改变某些特定的位而其他的位不发生变化的目的。

在寄存器的说明中,经常遇到“置位”或“清位”的说法。“置位”的意思是将这一位写入1,“清位”的意思是将这一位写入0。至于寄存器的功能位是通过置位还是清位体现的,要仔细阅读寄存器描述中对这一位的说明。

2.中断和中断控制器

I/O接口能够实现处理器与外围设备的数据交换,交换过程中,处理器处于主动的地位。然而一些情况下,设备最好也能够参与到程序流的控制当中。比如这样一种情况:当我们想通过串口适配器发送数据时,只需要设置好相应的寄存器并投递数据。然而,接收数据的情况有些不相同,尽管一旦数据到达,串口适配器会根据协议接收数据并保存于缓冲区,但是处理器不能够预料接收缓冲区的数据何时有效。一种方法是不断地询问串口适配器,这会浪费处理器的周期。因此,必须建立起一套设备及时通知处理器的机制,这就是中断(Interrupt)。

x86处理器只有一个中断接口,但是需要使用的中断源不止一个。因此,需要对中断进行扩展。中断的扩展由专门的中断扩展接口芯片完成。在IBM-PC构架中,使用了一个中断扩展接口,将一个中断扩展为8个;在IBM PC/AT构架中,又扩展了一个中断接口芯片,第二个中断接口芯片的中断“输出”接到了第一个中断接口芯片的输入上,因此,总的中断数目增加到了15个。这种中断级联的构架一直延续到现在。

IBM PC/AT构架的中断分配方案如图2-5所示。图2-5 中断及中断分配

IRQ的含义是“中断源请求”(Interrupt ReQuest),编号从IRQ0到IRQ15(其中IRQ2不再作为设备中断使用)。从图中看到,为串口准备了两个中断,分别是COM2对应的IRQ3和COM1对应的IRQ4。

IBM PC/AT使用8259作为可编程的中断控制器(PIC)。PIC在总线上的地址分别为0x0021(主PIC)和0x00A1(从PIC)。PIC有一个重要的寄存器是中断控制字寄存器(OCW),这个寄存器负责中断信号的屏蔽。如果这个寄存器的某一位置位,则说明所对应的中断信号被屏蔽。

因此,尽管中断信号是由串口适配器产生的,但是需要在PIC上解除屏蔽。如图2-6所示,OCW寄存器的偏移是1,所以,如果使用COM2,就需要解除IRQ3的屏蔽,汇编指令为图2-6 寄存器设置

所对应的屏蔽IRQ3的指令是

当来自IRQ的中断发生后,首先要经过中断控制器。如果下列两个条件都满足,则这个中断会提交给处理器。这两个条件是:

●中断控制器没有屏蔽这个中断。

●优先级判断的结果表明,这个中断应该有较高的优先级。

如果中断最终提交给了处理器,则处理器会保存当前程序执行的一些必要的状态,然后进行中断跳转,执行中断服务子程。中断服务子程结束后,会要求处理器返回原先的状态(中断返回指令),处理器恢复原先被打断的程序的状态,并继续执行。这样,通过处理器内建的中断功能以及中断控制器,程序执行的效率能够得到提高。

此外,在图2-5中还可以看到,COM2占用IRQ3,COM1占用IRQ4。一般情况下,IRQ3的优先级高于IRQ4,因此,这也是常推荐优先使用COM2的原因。不过,这种中断优先级常在极其特殊的情况下才考虑使用。

3.串口适配器编程

标准IBM PC/AT构架中配备了4个串口,即COM1~COM4。它们的IRQ资源号和基地址见表2-1。

表2-1所列的基地址是“构架地址”,也就是指固定在主板(OnBoard)上的串口地址。但是如果使用PCI或者ISA的串口扩展卡,则这个地址有可能不再是表2-1所列的地址。一般来说,在计算机的BIOS设置中都会说明串口的中断资源和基地址。如图2-7所示,注意左列的最后一行和右列的第一行。图2-7 AWARD BIOS设置

在表2-1中还应注意到,COM1和COM3、COM2和COM4共享IRQ资源。这是因为中断控制器的资源实际上非常紧缺,因此,不能够为每一个设备直接分配一个中断。如何对共享IRQ的资源进行编程,下面会有介绍。

串口适配器拥有12个不同的寄存器,分布在8个I/O地址空间中。显然,一些寄存器存在空间复用的情况。所谓寄存器空间复用,指的是实质上不同的寄存器使用同一个地址。那么在编程时如何区分这些寄存器?一种情况是读写复用,即进行读操作和写操作时,数据实际上会在不同的寄存器与总线之间流动。另一种情况是状态复用,即使用一个特殊的寄存器设置设备所工作的不同状态,在不同的状态下,一些地址所指向的寄存器有所不同。这两种情况在串口控制器中都有涉及。

表2-2为8250及兼容串口适配器的寄存器分布。

其中,DLAB列是除数寄存器锁存,它位于LCR的最高位。这就是所谓的“状态复用”。如果某寄存器该列的值为0,说明在DLAB无效的情况下,该地址指向这个寄存器;如果某寄存器该列的值为1,说明在DLAB有效的情况下,该地址指向这个寄存器;如果某寄存器该列的值为x,说明该寄存器不受DLAB的影响。另外有两对寄存器存在读写复用,即THR与RBR、IIR与FCR,其余的寄存器不存在复用。

寄存器的偏移前面已有介绍。一般来说,为了保持程序的通用性,寄存器读写程序通常为

下面分别介绍每个寄存器的含义以及功能。(1)THR/RBR(数据传送寄存器)

这两个寄存器负责数据的发送和接收。当数据写到THR后,在下一个时钟周期,这个数据就被发送;当有数据来到时(通常是中断进行通知),读取这个寄存器就能够得到接收的数据。

在8250的早期,或者说比8250更早的接口电路中,这两个寄存器设计得非常简单。而在16550等串口适配器中,增加了传输队列(FIFO)。这样,可以一次写入多个数据,或者一次读出多个数据,以保证处理器不会在写数和读数上花费太多精力。(2)DLL/DLH分频数

通过分频数(DL)可以进行波特率的设置。分频数是一个16位的整数,高位存于DLH寄存器,低位存于DLL寄存器。8250内部,首先将外部波特率时钟转化成标准115.2kHz的时钟。常用外部波特率时钟是1.8432MHz,8250就将它进行16分频(1.8432MHz=16×115.2kHz)。在8250兼容电路以及其后的IBM PC/XT架构中,使用了其他频率的外部时钟,但是编程模型总认为串口适配器有一个115.2kHz的时钟。分频数可以看做一个计数器满载值,有一个计数器以115.2kHz的频率进行计数,记到分频数代表的满载值就归零,并产生一个脉冲信号作为最终的波特率时钟参考提供给发送和接收移位寄存器。因此,发送和接收移位寄存器的波特率可以通过下式进行计算:

由于DL可取值为0~65535,所以看起来波特率可以使用很多取值。但是原则上只取一些特定的波特率,这可能是由于早期的设备不具备复杂的重配置功能。这些特定的波特率称为常用波特率,与此同时,DL也最好取与常用波特率对应的值。

常用波特率如表2-3所示。

设置分频数时应该注意,在波特率大于600时(事实上,串口工作的波特率通常都大于这个值),分频数的高位都是零。然而这并不意味着在改变波特率时,只去改变分频数低位寄存器而不去设置高位。进行完整的设置是一个好习惯,无论是考虑代码的易读性还是程序的可靠性。此外,分频数如果设置为0,串口适配器就会以不确定的波特率工作,导致产生潜在的问题。

由于分频数寄存器是状态复用的,并且被复用的寄存器(数据传送寄存器)的使用远比分频数寄存器频繁。所以,应该假设大部分时间内DLAB都是置位0的,只有当设置分频数时才置为1,并且设置完分频数后,立即将DLAB恢复为0。

下面是一段示例代码。(3)IER(中断允许寄存器)

8250自身提供了若干可能产生中断的事件,从这个意义上讲,8250相当于对IRQ又进行了一次扩展。与IRQ控制器的中断允许类似,这个寄存器用于控制内部众多中断事件的屏蔽。

表2-4说明了8250中需要IER参与的各种中断。

在16750串口适配器中,增加了低功耗中断和睡眠中断,分别位于bit5和bit4。“接收寄存器已满”:是最常用的中断。当8250接收到一帧数据,并完成校验等工作之后,这个中断会产生。对于16550,这个中断的含义是接收缓冲区队列已满。“发送寄存器已空”:当数据写入THR寄存器后,8250需要一定的时间进行串并转换和发送。正常情况下是立即完成的,但是考虑到UART的握手以及控制等,会出现数据不立即发送完毕的情况,此时如果在此写入数据,有可能造成数据丢失。因此,每写入一个字节,利用中断通知写入下一个字节是一个很好的解决方案。对于16550,这个中断的含义是发送缓冲区的所有数据都已经发送完毕,可以继续填充缓冲区。“接收线状态中断”:指的是接收线状态发生了改变。如果使用这个中断,可以考虑读取LSR的值,在软件一端进行错误处理等。“调制解调器状态中断”:用于通知除数据传送之外的调制解调器信息,例如,RI信号等。这个中断一般用于硬件流控制。

IER对相应的位进行操作可以控制这些中断是否屏蔽,注意,写入1是中断允许,这和8259有所不同。对于保留的位,直接写入0即可。(4)IIR(中断标识寄存器)

这个寄存器是只读的。通过读取特定的位,可以了解串口中断发生后,确切的事件来源。这包括两个方面,一是共享IRQ的串口中,是哪一个串口提交的中断;二是提交中断的串口属于4种中断中的哪一种。

除此之外,在16550及更高版本的串口适配器中,一些与传输队列相关的状态也位于这个寄存器,而在8250中,相应的位只能够读出0。

当IRQ中断发生后,考虑到多个设备可能共享同一个IRQ,因此,需要逐一检查到底哪一个设备产生了中断。对于串口适配器来说,需要检查的是IIR的bit0是否为0。只有当这一位是0时,才意味着中断由这个串口适配器产生。

中断来源设备确认为串口适配器后,需要辨别是产生了哪种中断。因此,需要读取IIR的1~3位。在表2-5中可以找到4种中断对应的值。

注意,对于8250来说,bit3总读出0。

所谓“清位事件”,指的是使用何种操作才能让串口适配器相信程序已经收到了中断并进行了处理。例如,接收寄存器满这个中断,只有在读取了接受寄存器之后,才被“清位”,此时,bit0会恢复到无中断的1值。记住,IIR是只读的,所有对IIR的“写”操作都是由适配器本身而不是总线接口完成。“队列超时”中断指数据在接收队列里存放了太长时间而没有得到读取。处理器没有及时取走数据,这是一个不常见的原因。通常,一次传输中的最后一端数据不足以填满接收队列,这时候就要求有一个超时中断提醒处理器,队列中的数据尽管不满,但是也应该取走。(5)FCR(队列传输控制器)

只有16550及以后的版本才有这个寄存器。在8250中,向+2偏移的地址写数据不会产生任何效果。

可以通过IIR的5~7位获取关于传输队列的信息,见表2-5。注意,如果在8250中读取这些位,总是读出0。

FCR寄存器用于对传输队列的行为进行控制,也可以关闭或者开启传输队列。

FCR寄存器位说明见表2-6。

FCR的第0位是传输队列允许,置位有效。当传输队列被开启时,可以通过设置FCR的其他位进行队列传输,当此位清位时,传输队列被关闭,这种模式也叫做“8250兼容模式”。关闭正在执行数据的传输队列将导致队列中的数据丢失,因此,应该确认队列不工作的前提下对队列实施控制。

通过对第1位和第2位的置位,可以清空接收队列和发送队列。同样,如果队列在执行传输,这个操作会使数据丢失。当清空操作完成后,串口适配器会自动将这一位清位。由于这个寄存器只写,所以硬件清位似乎和软件没有什么关系。

第3位是DMA模式允许。所谓DMA(Direct Memory Access,直接存储器访问),是指设备缓存不通过CPU的控制直接与存储器进行数据交换。如果这一位置位,则串口适配器芯片与DMA相关的引脚RXRDY和TXRDY(不是RS-232接口的TXD和RXD)信号线将输出特定的波形,以便于DMA控制器使用。然而,在IBM PC/AT架构中,这个功能被忽略。

第6~7位是队列中断极值(Trigger Threshold Value, TTV),这个值的含义是当队列已经接收多少数据之后就发起中断。这个功能使得硬件的适用性增强。例如,欲进行频繁的大量数据的传送(数据采集等),这样就可以将TTV设置得比较高,以节约处理器资源;如果数据的到来是突发的、少量的(例如数据敏感、报警等),就应该将TTV设置得低一些,或者不使用传输队列,以便于处理器及时得到反馈。TTV的最大值比传输队列的容量要低,这是因为从数据达到TTV到中断产生是需要一定时间的,因此,留出一部分余量保证数据不会丢失。如果TTV设置为1,那么看起来和没有传输队列8250有些相似,但是由于读取队列的时间窗口比较宽(有可能在中断发生到软件响应的这段时间里,有新的数据冲进了传输队列),因此,提高了数据传送的可靠性。

第5位是16750的队列扩展。在比16750版本低的串口适配器中,这一位保留并且必须设置为0。在16750中,此位清位意味着16750使用与16550大小相同的传输队列。如果16750的这一位置位,则它的大容量传输队列被启用,同时,TTV的代表方式会发生变化。所以,当开启16750的大容量传输队列之前,应该停止传输并重新设置TTV。

注意,这个寄存器是只写的,因此,只能进行设置而不能看到设置的结果(第5位“除外”,因为IIR上相应的位恰好具有相同的含义)。(6)LCR(线控制寄存器)

串口适配器当中的“线控制”是相对于并行数据控制而言的,逻辑上属于链路层控制。我们知道,在起止式异步传输协议中,经过串行化的数据还要添加校验和帧头才能在串行线上传输。将数据组织成符合起止式异步传输协议的一帧,需要在LCR中完成相关的设置。这个寄存器占用一个I/O地址且不和其他寄存器共享地址。

LCR寄存器位说明见表2-7。

0~1位用于设置字长,一般设置为8位。一些军用加密器可能需要5位的数据,而一些老式的ASCII打字机的数据位只有7位。假设您需要与类似的设备进行通信,则务必明确地设置好数据位。

在第2位的“1.5位停止位”,只有当数据位是5位的时候,才会使用1.5位的停止位,多余1位的停止位用于解决某些时序偏差所导致的问题。因此,如果传输设备的时钟不稳定,则可以考虑使用多位停止位。

第3~5位用于设置奇偶校验。不仅可以设置有无校验、奇偶校验,而且校验位可以保持某个恒定的值。这在对自行设计的传输设备进行调试的时候特别有用处。

当第6位置位时,将强制输出“Space”。

关于“Mark”和“Space”的含义,请回顾1.4.2节的内容。(7)LSR(线状态寄存器)

当LCR设置完毕后,串口适配器即根据协议的设置,以一定的格式发送数据。同样,当接收数据后,串口适配器便根据LCR的设置理解数据的格式。当接收到的格式与理解中的格式发生偏差时,就会发生字错误,错误的信息即存储于LSR寄存器中供软件读取。这个寄存器中所有位的数据均由硬件产生,软件只可读,因此,在下面的描述中我们使用“被置位”这样的字眼。

除此之外,关于传输寄存器空和满的信息,也放置于LSR寄存器中。

LSR寄存器位说明见表2-8。

第0位代表数据就绪。在8250中,如果接收到了完整的一帧,且还原数据的过程中没有发生错误,则此位被硬件置位。在16550及更高版本中,如果接收队列中的数据没有被取完,则这一位将一直保持置位。

第1~3位代表还原数据的过程中发生了错误。“溢出错误”:在8250中指的是RBR的数据还没有取走,新的数据就已经到来并尝试覆盖RBR中的数据;在16550及更高版本中,代表接收队列已经满,新的数据已经来到,并将试图使队列溢出。

解决溢出错误的最直接的方法是当数据到来时,尽快进行读RBR的操作,以便腾出足够的空间用于数据的接收,即便是带有传输队列的串口适配器,其队列的长度总是有限的。然而,“尽快读出数据”这个判断在软件很复杂的情况下是一个难题。

在单任务的操作系统或无操作系统的情况下,情况稍好处理。因为总线的速度要比串口传输的速度快得多,如果采用中断的方式处理数据,并且程序写得简洁,一般就能避免溢出。然而,如果软件每收到一个字节都要进行一番复杂的处理,这些处理一旦占用足够长的时间,就会发生第一个字节没有处理完,更多的字节就已经来到的情况,此时由于软件没有精力读下一个字节,于是就会发生溢出。出现这种情况就应该对软件的算法进行优化,或者在内存中开辟更大的缓冲区并优先接收数据。

但是,如果是多任务的操作系统,那么读取串口数据的线程如果处于较低的优先级,或者经常被搁置,其造成的结果是这个线程总是不能很痛快地工作。这样,如果读串口线程实际工作达到的数据吞吐率比串口本身传送的吞吐率低,就会造成溢出错误,后果就是数据的丢失。而在多任务操作系统中,线程的调度具有一定的不可预测性,这就导致问题的解决更加困难。

如果无论如何调度都不能解决溢出的问题,那么只能以降低波特率为代价换取更长的容忍时间,不过,无论是单任务还是多任务,数据的处理时间常常是动态的。如果根据木桶短板效应,将最慢的可能性设置为波特率,效率就有所下降。如何动态地进行吞吐率匹配,这恰恰是高层次协议所要关心的问题。“校验错误”:指的是帧提供的校验值不符合本地串口适配器所计算的校验值。例如,如果配置串口适配器为8位数据、1位奇校验、1位停止,则串转并的过程中,串口适配器会将起始位之后的8位看做有效负载,并把接下来的1位看做校验位,并与通过前8位计算好的校验值相比较,如果不相同,则会发生校验错误。如果配置成不进行校验,则校验错误不会发生。“帧错误”:指的是所期望的最后一位不是停止位。

发生校验错误和帧错误的原因多种多样。首先可能的原因是电路原因,例如,电缆连接不牢靠,导致电平紊乱无法进行采样。如果电路经过检查是没有问题的,那么最可能的原因是收发协议不匹配,包括波特率的不匹配以及帧格式的不匹配。这会导致数据的错位,从而产生帧错误或校验错误,如图2-8所示。图2-8 波特率不匹配引起校验错误和帧错误

第4位被置位,指的是串口接收器收到了超过一个帧长度的0信号。在正常情况下,在不传输数据时应该发送停止位信号1。

第5位如果被置位,则表示收到的数据已经被取走。在16550及更高版本中,指接收队列为空。

第6位如果被置位,表示欲发送的数据已经全部发送完成。在16550及更高版本中,指发送队列为空。

第7位如果被置位,说明接收队列中的某个数据存在校验错误或帧错误。如果软件不想追究具体是什么错误,可以不去再访问第1位和第2位,而是丢弃接收队列中的所有数据,因为不能够指明是哪个数据存在错误。需要注意,从接收器产生错误到LSR被置位到软件得知错误,是有一定的时间间隔的,在这个时间里,接收队列会继续填充新的数据。在不使用传输队列的情况下,这一位保留。(8)MCR(调制解调器控制寄存器)

MCR寄存器用于控制EIA RS-232标准中关于调制解调器的部分。这个功能已经很少使用了。

MCR寄存器位说明见表2-9。

这个寄存器可读写,占用一个I/O地址且不和其他寄存器共享地址。

第0位和第1位的数据会直接送达串口适配器芯片的RS-232电平转换器接口引脚。

第2位和第3位也与串口适配器芯片的两个引脚相连,在芯片描述中,这两个引脚的名称一般是OUT1和OUT2。我们知道串口适配器都与简化的9口接口相连,提供这两个引脚可能是为了连接到标准25口接口的某些信号。在其他一些兼容芯片中,这两个信号另有用途。例如,在LPC多功能芯片IT8705中,OUT2定义为允许串口中断,OUT1则不可使用。

第4位如果被置位,相当于进入了“虚拟回环模式”,这相当于在串口上连接了一个处于回环状态的调制解调器。发送的数据立即会被接收,而且CTS和DSR(见MSR寄存器)也会有相应的反应。

第5位是16750所独有的功能,该芯片内建了根据传输队列使用情况而自动设置的硬件流控制器。然而,16750是一款“高端芯片”,绝大多数计算机并不使用这款芯片,也不提供与之兼容的功能。(9)MSR(调制解调器状态寄存器)

这个寄存器只读,占用一个I/O地址且不和其他寄存器共享地址。

MSR寄存器位说明见表2-10。

表2-10中的CD、RI、DSR、CTS都是EIA RS-232标准中的调制解调器状态信号。4~7位的这些信号直接来自于芯片的对应引脚。

串口适配器还提供了信号改变通知。例如,某一次读出该寄存器时,CD位为1,如果过一段时间后CD变为0,则串口适配器不仅要将第7位置位,而且还要把第3位置位。但如果软件并不读取MSR,而一段时间后CD回到0,则串口适配器不仅要将第7位清位,而且还要把第3位清位,意思是自从上次读取之后,这一位没有发生改变。第2位比较特殊,只是在RI由1变为0时,串口适配器才将此位置位。

不使用0~3位一样能写出稳定的串口驱动程序,之所以保留这个功能,可能是因为历史上存在有这种需求的软件。(10)SR(涂鸦寄存器)

这个寄存器独占一个I/O地址,可以读出所写入的数据。向这个寄存器写数据不会影响到串口适配器的工作,读它的内容也不会得到串口适配器的任何信息。

由于串口适配器只有7个寄存器地址,所以设置了这个寄存器,使得串口适配器达到的地址空间为8位,这可以为片选解码器提供一定的方便。

4.识别串口适配器

从上面的讨论中我们得知,不同的串口适配器,例如8250、16550、16750,虽然保持着向下兼容,但是其功能随着版本的升高有一定的扩展。实际上,使用串口适配器的方法总可以分成两大类,一种方法是不去管到底安装了何种型号的芯片,只需要把它们都当做8250来用。如果只是面向简单的应用,这比较适用。

另外一类就是向最高处看齐。即把所有的芯片都当做16750来用。至于16750特有的功能,可以通过软件模拟的方法来实现(例如传输队列、大的传输队列以及自动硬件流控制)。在使用这个驱动程序的人看来,仿佛电脑里装的就是16750。由于很少有人能用到只是16750具有而16550没有的功能,因此,也可把16550作为最高端。

后一种思路由于提供了较多的功能,又最大限度地发挥了硬件的性能,因此,逐渐成为主流的设计理念。著名的Microsoft DirectX就是按照这种理念设计的。

实现多类型适配器的适用性就要进行硬件识别。那么,如何知道自己的电脑上安装了何种串口适配器,或者LPC多功能芯片实现了哪类串口适配器呢?只需要对FCR进行设置,再读取IIR中的传输队列信息就可以了。以一段代码为例:

在当代计算机体系中,所使用的串口适配器是与16550兼容的。

5.16550 串口适配器编程

我们将直接读写寄存器实现串口数据传输的方式称为串口适配器编程。

严格地讲,进行串口适配器编程应该在无操作系统的情况下进行。串口发送和接收程序是运行在计算机上唯一的线程。但是,不使用x86实验箱,而在实际的计算机上进行这种实验是比较困难的,这是因为当代计算机的结构非常复杂。当然,确实可以借助一些实验性质的操作系统完成独占的串口适配器编程,不过由于涉及到较多的体系结构方面的知识,其操作比较复杂。

实际上,对于串口适配器而言,由于大部分操作系统并不将其作为一个必需设备(即维持计算机运转的关键设备)看待,因此,在不受到干扰的情况下进行串口编程是有可能的。有一些特殊的计算机占用一个串口作为终端,但这样的计算机通常会提供不止一个串口供使用。

在DOS、Windows 9x中,可以直接访问串口适配器的寄存器。因此,实现简单的数据收发并不困难。然而,由于中断资源被操作系统所把持,因此,编写一个使用IRQ的串口通信程序就需要特殊的技巧。随着这两类操作系统退出历史舞台,一些在当时非常精彩的技巧被逐渐遗忘。

不管怎样,综合使用这些寄存器进行串口编程,其程序流程是值得关注的,尽管直接实现这个流程不太容易。

假如我们要使用串口适配器分别编写一段数据发送程序和数据接收程序。以数据发送程序为例,进行数据传送之前首先是准备好数据。数据一般放在一块连续的内存中,用一个头指针标明地址。然后准备设备。IBM PC/AT构架中所规定的串口地址是选择一个设备。

设备选择好之后,需要确认这个设备是否被占用。由于我们要发送数据,因此,需要读取LSR.6(LSR寄存器的第6位),看它是否为置位。如果已经被置位,则说明传送队列已经空闲。接着确认LSR.5接收队列是否也空闲。

如果这个串口仍然在使用怎么办?这就涉及到一个策略的问题。

由于8250占用两个寄存器,因此,除了片选信号外,还有寄存器选择信号。

提示:

8250的控制/状态寄存器和数据寄存器分别对应了8250内部的控制逻辑和数据缓存。控制/状态寄存器的存储信息构成了对8250的控制信号,8250的数据缓存就是数据寄存器。

IBM PC/AT构架定义了8250的两个寄存器地址,并由地址解码器产生片选信号。当片选信号有效的时候,8250才能够认为数据总线上的数据属于自己,或者允许将自己的数据输出到总线上。由于IBM PC/AT构架采纳了4个串口,这4个串口的有效地址范围分布于地址空间总线上。2.2 8251A接口电路

可编程串行接口芯片8251A支持异步起止式和同步面向字符的数据格式。这款芯片及其兼容电路具有接口简单、编程容易等特点,适合于单片机等小型应用场合。当然,8251A的功能没有8250系列多。尽管如此,8251A仍然可以适用于通用CPU接口。2.2.1 8251A的外部特性

8251A外部引脚共有28个,如表2-11所示。

8251A的CPU接口可以直接接入CPU的总线接口,由CPU内部生成相应的时序,而不必用额外的总线匹配电路。中断输出可以悬空,也可以通过IRQ控制器接入CPU, RS-232匹配的接口直接连接传输线路即可。

如果使用8051作为处理器,情况稍有不同。由于没有地址锁存接口,需要通过编程的方法生成原始时序,这比使用8051的外部扩展存储器接口要简单。8051可以产生波特率时钟信号,接入8251A的TxC和RxC。8051的中断接口较少,可选择TxRDY、RxRDY中必要的信号接入中断端口。2.2.2 8251A的内部结构和编程模型

可编程串行接口芯片8251A内部结构示意图如图2-9所示。图2-9 8251A内部结构示意图

8251A向CPU引出了4个寄存器,分别为发送寄存器(TxR)、接收寄存器(RxR)、控制寄存器(CR)和状态寄存器(SR)。TxR和RxR称为“数据接口”,当CD引脚置为低电平时,读写操作会映射到数据接口上;CR和SR称为“控制/状态接口”,当CD引脚置为高电平时,读写操作会映射到控制/状态接口上。CD可以看做某种“地址信号”。事实上,在很多实验中,CD就是通过地址解码产生的。

向控制寄存器中写入数据即实现对8251A的控制。数据的格式有一定的含义。根据表达功能的不同,分为方式命令字和工作命令字。

方式命令字的各位含义见表2-12和表2-13。

8251A的工作命令字用于进行某种操作或处于某种工作状态。

8251A的工作命令字如表2-14所示。2.2.3 8251A的状态字

8251A的状态字用于判断能否接收或发送;接收过程中是否有错误,如表2-15所示。2.2.4 8251A的方式命令和工作命令的使用

在使用8251A收发数据之前需要进行初始化。

8251A工作于同步方式时,写入方式字后,还要将同步字符写入到控制寄存器中。如图2-10所示为8251A工作于同步方式时的初始化流程。

8251A工作于异步方式时,初始化流程比较简单,先写入方式命令字选定为异步方式,并同时设置波特率、校验位、停止位等。然后设置工作命令字。

初始化之后就可以进行数据传输。由于串行传输速度较慢,因此,无论是发送还是接收,推荐采用中断方式进行处理。当然也可以使用查询方式。

发送数据时,首先向数据寄存器写入数据,然后等待中断或者查询状态寄存器的最低位(第0位)是否有效,以确定数据是否发送完毕;当接收数据时,首先等待中断或者查询状态寄存器的次低位(第1位)是否有效,以确定是否已经接收到数据,然后读取数据寄存器获得数据。2.2.5 8251A应用举例

假设某x86计算机系统将8251A接到总线上,并将数据接口映射到0x308上,控制/状态接口映射到0x309上。不使用中断接口。这样的计算机系统有两台,其串口采用三线制交叉连接,即TxD0→RxD1,RxD0←TxD1,公共地线。

我们采用异步通信方式,事先约定协议为8位数据位、2位停止位、奇校验、波特因子64。发送方数据存储于首地址为pSRC的内存空间中,接收方将数据存储于首地址为pDST的内存空间中。图2-10 8251A工作于同步方式时初始化流程

一旦协议定制完毕,双方的方式命令字也就确定了。

2位停止位,方式命令字最高2位为“11”,奇校验,5、4位为01,8位数据,3、2位为11,波特因子64,最低两位为“11”,所以方式命令字为0xDF。

发送端的程序如下:

接收端的程序如下:2.3 本章小结

本章介绍了串口接口电路的实现,分别讲解了8250兼容接口电路与8251A接口电路的基本知识,包括这些兼容接口电路的结构和编程方法,并给出了接口电路的编程实例。第3章 在Windows NT中搭建开发环境

本章介绍平台和开发工具。首先说明进行串口程序开发所需要的硬件和软件环境,然后结合第一个串口程序Hello World介绍Visual C++(简称VC++)的使用。我们把重点放在开发流程上,因为有一个良好的流程是非常重要的。3.1 准备工作

串口通信程序的开发涉及到软硬件之间的接口,因此,除了开发环境之外,还必须具有一定的设备条件。作为功能上和性能上的考虑,软件开发阶段的设备环境应当与软件应用阶段的设备环境尽可能保持一致。但是从实现基本功能和对代码进行正确性的角度看,在不具备应用阶段设备环境的情况下,使用一些方便易行的替代方案也是有一定好处的。本节推荐一些切实可行、经济实用的方案,供读者参考。3.1.1 使用串口调试助手

串口调试助手是一款常用的串口通信软件。我们使用它的目的主要有两点,第一点是在硬件环境的搭建阶段,使用它判断通信是否通畅;第二点就是在程序开发过程中,对数据的发送和接受进行验证。该软件的界面如图3-1所示。图3-1 串口调试助手

进行硬件测试时,需要在发送端和接收端分别运行串口调试助手,并进行匹配的设置。在发送端的数据发送区输入若干字符,并单击【手动发送】按钮,观察接收端的数据接受区是否正确地接受到了所发送的字符。3.1.2 双端口互联方案

如果拥有两台带有串口的计算机,或者计算机带有两个串口,就可以使用双母头交叉线连接两台计算机,或者在一台计算机上使用两个串口进行调试。

双母头交叉线的两端均为DE-9母头端子,任意一端的2号插孔与另一端的3号插孔具有电气连接。在选购时可以使用万用表判断是否为交叉线。

交叉连接时,任意一台计算机或任意一个端口均可作为发送方和接收方。3.1.3 单端口自联方案

如果计算机上只允许使用一个串口,则可以通过一段带金属头的导线,通过连接2端和3端实现自发自收。由于DE-9串口公头插针的直径比标准跳线针的直径大,所以直接连接可能存在接触不良。可以找一个废旧串口鼠标,将它的连接线剪断,使用万用表判断导线的连接关系,将2端和3端对应的两根导线接在一起,最后将这个插口插到串口插座上即可。

使用这种方法调试程序可能会出现问题。因为在后面章节我们可以知道,串口是不能够共享的,采用非自发自收的方式,则可以将发送模块和接收模块分别调试。但如果使用自发自收的方式,由于串口被程序所独享,所以只有当发送和接收部分同时设计完毕后才能够对其功能进行验证,而一旦验证失败,失败原因来自于发送模块还是接收模块,就很难得知。因此,尽可能不要使用这种方法。3.1.4 使用USB-UART转换器

现在大多数笔记本电脑,甚至一些台式机的主板都不再提供9针串口。针对这种情况,可以使用USB-UART转换器,如图3-2所示。图3-2 USB-UART转换器

这类设备提供驱动程序,用户只需按照所附说明进行软硬件的安装即可。安装完成后,可在设备管理器中查看所对应的端口号。

提示:

启动设备管理器的方法是:单击【开始】→【运行】菜单,在“运行”框中输入devmgmt.msc,单击【确定】按钮。

图3-3展示了一台安装了USB-UART转换器的计算机的设备管理器所显示的情况。高亮的设备即为转换器,它的串口编号是COM4。图3-3 在设备管理器当中的USB-UART转换器

值得注意的是,市售USB-UART转换器的核心通常是PL2303等专用转换器集成电路。由于缓存容量等限制,这些集成电路往往不能满足诸如传送率、丢帧率、稳定性等要求。因此,如果在最终的产品解决方案中使用这类设备,请仔细查看芯片的说明手册,考察其是否达到相关的技术要求。3.1.5 使用虚拟串口

如果除一台计算机之外,不具备任何额外的硬件条件,则可以考虑使用软件虚拟串口进行程序的调试。

本书推荐使用VSPM(虚拟串口),它利用IP包实现串口数据的转发。下面简要说明它的使用方法。

我们的目的是在一台计算机上创建两个串口,达到双端口互联方案的效果。

在第一次运行VSPM时,要求选择工作方式。这里选择“UDP广播模式”,如图3-4所示。图3-4 选择工作模式

下一步是选择建立虚拟串口的方式,这里选择第二项,即“建立默认的虚拟串口”,如图3-5所示。图3-5 建立虚拟串口的方式

确定后,出现了程序的主界面,如图3-6所示。图3-6 主界面

下面建立虚拟串口。选择【虚拟串口管理】→【新增虚拟串口】菜单,如图3-7所示。弹出“虚拟串口信息”对话框。图3-7 选择“新增虚拟串口”菜单项

按照图3-8的方法安装第一个串口COM1。如果COM1是物理端口,您就不会在端口列表中发现它,此时需要指定一个有效的端口名。

如果您的计算机已经连接到网络上,UDP接收地址列表可能不包含127.0.0.1,此时您只需要从列表中选择任意一个属于网络适配器的IP地址即可,并在图3-9的设置中使用同样的IP。但若您获得IP的方式是动态的,则可能会带来一些不便。本书的建议是,如果您使用UDP的方式,则请不要在具有网络连接的情况下调试程序。否则,请使用TCP/IP的直连模式,具体操作方法请阅读VSPM自带的说明书。图3-8 设置COM1

再添加一个串口,具体内容如图3-9所示。图3-9 设置COM2

仔细观察COM1和COM2的设置不难发现,两个虚拟串口的不同之处在于发送端口和接收端口是相互对调的,这和交叉线的结构有异曲同工之妙。3.2 使用Microsoft Visual C++

本书把精力集中于程序的设计流程方面,而尽可能地避开与开发工具联系过于紧密的内容。这一方面是因为Visual C++版本众多,无法照顾到所有的情况;另一方面,鉴于越来越多的开发人员开始在Windows平台上选择非Visual C++类的开发工具。因此,希望本书能够对这些读者有最大程度的帮助。3.2.1 开发平台的选择

本书中的所有程序均需要在Windows NT中编译和运行。Windows NT家族包括以下版本的Windows:

●Windows NT 4.0。

●Windows 2000,Windows XP, Windows Server 2003。

●Windows Vista, Windows Server 2008。

建议您在阅读本书的同时,使用下列版本的操作系统进行软件的开发:

●Windows 2000 with Services Pack 4。

●Windows XP with Services Pack 3。

●Windows Server 2003 with Services Pack 2。

对于其他版本的Windows NT操作系统,我们会在必要的时候加以说明。这一点,特别是使用Windows Vista的读者尤其注意。

由于涉及到与硬件相关的操作,所以如果您正在使用非Windows NT家族的操作系统,例如Windows 95、Windows 98以及Windows Me,则可能无法完成本书中的示例项目,或者在某些特定的情况发生错误。

Visual C++是Visual Studio开发套件的一个组件。最新版本是2008年3月推出的Visual Studio.NET 2008。目前主流的版本有:

●Visual C++6/Visual Studio 6。

●Visual C++7/Visual Studio.NET 2003。

●Visual C++8/Visual Studio.NET 2005。

此外,还有开发者使用Visual C++5,或者较为少见的Visual Studio.NET 2002。考虑到国内大部分开发者的实际情况,本书凡涉及到具体开发环境的使用,均同时以Visual C++6和Visual C++7作为标准,这是由于这两种开发环境存在较大的差异。而对于使用Visual C++8的读者,可以以Visual C++7的操作作为参考。

在Windows Vista和Windows Server 2008中安装上述版本的Visual Studio,均会收到操作系统关于兼容性方面的警告。Microsoft对其中某些版本的Visual Studio提供了升级服务包,请关注Microsoft官方网站以获取相关信息。3.2.2 工程类型和开发流程

使用Visual C++进行程序设计时,首先必须建立工程(Project)。工程中包含了为编译器和链接器准备的必要的设置。工程的设置非常复杂,其核心是要描述哪些文件需要被编译,如何被编译等必要的信息。Visual C++提供了图形化的可视界面供用户进行设置。

每个工程最终生成一个二进制的可执行体,即可执行的文件(.exe)或者链接库(.lib,.dll,.ocx等)。一个软件可能包含不止一个可执行体。Visual C++为软件设置了更大的容器,这个容器可以包含多个工程以及其他的文件。在Visual C++6中,这个容器叫做“工作间”(WorkShop),在更高版本的Visual Studio中被称为“解决方案”(Solution)。

Visual C++内置了设置好的一些工程类型,并以框架模板的形式提供给用户。进行串口通信开发主要需要以下3种工程类型:

●Win32控制台/Win32 Console。

●Win32动态链接库/Win32 DLL。

●MFC应用程序/MFC Application(.exe)。

在整个开发流程中的不同阶段需要使用不同的工程类型。它们的关系如图3-10所示。图3-10 开发流程和工程类型

在串口通信应用程序开发中,我们大体上遵循以下流程:

1)确定算法和方案,制定接口函数的规范。

2)使用Win32 Console建立工程,添加代码文件,并进行功能调试和性能测试。

3)使用MFC Application作为模板建立工程,设计图形界面,进行调试。

4)将串口通信函数包装,以便于应用于其他项目。

工程配置、代码书写、编译链接以及调试等,这些工作都能够在Visual C++中完成,所以Visual C++又称为“集成开发环境”。3.2.3 Hello World——第一个串口通信程序

本小节将介绍第一个串口通信程序。这个程序只有输出数据的功能,它通过串口向输出端输出语句“Hello, World”。

首先应当保证串口通信的物理通道是畅通的(注意,请不要使用自发自收的方式)。然后在输出端开启串口通信调试助手作为显示工具。假设输出端的端口名是COM2,输入端使用COM1。

然后建立工程。无论是Visual C++6还是Visual Studio 2003,在建立工程的同时就能够建立相应的工作间或者解决方案。

1)如果使用Visual C++6,请这样做:

选择【File】→【New】,弹出“New”对话框。选择“Win32 Console Application”,再给工程起一个响亮的名字,如图3-11所示。图3-11 新建工程

在如图3-12所示的设置中,选择“A simple application”,这样,Visual C++会为添加一些源代码文件。单击【Finish】按钮完成设置,在下一个对话框中直接单击【OK】按钮。图3-12 第一步的设置

我们得到了一个由3个源文件组成的工程,如图3-13所示,StdAfx.h和StdAfx.cpp称为预编译组件,Visual C++认为通过这样做可以加快编译速度。如果我们的工程需要引用一些不需改动的头文件,就把它放到StdAfx.h中。

在图3-13左侧的工程浏览器中选择“File View”,双击HelloWorldVC6.cpp。图3-13 文件视图

Visual C++已经为我们写好了以下一些代码:

2)如果使用Visual Studio 2003,请这样做:

选择【文件】→【新建】,弹出“新建”对话框,选择Win32控制台项目。建议最好取消“创建解决方案的目录”前面的选择框,因为解决方案里只有一个工程,如图3-14所示。图3-14 新建项目

单击【确定】按钮后,Visual C++询问相关的设置,如图3-15所示,请直接单击【完成】按钮即可。

在图3-16左侧的“解决方案资源管理器”对话框中双击HelloWorldVC7.cpp,查看Visual C++创建的代码。图3-15 项目设置图3-16 解决方案资源管理器

提示:

考虑到Unicode的兼容性问题,Visual C++7及更高的版本建议使用“T”标号,即包含tchar.h,并使用-tmain、TCHAR等标记代替main和char。在通信传输中采用Unicode会带来编解码的复杂性,因此,本书中除非特别考虑,均不使用Unicode。

下面的事情对于不同的开发环境是完全一样的。所以完全有理由合并不同版本的工程。在网上“下载专区”的源代码中,您会发现这个例子以这样一个文件结构出现:

您可以根据Visual C++的版本,打开相应的工程。无论您使用何种版本的Visual C++,工程所包含的源代码文件是相同的。

作为第一个程序,我们打算采用最简单的方式展示串口通信程序设计中最基本的技术要点。

在本书的前面讲到串口是计算机系统的I/O设备,所以需要使用I/O库的基本头文件(stdio.h)。在StdAfx.h中合适的位置包含这个头文件,还有以下必要的头文件:

为了显示Hello World,首先需要定义这个字符串:

接下来就要打开串口“COM1”:

当打开成功后,就把字符串写到这个文件中。

一定不要忘记关闭文件指针,尽管操作系统会自动关闭文件指针,但是为了照顾到程序代码的可读性以及程序的稳定性,建议您养成使用诸如fclose、CloseHandle等收尾处理函数的好习惯。

程序编写完成后,需要编译这个工程。编译的选项在Build(编译)菜单中。

现在打开接收端的串口调试助手,在发送端运行刚刚编译的程序,就会在接收端的数据接收区域中看到“Hello World!”。

为了试验这个通信示例的可行性,本书的作者尝试了很多种串口连接方案,结果运行得都非常好。尽管如此,还是相信有一些读者的程序不能正常地工作,即使物理连接是畅通的,而且COM1没有被其他设备(比如有一个串口鼠标)或程序(比如忘记关掉占用COM1的串口调试助手)占用。

试一试

请把fopen语句参数中的“COM1”换成“CON”,编译并运行,观察发生的现象。

如果您使用的是低于Windows Vista版本的操作系统,请尝试建立一个名为COM1的文件,观察操作系统的反馈并思考为什么。

如果您成功地运行了Hello World,请尝试使用fread进行串口数据的接收。3.3 本章小结

本章介绍了在Windows NT环境下串口通信开发与调试环境的搭建。最后通过一个简单的Hello World程序说明串口通信软件开发的一般流程。在以后的几章里,软件开发都在这个环境下进行,软件开发的流程也不超过本章介绍的范畴。第4章 使用Windows API串口编程

Windows以SDK串口通信函数的方式提供了应用程序对设备的操作接口。Windows API是串口通信开发的基本。本章将结合示例,详细介绍与串口通信相关的Windows API的使用方法。4.1 Windows API串口编程概述

Windows API在串口通信程序设计中所处的位置如图4-1所示。C/C++运行库、MSCOMM控件和CSerial类均调用Windows API。不仅串口通信程序如此,几乎所有的Windows应用程序的开发都不同程度地依赖于Windows API。图4-1 串口通信Windows API

本书所涉及到的Windows API主要有3类:

●与串口通信相关的Windows API。

●涉及I/O相关的Windows API。

●图形界面相关的Windows API。

需要说明的是,在本书中,我们不使用Windows API直接进行图形界面的开发,而使用其封装形式,即MFC的UI框架。后面章节还将介绍Qt库,它在Windows上的实现本质上也是对Windows API的封装。

本书把图形界面的开发放到第5章介绍,本章主要介绍前两种API的使用。

本节首先介绍一些串口通信程序开发的历史。4.1.1 不使用Windows API

在DOS时代,人们使用I/O寄存器读写的方式直接控制硬件。

我们知道,隐藏在串口接头后面的控制器是8250,它和中断控制器8259都是标准IBM PC/AT兼容机上的总线设备。在386时代,这两个器件是位于主板上的两块独立的集成电路。现在,它们和其他大部分总线输入/输出控制器一起被集成到南桥中。但是,其I/O地址和中断资源的分配保持向下兼容。

MS-DOS和基于MS-DOS的Windows,即16位的Windows 3.x以及16位—32位混合的Windows 95/98/Me提供了应用程序直接访问硬件资源的能力。换句话说,只要掌握了8250和8259的原理,就能够使用汇编语言的IN/OUT指令(或者C标准库中的inp和outp函数),通过设置和查询寄存器以及中断资源的方法编写串口通信程序。

在Windows NT中,32位的应用程序工作于Ring 3,这个级别拥有最低的权限。Windows NT中,32位的应用程序试图读写I/O地址空间时会引发一般保护性错误。因此,不能够直接使用IN/OUT指令对串口控制器进行控制。但是,在Windows NT中,16位的程序却可以“畅通无阻”地使用低层次I/O指令访问串口并进行读写。这是因为Windows NT内建了对虚拟8086模式的支持,这个子系统包括执行体NTVDM,核心驱动ntdos*.sys、ntio*.sys和Windows内核中相关的部分。当一个16位的程序被执行时,NTVDM进程被创建。当NTVDM截获到该16位程序发出的I/O指令后,会进行一系列的译码和权限限制处理,最终,这个操作被“安全”地提交给相应的I/O模块,或返回需要的数据,或返回错误。

NTVDM的存在是Windows NT向下兼容性的一种体现。之所以要保留16位程序直接操作I/O的功能可行性,是为了兼容一些DOS时代程序及设备的组合。我们可以设想这样一个场景:一款DOS游戏需要某种游戏杆(串口的或者并口)的支持,但是游戏杆的厂家可能没有能力提供Windows NT下的驱动程序,游戏的出品商也不再提供Windows版本的游戏。然而,将计算机升级到Windows NT的用户可能依然非常想玩这款游戏。所以,为了照顾到这部分用户的需求,Windows NT提供了NTVDM,使得在不对游戏程序和游戏杆进行任何改动的前提下,用户在新系统下仍然可以玩老游戏。

NTVDM也具有以下几点不足:

首先,NTVDM是虚拟机,它仅仅能够对功能要求进行有限的保证。在NTVDM上运行的16位程序,其性能大打折扣。对于较大量数据的传输而言,这是不能容忍的。

其次,NTVDM中所运行的16位程序与同系统中的32位应用程序的通信非常困难。Microsoft没有提供此方面完整的文档。因此,除非在极端的情况下使用它,否则选择这种方式进行串口通信开发是不明智的。

最后,Windows将逐渐放弃对16位应用程序的支持。16位程序移植性较差,在新环境下要花费大量的精力进行修改。而串口通信所使用的Windows API是文档化的,Microsoft的技术路线一向对使用文档化Windows API的应用程序提供兼容性的保证。

例如,前面介绍的“Hello World”程序虽然使用了C标准库,但实际上,这个程序在MS-DOS、非NT的Windows以及非Windows操作系统(比如UNIX/Linux)中支持标准库的编译器上均可以编译通过,并且在这些操作系统上都能够正常运行(在UNIX/Linux中不使用“COM1”,而是其他的名称)。这个程序是借助标准库“移植”而来的。

注意,在Windows中,标准库是基于Windows API实现的,尤其是涉及内存读写和I/O读写的部分。例如,fopen函数最终会调用CreateFile进行处理。然而,Windows中标准库的I/O部分存在很多不完善的地方。尽管Windows提供了文件设备操作符,但是除了磁盘文件的读写以及控制台的输入/输出,在Windows中并不使用标准库操作设备。在串口程序中体现得很明显:我们无法在程序中利用Windows API设置波特率等串口属性,因为设置这些属性的函数,标准库不直接提供;而若使用Windows API,又必须提供“文件句柄”。与其把fopen函数获取的“文件指针”转化为文件句柄,何不一开始就使用CreateFile呢?

因此,在“Hello World”中所使用的标准库开发方法掩盖了操作系统的实际功能,这对我们的学习是不利的。4.1.2 Windows API初探

Windows API是Windows操作系统的编程接口,它以C语言库的形式提供给开发者。这个库包含了必要的头文件和库文件,和一些小工具组合在一起,构成了Windows SDK。用户可以从Microsoft网站获取单独的Windows SDK。Visual C++包含了当时最稳定的SDK。

对于绝大多数系统核心的功能实现在于操作系统本身,而并不包含在Windows SDK中。在SDK中只以引导库的方式提供了这些功能函数的符号链接。例如,CreateFile函数的符号存在于kernel32.lib中,在链接的过程中应用程序包含CreateFile的符号,当应用程序运行时,通过符号的引导调用系统kernel32.dll中所对应的函数。

显式地(这里指不通过标准库等迂回的方式)使用绝大多数Windows API,尤其是核心Windows API,只需要包含头文件Windows.h。

提示:

原则上还需要在编译后链接基本的Windows SDK符号库,例如kernel32.lib等。但是Visual C++的工程模板已经为我们准备好了这些设置。

下面使用Windows API在控制台上显示Hello World。控制台也是一个设备,其设备名称是CON。使用Windows API的文件操作函数(注意,设备即是文件)打开它,向其中写入字符并关闭。

我们创建一个与第3章中“Hello World”程序相同的框架。首先修改StdaFx.h,包含SDK头文件:

在主函数中定义需要显示的字符串:

下面使用CreateFile。这个函数的参数众多,在下一节中会详细介绍。有一些参数的含义在程序中可以略知一二。

如果打开设备成功,则使用WriteFile写入字符串。

最后关闭文件句柄hFile。

编译并运行。

这个SDK版本的Hello World是一切通信程序的基础。在串口通信中,CON将被COM1、COM2等串口设备名所代替。

如果您在第3章已经成功地运行了标准库版的Hello World串口通信程序,那么在这个SDK版本中,只要把CreateFile的第一个参数改成“COM1”,就可以得到同样的结果。4.1.3 使用Windows API进行串口开发

在Windows中进行32位串口程序的开发和DOS环境下的开发过程有很大的不同。32位的应用程序不能够直接操作I/O寄存器,也不能随心所欲地设置中断。

使用Windows API,就意味着使用操作系统提供的功能接口。因此,有必要对操作系统的相关知识作了解。

1.硬件接口

Windows使用驱动程序实现对硬件资源的管理和控制。驱动程序和内核一起,工作于最高权限级Ring 0。驱动程序需要完成必需的操作系统——特定硬件接口层的实现,例如,电源管理、I/O资源的分配等,与此同时,驱动程序还暴露I/O接口供上层应用程序的调用。

作为一种通用设备,Windows提供了串口驱动程序,如图4-2所示。

因此,在了解串口通信物理过程的基础上,通过学习Windows提供的相关功能,就可以进行串口通信的开发。这里需要强调一点,在Windows环境下,一般使用串口控制器的异步模式,或者称使用通用异步串行控制器(UART)。

特别注意的是,串口属于一种通信资源(Communications Resources),这和早期的串口—调制解调器的构架有密切的关系。在Windows中,有一类专门的函数对通信资源进行控制和管理。设置串口属性(波特率、奇偶校验)的函数也包括于其中。这些功能是在内核中实现的。

2.I/O操作

作为通信资源的串口,从更低的层面上说,也是一种I/O资源。作为多任务的操作系统,Windows实现了I/O资源的调度,并提供了相关的Windows API。图4-2 串口驱动程序

为了能够同时实现读写,需要为读操作和写操作分别设置线程。同一进程内的线程共享该进程的资源。

下一节我们将探讨Windows的I/O机制。在串口通信中,我们使用异步I/O。注意,这里的“异步”指的是Windows I/O中的调度机制,也称为“交叠式”,这和串行通信协议中的“异步”有概念上的不同。

3.Windows下串口程序设计的一般流程

Windows为串口硬件结构提供了必要的抽象和准备工作。在Windows中,发送的数据必须先放置于发送缓冲区,串口接收的数据经过串并转换器处理后,也置于缓冲区中。这个过程由操作系统完成。操作系统提供一系列的通知机制,应用程序可以设置串口的属性,并响应来自操作系统的信息。对缓冲区的数据进行读写,要使用Windows I/O相关的API。

一般来讲,Windows下进行串口通信应遵循以下流程:

1)开启串口。

2)设置事件量(Event),供异步读写线程使用。

3)设置串口属性。

4)对串口进行读写。

5)关闭事件量,结束读写。

6)关闭串口。

后面两节将介绍Windows对设备的读写操作、与串口通信相关的概念和操作方法。4.2 同步和异步I/O——基本的读写问题

执行串口的读写操作时,要使用与一般文件读写基本相同的Windows API函数。无论是磁盘上的文件,还是串口和控制台,这些操作都是要和各类输入/输出设备打交道的,所以称为I/O(输入/输出)操作。

Windows的I/O操作分为两种类型:同步的(Synchronous)和异步的(Asynchronous)。

使用同步I/O,就意味着I/O操作必须完成,才能进行下一步的工作;而使用异步I/O,则I/O操作不必完成,即可执行操作之后的代码。I/O操作完成后,以某种机制通知该程序。正因为如此,同步I/O也称为不交叠I/O(Nonoverlapped),异步I/O也称为交叠I/O(Overlapped)。示意图如图4-3所示。图4-3 同步和异步I/O

从图4-3可以看出,同步I/O的程序流程比较简单,而异步I/O的流程比较复杂。在实际的串口通信中,我们主要使用异步I/O。

本节主要介绍几个核心的串口通信相关的I/O API函数和数据结构。在这些函数和数据结构所介绍顺序的安排上,我们采取了异步I/O的流程。在本节的最后将介绍一个关于同步和异步I/O的小例子。4.2.1 CreateFile函数——开启串口

CreateFile的调用约定是:(1)lpFileName:设备/文件名

串口的设备名是COM1、COM2……,标准IBM兼容机最多配备4个串口,但是在这里可以不受4个串口的限制。一般来说,不要使用超过256的值。(2)dwDesiredAccess:访问方式

一般设置为GENERIC-READ|GENERIC-WRITE。

您可能对这种设置方式比较陌生。Windows API常用的数据类型为DWORD,是32位的无符号长整数。GENERIC-READ和GENERIC-WRITE是winnt.h中定义的两个宏(0x80000000和0x40000000),“|”是按位或运算。GENERIC-READ|GENERIC-WRITE就是0xC0000000,表示同时进行两种设置,这与寄存器的“按位设置”非常类似。

如果在一个程序(假设为程序A)中使用GENERIC-READ打开某个串口,而在程序B中使用GENERIC-WRITE打开同一个串口,那么是不是就可以实现程序A接收该串口的数据,程序B通过该串口发送数据,并且两个程序互不干扰呢?答案是否定的。串口一旦被成功地打开,在它被关闭之前,是不能够再次打开的,无论先前和之后是采用何种方式进行打开的。(3)dwShareMode

后续操作的共享模式。可选值为FILE-SHARE-DELETE(允许再次打开并进行删除操作)、FILE-SHARE-READ(允许再次打开并进行读操作)、FILE-SHARE-WRITE(允许再次打开并进行写操作)和0。但是在串口通信中,这个值总设置为0,含义是在串口打开之后关闭之前,不允许再次打开串口。(4)lpSecurityAttributes

权限控制属性。一般设置为NULL,含义是CreateFile所返回的句柄不能够被子进程继承。(5)dwCreationDisposition

当欲打开的文件已经存在时,应当以何种模式打开。Windows SDK规定,在串口通信中的这个值必须设置为OPEN-EXISTING。(6)dwFlagsAndAttributes

使用这个参数确定文件的属性和一些标志。如果使用FILE-FLAG-OVERLAPPED,则使用异步模式使用设备文件,否则使用同步模式。(7)hTemplateFile

临时文件的句柄。我们不使用临时文件,况且,CreateFile函数的设备文件如果已经存在,则忽略这个值。所以这个参数值为0。4.2.2 CreateEvent函数——创建事件

事件(Event)是Windows的内核对象(Kernel Object)之一。事件的引入是Windows提示:

实现资源共享访问的基本机制之一。在异步模式中需要创建一个事件,其目的是实现和操作系统内核调度器的通信,以便能够收到I/O完成的信息。

CreateEvent的调用约定是:(1)lpEventAttributes

权限控制属性。设置为NULL。(2)bManualReset

是否需要手动恢复。如果把事件比喻成一盏指示灯,这个值就决定了当指示灯亮的时候是否需要手动关闭。这里选择“是”。(3)bInitialState

初始化状态。决定在事件“指示灯”被构造时,它是亮的还是灭的。选择“否”。(4)lpName

名称。由于我们与操作系统而不是与其他进程之间进行通信,所以设置为NULL。

CreateEvent返回事件句柄的示例将在后面介绍。4.2.3 Overlapped结构——异步模式信息的表达

在异步模式传输中,控制信息储存于Overlapped结构中。其定义为:(1)Internal和InternalHigh

这两个参数供操作系统内部使用,不需要进行设置。(2)Offset和OffsetHigh

它们表示从哪个偏移开始。Windows SDK指出,在串口通信中,这个值将被忽略。(3)hEvent事件句柄

使用CreateEvent返回的句柄。4.2.4 WriteFile函数——发送数据

调用约定如下:(1)hFile:已经打开的文件句柄

此处使用CreateFile返回的句柄。(2)lpBuffer:缓冲区头指针

它的类型是LPCVIOD,可以不经转化地传递任意类型的指针。如果需要传递的是一个例化的结构体,则可以使用“&”操作取地址。(3)nNumberOfBytesToWrite:将要写入的字节数

这个参数与lpBuffer一起,确定了内存中的哪些数据需要发送。比如,如果需要发送整个字符串,就可以使用strlen(lpBuffer);如果发送结构体,则可以使用sizeof获取其字节数。(4)lpNumberOfBytesWritten:已经写入的字节数

传递一个DWORD变量的地址,可以获取实际写入的字节数。

注意,当使用同步模式传输的时候,这个参数不可以设置为NULL。当使用异步模式传输的时候,这个参数可能不能正确地反映实际写入的字节数,因此,可以设置为NULL。调用GetOverlappedResult可以获取实际写入的字节数。(5)lpOverlapped:交叠结构体

当使用异步模式传输的时候,这个值不能够设置为NULL,而是一个Overlapped结构体的地址。4.2.5 ReadFile函数——接收数据

ReadFile的调用约定是:

这些参数的含义与WriteFile基本相同。接收数据成功后,将保存在lpBuffer和nNumberOfBytesToRead指定的内存块中。

如果在同步模式中,WriteFile或ReadFile返回0值,说明数据写/读完成。否则说明失败。使用GetLastError函数可以获取错误代码。

如果在异步模式中,WriteFile或ReadFile返回非0值,且GetLastError返回除ERROR-IO-PENDING之外的其他值,说明发生了错误。

如果在异步模式中,WriteFile或ReadFile返回非0值,且GetLastError返回了ERROR-IO-PENDING,此时并没有错误发生,而是异步的写/读已经提交给操作系统。

在异步模式中,WriteFile或ReadFile返回0值,说明写/读立即完成。这种情况只有在读写操作完成的时间短于读写操作提交的时间时才会出现。4.2.6 WaitForSingleObject——等待事件信号

调用约定是:

WaitForSingleObject属于Windows Wait Function集合。WaitForSingleObject函数在调用时,在不满足返回条件的情况下,将阻塞调用它的线程;如果hHandle是一个Event的句柄,则这个函数在以下情况之一发生时返回:

●这个Event产生了完成信号,返回值WAIT-OBJECT-0。

●这个Event没有产生完成信号,但是自调用起,其时间超过了dwMilliseconds定义的时间限额。返回值WAIT-TIMEOUT。

dwMilliseconds的单位是毫秒。如果赋给INFINITE,含义是忽略任何超时。4.2.7 一个同步和异步I/O例子

与Windows I/O相关的API函数就介绍到这里。为了使您深刻体会到同步和异步的区别,本小节介绍一个例子。

由于与串行通信设置相关的几个API函数还没有介绍,所以这里使用文件I/O。在这个程序里,首先准备一定大小的内存区块,然后分别用同步和异步的方式写入磁盘。所关注的一点就是各个方法中不同函数返回时所需要的时间。为了测量这个时间,我们使用QueryPerformanceCounter这个核心API。

启动Visual C++,建立控制台应用程序。在stdafx.h中包含windows.h和stdio.h(因为要使用printf)。

准备一个5MB的内存块(您也可视电脑的具体情况,酌情增减这个值)。

下面编写同步写文件的子函数。在写文件之前需要打开文件。为了使计时精确,打开文件之前先进行删除操作,让每一次写入都要求操作系统重新分配磁盘空间。注意,为CreateFile设置合适的参数以便使用同步I/O。

其中,t1和t2用于计时。

下面调用WriteFile函数进行写入。在同步I/O中,第5个参数设置为NULL。

在WriteFile调用的前后各使用一次QueryPerformanceCounter函数,以标记时间点。两次时间点相减就是一次同步I/O所消耗的时间。

提示:

LARGE-INTEGER可以提供不受系统字长限制的整数。它的具体用法请查阅MSDN。

下面编写异步I/O的处理函数。注意使用CreateFile的方法。

接下来需要创建overlapped结构和事件。

然后调用WriteFile,提供Overlapped结构,并设置时间点。

在写操作提交后,需要等待写入操作完成。此时调用WaitForSingleObject函数。在此函数返回后再记录一个时间点。

最后执行清理工作,并输出结果。

在主函数中分别调用这两个I/O操作。

编译并运行。结果如下:

在您的计算机上,counts的具体值可能会稍有出入。但是有一点是确定的,即使用异步I/O,其WriteFile返回的时间远小于同步I/O。而两种I/O完全完成的时间却在同一个数量级上。4.3 Windows通信API

上一节我们介绍了I/O的问题。在前面章节也介绍过串口通信有各种各样的硬件设置,这些设置是通过将特殊的控制字写入相应的寄存器实现的,而这也是在MS-DOS下进行串口编程不可缺少的工作。

Windows掩盖了硬件操作中的细节,并通过驱动程序与API相结合的方式提供上层软件操作硬件的编程接口。作为硬件资源的串口,其配置和使用也相同。自Windows 3.x开始,串口的相关设置API就在SDK中出现。在Windows 95/98/Me和Windows NT中,这些API函数得到完善。在MSDN中,这些函数称为Communication API(通信API)。

与串口相关的通信API经常用到的有两类,一类函数对串口进行设置,另一类提供串口的消息机制。4.3.1 DCB概述

设置串口的API函数是SetCommState。调用这个函数时,除了需要串口文件句柄之外,还需要一个“设备控制块”(Device-Control Block, DCB)的数据结构。

DCB结构非常复杂。其定义如下:

●DCBlength:本数据结构的长度。

●BaudRate:波特率。可以设置为具体的数值,也可以设置为系统定义的值,如CBR-9600等。

●fBinary:是否使用二进制模式。这个值总被设置为1,否则不能工作。

●fParity:是否使用校验位。

●fOutxCtsFlow:硬件流控制位。

●fOutxDsrFlow:硬件流控制位。

●fDtrControl:硬件流控制位。

●fDsrSensitivity:硬件流控制位。

●fTXContinueOnXoff:硬件流控制位。

●fOutX:软件流控制位。

●fInX:软件流控制位。

●fErrorChar:如果允许校验且校验失败,用此字符代替校验失败的字符。

●fNull:是否清理接收字符中的NULL。

●fRtsControl:硬件流控制位。

●fAbortOnError:如果传输过程中出现错误,传输立即停止,直到调用ClearCommError函数才重新开始传输。

●fDummy2:保留。

●wReserved:保留。

●XonLim:输入缓冲区下限。

●XoffLim:输入缓冲区上限。

●ByteSize:数据位长度。

●XonChar:软件流控制的XON字符。

●XoffChar:软件流控制的XOFF字符。

●ErrorChar:软件流控制的校验错误替代字符。

●EofChar:软件流控制的数据结尾字符。

●EvtChar:软件流控制的事件字符。

●wReserved1:保留。

●Parity:校验方式。包括EVENPARITY(偶校验)、MARKPARITY(置位校验)、NOPARITY(无校验位)、ODDPARITY(奇校验)、SPACEPARITY(清位校验)。

●StopBits:停止位长度。包括ONESTOPBIT(1位停止位)、ONE5STOPBITS(1.5位停止位长度)、TWOSTOPBITS(2位停止位长度)。

DCB包含了基本协议和软硬件流控制的设置。通过SetCommState函数进行串口设置是非常必要的一环,大多数通信时发生的错误都和错误地设置DCB有密切的关系。所以,当串口传输发生意料之外的情况时,应该首先检查DCB是否设置得正确。

设置DCB有3种方法,第一种方法是使用API函数GetCommState,这个函数将返回指定串口(通过句柄)的DCB设置,因为这些设置实际上是保存在“系统内部”的。下面的示例代码说明如何获取DCB。

第二种方法是使用API函数BuildCommDCB帮助建立DCB。这是在简化版三线通信中最常用的方法。使用者提供波特率、校验方法、停止位和数据位,BuildCommDCB则将“默认”的流控制等成员填好。需要注意的是,传给BuildCommDCB的DCB必须经过全零初始化,也就是说,BuildCommDCB并不是补充除用户设置外所有的成员。示例代码如下:

第三种方法是手工填充DCB的所有成员。虽然DCB已经是文档化的,但是不推荐这种方法。首先是比较麻烦,且容易出错;其次是不能够保证Win32平台未来实现中DCB的变化(例如几个保留成员等)。但是,如果打算使用精确控制的高级功能,例如,调制解调器的控制,就应该熟悉DCB的所有成员并进行手工处理。

不管怎样,DCB对串口的设置直到调用了SetCommState才能生效,并且,不能够不设置DCB而对系统默认值存有某种“假设”。4.3.2 流控制

在前几章我们介绍过流控制。除了RS-232本身提供的硬件流控制之外,操作系统也提供了基于字符的流控制,即软件流控制。

鉴于需要涉及到具体的程序设计,因此,在这里把流控制的内容进行细化,并着重说明Windows下如何操作流控制。

有时候,在软件模拟情况下工作正常的通信,连接设备后往往出现错误,数据没有传达或者无法收到,通常,问题就出现于没有使用或者没有正确地使用流控制。在DCB中,涉及流控制的成员fOutxCtsFlow、fOutxDsrFlow和fOutX,一旦这三个成员中任何一个设置为“TRUE”,那么硬件流控制就有可能出问题。ClearCommError函数也可以帮助解决问题,调用这个函数,检查COMSTAT结构体,它能够告诉我们是否由于流控制的问题造成通信失败。

我们知道,硬件流控制是使用非传输用连接线的电平控制传输是否有效,也就是说,如果启用了硬件流控制,那么流控制线的电平必须设置正确。这里需要指出,DCB只用来设置DTE,也就是串口通信中的HOST,在Windows中,没有任何方法可以用来设置DCE的流控制。

CTS(Clear To Send)输出流控制:DCE将此线上的电平设置为有效,说明它可以接收数据。如果fOutxCtsFlow设置为TRUE,则DTE会根据CTS线上的状态决定是否输出数据,否则DTE会忽略此线上的状态。

DSR(Data Set Ready)输出流控制:DCE将此线上的电平设置为有效,说明它可以接收数据。如果fOutxDsrFlow设置为TRUE,则DTE会根据DSR线上的状态决定是否输出数据,否则DTE会忽略此线上的状态。

DSR(Data Set Ready)输入流控制:如果DSR是低电平,则DTE忽略所接收的数据;反之,DTE不忽略所接收的数据。当且仅当fDsrSensitivity设置为“TRUE”时,这个行为才有效。

RTS(Ready To Send)输入流控制:这个行为是DTE所控制的。当fRtsControl设置为RTS-CONTROL-HANDSHAKE时,下列行为有效:当接收缓冲区“将空”的时候,RTS被置为高。否则,接收缓冲区“将满”的时候,RTS被置为低。当fRtsControl设置为RTS-CONTROL-TOGGLE时,当有数据需要传送时,RTS被驱动程序设置为高,否则,当没有数据传送时,RTS设置为低。当fRtsControl设置为RTS-CONTROL-ENABLE或者RTS-CONTROL-DISABLE时,意味着RTS被“手动”设置。注意,在Windows 95中,设置为RTS-CONTROL-TOGGLE等同于设置为RTS-CONTROL-ENABLE。

那么,DCE如果也有相应的硬件流控制,则应该在RTS为低时停止发送数据给DTE。

DTR(Data Terminal Ready)输入流控制:这个行为也是DTE所控制的。如果fDtrControl被设置为DTR-CONTROL-HANDSHAKE,下面所述的控制方式被启用:当接收缓冲区“将空”的时候,DTR被置为高。否则,接收缓冲区“将满”的时候,DTR被置为低。当fDtrControl设置为DTR-CONTROL-ENABLE或者DTR-CONTROL-DISABLE时,意味着DTR被“手动”设置。DCE如果也有相应的硬件流控制,则应该在DTR为低时停止发送数据给DTE。

当硬件流控制被启用后,如果硬件流控制生效,串口事件会传回特定的值。CE-RXOVER的意思是接收缓冲区满,且数据丢失。也就是说,如果数据到来的速度大于串口拾取的速度,这个时间产生。除非使用输入流控制并且DCE端也进行了匹配,否则只增加输入缓冲区的大小不能够解决这个问题。使用输入流控制,就可以让DCE暂停数据的传输。

与之类似的错误是CE-OVERRUN,它指硬件系统不能在传输速度允许的范围内完成串行数据的解析,也就是说,这个错误的层次比CE-RXOVER要低。解决的办法只有更换新的硬件系统,或者降低传输的吞吐率。

硬件流控制中,使用XoffLim和XonLim作为缓冲区“将满”和“将空”的标志。

软件流控制不使用额外的硬件连接线,适合于简化的三线串口传输。

软件流控制使用特殊的字符作为传输。启用软件传输,DCB的fOutX和fInX需要设置为TRUE。fOutX控制输出流控制允许,fInX控制输入流控制允许。

软件流控制使用XON字符和XOFF字符作为传输起始和传输结束的控制符。这两个字符在DCB块的XonChar成员和XoffChar成员中设置。输入流控制中,在接收缓冲区“将满”时,XOFF字符被发送;在接收缓冲区“将空”时,XON字符被发送。输出流控制中,在发送缓冲区“将满”时,XON字符被发送;在发送缓冲区“将空”时,XOFF字符被发送。

系统有可能在执行接收任务时也执行发送任务。输入流控制中,在系统发出XOFF后,如果fTXContinueOnXoff被设置为TRUE,那么系统的发送任务继续;如果设置为FALSE,系统的发送任务将暂停,直到流控制器发出XON。

如果DTE接收到了XOFF,那么DTE将暂停传输,并等待XON以恢复传输。

注意,如果设置了软件流控制,则上层软件将读不到XON和XOFF,然而,上层软件有可能发送这两个字符。所以如果进行二进制传输,必须进行扰码。

在软件流控制中,使用XoffLim和XonLim作为缓冲区“将满”和“将空”的标志。

大多数串口通信程序,尤其是通用性较强的软件,都给用户提供了硬件流控制、软件流控制和不进行流控制的选择。当然,如果针对特定的设备开发串口通信应用程序,则不需要面面俱到。4.3.3 传输超时

传输超时经常出现在I/O操作中,串口也不例外。传输超时体现在:如果一个操作超过了所设定的允许的操作时间,这个操作也算是成功的,并且,ReadFile、WriteFile、GetOverlappedResult和WaitForSingleObject并没有相应的错误代码的返回。因此,判断I/O操作到底是超时还是成功完成的唯一办法是,观察实际传输的字节量是否少于设定传输的字节量。比如,ReadFile返回TRUE,但是读取了较少的字节,那就说明传输超时。如果一个异步I/O超时了,那么overlapped事件点亮,WaitForSingleObject返回WAIT-OBJECT-O, GetOverlappedResult返回TRUE,但是dwBytes能够告诉我们,在超时之前有多少数据被传输。如代码:

串口通信函数SetCommTimeouts定义了一个端口传输超时的时间,即结构体COMMTIMEOUTS。获取传输超时的设置,使用GetCommTimeouts。在程序修改COMMTIMEOUTS之前最好需要先查找系统的默认值,这是因为,在改变超时默认值之后,最好把原来的值返还给系统。下面是设置超时的示例程序:

需要特别注意,串口通信API中的超时和内核操作(例如WaitForSingleObject)中的超时不一样。

如果把COMMTIMEOUTS的成员都设置为0,则说明超时无效。这意味着同步的I/O在所有数据传输之前均阻塞,异步传输中,除非传输被强行终止,否则不会结束。

COMMTIMEOUTS的第一个成员ReadIntervalTimeout的含义是,以多大的毫秒间隔读取缓冲区内的数据。如果两个相邻字节到达时间超过此设置,则操作超时。第二个成员ReadTotalTimeoutMultiplier根据所读取的字节数设置总的超时时间。第三个成员指无论读取多少字节,都设置一个常数的总超时时间。后两个成员的含义类推。

应用程序应该根据所连接设备的吞吐率合理设置超时。4.3.4 串口状态

获取串口的工作状态有两种方法,一种是被动法,即设置一个事件标志位,当事件发生后,哪些类型的事件可以提供给应用程序。执行这个设置的API函数是SetCommMask,而WaitCommEvent函数将等待时间的发生。另一种是主动法,即不停地调用一系列的查看函数查询状态,这种方法不推荐。

1.通信事件

一旦通信启动,事件随时可能发生。如刚刚所述,SetCommMask设置事件类型的过滤,WaitCommEvent进行同步的或者异步的事件查询。注意,通信API中的事件与内核事件是不同的概念。

下面是一个例子:

各个设置位的含义如下:

●EV_BREAK:输入被打断。

●EV_CTS:CTS线上状态改变。得知具体改变的情况应调用GetCommModemStatus函数。

●EV_DSR:DSR线上状态改变。得知具体改变的情况应调用GetCommModemStatus函数。

●EV_ERR:串并转换解析失败,错误包括CE-FRAME(帧错误)、CE_OVERRUN(溢出错误)以及CE_RXPARITY(校验错误)。调用ClearCommError可以获取具体信息。

●EV_RING:RI振铃检测。

●EV_RLSD:CD线上状态改变。得知具体改变的情况应调用GetCommModemStatus函数。

●EV_RXCHAR:接收到一个字符。

●EV-RXFLAG:接收到软件流控制的事件字符。

●EV_TXEMPTY:内存缓冲区中的所有字符已经送达硬件(不意味发送完毕,有可能硬件也有缓冲区)。

如果串口I/O是以非交叠(同步)方式打开的,则WaitCommEvent会阻塞直到任何一个设置位标志的事件产生。如检测RING事件:

注意,Windows 95不支持该事件的检测。

如果采用交叠式的I/O,则以下代码可用于检测各种事件:

可以看到,这段代码非常类似于overlapped读流程。事实上,MSDN的例子MTTTY就使用了WaitForMultipleObjects等待串口事件和I/O读事件。

多线程的情况下,会有两种可能:第一,如果串口以非交叠式打开,则WaitCommEvent会阻塞线程直至事件发生;如果另一个线程调用SetCommMask,那么这个操作也会被阻塞。这是因为,前一个线程中的WaitCommEvent一直在执行,而作为一种独享资源,每次只能执行一个通信API。第二,如果串口以交叠式打开,其他线程的WaitCommEvent会立即完成,而SetCommMask会设置为NULL。

2.数据到来

EV-RXCHAR的含义是已经接收到一个字节。这个事件非常有用。应用程序在接收到这个事件后,会调用ReadFile进行数据的读取。尤其在以交叠式打开的串口中,这样就不必一直读取串口数据了。示例代码如下:

上面的代码等待EV-RXCHAR这个通信事件。当这个事件发生后,调用ReadFile读取1B的数据,紧接着,继续等待下一个EV_RXCHAR。当少量而延迟短的数据到来时,这段代码能够很好地工作,然而,当大量连续的数据到来后,就会出现以下问题:第一个字节会使EV-RXCHAR事件发生,当正在读取第一个字节时,第二个字节来临。因此,在系统内部产生EV-RXCHAR,当第一个字节没读完而第三个字节来到时,又将尝试产生EV-RXCHAR。此时第一个字节读取完毕,WaitCommEvent被调用,EV-RXCHAR被通知,程序读取了第二个字节,而对第三个字节浑然不知。当第四个字节来到时,读到的却是第三个字节。这就造成了事件和信息的不同步。

似乎每次读更多的字节就能避免这个问题,但是,对于读者而言,他永远不知道应该读的数据到底有多少,也就是说,假设每次读n个字节,当大于n的数据一次性很快来到时,仍然会产生不同步。

解决这个问题的方法是在事件来临后,反复读取缓冲区,直到无数据可读为止。为了提高效率,也可以调用ClearCommError确认缓冲区中到底有多少数据没有读。

使用上面的代码首先应该设置合适的通信超时。

不仅仅是接收数据,其他事件也有这样的问题。对于CTS等硬流控制线,Windows设计了一类特别的函数进行查询。

3.错误处理

EV-ERR用于标明串并解析时发生的错误。波特率不匹配会引起这类错误。如果DCB经过了设置,那么必须调用ClearCommError之后,才能从错误中恢复。

ClearCommError不仅仅是字面意义上“清除错误”,它还能够给出错误的来源:是串并解析错误还是流控制错误。下面给出代码:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载