串口属性设置
上一节的串口基本操作虽然可以进行基本的串口数据收发,但只能使用串口驱动默认的属性(9600,8n1,无流控),而在实际应用中,往往要设置串口属性如波特率、数据位、奇偶校验、停止位等。
接上篇《【Linux公开课】Linux串口编程》!
16.2 串口属性设置
上一节的串口基本操作虽然可以进行基本的串口数据收发,但只能使用串口驱动默认的属性(9600,8n1,无流控),而在实际应用中,往往要设置串口属性如波特率、数据位、奇偶校验、停止位等。
16.2.1 终端属性描述
前面提到过,进行串口编程时需要包含<termios.h>头文件。该文件包含了POSIX终端属性描述结构struct termios,该结构如程序清单16.5所示。
程序清单16.5 termios结构
struct termios {
tcflag_t c_cflag /* 控制标志 */
tcflag_t c_iflag; /* 输入标志 */
tcflag_t c_oflag; /* 输出标志 */
tcflag_t c_lflag; /* 本地标志 */
tcflag_t c_cc[NCCS]; /* 控制字符 */
};
tcflag_t的定义为:
typedef unsigned int tcflag_t;
下面对termios结构各成员进行简单介绍。
1、控制标志
通过termios结构的c_cflag成员可设置串口的波特率、数据位、奇偶校验、停止位以及流控制,详见后续对应部分的描述。
2、输入标志
c_iflag成员负责控制串口输入数据的处理,它的部分可用标志如表16.1所列。
表16.1 c_iflag标志
标志
|
说明
|
标志
|
说明
|
INPCK
|
打开输入奇偶校验
|
IXOFF
|
启用/停止输入控制流起作用
|
IGNPAR
|
忽略奇偶错字符
|
IGNBRK
|
忽略BREAK条件
|
PARMRK
|
标记奇偶错
|
INLCR
|
将输入的NL转换为CR
|
ISTRIP
|
剥除字符第8位
|
IGNCR
|
忽略CR
|
IXON
|
启用/停止输出控制流起作用
|
ICRNL
|
将输入的CR转换为NL
|
使用软件流控制是启用IXON、IXOFF和IXANY选项:
options.c_iflag |= (IXON | IXOFF | IXANY);
相反,要禁用软件流控制是禁止上面的选项:
options.c_iflag &= ~(IXON | IXOFF | IXANY);
3、输出标志
termios结构的c_oflag成员管理输出过滤,它的部分选项标志如表16.2所列。
表16.2 c_oflag标志
标志
|
说明
|
标志
|
说明
|
BSDLY
|
退格延迟屏蔽
|
OLCUC
|
将输出的小写字符转换为大写字符
|
CMSPAR
|
标志或空奇偶性
|
ONLCR
|
将NL转换为CR-NL
|
CRDLY
|
CR延迟屏蔽
|
ONLRET
|
NL执行CR功能
|
FFDLY
|
换页延迟屏蔽
|
ONOCR
|
在0列不输出CR
|
OCRNL
|
将输出的CR转换为NL
|
OPOST
|
执行输出处理
|
OFDEL
|
填充符为DEL,否则为NULL
|
OXTABS
|
将制表符扩充为空格
|
OFILL
|
对于延迟使用填充符
|
—
|
—
|
启用输出处理需要在c_oflag成员中启用OPOST选项,其操作方法如下:
options.c_oflag |= OPOST;
使用原始输出,就是禁用输出处理,使数据能不经过处理、过滤地完整地输出到串口。当OPOST被禁止,c_oflag其它选项也被忽略,其操作方法如下:
options.c_oflag &= ~OPOST;
4、本地标志
termios结构的c_lflag成员影响驱动程序和用户之间的接口,它的部分可用标志如表16.3所列。
表16.3 c_lflag标志
标志
|
说明
|
标志
|
说明
|
ISIG
|
启用终端产生的信号
|
NOFLSH
|
在中断或退出键后禁用刷清
|
ICANON
|
启用规范输入
|
IEXTEN
|
启用扩充的输入字符处理
|
XCASE
|
规范大/小写表示
|
ECHOCTL
|
回送控制字符为(char)
|
ECHO
|
进行回送
|
ECHOPRT
|
硬拷贝的可见擦除方式
|
ECHOE
|
可见擦除字符
|
ECHOKE
|
Kill的可见擦除
|
ECHOK
|
回送kill符
|
PENDIN
|
重新打印未决输入
|
ECHONL
|
回送NL
|
TOSTOP
|
对于后台输出发送SIGTTOU
|
规范模式是行处理的。调用read读取串口数据时,每次返回一行数据。当选择规范模式时,需要启用ICANON、ECHO和ECHOE选项:
options.c_lflag |= (ICANON | ECHO | ECHOE);
当串口设备作为用户终端时,通常要把串口设备配置成规范模式。
在原始模式下,串口输入数据是不经过处理的,在串口接口接收的数据被完整保留。要使串口设备工作在原始模式,需要关闭ICANON、ECHO、ECHOE和ISIG选项,其操作方法如下:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
5、控制字符组
termios结构的c_cc成员是一个数组,其长度是NCCS,一般介于15-20之间。c_cc数组的每个元素的下标都用一个宏表示,它的部分下标标志名及说明如表16.4所列。
表16.4 c_cc标志
标志
|
说明
|
标志
|
说明
|
VINTR
|
中断
|
VEOL
|
行结束
|
VQUIT
|
退出
|
VMIN
|
需读取的最小字节数
|
VERASE
|
擦除
|
VTIME
|
与“VMIN”配合使用,是指限定的传输或等待的最长时间
|
VEOF
|
行结束
|
—
|
—
|
16.2.2 获取和设置终端属性
使用函数tcgetattr()可以获取串口设备的termios结构。该函数原型如下:
int tcgetattr(int fd, struct termios *termptr);
函数执行成功返回0,串口设备的termios结构由temptr参数返回;若出错则返回-1。
获得termios结构后,可以把串口的属性设置到termios结构中。串口属性设置完成后,可通过tcsetattr()函数把新的属性设置应用到串口中。tcsetattr()函数原型如下:
int tcsetattr(int fd, int opt, const struct termios *termptr);
在串口驱动程序里有输入缓冲区和输出缓冲区。在改变串口属性时,缓冲区可能有数据存在,如何处理缓冲区中的数据,可通过opt参数实现:
上述两函数执行时,若成功则返回0,若出错则返回-1。
16.2.3 设置波特率
串口的波特率分输入波特率和输出波特率,可分别通过cfsetispeed()和cfsetospeed()函数设置。这两个函数原型为:
int cfsetispeed(struct termios *termptr, speed_t speed);
int cfsetospeed(struct termios *termptr, speed_t speed);
这两个函数若执行成功返回0,若出错则返回-1。speed参数为需要设置的波特率,可选择的常量如表16.5所列。
表16.5 波特率常量
标志
|
说明
|
标志
|
说明
|
B0
|
0位/秒(挂起)
|
B9600
|
9600位/秒
|
B110
|
100位/秒
|
B19200
|
19200位/秒
|
B134
|
134位/秒
|
B57600
|
57600位/秒
|
B1200
|
1200位/秒
|
B115200
|
115200位/秒
|
B2400
|
2400位/秒
|
B460800
|
460800位/秒
|
B4800
|
4800位/秒
|
—
|
—
|
通常来说,串口的输入和输出波特率都设置为同一个值,如将波特率设置为115200的代码为:
cfsetispeed(&opt, B115200);
cfsetospeed(&opt, B115200);
程序清单16.6所示的set_baudrate()函数实现了波特率设置操作。该函数将串口输入/输出设置为相同的波特率,使用时只需填写所需波特率即可。
程序清单16.6 set_baudrate()函数
static void set_baudrate (struct termios *opt, unsigned int baudrate)
{
cfsetispeed(opt, baudrate);
cfsetospeed(opt, baudrate);
}
使用set_baudrate()函数设置串口输入/输出波特率为115200的代码为:
set_baudrate(&opt, B115200));
16.2.4 设置数据位
设置串口数据位是在termios结构的c_cflag成员上设置,可用的选项标志如表16.6所列。
表16.6 设置数据位可用的标志选项
标志
|
说明
|
标志
|
说明
|
CSIZE
|
数据位屏蔽
|
CS7
|
7位数据位
|
CS5
|
5位数据位
|
CS8
|
8位数据位
|
CS6
|
6位数据位
|
—
|
—
|
设置串口的数据位为8位的代码为:
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
在该代码中,把CS8改成CS5、CS6或CS7,分别可以把串口的数据位设置为5位、6位或7位。
程序清单16.7所示的set_data_bit()函数实了串口数据位的设置。
程序清单16.7 set_data_bit函数
static void set_data_bit (struct termios *opt, unsigned int databit)
{
opt->c_cflag &= ~CSIZE;
switch (databit) {
case 8:
opt->c_cflag |= CS8;
break;
case 7:
opt->c_cflag |= CS7;
break;
case 6:
opt->c_cflag |= CS6;
break;
case 5:
opt->c_cflag |= CS5;
break;
default:
opt->c_cflag |= CS8;
break;
}
}
在set_data_bit()函数中,databit参数可以取值为8、7、6、5,分别表示把数据位设置为8位、7位、6位、5位。
使用set_data_bit()函数设置8位数据位的代码如下:
set_data_bit(8)
16.2.5 设置奇偶校验
设置串口的奇偶校验是在termios结构的c_cflag成员上设置,可用的选项标志如表16.7所列。
表16.7 奇偶校验标志
标志
|
说明
|
PARENB
|
进行奇偶校验
|
PARODD
|
奇校验,否则为偶校验
|
Linux的串口驱动支持无校验(‘N’)、偶校验(‘E’)和奇校验(‘O’)。
opt->c_cflag &= ~PARENB;
opt->c_cflag |= PARENB;
opt->c_cflag &= ~PARODD;
opt->c_cflag |= PARENB;
opt->c_cflag |= ~PARODD;
程序清单16.8所示的set_parity()函数实现了串口奇偶校验设置。
程序清单16.8 set_parity函数
static void set_parity (struct termios *opt, char parity)
{
switch (parity) {
case 'N': /* 无校验 */
case 'n':
opt->c_cflag &= ~PARENB;
break;
case 'E': /* 偶校验 */
case ‘e‘:
opt->c_cflag |= PARENB;
opt->c_cflag &= ~PARODD;
break;
case 'O': /* 奇校验 */
case ‘o‘:
opt->c_cflag |= PARENB;
opt->c_cflag |= ~PARODD;
break;
default: /* 其它选择为无校验 */
opt->c_cflag &= ~PARENB;
break;
}
}
在set_parity函数中,parity参数可以取值为:‘N’和‘n’(无奇偶校验)、‘E’和‘e’(表示偶校验)、‘O’和‘o’(表示奇校验)。
设置串口为无校验的代码如下:
static void set_parity (&opt, ‘N’);
或
static void set_parity (&opt, ‘n’);
16.2.6 设置停止位
设置串口停止位是在termios对象的c_cflag成员上设置,需要用到的选项标志为CSTOPB(2位停止位,否则为1位)。
例如,设置1位停止位的方法为:
opt->c_cflag &= ~CSTOPB;
程序清单16.9所示的set_stopbit()函数实现串口停止位的设置。
程序清单16.9 set_parity函数
static void set_stopbit (struct termios *opt, const char *stopbit)
{
if (0 == strcmp (stopbit, "1")) {
opt->c_cflag &= ~CSTOPB; /* 1位停止位t */
} else if (0 == strcmp (stopbit, "1.5")) {
opt->c_cflag &= ~CSTOPB; /* 1.5位停止位 */
}else if (0 == strcmp (stopbit, "2")) {
opt->c_cflag |= CSTOPB; /* 2 位停止位 */
}else {
opt->c_cflag &= ~CSTOPB; /* 1 位停止位 */
}
}
在set_stopbit()函数中,stopbit参数可以取值为:“1”(1位停止位)、“1.5”(1.5位停止位)和“2”(2位停止位)。
设置串口为1位停止位的代码如下:
set_stopbit(&opt, "1");
16.2.7 其它设置
调用read()函数读取串口数据时,返回读取数据的数量需要考虑两个变量:MIN和TIME。MIN和TIME在termios结构的c_cc成员的数组下标名为VMIN和VTIME。
MIN是指一次read调用期望返回的最小字节数。VTIME说明等待数据到达的分秒数(秒的1/10为分秒)。TIME与MIN组合使用的具体含义分为以下四种情形:
计时器在收到第一个字节后启动,在计时器超时之前(TIME的时间到),若已收到MIN个字节,则read返回MIN个字节,否则,在计时器超时后返回实际接收到的字节。
注意:因为只有在接收到第一个字节时才开始计时,所以至少可以返回1个字节。这种情形中,在接到第一个字节之前,调用者阻塞。如果在调用read时数据已经可用,则如同在read后数据立即被接到一样。
MIN个字节完整接收后,read才返回,这可能会造成read无限期地阻塞。
TIME为允许等待的最大时间,计时器在调用read时立即启动,在串口接到1字节数据或者计时器超时后即返回,如果是计时器超时,则返回0。
如果有数据可用,则read最多返回所要求的字节数,如果无数据可用,则read立即返回0。
设置TIME为150、MIN为255的方法如下:
opt.c_cc[VTIME] = 150;
opt.c_cc[VMIN] = 255;
16.2.8 串口属性设置函数
程序清单16.10所示的set_port_attr函数实现了串口属性的设置。
程序清单16.10 终端属性设置函数
int set_port_attr (int fd,int baudrate, int databit, const char *stopbit, char parity, int vtime,int vmin )
{
struct termios opt;
tcgetattr(fd, &opt);
set_baudrate(&opt, baudrate);
opt.c_cflag |= CLOCAL | CREAD; /* | CRTSCTS */
set_data_bit(&opt, databit);
set_parity(&opt, parity);
set_stopbit(&opt, stopbit);
opt.c_oflag = 0;
opt.c_lflag |= 0;
opt.c_oflag &= ~OPOST;
opt.c_cc[VTIME] = vtime;
opt.c_cc[VMIN] = vmin;
tcflush (fd, TCIFLUSH);
return (tcsetattr (fd, TCSANOW, &opt));
}
set_port_attr函数调用成功时,返回0;调用失败时,返回-1。
设置串口属性为“115200、8n1”的代码如下:
ret = set_port_attr (fd, B115200, 8, "1", 'N', 150, 255 );
if(ret < 0) {
printf("set uart arrt faile \n");
exit(-1);
}
16.2.9 串口范例2
程序清单16.11所示代码可以设置串口属性后,作数据收发操作。在该代码中,程序在打开串口设备后,把串口属性设置为“115200 8n1”;然后在串口发送“hello ZLG!”的字符串,然后准备接收字符串;在接收了字符串之后,把接收的字符打印出来。
程序清单16.11 串口操作示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <asm/termios.h>
#include "serial.h"
#define DEV_NAME "/dev/ttyS1"
int main (int argc, char *argv[])
{
int fd;
int len, i,ret;
char buf[] = "hello ZLG!";
fd = open(DEV_NAME, O_RDWR | O_NOCTTY);
if(fd < 0) {
perror(DEV_NAME);
return -1;
}
ret = set_port_attr (fd, B115200, 8, "1", 'N',150,255 ); /* 115200 8n1 */
if(ret < 0) {
printf("set uart arrt faile \n");
exit(-1);
}
len = write(fd, buf, sizeof(buf)); /* 向串口发送字符串 */
if (len < 0) {
printf("write data error \n");
return -1;
}
len = read(fd, buf, sizeof(buf)); /* 在串口读取字符串 */
if (len < 0) {
printf("read error \n");
return -1;
}
printf("%s \n", buf); /* 打印在串口读取的字符串 */
return(0);
}
串口范例2代码可用的测试方法如下:
1、把示例代码在Linux主机上作本地编译,生成test_uart_2程序文件;
2、使用另外一台Windows电脑和Linux主机建立串口连接;
3、在Windows电脑打开串口助手软件,设置串口属性为“15200,8n1,无流控”;
4、在Linux主机运行test_uart_2程序:
[email protected]:~/uart_test$ sudo ./test_uart_2
这时串口助手软件接收到“hello ZLG!”字符串,如图16.4所示。
图16.4 test_uart_2程序发送数据成功
5、在串口助手的发送“hello ZLG!”的字符串;
这时Linux主机的test_uart_2程序打印从串口接收的字符串如图16.5所示。
图16.5 test_uart_2接收数据成功