2013-01-08 14:09:22 qzs_13933908434 阅读数 452
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3493 人正在学习 去看看 李强


引言

POSIX操作系统的串口编程指南》将会教你如何成功、高效和可移植的在UNIX环境或PC上对串口进行编程。每一章提供的例程都使用POSIX(Portable Standard for UNIX)终端控制函数,只需极少的修改就可运行在IRIX HP-UX SunOS Solaris Digital UNIX Linux等大多数类UNIX操作系统。操作系统间最大的不同是串口文件名和文件锁。

本指南包括以下章节和附录:

第一章:串口通信基础

第二章:配置串口

第三章:与MODEM通信

第四章:高级串口编程

附录ARS-232引脚

附录BASCII编码

 

 

第一章:串口通信基础

本章介绍串口通信,RS-232和其他多数计算机使用的标准,还有如何用C语言访问串口。

 

什么是串口通信

计算机传送信息(数据)时,每次传送一个或多个位。串口每次只传送一个位。串口通信包括多数网络设备、键盘、鼠标、MODEM和终端。

进行串口通信时,每次只发送一位,不是0就是1。你有时会听到一些专业术语,mark表示on状态,space表示off状态。

串行数据的速度通常表示为位每秒(bps)或波特率(baud),这只是表示一秒内能发送多少个数字01。在计算机诞生的初期,300baud已经被认为很快了,但是今天的计算机可以将RS-232的速度提高至430800baud!当波特率超过1000时,你通常会看到速率显示为kilo baudkbps。当波特率超过1000000时,会显示为mega baudMbps

当提到串行设备或串行接口时,他们通常称为数据通信设备(DCE)或数据终端设备(DTE),两者之间的区别很简单——每一对信号,例如发送和接收,它们是交叉的。如果要把两个DCE设备或两个DTE设备连接到一起,需要用串行交叉线或转接器将两个信号交换。

 

什么是RS-232

RS-232是 Electronic Industries Association ("EIA")定义的一个标准串行通信电气接口。RS-232事实上有三种(ABC),它们分别使用不同的电平定义onoff。使用最多的是RS-232C,它定义-3V-12V的电压作为mark位(on),定义+3V+12V的电压作为space位(off)。RS-232C标准最远可以传输8米。你通常可以使信号传的更远,只要波特率设置的够低。

除了数据输入和输出线,还有其他信号用来提供计时、状态和握手:

Table 1 - RS-232 Pin Assignments

Pin

Description

Pin

Description

1

Earth Ground

14

Secondary TXD

2

TXD- Transmitted Data

15

Transmit Clock

3

RXD- Received Data

16

Secondary RXD

4

RTS- Request To Send

17

Receiver Clock

5

CTS- Clear To Send

18

Unassigned

6

DSR- Data Set Ready

19

Secondary RTS

7

GND- Logic Ground

20

DTR- Data Terminal Ready

8

DCD- Data Carrier Detect

21

Signal Quality Detect

9

Reserved

22

Ring Detect

10

Reserved

23

Data Rate Select

11

Unassigned

24

Transmit Clock

12

Secondary DCD

25

Unassigned

13

Secondary CTS

 

 

你还可以看到另外两个串行标准——RS-422RS-573RS-422使用更低的电平和差分信号,从而将传输距离提高到300mRS-573定义了通常PC上使用的9针串口和电平。

 

信号定义

RS-232为串口通信定义了18个不同的信号。在UNIX环境中通常只使用其中的6个。

GND - Logic Group(逻辑地)

严格的说,逻辑地不是一个信号,但是没有它其他信号就不能用了。基本上,逻辑地是一个参考电平,通过它来判断电压的正负。

TXD - Transmitted Data(发送数据)

TXD信号将数据从你的工作站中发送到另一端的计算机或设备(例如MODEM)。mark电平被当做1space电平被当做0.

RXD - Received Data(接收数据)

RXD信号将数据从其他计算机或设备上发送到你的工作站中。信号解析与TXD相同。

DCD - Data Carrier Detect(数据监听)

DCD信号是从位于串口线另一端的计算机或设备接收到的。该信号上的space电平意味着计算机或设备已经处于连接状态。DCD不一定有效,有些设备是没有DCD信号的。

DTR - Data Terminal Ready

DTR信号是由你的工作站产生的,它用来告诉另一端的计算机或设备,你已经准备好(space电平)或没有准备好(mark电平)。一般情况下,在你打开工作站的串口时,DTR就被自动使能了。

CTS - Clear To Send

CTS信号也是从另一端的计算机接收到的。该信号上的space电平意味着你可以发送更多的数据。CTS通常被用来管理从你的工作站到另一端的数据流。

RTS - Request To Send

如果RTS信号被你的工作站设置为space电平,表示有很多数据已经准备好发送了。

就像CTSRTS一样,它也用来管理工作站与另一端计算机之间的数据流。多数工作站会一直把它设为space电平。

 

异步通信

为了让计算机理解传给它的串行数据,要用些方法来确定字符的开始和结束位置。这专门有异步串行数据来处理。

在异步模式下,串行数据线一直保持mark(1)状态,直到接收到一个字符。每个字符都有一个开始(start)位,在它之后紧跟着字符的每一位,然后是一个可选的parity(奇偶)位,还有一个或多个停止(stop)位。开始(start)位总是一个space0),它告诉计算机新的串行数据就要到了。数据可以在任何时候发生和接收。因此叫做异步。

Figure 1 - Asynchronous Data Transmission

可选的parity位是简单的数据位的和,它表示数据中的mark1)位的个数是奇数还是偶数。奇校验(even parity)时,如果字符中包含奇数个1parity位就是0。偶校验(odd parity)时,如果数据中有偶数个1parity为就是0。你可能还听说过space paritymark paritynoparitySpace parity就是parity位一直是0mark parity就是parity位一直是1no parity就是没有parity位。

剩余的位叫做停止位(stop bits),可以是1位,1.5位或2位,位于字符之间,停止位的值总是1。过去,停止位用来让计算机有时间处理已经接收到的字符,但是现在,它只是用来同步接收计算机和接收的字符。

异步数据格式通常设为“8N1”,“7E1”,诸如此类。它们的意思分别是“8位数据位,没有奇偶校验,1位停止位”和“7位数据位,奇校验,1位停止位”。

 

什么是全双工和半双工

全双工(Full duplex)的意思是,计算机可以同时接收和发送数据,这意味着有两个分开的数据通道(一个输入,一个输出)。

半双工的意思是,计算机不能在同一时间发生或接收数据。通常这意味着只有一个信号数据通道用来通信。这并不是有些RS-232信号没有使用,而是,通信连接使用了与RS-232不同的不支持全双工的标准。

 

流控制

经常有必要对两个串口间传送的数据流进行控制。这是由串行通信连接中的某个串口或存储介质的限制造成的。在异步通信中,通常有两种方法。

第一种方法叫做“软件”流控制,使用特殊的字符来开始(XONDC1,021 octal)或停止(XOFFDC3,023 octal)数据流。这些字符在美国信息互换标准代码(ASCII)中定义。当传输文本信息时,这些代码是有用的,它们不能用来传输没有经过特殊编程的其他类型的信息。

 

什么是Break

正常情况下,发生或接受信号会一直保持mark电平,直到一个新的字符被传送。如果一个信号降为space电平,并保持一段时间,通常是1/41/2秒,这时就存在break的情况了。

Break有事被用来重启通信连接或改变通信设备(如MODEM)的操作模式,第三章会深入讨论这些应用。

 

同步通信

与异步数据不同,同步数据是一个恒定的比特流。为了读取链接中的数据,计算机必须提供或接收一个公共的位时钟,这样发送方和接收放就同步了。

即使是同步通信,计算机也必须以某种方式标记数据的开始。常见的做法是使用数据包协议,例如 Serial Data Link Control ("SDLC")High-Speed Data Link Control ("HDLC")

每个协议都定义了明确的比特序列来表示一个数据包的开始和结束。协议还定义了一个用来表示没有数据的比特序列。这些比特序列可以使计算机确定数据包的开端。

由于同步协议不需要在每个字符都使用同步位,它在性能上比异步通信提高了至少25%,更适合远程网络,可以配置多个串行接口。

尽管同步通信有速度优势,多数RS-232硬件还是不支持它,以免增加额外的软件和硬件。

 

 

使用串口

像所有的文件一样,UNIX提供通过设备文件访问串口。要使用一个串口你只要打开相应的设备文件。

 

串口文件

每个UNIX系统下的串口都有一个或多个设备文件(在/dev文件夹下)与它关联:

Table 2 - Serial Port Device Files

System

Port 1

Port 2

IRIX®

/dev/ttyf1

/dev/ttyf2

HP-UX

/dev/tty1p0

/dev/tty2p0

Solaris®/SunOS®

/dev/ttya

/dev/ttyb

Linux®

/dev/ttyS0

/dev/ttyS1

Digital UNIX®

/dev/tty01

/dev/tty02

 

打开一个串口

串口也是一个文件,可以用open2)函数打开。有一个问题是,UNIX系统的设备文件通常是不允许普通用户使用的。解决这个问题的办法包括:改变文件的使用权限,在root用户下运行程序,或者改变你的程序的USERID,使它可以作为设备文件的所有者运行。

现在假设设备文件可以被所有用户使用。在IRIX系统下打开串口设备的代码如下:

  1. Listing 1 - Opening a serial port.  
  2. #include <stdio.h>   /* Standard input/output definitions */  
  3. #include <string.h>  /* String function definitions */  
  4. #include <unistd.h>  /* UNIX standard function definitions */  
  5. #include <fcntl.h>   /* File control definitions */  
  6. #include <errno.h>   /* Error number definitions */  
  7. #include <termios.h> /* POSIX terminal control definitions */  
  8. /* 
  9.  * 'open_port()' - Open serial port 1. 
  10.  * 
  11.  * Returns the file descriptor on success or -1 on error. 
  12.  */  
  13.   
  14.    
  15. int  open_port(void)  
  16. {  
  17.   
  18.   int fd; /* File descriptor for the port */  
  19.   
  20.   fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);  
  21.   if (fd == -1)  
  22.  {  
  23.   
  24.    /* 
  25.     * Could not open the port. 
  26.     */  
  27.   
  28.      perror("open_port: Unable to open /dev/ttyf1 - ");  
  29.   }  
  30.  else  
  31.     fcntl(fd, F_SETFL, 0);  
  32.   return (fd);  
  33.   
  34. }  


 

在其他操作系统上需要使用相应的设备文件名。

Open选项

你应该留言到了,我们打开设备文件的时候,在读写模式后面使用了两个标志:

fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);

O_NOCTTY标志告诉UNIX,这个程序不想成为“控制终端”。如果你不指定这个标志,任何输入(例如键盘中断信号)将会影响你的进程。getty1M/8程序会在启动登录进程的时候使用这个特性,正常的用户程序是不需要这个特性的。

O_NDELAY标志告诉UNIX,程序不关注DCD信号线的状态——串口的另一端是否已经打开并运行。如果不指定这个标志,你的进程将会进入睡眠状态,直到DCD信号线为space电平

 

向串口写数据

向串口写数据很容易——只需要用write(2)系统调用发生数据即可:

n = write(fd, "ATZ/r", 4);

if (n < 0)

  fputs("write() of 4 bytes failed!/n", stderr);

write函数返回发生的字节数,如果调用失败返回-1这种情况一直持续到关闭串口。

常见的错误是EIO,当MODEM或数据连接将Data Carrier Detect (DCD)信号线弄掉时会发生这个错误。

 

从串口读取数据

从串口读取数据有一点麻烦。当你在raw data模式下操作串口时,每个read2)调用都会返回串口输入缓冲区里实际接收到的字符的个数。如果没有字符是有效的,函数会阻塞(等待),直到有字符输入、定时器过期、或发生错误。可以用下面的代码使read函数立即返回:

fcntl(fd, F_SETFL, FNDELAY);

FNDELAY选项会使read函数在串口没有有效字符时返回0。要恢复正常(阻塞)属性,调用没有FNDELAY选项的fcntl函数:

fcntl(fd, F_SETFL, 0);

这个函数也可以在用O_NDELAY 选项打开串口后调用。

 

关闭串口

要关闭串口,只需要使用close系统调用:

close(fd);

关闭串口后通常会将DTR信号设为低电平,这会使多数MODEM挂起

2005-04-09 22:56:00 pangdae 阅读数 1101
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3493 人正在学习 去看看 李强

原文为Serial Programming Guide for POSIX Operating Systems(5th Edition,Michael R. Sweet,Copyright 1994-1999, All Rights Reserved.)转载本翻译请与作者联系pangdae2005@yahoo.co.uk

POSIX操作系统串口编程

介绍

POSIX操作系统串口编程将指导您如何在UNIX工作站或者PC上编制成功、有效、可移植的相关程序。每一章节均提供一些使用POSIX终端控制函数的例程,目的是使其在很少的修改下能够运行于IRIX,HP-UX,SunOS,Solaris,Digital UNIX,Linux等大多数UNIX操作系统。这些操作系统使用文件作为端口设备和锁文件。

第一章 串口通讯基础

本章介绍串口通讯,RS-232和其他一些标准,以及如何使用C程序访问串口。

什么是串口通讯?

计算机同时传送一或多位信息(数据),串口通讯包括大多数的设备,键盘,鼠标,调制解调器和终端。使用串口通讯收发数据的每一个字(如字符或字节)时,都是同时收发一位,每一位或为开或为关,有时也可以记为有(mark)或无(space)。串口通讯传输数据的速度通常表示为位每秒(bps)或者波特率(baud).它描述了一秒钟内能够传送多少个零和一。回首计算机早期的时代,300波特被认为是快速的,但是现在的计算机能够按照RS-232的速度传送430800波特,当波特率超过了1000,就使用千波特或者kbps来表示,例如9.6k等。超过了1,000,000,就使用兆波特或者Mbps来表示。串口设备或端口中,有的被标为数据传输设备DCE或数据终端设备DTE,他们之间的不同是每一对信号线,如收发,都是被交替的。当两个DTE或者DCE设备接口相连时,常使用虚拟数据线或适配器交换一对信号。

什么是RS-232?

RS-232是一种被电子工业协会EIA定义的串口通讯标准电器接口。RS-232实际上包含了三种不同的规格,每一种规格定义了不同的零和一的电压范围。最常用的类型是RS-232C,它定义了高电位范围为-3v到-12v,低电位范围为+3v到+12v。RS-232C规格说明信号最大可以传出8m。当波特足够低的时候,信号可以传送远于8m。导线中传送的包括进或出的数据,时钟,状态,握手信号。


2015-11-28 22:27:43 success_by_choice 阅读数 320
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3493 人正在学习 去看看 李强

第一章,串行通讯基础

这章介绍了串行通讯,RS-232和其它一些在计算机上广泛使用的标准,同时还有如何从C程序访问串口的内容。

什么是串行通讯?
计算机一次传输一或多位的信息(数据)。串行是指一次传输一位数据。串行通讯包括了大多数的网络设备、键盘、 麦克风,调制解调器和终端。当进行串行通讯时,你传送或接受的每个字(例如:字节或字符)是以每次一位传输的。 每位或者为on 或者 off。有时你也能听到以mark 表示 on 状态和 space 表示 off 状态。

串行数据的速度常表示为比特每秒 (“bps”) 或波特率 (“波特”)。这只是用于表示每秒能够传送的1和0的数量。追溯到计算机时代的早期,300波特被认为是很快的速度,但今天的计算机能够控制RS-232 速度高达430,800波特!当波特率超过1,000,你通常将看到速度被表示为千波特,或kbps (例如9.6k, 19.2k等等)。对于超过1,000,000的速度表示为兆波特,或者Mbps (例如1.5Mbps)。

当提及串行设备或端口的时候,他们被表明数据通讯设备 (“DCE”)或者数据终端设备 (“DTE”)。 这之间的差别很简单每对信号,例如传送和接受,都需要交换。当连接两个DTE或两个DCE接口到一起的时候,需要使用一 个串行null-MODEM 缆线或适配卡来交换信号对。

什么是 RS-232?
RS-232一标准的串行通讯电气接口,由 Electronic Industries Association (“EIA”)定 义。RS-232实际分为三部分(A, B和C),每部分都定义了不同的 on 与 off 间的电压等级。最为常用的是RS-232C,它定义mark (on) 一位电压在-3V到-12V之间和space (off)一位在电压+3V到 +12V之间。 RS-232C定义这些信号可以传输达25英尺 (8米)有效。只要波特率足够低,你通常能够传送更长的距离。

信号定义
RS-232标准定义了18种不同的串行通讯信号。当然,在UNIX环境下只有6种。

GND - Logic Ground 逻辑地
学术上而言,逻辑地不是个信号,但没有它,其它信号都不能工作。简而言之,逻辑地作为参考电压, 从而使电子器件知道某个电压是正或负。

TXD - Transmitted Data 传送数据
TXD信号传送数据从你的工作站到计算机或在另一端的设备(例如MODEM)。一个mark电压被解释为1,与 此同时一space电压被解释为0。

RXD - Received Data 接受数据
RXD信号传送数据从计算机或另一端的设备到你的工作站。类似TXD,mark和space电压相应地被解释为1和0。

DCD - Data Carrier Detect 载波侦听
DCD信号接受你的串行电缆另一头的计算机或其它设备信号。一个space电压表示计算机或设备当前连接着或在线。DCD并不总可用或存在。

DTR - Data Terminal Ready 数据终端准备好
DTR信号由你的工作站产生,告诉另一端的计算机或设备你准备好了 (一个space电压) 或者没有 (一个mark电压)。当你在工作站上打开串行接口时,DTR总是自动有效。

CTS - Clear To Send 允许发送
CTS信号接受自串行缆线另一端。一个space电压表明可以从你的工作站发送更多的串行数据。

CTS通常被用作控制从你的工作站发往另一端的串行数据。

RTS - Request To Send 要求发送
RTS信号被你的工作站设置为 space 电压表明有更多的数据等待传送。

类似 CTS,RTS帮助控制你的工作站和另一端的计算机或设备间的数据流。多数工作站始终保持这个信号为space电压。

  • 异步通讯

为使计算机理解进入它的串行数据,它需要一些途径决定那里是字符的开始和结束。本指南仅讨论异步串行数据。

在异步模式下,串行数据线保持在mark (1)状态,直到有字符传送。一位起始 start 位,字符内容的每一位,一位可选的校验位,以及一位或一位半的终止位。起始位始终是一个 space (0),从而告诉计算机新的串行数据到来。数据能够在任何时候传送或接受,所以称做异步。

可选的校验是一个简单数据位的加和,表明了数据位包含偶数或奇数个1。如果是偶校验, 当在字符中的1是偶数个数时,校验位为0。如果是奇校验,当字符中的1是奇数个时,校验位为0。你可能也听说过space校验, mark校验, 和无校验。Space校验意味着校验位始终为0,而mark校验意味着校验位始终为1。无校验意味校验不存在或不传输。

余下的被称为停止位。这可以是1, 1.5, 或2位停止位在字符之间,始终是1。停止位原先是用来给计算机时间处理前面的字符的,但现在只是用来同步计算机和接收字符。

异步数据格式通常表示为”8N1”, “7E1”,诸如此类。它们相应地表示”8位数据,无校验,1位停止”和”7位数据,偶校验,1位停止”。

  • 什么是全双工与半双工?

全双工 表明计算机能够同时接受和发送数据 - 有两个分离的数据通道(一个输入,一个输出)。

半双工 表明计算机不能同时接受和发送数据。通常这意味着只有一个数据通道用来通讯。这并不意味着一些 RS-232信号没有使用。而通常是表明通讯链路使用了不同于RS-232的标准,而它们不支持全双工操作。

  • 流控制

通常在两个串口间传送数据时,需要控制数据流。这可能是由于通讯连接媒介、一个串行接口、或某些存储介质的限制。两种方法通常用于异步数据。

第一个方法叫作”软件”流控制,使用特殊字符开始 (XON or DC1, 021 octal) 或停止 (XOFF or DC3, 023 octal) 数据流。这些字符由 American Standard Code for Information Interchange (“ASCII”)定义。虽然这些代码在传送文本信息时十分有用,但没有特殊的编程手段,在传输其它类型信息的时候就不能使用了。

第二个方法叫做”硬件”流控制,使用RS-232 CTS 和RTS信号代替特殊字符。当准备好接受更多的数据后,接受方设置CTS为space电压;而设置为mark电压,表示没有准备好。同样的,准备好发送更 多数据后,发送方将RTS设置为 space电压。因为硬件流控制使用独立的信号,所以同样情况下,它要比需要传送更多位信息的软件流控制快许多。 CTS/RTS流控制并不被所有的硬件或操作系统支持。

  • 什么是中断?

N正常的一个接受或发送数据信号保持在mark电压知道一个新的字符被传送。如果信道掉到space电压一段较长时间,通常是1/4到1/2秒,就说出现了个中断。

中断有时是用于重置通讯线或改变通讯硬件的模式,例如MODEM.第三章,MODEM通讯更为详细地讨论了这些。

  • 同步通讯

不同于异步数据,同步数据表现为连续数据流。为在线读取数据,计算机必须提供或接受一比特同步时钟,从而 使接受和发送同步。

即便是同步情况下,计算机仍然必须标明数据的开始。最通常的做法是使用数据包协议,例如 串行 Data Link Control(“SDLC”)或 High-Speed Data Link Control (“HDLC”)。

每个协议都定义了表示数据包开始和结束的一定比特序列。且都定义了没有数据时的比特序列。这些比特序列使 得计算机能够看到数据包的起始。

因为同步协议不使用每字符的同步位,它们通常提供比异步通讯至少高25%的效率,适合于在两个以上的串行接口 环境下,远距离网络操作与配置。

尽管同步通讯的速度优势,多数RS-232硬件仍不支持,主要是由于额外的硬件和软件需求。

  • 访问串行端口

象所有的设备一样,UNIX提供设备文件以访问端口。要访问一个串行端口,你只要简单地打开相应的设备文件。

  • 串行端口文件

每个串行端口在UNIX系统上有一个或多个设备文件 (文件在/dev目录下)相关联。

打开串行端口
由于串行端口是一个文件, open( ) 函数正是用来访问它的。存在的阻碍是UNIX下设备文件经常不允许普通用户访问。 工作的范围包括改变有疑问的文件访问许可,以超级用户(root)运行你的程序, 或使你的程序设置userid,从而它能以设备文件的拥有者运行(由于显著的安全性问题,不建议用…)

现在我们假设文件能够被所有的用户访问。 列表1 表示了在运行Linux的PC上打开串行端口一的例子。

 #include <stdio.h>   /* Standard input/output definitions */
    #include <string.h>  /* String function definitions */
    #include <unistd.h>  /* UNIX standard function definitions */
    #include <fcntl.h>   /* File control definitions */
    #include <errno.h>   /* Error number definitions */
    #include <termios.h> /* POSIX terminal control definitions */

    /*
     * 'open_port()' - Open Serial port 1.
     *
     * Returns the file descriptor on success or -1 on error.
     */

    int
    open_port(void)
    {
      int fd; /* File descriptor for the port */


      fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
      if (fd == -1)
      {
       /*
    * Could not open the 端口.
    */

    perror("open_端口: Unable to open /dev/ttyS0 - ");
      }
      else
    fcntl(fd, F_SETFL, 0);

       return (fd);
    }

其它系统可能需要不同的设备文件名,然而代码是一样的。

Open 选项

你将注意到我们打开设备文件时,除了读写模式外,还使用了两个其它的标志:

fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);

O_NOCTTY 标志告诉UNIX,该程序不想成为那个端口的”控制终端”。如果你不指明这个,任何输入(例如键盘中断信号等诸如此类)都会影响你的进程。象getty(1M/8) 之类的程序在启动登录进程时,设置这一特性,但通常用户的程序不想有这种行为。

O_NDELAY 标志告诉UNIX,该程序不关心DCD信号的输入状态 - 即无论另一端端口是否启用和运行。如果你不指明这个标志,你的进程将休眠,直到DCD信号线是space电压。

写数据到端口

写数据到端口是简单的 – 只需要使用 write( ) 系统呼叫去传送数据:

n = write(fd, "ATZ/r", 4);
if (n < 0)
  fputs("write() of 4 bytes failed!/n", stderr);

write 函数返回发送的字节号 – 或1如果发现了任何错误。通常唯一的错误你可能运行进入是 EIO , 即当MODEM或数据链路丢失Carrier Detect (DCD) 线。该条件允许你保持直到端口关闭。

从端口读数据端口

从端口读取数据有一点小骗术。当你以行数据模式操作端口,每个 read( ) 系统调用都会返回, 而不论多少字符实际存在于串行输入缓冲中。 如果没有字符存在,调用将阻塞(等待)直到有字符进入,或超时,或错误出现。read方法能够用以下方法获得立即的返回:

fcntl(fd, F_SETFL, FNDELAY);

当端口没有接受到字符时,FNDELAY 项使得 read 函数返回0。要实现标准的(阻塞)行为,调用 fcntl() 而不带FNDELAY 项:

fcntl(fd, F_SETFL, 0);

这同样可以在带O_NDELAY项打开串行端口后使用。

关闭串行端口
要关闭串行端口,只要使用 close 系统调用:

close(fd);

关闭串行端口通常同时将DTR信号设置为低,这将使多数MODEM挂机。

第二章,设置串行端口

本章讨论如何通过POSIX termios接口,在C语言中设置端口。

POSIX终端接口
多数系统支持通过POSIX终端 (串行) 接口来改变,例如波特率,字符尺寸,等参数。你需要做的第一件事情是引入文件 <termios.h>;它定义了终端控制结构和POSIX控制函数。

两个最重要的POSIX函数函数是 tcgetattr(3) 和 tcsetattr(3)。这些获得和设定相关的终端特性, 你要提供一个termios 结构的指针包含所有可用的串行设置项:

表 3 - Termios结构成员
成员 描述
c_cflag 控制项
c_lflag 线路项
c_iflag 输入项
c_oflag 输出项
c_cc 控制字符
c_ispeed 输入波特(新接口)
c_ospeed 输出波特(新接口)

  • 控制项

c_cflag成员控制波特率、数据位、校验、停止位和硬件流控制。所有支持的设置都有常数对应。
表 4 - c_cflag成员对应的常数
常数 描述
CBAUD 波特率掩码
B0 0 波特 (drop DTR)
B50 50 波特
B75 75 波特
B110 110 波特
B134 134.5 波特
B150 150 波特
B200 200 波特
B300 300 波特
B600 600 波特
B1200 1200 波特
B1800 1800 波特
B2400 2400 波特
B4800 4800 波特
B9600 9600 波特
B19200 19200 波特
B38400 38400 波特
B57600 57,600 波特
B76800 76,800 波特
B115200 115,200 波特
EXTA 外部速率时钟
EXTB 外部速率时钟
CSIZE 位数据位掩码
CS5 5位数据位
CS6 6位数据位
CS7 7位数据位
CS8 8位数据位
CSTOPB 2位停止位(不指明是1位)
CREAD 接收有效
PARENB 校验位有效
PARODD 使用奇校验代替偶校验
HUPCL 最后关闭后,挂起(降低 DTR)
CLOCAL 本地线 - 不改变端口的”拥有者”
LOBLK 阻塞方式工作控制输出
CNEW_RTSCTS
CRTSCTS 采用硬件流控制(在所有的平台上都不支持)
c_cflag成员包含两个选项必须总是有效,CLOCAL 和 CREAD。这些将保证你的程序不会成为端口的所有者, 从而妨碍控制工作和挂起信号,并使串行接口驱动将读取进入的数据。

波特率常数 (CBAUD, B9600, 等等。)是用来老式接口,那些缺少 c_ispeed 和 c_ospeed 成员的。参考下一章关于用于波特率设定的POSIX函数。

决不 直接初始化 c_cflag (或其它)成员;你应该总是用 AND, OR和NOT比特操作来设定或清除成员中的比特位。 不同的操作系统版本(甚至是补丁) 能也确实使用不同方式处理比特位,所以使用比特操作符,能够使你避免在使用一个新的串行设 备要用的比特位时遭到残败。

设置波特率
波特率存储于不同的位置依赖于具体系统。旧版本接口存储波特率在 c_cflag成员中,使用一个表4中的波特率常数, 而新的实现提供了c_ispeed和 c_ospeed 成员来包含波特率数值。

cfsetospeed( ) 和 cfsetispeed( ) 函数用于提供波特率设置到 termios 结构中,而不论其下的操作系统系统接口如何。

 struct termios options;

    /*
     * Get the current options for the port...
     */

    tcgetattr(fd, &options);

    /*
     * Set the 波特 rates to 19200...
     */

    cfsetispeed(&options, B19200);
    cfsetospeed(&options, B19200);

    /*
     * Enable the receiver and set local mode...
     */

    options.c_cflag |= (CLOCAL | CREAD);

    /*
     * Set the new options for the port...
     */

    tcsetattr(fd, TCSANOW, &options);

tcgetattr( ) 函数用当前的串行端口设置来填充你的termios结构。在我们设置了波特率、本地模式和串行数据接收, 我们使用tcsetattr( )选定新的设置。TCSANOW常数表明所有的改变立即生效而不等待发送或接受的数据结束。还 有其它的参数常数用来等待输出和输入结束或刷新输入输出缓冲。

多数系统不支持端口有不同的输入与输出速率,所以确定设置为相同的值以获得最大的可移植性。

表 5 - tcsetattr 常数
常数 描述
TCSANOW 立即改变,不等待数据结束
TCSADRAIN 等待直到所有的都传完
TCSAFLUSH 刷新输入输出缓冲,然后改变

  • 设置字符尺寸

不同于波特率,没有简便的函数用于设定字符尺寸。取而代之的是你需要做一点比特掩码的操作来实现。字符的大小是以位方 式设定:

 options.c_cflag &= ~CSIZE; /* Mask the character size bits */
    options.c_cflag |= CS8;= ~PARENB
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;

偶校验 (7E1):
options.c_cflag |= PARENB
options.c_cflag &= ~PARODD
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;

奇校验 (7O1):
options.c_cflag |= PARENB
options.c_cflag |= PARODD
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;= ~PARENB
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;

设置硬件流控制
某些版本的UNIX支持硬件流控制,采用的是CTS (Clear To Send) 和RTS (Request To Send) 信号线。如果CNEW_RTSCTS 或 CRTSCTS 常数在你的系统上有定义,硬件流控制可能得到支持。以下用于选择硬件流控制:

options.c_cflag |= CNEW_RTSCTS;    /* Also called CRTSCTS */

类似的,关闭硬件流控制:

options.c_cflag &= ~CNEW_RTSCTS;
  • 本地项

本地模式成员 c_lflag 控制输入字符如何被串行驱动使用。一般你设置 c_lflag 成员为 canonical 或 raw 输入方式。
表 6 - c_lflag成员常数
常数 描述
ISIG 使SIGINTR, SIGSUSP, SIGDSUSP, SIGQUIT等信号作用
ICANON 设定规范canonical(或行式raw)
XCASE Map uppercase /lowercase (废除)
ECHO 回显输入字符
ECHOE 回显擦除字符 BS-SP-BS
ECHOK 在删除字符后,回显 NL
ECHONL 回显 NL
NOFLSH 不在中断或退出字符后刷新输入缓冲
IEXTEN
扩展函数作用

ECHOCTL 以^char方式回显控制字符和以~?方式显示删除字符
ECHOPRT 回显提示有删除字符
ECHOKE BS-SP-BS整行,在有行删除时
FLUSHO 刷新输出
PENDIN 在下次读或输入时,将未决字符重打
TOSTOP 传送SIGTTOU信号作为背景

  • 选择规范方式输入

规范方式输入是行式操作的。输入字符存放于一用户可编辑的缓冲中,直到接受到CR (回车)或 LF (进行)。

当选择这个模式时,你通常选择ICANON, ECHO, and ECHOE options:

options.c_lflag |= (ICANON | ECHO | ECHOE);
  • 选择行方式输入

行式输入是不经处理的。输入字符在接受到后就直接传递,保持原样不处理。一般当你用行方式输入时,你需要去除 ICANON, ECHO, ECHOE, and ISIG 项:

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
  • 关于回显的说明

决不 使用输入回显 (ECHO, ECHOE) ,当传送指令到一MODEM或其它计算机时,因为如果对方也回显字符的话,你就造成了在两个串口间反馈死循环!

  • 输入项

输入模式成员 c_iflag 控制任何在端口接收字符的输入处理。类似 c_cflag 域,最终存储于 c_iflag 比特位符OR操作于期望项。

表 7 - c_iflag成员常数
常数 描述
INPCK 校验有效
IGNPAR 忽略校验错误
PARMRK 标记校验错误
ISTRIP 剥离校验位
IXON 软件流控制作用 (输出)
IXOFF 软件流控制作用 (输入)
IXANY 允许任何字符再次启动流
IGNBRK 忽略任何中断条件
BRKINT 传送SIGINT,当检测到中断
INLCR 映射NL到CR
IGNCR 忽略CR
ICRNL 映射CR到NL
IUCLC 映射大写到小写
IMAXBEL 在输入线上回显BEL过长

  • 设置输入校验项

当你在c_cflag成员中选择了校验作用(PARENB),你就应该使输入校验检查作用。输入校验检查的相关常数是INPCK, IGNPAR, PARMRK , 和 ISTRIP。一般你将选择 INPCK 和 ISTRIP 来检查和剥离校验位:

options.c_iflag |= (INPCK | ISTRIP);

IGNPAR 是个稍微有点危险的选项,告诉串行驱动忽略校验错误和传递输入数据,只当没有错误发生。在测试通讯链路质 量时有用,但一般没有实际使用的理由。

PARMRK 使用特殊字符将输入流中校验错误标记’marked’。如果 IGNPAR 作用,在每个有校验错误的字符前插入一个NUL字符 (000 八进制)。 除此之外,伴随错误字符发送DEL (177 八进制) 和 NUL 字符。

设置软件流控制
软件流控制通过设置 IXON, IXOFF 和 IXANY 常数:

options.c_iflag |= (IXON | IXOFF | IXANY);

取消软件流控制,只要简单地屏蔽那些数据位:

options.c_iflag &= ~(IXON | IXOFF | IXANY);

XON (开始传送数据) XOFF (停止数据传送) 字符的定义在 c_cc 数组中定义,描述如下。

  • 输出项

c_oflag 成员包含输出过滤项。类似输入模式,你能选择处理或行式数据输出。

表 8 - c_oflag成员常数
常数 描述
OPOST 输出后处理 (不设置 = 行式输出)
OLCUC 映射小写到大写
ONLCR 映射NL到CR-NL
OCRNL 映射CR到NL
NOCR 没有CR输出在列0
ONLRET NL实现CR功能
OFILL 对于延时用填充字符
OFDEL 填充字符DEL
NLDLY 在行之间,需要掩蔽延时
NL0 NL没有延时
NL1 在换行后,新的输出前延时100ms
CRDLY 回车到左边,需要掩蔽的延时
CR0 CR没有延时
CR1 CR后的延时,依赖于当前的列位置
CR2 CR后延时100ms
CR3 CR后延时150ms
TABDLY TAB后需要延时
TAB0 TAB后没有延时
TAB1 TAB后的延时根据当前的列位置
TAB2 TAB后延时100ms
TAB3 TAB扩展为空格
BSDLY BS后需要延时掩蔽
BS0 BS后无延时
BS1 BS后延时50ms
VTDLY VT后需要延时掩蔽
VT0 VT后无延时
VT1 VT后延时2秒
FFDLY FF后需要延时掩蔽
FF0 FF后无延时
FF1 FF后延时2秒

选择输出处理

输出处理是通过选择c_oflag成员中的OPOST :

options.c_oflag |= OPOST;

在众多不同的项目中,你可能只会用到ONLCR 项,该项映射换行为 CR-LF 对。余下的输出项主要都是出于历史原因,要追溯到打印机和终端还不能和串行数据流保持一致的时候!

选择行式输出

行式输出是通过重置c_oflag成员中的OPOST 项来实现的。

options.c_oflag &= ~OPOST;

当OPOST 设为不可用十,所有其它在c_oflag中的比特项都被忽略。

  • 控制字符

c_cc 字符数组包含了控制字符和超时参数。数组的每个元素都有常数定义。

表 9 - c_cc成员中的控制字符
常数 描述 Key
VINTR 中断 CTRL-C
VQUIT 退出 CTRL-Z
VERASE 擦除 空格(BS)
VKILL 删行 CTRL-U
VEOF 文件结尾 CTRL-D
VEOL 行结尾 回车(CR)
VEOL2 第二行结尾 进行(LF)
VMIN 读取的最小字符数 -
VTIME 等待时间(数十秒) -

设置软件流控制字符

c_cc数组的VSTART 和 VSTOP 元素包含用于软件流控制的字符。通常它们设定为DC1 (021 八进制) 和 DC3 (023 八进制),代表ASCII 标准的XON和XOFF字符。

设置读取超时

UNIX串行接口驱动提供确定的字符和包超时设定。c_cc 数组的两个元素用于该目的:VMIN 和 VTIME。 对于通过open或 fcntl的文件,在规范模式下canonical或当NDELAY项被设置,忽略超时设置。

VMIN 指明了最小的读取字符。如果它被设置为0,VTIME的值用于指明等待读取每个字符的时间。注意这不是意味着 read 调用读取N个字节需要等待N个字符进入。而是,超时将仅对第一个字符作用,并且read 调用将立即返回字符的个数 (最大为你要求的数目)。

如果 VMIN 非0,VTIME 指明了等待第一个字符到来的时间。如果在指定时间内读到一个字符,任何读操作将阻塞 (等待)直到所有VMIN 规定的字符数读到。那就是说,一旦第一个字符读到,串行接口驱动期望接收一整个包的字符 (VMIN 规定字符数)。如果在规定时间内没有字符被读取,read调用返回0。该方法允许你告诉串行驱动,你准确需要 N 个字节,以及 read 调用将只返回0或N字节。然而,超时只对第一个字符作用,所以如果由于某些原因驱动丢失了N个字 节包中的某个字符,read 调用将永远阻塞等待字符输入。

VTIME 指明等待输入的时间是以十分之一秒为单位。如果VTIME 设置为0 (缺省),读操作将不确定地阻塞(等待) ,直到用open or fcntl在端口上设置NDELAY 项。

第三章,MODEM通讯

本章包括了拨号调制/解调器(MODEM)的通讯基础知识。供MODEM使用的例子采用的是事实上的标准AT指令。

什么是MODEM?
MODEM是调制串行数据为频率信号,从而可以在模拟数据线路上传输,例如电话线或TV电缆接线。一个标准的电话MODEM转换串行数据 为语音,从而能够在电话线上传送,由于速度和复杂的转换机制,这些语音听起来象高声尖叫。

今天的电话MODEM能够在电话线上以每秒53,000比特的速率传输数据,或者说是53kbps。此外,多数MODEM使用数据压缩技术,从而可 将某些类型的比特速率提高到100kbps以上。

与MODEM通讯
和MODEM通讯的第一步是打开端口并设置为行方式输入,就象列表 3中所示。

列表3 - 设置端口为行式输入。

int            fd;
struct termios options;
/* open the port */
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
fcntl(fd, F_SETFL, 0);

/* get the current options */
tcgetattr(fd, &options);

/* set raw input, 1 second timeout */
options.c_cflag     |= (CLOCAL | CREAD);
options.c_lflag     &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_oflag     &= ~OPOST;
options.c_cc[VMIN]  = 0;
options.c_cc[VTIME] = 10;

/* set the options */
tcsetattr(fd, TCSANOW, &options);

下一步是建立与MODEM的通讯。最好的办法是发送”AT”指令到MODEM。这还允许智能MODEM检测你使用的波特率。 当MODEM正确连接和加电,它将返回”OK”。

列表 4 - 初试化MODEM.

int                  /* O - 0 = MODEM ok, -1 = MODEM bad */
init_modem(int fd)   /* I - Serial port file */
{
  char buffer[255];  /* Input buffer */
  char *bufptr;      /* Current char in buffer */
  int  nbytes;       /* Number of bytes read */
  int  tries;        /* Number of tries so far */

  for (tries = 0; tries < 3; tries ++)
  {
   /* send an AT command followed by a CR */
if (write(fd, "AT/r", 3) < 3)
continue;

   /* read characters into our string buffer until we get a CR or NL */
bufptr = buffer;
while ((nbytes = read(fd, bufptr, buffer + sizeof(buffer) - bufptr - 1)) > 0)
{
   bufptr += nbytes;
   if (bufptr[-1] == '/n' || bufptr[-1] == '/r')
        break;
}

   /* nul terminate the string and see if we got an OK response */
*bufptr = '/0';

if (strncmp(buffer, "OK", 2) == 0)
return (0);
  }

  return (-1);
}

标准MODEM指令
多数MODEM支持”AT”指令,这么称呼是因为每个指令都以”AT”字符开头。每个指令都以”AT”字符开头,跟以特殊指令和回车结尾(CR, 015 octal)。处理指令之后,MODEM将根据指令返回若干文本信息之一。

ATD - Dial A Number拨一个号码
ATD指令用户拨一个指定号码。除数字外,你还能添加少量内容,包括指明是语音(“T”)或脉冲(“P”)拨号,等待一秒 (“,”)和等待拨号音(“W”):

ATDT 555-1212
ATDT 18008008008W1234,1,1234
ATD T555-1212WP1234

MODEM将返回以下信息之一:

NO DIALTONE
BUSY
NO CARRIER
CONNECT
CONNECT baud

ATH - Hang Up挂起
ATH指令导致MODEM挂起。因为MODEM是处于”命令”模式,你通常是不需要在普通通话中这么做。

DTR信号下降,将导致多数MODEM挂起;你可以通过将波特率设置为0至少一秒来实现。降低DTR信号同时也将MODEM返回命令模式。

在挂起成功后,MODEM将返回”NO CARRIER”。如果MODEM依然连接,将传回”CONNECT”或”CONNECT BAUD”。

ATZ - Reset MODEM 重置MODEM
ATZ指令重置MODEM。MODEM将返回字符”OK”。
MODEM常见通讯问题
首先和最为重要的,不要忘记关闭输入回显。 输入回显将导致MODEM和计算机间形成反馈回路。

其次,在传输指令到MODEM时,必须以回车(CR)结尾,而不是新行(NL)。C对于CR的字符常数是”/r”。

最后,当操作MODEM时,请确认你使用的波特率是MODEM所支持的。尽管许多MODEM能够自动检测波特率,一些限制 (通常在老式MODEM上是19.2kbps)你必须注意。

第四章,高级串行编程

本章包括了高级串行编程技术,使用 ioctl(2) 和 select(2) 系统调用。

串行端口 IOCTLs
在第二章,串行端口设置中,我们使用 tcgetattr 和 tcsetattr 函数来设置串行端口。在UNIX下这些函数使用ioctl(2) 系统呼叫来实现其功能。

ioctl 系统调用使用三个参数:

int ioctl(int fd, int request, ...);

fd 参数指明串行端口文件描述符。 request 参数是一个常数定义于<termios.h> 头文件中,通常是列在表 10 中的某个常数。

表 10 - IOCTL requests 串行端口
Request 描述 POSIX Function
TCGETS 获得串行端口设置。 tcgetattr
TCSETS 立即设置串行端口。 tcsetattr(fd, TCSANOW, &options)
TCSETSF 刷新输入输出缓冲后,设置串行端口。 tcsetattr(fd, TCSAFLUSH, &options)
TCSETSW 允许输入输出缓冲排空后,设置串行端口。 tcsetattr(fd, TCSADRAIN, &options)
TCSBRK 一个给定时间的中断 tcsendbreak, tcdrain
TCXONC 软件流控制 tcflow
TCFLSH 刷新输入输出缓冲 tcflush
TIOCMGET 返回”MODEM”状态位。 None
TIOCMSET 设定”MODEM”状态位。 None
FIONREAD 返回输入缓冲中的比特数目 None
获得控制信号
TIOCMGET ioctl 获得当前”MODEM”状态位,包含所有RS-232信号线,除了RXD和TXD, 列在表 11。

要获得状态位,调用 ioctl 带一个整数指针保存数据位,就象列表5 .

列表 5 - 获得MODEM状态

#include <unistd.h>
#include <termios.h>

int fd;
int status;

ioctl(fd, TIOCMGET, &status);

表 11 - 控制常数
常数 描述
TIOCM_LE DSR (data set ready/line enable)
TIOCM_DTR DTR (data terminal ready)
TIOCM_RTS RTS (request to send)
TIOCM_ST Secondary TXD (transmit)
TIOCM_SR Secondary RXD (receive)
TIOCM_CTS CTS (clear to send)
TIOCM_CAR DCD (data carrier detect)
TIOCM_CD Synonym for TIOCM_CAR
TIOCM_RNG RNG (ring)
TIOCM_RI Synonym for TIOCM_RNG
TIOCM_DSR DSR (data set ready)
设定控制信号
TIOCMSET ioctl 设置”MODEM”状态,定义如上。要降低DTR信号,你可以使用列表 6中的代码。

列表 6 - 使用TIOCMSET ioctl 降低DTR信号。

 #include <unistd.h>
    #include <termios.h>

    int fd;
    int status;

    ioctl(fd, TIOCMGET, &status);

    status &= ~TIOCM_DTR;

    ioctl(fd, TIOCMSET, &status);

能够设定的数位依赖于操作系统,驱动和使用模式。查询你的操作系统文档以获得信息。

获得存在的比特字节个数
FIONREAD ioctl 获得串行端口输入缓冲的字节个数。你传递TIOCMGET的同时用一个整数指针获得字节个数,例如列表 7.

列表 7 - 获得输入缓冲的字节数目。

#include <unistd.h>
#include <termios.h>

int fd;
int bytes;

ioctl(fd, FIONREAD, &bytes);

这在用串行端口获得数据时很有用,使你的程序能够在尝试读取前确定字节个数。

从串行端口选择输入
虽然简单应用程序能够获得或等待串行端口的数据,多数应用程序并不简单并需要从多个来源获得输入。

UNIX通过select(2) 系统调用提供这一能力。系统调用允许你的程序检查输入,输出,或在一或多个文件描述符上的错误条件。 文件描述符能够指定到串行端口,普通文件,其它设备,管道或端口。你能检查获得的未完输入,等待输入结束,或者在指定时间后超时, 使用select系统呼叫非常灵活。

多数GUI工具提供了一个界面访问select;我们将在本章稍后讨论X Intrinsics (“Xt”)库。

SELECT系统呼叫
select 系统呼叫能够接受5个参数:

int select(int max_fd, fd_set *input, fd_set *output, fd_set *error,
           struct timeval *timeout);

max_fd 参数指定了最高数字的文件描述符,在input, output, 和 error 集合。input, output, 和error 参数指明了用于未处理的文件描述符设置的输入,输出或错误条件;指定 NULL来禁止检测相关条件。这些设置用以下三个宏初始化:

FD_ZERO(fd_set);
FD_SET(fd, fd_set);
FD_CLR(fd, fd_set);

FD_ZERO宏完全清除设定。FD_SET 和 FD_CLR 宏从设置中添加和移除相关的文件描述符。

超时 timeout 参数指明了超时设置由秒(timeout.tv_sec)和毫秒(timeout.tv_usec )组成。从一或多个文件描述符来源获得数据,设置秒和毫秒为零。无限等待,指定NULL到超时指针。

select 系统调用返回未处理的文件描述符的个数,或者-1代表一个错误。

使用SELECT系统调用
假设我们从一个串行端口和一个套接字获得数据。我们从每个文件描述符检查输入,但同时想在10秒内没有输入时提醒用户。要实现这, 我们需要使用select系统调用,如列表 8.

列表 8 - 使用SELECT处理一个以上的输入来源。

#include <unistd.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/select.h>

    int            n;
    int            socket;
    int            fd;
    int            max_fd;
    fd_set         input;
    struct timeval timeout;

    /* Initialize the input set */
    FD_ZERO(input);
    FD_SET(fd, input);
    FD_SET(socket, input);

    max_fd = (socket >

    /* Initialize the timeout structure */
    timeout.tv_sec  = 10;
    timeout.tv_usec = 0;

    /* Do the select */
    n = select(max_fd, &input, NULL, NULL, &timeout);

    /* See if there was an error */
    if (n < 0)
      perror("select failed");
    else if (n == 0)
      puts("TIMEOUT");
    else
    {
      /* We have input */
      if (FD_ISSET(fd, input))
       process_fd();
      if (FD_ISSET(socket, input))
       process_socket();
    }

你注意到我们首先检查select 系统调用的返回值。0和-1值导致警告和错误信息。大于0的值表明有一或多个未处理的值。

要确定哪个文件描述符有没处理的输入,我们使用FD_ISSET 宏来测试每个文件描述符的输入设置。如果文件描述符设定了,且设定的条件出现(这里就是有输入没处理),我们就需要做点什么。

使用SELECT配合X Intrinsics库
X Intrinsics库提供了一个 select 系统调用的接口,通过 XtAppAddInput(3x) 和 XtAppRemoveInput(3x) 函数:

int XtAppAddInput(XtAppContext context, int fd, int mask,
XtInputProc proc, XtPointer data);
void XtAppRemoveInput(XtAppContext context, int input);
select 系统调用是内部使用,用于实现来自X server的超时,工作过程和输入检查。这些函数能够与任何基于Xt的工具一同使用,包括Xaw, Lesstif, and Motif。

XtAppAddInput的proc 参数指明了当在文件描述符上选定的条件出现时(例如,有输入),调用的函数。类似前面的例子,你可以用 process_fd 或 process_socket 函数。

因为Xt限制了你访问 select 系统调用,你需要用其它方法实现超时功能,可能的方法有通过XtAppAddTimeout(3x)。

2011-06-27 22:39:00 tyl_1120 阅读数 536
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3493 人正在学习 去看看 李强

简介

posix 操作系统串口编程指南将教你如何在您的UNIX ®工作站或PC机成功,高效, 便携的编写串口程序。每章提供的编程示例使用的POSIX (可移植的UNIX标准)终端控制功能,并只要经过极少数的修改就可以运行在IRIX ® , HP - UX, SunOS ® ,以及Solaris ® ,Digital UNIX ® , Linux® ,和大多数其他的类UNIX操作系统。操作系统的最大的不同是用于串口设备和锁定的文件的文件名。

这个指南有下面几章和附录组成

第一章 串口编程基础

第二章 配置串口

第三章 调制解调器通信

第四章 高级串口编程

附录A RS-232引脚

附录B ASCII控制流

----------------------------------------------------------------------

第一章 串口编程基础

本章介绍串口通信,RS232和用语大多数计算机的其他标准以及如何用c程序访问串行口。

什么是串口通信?

计算机可以每次传送1bit或者nbit信息(数据),串口指每次只传送1bit数据。串口通信包括多种网络设备:键盘,鼠标,猫,和终端。

当用串口传送一个字(即字符或字节)数据,你接收和发送时每次传送一个bit.每个bit是1或者0.

串口的数据传输速率大都是以bit/s(bps)或者波特率(baud)表示1和0在每秒内传输的个数。回到计算机开始的时代,300baud被认为是很快的,但现今电脑应用RS232速率可以达到43800baud(波特)。当波特率超过1000,你将经常看见速率为千boud,或者kbps(例如9.6k,19.2k等).当速度在1,000,000以上表示为megabaud或者Mbps(例如1.5Mbps)。

当谈到串口设备和端口,他们被称为数据通信设备(DCE)或者数据终端设备(DTE)。他们之间的区别很简单-每对传输信号和接收,是交换。当两个DTE或者两个DCE设备连接在一起,一个无猫串口电缆或者适配器被用作交换信号对。

什么是 RS-232?

RS-232是电气工业组织(EIA)制定的为串口通信的电气化接口标准。RS-232实际上分三个等级(A,B,C),他们每个有不同的高低电压标准。填充应用最广泛的是RS-232C,他定义逻辑1的电压在-3V到-12V,逻辑0的电压在+3V到+12V。RS-232规格上讲这些信号在25英尺后就无法使用。你通常可以通过降低波特率将数据传得更远。

除了电缆进出数据,还有其他时间,状态,握手:

表1 - RS-232 引脚 管脚 描述

1 地

2 TXD-传输数据

3 RXD-接收数据

4 RTS-要求发送

5 CTS-清除待发

6 DSR-数据设置完成

7 GND-逻辑地

8 DCD-数据载波检测

9 预留

10 预留

11 未分配

12 备用DCD

13 备用CTS

14 备用 TXD

15 传输时钟

16 备用 RXD

17 接收时钟

18 未分配

19 备用 RTS

20 DTR-数据终端准备

21 信号质量检查

22 环路检查

23 数据速率选择

24 发送时钟

25 未分配

有两种接口标准RS-422和RS-422。RS-422用更低的电压和不同信号允许线缆长度上限为1000英尺(300米)。RS-574定义9引脚的串口连接和电压。

信号定义

RS-232标准定义串口中18中不同的信号。在这些中仅有6个是在UNIX环境中可用。

GND - 逻辑地

逻辑地不是信号,办事没有它却没有办法操作信号。基本上,逻辑地是一个参考电压已让电子管知道那些事正的和负的。

TXD- 传输数据

TXD信号载波数据从你的工作站到另一端的计算机或者设备(如猫)。一个高电平表示1,一个空电平表示0.

RXD- 接收数据

RXD载波信号将电脑或设备传输到工作站。像TXD,高低电平分别表示1和0.

DCD-数据载波检测

DCD信号接收来自电脑或者其他串口电缆。一个低电平在信号线上表示设备和电脑正在连接。DCD通常没有用。

DTR- 数据终端准备

DTR信号是工作站产生兵告诉计算机或者另一端的设备你正在准备(空电平)或者没有准备(高电平)。DTR能够自动运行当你打开在工作站上的串口。

CTS- 清除发送

CTS信号从其他串口电缆接收数据。

空电平表示已经准备从工作站发送串口数据。CTS通常用来控制从工作站到终端的串行数据流。

RTS- 请求发送

TRS信号设置低电平被工作站用来表示更多的数据准备发送。

像CTS,RTS辅助控制从工作站到计算机和另一端的串口设备的数据流。大部分工作站始终设置这种信号为低电平。

异步通信

对于计算机要知道什么时候串行数据传送进来,他需要一些方法决定从那个字符开始从哪里结束和下一个起始点。本指南专门处理异步串行数据。

在异步模式下串口数据线保持高电平直到出现一个字符被传送。一个开始位之前每个字符和后边紧跟的位的字符性质,一个奇偶校验位,和一个或者多个停止位。开始位通常是一个空电平然后告诉电脑新的串行数据有用。数据然后被发送或者任何时间接收,因此称为异步。

Figure 1 - Asynchronous Data Transmission

 

奇偶校验位是这个数据位的和表示这些数据包含一个偶数或奇数个1。偶校验,奇偶校验位是0如果在字符里1的数目里是偶数0;奇校验,奇偶校验位是0如果数据中1的数目是1.你也许听过低电平校验,高电平校验,无奇偶校验。低校验是指奇偶校验位是全0,高校验指得是这些位全为1。无校验是指无校验位表示或者传输。 剩余的位被称为停止位。

在连个字符间可以为1,1.5,或者2停止bits并且他们的值为1.停止位以前被用来给计算机时间处理前边的字符,但是现在只是为异步解释计算机接收字符服务。

异步数据格式通常表示为"8N1", "7E1",和往前。这些分别代表“8位数据位,无奇偶校验,1bit停止位”和“7位数据位,偶校验,1bit停止位”。

什么是全双工和半单工?

全双工是指计算机能够同时接收和传输数据-有两个单独的数据信道(一进一出)。

半双工是指计算机不能同时接收和传输数据。通常这意味着只有单一的信道通信。但是并不是所有的RS232信号都不使用。然而,他通常指得是通信链接用其他标准而不是RS232,RS232并不支持全双工操作。

流控

当在俩个串口间传输数据控制数据流是必须的。这是因为限制中间串口通信连接,一个是串口,或者另外一些是存储媒体。两种方法是常用的异步通信数据。

第一个方法是通常被称为“软件”流控和应用专用字符开始(XON or DC1, 021 octal)或者停止(XOFF or DC3, 023 octal)数据流。这些字符定义在美国交换信息标准编码(ASCII).虽然这些代码传输文本信息很有用,但是他们在没有特殊程序转换的时候不能使用。

第二种方法被称为“硬件”控制流,应用RS-232CTS和RTS信号代替特殊字符。接受者当它准备接受更多数据的时候设置CTS为低电压,同样的,还没有准备传输别的数据的时候,传送者设置RTS为低电平。因为硬件流控用一组分离的信号,他比软流控更快,同样需要发送和接收多位信心做同样的事情。CTS/RTS流控并非被所有的硬件和操作系统支持。

什么是中断?

通常一个接收或者传输数据信号保持在高电平知道一个新的字符传输。如果信号下降到低电平很长时间,常常是1/4,1/2秒,然后可以说产生中断。

一个中断有时候用来重启一个通信线路或者改变操作系统的通信模式例如猫。第三章,调制解调器涵盖这些更加高级的应用。

同步通信

不像异步通信,同步通信表现为一个恒定的数据流。读取线上的数据,电脑必须提供或者接收一个同步时钟以至发送和接收同步。

甚至这样的同步,电脑必须用某种方法标记开始数据。最常见的做法是用一个数据协议包例如串行数据链路控制(SDLC)或者高速数据链路控制(HDLC).

每个协议定义确定的位序列来表示数据包的开始和结束。每个协议也定义一个bit序列,用在没有数据的时候;这些序列允许电脑开始查看开始的数据包。

因为同步协议没有用每个字符同步位,他们通常提供至少25%的改进比异步通信,并且更加适合远程网络和配置更多的串行口。

尽管同步通信的高速率优点,但是大多数RS-232硬件并不支持,因为还需要额外的硬件和软件。

接入串行口

像所有的设备一样,UNIX提供接入串口的设备文件。要接入串口只需有打开这些相应的设备文件。 串口文件 每个串口在UNIX系统上有一个或多个设备文件(文件在 /dev 目录)与它有关:

表 2 - 串口和设备文件

system                                     port1             prot2

IRIX®                                   /dev/ttyf1       /dev/ttyf2

HP-UX                                 /dev/tty1p0       /dev/tty2p0

Solaris®/SunOS®                /dev/ttya           /dev/ttyb

Linux®                                /dev/ttyS0          /dev/ttyS1

Digital UNIX®                        /dev/tty01           /dev/tty02

打开一个串口

因为一个串口是一个文件,所以open()函数被用来接入它。一个问题是UNIX设备文件普通用户通常不能接入。工作区包括接口的权限到文件的问题,运行你的程序作为超级用户(root),或者设置程序的用户名以让普通用户也能运行设备文件。

现在我们假设文件已经可以让所有用户使用。这个代码打开串口1工作区在IRIX操作系统上:

Listing 1 - 打开串口

#include /* 标准输入/输出定义 */

#include /* 串函数定义 */

#include /* UNIX 标准函数定义*/

#include /* 文件控制定义 */

#include /* 错误数定义 */

#include /* POSIX 终端控制定义 */

/* * 'open_port()' - 打开串口 1. * * 返回文件描述成功或者 -1表示错误。 */

int open_port(void) {

        int fd;   /* 文件描述端口定义*/

        fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);

        if (fd == -1){

                /* * 不能打开端口。 */

              perror("open_port: Unable to open /dev/ttyf1 - ");

        }

        else

              fcntl(fd, F_SETFL, 0);

      return (fd);

}

其他的操作系统将需要相应的设备文件名,但是其他的代码是一样的。

开放操作

你将发现当你打开设备文件,我们通常使用两个标记除了read+write模式:

             fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);

O_NOCTTY标志告诉UNIX这个程序不想成为“控制终端”控制的端口,如果你不能明确说明这个,那么任何输入(像键盘中断信号等)将会影响你的程序。程序例如getty(1M/8)应用这个特征当开始登陆程序,但是通常一个用户程序不希望这个问题。

O_NDELAY标志告诉UNIX这个程序不关心DCD信号线状态-是否其他的端口是否运行。如果不指定这个标号,你的程序将会在DCD信号线是低电平时停止。

写数据到端口

写入数据到端口很简单-只要应用write() 系统回叫发送数据给它:

            n = write(fd, "ATZ/r", 4);

             if (n < 0)

                       fputs("write() of 4 bytes failed!/n", stderr);

write函数返回位的数值或者当错误时为-1。通常这个错误只有当一个调制解调器或者数据连接丢数据载波检测行时,你将运行到EIO 。这个情况将会维持直到你释放端口。

读端口数据

读端口数据有点麻烦。当你操作这个端口在原始数据模式,每个read()系统回叫将返回串口输入缓存实际提供的无论多少字符。如果没有字符可用,回叫将会阻塞(等待)直到字符进入,一个间隔计时器失效,或者错误发送。read函数将会立即返回通过下面这样做:

               fcntl(fd, F_SETFL, FNDELAY);

FNDELAY选项使read函数返回0,当没有字符在端口上。存储正常(阻塞)动作,没有FNDELAY选项则回叫fcntl():

                fcntl(fd, F_SETFL, 0);

这个同样应用在操作 O_NDELAY选项打开串口。

关闭串口

关闭串口,使用close系统调用:

               close(fd);

关闭一个串口将会也设置DTR信号低电平,这会把调制解调挂起。

---------------------------------------------------------------------------------------第一章完(待续)

第二章 配置串口

这章讨论如何配置串口从C用POSIX终端接口。

POSIX终端接口

大多数系统支持POSIX终端(串)接口改变参数例如波特率,字符大小,等。首先你需要做的是包含头文件 <termios.h>; 这个文件定义终端控制结构和POSIX控制函数。

最重要的POSIX函数是tcgetattr(3) 和 tcsetattr(3),这两个分别表示获取和设置终端属性。你提供一个指针到包含可用的所有串口选项控制台结构:

表3-控制台结构成员

Member       Description
c_cflag        控制选项
c_lflag          现行选项
c_iflag          输入选项
c_oflag        输出选项
c_cc            控制字符

c_ispeed      输入波特率 (new interface)
c_ospeed 输出波特率(new interface)

控制选项

c_cflag成员控制波特率,一些数据位,奇偶校验,停止位和硬件控制流。有常数为所有的支持配置。

Table 4 - c_cflag 成员常数描述

常数         描述
CBAUD Bit mask for baud rate
B0            0 baud (drop DTR)
B50          50 baud
B75         75 baud
B110       110 baud
B134       134.5 baud
B150        150 baud
B200        200 baud
B300       300 baud
B600        600 baud
B1200    1200 baud
B1800     1800 baud
B2400     2400 baud
B4800     4800 baud
B9600      9600 baud
B19200 19200 baud
B38400     38400 baud
B57600    57,600 baud
B76800    76,800 baud
B115200 115,200 baud
EXTA        External rate clock
EXTB         External rate clock
CSIZE        Bit mask for data bits
CS5           5       data bits
CS6            6 data bits
CS7           7 data bits
CS8             8 data bits
CSTOPB     2 stop bits (1 otherwise)
CREAD         Enable receiver
PARENB       Enable parity bit
PARODD    Use odd parity instead of even
HUPCL        Hangup (drop DTR) on last close
CLOCAL      Local line - do not change "owner" of port
LOBLK        Block job control output
CNEW_ RTSCTS
CRTSCTS       Enable hardware flow control (not supported on all platforms)

c_cflag成员包含两个选项应该常常应用,CLOCAL和CREAD.这将确保你的程序不被其他端口控制和挂起信号干扰,同时串口接口驱动将读取进入的数据。

波特率常量 (CBAUD, B9600, etc.) 是用来为更老的接口,他们没有c_ispeed和c_ospeed 成员。参考下一节关于POSIX函数信息来设置波特率。

没有直接初始化 c_cflag (或者其他flag)成员;你应该常常使用逐位与,或,非操作来设置或者清空成员位。不同的操作系统版本(和甚至补丁)应用各不相同,因此用位操作将会防止你使用错误的位标志,这些位标志需要更新一个串口驱动。

设置波特率

不同的操作系统波特率保存在不同的地方。老的接口依靠表4中的波特率常数存储波特率c_cflag成员,然而更新的应用提供c_ispeed和c_ospeed 成员,他们包含实际的波特率数值。

cfsetospeed(3) 和cfsetispeed(3)函数不论在何种操作系统接口提供设置到控制台的波特率。典型的你最好应用下边的代码设置波特率:

Listing 2 - 设置波特率
struct termios options;

/*
* 得到现在端口的选项。。。。。。
*/

tcgetattr(fd, &options);

/*
*设置波特率为19200。。。。

*/

cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);

/*
* 启动接收者和设置本地模式。。。。。。
*/

options.c_cflag |= (CLOCAL | CREAD);

/*
* 设置新的端口选项。。。。。。。
*/

tcsetattr(fd, TCSANOW, &options);

tcgetattr(3) 函数装满你提供的串口配置到控制台。然后我们设置波特率和让本地模式运行和串口数据接收,我们用tcsetattr(3).选择新的配置。TCSANOW 常量规定所以的改变应该立即执行而不等待数据传送和接收结束。有其他的常量来等待输入和输出完成或者刷新输入输出缓存。

大多数系统不支持不同的输入输出速率,所以要保证设置相同的值保证最大的可移植性。
Table 5 - Constants for tcsetattr

Constant                    Description
TCSANOW               Make changes now without waiting for data to complete
TCSADRAIN              Wait until everything has been transmitted
TCSAFLUSH             Flush input and output buffers and make the change

设置字符大小

不像波特率,没有方便的函数来设置字符大小。你必须做一个小的位掩码设置。字符的尺寸在位中指定:

options.c_cflag &= ~CSIZE;     /* 字符大小的位掩码*/
options.c_cflag |= CS8;             /* 选中8位数据位*/

设置奇偶校验

Like the character size you must manually set the parity enable and parity type bits. UNIX serial drivers support even, odd, and no parity bit generation. Space parity can be simulated with clever coding.

像字节大小一样你必须手动设置奇偶校验使能和校验类型。UNIX串口驱动支持奇数,偶数,和无校验位系列。空校验能用代码仿真。
无校验(8N1):
options.c_cflag &= ~PARENB
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
奇校验(7E1):
options.c_cflag |= PARENB
options.c_cflag &= ~PARODD
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
偶校验(7O1):
options.c_cflag |= PARENB
options.c_cflag |= PARODD
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
空校验设置和无校验相同(7S1):

options.c_cflag &= ~PARENB
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;

设置硬件流控

某些版本的UNIX支持使用CTS和RTS信号线的硬件流控。如果 CNEW_RTSCTS or CRTSCTS 常数被系统定义然后然后硬件流控才能被支持。按照下边能让硬件流控:
options.c_cflag |= CNEW_RTSCTS;    /* Also called CRTSCTS */

同样, 停止硬件流控l:
options.c_cflag &= ~CNEW_RTSCTS;

本地选项

本地选项成员c——cflag控制串口驱动管理如何输入字符。通常你能配置c_lflag 成员对标准或者原始输入。

2005-11-17 00:28:00 rabo 阅读数 1311
  • Linux零基础视频课程

    Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。针对的是Linux服务器方面的知识,适合从事运维或后端开发的人员。 课程难点:     Linux命令较多,没有图形界面,对于新手理解有难点,复杂的命令也容易记混淆。

    3493 人正在学习 去看看 李强
POSIX操作系统的串行编程指南
第五版
Michael R.Sweet
Copyright 1994-1999, Allrights Reserved

目录

序言

第一章 基本的串口通讯
什么是串口通讯
什么是RS-232
信号定义
异步通讯
什么是双工和单工
流量控制
什么是断开/break
同步通讯
访问串行端口
串行端口文件
打开串行端口
写入端口
读出端口
关闭端口

第二章 配置串行端口
POSIX终端接口
控制选项
本地选项
输入选项
输出选项
控制字符

第三章 Modem通讯
什么是Modem
与Modem通讯
标准Modem命令
常见的Modem通讯问题

第四章 高级串行端口编程
串行端口IOCTLs
获得控制信号
设置控制信号
获得有效字节数
从一个串行端口中选择输入
select系统调用
使用select系统调用
使用X Intrinsics库的Select接口
附录A.引出线
RS-232 引出线
RS-422 引出线
RS-574 (IBM PC/AT)引出线
SGI 引出线

附录B.控制符的ASCII编码
控制编码


序言
这篇POSIX操作系统的串口编程指南将告诉你如何正确,有效,可移植地对PC上的Unix工作站的串行端口进行编程.每一章提供了
使用POSIX终端控制函数的程序范例,只需要进行少量的更动就可以在IRIX,HP-UX,SunOS,Solaris,DigitalUnix,Linux
和其它的Unix上运行.并且你将会发现,各个操作系统之间的最大区别仅仅是用于标志串口设备和锁文件的文件名不同.

该指南被按如下的章节和附录给予说明.

第一章 基本的串口通讯
第二章 配置串行端口
第三章 Modem通讯
第四章 高级串行端口编程
附录A.引出线
附录B.控制符的ASCII编码



第一章 基本的串口通讯
这一章介绍了串行通讯,RS-232和其它用于大多数计算机上的标准,同时给出了一个用于访问串口的C程序.

1.1 什么是串口通讯 ?

计算机在某一时间内会传送一个或多个位,而串口是一次一位地传送.串口通讯包括大多数网络设备,键盘,鼠标,
调制解调器和终端设备.

当使用串口通讯时,你发送或收到的每一个字,(比如一个字节或者一个字符),实际上都是一次一位地传送的.每一
位或者为1或者为0.之后在你将听到的术语中,mark表示on(1),space表示off(0).

串行数据的速度经常用每秒位数(bps)或者波特率(baud)来表示.用来表示一秒中有多少1或者0被传送.在计算机
发明的初期,300baud被认为是很快的,而今天计算机的RS232可以达到430,800baud的速度!当波特率超过1,000时,
你通常会发现速率用千波特率,或kps(例如9,6k,19,2k)来表示.对于超过1,000,000的速率用兆波特率,或Mbps
(例如1.5Mbps).

当提到串口设备或者端口时,它们或者被标记为数据通信设备("DCE")或者数据终端设备("DTE").两者的不同非常简单:
每一个信号对,比如发送和接收,刚好反过来了.当要将两个DTE或者两个DCE接口连在一起时,一个串行的可以交换信号
对的NULL-Modem电缆或适配器可以完成这一任务.


1.2 什么是RS-232

RS-232是一种标准的用于EIA定义的串口通讯的电气接口.RS-232实际上由三种不同特点组成.每一种定义了不同
电压范围的on和off.常见的是RS-232C,定义mark(1)(on)指-3V到-12V之间的电压,space(0)指+3V到+12V之间的电压.
RS-232C规范中指出这些信号可以发送到25英尺(约8米)远.如果波特率足够低的话,这些信号可以发送的更远些.

除了用于传输的线路,还要提供定时,状态和握手:

表1  RS-232 引脚定义

引出口号  说明        引出口号   说明            引出口号  说明     引出口号  说明            引出口号  说明   
---------+-----------+----------+---------------+---------+--------+---------+---------------+---------+------------
1        |接地       |6         |DSR数据序列就绪|11       |未定义  |16       |后备RXD        |21       |信号质量检测
---------+-----------+----------+---------------+---------+--------+---------+---------------+---------+------------
2        |TXD输出    |7         |GND逻辑地      |12       |后备DCD |17       |接收者时钟     |22       |闹钟检测
---------+-----------+----------+---------------+---------+--------+---------+---------------+---------+------------
3        |RXD输入    |8         |DCD数据负载检测|13       |后备CTS |18       |未定义         |23       |数据速率选择
---------+-----------+----------+---------------+---------+--------+---------+---------------+---------+------------
4        |RTS请求发送|9         |保留           |14       |后备TXD |19       |后备RTS        |24       |传输时钟
---------+-----------+----------+---------------+---------+--------+---------+---------------+---------+------------
5        |CTS请求接收|10        |保留           |15       |传输时钟|20       |DTR数据终端就绪|25       |未定义
---------+-----------+----------+---------------+---------+--------+---------+---------------+---------+------------



另外还有用于串口的两个标准,RS-422和RS-574.RS-422使用更低的电压和微分信号使电缆通讯距离达到将近1000英尺
(约300米).RS-574则定义了9针的PC串口连接器和电压.

1.3 信号定义

RS-232标准定义了18个不同的用于串口通讯的信号.其中,仅有6个在Unix环境中是普遍适用的.

GND-逻辑地

严格地来说,逻辑地并不是信号,但是没有它,其它的信号无法工作.逻辑地作为一个参照电压以告诉电器
哪个电压是正哪些是负.

TXD-数据被发送

TXD信号承载从你的工作站发送到其它的机器或设备上的数据.mark电压表示1,space电压表示0.

RXD-被接收的数据
RXD信号承载从其它机器或设备上向你的工作站发送数据.类似TXD,mark和space电压分别表示1和0.

DCD-数据载波探测
DCD信号从串口电缆的另一端的机器或设备发送出来.串行线上的space(0)电压表示机器或设备处于连线状态.
DCD并不总是被使用.

DTR-数据终端就绪
DTR信号是从你的工作站发送出来,告诉另一端的机器或设备你已经就绪或者尚未就绪,分别用space(0)电压和
mark(1)电压表示.自从你的工作站打开串行接口之后,DTR一直处于自动激活状态.

CTS-设备接收就绪,等待新的数据被发送 ( Clear to Send )
CTS信号来自串行电缆的另一端.space(0)电压表示你可以从你的工作站上发送更多数据了.(以前接收的已
经处理完毕)

CTS常用来调节工作站与串行线另一端的数据传送流量.

RTS-请求发送
RTS信号被你的工作站置成space(0)电压用来表示有更多的数据等待发送.
与CTS类似,RTS也是用来调节串行电缆两个终端之间的数据传送流量.大多数工作站把这个信号一直置为
space(0)电压.

1.4 异步通讯
对于支持串行通讯的机器而言,它需要知道如何判别数据起始和终止.这篇指南将介绍异步串行数据.

在异步模式下,串行数据直到有字符被发送前一直保持mark(1)状态.在每个字符之前有一开始位,紧接着是结束位,一
个可选的奇偶位和若干个停止位.起始位总是space(0),告诉机器串行数据已经准备好了.数据可以在任何时候被发送
或接收,因此称之为异步的.

图1 异步数据传输

space    +--+     +-----+  +--+  +--+       +12伏
         |开|0  1 |2  3 |4 |5 |6 |7 |
         |始|位 位|位 位|位|位|位|位|
         |  |     |     |  |  |  |  |
mark ----+  +-----+ +--+  +--+  +------ -12伏

可选的奇偶位简单地统计数据中包含奇数个1还是偶数个1.如果有偶数个1,则这位为0,称偶校验位.如果是奇数个1,则
该位为0,称奇校验位.通常还有spaceparity,mark parity和no parity的说法.SpaceParity就是校验位总是0,markParity
就是校验位总是1,NoParity表示不使用或不传输校验位.

接着的那位是终止位.在字符之间可以有1,1.5或2个终止位,它们的值总是1.以前终止位是为了让机器有更多的时间去
处理前一个字符,但现在仅仅是用于在机器接收字符时的同步.

异步数据格式通常用诸如"8N1","7E1"等等表示.前者表示8个数据位,没有校验位,1个终止位.后者表示7个数据位,偶校
验位,1个终止位.

1.4.1 什么是全双工和单双工

全双工表示机器可以同时既发送数据又可以接收数据,有两个独立的数据通道(一进一出).

单双工表示机器不能在发送数据的同时又接收数据.通常这意味这仅仅有一个数据通道在使用.但这并不意味不能使用
RS-232信号,而是通讯联接仅能使用不支持全双工操作的其它的标准,而不是RS-232.


1.4.2 流量控制

当在两个串行接口之间传输数据时通常有需要调节数据流量.这可能需要对之间的串行通讯联接,某个串行接口或者某个
存储介质实行一些限制.对于异步数据通常有两种方法:

一是软件流量控制.使用特殊的字符去标志数据流的开始(XON,DC1,八进制的021或结束(XOFF,DC3,八进制的023).这些
字符可以在ASCII中找到定义.然而当传输文本信息时,这些字符本身拥有一定的含义,不能被使用.

另一种是硬件流量控制,用RS232的CTS和RTS信号代替特殊字符用于控制.当接收方准备接收更多的数据时,设置CTS为
space(0),反之设成mark(1).对应的,当发送方准备发送更多的数据时,设置RTS为space(0).由于硬件的流量控制使用
独立的一套信号,比软件的实现更快,因为作同样的工作,后者需要发送或接收多位信息.另外需要注意的是,并不是所
有的硬件和操作系统都支持CTS/RTS流量控制.

1.4.3 什么是中断/Break

一般而言,一个接收或者传输数据的信号直到一个新的字符被传送之前一直保持mark(1).信号过了很长时间后才被降
低到space(1),通常时1/4到1/2秒,那么我们称这种状态为break.

Break有时被用来重置通讯线路或者改变诸如Modem的通讯设备的工作模式.第三章的"如何操作Modem"将更深入地谈论
这个问题.

1.5 同步通讯

与异步数据不同的是,同步数据是一种恒定的位流.为了从通讯线上读出数据,机器必须发送或者接收一个普通的位作为
时钟以达到发送端和接收端的同步.

对于同步,机器必须标志数据的起始位置.实现它的最普通的方法时使用类似串行数据链路控制SDLC或者高速数据链路控
制HDLC之类的数据包协议.

每一个协议定义了固定的位顺序以表示数据包的开始和结束,同时也定义了被用来表示没有数据时使用的位顺序.机器
根据这些位顺序可以知道数据包的开始位.

因为同步协议不使用每个字符的同步位,所以它们一般能比异步通讯提高至少25%的性能,适合远程网络和两个以上的串行
接口的配置.

尽管同步协议拥有速度优势,但大多数RS232硬件仍然由于额外的硬件和必要的软件而没有支持它.


1.6 访问串行端口

类似于所有设备,UNIX系统通过设备文件提供了对串行端口的访问.为了访问它,仅仅需要打开相应的设备文件.

1.6.1 串行端口文件
在UNIX系统的每一个串行端口都有一个或者更多的设备文件与它们相对应(在/dev/目录下).

表2 -----串行端口的设备文件名
            ----------------------------------------+------------------+-------------- --+
操作系统                 |     端口1       |      端口2      |
                             ---------------------------------------+------------------+-----------------+
IRIX(R)                    |      /dev/ttyf1   |    /dev/ttyf2    |
                             ---------------------------------------+------------------+-----------------+
HP-UX                                     |    /dev/tty1p0  |   /dev/tty2p0 |
                             ---------------------------------------+------------------+-----------------+
Solairis(R)/SunOS(R)          |      /dev/ttya     |  ./dev/ttyb      |
                             ---------------------------------------+------------------+-----------------+
Linux(R)                                  |  /dev/ttyS0     |  /dev/ttyS1     |
                             ---------------------------------------+------------------+-----------------+
Digital UNIX(R)                     |  /dev/tty01      | /dev/tty02       |
                             ---------------------------------------+------------------+-----------------+

1.6.2 打开串行端口

既然串行端口是一个文件,open(2)函数可以被用来访问它.UNIX系统的一个特点是一般用户无法访问设备文件.所以需要
更改你所关心的设备文件的访问权限,以超级用户来运行程序或者在你的程序中使用setuid,从而拥有设备文件的访问权.

这里,我们假设该文件可以被所有用户访问.在运行IRIX的SGI工作站上打开串行端口的代码如下:

listing 1 open a serial port

#include <stdio.h> /*标准的输入输出*/
#include <string.h> /*字符串相关函数*/
#include <unistd.h> /*UNIX标准的函数*/
#include <fcntl.h> /*文件控制*/
#iclude <errno.h> /*出错信息*/
#include <termios.h> /*POSIX终端控制定义*/

/* open_port() ----打开串行端口*/
打开成功则返回文件描述符,否则返回-1
*/

int open_port(void)
{
int fd;
fd = open("/dev/ttyf1",O_RDWR | O_NOCTTY | O_NDELAY );
if( fd == -1 ){
perror("open_port:Unable to open /dev/ttyf1 -");
}
else
fcntl(fd,F_SETFL,0);

return fd;
}
其他的系统只需要更换设备文件名,其它代码是一样的.

1.6.3 端口的打开选项

你会发现当我们打开设备文件后,我们除了用读写模式外还用了另外两个标志:
fd = open("/dev/ttyf1",O_RDWR | O_NOCTTY | O_NDELAY );

O_NOCTTY标志告诉UNIX系统,这个程序不会成为对应这个端口的控制终端,如果没有指定这个标志,那么任何一个
输入,诸如键盘中止信号等等,都将会影响你的进程.类似于getty的程序当启动login进程时都使用了这个特性,
但是一般的用户程序不需要这这样作.

O_NDELAY标志告诉UNIX系统这个程序不关心DCD信号线所处的状态,它用来表示端口的另一端是否激活或者停止.
如果你想指定这个标志,你的进程将会一直处在睡眠态,直到DCD信号线是space(0).

1.6.4 写数据到端口

写数据到端口很简单,就是用write(2)系统调用把数据发送出去.

n = write(fd,"ATZ/r",4);
if( n < 0 )
fputs("write() of 4 bytes failed!/n",stderr);

write返回有多少个字节被发送出去,如果出错,则返回为-1.通常你可能遇到的唯一的错误是EIO,当MODEM或者
数据链路撤销数据载波探测(DCD linke)时会出现这种情况,这种情况将一直存在,直到你关闭端口.

1.6.5 从端口中读取数据

从端口中读出数据有些技巧.当你在RAW数据模式下操作时,每一个read(2)系统调用将返回当前串行输入缓存区中
存在的字符数.如果没有字符,这个系统调用将一直阻塞到有字符到达或者间隔时钟过期,或者错误发生.当read函数
如下设置后将会立即返回.

fcntl(fd,F_SETFL,FNDELAY);

FNDELAY选项将导致当端口上没有字符可读时,read函数返回0.为恢复到一般状态,可以在调用fcntl时不带FNDELAY选项.

fcntl(fd,F_SETFL,0);

在用O_NDELAY打开端口之后也可以这样来恢复.

1.6.6 关闭串行端口

为了关闭串行端口,只需要使用close系统调用

close(fd);

关闭串行端口经常会设置DTR信号为低,导致大多数的MODEM挂起.


第二章 配置串行端口
这一章将讨论如何用C语言的POSIX终端接口去配置串行端口.

2. 1 POSIX终端接口

大多数系统都支持POSIX终端接口,用于改变诸如波特率,字符大小等等参数.你所需要作的第一件事情是将termios.h
include到你的程序中,这个文件中其中定义了POSIX控制函数和终端控制结构.

两个重要的POSIX函数是tcgetattr(3)和tcsetattr(3),分别用于获得和设置终端属性;你需要提供一个包含所有可用串
行选项的终端结构:

表3 - Termios结构的成员
+---------------------------------------------------------------+
| 成员                | 说明                                               |
+---------------------------------------------------------------+
| c_cflag             | 控制选项                                     |
+---------------------------------------------------------------+
| c_cflag             | 线路选项                                     |
+---------------------------------------------------------------+
| c_iflag              | 输入选项                                     |
+---------------------------------------------------------------+
| c_oflag            | 输出选项                                      |
+---------------------------------------------------------------+
| c_cc                 | 控制特性                                      |
+---------------------------------------------------------------+
| c_ispeed         | 输入波特律(新接口)               |
+---------------------------------------------------------------+
| c_ospeed        | 输出波特律(新接口)               |
+---------------------------------------------------------------+

2.2 控制选项

c_cflag成员用于控制波特率,数据位的数目,奇偶校验位,终止位和硬件流控制.以下是所支持的配置常数.

表4 - c_cfalg成员的可设置常量
+---------------------+-----------------------------------------+
| 常量                   | 说明                                             |
+---------------------+-----------------------------------------+
| CBAND               | 波特律的位掩码                    |
+---------------------+-----------------------------------------+
| B0                        | 0 波特 (放弃DTR)                    |
+---------------------+-----------------------------------------+
| B50                      |  50 波特                         |
+---------------------+-----------------------------------------+
| B75                     | 75 波特                         |
+---------------------+-----------------------------------------+
| B110                   | 110 波特                                      |
+---------------------+-----------------------------------------+
| B134                   | 134 波特                                      |
+---------------------+-----------------------------------------+
| B150                   | 150 波特                                      |
+---------------------+-----------------------------------------+
| B200                   | 200 波特                                      |
+---------------------+-----------------------------------------+
| B300                   | 300 波特                                      |
+---------------------+-----------------------------------------+
| B600                   | 600 波特                                      |
+---------------------+-----------------------------------------+
| B1200                 | 1200 波特                                   |
+---------------------+-----------------------------------------+
| B1800                 | 1800 波特                                   |
+---------------------+-----------------------------------------+
| B2400                 | 2400 波特                                   |
+---------------------+-----------------------------------------+
| B4800                 | 4800 波特                                   |
+---------------------+-----------------------------------------+
| B9600                 | 9600 波特                                   |
+---------------------+-----------------------------------------+
| B19200               | 19200 波特                                |
+---------------------+-----------------------------------------+
| B38400               | 38400 波特                                |
+---------------------+-----------------------------------------+
| B57600               | 57600 波特                                |
+---------------------+-----------------------------------------+
| B115200            | 115200 波特                              |
+---------------------+-----------------------------------------+
| EXTA                  | 外部时钟律                              |
+---------------------+-----------------------------------------+
| EXTB                  | 外部时钟律                              |
+---------------------+-----------------------------------------+
| CSIZE                 | 数据位的位掩码                    |
+---------------------+-----------------------------------------+
| CS5                     |  5个数据位                               |
+---------------------+-----------------------------------------+
| CS6                     | 6个数据位                                 |
+---------------------+-----------------------------------------+
| CS7                     | 7个数据位                                 |
+---------------------+-----------------------------------------+
| CS8                     | 8个数据位                                 |
+---------------------+-----------------------------------------+
| CSTOPB            | 2个停止位(不设则是1个停止位)  |
+---------------------+-----------------------------------------+
| CREAD               | 接收使能                                   |
+---------------------+-----------------------------------------+
| PARENB            | 校验位使能                               |
+---------------------+-----------------------------------------+
| PARODD           | 使用奇校验而不使用偶校验 |
+---------------------+-----------------------------------------+
| HUPCL               | 最后关闭时挂线(放弃DTR)            |
+---------------------+-----------------------------------------+
| CLOCAL            | 内部线路 - 不改变端口所有者     |
+---------------------+-----------------------------------------+
| LOBLK               | 块作业控制输出                          |
+---------------------+-----------------------------------------+
| CNET_CTSRTS  | 硬件流控制使能                      |
| CRTSCTS             | (并非所有硬件都支持)                |
+---------------------+-----------------------------------------+

c_cflag成员包含了两个应该一直激活的选项,CLOCAL和 CREAD.These will ensure that your program does not
become the 'owner' of the port to sporatic job control and hangup signals,and also that the serail
interface driver will read incomfing data bytes.

波特率常数(CBAUD,B9600,等)被用于没有c_ispeed 和 c_ospeed成员的早期接口.下一小节中将使用POSIX函数设置波特率.

不要直接去初始化c_cflag的(或其它标志的)成员;你应该总是去使用逐位的AND,OR和NOT操作符去设置或清除成员中的位.
不同版本的操作系统(甚至补丁)都可以不同方式地使用这些位,所以用逐位操作符将会避免你使用将在更新的串口驱动
中使用的位标志.

2.2.1 设置波特率

不同的系统的波特率储存在不同的位置.早期的接口用表4中的某一个波特率常数储存在c_cflag中,然而更新的接口则
是使用c_ispeed和c_ospeed去储存实际的波特率值.

cfsetospeed和cfsetispeed函数用来设置termios结构中的波特率,不管是在哪一个系统接口下.一般你可以用以下的代
码设置波特率:

listing-2 Setting the baud rate.

stuct termios options;

/* Get the current optinos for the port ...*/
tcgetattr(fd,&options);

/* Set the baud rates to 19200 ... */
cfsetispeed(&options,B19200);
cfsetospeed(&options,B19200);

/* Enable the receiver and set local mode ... */
options.c_cflag |= (CLOCAL | CREAD );

/* Set the new options for the port ... */
tcsetattr(fd,TCSANOW,&options);

tcgetattr(3) 函数将当前串行端口的配置填充到options结构中.在我们设置波特率,激活本地模式和串行数据接收后,用
tcsetattr(3)去设置新的配置.TCSANOW常数指定所有的更改立即生效,而不会去等待输出的数据发送完毕和输入的数据接收
完毕,也可以用其它的常数去等待输入和输出的完成或者刷新输入和输出缓存.

大多数系统不支持输入与输出的速率不等,所以为了可移植性必须将它们设成相等的值.

表5 - tcsetattr可设置常量
+---------------------+-----------------------------------------+
| 常量                   | 说明                                             |
+---------------------+-----------------------------------------+
| TCSANOW        | 不等数据传输完全就改变|
+---------------------+-----------------------------------------+
| TCSADRAIN     | 等待所有数据传输结束     |
+---------------------+-----------------------------------------+
| TCSAFLUSH    | 清空输入输出缓冲区并设置|
+---------------------+-----------------------------------------+

2.2.2 设置字符的大小

不象波特率,设置字符大小没有可用的函数.你不得不用位掩码去设置它们.字符大小用位指定:

options.c_cflag &= ~CSIZE; /* mask the character size bits */
options.c_cflag |= CS8; /* select 8 data bits */

2.2.3 设置奇偶校验位

类似字符大小,你必须手工激活奇偶校验和设置校验类型.UNIX串行驱动支持偶,奇和无校验位.space(0)将用更好的方式去
表示.

.NO parity(8N1):

options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= ~CS8;

.Even parity(7E1):

options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;

.Odd Parity(7O1):

options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;

.Space parity is setup the same as no parity(7S1):

options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;

2.2.4 设置硬件流控制

一些版本的UNIX支持使用CTS(Clear to Send)和RTS(Requeut to send)信号线的硬件流量控制.如果你的系统中定义了
CNEW_RTSCTS或者CRTSCTS常数,那么就可能支持硬件流量控制.以下可以激活硬件流量控制:

options.c_cflag |= CNEW_RTSCTS; /* also called CRTSCTS */

类似地可以这样关闭硬件流量控制:

options.c_cflag &= ~CNEW_RTSCTS;


2.3 本地选项

本地模式c_lflags控制驱动程序如何管理输入字符.一般而言,你可以配置c_lflag为规范或者原始输入.

表6 - c_lflag成员可设置常量
+---------------------+-----------------------------------------+
| 常量                   | 说明                                             |
+---------------------+-----------------------------------------+
| ISIG                     | 将SIGINTR, SIGSUSP, SIGDSUSP
               |和  SIGQUIT信号使能            |
+---------------------+-----------------------------------------+
| ICANON             | 标准(canonical)输入模式使能
(否则是原始[raw]方式)          |
+---------------------+-----------------------------------------+
| XCASE               | 映射大写字母为 /小写字母(此常量已过时)  |
+---------------------+-----------------------------------------+
| ECHO                | 输入字符回显使能                |
+---------------------+-----------------------------------------+
| ECHOE               | 删除字符回显为BS-SP-BS使能 |
+---------------------+-----------------------------------------+
| ECHOK               | kill字符后回显NL(新行)字符 |
+---------------------+-----------------------------------------+
| ECHONL            | 回显NL字符                            |
+---------------------+-----------------------------------------+
| NOFLUSH         | 取消中断或退出字符后清空输入缓冲区|
+---------------------+-----------------------------------------+
| IEXTEN              | 扩展功能使能                         |
+---------------------+-----------------------------------------+
| ECHOCTL         | 回显控制字符为"^字符", 删除字符为"~?"|
+---------------------+-----------------------------------------+
| ECHOPRT         | 回显被删除的字符为字符删除|
+---------------------+-----------------------------------------+
| ECHOKE              | 在行kill字符时回显全行BS-SP-BS|
+---------------------+-----------------------------------------+
| FLUSHO            | 清空输出                                   |
+---------------------+-----------------------------------------+
| PENDIN              | 在下一个读入或输入字符
                                                  时重打入等待输入 数据   |
+---------------------+-----------------------------------------+
| TOSTOP            | 在后台输出时发送SIGTTOU信号|
+---------------------+-----------------------------------------+

2.3.1 选择规范输入
规范输入是面向行的输入方式.输入字符被放进用于和用户交互可以编辑的缓存区中,直到读入回车键或者换行符才结束.
当选择这种模式时,你一般可以使用ICANON,ECHO和ECHOE几个选项:

options.c_lflag |= ( ICANON | ECHO | ECHOE );

2.3.2 选择原始输入
原始输入是未经处理的.当接收时,输入的字符在它们被收到后立即被传送.使用原始输入时,一般你可以取消选择
ICANOON,ECHO,ECHOE和ISIG选项:
options.c_lflag &= ~ ( ICANON | ECHO | ECHOE );

2.3.3 输入回显的注意事项
输入模式c_iflag控制端口接收端的字符输入处理.类似于c_cflag,储存在c_iflag中的值是所需选项的逐位或.

2.4 输入选项

Table 7

2.4.1 设置输入奇偶选项

当你激活c_cflag中的奇偶检验后,你应该激活输入的奇偶检验.与之相关的常数有INPCK,IGNPAR,PARMRK和ISTRTP.一般你
将选择INPCK和ISTRTP去激活检验和移除奇偶位:

options.c_iflag |= (INPCK | ISTRIP );

IGNPAR有些危险,它告诉串行驱动忽略奇欧错误,继续传送字符就好像没有错误发生一样.这可能对检验通讯链路的质量
有帮助,当一般情况下由于实际原因而不使用.

PARMRK导致奇偶错误在输入流中用特殊字符被标记.如果IGNPAR被激活,在每一个奇偶错误发生的字符之前,NULL字符
(000八进制)将被发送给你的程序.否则,DEL(177八进制)和 NUL字符将和错误的字符一起被发送.

2.4.2 设置软件流控制

软件流控制可以用IXON,IXOFF和IXANY常数来激活:

options.c_iflag |= (IXON | IXOFF | OXANY );

取消它仅需将那些位掩住:

options.c_iflag &= ~(IXON | IXOFF | OXANY );

IXON(起始数据)和XOFF(终止数据)在以下介绍的c_cc数组中被定义

2.5 输出选项

c_oflag包含了输出过滤选项.类似于输入模式,你可以选择已预处理或者原始的数据输出.

Table 8

2.5.1 选择预处理的输出

可以通过设置OPOST选项来选择预处理的输出:

options.c_oflag |= OPOST;

在所有各种选项中,你也许仅仅可以使用ONLCR选项,它将新行映射成CR和LF两个符号.
其它的输出选项主要是由于过去的行式打印机和终端的速率不能跟上串行数据流.

2.5.2 选择原始输出

原始输出可以用c_oflag的OPOST选项

options.c_oflag &= ~OPOST;

当OPOST选项取消后,所有c_oflag的其它选项位都忽略.


2.6 控制字符

c_cc字符数组包含了过时参数和控制字符的定义.数组中的每一个元素都用常数定义.

Table 9

2.6.1 设置软件流控制字符

c_cc数组中的VSTART 和 VSTOP是勇于软件流控制的字符.一般而言,它们应该被设置成
DC1(021八进制)和DC3(023八进制),分别表示ASCII码中的XON和XOFF字符.

2.6.2 设置读超时

UNIX串行接口驱动提供了对字符和数据包的读超时控制.c_cc数组中的VMIN和VTIME就是
这样的两个控制字符.在规范的输入模式下或者当NDELAY选项在对文件操作的open或fcntl
被指定时,这儿的超时控制就被忽略而使用无效.

VMIN指定了最少读取的字符数.如果被设置为零,那么VTIME值就指定了读取每个字符的等待
时间.注意的是,这并不意味着读取N字节的read系统调用将会等待到读取N个字符为止.相反,
这个超时控制仅仅用于第一个字符,read系统调用将立即返回有效的字符数(倚赖于read请求
的字符数).

/*------这地方没明白,好像是如果你请求了n个字节,这样设置的话,当读到一个字
节的话,read立即返回,并返回为n,也许这种情况下,多用于1个字节的超时读取,n=1.所以这种i
情况下读取n个字节的话,应该for(int i = 0;i< n; i++) read(fd,c,1);
如果是这样的话,原文这样写就好懂多了.
Rather,the timeout will apply the first charater and the read call will return one
or zero,at most one byte.So if you want to read n bytes,you should loop again,which
is up to the number you want to request.
*/

如果VMIN非零,VTIME指定了读取第一个字符的等待时间,如果在给定时间内读取了一个字符,那么
read系统调用将阻塞直到所有VMIN指定的字符数被读取.也就是说一旦读到第一个字符,串行接口
驱动将期望收到完整的VMIN长度的数据包.如果在给定的时间内没有没有读到任何字符.read调
用将会返回为零.这种设置要求串行驱动准确地读取N字节的字符,read调用要么返回为零或者N.
但是,超时仅仅指定在读取第一个字符,所以如果由于某种原因,驱动丢失了N字节数据包中的一个
字符,那么read调用将会永久性地阻塞在这儿,等待更多的输入字符.

VTIME是以1/10秒为单位指定接收字符的超时时间.如果VTIME设置为零(缺省情况下),而该端口又没
有用open或fcntl设置NDELAY,read将会阻塞不确定的时间.


第三章 Modem通讯
这一章描述了拨号电话调制解调器通讯的基本知识.并给出了一个使用标准AT指令集的MODEM范例.

3.1 什么是Modem

MODEMs是一个将串行数据调制可以在模拟数据链路上传输的一定频率的设备,比如电话线或者有线电
视线 . 一个标准的电话调制解调将串行数据转换成可以可以在电话线上传输的音调;由于这种转换
的速度和复杂,这些音调听起来更象大声的尖叫.

现在的电话MODEMs可以以每秒53,000位,也就是53kbps的速度在电话线上传输数据,另外大多数的MODEMs
使用数据压缩技术提高传输速率,有些类型的数据可以超过100kbps.

3.2 与MODEM通讯

第一步是打开并配置原始输入的端口
Listing 3 配置原始输入端口
int fd;
struct termios options;

/*打开端口*/
fd = open("/dev/ttyf1",O_RDWR|O_NOCTTY|O_NDELAY);

fcntl(fd,F_SETFL,0);

/*获取当前选项*/
tcgetattr(fd,&options);

/*设置原始输入,1秒的超时*/
options.c_cflag |= (CLOCAL|CREAD);
options.c_lflag &= ~(ICANON | ECHO | ECHOE |ISIG);
optiions.c_oflag &= ~OPOST;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 10;

/*设定选项*/
tcsetattr(fd,TCSANOW,&options);

下一步是建立预MODEM的通讯.最好的方法是发送AT命令给MODEM.这也可以让MODEM去探测你
将使用的波特率.当MODEM打开并成功地连接上后,它将回应"OK".

Listing 4 初始化MODEM
int /* 返回值 0 = 成功, -1 = 失败 */
init_modem(int fd) /* 串行端口文件描述符 */
{
char buffer[255]; /* 输入缓冲区 */
char *bufptr; /* 缓存区中的当前字符 */
int  nbytes; /* 读取的字节数 */
int tries; /* 目前重试的次数 */

for(tries = 0; tries < 3 ;tries ++)
{
/*发送AT命令,后跟一个CR */
if(write(fd,"AT/r",3) < 3)
continue;

/*将字符读入字符串缓冲中,直到一个新行 */
bufptr = buffer;
while( ( nbytes = read(fd,bufptr,buffer+sizeof(buffer) - bufptr -1 )) > 0)
{
bufptr += nbytes;
if( bufptr[-1] == '/n' || bufptr[-1] == '/r' )
break;
}

/*设置 NULL中止字符串,看我们是否得到了一个成功的回应 */
*bufptr = '/0';
if( strncmp(buffer,"OK",2) == 0)
return (0);
}
return (-1);
}

3.3 标准的MODEM命令

大多数的MODEM支持AT命令集,所以每个命令前冠以"AT".每个命令按照以下根式被发送:AT+指定的命令+回车字符
(CR,015八进制).在处理命令之后,MODEM将会根据命令回应相应的文本消息.

3.3.1 ATD ----拨单个数字

ATD命令拨指定的数字.除了数字和长划符号外,你可以指定音调("T"),脉冲("P"),每秒钟的暂停(",")和等待拨号音("W"):
ATDT 555-1212
ATDT 18009009009W1234,1,1234
ATD  T555-1212WP1234

MODEM的回应消息有以下几种:
NO DIALTONE
BUSY
NO CARRIER
CONNECT
CONNECT baud

3.3.2 ATH---挂起

ATH命令导致MODEM挂起.因为MODEM必须是在"命令"模式中,你也许在普通的电话呼叫中不使用它.

如果DTR被dropped,大多数MODEM也会挂起.你可以通过在至少一秒的时间中设置波特率为0实现这一点.DroppingDTR也会让MODEM
回到命令模式中.

在一次成功的挂起之后,MODEM将会回应"NO CARRIER".如果MODEM仍然是被连接的,"CONNECT"或者"CONNECT baud"消息将会被发送.

3.3.3 ATZ---重置MODEM命令

ATZ命令将重置MODEM.MODEM将回应"OK".

3.4 一般MODEM通讯的问题

首先最重要的是,不要忘记取消输入回显.输入回显将会造成MODEM和机器之间的一次反馈.

其次,当发送MODEM命令时,你必须以回车CR来结束它,而不是一个新行NL.C语言中CR的字符是'/r'.

最后,当使用MODEM时,取保你使用的是MODEM所支持的波特率.尽管许多MODEM可以自动波特率探测,但一些仍然有19.2kbps的限制,
不得不注意.
没有更多推荐了,返回首页