6.4.4 使用tcgetattr函数与tcsetattr函数控制终端
为了便于通过程序来获得和修改终端参数,Linux还提供了tcgetattr函数和tcsetattr函数。tcgetattr用于获取终端的相关参数,而tcsetattr函数用于设置终端参数。这两个函数的具体信息如表6.2所示。
表6.2 tcgetattr函数和tcsetattr函数
头文件 |
<termios.h> <unistd.h> |
||
函数形式 |
int tcgetattr(int fd, struct termios *termios_p); int tcsetattr(int fd, int optional_actions, const struct termios *termios_p); |
||
返回值 |
成功 |
失败 |
是否设置 errno |
0 |
−1 |
是 |
说明:tcgetattr函数用于获取与终端相关的参数。参数fd为终端的文件描述符,返回的结果保存在termios结构体中,该结构体一般包括如下的成员:
|
其具体意义如下。
c_iflag:输入模式标志,控制终端输入方式,具体参数如表6.3所示。
表6.3 c_iflag参数表
键 值 |
说 明 |
IGNBRK |
忽略 BREAK 键输入 |
BRKINT |
如果设置了 IGNBRK , BREAK 键的输入将被忽略,如果设置了 BRKINT ,将产生 SIGINT 中断 |
IGNPAR |
忽略奇偶校验错误 |
PARMRK |
标识奇偶校验错误 |
INPCK |
允许输入奇偶校验 |
ISTRIP |
去除字符的第 8 个比特 |
INLCR |
将输入的 NL (换行)转换成 CR (回车) |
IGNCR |
忽略输入的回车 |
ICRNL |
将输入的回车转化成换行(如果 IGNCR 未设置的情况下) |
IUCLC |
将输入的大写字符转换成小写字符(非 POSIX ) |
IXON |
允许输入时对 XON/XOFF 流进行控制 |
IXANY |
输入任何字符将重启停止的输出 |
IXOFF |
允许输入时对 XON/XOFF 流进行控制 |
IMAXBEL |
当输入队列满的时候开始响铃, Linux 在使用该参数而是认为该参数总是已经设置 |
c_oflag:输出模式标志,控制终端输出方式,具体参数如表6.4所示。
表6.4 c_oflag参数
键 值 |
说 明 |
OPOST |
处理后输出 |
OLCUC |
将输入的小写字符转换成大写字符(非 POSIX ) |
ONLCR |
将输入的 NL (换行)转换成 CR (回车)及 NL (换行) |
OCRNL |
将输入的 CR (回车)转换成 NL (换行) |
ONOCR |
第一行不输出回车符 |
ONLRET |
不输出回车符 |
OFILL |
发送填充字符以延迟终端输出 |
OFDEL |
以 ASCII 码的 DEL 作为填充字符,如果未设置该参数,填充字符将是 NUL ( ‘/0’ )(非 POSIX ) |
NLDLY |
换行输出延时,可以取 NL0 (不延迟)或 NL1 (延迟 0.1s ) |
CRDLY |
回车延迟,取值范围为: CR0 、 CR1 、 CR2 和 CR3 |
TABDLY |
水平制表符输出延迟,取值范围为: TAB0 、 TAB1 、 TAB2 和 TAB3 |
BSDLY |
空格输出延迟,可以取 BS0 或 BS1 |
VTDLY |
垂直制表符输出延迟,可以取 VT0 或 VT1 |
FFDLY |
换页延迟,可以取 FF0 或 FF1 |
c_cflag:控制模式标志,指定终端硬件控制信息,具体参数如表6.5所示。
表6.5 c_oflag参数
键 值 |
说 明 |
CBAUD |
波特率( 4+1 位)(非 POSIX ) |
CBAUDEX |
附加波特率( 1 位)(非 POSIX ) |
CSIZE |
字符长度,取值范围为 CS5 、 CS6 、 CS7 或 CS8 |
CSTOPB |
设置两个停止位 |
CREAD |
使用接收器 |
PARENB |
使用奇偶校验 |
PARODD |
对输入使用奇偶校验,对输出使用偶校验 |
HUPCL |
关闭设备时挂起 |
CLOCAL |
忽略调制解调器线路状态 |
CRTSCTS |
使用 RTS/CTS 流控制 |
c_lflag:本地模式标志,控制终端编辑功能,具体参数如表6.6所示。
表6.6 c_lflag参数
键 值 |
说 明 |
ISIG |
当输入 INTR 、 QUIT 、 SUSP 或 DSUSP 时,产生相应的信号 |
ICANON |
使用标准输入模式 |
XCASE |
在 ICANON 和 XCASE 同时设置的情况下,终端只使用大写。如果只设置了 XCASE ,则输入字符将被转换为小写字符,除非字符使用了转义字符(非 POSIX ,且 Linux 不支持该参数) |
ECHO |
显示输入字符 |
ECHOE |
如果 ICANON 同时设置, ERASE 将删除输入的字符, WERASE 将删除输入的单词 |
ECHOK |
如果 ICANON 同时设置, KILL 将删除当前行 |
ECHONL |
如果 ICANON 同时设置,即使 ECHO 没有设置依然显示换行符 |
ECHOPRT |
如果 ECHO 和 ICANON 同时设置,将删除打印出的字符(非 POSIX ) |
TOSTOP |
向后台输出发送 SIGTTOU 信号 |
c_cc[NCCS]:控制字符,用于保存终端驱动程序中的特殊字符,如输入结束符等。c_cc中定义了如表6.7所示的控制字符。
表6.7 c_cc支持的控制字符
宏 |
说 明 |
宏 |
说 明 |
VINTR |
Interrupt 字符 |
VEOL |
附加的 End-of-file 字符 |
VQUIT |
Quit 字符 |
VTIME |
非规范模式读取时的超时时间 |
VERASE |
Erase 字符 |
VSTOP |
Stop 字符 |
VKILL |
Kill 字符 |
VSTART |
Start 字符 |
VEOF |
End-of-file 字符 |
VSUSP |
Suspend 字符 |
VMIN |
非规范模式读取时的最小字符数 |
|
|
tcsetattr函数用于设置终端的相关参数。参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数。
optional_actions可以取如下的值。
TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。
错误信息:
EBADF:非法的文件描述符。
EINTR:tcsetattr函数调用被信号中断。
EINVAL:参数optional_actions使用了非法值,或参数termios中使用了非法值。
ENCTTY:非终端的文件描述符。
实例演练:
程序p6.2.c通过修改终端控制字符,将终端输入结束符由“Ctrl+D”,修改成了“Ctrl+G”。首先,程序调用 tcgetattr函数获得标准输入的termios信息,将termios结构体中的c_cc[VEOF]控制字符的修改成0x07(即 Ctrl+G);然后,使用tcsetattr函数将修改后的termios参数设置到终端中。具体代码如下所示:
|
使用gcc编译p6.2.c程序,得到名为p6.2的可执行程序。在执行p6.2程序前,按“Ctrl+D”可以使终端结束。执行p6.2程序后,按“Ctrl+D”失去了作用,而输入“Ctrl+G”实现了原来“Ctrl+D”的功能。
串口配置时一些重要且应注意的事项:
除了一般的波特率、数据位、校验位、停止位、超时配置外,还有一些配置也是很重要的,否则有可能出错丢码或者不正常的现象,在此做一份记录;
本人测试过的正常代码实例:
// 打开串口:
static INT32 Open_Dev(char *dev)
{
INT32 fd;
if((fd = open(dev, O_RDWR | O_NOCTTY | O_NDELAY )) == -1)
{
//perror(">can not open the dev!");
return -1;
}
return fd;
}
// 关闭串口:
static void Close_Dev(INT32 fd)
{
if(fd > 0)
{
close(fd);
}
}
// 设置波特率:
static INT32 Set_Baudrate(INT32 fd,UINT32 baudrate)
{
UINT32 speed_arr[] = {B57600,B38400,B19200,B9600,B4800,B2400,B1200,B600,B300,}; //define in termios.h
UINT32 baud_arr[] = {57600,38400,19200,9600,4800,2400,1200,600,300,};
UINT32 i;
UINT32 status;
struct termios opt;
tcgetattr(fd,&opt); //get attr
for(i=0; i<sizeof(baud_arr)/sizeof(UINT32); i++)
{
if(baudrate == baud_arr[i])
{
tcflush(fd,TCIOFLUSH); //flush
cfsetispeed(&opt,speed_arr[i]); //set in baudrate
cfsetospeed(&opt,speed_arr[i]); //set out baudrate
status = tcsetattr(fd,TCSANOW,&opt);
if(status != 0)
{
//perror(">Set_Baudrate: tcseattr failed!/n");
return RET_ERR;
}
tcflush(fd,TCIOFLUSH);
return RET_OK;
}
}
return RET_ERR;
}
// 设置数据位、校验位、停止位和其它重要配置:
static INT32 Set_Parity(INT32 fd,UINT32 databits,UINT32 stopbits,char parity)
{
struct termios opt;
if(tcgetattr(fd,&opt) != 0)
{
//perror(">Set_Parity: tcgetattr failed!/n");
return (RET_ERR);
}
//data bits
opt.c_cflag &= ~CSIZE;
switch (databits)
{
case 5:
opt.c_cflag |= CS5;
break;
case 6:
opt.c_cflag |= CS6;
break;
case 7:
opt.c_cflag |= CS7;
break;
case 8:
opt.c_cflag |= CS8;
break;
default:
//perror(">Unsupported databits/n");
return (RET_ERR);
break;
}
//parity
switch (parity)
{
case 'n':
case 'N':
opt.c_cflag &= ~PARENB; //Clear parity enable
opt.c_iflag &= ~INPCK; // Disnable parity checking
break;
case 'o':
case 'O':
opt.c_cflag |= (PARODD | PARENB);
opt.c_iflag |= INPCK; // Enable parity checking
break;
case 'e':
case 'E':
opt.c_cflag |= PARENB; // Enable parity
opt.c_cflag &= ~PARODD;
opt.c_iflag |= INPCK; // Enable parity checking
break;
case 'S':
case 's': //as no parity
opt.c_cflag &= ~PARENB;
opt.c_cflag &= ~CSTOPB;
break;
default:
//perror(">Unsupported parity/n");
return (RET_ERR);
break;
}
// stop bits
switch (stopbits)
{
case 1:
opt.c_cflag &= ~CSTOPB;
break;
case 2:
opt.c_cflag |= CSTOPB;
break;
default:
//perror(">Unsupported stopbits/n");
return (RET_ERR);
break;
}
tcflush(fd,TCIFLUSH); // Update the options and do it NOW
//time out(here no use because "open(o_NDELAY)")
opt.c_cc[VTIME] = 150; // 15 seconds (1/10sec)
opt.c_cc[VMIN] = 0;
/*---------------------- 重要 ----------------------*/
// 保证本程序不会成为端口的所有者,从而妨碍控制工作和挂起信号 .
opt.c_cflag |= (CLOCAL | CREAD);
// 选择行方式输入 : 行式输入是不经处理的 .
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 选择行式输出
opt.c_oflag &= ~OPOST;
// 取消软件流控制(不设置可能存在丢码)
opt.c_iflag &= ~(IXON | IXOFF | IXANY);
/*----------------------------------------------------*/
if (tcsetattr(fd,TCSANOW,&opt) != 0)
{
//perror(">Set_Parity: tcsetattr failed!/n");
return (RET_ERR);
}
return (RET_OK);
}
注意上面实例是中的红色字样(重要的设置);
设置数据位、校验位、停止位和其它重要配置中的红字部分:因为有些版本的 linux 可能存已默认配置,所以不用配置也可能,但如果默认配置不是这样而又没有进行配置则就可能会出现丢码或者收发码不正常的情况 (本人曾遇到过)!!!