linux串口编程
时间:2022-04-03 16:26
按照对linux系统的理解,串口编程的顺序无非就是open,read,write,close,而串口有波特率、数据位等重要参数需要设置,因此还应该用到设置函数,那么接下来就带着这几个问题去学习linux下的串口编程。
1、open
linux串口编程其实也是文件编程,首先要用open函数打开串口设备,获得文件描述符,open函数的简介参照:
首先需要关心的是需要打开的文件名,它肯定是/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);
2、串口设置
打开串口设备之后还需要对串口进行设置。使用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成员中和软件流控相关的键值及解释如下:
Enable software flow control (outgoing) |
相关推荐
电脑软件本类排行今日推荐热门手游 |