Linux termios 分析

termios是在Posix规范中定义的标准接口,表示终端设备(包括虚拟终端、串口等)。因为串口是一种终端设备,所以通过终端编程接口对其进行配置和控制。

A、termios结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
struct 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 */ /*行控制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
};

1. c_iflag 标志常量:Input mode (输入模式)

input mode可以在输入值传给程序之前控制其处理的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* c_iflag bits */
#define IGNBRK 0000001 //忽略输入中的 BREAK 状态(忽略命令行中的中断)
#define BRKINT 0000002 //当发生中断时发送SIGINT信号;如果设置了GNBRK,将忽略 BREAK
#define IGNPAR 0000004 //忽略桢错误和奇偶校验错
#define PARMRK 0000010 //标记奇偶校验错误
#define INPCK 0000020 //启用输入奇偶检测
#define ISTRIP 0000040 //去掉输入字符的第8位
#define INLCR 0000100 //将输入的NL转换为CR(将收到的换行符号转换为回车)
#define IGNCR 0000200 //忽略CR。(忽略输入中的回车)
#define ICRNL 0000400 //将输入的CR映射到NL(当输入信号有 CR 时不会终止输入)
#define IUCLC 0001000 //将输入的大写字符转换为小写字符
#define IXON 0002000 //启用输出的 XON/XOFF 流控制。( 启动输出软件流控)
#define IXANY 0004000 //允许字符重新启动输出
#define IXOFF 0010000 //启用输入的 XON/XOFF 流控制。
#define IMAXBEL 0020000 //当输入队列满时响零;Linux 没有实现这一位,总是将它视为已设置。
#define IUTF8 0040000

2. c_oflag 标志常量:Output mode (输出模式)

Output mode主要负责控制输出字元的处理方式。

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
40
/* c_oflag bits */
#define OPOST 0000001 //启用具体实现自行定义的输出处理
#define OLCUC 0000002 //将输出中的小写字母转化为大写字母
#define ONLCR 0000004 //将输出中的NL转换为CR
#define OCRNL 0000010
#define ONOCR 0000020 //不在第 0 列输出回车
#define ONLRET 0000040 //不输出回车
#define OFILL 0000100 //发送填充字符作为延时,而不是使用定时来延时
#define OFDEL 0000200 //填充字符是 ASCII DEL (0177)。如果不设置,填充字符则是 ASCII NUL
#if defined __USE_MISC || defined __USE_XOPEN
# define NLDLY 0000400 //新行延时掩码。取值为 NL0 和 NL1
# define NL0 0000000
# define NL1 0000400
# define CRDLY 0003000 //回车延时掩码。取值为 CR0, CR1, CR2, 或 CR3
# define CR0 0000000
# define CR1 0001000
# define CR2 0002000
# define CR3 0003000
# define TABDLY 0014000 //水平跳格延时掩码。取值为 TAB0, TAB1, TAB2, TAB3(或 XTABS)
# define TAB0 0000000
# define TAB1 0004000
# define TAB2 0010000
# define TAB3 0014000
# define BSDLY 0020000 //回退延时掩码。取值为 BS0 或 BS1。(从来没有被实现过)
# define BS0 0000000
# define BS1 0020000
# define FFDLY 0100000 //进表延时掩码。取值为 FF0 或 FF1
# define FF0 0000000
# define FF1 0100000
#endif


#define VTDLY 0040000 //竖直跳格延时掩码。取值为 VT0 或 VT1
#define VT0 0000000
#define VT1 0040000


#ifdef __USE_MISC
# define XTABS 0014000
#endif

3. c_cflag 标志常量:Control mode ( 控制模式)

Control mode主要用于控制终端设备的硬件设置。利用termios结构的c_cflag的标志来加以控制。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/* c_cflag bit meaning */
#ifdef __USE_MISC
# define CBAUD 0010017 //波特率的位掩码 (4+1 位)
#endif
#define B0 0000000 /* hang up *///0波特率(放弃DTR)
#define B50 0000001
#define B75 0000002
#define B110 0000003
#define B134 0000004
#define B150 0000005
#define B200 0000006
#define B300 0000007
#define B600 0000010
#define B1200 0000011
#define B1800 0000012
#define B2400 0000013
#define B4800 0000014
#define B9600 0000015
#define B19200 0000016
#define B38400 0000017
#ifdef __USE_MISC
# define EXTA B19200
# define EXTB B38400
#endif
#define CSIZE 0000060 //数据位的位掩码(字符长度掩码)
#define CS5 0000000 //5个数据位
#define CS6 0000020
#define CS7 0000040
#define CS8 0000060 //8个数据位
#define CSTOPB 0000100 //设置两位停止位,否则为一位
#define CREAD 0000200 //接收使能
#define PARENB 0000400 //校验位使能
#define PARODD 0001000 //使用奇校验而不使用偶校验
#define HUPCL 0002000 //关闭时挂断(放弃DTR)
#define CLOCAL 0004000 //本地链接(不改变端口所有者)
#ifdef __USE_MISC
# define CBAUDEX 0010000
#endif
#define B57600 0010001
#define B115200 0010002
#define B230400 0010003
#define B460800 0010004
#define B500000 0010005
#define B576000 0010006
#define B921600 0010007
#define B1000000 0010010
#define B1152000 0010011
#define B1500000 0010012
#define B2000000 0010013
#define B2500000 0010014
#define B3000000 0010015
#define B3500000 0010016
#define B4000000 0010017
#define __MAX_BAUD B4000000
#ifdef __USE_MISC
# define CIBAUD 002003600000 /* input baud rate (not used) *///输入速度的掩码
# define CMSPAR 010000000000 /* mark or space (stick) parity */
# define CRTSCTS 020000000000 /* flow control *///使能硬件流控制
#endif

4. c_lflag 标志常量:Local mode (局部模式)

Local mode主要用来控制终端设备不同的特色。利用termios结构里的c_lflag的标志来设定局部模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* c_lflag bits */
#define ISIG 0000001 //若收到信号字符(INTR、QUIT等),则会产生相应的信号
#define ICANON 0000002 //终端规范模式,默认为规范模式,否则为非规范模式
#if defined __USE_MISC || (defined __USE_XOPEN && !defined __USE_XOPEN2K)
# define XCASE 0000004 //(Linux 下不被支持)如果同时设置了ICANON,终端只有大写
#endif
#define ECHO 0000010 //启动输入字符的本地本地回显功能
#define ECHOE 0000020 //若设置ICANNO,则允许退格操作
#define ECHOK 0000040 //若设置ICANNO,则KILL字符会删除当前行
#define ECHONL 0000100 //若设置ICANNO, 则允许回显换行符
#define NOFLSH 0000200 //禁止在产生 SIGINT, SIGQUIT 和 SIGSUSP 信号时刷新输入和输出队列
#define TOSTOP 0000400 //向试图写控制终端的后台进程组发送 SIGTTOU 信号
#ifdef __USE_MISC
# define ECHOCTL 0001000 //若设置ECHO,则控制字符(制表符、换行符)会显示成"^X",其中X的ASCII码
等于给相应控制字符的ASCII码加上0x40
# define ECHOPRT 0002000 //若设置ICANNO,字符在删除的同时被打印
# define ECHOKE 0004000 //若设置ICANNO,回显删除一行中的每个字符
# define FLUSHO 0010000 //(Linux 下不被支持)输出被刷新
# define PENDIN 0040000 //(Linux 下不被支持)在读入下一个字符时,输入队列中所有字符被重新输出
#endif
#define IEXTEN 0100000 //启用自定义的输入处理,这个标志必须与 ICANON同时使用
#ifdef __USE_MISC
# define EXTPROC 0200000
#endif

5. c_cc 数组:特殊控制字元可提供使用者设定一些特殊的功能

特殊控制字元主要是利用termios结构里c_cc的阵列成员 来做设定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* c_cc characters */
#define VINTR 0 //中断字符。发出 SIGINT 信号。
#define VQUIT 1 //退出字符。发出 SIGQUIT 信号。
#define VERASE 2 //删除字符。删除上一个还没有删掉的字符,但不删除上一个EOF 或行首。
#define VKILL 3 //终止字符。删除自上一个 EOF 或行首以来的输入。
#define VEOF 4 //文件尾字符。这个字符使得 tty 缓冲中的内容被送到等待输入的用户程序中,而不必等到 EOL。
#define VTIME 5 //非 canonical 模式读时的延时,以十分之一秒为单位
#define VMIN 6 //VMIN :非 canonical 模式读的最小字符数
#define VSWTC 7 //(Linux 下不被支持)开关字符。
#define VSTART 8 //开始字符。重新开始被 Stop 字符中止的输出。
#define VSTOP 9 //停止字符。
#define VSUSP 10 //挂起字符。
#define VEOL 11 //附加的行尾字符。
#define VREPRINT 12 //重新输出未读的字符。
#define VDISCARD 13 //(Linux 下不被支持)开关:开始/结束丢弃未完成的输出。
#define VWERASE 14 //删除词。
#define VLNEXT 15 //字面上的下一个。引用下一个输入字符,取消它的任何特殊含义。
#define VEOL2 16 //另一个行尾字符。

6. 其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* tcflow() and TCXONC use these */
#define TCOOFF 0
#define TCOON 1
#define TCIOFF 2
#define TCION 3


/* tcflush() and TCFLSH use these */
#define TCIFLUSH 0
#define TCOFLUSH 1
#define TCIOFLUSH 2


/* tcsetattr uses these */
#define TCSANOW 0
#define TCSADRAIN 1
#define TCSAFLUSH 2

B. 相关函数

1. tcgetattr

1
2
/* Put the state of FD into *TERMIOS_P.  */
extern int tcgetattr (int __fd, struct termios *__termios_p) __THROW;

取得终端介质(fd)初始值,并把其值 赋给temios_p;函数可以从后台进程中调用;但是,终端属性可能被后来的前 台进程所改变。

2. tcsetattr

1
2
3
4
/* Set the state of FD to *TERMIOS_P.
Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>. */
extern int tcsetattr (int __fd, int __optional_actions,
const struct termios *__termios_p) __THROW;

设置与终端相关的参数 (除非需要底层支持却无法满足),使用termios_p 引用的 termios 结构。optional_actions (tcsetattr函数的第二个参数)指定了什么时候改变会起作用:

  • TCSANOW:改变立即发生
  • TCSADRAIN:改变在所有写入 fd 的输出都被传输后生效。
  • TCSAFLUSH :改变在所有写入 fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃

3. tcsendbreak

1
2
/* Send zero bits on FD.  */
extern int tcsendbreak (int __fd, int __duration) __THROW;

传送连续的 0 值比特流,持续一段时间,如果终端使用异步串行数据传输的话。如果 duration 是 0,它至少传输 0.25 秒,不会超过 0.5 秒。如果duration 非零,它发送的时间长度由实现定义。如果终端并非使用异步串行数据传输,tcsendbreak() 什么都不做。

4. tcdrain

1
2
3
4
5
6
/* Wait for pending output to be written on FD.


This function is a cancellation point and therefore not marked with
__THROW. */
extern int tcdrain (int __fd);

等待直到所有写入 fd 引用的对象的输出都被传输。

5. tcflush

1
2
3
/* Flush pending data on FD.
Values for QUEUE_SELECTOR (TC{I,O,IO}FLUSH) are in <bits/termios.h>. */
extern int tcflush (int __fd, int __queue_selector) __THROW;

丢弃要写入引用的对象,但是尚未传输的数据,或者收到但是尚未读取的数据,取决于 queue_selector 的值:

  • TCIFLUSH:刷新收到的数据但是不读
  • TCOFLUSH:刷新写入的数据但是不传送
  • TCIOFLUSH:同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送

6. tcflow

1
2
3
/* Suspend or restart transmission on FD.
Values for ACTION (TC[IO]{OFF,ON}) are in <bits/termios.h>. */
extern int tcflow (int __fd, int __action) __THROW;

挂起 fd 引用的对象上的数据传输或接收,取决于 action 的值:

  • TCOOFF :挂起输出
  • TCOON :重新开始被挂起的输出
  • TCIOFF :发送一个 STOP 字符,停止终端设备向系统传送数据
  • TCION :发送一个 START 字符,使终端设备向系统传输数据打开一个终端设备时的默认设置是输入和输出都没有挂起。

7. 波特率函数

被用来获取和设置 termios 结构中,输入和输出波特率的值。新值不会马上生效,直到成功调用了 tcsetattr() 函数。设置速度为 B0 使得 modem “挂机”。与 B38400 相应的实际比特率可以用setserial(8) 调整。 输入和输出波特率被保存于 termios 结构中。

1
2
3
4
5
6
7
8
9
10
11
/* Return the output baud rate stored in *TERMIOS_P.  */
extern speed_t cfgetospeed (const struct termios *__termios_p) __THROW;

/* Return the input baud rate stored in *TERMIOS_P. */
extern speed_t cfgetispeed (const struct termios *__termios_p) __THROW;

/* Set the output baud rate stored in *TERMIOS_P to SPEED. */
extern int cfsetospeed (struct termios *__termios_p, speed_t __speed) __THROW;

/* Set the input baud rate stored in *TERMIOS_P to SPEED. */
extern int cfsetispeed (struct termios *__termios_p, speed_t __speed) __THROW;
  • cfgetospeed() 返回 termios_p 指向的 termios 结构中存储的输出波特率
  • cfgetispeed() 返回 termios结构中存储的输入波特率
  • cfsetospeed() 设置 termios_p 指向的 termios 结构中存储的输出波特率为speed
  • cfsetispeed() 设置 termios 结构中存储的输入波特率为 speed

8. 返回值

cfgetospeed()、cfgetispeed() 返回 termios 结构中存储的输入波特率。
其他函数返回 0-成功;-1-失败,并且为 errno 置值来指示错误。

C. 实例:串口

串口是计算机上一种非常通用设备通信的协议,常用PC机上包含的是RS232规格的串口,具有连接线少,通讯简单,得到广泛的使用。

Linux对所有设备的访问是通过设备文件来进行的,串口也是这样,为了访问串口,只需打开其设备文件即可操作串口设备。在linux系统下面,每一个串口设备都有设备文件与其关联,设备文件位于系统的/dev目录下面。如linux下的/ttyS0,/ttyS1分别表示的是串口1和串口2。

在串口编程中,比较重要的是串口的设置,需要设置的部分包括:波特率,数据位,停止位,奇偶校验位

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
/* stty.h 头文件*/
#ifndef __STTY_H__
#define __STTY_H__

//包含头文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <pthread.h>

// 串口设备信息结构
typedef struct tty_info_t
{
int fd; // 串口设备ID
pthread_mutex_t mt; // 线程同步互斥对象
char name[24]; // 串口设备名称,例:"/dev/ttyS0"
struct termios ntm; // 新的串口设备选项
struct termios otm; // 旧的串口设备选项
} TTY_INFO;
//

// 串口操作函数
TTY_INFO *readyTTY(int id);
int setTTYSpeed(TTY_INFO *ptty, int speed);
int setTTYParity(TTY_INFO *ptty, int databits, int parity, int stopbits);
int cleanTTY(TTY_INFO *ptty);
int sendnTTY(TTY_INFO *ptty, char *pbuf,int size);
int recvnTTY(TTY_INFO *ptty, char *pbuf,int size);
int lockTTY(TTY_INFO *ptty);
int unlockTTY(TTY_INFO *ptty);

#endif
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/* stty.c 源码*/
/*从头文件中的函数定义不难看出,函数的功能,使用过程如下:
(1) 打开串口设备,调用函数setTTYSpeed();
(2) 设置串口读写的波特率,调用函数setTTYSpeed();
(3) 设置串口的属性,包括停止位、校验位、数据位等,调用函数setTTYParity();
(4) 向串口写入数据,调用函数sendnTTY();
(5) 从串口读出数据,调用函数recvnTTY();
(6) 操作完成后,需要调用函数cleanTTY()来释放申请的串口信息接口;
其中,lockTTY()和unlockTTY()是为了能够在多线程中使用。
在读写操作的前后,需要锁定和释放串口资源。 */

#include <stdio.h>
#include <sys/ioctl.h>
#include "stty.h"

// 初始化串口设备并进行原有设置的保存
TTY_INFO *readyTTY(int id)
{
TTY_INFO *ptty;

ptty = (TTY_INFO *)malloc(sizeof(TTY_INFO));
if(ptty == NULL)
return NULL;
memset(ptty,0,sizeof(TTY_INFO));
pthread_mutex_init(&ptty->mt,NULL);
sprintf(ptty->name,"/dev/ttyS%d",id);

// 打开并且设置串口
// O_RDWR 读、写打开
// O_NOCTTY 该参数不会使打开的文件成为该进程的控制终端
// O_NDELAY 这个程序不关心DCD信号线所处的状态,端口的另一端是否激活或者停止
ptty->fd = open(ptty->name, O_RDWR | O_NOCTTY |O_NDELAY);
if (ptty->fd <0)
{
free(ptty);
return NULL;
}

// 取得并且保存原来的设置
tcgetattr(ptty->fd,&ptty->otm);
return ptty;
}


// 清理串口设备资源
int cleanTTY(TTY_INFO *ptty)
{
// 关闭打开的串口设备
if(ptty->fd>0)
{
tcsetattr(ptty->fd,TCSANOW,&ptty->otm);
close(ptty->fd);
ptty->fd = -1;
free(ptty);
ptty = NULL;
}

return 0;
}


// 设置串口通信速率
// ptty 参数类型(TTY_INFO *),已经初始化的串口设备信息结构指针
// speed 参数类型(int),用来设置串口的波特率
// return 返回值类型(int),函数执行成功返回零值,否则返回大于零的值
int setTTYSpeed(TTY_INFO *ptty, int speed)
{
int i;

// 进行新的串口设置,数据位为8位
bzero(&ptty->ntm, sizeof(ptty->ntm));
tcgetattr(ptty->fd,&ptty->ntm);
ptty->ntm.c_cflag = /*CS8 |*/ CLOCAL | CREAD;

switch(speed)
{
case 300:
ptty->ntm.c_cflag |= B300;
break;
case 1200:
ptty->ntm.c_cflag |= B1200;
break;
case 2400:
ptty->ntm.c_cflag |= B2400;
break;
case 4800:
ptty->ntm.c_cflag |= B4800;
break;
case 9600:
ptty->ntm.c_cflag |= B9600;
break;
case 19200:
ptty->ntm.c_cflag |= B19200;
break;
case 38400:
ptty->ntm.c_cflag |= B38400;
break;
case 115200:
ptty->ntm.c_cflag |= B115200;
break;
}
ptty->ntm.c_iflag = IGNPAR;
ptty->ntm.c_oflag = 0;

tcflush(ptty->fd, TCIFLUSH);
tcsetattr(ptty->fd,TCSANOW,&ptty->ntm);

return 0;
}


// 设置串口数据位,停止位和效验位
// ptty 参数类型(TTY_INFO *),已经初始化的串口设备信息结构指针
// databits 参数类型(int), 数据位,取值为7或者8
// stopbits 参数类型(int),停止位,取值为1或者2
// parity 参数类型(int),效验类型 取值为N,E,O,,S
// return 返回值类型(int),函数执行成功返回零值,否则返回大于零的值
int setTTYParity(TTY_INFO *ptty,int databits,int parity,int stopbits)
{
// 取得串口设置
if( tcgetattr(ptty->fd,&ptty->ntm) != 0)
{
printf("SetupSerial [%s]\n",ptty->name);
return 1;
}

bzero(&ptty->ntm, sizeof(ptty->ntm));
ptty->ntm.c_cflag = CS8 | CLOCAL | CREAD;
ptty->ntm.c_iflag = IGNPAR;
ptty->ntm.c_oflag = 0;

// 设置串口的各种参数
ptty->ntm.c_cflag &= ~CSIZE;

//设置数据位数
switch (databits)
{
case 7:
ptty->ntm.c_cflag |= CS7;
break;
case 8:
ptty->ntm.c_cflag |= CS8;
break;
default:
printf("Unsupported data size\n");
return 5;
}

// 设置奇偶校验位数
switch (parity)
{
case n:
case N:
ptty->ntm.c_cflag &= ~PARENB; /* Clear parity enable */
ptty->ntm.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case o:
case O:
ptty->ntm.c_cflag |= (PARODD|PARENB); /* 设置为奇效验*/
ptty->ntm.c_iflag |= INPCK; /* Disnable parity checking */
break;
case e:
case E:
ptty->ntm.c_cflag |= PARENB; /* Enable parity */
ptty->ntm.c_cflag &= ~PARODD; /* 转换为偶效验*/
ptty->ntm.c_iflag |= INPCK; /* Disnable parity checking */
break;
case S:
case s: /*as no parity*/
ptty->ntm.c_cflag &= ~PARENB;
ptty->ntm.c_cflag &= ~CSTOPB;
break;
default:
printf("Unsupported parity\n");
return 2;
}

// 设置停止位
switch (stopbits)
{
case 1:
ptty->ntm.c_cflag &= ~CSTOPB;
break;
case 2:
ptty->ntm.c_cflag |= CSTOPB;
break;
default:
printf("Unsupported stop bits\n");
return 3;
}

ptty->ntm.c_lflag = 0;
ptty->ntm.c_cc[VTIME] = 0; // inter-character timer unused

ptty->ntm.c_cc[VMIN] = 1; // blocking read until 1 chars received

tcflush(ptty->fd, TCIFLUSH);
if (tcsetattr(ptty->fd,TCSANOW,&ptty->ntm) != 0)
{
printf("SetupSerial \n");
return 4;
}

return 0;
}


int recvnTTY(TTY_INFO *ptty,char *pbuf,int size)
{
int ret,left,bytes;
left = size;

while(left>0)
{
ret = 0;
bytes = 0;

pthread_mutex_lock(&ptty->mt);

// 得到缓冲区里有多少字节要被读取,然后将字节数放入bytes里面
ioctl(ptty->fd, FIONREAD, &bytes);
if(bytes>0)
{
ret = read(ptty->fd,pbuf,left);
}
pthread_mutex_unlock(&ptty->mt);
if(ret >0)
{
left -= ret;
pbuf += ret;
}
usleep(100);
}

return size - left;
}


int sendnTTY(TTY_INFO *ptty,char *pbuf,int size)
{
int ret,nleft;
char *ptmp;

ret = 0;
nleft = size;
ptmp = pbuf;

while(nleft>0)
{
pthread_mutex_lock(&ptty->mt);
ret = write(ptty->fd,ptmp,nleft);
pthread_mutex_unlock(&ptty->mt);

if(ret >0)
{
nleft -= ret;
ptmp += ret;
}
//usleep(100);
}

return size - nleft;
}


int lockTTY(TTY_INFO *ptty)
{
if(ptty->fd < 0)
{
return 1;
}

return flock(ptty->fd, LOCK_EX);
}


int unlockTTY(TTY_INFO *ptty)
{
if(ptty->fd < 0)
{
return 1;
}

return flock(ptty->fd, LOCK_UN);
}


#ifdef LEAF_TTY_TEST
///////////////////////////////////////////////////////////////////////////////

// 接口测试

int main(int argc,char **argv)
{
TTY_INFO *ptty;
int nbyte,idx;
unsigned char cc[16];

ptty = readyTTY(0);
if(ptty == NULL)
{
printf("readyTTY(0) error\n");
return 1;
}

lockTTY(ptty);
if(setTTYSpeed(ptty,9600)>0)
{
printf("setTTYSpeed() error\n");
return -1;
}

if(setTTYParity(ptty,8,N,1)>0)
{
printf("setTTYParity() error\n");
return -1;
}

idx = 0;
while(1)
{
cc[0] = 0xFA;
sendnTTY(ptty,&cc[0],1);
nbyte = recvnTTY(ptty,cc,1);
printf("%d:%02X\n",idx++,cc[0]);
}

cleanTTY(ptty);
}
#endif

D. 实例:非堵塞按键监听

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
40
//用非阻塞io
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

int kbhit(void)
{
struct termios oldt, newt; //termios结构体
int ch;
int oldf;

tcgetattr(STDIN_FILENO, &oldt); //获取标准输入设备(一般是键盘)的文件描述符
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO); //终端非规范模式,屏蔽整行缓存;输入不回显
tcsetattr(STDIN_FILENO, TCSANOW, &newt); //设置改变立即发生,不需要回车

//设置为非堵塞
oldf = fcntl(STDIN_FILENO, F_GETFL, 0); //获取文件状态标志
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); //设置文件状态标志为非阻塞I/O

ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);

if(ch != EOF)
{
ungetc(ch, stdin);
return 1;
}
return 0;
}

int main(void)
{
while(!kbhit())
puts("Press a key!");
printf("You pressed '%c'!/n", getchar());
return 0;
}