串口termios函数

https://blog.csdn.net/williamwang2013/article/details/8560552

linux使用terminfo数据库来描述终端能力以及调用对应功能的方法
POSIX定义了完成终端I/O的标准方法:TERMIOS函数族

#include <termios.h>
#include <unistd.h>
struct termios{
              tcflag_t c_iflag;      /* input modes */
              tcflag_t c_oflag;      /* output modes */
              tcflag_t c_cflag;      /* control modes */
              tcflag_t c_lflag;      /* local modes */
              cc_t c_cc [NCCS];      /* control chars */
}

其具体意义如下。

c_iflag:输入模式标志,控制终端输入方式,具体参数如表6.3所示。

表6.3 c_iflag参数表

键 值说 明
IGNBRK忽略BREAK键输入
BRKINT如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断
IGNPAR忽略奇偶校验错误
PARMRK标识奇偶校验错误
INPCK允许输入奇偶校验
ISTRIP去除字符的第8个比特
INLCR将输入的NL(换行)转换成CR(回车)
IGNCR忽略输入的回车
ICRNL将输入的回车转化成换行(如果IGNCR未设置的情况下)
IUCLC将输入的大写字符转换成小写字符(非POSIX)
IXON允许输入时对XON/XOFF流进行控制
IXANY输入任何字符将重启停止的输出
IXOFF允许输入时对XON/XOFF流进行控制
IMAXBEL当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置

c_oflag:输出模式标志,控制终端输出方式,具体参数如表6.4所示。

表6.4 c_oflag参数

键 值说 明
OPOST处理后输出
OLCUC将输入的小写字符转换成大写字符(非POSIX)
ONLCR将输入的NL(换行)转换成CR(回车)及NL(换行)
OCRNL将输入的CR(回车)转换成NL(换行)
ONOCR第一行不输出回车符
ONLRET不输出回车符
OFILL发送填充字符以延迟终端输出
OFDEL以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘\0’)(非POSIX)
NLDLY换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)
CRDLY回车延迟,取值范围为:CR0、CR1、CR2和 CR3
TABDLY水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3
BSDLY空格输出延迟,可以取BS0或BS1
VTDLY垂直制表符输出延迟,可以取VT0或VT1
FFDLY换页延迟,可以取FF0或FF1

c_cflag:控制模式标志,指定终端硬件控制信息,具体参数如表6.5所示。

表6.5 c_oflag参数

键 值说 明
CBAUD波特率(4+1位)(非POSIX)
CBAUDEX附加波特率(1位)(非POSIX)
CSIZE字符长度,取值范围为CS5、CS6、CS7或CS8
CSTOPB设置两个停止位
CREAD使用接收器
PARENB使用奇偶校验
PARODD对输入使用奇偶校验,对输出使用偶校验
HUPCL关闭设备时挂起
CLOCAL忽略调制解调器线路状态
CRTSCTS使用RTS/CTS流控制

c_lflag:本地模式标志,控制终端编辑功能,具体参数如表6.6所示。

表6.6 c_lflag参数

键 值说 明
ISIG当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
ICANON使用标准输入模式
XCASE在ICANON和XCASE同时设置的情况下,终端只使用大写。如果只设置了XCASE,则输入字符将被转换为小写字符,除非字符使用了转义字符(非POSIX,且Linux不支持该参数)
ECHO显示输入字符
ECHOE如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词
ECHOK如果ICANON同时设置,KILL将删除当前行
ECHONL如果ICANON同时设置,即使ECHO没有设置依然显示换行符
ECHOPRT如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
TOSTOP向后台输出发送SIGTTOU信号

c_cc[NCCS]:控制字符,用于保存终端驱动程序中的特殊字符,如输入结束符等。c_cc中定义了如表6.7所示的控制字符。

表6.7 c_cc支持的控制字符

说 明
VINTRInterrupt字符
VEOL附加的End-of-file字符
VQUITQuit字符
VTIME非规范模式读取时的超时时间
VERASEErase字符
VSTOPStop字符
VKILLKill字符
VSTARTStart字符
VEOFEnd-of-file字符
VSUSPSuspend字符
VMIN非规范模式读取时的最小字符数

1.模式

Cbreak模式

除了"Del"和"Ctrl"键外,接受其他所有字符输入, raw()和cbreak()两个函数都可以禁止行缓冲(line buffering)。区别是:在raw()函数模式下,处理挂起(CTRLZ)、 中断或退出(CTRLC) 等控制字符时,将直接传送给程序去处理而不产生终端信号;而在 cbreak()模式下,控制字符将被终端驱动程序解释成其它字符。

Raw模式

可以禁止行缓冲(line buffering),处理挂起(CTRLZ)、中断或退出(CTRLC)等控制字符时,将直接传送给程序去处理而不产生终端信号
详情查看curses库中的cbreak()和 raw()函数。
另外可以用命令直接操作
stty -a 这个命令用来查看当前终端的设置情况
stty sane 如果不小心设错了终端模式,可用这个命令恢复,另一种恢复办法是在设置之前保存当前stty设置,在需要时再读出
stty -g > save_stty 将当前设置保存到文件save_atty中
stty $(cat save_stty) 读出save_atty文件,恢复原终端设置

2.获取和设置终端属性

int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, struct termios *termios_p);
其中optional_actions决定什么时候起作用,可取如下值
TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。

注意:当进行多重修改时,应当在这个函数之后再次调用 tcgetattr() 来检测是否所有修改都成功实现。

3.波特率函数

获取波特率

speed_t cfgetospeed(struct termios *termios_p);
speed_t cfgetispeed(struct termios *termios_p);

设置波特率

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

speed取值必须是以下常量之一:
B0 B50 B75 B110 B134 B150 B200 B300 B600 B1200 B1800 B2400 B4800 B9600 B19200 B38400 B57600 B115200 B230400
其中:零值 B0 用来中断连接。如果指定了 B0,不应当再假定存在连接。通常,这样将断开连接。CBAUDEX 是一个掩码,指示高于 POSIX.1 定义的速度的那一些 (57600 及以上)。因此,B57600 & CBAUDEX 为非零。

4.线路控制函数

int tcdrain (int fd); //等待所有写入fd中的数据输出

int tcflush (int fd, int queue_selector); //丢弃要写入fd,但尚未传输的数据,或者收到但是尚未读取的数据。
取决于queue_selector 的值:
TCIFLUSH: 刷新收到的数据但是不读
TCOFLUSH: 刷新写入的数据但是不传送
TCIOFLUSH: 同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送

int tcflow (int fd, int action); //挂起 fd 上的数据传输或接收。
取决于 action 的值:
TCOOFF :挂起输出
TCOON :重新开始被挂起的输出
TCIOFF :发送一个 STOP 字符,停止终端设备向系统传送数据
TCION :发送一个 START 字符,使终端设备向系统传输数据
打开一个终端设备时的默认设置是输入和输出都没有挂起。

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

5.进程组控制函数

pid_t tcgetpgrp(int fd); //获取前台进程组的进程组ID

int tcsetpgrp(int fd, pid_t pgrpid); //设置前台进程组的进程组ID

pid_t tcgetsid(int fd); //获取会话首进程的进程组ID

6.cfmakeraw

设置终端的 Raw 模式 ,设置终端属性:

termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;

7.其他

#include <stdio.h>
char *ctermid(char *s); //决定控制终端名称

#include <unistd.h>
int isatty(int desc); //判断描述符是否为终端

char *ttyname(int desc); //返回终端名称

int getopt(int argc,char * const argv[ ],const char * optstring); //分析命令行参数
其中argc和argv是由main()传递的参数个数和内容,optstring 则代表欲处理的选项字符串。

修改终端控制字符示例

#include
#include
#include
#include
int main(void)
{
    //term用于存储获得的终端参数信息
    struct termios term;
    int err;
    
    //获得标准输入的终端参数,将获得的信息保存在term变量中
    if(tcgetattr(STDIN_FILENO,&term)==-1)
    {
	    perror("Cannot get standard input description");
	    return 1;
    }
    
    //修改获得的终端信息的结束控制字符
    term.c_cc[VEOF]=(cc_t)0x07;
    
    //使用tcsetattr函数将修改后的终端参数设置到标准输入中
    //err用于保存函数调用后的结果
    err=tcsetattr(STDIN_FILENO,TCSAFLUSH,&term);
    
    //如果err为-1或是出现EINTR错误(函数执行被信号中断),
    //给出相关出错信息
    if(err==-1 && err==EINTR)
    {
	    perror("Failed to change EOF character");
	    return 1;
    }
    return 0;
}

O_NOCTTY:如果打开的是一个终端设备,这个程序不会成为对应这个端口的控制终端,如果没有该标志,任何一个输入,例如键盘中止信号等,都将影响进程。

设置波特率的例子函数

/**
*@brief 设置串口通信速率
*@param fd 类型 int 打开串口的文件句柄
*@param speed 类型 int 串口速度
*@return void
*/
 
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,  B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300, };
 
void set_speed(int fd, int speed)
{
    int i;
    int status;
    struct termios Opt;
    
    tcgetattr(fd, &Opt);
 
    for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) 
    {
	    if (speed == name_arr[i]) 
	    {
		    tcflush(fd, TCIOFLUSH);
		    
		    cfsetispeed(&Opt, speed_arr[i]);
		    cfsetospeed(&Opt, speed_arr[i]);
		    
		    status = tcsetattr(fd1, TCSANOW, &Opt);
		    if (status != 0) 
		    {
			    perror("tcsetattr fd1");
			    return;
		    }
		    
		    tcflush(fd,TCIOFLUSH);
	    }
    }
}

设置效验的函数

/**
*@brief 设置串口数据位,停止位和效验位
*@param fd 类型 int 打开的串口文件句柄
*@param databits 类型 int 数据位 取值 为 7 或者8
*@param stopbits 类型 int 停止位 取值为 1 或者2
*@param parity 类型 int 效验类型 取值为N,E,O,,S
*/
 
int set_Parity(int fd,int databits,int stopbits,int parity)
{
    struct termios options;
     
    if ( tcgetattr( fd,&options) != 0) 
    {
	    perror("SetupSerial 1");
	    return(FALSE);
    }
     
    options.c_cflag &= ~CSIZE;
     
    switch (databits) /*设置数据位数*/
    {
         case 7:
		    options.c_cflag |= CS7;
	         break;
	    case 8:
		    options.c_cflag |= CS8;
		    break;
	    default:
		    fprintf(stderr,"Unsupported data sizen"); return (FALSE);
    }
     
    switch (parity)
    {
	    case 'n':
	    case 'N':
		    options.c_cflag &= ~PARENB; /* Clear parity enable */
		    options.c_iflag &= ~INPCK; /* Enable parity checking */
		    break;
	    case 'o':
	    case 'O':
		    options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
		    options.c_iflag |= INPCK; /* Disnable parity checking */
		    break;
	    case 'e':
	    case 'E':
		    options.c_cflag |= PARENB; /* Enable parity */
		    options.c_cflag &= ~PARODD; /* 转换为偶效验*/
		    options.c_iflag |= INPCK; /* Disnable parity checking */
		    break;
	    case 'S':
	    case 's': /*as no parity*/
		    options.c_cflag &= ~PARENB;
		    options.c_cflag &= ~CSTOPB;break;
	    default:
		    fprintf(stderr,"Unsupported parityn");
		    return (FALSE);
}

设置停止位

switch (stopbits)
{
    case 1:
	    options.c_cflag &= ~CSTOPB;
	    break;
    case 2:
	    options.c_cflag |= CSTOPB;
	    break;
    default:
	    fprintf(stderr,"Unsupported stop bitsn");
	    return (FALSE);
    }
     
    /* Set input parity option */
    if (parity != 'n')
         options.c_iflag |= INPCK;
     
    tcflush(fd,TCIFLUSH);
     
    options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/
    options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
     
    if (tcsetattr(fd,TCSANOW,&options) != 0)
    {
	    perror("SetupSerial 3");
	    return (FALSE);
    }
    return (TRUE);
}

在上述代码中,有两句话特别重要:

options.c_cc[VTIME] = 0; /* 设置超时0 seconds*/  
options.c_cc[VMIN] = 13; /* define the minimum bytes data to be readed*/

这两句话决定了对串口读取的函数read()的一些功能。

对串口操作的结构体是

struct{
       tcflag_t   c_iflag;    /*输入模式标记*/
       tcflag_t   c_oflag;   /*输出模式标记*/
       tcflag_t   c_cflag;   /*控制模式标记*/
       tcflag_t   c_lflag;    /*本地模式标记*/
       cc_t        c_line;     /*线路规程*/
       cc_t        c_cc[NCCS];  /*控制符号*/
};

其中cc_t c_line只有在一些特殊的系统程序(比如,设置通过tty设备来通信的网络协议)中才会用。在数组c_cc中有两个下标(VTIME和VMIN)对应的元素不是控制符,并且只是在原始模式下有效。只有在原始模式下,他们决定了read()函数在什么时候返回。在标准模式下,除非设置了O_NONBLOCK选项,否则只有当遇到文件结束符或各行的字符都已经编辑完毕后才返回。

控制符VTIME和VMIN之间有着复杂的关系。
VTIME定义要求等待的零到几百毫秒的时间量(通常是一个8位的unsigned char变量,取值不能大于cc_t)。
VMIN定义了要求等待的最小字节数(不是要求读的字节数——read()的第三个参数才是指定要求读的最大字节数),这个字节数可能是0。

l) 如果VTIME取0,VMIN定义了要求等待读取的最小字节数。函数read()只有在读取了VMIN个字节的数据或者收到一个信号的时候才返回。

  1. 如果VMIN取0,VTIME定义了即使没有数据可以读取,read()函数返回前也要等待几百毫秒的时间量。这时,read()函数不需要像其通常情况那样要遇到一个文件结束标志才返回0。

  2. 如果VTIME和VMIN都不取0,VTIME定义的是当接收到第一个字节的数据后开始计算等待的时间量。如果当调用read函数时可以得到数据,计时器马上开始计时。如果当调用read函数时还没有任何数据可读,则等接收到第一个字节的数据后,计时器开始计时。函数read可能会在读取到VMIN个字节的数据后返回,也可能在计时完毕后返回,这主要取决于哪个条件首先实现。不过函数至少会读取到一个字节的数据,因为计时器是在读取到第一个数据时开始计时的。

  3. **如果VTIME和VMIN都取0,即使读取不到任何数据,函数read也会立即返回。**同时,返回值0表示read函数不需要等待文件结束标志就返回了。

这就是这两个变量对read函数的影响。

注意的问题:
如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
options.c_oflag &= ~OPOST; /*Output*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值