现在的位置: 首页 > 综合 > 正文

DOS程序员参考手册[7]

2013年09月20日 ⁄ 综合 ⁄ 共 27817字 ⁄ 字号 评论关闭

131页
              第7章     串行设备
    现代计算机世界的串行设备是非常吸引人的。感谢串行设备,借助这样的设备,再利
用如CompuServe或MCI Mial的系统,我们就能与世界各地不同的人们进行通信联系。
本章的主题是有关串行设备为什么能以目前的方式运行,以及我们怎样才能利用它们的
特性。
    在这一章里,都要以与其它计算机进行通信这种方式而与串行设备一起工作——这
是当前这种通信方式最常见的用户。串行通信还可以用于打印机、传感器和许多其它的设
备。并且这种通信不必是双向的。例如,与打印机的通信占主导地位的是单向的:从计算
机到打印机。或者可以建立一个程序来读取Associated Press新闻电信,它是一个1200bps
(每秒位)的单向数据传送,或NOAA的天气信息,它是一个50bps的单向数据传送。
    本章集中介绍一个简单的双向终端通信程序,该程序包括了串行通信的所有方面。但
在编写终端程序之前,应该知道串行接口的工作方式。定义一些基本术语后,就要提供有
关IBM PC串行接口芯片(UART)的工作方式方面的细节。然后讨论上升到硬件水平以
便用户了解怎样直接控制芯片。
    借助一些基本原理,可以编写两个终端程序。第一个利用BIOS功能去访问串行端
口。将会看到:这种类型的程序用于重要的通信时太慢了。如果直接与UART芯片一起工
作,就可以获得一定的速度和控制。尽管第二个终端程序证明了这项技术,但即使是这样,
它对1200bps的实际通信也还是太慢了。一个真正的实用通信程序必须将本章中的技巧
与第11章“中断处理程序”中的内容结合在一起。编写一个中断驱动的通俗读物程序,并
非本书所涉及的主题,它是对任何PC程序员的耐心和他们的理解程度的考验。有关编写
这样的程序的实例可以参看《Advanced Assembly Language》,由Allen L,Wyatt编写(由
Que公司出版)。
    从基本原理上讲,一个串行接口是将计算机内部数据的并行格式(8位字节)转变成
一个串行格式(1位),后者能在单根数据线上进行传递。这种转换能由软件来执行,但在
PC机中,硬件能更有效地完成它。
    图7.1分析了串行接口的基本目的:将信息由并行格式转变成串行格式,或反过来。
数据从该接口的一边进入,从另一边产生和转换。接口把数据的每个字符转变成一个信息
“包”,从而将数据转变成串行格式;这个信息“包”能以发送和接收双方都认可的某种途径
来进行传递(有关串行格式的更详细讨论,见本章稍后部分)。只有在每个串行连接的终端
都使用同样的数据格式和相同的传递速度时,计算机之间才能成功地进行通信。
    常用两种方法来传递串行信息。它们都称作定时方法,它们都是通过串行来传递和接
                                                                                              
132页
         图7.1串行接口将数据形式由并行的(内部的)变成串行的(外部的)
收信息的。第一种方法叫作同步通信,它维持对数据经连接而进行的传递和接收的严格控
制。在这个过程中,数据以精确定时的间隔进行传递。定时信息是与数据一起传递的,以
便接收方计算机能与要接收的信息同步。这类通信通常用于小型机或主机应用程序,有关
它的讨论则超出了本书的范围。
    第二种方法叫做异步通信。在这种方法里,每个信息包放在通信线上,彼此之间并没
有精确限定的时间间隔。这些数据包能够一个挨一个地快速传递或每个传递之间间隔不
同的时间。这种方法对于大多数微机,包括IBM系列都是适合的。
    尽管可以为其它类型的串行通信(如前面提到的同步方法)购买硬件接口设备,但异
步通信应该能满足大多数通常目的的需要。
    图7.2显示了通信线上的串行信息包(可以作为异步通信的时间功能)。该线路通常
保持标记状态(高电压)。当下降到空闲状态(低电压)时,就是给信号说明字符开始了(开
始位)。要确定下一个位是高或低,可以用以位频率为基础的精确时间间隔来对线路进行
抽样。在标记状态时,数据位后会紧跟一个或更多的停止位,从而有足够的时间用于字符
处理和用于系统准备下一个字符。
                                      图7.2串行传递
                      7.1串行接口
    在进行异步通信的更详细讨论之前,应该了解基本的通信术语。我们还要看到,串行
接口将并行格式数据转换成容易经过串行通信连接进行传递的信息包形式。这些信息包

133页
油许多特定的信息位构成。每个位都有一个特定的目的:开始位、数据位、停止位以及奇偶
性。下面定义这些和其它几个重要的术语:
      ·开始位在实际字符数据之前送出的一个位,用来警告接收方计算机:一个字符
        就要来了。该位由串行设备自动送出。
      ·数据位该位代表正在传递的单个字符。(数据位的个数通常即是字的长度)。在
      计算机奇偶性的情况下,串行设备的正常通信使用7位的字长度(见下列定义);
        否则就使用8位的字长度。 IBM PC上的串行通信芯片能够操纵5-8个数据位。
      ·奇偶性一个简单的字符水平“真假性”检查,接收方可以利用它来看看是否已正
        确地接收到了字符。计算奇偶性的方法是:首先计数正在传递的信息包中数据部
      分设置为1的位数,然后加上所希望的奇偶性类型位代表。对于EVEN(偶)奇偶
      性,设置为1的数据位的总数再加上奇偶性位必须是一个偶数。相反,ODD(奇)奇
      偶性中就会带来一个奇数。其他的奇偶性位可能的设置包括MARK(总是设置成
      1),SPACE(总是设为0),或NONE(总是忽略)。
      ·停止位该位在数据包的结束处送出,以便给接收方时间,在下一个字符到来之
      前处理已有的一个字符。对于将要操纵的所有通信,正常情况下只有一个停止位
      (只有当通信以极低的速度,如110bps进行时才需要2个停止位)。
      ·波特率这是一个电气学名词,代表一条通信线的传输速率。它常常(尽管不正
      确)用来指位速率。
    ·位速率传输速度,即每秒传送的位数,常常(尽管不正确)被称为波特率。位速率
      是个更精确的词,故在本书中主要使用它。
    ·全双工一种通信手段,传递时,显示在屏幕上的信息就是输送给远处计算机的
      字符的回送。
    ·半双工也是一种通信手段,其中传送给远处计算机的信息并未返回到送出的计
      算机。
    一台计算机与另一台计算机通信时,双方必须根据一系列预先限定的用来定义所传
递的信息格式的参数进行操作。如果两台计算机没有设置为相同值,它们之间的通信就是
不可靠的。计算机之间的通信没有象应有的那样普及,一个原因是程序员还没能在不需用
户去理解专业化的、复杂术语的通信标准上达到共识。
    但许多配置却是相当标准的。通常,8个数据位,没有奇偶性,以及一个停止位或者7
个数据位,EVEN或ODD奇偶性以及一个停止位就可以工作了。对于大多数联机计算机
系统,位速率一般都建立在1200、2400或9600bps上。如果试试这些配置中的一个,那么
几乎总是能与联机计算机系统中的某台计算机匹配上。
    传递信息之后,计时至关重要。通信线在接收到开始位之前是空闲的。开始位到达后,
就要在精确的时间间隔内对通信线进行采样,以接收构成字符的单个位。奇偶性位则用来
计算所传递的字符的正确程度,它紧跟在数据位之后,最后,得到并释放停止位,接收方又
在等待下一个开始位。
    如果这些背景知识使人晕头转向,那么可以放松一下。IBM微机已安装了管理串行
                                                                                        
134页
    通信的低水平行为的硬件。在确定要使用的通信参数并把它们输入以后,串行转换硬件能
    保证使用户得到所希望的东西。
                      7.2串行转换:UART
      IBM微机(以及大多数兼容tA)都使用一个硬件芯片:它起初以8250 Universal Asyn-
    chronous Receiver/Transmitter(8250通用异步接收/传送器)为基础,由National Semicon-
    ductor(国家半导体)公司生产。在Pc机和Pc/xT的早期,就已经使用了这种芯片的其它
    类型。最近的则是Pc16450和Pc16550。这类芯片在功能上都与8250等同,但能提供更
    高的效率和速度。 Pc16550除了与8250兼容以外,还有一个使用了FIFO(先进、先出)数
    据缓冲的操作方式,该数据缓冲大大地增加了吞吐量。在这一章里,这3个芯片简单地称
    作UART(广泛的异步接收者/运输者)。
      与一些依赖软件来管理通信的系统相比,UART让人迷惑不解。它参与到接收和传
    递信息位的具体活动中,从而使程序员不用完成其它任务。
      假定需穿过一系列电线来送出数据,而此时线上电压水平在不断变化。如果编写了程
    序来管理这条线路,那么就能直接控制这条线,并告知任何需要的东西。听起来很困难,是
    不是?理论上并不是这样,但过程是繁琐的,并易导致细微的出错。有些系统(例如,原始
    的TSR80彩色计算机)只能以这种方式处理串行通信。
      借助UART,就不必添麻烦去编写在通信线上开或关来获得数据的一个软件控制程
序。要花少得多的精力去编写和测试软件uART,UART芯片为使用者提供了大量的控
    制,并允许与其他设备进行快速的、标准的通信。
      8250和16450 UART分别有10个可以编程的1个字节寄存器;16550则有11个。
这些寄存器控制和监视串行端口。大多数寄存器用于初始化,只有几个用得有规律。所有
寄存器都可通过7个I/O端口地址进行访问。这些地址作为来自一个基地址的偏移值来
计算,基地址是随着所使用的通信端口而改变的。表7.1显示了coM1:到coM4:的基地
址;表7.2列举了这些地址的偏移值,它们分别控制每个UART寄存器。
                              表7.1 IBM通信端口的基地址
                通信端口                  基地址
                COM1:                    03F8h
                COM2:                   02F8h
                COM3:                   03E8h
                COM4:                    02E8h
                              表7.2 UART寄存器:来自基地址的值
      偏移值  LST位7                意    义
      0     0       传递保持者寄存器(THR)和接收者数据寄存器(RDR)
      0         1         波特率除数值的低字节(BRDL)
      
135页
                                                                      (续)
偏移值      LST位7        意    义
    1         0           中断允许寄存器(IER)
    1         1           波特率除数值的高字节(BRDH)
    2         x           中断识别寄存器(IIR)和FIFO控制寄存器(FCR一16550UART独有)
    3         x           线控制寄存器(LCR)
    4         X           调制解调器控制寄存器(MCR)
    5         X           线状态寄存器(LSR)
    6         x           调制解调器状态寄存器(MSR)
读者可能已注意到,即使UART有10或11个寄存器去控制操作,但却只有7个端
口地址。这7个地址中的大多数支持一个以上的寄存器。在偏移值为0时,不论什么时候
向端口写,都可以访问THR,而不论什么时候从端口读都可以访问RDR。因为这两个寄
存器中没有一个需要同时的读和写访问,所以这两者的结合很有意思。这种安排与用在
16550 UART上的安排是相同的;IIR和FCT寄存器共享相同的寄存器。IIR是一个只读
的寄存器;FCR则是一个只写的寄存器。
当LSR第7位设置为1时,偏移值为0和1的寄存器则会执行另一项功能。当这种
情况发生时,这两个端口就会访问BRD寄存器(因为只有在芯片的初始化期间才访问
BRD寄存器,所以它们在通常操作期间能安全地保存起来)。
    让我们看看每个UART寄存器都做些什么。
7.2.1发送保持寄存器(THR)
该寄存器保持有将要送出的数据字节。如果LSR的第5位指示该寄存器是空的,那
么就可以向它写入数据。
7.2.2接收数据寄存器(RDR)
该寄存器保持有最近从通信线上接收到的数据韶。如果LSR0位指示已接收到一
个字节,那么就可以读取该寄存器。
7.2.3波特率除数(BRD)
BRD是一个16位数,它指定UART使用的传输率(不是波特率,不考虑UART设计
考所给的正规名字)。它在两个8位端口(BRDt和BRDH)之间划分开来。要确定位传输
速率,就可用UART的内部时钟频率(1.832MHz)来除以BRD,如下所示:
            BRD=时钟速度/16*期望的bps
使用该等式很容易确定不同位速度的设置。例如计算BRD为9,600,等式如下∶
        BRD=1843200/16*1200=1843200/19200=96=0060h
于是,BRDH必须设置为0,而且BRDL必须设置成0Ch。
136页
      可以使用该等式来构造表7.3所列举的典型位速率的BRD。
                                  表7.3 波特率除数
              位速率                             BRDH                                BRDL
              50                                  09h                                 00h
              110                                 04h                                 17h
              300                                  01h                                80h
              1200                                 00h                                60h
              2400                                 00h                                30h
              4800                                00h                                18h
              9600                                00h                                 0Ch
              19200                                00h                                06h
    注意IBM警告其BIOS早期版本用户,不要设置高于9600bps的位速率。但可在
          19200bps(甚至更高)的位速率下安全地驱动UART。
          要设置BRD,必须首先将LCR的第7位设置成1。然后可以安全地将所需除数输
          出给它们的I/O地址(参见表7.2)。设置BRD后,应马上清除LCR的第7位。
    7.2.4中断允许寄存器(IER)
        IER控制UART产生的中断类型,一次可以允许一个或更多的中断,这依据编写中
    断处理程序的方法而定。不论什么时候允许了一个中断,就必须采用一个特定的行为来清
    除它。表7.4显示了寄存器位上所指定的中断以及需用来清除每个中断的合适行为。
                           表7.4中断允许寄存器
            位                      启    动                      操    作
            0                       数据接收                      读RDR
            1                       THR空                         输出到THR
            2                       数据出错或中断                读LSR
            3                       MSR改变                       读MSR
            4~7                    未用:总是设置为0
        当表7.4中所示启动条件之一发生并且相应的IER设置为1时,中断就会产生。
    7.2.5中断识别寄存器(IIR)
        中断发生时通信程序能从IIR的位设置来识别该中断。表7.5列举了这些位的意义。
                                      表7.5中断识别寄存器
            位                  意    义
            0             已发生的中断超过1个
            1~2          中断ID
            3             中断ID(MsB~16550 UART独有;在其它UART上总是设置0)
            4~5          未用;总是设置为0
            6~7        FIFO缓冲允许标志(16550 UART独有;在其它UART上总是设置为0)
      
137页
    如果软件是由中断驱动的,那么首先必须限定希望产生的中断的类型(参考表7.4)。
然后,接到一个中断请求后,必须检查IIR,看看发生的中断是哪种类型。表7.6指示用来
识别中断的三个位(8250和16450 UART只有第1位和第2位)的可能设置。
                                  表7.6中断ID位设置
        第三位           第二位                第一位                    意义
          0               0                     0                   MSR发生变化
          0               0                     1                   THR空
          1               1                     0                   接收FIFO字符超时
          0               1                     0                   数据接收
          0               1                     1                   数据接收出错或中断
7.2.6 FIFO控制寄存器(FCR)
    16550 UART添加了一项功能,用于缓冲正在送出或接收的数据。该缓冲称为FI-
FO——(先进先出)。在UART的早期型号中,这种缓冲是没有用的。表7.7显示了该寄
存器各位的意义。
                                表7.7 FIFO控制寄存器
           位                     意义
           0                    允许和清除FIFO缓冲
           1                    接收重新设置的FIFO缓冲
           2                    传递重新设置的FIFO缓冲
           3                    DMA模式选择
           4~5                 保留
           6                    接收方触发器(LSB)
           7                    接收方触发器(MSB)
    FCR的第6位及第7位用来指示应该用怎样的触发器水平来产生中断。这种水平即
是指示在中断产生之前,接收缓冲应该有多满。如果有快速中断设备途径,就可以设置高
水平触发器,并且较少中断主程序。表7.8显示了可能的触发器水平。
                                表7.8 FCR中断触发器水平
        位                        水平
        00                       1个字节
        10                       4个字节
        01                       8个字节
        11                       14个字节
7.2.7线控制寄存器(LCR)
    它是串行线的主要控制寄存器。表7.9介绍该寄存器的位分配情况。
                                  表7.9线控制寄存器
      位            意义                设置            备注
      0-1          字符长度
                      5个位             00
                      6个位             01
                    7个位               10
                    8个位               11
                                                                                                
138页
      位              意义                设置            备注
      2               停止位
                      1个位               0
                      1.5个位                            如果使用5位字符
                      2个位               1               如果使用6-、7-、8-位字符
      3-5             奇偶性
                      16NORE              000
                      000                 100
                      EVEN                UA
                      MARK                101
                      SPACE               111
      6               中断条件
                      禁止                0
                      许可                1
      7               端口触发器
                      正常                0                使用THR/RDR和IER寄存器
                      可选的              1                使用BRDL和BRDH寄存器
7.2.8调制解调器控制寄存器(MCR)
    MCR将控制线设置给调制解调器,并通过这些线告诉调制解调器计算机将要送出字
符,接收字符,或两者都要做。表7.10显示了该寄存器的位分配。
                            表7.10调制解调器控制寄存器
         位                            意义
         0                        设置DTR线活动
         1                        设置RTS线活动
         2                        用户输出#1(Hayes Reset)
         3                        用户输出#2(Enable Ints)
         4                        UART环路
         5~7                     未用;设置成零
     数据终端就绪(DTR)线告诉调制解调器,计算机已打开并准备接收来自调制解调器
的信息。请求送出(RTS)线告诉解调器计算机准备向线上送出一些东西。平常情况下,可
以安全地将DTR和RTS都设置成1来打开这些线。一些解调器会忽视(或设置成会忽
视)这些信号,但;日式的解调器不会这样。第2位只能被特别的硬件(如Hayes SmartMo-
dem内部板,它使用第2位来重新设置解调器)所使用,并且应被初始化为0。第3位(用
户输出#2),它与UART的中断设备紧密结合在一起,如果它未设置成1,就会中断处
理。第4位允许对带有线上通信的程序进行测试。在这种环路状态下,送出端口的数据会
作为输入一样再次出现。
7.2.9线状态寄存器(LSR)
    LSR会告知通信线的状态(见表7.11)。借助该寄存器,可以诊断通常的线路问题。
            
139页
                         表7.11线状态寄存器(LSR)
      位                    意  义
      0               接收到的数据;RDR中的字节
      1               因为下一个字节到达前,先前的字节还未被读取,就发生了超出错
      2                  奇偶性出错
      3                     传送不同步(字符读取后未发现停止位)而导致帧差错
      4                     中断探测
      5                   THR是空的;可以向线上输出一个字符
      6                   TSR空;TSR将来自THR的字符放在线上,每次放一个位
      7                   超时(在16450和16550 UART中总是设置成0)
7.2.10调制解调器状态寄存器(MSR)
    调制解调器的状态决定于某个状态线是高还是低,以及自最后一个寄存器读取以后,
特定的线上的状态是否已发生了变化。表7.12显示了MSR中的位是如何分配的。
                              表7.12 MSR各位的意义
      位                                意    义
      0                           清除发送(CTS)发生变化
      1                           数据设置就绪(DsR)发生变化
      2                           环形指示器(RI)发生变化
      3                           数据携带者探测(DCD)发生改变
      4                           CTS设置为高电平
      5                           DSR设置为高电平
      6                           RI设置为高电平
      7                           DCD设置为高电平
调制解调器信号与连结计算机和串行设备的电信号线状态的改变相对应。依设备而
定,这些硬件信号可能用也可能不用。一些调制解调器没有利用它们而只是依赖Hayes命
令设置来处理通信。调制解调器(或其他串行设备)可能使用调制解调器信号,表7.13列
举了它们的意义。
                          表7.13调制解调器信号
      信号                              意  义
      CTS                         调制解调器准备接收来自计算机的字符。
      DSR                         调制解调器已打开并准备操作。
      RI                          电话线是环形的。因为线是环形的,所以RI保持高电
                                  平,以便计算机能探测这些环线
      DCD                         调制解调器之间的联系
                                                                                         
140页
                  7.3将通信端口初始化
      直接与UART进行工作并没有看起来的那么容易。甚至初始化工作,也会变成一个
复杂的操作,它依赖于以正确的顺序来对寄存器排序,以便产生特定的效果。对于许多程
序(甚至那些打算直接访问UART的程序),不必直接初始化此芯片。可以通过目的是使
任务简单化的设计的BIOS功能来控制初始化过程。在这个编程领域(象其他地方一样),
并不需要做多少准备工作。
      要访问对串行端口进行初始化的BIOS功能,将AH寄存器设置0,DX寄存器则设置
成代表将要初始化的通信端口的序号(从零开始计数,于是,0=COM1:,2=COM3:,3=
COM4:)。因为IBM BIOS的一些版本并非内在地支持4个通信端口,所以DX设置会只
限于0或1。而所有的PS/2系列都支持4个通信端口。
      最后,将AL设置成所希望的初始化参数。表7.14列举了可能的AL设置。
                            表7.14 AL的BlOS通信端口初始化设置
      位                      意义                            设置
      0-1                     字长
                              未用                            00
                              未用                            01
                              7个位                           10
                              8个位                           11
      2                       停止位
                              1个位                           0
                              2个位                           1
      3~4                    奇偶性
                              NONE                            00
                              000                             01
                              NONE                            10
                              EVEN                            11
      5~7                    位速率
                              110bps                          000
                              150bps                           001
                              300bps                          010
                              600bps                          011
                             1200Bps                            100
                              2400bps                         101
                             4800bpS                         110
                              9600bps                           111
      在AH、AL和DX设置成必需的值以后,生成一个Int 14h,将通信端口根据所需规格
进行设置(有关BIOS功能所必需的寄存器设置,请见表7.17;有关该功能的进一步情况,
请参见第五部分“BIOS功能参考手册”一节的内容)。
      
141页
    由表7.14可见,不能设置5个或6个位的数据长度,也不能设置低于110bps或高于
9600bps的位速率。对于某些应用程序,初始化选项是有限的。例如,当结合某个特殊化的
应用程序如NOAA天气信息时,它只有50bps和5位字长度,那么唯一的初始化选项就
是通过直接处理UART寄存器来对通信端口进行初始化。
    IBM PS/2系列计算机,有另一个BIOS功能提供对通倍接口的一些附加的控制。要
访问此功能,可以将AH寄存器设置成4,而对于通常的BIOS功能,则可将DX寄存器设
置成代表需初始化的通信端口的值。然后,将AL设置成0或1,依据是否需要线上的中断
条件而定。通常,AL设置为0(没有中断)。BH必须设置成所需的奇偶性,如表7.15所示。
                  表7.15 BH(功能14/4)的奇偶性设置
             设置                      奇偶性意义
              0                            无
              1                            奇
              2                            偶
              3                            标记
              4                            SPACE
    BL必须设置成所需的停止位的数字;0代表1个停止位,1代表1.5个(用于5位数
据长度)或2个停止位(用于6个、7个或8个位数据长度)。
    CH中的数据长度是特定的,为5,比所需的数据位数小。于是0代表5个数据位,3则
等于8个数据位。
    最后,CL应设置成所需的位速率。位速率由表7.16中所列的设置而定。
                          表7.16 CL的位速率设置(功能14/4)
              设置                          位速率
              0                             110bps
              1                             150bps
              2                             300bps
              3                             600bps
              4                             1200bps
              5                             2400bps
              6                             4800bps
              7                             4800bps
              8                             19200bps       
    设置了所有寄存器(AH、AL、BH、BL、CH、CL和DX)的值后,就可调用Int 14h来按
特定的那样设置通信端口。表7.17总结了本节出现的两个BIOS功能必需的寄存器设
置。有关这些功能的更多信息,可参见第五部分的“BIOS功能参考手册”一节。
·                        表7.17 BIOS通信端日初始化功能小结
      功能              参数                        及可能的设置
      AH=0             AL                      根据表7.14表中的数据来设置
                        DX                     所需的通信端口0(COM:)到3(COM4:)
      AH=4                                     (只在PS/2系列中发挥作用)
                        AL                      中断条件设置
                        BH                      奇偶性
                        BL                      停止位
                                                                                          
142页
        功能            参数                  及可能的设置
                        CH                    字长度
                        CL                    位速率
                        DX                    所需的通信端口:0(COM:)到3(COM4:)
      如果不使用BIOS对通信端口进行初始化的功能,那么就不能把某个寄存器设置(通
过BIOS功能)成所需的值,这时就必须直接访问UART。但在假设“BIOS不能做”之前,
先试着用BIOS的初始化功能来进行操作——可能会发现它有更多的用处。
      如果说完成UART的所有配置的想法很吓人的话,那么请记住确实可在任何时候改
变配置的任何部分。这意味着可以使用BIOS程序在其范围内设置所有的参数,然后直接
到UART硬件水平来修改速度、字大小或奇偶性等等的值。
      通过利用直接的UART初始化,可以将任何速度设置到115.2Kbps。这其中的秘密
隐含在本章前面所列的速度等式中;变成下面的格式就略简单一些:
              除数=115.200/期望的bps速率
      这里主要的区别就是时钟速度已经与16位参数结合在一起变成了一个数字常量。可
以看出115.2Kbps率(由某些插板式计算机数据——传输程序达到这么高的速度)是可
能来自串行接口(除数0001)的最大值的。
      从表7.1到表7.10提供了做这件事时所需的所有信息;唯一要小心的是在向某个端
口进行输出后不要马上尝试从它输入,因为UART比许多现代的CPU反应要慢得多。
BIOS程序在每个OUT命令都包含JMP $ +2命令,这样就带来足够的延迟,以便确定
在高水平的时钟速度下不出现问题;如果要添加用户自己的初始化过程,那么这是一个很
好的规则。
                      7.4调制解调器
      调制解调器(modem)是调制器——解调器(modulator,demodulator)的缩写。尽管如
何使用某个特定调制解调器的详细指导,超出了本书的范围,但本章还是要作一些大致的
介绍。
      首先,大多数调制解调器都宣传为与Hayes兼容的,所以调制解调器的控制是在送给
它的命令序列(字符串)中,而不是在它自己的控制线中。许多复杂的终端程序利用这些
Hayes命令序列来与调制解调器通信;这些程序能直接控制调制解调器的许多功能。我们
所要做的就是告诉这个程序想要做什么。
      如果调制解调器利用它自己的控制线而不是字符串命令设置,那我们只需在BIOS
或硬件水平直接操纵解调器的控制线就可以控制它。 BIOS和DOS功能通常能很好地将
这些控制线从程序中隐藏起来,这意味着必须直接由UART去控制这些功能。
      并非所有串行通信都需要调制解调器。例如,在与中心计算机串行连接的大多数事务
中,这种连接可从直接用电线连接到PC机上,如果中心计算机与这台pc相隔较近的话。

143页
从技术上讲,存在外界干扰的情况下,400英尺的距离可以工作,但上面两者的距离还是
应该不超过150英尺。另外如果打印机和串行设备能够满足电缆长度的要求,那么也可以
在没有调制解调器的情况下运行它们。
    通过电话线进行通信,必须使用调制解调器。它的电话系统的波段宽度相对有限(从
300到3000Hz)。从UART输出的是一系列矩形波会变形而无法辨认。结果就没法通信。
调制解调器可以将UART输出的矩形波转变成一系列的音调,从而降至电话线的波段之
内。
      旧式的、较慢的调制解调器只能简单地转变输出:一个音调对应0,另一个对应1。新
式的、较快的调制解调器不仅利用音调,而且利用了多路复用技术,相位调整定相以及其
它的电学信号成份来通过一条线传递更大容量的信息。
                  7.5编写一个终端程序
    现在我们已经看到UART如何发挥作用以及调制解调器的工作方式,那就差不多已
有准备去设计一个简单的终端程序,但在开始之前,还应该注意许多别的因素,本节就来
讨论它们。
    通信程序能以两种方式来执行。第一种是使用查询方法:程序周期性地检查一下串行
端口,以确定收到的字符是否有用。如果有用,就可以处理它并继续下去。第二种从计算
机时间来考虑,它更为有效。它以中断为基础:用户与计算机一起工作,直到收到的字符有
用,用户就被UART中断。然后处理该字符,并返回到中断之前进行的工作上。
    本章只讨论第一种方法。但它存在一些问题。尽管用户的计算机能与其它计算机通
信,但可能以高于300bps的速度失去收到的字符。特别是当屏幕填满了而不得不滚动一
行时。这些以查询为基础的程序是用于学习,而不是通常使用的。本章一开始就提到,必
须将这里的知识与第11章的内容结合起来以产生自己的中断驱动通信程序。
7.5.1双工考虑、
    在利用串行控制的查询方法之前,必须决定一下:希望进行的通信是全双工还是半双
工。读者可能回想起这个名词,本章早些时候引入了这个词,即全双工通信,表示每个通过
通信连接送出的字符都从远处的计算机回送回来。屏幕上所看到的字符是被其它计算机
在接收、回送(或传递)的字符。这些字符滚动在两个方向上同时进行——可以将字符输送
给计算机,同时它将别的字符送回来。以下所示为简单的终端程序的全双工执行过程:
    1.如果一个字符在键盘上,那么送出它。
    2,如果字符是在串行端口,就显示它。
    3.回到第一步。
    尽管大多数计算机利用全双工通信,但仍有一些利用半双工(每次字符只向一个方向
滚动)。因为远处的计算机不回送(再传递)它接收到的字符,所以半双工终端程序只在一
小点上与全双工版本不同:
                                                                                      
144页
      1.如果一个字符在键盘上,那么送出并显示它。
      2.如果字符是在串行端口,就显示它。
      3.回到第一步。
      设中间的区别在于操纵从键盘获得字符的编码节中。下面的小节显示了它的代码怎
样受到了影响。
7.5.2控制程序:Term.C
      从概念上说,通信程序是非常简单的。列表7.1的程序,以C语言编写,它执行本章
讨论的基本概念。
    列表7.1
            /*Term.c
                Listing 7.1 of DOS Programmer'S Reference*/
            #include<Stdio.h>
            #include<COnio.h>
            #include<Stdlib.h>
            #define FALSE 0
            #define TRUE !FALSE
            main()
            {
                    void setup(void);
                    int    keybd(void);
                    void serial(void);
                    ClrSCr();
              printf("Simple Terminal Program\n\n\n");
              setup();
              while(TRUE){
                  if(!keybd())
                        exit(0);
                  serial();
              }
        }
      除了清除终端屏幕(由cls()函数完成)和设置端口(借助setup()函数)以外,这是一
个终端程序的简单应用。它可以一直重复下去,从键盘(用keybd()函数)或从串行端口
(serial()函数)来交替获得字符。
      注意该程序提供了一个方法来结束程序,就象所有负责任的程序应该做到的那样:如
果键盘处理程序返回FALSE,程序就结束了(正如Listing 7.4所示,已经编好了这样的程
序;不论什么时候按下Shift-F1,键盘功能都会返回FALSE)。
7.5.3支持函数
      让我们看看每一个支持函数。keybd()函数有两个版本:一个处理全双工操作,另一个
则处理半双工操作。可以利用任何满足需要的函数。                    
      一、初始化:setup()函数
      setup()是用户开发的第一个函数,可以用它来对串行端口初始化。正如这里所显示

145页
的,该函数利用一个简单化的、硬编码的配置:1200bps,8位,无奇偶性和1个停止位。列
表7.2显示了怎样实现这个配置。
    列表7.2
          /* Setup.c
            Listing 7.2 of DOS Programmer'S Reference*/
          #include<Stdio.h>
          #include<dOS.h>
        #define  COM1       0
          #define RS232     0x14
          #define   sETUP       0x83 /* 1200 baud,8bits,no parity,*/
                                        /* 1 stop bit*/
          void setup()
          {
                union REGS regs;
                printf("Setup the serial port\n");
                regS.h.ah=0;
                regS.x.dx=cOM1;
                regs.h.al=SETUP;
                int86(Rs232,&regs,&regs);
          }
    如上面程序所示,setup()将COM1:初始化为1200bps,并设置8位字长度无奇偶性
和1个停止位。
. setup()还利用了本章前面介绍的基本的BIOS通信端口初始化功能。
      二、一个初始化的替代函数
    setup()函数并不灵活——只能给串行端口设置一个特定的位速率和数据格式。作为
一个替代物,可以产生一个更灵活的setup()函数来处理多元初始化参数(见列表7.3)。
    列表7.3
        /*Setup2.c
        Listing 7.3 of DOS Programmer'S Reference*/
        #include <stdio.h>
        #include <dOS.h>
        #define       RS232             0x14
          #define     B300                0x40
          #define     B1200              0x80
          #define     B2400               0xa0
          #define    NOPARITY            0x00
          #define     EVEN                0x18
          #define     ODD                 0x08
          #define     WORD7               0x02
          #define    WORD8               0x03
          #define     STOP1               0x00
          #define     STOP2               0x40
          int setup(port,bpS,WOrd,parity,Stop)
146页
int port, /*COM port, 0=COM1, 1=COM2, etc.*/
bpS,/*bpS rate*/
wOrd,/*wOrd length*/
parity,/*0=off,1=EVEN,2=ODD*/
stop;/*number of stop bits*/
{
union REGS regs;
unsigned char setup;
Setup=0;
printf("Set up the serial pOrt/n");
if(port!=0&&port!=1)
return (-1);/* bad COM port*/
switch(bps){
case 300:
Setup|=B300;
break;
case 1200:
setup|=B1200;
break;
case 2400:
setup|=B2400;
break;
default:
return (-2);/*not 300/1200/2400 bps*/
}
if(word==7)
setup=WORD7;
else if(word==8)
setup=WORD8;
else
return (-3); /*not 7 or 8 bits*/
if(parity==0)
Setup|=NOPARITY;
elSe if(parity==1)
setup=EVEN;
else if (parity==2)
setup|=ODD;
else
return (-4);/*bad parity code*/
if(stop==1)
Setup=STOP1;
else if(Stop==2)
Setup=STOP2;
else
return (-5);/*not 1 or 2 bits */
regs.h.ah=0;
regs.x.dx=port;
regs.h.al=setup;
int86(RS232, &regs, &regs);/*set up the port*/
return (0);
}
setup()的这个更长版本,允许我们对每个参数进行出错检查,并能更灵活地设置参
数。表7.1中的term.c不能使用这个替代性的setup()函数。它是在一个假设的基础上
编写出来:配置值建立在程序中。如果需要就可以自己添加,但随后必须也提供一种方法,或

147页
从命令行,或者就象输入给程序的参数一样,来设置适当的初始化参数。
   如果对coM1以外的端口初始化,就必须也改变其它的支持函数——xmit()chrdy
()rch()和loOpback()——来支持其他的通信端口。以过测试并且直到对基本的串行端
口编程感到满意时,可能想使用Listing7.2中所示的Setup()的功能有限的版本然后与列
表7.3所示的版本一起试验。
三、键盘控制:keybd()函数
该函数管理键盘,通过它可以确定字符是否已由键盘输入,然后,如果是就传递这个
字符(见列表7.4)。
列表7.4
      /*Keybd.c
      Listing 7.4 of DOS Programmer'S Reference*/
      #include <stdio.h>
      #include <dos.h>
      #define     FALSE       0
      #define     TRUE       !FALSE
      #define    SF1         84
      int keybd()
      {
            char c;
            int get_Ch(void);
            void xmit(int ch);
            if((c=get_Ch())>=0){
                  /* There has been a keystrOke*/
                  if(c==0){
                  /* The first character was zero*/
                        if((c=get_ch))==SF1)
                                return (FALSE);
                        return (TRUE);
                  }
                  xmit(C);
          }..................................
          return (TRUE);
      }
    get_ch()函数的操作是keybd()函数操作的主要部分,这些将在下一节讨论。现在需
要了解的是:如果get_ch()返回0,就说明一个特殊的字符或键组合已从键盘输入了。这
种情况下,get_ch()必须再次被调用,以获得所按键的键盘扫描码。
keybd()专门用来寻找这个特殊字符值(一个0);如果找到了就寻找下一个。如果下
一个是shift-F1,字符就返回FALSE给调用程序;如果不是(例如按下另一个特殊键组合
或非ASCII字符),不会返回TRUE,并且忽略字符项。
    如果从键盘输入一个平常的ASCII值,get_ch()函数不会返回0;字符通过xmit()函
数进行发送(就象输入一样,不加改变)。
    (一)keybd()用于半双工通信
    只需添加一行就能使keybd()函数与半双工操作兼容。在包含xmit()函数的行之后

148页
紧跟着插入下面一行:
              putscrn(c);
      基本上,该函数将字符显示到屏幕上(在本章后面你会了解putscrn()的精确开发和
使用技巧)。
      (二)I/O控制:get_ch()和xmit()函数
    这两个函数支持keybd()函数。至于set_ch(),可参见列表7.5,它可以从键盘重新获
得一个字符(如果该字符有效);而xmit()则通过串行端口来传送字符。
      列表7.5
            /*Get_ch.c
            Listing 7.5 Of DoS Programmer's Reference*/
            #include <stdio.h>
          #include<dOS.h>
          #define   MASK         0x7f
          #define    ZFLAG     0x40
          int get_Ch()
          {
                  union REGS regs;
                  regs.h.ah=6;
                  regS.h.dl=0xff;
              intdos(&regs,&regs);
              if(regs.X.flags&zFLAG)
                  retUrn (-1);
              return (regs.h.al&MASK);
        }
    get_ch()利用DOS直接的控制台I/O功能(见第6章)来输入字符。读者可能回忆起
来了:这个DOs功能设置零进位标志(ZFLAG)来指示是否有一个字符。get_ch()测试
ZFLAG,看看字符是否有用。如果有用,则为AL中的字符;功能就将AL返回到调用程
序,并将最高位设置为0。最高位与屏蔽值相与就可以去除它。这种屏蔽过程能消除传输
8位字符时所带来的任何可能的问题)。
      列表7.6显示的xmit.c(),是完成keybd()必需的另一条途径。
    列表7.6
            /*Xmit.C
            Listing 7.6 of DOS Programmer'S Reference*/
            #include<dos.h>
            #define   RS232     0x14
            #define   WRITECH   1
            #define    COM1       0
            VOid xmit(ch)
                  char ch;
            {
                  union REGS regs;
                  regS.h.ah=WRITECH;
                  regs.x.dX=COM1;
                  regS.h.al=ch;
              int86(RS232,&regs,&regs);
        }

149页
  xmit()函数只是将字符写给BIOS中的串行端口处理程序。由列表7.6可见,这个
BIOS函数只需使用3个寄存器:AH包含所需的功能号(1),AL是传送的字符(由调用程
序传递给xmit()),以及DX是用于传送的通信端口(0,指定的是COM1)。有关BIOS功
用的更多信息,可参见第五部分的“BIOS功能参考手册”一节。
    四、接收字符:srial()函数
目前已了解了keybd()、get_ch()和xmit(),那么接下来就应准备检查终端程序的其
它重要部分——接收并显示任何输入字符的部分。列表7.7显示了这个函数,它的名字叫
做srial()。
列表7.7
        /* Serial.C
          Listing 7.7 of DOS Programmer'S ReferenCe*/
        #include<Stdio.h>
        #include<dOS.h>
        VOid serial()
        {
             char c;
              int chrdy(void);
              int rch(void);
              void putSCrn(int c);
              if(chrdy()){
                    c=rCh();
                    putSCrn(c);
              }
        }
    让我们考察一下serial()的完整性所必需的3个函数:chrdy()、rch()和putscrn()。
    (一)串行端口状态:chrdy()函数
    该函数检查一下输入的字符是否在串行端口上。它利用BIOS Int 14h的功能03h,能
返回通信端口状态(有关该功能的详细情况,参见“BIOS功能参考手册”一节,见第五部
分)。列表7.8显示了怎样执行chrdy()。
列表7.8
      /*Chrdy.c
          Listing 7.8 of DOS Programmer'S ReferenCe*/
      #include<Stdio.h>
      #inClude<dOS.h>
      #define   RS232       0x14
      #define  STATUS     3
      #define COm1       0
      #define   DTARDY    0x100
      int ChrdY()
      {
            union REGS regs;
            regs.h.ah=STATUS;
            regS.x.dX=COM1;

150页
          int86(RS232,&regs,&regs);
          return (regs.x.ax&DTARDY);
    }
(二)访问接收到的字符:Rch()函数
当chrdy()返回TRUE,告诉使用者程序一个字符正等着时,rch()函数就用来获得该
字符(见列表7.9)。
列表7.9
      /* Rch.c
        Listing 7.9 of DOS Programmer'S Reference*/
      #include<Stdio.h>
      #include<doS.h>
      #define    com1         0
      #define     RS232         0x14
      #define    READcH        2
      #define      maSK        0x7f
      int rCh()                 
      {
              union REGS regs;
              regs.h.ah=READCH;
              regs.x.dx=COM1;
              int86(RS232, &regs,&regs);
          return (regs.h.al&MASK); /*strip parity bit*/
      }
所输入字符的最高位已经去掉,就象在get_ch()中一样,将AL中的值与屏蔽值相与
便可去掉最高位。
    (三)屏幕显示:Putscrn()和Put_ch()函数
Serial()的最后部分是用来在屏幕上显示字符的方法。因为只有能打印的字符才能显
示出来,所以计了putscrn()(列表7.10所示)来忽略所有控制字符,只除了一个回车键
和换行码以外。
列表7.10
        /*putscrn.c
        Listing 7.10 of DOS Programmer's ReferenCe*/
      #include <stdio.h>
        #include <dOS.h>
        #define   CR    0x0d
        #define     LF    0x0a
        void putscrn(c)
              char c;
        {
              void put_ch(int);
            if(c>=' '||C==CR||c==LF) 
                put_ch(c);
        }

151页
  putscrn()利用基本的字符输出的DOs功能来调用put_ch(),将字符写到显示器上。
(有关这个功能,可参见第五部分“DOs功能参考手册”)。下面显示了put_ch()的工作方
式。
列表7.11
    /*put_ch.c
        Listing 7.11 of DOS Programmer'S Reference*/
        #include <stdio.h>
        #include <dOS.h>
        #define       CHAROUT     2
        void pUt_Ch(c)
            char c;
        {
               union REGS regs;
          regs.h.ah=CHAROUT;
            regS.h.dl=C;
            intdos(&regs,&regs);
      }
                    7.6使用term.c
    目前已经了解了term.c所必需的每个函数,输入它们并试着来使用term.c不难发
现:尽管你的计算机不能与其它计算机通信,但是如果输入的字符以快速的序列更多地到
达的话,有可能会失去一些字符。只有一行或两行的短短的信息可能就会使程序超负荷。
毛病在哪儿?
    存在许多问题:
    .因为终端程序并不为效率而编写,所以它包括许多分支调用——每一个都要占用
      时间。如果只将少数几个函数编进程序中

抱歉!评论已关闭.