12.串口通讯与终端设备¶

12.4.2. termios结构体¶

示例代码中的第三部分,使用了termios结构体,它是在POSIX规范中定义的标准接口。

Linux系统利用termios来设置串口的参数,它是在头文件包含的中定义的,

该文件中还包含了各个结构体成员可使用的宏值,

请自己使用locate命令查找该文件打开来阅读,关于termios结构体的定义摘录如下所示。

termios结构体(位于主机/usr/include/bits/termios.h文件)¶

1

2

3

4

5

6

7

8

9

10

11

12struct termios {

tcflag_t c_iflag; /* input mode flags */

tcflag_t c_oflag; /* output mode flags */

tcflag_t c_cflag; /* control mode flags */

tcflag_t c_lflag; /* local mode flags */

cc_t c_line; /* line discipline */

cc_t c_cc[NCCS]; /* control characters */

speed_t c_ispeed; /* input speed */

speed_t c_ospeed; /* output speed */

#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1

#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1

};

下面我们介绍一下各个结构体成员,主要是关注c_iflag、c_cflag以及c_ispeed、c_ospeed即可:

c_iflag:输入(input)模式标志,用于控制如何对串口输入的字符进行处理,常用的选项值见下表。

表 c_iflag选项值

选项值

作用

INPCK

启用输入奇偶检测

IGNPAR

忽略帧错误和奇偶检验错误

IGNCR

忽略输入中的回车

IXON

开启XON/XOFF流控制

IXOFF

关闭XON/XOFF流控制

c_oflag:输出(output)模式标志,用于控制串口的输出模式,常用的选项值见下表。

表 c_oflag选项值

选项值

作用

ONLCR

将输出中的换行符NL映射为回车-换行CR

OCRNL

将输出的回车映射为换行符

ONLRET

不输出回车

OFILL

发送填充字符串

c_cflag:控制(control)模式标志,用于控制串口的基本参数,如数据位、停止位等,

常用配置见下表,特别地,c_cflag结构体成员还包含了波特率的参数。

表 c_cflag选项值

选项值

作用

CSIZE

设置数据位长度,可以配置为CS5、CS6、CS7、CS8。

CSTOPB

如果设置 CSTOPB 标志,则使用两位停止位

PARENB

使能奇偶检验

PARODD

设置为奇校验

c_lflag:本地(local)模式标志,主要用于控制驱动程序与用户的交互,在串口通信中,

实际上用不到该成员变量。

选项值

作用

ISIG

如果设置 ISIG 标志,当接收到字符INTR、QUIT等字符,系统会产生相应的信号。

ECHO

是否需要回显字符

ICANON

若设置了 ICANON 标志,则表示终端处于规范式输入状态,允许使用特殊字符EOF、KILL等

ECHONL

若该标志位和ICANON标志位同时被设置,则回显换行符NL

c_cc[NCCS]:该数组包含了终端的所有特殊字符,

可以修改特殊字符对应的键值(Ctrl+C产生的^C,ASCII码为0x03),部分内容如下表。

表 c_cc中各成员对应的下标值

数组的下标值

作用

VINTR

中断字符,若接收到该字符时,会发送SIGINT信号。当设置了c_lflag的ISIG标志位时,该字母不再作为输入传递。

VERASE

删除字符,删除上一个字符。

VIM

设置非标准模式读取的最小字节数

VTIM

设置非标准模式读取时的延时值,单位为十分之一秒。

c_ispeed和c_ospeed:记录串口的输入和输出波特率(input speed和output speed),

部分可取值如下代码所示,宏定义中的数字以“0”开头,在C语言中这是表示8进制数字的方式。

波特率定义(位于/usr/include/bits/termios.h)¶

1

2

3

4

5

6

7

8//注意以0开头的数字在是C语言的8进制数字形式

#define B1200 0000011

#define B1800 0000012

#define B2400 0000013

#define B4800 0000014

#define B9600 0000015

#define B19200 0000016

#define B38400 0000017

宏定义:termios结构体内部有_HAVE_STRUCT_TERMIOS_C_ISPEED和_HAVE_STRUCT_TERMIOS_C_OSPEED

两个宏定义,它们的宏值都为1,表示它支持c_ispeed和c_ospeed表示方式,

部分标准中不支持使用这两个结构体成员表示波特率,而只使用c_cflag来表示。

直接看结构体的定义比较抽象,下面我们以修改串口波特率、数据位、校验位和停止位的示例代码进行讲解。

接下来几个小节的代码,是我们从配套代码仓库/linux_app/tty/c_full/sources/bsp_uart.c文件截取的,

该文件以比较完善的方式封装了串口的配置,而本书提取出了代码中的重点进行分析,

感兴趣的读者可以打开配套的工程文件阅读。

12.4.2.1. 配置串口波特率¶

修改终端串口波特率的示例代码如下所示。

示例代码-修改串口波特率¶

1

2

3

4

5

6

7

8

9

10

11//定义termios型变量opt

struct termios opt;

//fd是使用open打开设备文件得到的文件句柄

// 获取串口参数opt

tcgetattr(fd, &opt);

//设置串口输出波特率

cfsetospeed(&opt, B9600);

//设置串口输入波特率

cfsetispeed(&opt, B9600);

//更新配置

tcsetattr(fd, TCSANOW, &opt);

代码中使用到了头文件termios.h的库函数tcgetattr、cfsetispeed、cfsetospeed和tcsetattr。

其中tcgetattr和tcsetattr函数分别用于读取和设置串口的参数,原型如下:

1

2

3

4

5

6

7#include

#include

int tcgetattr(int fd, struct termios *termios_p);

int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);

形参fd:指定串口设备文件的文件描述符。

形参termios_p:指向串口参数的结构体termios,tcgetattr读取到的参数会保存在该结构体中,

而tcsetattr则根据该结构体配置设备参数。

形参optional_actions:仅tcsetattr函数有这个参数,它用于指示配置什么时候生效,

它支持的配置参数如下:

TCSANOW表示立即生效。

TCSADRAIN表示待所有数据传输结束后配置生效。

TCSAFLUSH表示输入输出缓冲区为空时配置有效。

跟示例代码中的一样,通常都使用选项TCSANOW,让写入的参数配置立马生效。

代码中的cfsetispeed和cfsetospeed函数分别用于设置termios结构体的输入和输出波特率,

另外还有cfsetspeed函数可以同时设置输入和输出波特率参数为相同的值,原型如下:

1

2

3

4

5int cfsetispeed(struct termios *termios_p, speed_t speed);

int cfsetospeed(struct termios *termios_p, speed_t speed);

int cfsetspeed(struct termios *termios_p, speed_t speed);

使用这些函数要注意两点:

speed参数需要使用类似前面代码定义的宏值。

这三个函数只是修改了termios的opt变量的内容,并没有写入到设备文件,

因此在修改完它的内容后,还需要调用tcsetattr函数,把opt变量中的配置写入到设备,使它生效。

这就是修改终端设备参数的过程,读取原配置、修改termios参数、写入termios参数。

12.4.2.2. 配置串口停止位¶

c_cflag中的标志位CSTOPB,用于设置串口通信停止位的长度。若该值为0,

则停止位的长度为1位;若设置该位为1,则停止位的长度为两位,具体实现如下所示。

示例代码-配置停止位¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25//在bits/termios.h文件中关于CSTOPB的定义

//注意以0开头的数字在是C语言的8进制数字形式

#define CSTOPB 0000100

//

//设置停止位示例

//定义termios型变量opt

struct termios opt;

// 获取串口参数opt

tcgetattr(fd, &opt);

/* 设置停止位*/

switch (stopbits)

{

//设置停止位为1位

case 1:

opt.c_cflag &= ~CSTOPB;

break;

//设置停止位为2位

case 2:

opt.c_cflag |= CSTOPB;

break;

}

//更新配置

tcsetattr(fd, TCSANOW, &opt);

示例代码依然是采取了获取当前参数、修改配置、更新配置的套路。

修改配置的代码中使用了“&=~”、“|=”这种位操作方法,主要是为了避免影响到变量中的其它位,

因为在c_cflag的其它位还包含了校验位、数据位和波特率相关的配置,如果直接使用“=”赋值,

那其它配置都会受到影响,而且操作不方便。在后面学习裸机开发,对寄存器操作时会经常用到这种方式。

若没接触过这些位操作方式,可参考本书附录中《第65章 位操作方法》的说明。

简单来说,示例中的“&=~”把c_cflag变量中CSTOPB对应的数据位清0,

而“|=”则把c_cflag变量中CSTOPB对应的数据位置1,

达到在不影响其它配置的情况下把停止位配置为1位或两位。

12.4.2.3. 配置串口校验位¶

配置串口的校验位涉及到termios成员c_cflag的标志位PARENB、PARODD 以及c_iflag的标志位INPCK,

其中PARENB和INPCK共同决定是否使能奇偶校验,而PARODD 决定使用奇校验还是偶校验,

配置的示例代码如下所示。

示例代码-配置奇偶校验¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39//bits/termios.h的位定义

//注意以0开头的数字在是C语言的8进制数字形式

/* c_cflag bit meaning */

#define PARENB 0000400

#define PARODD 0001000

/* c_iflag bits */

#define INPCK 0000020

//

//定义termios型变量opt

struct termios opt;

// 获取串口参数opt

tcgetattr(fd, &opt);

switch (parity)

{

case 'n':

case 'N':

options.c_cflag &= ~PARENB; /* 不使用奇偶校验 */

options.c_iflag &= ~INPCK; /* 禁止输入奇偶检测 */

break;

case 'o':

case 'O':

options.c_cflag |= PARENB; /* 启用奇偶效验 */

options.c_iflag |= INPCK; /* 启用输入奇偶检测 */

options.c_cflag |= PARODD ; /* 设置为奇效验 */

break;

case 'e':

case 'E':

options.c_cflag |= PARENB; /* 启用奇偶效验 */

options.c_iflag |= INPCK; /* 启用输入奇偶检测 */

options.c_cflag &= ~PARODD; /* 设置为偶效验*/

break;

}

//更新配置

tcsetattr(fd, TCSANOW, &opt);

配置非常简单,不校验时同时把PARENB和INPCK位清零,启用校验时把PARENB和INPCK同时置1,

而PARODD为1时指定为奇校验,为0时是偶校验。

12.4.2.4. 配置串口数据位¶

串口的数据位是由c_cflag中的CSIZE配置的,由于串口支持5、6、7、8位的配置,一共有四种,

所以在c_cflag中使用了两个数据位进行配置,在配置前我们需要先对CSIZE数据位清零,

然后再赋予5、6、7、8的宏配置值,具体代码如下所示。

示例代码-设置数据位长度位¶

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33//bits/termios.h的位定义

//注意以0开头的数字在是C语言的8进制数字形式

#define CSIZE 0000060

#define CS5 0000000

#define CS6 0000020

#define CS7 0000040

#define CS8 0000060

//

//定义termios型变量opt

struct termios opt;

// 获取串口参数opt

tcgetattr(fd, &opt);

//先清除CSIZE数据位的内容

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;

}

//更新配置

tcsetattr(fd, TCSANOW, &opt);

学习了使用termios结构体配置串口参数的各种方式后,请再回过头看看前面的main.c示例文件代码,

相信已经不用再介绍了。

Top