标签:睡眠状态 接下来 flow 默认 class 流控 结束 常用 ext
按照对linux系统的理解,串口编程的顺序无非就是open,read,write,close,而串口有波特率、数据位等重要参数需要设置,因此还应该用到设置函数,那么接下来就带着这几个问题去学习linux下的串口编程。
linux串口编程其实也是文件编程,首先要用open函数打开串口设备,获得文件描述符,open函数的简介参照:http://blog.sina.com.cn/s/blog_54f82cc201010oow.html
首先需要关心的是需要打开的文件名,它肯定是/dev路径下的某个设备,串口设备一般叫做ttyS*,ttySAC*,ttyUSB*,我以前就知道这些,后来发现竟然还有叫ttyO*的(当时调试的时候看见/dev目录下有ttyS*的,我就不假思索的,理所当然的代码中写的是ttyS*,结果程序运行时这玩意还成功打开了,但设置的时候就出问题,万万没想到啊,当时用的那个平台ttyS*不是串口,相同目录下还有叫ttyO*的文件,那才是串口)。
串口设备明显是可读写的,因此传入的第二个参数为O_RDWR。
因此打开串口1的操作为:
fd = open("/dev/ttySAC1", O_RDWR);
如果得到的fd不等于-1则表示成功打开串口设备了。
我在使用过程中遇到过串口无法打开的问题,open返回值为-1。查阅资料后使用命令:sudo chmod 666 /dev/ttyUSB0 修改串口设备权限后就能成功打开了。
open函数传入的第二个参数一般会用到O_NOCTTY这个标志,它表示阻止操作系统将打开的文件指定为进程的控制终端,如果没有指定这个标志,那么任何一个输入都将会影响用户的进程。我不知道它具体是怎么影响的,但是最好还是加上这个标志。
此外O_NONBLOCK这个标志也比较常用,它表示以非阻塞模式打开文件,当调用read的时候,如果没有数据也会立即返回-1。有些人会用O_NDELAY这个标志,关于这个标志的解释网上就有很多说法了,我看到就有三种说法:
1、与O_NONBLOCK一样也是以非阻塞模式打开,但如果没有读取到数据,O_NDELAY返回的是0,而O_NONBLOCK返回的是-1,并且会设置errno为EAGAIN。
2、O_NDELAY表示这个程序不关心DCD信号线所处的状态,端口的另一端是否激活或者停止。如果用户不指定了这个标志,则进程将会一直处在睡眠状态,直到DCD信号线被激活。
3、与O_NONBLOCK等价。
为了探究答案,我在一份linux3.14.38的源代码中搜索,发现这两个标志的值与平台相关。我在Ubuntu中直接用printf打印出来两个标志的值是完全一样的,并且read函数返回的值为都是-1,并不是第一条说的那样O_NDELAY返回的是0。
我暂时也没有更多的平台去验证,暂且认为网上的那些说法都是基于作者自己正在使用的平台上说的,而在我使用的Ubuntu中O_NDELAY与O_NONBLOCK是完全相同的。
所以打开串口的操作应为下列2句中的一句:
fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY);
fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY | O_NONBLOCK);
打开串口设备之后还需要对串口进行设置。使用tcsetattr函数设置串口,函数原型为:
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
成功返回0,失败返回-1。
第一个参数fd表示打开的串口文件描述符。
第二个参数optional_actions用于控制修改起作用的时间。可以取下列值:
TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。
第三个参数struct termios中包含了串口属性,struct termios定义为:
1 #define NCCS 19
2 struct termios {
3 tcflag_t c_iflag; /* input mode flags */
4 tcflag_t c_oflag; /* output mode flags */
5 tcflag_t c_cflag; /* control mode flags */
6 tcflag_t c_lflag; /* local mode flags */
7 cc_t c_line; /* line discipline */
8 cc_t c_cc[NCCS]; /* control characters */
9 };
看到这种不认识的数据类型就头大,实际上tcflag_t类型就是unsigned int,而cc_t类型就是unsigned char。
由于struct termios里面的成员太多,因此使用tcgetattr函数先获取到原来的属性,然后再修改我们关心的属性。
调用tcgetattr函数获取属性,tcgetattr函数原型为:
int tcgetattr(int fd, struct termios *termios_p);
成功返回0,失败返回-1。
一般来说串口需要关心的属性为:波特率、数据位、校验位、停止位、流控。下面逐一说明这些数据在哪里改。
波特率:
与波特率相关的成员为c_cflag,其中键值(域)CBAUD表示波特率,该域中的位不同的组合表示不同的波特率,表示波特率也用的也是宏,支持的宏如下:
B0
B50
B75
B110
B134
B150
B200
B300
B600
B1200
B1800
B2400
B4800
B9600
B19200
B38400
B57600
B115200
B230400
比如将波特率修改为115200的代码就可以这样写:
1 tcgetattr(fd, &termios_uart);
2 termios_uart.c_cflag &= ~CBAUD;
3 termios_uart.c_cflag |= B115200;
4 tcsetattr(fd, TCSANOW, &termios_uart);
在linux中提供了专门设置波特率的函数,用cfsetispeed和cfsetospeed设置输入输出波特率,还有一个cfsetspeed函数,它们的函数原型为:
int 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_t类型其实就是unsigned int类型,其取值也正是CBAUD域中可以选择的数据。
但是看到这里就有疑问了,c_cflag成员中没有将输入波特率和输出波特率分开,这里为什么会有几个不同的设置波特率的函数,这个问题在另一篇博客中仔细探究。
那么设置波特率的代码就变成了这样:
1 tcgetattr(fd, &termios_uart);
2 cfsetspeed(&termios_uart, B115200);
3 tcsetattr(fd, TCSANOW, &termios_uart);
数据位:
与数据位相关的成员为c_cflag,其中键值(域)CSIZE表示数据位,与波特率的设置一样,该域中的位不同的组合表示不同的数据位,也可以用宏来表示,支持的宏如下:
CS5,CS6,CS7,CS8,分别表示数据位为5位、6位、7位、8位。
将数据位设置为8位就可以这样写:
1 tcgetattr(fd, &termios_uart);
2 termios_uart.c_cflag &= ~CSIZE;
3 termios_uart.c_cflag |= CS8;
4 tcsetattr(fd, TCSANOW, &termios_uart);
校验位:
c_cflag中键值PARENB置1表示使用奇偶校验,否则表示不使用校验,在PARENB置1的前提下,键值 PARODD置1表示使用奇校验,否则使用偶校验。
另外在c_iflag中也有与校验相关的位。如下表:
IGNPAR | Ignore framing errors and parity errors. |
PARMRK | If IGNPAR is not set, prefix a character with a parity error or framing error with \377 \0. If neither IGNPAR nor PARMRK is set, read a character with a par‐ ity error or framing error as \0. |
INPCK | Enable input parity checking. |
ISTRIP | Strip off eighth bit. |
停止位:
c_cflag中键值CSTOPB置1表示使用2个停止位,否则表示使用1个停止位。
流控:
c_cflag中键值CRTSCTS置1表示使用硬件流控,否则表示不使用硬件流控。
软件流控则在c_iflag成员中定义,c_iflag成员中和软件流控相关的键值及解释如下:
IXON | Enable software flow control (outgoing) |
IXOFF | Enable software flow control (incoming) |
IXANY | Allow any character to start flow again |
所谓流控指的就是控制数据流的流量,如果当接收端接收不了那么多数据时,发送端还在不断的发数据,这样就会出问题。
硬件流控就是比如A给B发送数据,他们之间除了一根数据线连接以外还有一根流控线,当B将流控线拉高时A才能像B发送数据,当B将流控线拉低时,A就不能发送数据了。
软件流控没有专门的流控线,如果串口是全双工的,但是如果当 A给B发送数据的时候B不会给A发送数据,那么B就可以将这根数据线用来发送流控数据,当B想要接受数据时,B给A发送IXON,当B不想接受数据时,B给A发送IXOFF。
理论上来说硬件流控与软件流控可以同时使用,但一般没人会这么做。
其他设置:
你以为这样就完了吗,这只是按照裸机开发的思维方式的操作步骤,但这是在linux操作系统上的东西,而且struct termios结构中有这么多成员,这些成员肯定都是有用的啊。
如果这些东西不设置,程序也许能正常运行,那是因为通过tcgetattr函数获取到了一个默认值,而且这个值是会保存的,下次获取到的值是上次设置的值(无论文件是否关闭,程序是否退出),因此写一份比较健全的代码就至关重要了。
这里举两个例子说明一下其他参数的重要性,如c_lflag中有一个ECHO标志,该标志置位时串口会将收到的数据回传回去,表现就是你给他发什么东西它就会给你回什么东西。
另外还有一个函数比较重要,那就是tcflush,tcflush函数原型为:int tcflush(int fd, int queue_selector); queue_selector的取值和意义如下:
TCIFLUSH | 刷新数据接收但不读 |
TCOFLUSH | 刷新数据写入但不传输 |
TCIOFLUSH | 刷新数据接收但不读同时刷新数据写入但不传输 |
直接翻译过来非常拗口,理解一下就是无论是即将要发送的数据还是即将要接收的数据,都是放在缓冲区里的,TCIFLUSH意思就是数据已经放在缓冲区里了,但是还没有调用read函数去读,那么直接清空读缓冲区,调用read的时候也接收不到数据。TCOFLUSH的意思就是如果调用了write函数,但是实际上数据还没有开始往外发,那么清空写缓冲区,数据也不发了。
https://www.cnblogs.com/dartagnan/archive/2013/04/25/3042417.html这篇博客中将里面的成员讲解的非常详细。
测试程序代码:
1 /**
2 * filename: uart.c
3 * author: Suzkfly
4 * date: 2021-01-16
5 * platform: Ubuntu
6 * 将USB转串口连接至Ubuntu中,运行程序,能打印串口接收到的数据,也能从终端中获取
7 * 数据发送出去。
8 * 如果不能成功打开设备,使用则ls /dev/ttyUSB0命令看设备是否存在,若存在,则使用
9 * sudo chmod 666 /dev/ttyUSB0 修改文件权限后重新运行程序。
10 */
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <fcntl.h>
15 #include <termios.h>
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <string.h>
19
20 /**
21 * \brief define
22 * @{
23 */
24 #define UART_DEV_PATH "/dev/ttyUSB0" /**\brief< 定义打开的串口设备路径 */
25
26 /**
27 * @}
28 */
29
30 /**
31 * \brief 打开串口设备
32 *
33 * \param[in] p_path:设备路径
34 *
35 * \retval 成功返回文件描述符,失败返回-1
36 */
37 int uart_open(const char *p_path)
38 {
39 /* O_NOCTTY:阻止操作系统将打开的文件指定为进程的控制终端,如果没有指定这个标
40 志,那么任何一个输入都将会影响用户的进程。 */
41 /* O_NONBLOCK:使I/O变成非阻塞模式,调用read如果没有接收到数据则立马返回-1,
42 并且设置errno为EAGAIN。*/
43 /* O_NDELAY: 与O_NONBLOCK完全相同 */
44 return open(p_path, O_RDWR | O_NOCTTY);
45 }
46
47 /**
48 * \brief 测试函数,打印struct termios各成员值
49 */
50 static void __print_termios(struct termios *p_termios)
51 {
52 printf("c_iflag = %#08x\n", p_termios->c_iflag);
53 printf("c_oflag = %#08x\n", p_termios->c_oflag);
54 printf("c_cflag = %#08x\n", p_termios->c_cflag);
55 printf("c_lflag = %#08x\n\n", p_termios->c_lflag);
56 }
57
58 /**
59 * \brief 设置串口属性
60 *
61 * \param[in] fd:打开的串口设备的文件描述符
62 * \param[in] baudrate:波特率
63 * #{0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800,
64 * 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400}
65 * \param[in] bits:数据位
66 * #{5, 6, 7, 8}
67 * \param[in] parity:校验
68 * #‘n‘/‘N‘:无校验
69 * #‘o‘/‘O‘:奇校验
70 * #‘e‘,‘E‘:偶校验
71 * \param[in] stop:停止位
72 * #1:1个停止位
73 * #2:2个停止位
74 * \param[in] flow:流控
75 * #‘n‘/‘N‘:不使用流控
76 * #‘h‘/‘H‘:使用硬件流控
77 * #‘s‘/‘S‘:使用软件流控
78 *
79 * \retval 成功返回0,失败返回-1,并打印失败原因
80 *
81 * \note 虽然波特率设置支持这么多值,但并不代表输入表中支持的值波特率就
82 * 一定能设置成功。
83 */
84 int uart_set(int fd, int baudrate, int bits, char parity, int stop, char flow)
85 {
86 struct termios termios_uart;
87 int ret = 0;
88 speed_t uart_speed = 0;
89
90 /* 获取串口属性 */
91 memset(&termios_uart, 0, sizeof(termios_uart));
92 ret = tcgetattr(fd, &termios_uart);
93 if (ret == -1) {
94 printf("tcgetattr failed\n");
95 return -1;
96 }
97
98 //__print_termios(&termios_uart);
99
100 /* 设置波特率 */
101 switch (baudrate) {
102 case 0: uart_speed = B0; break;
103 case 50: uart_speed = B50; break;
104 case 75: uart_speed = B75; break;
105 case 110: uart_speed = B110; break;
106 case 134: uart_speed = B134; break;
107 case 150: uart_speed = B150; break;
108 case 200: uart_speed = B200; break;
109 case 300: uart_speed = B300; break;
110 case 600: uart_speed = B600; break;
111 case 1200: uart_speed = B1200; break;
112 case 1800: uart_speed = B1800; break;
113 case 2400: uart_speed = B2400; break;
114 case 4800: uart_speed = B4800; break;
115 case 9600: uart_speed = B9600; break;
116 case 19200: uart_speed = B19200; break;
117 case 38400: uart_speed = B38400; break;
118 case 57600: uart_speed = B57600; break;
119 case 115200: uart_speed = B115200; break;
120 case 230400: uart_speed = B230400; break;
121 default: printf("Baud rate not supported\n"); return -1;
122 }
123 cfsetspeed(&termios_uart, uart_speed);
124
125 /* 设置数据位 */
126 switch (bits) {
127 case 5: /* 数据位5 */
128 termios_uart.c_cflag &= ~CSIZE;
129 termios_uart.c_cflag |= CS5;
130 break;
131
132 case 6: /* 数据位6 */
133 termios_uart.c_cflag &= ~CSIZE;
134 termios_uart.c_cflag |= CS6;
135 break;
136
137 case 7: /* 数据位7 */
138 termios_uart.c_cflag &= ~CSIZE;
139 termios_uart.c_cflag |= CS7;
140 break;
141
142 case 8: /* 数据位8 */
143 termios_uart.c_cflag &= ~CSIZE;
144 termios_uart.c_cflag |= CS8;
145 break;
146
147 default:
148 printf("Data bits not supported\n");
149 return -1;
150 }
151
152 /* 设置校验位 */
153 switch (parity) {
154 case ‘n‘: /* 无校验 */
155 case ‘N‘:
156 termios_uart.c_cflag &= ~PARENB;
157 termios_uart.c_iflag &= ~INPCK; /* 禁能输入奇偶校验 */
158 break;
159
160 case ‘o‘: /* 奇校验 */
161 case ‘O‘:
162 termios_uart.c_cflag |= PARENB;
163 termios_uart.c_cflag |= PARODD;
164 termios_uart.c_iflag |= INPCK; /* 使能输入奇偶校验 */
165 termios_uart.c_iflag |= ISTRIP; /* 除去第八个位(奇偶校验位) */
166 break;
167
168 case ‘e‘: /* 偶校验 */
169 case ‘E‘:
170 termios_uart.c_cflag |= PARENB;
171 termios_uart.c_cflag &= ~PARODD;
172 termios_uart.c_iflag |= INPCK; /* 使能输入奇偶校验 */
173 termios_uart.c_iflag |= ISTRIP; /* 除去第八个位(奇偶校验位) */
174 break;
175
176 default:
177 printf("Parity not supported\n");
178 return -1;
179 }
180
181 /* 设置停止位 */
182 switch (stop) {
183 case 1: termios_uart.c_cflag &= ~CSTOPB; break; /* 1个停止位 */
184 case 2: termios_uart.c_cflag |= CSTOPB; break; /* 2个停止位 */
185 default: printf("Stop bits not supported\n");
186 }
187
188 /* 设置流控 */
189 switch (flow) {
190 case ‘n‘:
191 case ‘N‘: /* 无流控 */
192 termios_uart.c_cflag &= ~CRTSCTS;
193 termios_uart.c_iflag &= ~(IXON | IXOFF | IXANY);
194 break;
195
196 case ‘h‘:
197 case ‘H‘: /* 硬件流控 */
198 termios_uart.c_cflag |= CRTSCTS;
199 termios_uart.c_iflag &= ~(IXON | IXOFF | IXANY);
200 break;
201
202 case ‘s‘:
203 case ‘S‘: /* 软件流控 */
204 termios_uart.c_cflag &= ~CRTSCTS;
205 termios_uart.c_iflag |= (IXON | IXOFF | IXANY);
206 break;
207
208 default:
209 printf("Flow control parameter error\n");
210 return -1;
211 }
212
213 /* 其他设置 */
214 termios_uart.c_cflag |= CLOCAL; /* 忽略modem(调制解调器)控制线 */
215 termios_uart.c_cflag |= CREAD; /* 使能接收 */
216
217 /* 禁能执行定义(implementation-defined)输出处理,意思就是输出的某些特殊数
218 据会作特殊处理,如果禁能的话那么就按原始数据输出 */
219 termios_uart.c_oflag &= ~OPOST;
220
221 /**
222 * 设置本地模式位原始模式
223 * ICANON:规范输入模式,如果设置了那么退格等特殊字符会产生实际动作
224 * ECHO:则将输入字符回送到终端设备
225 * ECHOE:如果ICANON也设置了,那么收到ERASE字符后会从显示字符中擦除一个字符
226 * 通俗点理解就是收到退格键后显示内容会往回删一个字符
227 * ISIG:使终端产生的信号起作用。(比如按ctrl+c可以使程序退出)
228 */
229 termios_uart.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
230
231 /**
232 * 设置等待时间和最小接收字符
233 * 这两个值只有在阻塞模式下有意义,也就是说open的时候不能传入O_NONBLOCK,
234 * 如果经过了c_cc[VTIME]这么长时间,缓冲区内有数据,但是还没达到c_cc[VMIN]个
235 * 数据,read也会返回。而如果当缓冲区内有了c_cc[VMIN]个数据时,无论等待时间
236 * 是否到了c_cc[VTIME],read都会返回,但返回值可能比c_cc[VMIN]还大。如果将
237 * c_cc[VMIN]的值设置为0,那么当经过c_cc[VTIME]时间后read也会返回,返回值
238 * 为0。如果将c_cc[VTIME]和c_cc[VMIN]都设置为0,那么程序运行的效果与设置
239 * O_NONBLOCK类似,不同的是如果设置了O_NONBLOCK,那么在没有数据时read返回-1,
240 * 而如果没有设置O_NONBLOCK,那么在没有数据时read返回的是0。
241 */
242 termios_uart.c_cc[VTIME] = 1; /* 设置等待时间,单位1/10秒 */
243 termios_uart.c_cc[VMIN] = 1; /* 最少读取一个字符 */
244
245 tcflush(fd, TCIFLUSH); /* 清空读缓冲区 */
246
247 /* 写入配置 */
248 ret = tcsetattr(fd, TCSANOW, &termios_uart);
249 if (ret == -1) {
250 printf("tcsetattr failed\n");
251 }
252
253 return ret;
254 }
255
256
257 /**
258 * \test
259 * @{
260 */
261 int main(int argc, const char *argv[])
262 {
263 int fd = 0;
264 int ret = 0;
265 int pid = 0;
266 char buf[128] = { 0 };
267 int len = 0;
268 int i = 0;
269
270 /* 打开串口设备 */
271 fd = uart_open(UART_DEV_PATH);
272 if (fd < 0) {
273 printf("open %s failed\n", UART_DEV_PATH);
274 return 0;
275 }
276
277 /**
278 * 配置串口:
279 * 波特率:9600
280 * 数据位:8
281 * 校验 :无校验
282 * 停止位:1
283 * 流控 :无流控
284 */
285 ret = uart_set(fd, 9600, 8, ‘n‘, 1, ‘n‘);
286 if (ret == -1) {
287 return 0;
288 }
289
290 pid = fork();
291 if (pid > 0) { /* 读数据 */
292 while (1) {
293 memset(buf, 0, sizeof(buf));
294 len = read(fd, buf, sizeof(buf));
295 if (len > 0) {
296 printf("len: %d\n", len);
297 printf("data: ");
298 #if 0 /* 十六进制打印 */
299 for (i = 0; i < len; i++) {
300 printf("%02x ", buf[i]);
301 }
302 printf("\n\n");
303 #else /* 字符串打印 */
304 printf("%s\n\n", buf);
305 #endif
306 } else {
307 printf("len = %d\n", len);
308 }
309 }
310 } else if (pid == 0) { /* 写数据 */
311 while (1) {
312 scanf("%s", buf);
313 write(fd, buf, strlen(buf));
314 }
315 } else {
316 printf("fork failed\n");
317 }
318
319 return 0;
320 }
321
322 /**
323 * @}
324 */
标签:睡眠状态 接下来 flow 默认 class 流控 结束 常用 ext
原文地址:https://www.cnblogs.com/Suzkfly/p/11055532.html