LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 2399|回复: 8

我翻译的Serial-Programming-HOWTO,大家帮忙除一下错

[复制链接]
发表于 2004-2-19 13:27:07 | 显示全部楼层 |阅读模式
2入门
2.1除错
测试设备的最好的方法是将你的机器和另外一台Linux主机用一根串口线连接起来,在那台机器上使用miniterm软件向你的机器上发送字符。miniterm很容易编译安装,只有#define MODEMDEVICE "/dev/ttyS0"一条语句可能需要你更改(对应于COM1,COM2可以将它设为ttyS0和ttyS1),而且它能够将你的键盘输入通过串行口传到另外一台机器上。重要的是,所有字符都是未经加工的传送。所以测试连接状况时,你在可以在两台机器打开此软件并在一台机器上输入字符,这些字符就会在另外一台机器上出现。
自己制作串口连接线时,你需要将一端的传送端与另一端接收端连接,一端的接收端与另外一端的传送端连接,详情见Serial-HOWTO第七节。
如果你的电脑有两个串行接口的话那么你使用一台机器做这些试验就可以了,你可以在两个虚拟控制台上各运行一个miniterm分别用来发送和接收结果,如果你使用串口鼠标,记得在试验前将/dev/mouse改换指向。如果你正在使用串口HUB,请将此设备配置正确,我的电脑就因为串口HUB配置错误而丢失数据。注意,在一台机器上运行两个串口应用程序也不能保证绝对同步。
设备/dev/ttyS*在你的机器上代表终端设备,而且在系统启动前已经配置好。需要注意的是,这个设备是用来显示机器中设备的输入信息的,传送来的字符在显示时可能经过了中间设备的某种变换。
可以把设置信息存放在asm/termbits.h文件中定义的一个termio型的结构变量中(struct termio),
#define NCCS 19
struct termios {
tcflag_t c_iflag;/*输入参数设置*/
tcflag_t c_oflag;/* 输出参数设置 */
tcflag_t c_cflag;/* 控制方式参数 */
tcflag_t c_lflag;/* 本地工作方式 */
cc_t c_line; /* 列规则 */
cc_t c_cc[NCCS];/* 控制字符 */
};
这个头文件包括了所有的参数的定义:
c_iflag中的输入参数定义了所有的输入方法,就是说字符被read函数读入之前会被进行这样的预处理。同样,c_oflag定义了所有的输出方法。
c_flag包括了对端口的设置,比如波特率,停止符号等等。
c_lflag包括了所有的本地工作方式。
c_cc定义了所有的控制符号,例如文件结束和停止符。
所有的这些参数在<asm/termios.h>中都有默认值,man手册页termios(3)中有这些参数的具体描述。termio结构体中还包括在类POSIX系统中使用的c_line参数(被称做列规则)
2.3串行设备的工作方式
串行设备有三种不同的工作方式,你需要为你的程序选择适当的工作方式。但是,不要使用反复查询的方法来获取输入,这样的话可能会丢失信号,而且用常规方法无法查觉错误的存在。
2.3.1标准输入方式
这时使用最普遍的输入方法,这种方法可以用来接收以列为单位传送数据的设备发来的信号,每次调用read函数只会返回一整列的输入结果,一列往往以ASCII字符NL为结束标志。注意:DOS中的结束符CR在默认状态下不是结束标志。
标准输入方式也可以识别删除重绘等操作。
同时你也可以将CR定义成结束标志。
2.3.2非标准输入方式
非标准输入方式是指每次读取一定数量的字符。在程序每次只需要输入一定数量的字符,或者所连接的设备每次只发送一定量的字符时你可以使用这种方法。
2.3.3异步输入
上面所说的两种方式都可以以同步或异步的方式工作,默认是同步方式,就是read进程一直存在(在没有输入时进程阻塞并进行等待),直到全部完成。在异步方式中,read方法每次在接收完成后发送特定的信号来告诉发送方发送剩余的数据(这个信号可以被发送方信号收集器检查到)。
2.3.4多点输入
这不是一个新的输入方式但是在你使用多个输入设备时会用到,在我的程序中需要通过TCP/IP协议处理多个输入源的信号,而且这些信号时同时传送的。
在下面的例子中,程序需要从多个输入源中获取信号,如果有数据到达,那么进程开始,在输入结束后继续等待。
虽然下面的方法看起来很复杂,但是需要你了解的是linux是个多任务操作系统,系统调用名“select”在等待过程中并不过多占用CPU,但在数据到达时会适当降低其他程序对CPU的占用率来处理输入。

3.程序示例
这里所有例子都是从miniterm.c中节选的。
数据缓冲区我们定义为255个字符大小,和标准输入方式能处理的最大数据长度相同,这个长度在linux/linits.h或者posix1_lim.h中定义了。
在测试代码前别忘了为你的串口设置权限,例如chmod a+rw /dev/ttyS1。
3.1标准输入方式
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

#define BAUDRATE B38400
/* 你可以重新设置波特率 */
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX compliant source */

#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE;

main()
{
int fd,c, res;
struct termios oldtio,newtio;
char buf[255];
/*
Open modem device for reading and writing and not as controlling tty
because we don't want to get killed if linenoise sends CTRL-C.
*/
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
if (fd <0) {perror(MODEMDEVICE); exit(-1); }

tcgetattr(fd,&oldtio); /* 存储当前串口设置 */
bzero(&newtio, sizeof(newtio)); /* 为新的设置清空此数组 */

/*
BAUDRATE: 设置波特率bps. 你也可以使用 cfsetispeed 和 cfsetospeed.
CRTSCTS : 输出流控制 ,详情请参看Serial-HOWTO第七节
CS8 : 8n1 (8bit,无奇偶校验,1停止位)
CLOCAL : 本地连接,无modem
CREAD : 可以接收字符
*/
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;

/*
IGNPAR : 忽略奇偶校验位
ICRNL : 将CR转换为NL
*/
newtio.c_iflag = IGNPAR | ICRNL;

/*
输出前不要变换
*/
newtio.c_oflag = 0;

/*
ICANON : 使用标准输入
*/
newtio.c_lflag = ICANON;

/*
在下面初始化所有控制符
这些参数的默认值在 /usr/include/termios.h中给出
*/
newtio.c_cc[VINTR] = 0; /* Ctrl-c */
newtio.c_cc[VQUIT] = 0; /* Ctrl-\ */
newtio.c_cc[VERASE] = 0; /* del */
newtio.c_cc[VKILL] = 0; /* @ */
newtio.c_cc[VEOF] = 4; /* Ctrl-d */
newtio.c_cc[VTIME] = 0; /* 不使用超时 */
newtio.c_cc[VMIN] = 1; /* 在接收到一个字符后暂停读取过程 */
newtio.c_cc[VSWTC] = 0; /* '\0' */
newtio.c_cc[VSTART] = 0; /* Ctrl-q */
newtio.c_cc[VSTOP] = 0; /* Ctrl-s */
newtio.c_cc[VSUSP] = 0; /* Ctrl-z */
newtio.c_cc[VEOL] = 0; /* '\0' */
newtio.c_cc[VREPRINT] = 0; /* Ctrl-r */
newtio.c_cc[VDISCARD] = 0; /* Ctrl-u */
newtio.c_cc[VWERASE] = 0; /* Ctrl-w */
newtio.c_cc[VLNEXT] = 0; /* Ctrl-v */
newtio.c_cc[VEOL2] = 0; /* '\0' */

/*
清空modem线中的信号并开始设置端口
*/
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);

/*
设置接收, 开始准备接收数据
在本程序中, 在列的首尾是'z'的话则推出程序
*/
while (STOP==FALSE) { /* 一直循环直到检测到特定字符 */
/*读进程会一直等待列结束符, 不论是否超过255个字符。 如果实际得到的字符数目小于这个数值,后面的字符串读取操作会将后面的无效字符也读走,所以我们要设置一个res变量记录有效字符的数量
*/
res = read(fd,buf,255);
buf[res]=0; /* set end of string, so we can printf */
printf(":%s:%d\n", buf, res);
if (buf[0]=='z') STOP=TRUE;
}
/* 恢复备份的状态 */
tcsetattr(fd,TCSANOW,&oldtio);
}
3.2非标准输入方式
在非标准输入方式中,输入的字符并不收列结束符或其他控制字符的限制。控制这个模式的工作方式的有两个参数:
c_cc[VTIME] -- 字符计数器
c_cc[VMIN] -- 在结束读取之前应该接收到的字符的最小个数
如果 VMIN>0并且VTIME=0,VMIN则确定了接收字符的个数,还有因为VTIMER=0,所以不使用计数器。
如果VMIN=0而且VTIME>0,VTIME则就是超时的时间,在一个单个字符(因为VMIN=0)接收后或者VTIME所表示的时间到达之后,read过程结束。
如果VMIN=0而且VTIME=0,read过程会立即结束,所有到达的字符或者读字符的请求都将被忽略。在这种情况出现前你必须使用一个fcntl(fd F_SETFL,FNDELAY)函数。
你可以通过修改newio.c_cc[VTIME]和newio.c_cc[VMIN]来在上面所说的三种状态间切换。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX规则 */
#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE;

main()
{
int fd,c, res;
struct termios oldtio,newtio;
char buf[255];

fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
if (fd <0) {perror(MODEMDEVICE); exit(-1); }

tcgetattr(fd,&oldtio); /* 备份现在的串口状态 */

bzero(&newtio, sizeof(newtio));
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR;
newtio.c_oflag = 0;

/* 设置输入方式(设置成非标准方式) */
newtio.c_lflag = 0;

newtio.c_cc[VTIME] = 0; /* 不设置超时时间 */
newtio.c_cc[VMIN] = 5; /* 得到5个字符后停止读取 */

tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);


while (STOP==FALSE) { /* 等待输入 */
res = read(fd,buf,255); /* 得到5个字符停止 */
buf[res]=0; /* 打印结果 */
printf(":%s:%d\n", buf, res);
if (buf[0]=='z') STOP=TRUE;
}
tcsetattr(fd,TCSANOW,&oldtio);
}
3.3异步输入
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>

#define BAUDRATE B38400
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX规则 */
#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE;

void signal_handler_IO (int status); /* 定义信号处理方式 */
int wait_flag=TRUE; /* 没有信号输入时设置为TRUE */

main()
{
int fd,c, res;
struct termios oldtio,newtio;
struct sigaction saio; /* 设置信号处理动作参数 */
char buf[255];

/* open the device to be non-blocking (read will return immediatly) */
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd <0) {perror(MODEMDEVICE); exit(-1); }

/* 在异步处理前设置信号的处理方式 */
saio.sa_handler = signal_handler_IO;
saio.sa_mask = 0;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);

/* allow the process to receive SIGIO */
fcntl(fd, F_SETOWN, getpid());
/* 使用异步方式
F_SETFL只有O_APPEND和O……NONBLOCK两个值 */
fcntl(fd, F_SETFL, FASYNC);

tcgetattr(fd,&oldtio); /* save current port settings */
/* 为异步传送设置好串口状态 */
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
newtio.c_cc[VMIN]=1;
newtio.c_cc[VTIME]=0;
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);

/* 循环等待输入 */
while (STOP==FALSE) {
printf(".\n");usleep(100000);
/* 在收到信号后,设置wait_flag = FALSE, 开始读取 */
if (wait_flag==FALSE) {
res = read(fd,buf,255);
buf[res]=0;
printf(":%s:%d\n", buf, res);
if (res==1) STOP=TRUE; /* 收到CR后停止读取 */
wait_flag = TRUE; /* 等待下面的数据 */
}
}
/* 恢复串口设置 */
tcsetattr(fd,TCSANOW,&oldtio);
}

/***************************************************************************
    将等待标志wait_flag设置为FLASE,指出已经接收到了字符输入
***************************************************************************/

void signal_handler_IO (int status)
{
printf("received SIGIO signal.\n");
wait_flag = FALSE;
}
3.4使用多个输入源
这个小节只是一个提示,代码也只是整个代码的一部分,需要许多其他的代码才能工作。
系统调用select和相关宏定义使用一个fd_set变量,这是一个字位数组,数组的每个单位都只占一个字位。select需要一个fd_set用做文件描述并且还会返回一个fd_set。详细内容请参考man手册select(2).
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

main()
{
int fd1, fd2; /* 两个输入端口 */
fd_set readfs; /* fd_set文件描述符 */
int maxfd; /* maximum file desciptor used */
int loop=1; /* 无限循环 */

/* open_input_source打开并设置好端口并返回一个文件指针 */
fd1 = open_input_source("/dev/ttyS1"); /* COM2 */
if (fd1<0) exit(0);
fd2 = open_input_source("/dev/ttyS2"); /* COM3 */
if (fd2<0) exit(0);
maxfd = MAX (fd1, fd2)+1; /* 返回最大的文件大小 */

/* 循环等待输入 */
while (loop) {
FD_SET(fd1, &readfs); /* 测试端口1 */
FD_SET(fd2, &readfs); /* 测试端口2 */
/* 阻塞进程并等待输入 */
select(maxfd, &readfs, NULL, NULL, NULL);
if (FD_ISSET(fd1)) /* 端口1有数据输入 */
handle_input_from_source1();
if (FD_ISSET(fd2)) /* 端口2有数据输入 */
handle_input_from_source2();
}
}
这个例子在没有输入时会无限制等待,如果你要设置超时时间,可以用下面的方法代替select的直接调用:
int res;
struct timeval Timeout;

/* set timeout value within input loop */
Timeout.tv_usec = 0; /* 毫秒 */
Timeout.tv_sec = 1; /* 秒 */
res = select(maxfd, &readfs, NULL, NULL, &Timeout);
if (res==0)
这段程序会在1秒种后超时,超时后select会返回。
 楼主| 发表于 2004-2-19 13:28:39 | 显示全部楼层
这里面好像有错误,请大家给更正一下,谢谢
发表于 2004-2-19 16:13:32 | 显示全部楼层
把原文地址贴一下。
 楼主| 发表于 2004-2-19 19:48:59 | 显示全部楼层
发表于 2004-3-8 22:29:36 | 显示全部楼层
canonical & non-canonical 翻译成 标准 和 非标准, 有根据嘛?

我在<<linux程序设计>>上看到,是“授权”和“非授权”。
之前一直做的serial的工作,你的翻译有word文档吗?能不能发到我的信箱,我可以用修改模式,和你一同交流下。
看 html 眼睛太累了

puccacarol@hotmail.com
 楼主| 发表于 2004-3-10 22:16:32 | 显示全部楼层
已经发出了
拜托了
发表于 2004-3-12 20:27:34 | 显示全部楼层
http://www.linux.org.tw/CLDP/OLD/HOWTOs.html

这个是台湾已经翻译的 linux how to 列表
大家参考下
发表于 2004-3-12 21:42:51 | 显示全部楼层
请大家看看:

Whenever possible, do not loop reading single characters to get a complete string. When I did this, I lost characters, whereas a read for the whole string did not show any errors.

台湾人的翻译:
?量避免使用迴圈?碜x取單一的字元再組成字串. 我曾這樣做過, 會掉字元, 且對 read 而言不會顯示任何錯誤.

我觉得不太对,以下是我的理解:
不要采用循环读取单字符的方式来获取一个字符串。
我以前这样做的时候,就丢失了字符,而读取整个字符串的 read 方法,就没有这种错误。
发表于 2006-1-18 10:26:49 | 显示全部楼层
canonical 应该翻译成“规范”吧
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表