1 终端 I/O
在 Linux 系统中,串口属于终端 I/O 操作。终端 I/O 有两种不同的工作模式:
- 规范模式(canonical mode)。在这种模式中,对终端输入以行为单位进行处理。对于每个读请求,终端驱动程序最多返回一行。
- 非规范模式(noncanonical mode)。输入字符不装配成行。串口操作一般都是使用非规范模式。
每个终端设备都有一个输入队列和输出队列,如下图所示:
对此图要说明以下几点:
- 如果打开了回显功能,则在输入队列和输出队列之间有一个隐含的连接。
- 输入队列的长度 MAX_INPUT 是有限值。当一个特定设备的输入队列已经填满时,系统的行为将依赖于实现。这种情况发生时大多数 UNIX 系统回显响铃字符。
- 图中没有显示另一个输入限制 MAX_CANON。这个限制是一个规范输入行的最大字节数。
- 虽然输出队列的长度通常也是有限的,但是程序并不能获得这个定义其长度的变量,因为当输出队列将要填满时,内核便直接使写进程休眠,直至写队列中有可用的空间。
- 函数 tcflush() 可以 flush 输入或输出队列。
Linux 系统在一个成为线路规程(line discipline) 的模块中进行全部的规范处理。可以将这个模块设想成一个盒子,位于内核通用的读、写函数和实际设备驱动程序之间。示意图如下:
所有可以检测和更改的终端设备特性都包含在 termios 结构中。该结构定义在头文件 <termbits.h> 中。
#ifndef _ALPHA_TERMBITS_H
#define _ALPHA_TERMBITS_H
#include <linux/posix_types.h>
typedef unsigned char cc_t;
typedef unsigned int speed_t;
typedef unsigned int tcflag_t;
/*
* termios type and macro definitions. Be careful about adding stuff
* to this file since it's used in GNU libc and there are strict rules
* concerning namespace pollution.
*/
#define NCCS 19
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_cc[NCCS]; /* control characters */
cc_t c_line; /* line discipline (== c_cc[19]) */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
/* Alpha has matching termios and ktermios */
struct ktermios {
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_cc[NCCS]; /* control characters */
cc_t c_line; /* line discipline (== c_cc[19]) */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
/* c_cc characters */
#define VEOF 0
#define VEOL 1
#define VEOL2 2
#define VERASE 3
#define VWERASE 4
#define VKILL 5
#define VREPRINT 6
#define VSWTC 7
#define VINTR 8
#define VQUIT 9
#define VSUSP 10
#define VSTART 12
#define VSTOP 13
#define VLNEXT 14
#define VDISCARD 15
#define VMIN 16
#define VTIME 17
/* c_iflag bits */
#define IGNBRK 0000001
#define BRKINT 0000002
#define IGNPAR 0000004 // ignore characters with parity errors
#define PARMRK 0000010 // mark parity errors
#define INPCK 0000020 // enable input parity checking
#define ISTRIP 0000040
#define INLCR 0000100
#define IGNCR 0000200
#define ICRNL 0000400
#define IXON 0001000
#define IXOFF 0002000
#define IXANY 0004000
#define IUCLC 0010000
#define IMAXBEL 0020000
#define IUTF8 0040000
/* c_oflag bits */
#define OPOST 0000001
#define ONLCR 0000002
#define OLCUC 0000004
#define OCRNL 0000010
#define ONOCR 0000020
#define ONLRET 0000040
#define OFILL 00000100
#define OFDEL 00000200
#define NLDLY 00001400
#define NL0 00000000
#define NL1 00000400
#define NL2 00001000
#define NL3 00001400
#define TABDLY 00006000
#define TAB0 00000000
#define TAB1 00002000
#define TAB2 00004000
#define TAB3 00006000
#define CRDLY 00030000
#define CR0 00000000
#define CR1 00010000
#define CR2 00020000
#define CR3 00030000
#define FFDLY 00040000
#define FF0 00000000
#define FF1 00040000
#define BSDLY 00100000
#define BS0 00000000
#define BS1 00100000
#define VTDLY 00200000
#define VT0 00000000
#define VT1 00200000
#define XTABS 01000000 /* Hmm.. Linux/i386 considers this part of TABDLY.. */
/* c_cflag bit meaning */
#define CBAUD 0000037
#define B0 0000000 /* hang up */
#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
#define EXTA B19200
#define EXTB B38400
#define CBAUDEX 0000000
#define B57600 00020
#define B115200 00021
#define B230400 00022
#define B460800 00023
#define B500000 00024
#define B576000 00025
#define B921600 00026
#define B1000000 00027
#define B1152000 00030
#define B1500000 00031
#define B2000000 00032
#define B2500000 00033
#define B3000000 00034
#define B3500000 00035
#define B4000000 00036
#define CSIZE 00001400 // character size mask
#define CS5 00000000 // 5 character size per byte
#define CS6 00000400 // 6 character size per byte
#define CS7 00001000 // 7 character size per byte
#define CS8 00001400 // 8 character size per byte
#define CSTOPB 00002000 // send two stop bits, else one
#define CREAD 00004000 // enable receiver
#define PARENB 00010000 // parity enable
#define PARODD 00020000 // odd parity, else even
#define HUPCL 00040000
#define CLOCAL 00100000
#define CMSPAR 010000000000
#define CRTSCTS 020000000000 //hardware flow control
/* c_lflag bits */
#define ISIG 0x00000080
#define ICANON 0x00000100 // canonical input
#define XCASE 0x00004000
#define ECHO 0x00000008
#define ECHOE 0x00000002
#define ECHOK 0x00000004
#define ECHONL 0x00000010
#define NOFLSH 0x80000000
#define TOSTOP 0x00400000
#define ECHOCTL 0x00000040
#define ECHOPRT 0x00000020
#define ECHOKE 0x00000001
#define FLUSHO 0x00800000
#define PENDIN 0x20000000
#define IEXTEN 0x00000400
#define EXTPROC 0x10000000
/* Values for the ACTION argument to `tcflow'. */
#define TCOOFF 0
#define TCOON 1
#define TCIOFF 2
#define TCION 3
/* Values for the QUEUE_SELECTOR argument to `tcflush'. */
#define TCIFLUSH 0 // input and output queue
#define TCOFLUSH 1 // output queue
#define TCIOFLUSH 2 // input queue
/* Values for the OPTIONAL_ACTIONS argument to `tcsetattr'. */
#define TCSANOW 0 // the change occurs immediately
#define TCSADRAIN 1 // the change occurs after all output has been transmitted. This option should be used if we are changing the output parameters.
#define TCSAFLUSH 2 // the change occurs after all output has been transmitted. Furthermore, when the change takes place, all input data that has not been read is flushed.
#endif /* _ALPHA_TERMBITS_H */
- 输入标志通过终端设备驱动程序控制字符的输入。例如,剥除输入字节的第8位,允许输入奇偶校验。
- 输出标志则控制驱动程序输出。例如,执行输出处理、将换行符转换为CR/LF。
- 控制标志控制波特率、字节位数、奇偶校验、流控制等。
- 本地标志映像驱动程序和用户之间的接口。例如,打开回显或关闭、可视地擦除字符、允许终端产生的信号以及对后台输出的作业控制停止信号。
终端 I/O 的函数包含在头文件 <termios.h>中。具体函数如下表所示:
函数 | 描述 |
---|---|
tcgetattr | fetch attributes(termios structure) |
tcsetattr | set attributes(termios structure) |
cfgetispeed | get input speed |
cfgetospeed | get output speed |
cfsetispeed | set input speed |
cfsetospeed | set output speed |
tcdrain | wait for all output to be transmitted |
tcflow | suspend transmit or receive |
tcflush | flush pending input and/or input |
tcsendbreak | send BREAK character |
各函数的关系如下:
通过关闭 termios 结构中的 c_lflag 字段的 ICANON 标志来指定非规范模式。在非规范模式中,系统如何知道在什么时候将数据返回给我们呢?如果它一次返回一个字节,那么系统的开销就会过大。在启动读数据之前,往往不知道要读多少数据,所以系统不能总是一次返回多个字节。
解决方法是,当已读了指定量的数据后,或者已经超过了给定量的时间后,即通知系统返回。这种技术使用了 termios 结构中的 c_cc 数组的两个变量: MIN 和 TIME。 c_cc 数组中的这两个元素的下标名为 VMIN 和 VTIME。
MIN 指定一个 read 返回前的最小字节数。TIME 指定等待数据到达的分秒数(分秒为秒的 1//10 )。有下列 4 中情形。
- MIN > 0, TIME >0
TIME 指定一个字节间定时器(interbyte timer),它只在第一个字节被接收时启动。在该定时器超时之前,若已接到 MIN 个字节,则 read 函数返回 MIN 个字节。如果在接到 MIN 个字节之前,该定时器已超时,则 read 函数返回已接收到的字节。 - MIN > 0, TIME = 0
read 函数在接收到 MIN 个字节之前不返回。这会造成 read 函数无限期阻塞。 - MIN = 0, TIME > 0
TIME 指定一个调用 read 函数时启动的读定时器。在接到一个字节或该定时器超时时, read 函数即返回。如果是定时器超时,则 read 函数返回 0。 - MIN = 0,TIME = 0
如果有数据可用,则 read 函数最多返回所要求的字节数。如果无数据可用,则 read 函数返回 0。
2 串口操作
2.1 打开串口
在 Linux 操作系统中,一切都是文件,对串口的操作都归类与对文件的操作。下图是 IMX6DL 控制板 /dev 目录下的设备文件:
其中,ttymxc0、ttymxc1、ttymxc2 分别代表了串口 1、2、3 的设备文件。
fd = open("/dev/ttymxc0",O_RDWR|O_NOCTTY| O_NONBLOCK )
- fd:文件描述符,int 值,在 Linux 系统中代表一个打开的文件。
- O_RDWR:可读、可写
- O_NOCTTY:如果设备是终端设备,则不将该设备分配作为此进程的控制终端
- O_NONBLOCK:非阻塞方式打开
2.2 设备串口参数
int uart_set(int fd,int baude,int c_flow,int bits,char parity,int stop)
{
struct termios options;
/*获取终端属性*/
if(tcgetattr(fd,&options) < 0)
{
LOG_ERROR("tcgetattr error");
return -1;
}
/*设置输入输出波特率,两者保持一致*/
switch(baude)
{
case 4800:
cfsetispeed(&options,B4800);
cfsetospeed(&options,B4800);
break;
case 9600:
cfsetispeed(&options,B9600);
cfsetospeed(&options,B9600);
break;
case 19200:
cfsetispeed(&options,B19200);
cfsetospeed(&options,B19200);
break;
case 38400:
cfsetispeed(&options,B38400);
cfsetospeed(&options,B38400);
break;
default:
LOG_ERROR("Unkown baude!");
return -1;
}
/*设置控制模式*/
options.c_cflag |= CLOCAL;//忽略调制解调器状态线
options.c_cflag |= CREAD;//使能接收
/*设置数据流控制*/
switch(c_flow)
{
case 0://不进行流控制
options.c_cflag &= ~CRTSCTS;
break;
case 1://进行硬件流控制
options.c_cflag |= CRTSCTS;
break;
case 2://进行软件流控制
options.c_cflag |= IXON|IXOFF|IXANY;
break;
default:
LOG_ERROR("Unkown c_flow!\n");
return -1;
}
/*设置数据位*/
switch(bits)
{
case 5:
options.c_cflag &= ~CSIZE;//屏蔽其它标志位
options.c_cflag |= CS5;
break;
case 6:
options.c_cflag &= ~CSIZE;//屏蔽其它标志位
options.c_cflag |= CS6;
break;
case 7:
options.c_cflag &= ~CSIZE;//屏蔽其它标志位
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag &= ~CSIZE;//屏蔽其它标志位
options.c_cflag |= CS8;
break;
default:
LOG_ERROR("Unkown bits!");
return -1;
}
/*设置校验位*/
switch(parity)
{
/*无奇偶校验位*/
case 'n':
case 'N':
options.c_cflag &= ~PARENB;//关闭奇偶校验
break;
/*设置奇校验*/
case 'o':
case 'O':
options.c_cflag |= PARENB;//PARENB:产生奇偶位,执行奇偶校验
options.c_cflag |= PARODD;//PARODD:若设置则为奇校验,否则为偶校验
break;
/*设置偶校验*/
case 'e':
case 'E':
options.c_cflag |= PARENB;//PARENB:产生奇偶位,执行奇偶校验
options.c_cflag &= ~PARODD;//PARODD:若设置则为奇校验,否则为偶校验
break;
default:
LOG_ERROR("Unkown parity!");
return -1;
}
/*设置停止位*/
switch(stop)
{
case 1:
options.c_cflag &= ~CSTOPB;//CSTOPB:使用一位停止位
break;
case 2:
options.c_cflag |= CSTOPB;//CSTOPB:使用两位停止位
break;
default:
LOG_ERROR("Unkown stop!");
return -1;
}
/*设置输出模式为原始输出*/
options.c_oflag &= ~OPOST;//OPOST:若设置则按定义的输出处理,否则所有c_oflag失效
/*设置本地模式为原始模式*/
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/*
*ICANON:允许规范模式进行输入处理
*ECHO:允许输入字符的本地回显
*ECHOE:在接收EPASE时执行Backspace,Space,Backspace组合
*ISIG:允许信号
*/
/*设置等待时间和最小接受字符*/
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;//最少读取一个字符
/*如果发生数据溢出,只接受数据,但是不进行读操作*/
tcflush(fd,TCIFLUSH);
/*激活配置*/
if(tcsetattr(fd,TCSANOW,&options) < 0)
{
LOG_ERROR("tcsetattr failed");
return -1;
}
return 0;
}
- baude:波特率
- c_flow:流控制
- bits:字节位数
- parity:奇偶校验
- stop:停止位
2.3 写操作
ssize_t write(int fd, const void *buf, size_t nbytes)
返回值:若成功,返回已写的字节数;若出错,返回 -1
若输出队列已满,当文件以 BLOCK 方式打开(默认方式),write 阻塞至输出队列可用,直到输出字节全部发送结束;当文件以 O_NONBLOCK 方式打开,write 直接返回,返回值为已经写入的字节数。
2.4 读操作
ssize_t read(int fd, void *buf, size_t nbytes)
返回值:读到的字节数,若已经到文件尾,返回 0;若出错,返回 -1
若输入队列中无数据可读,当文件以 BLOCK 方式打开(默认方式),read 阻塞至输入队列可用,直到要求的字节数读满;当文件以 O_NONBLOCK 方式打开, read 直接返回,返回值为已读的字节数。
2.5 关闭操作
int close(int fd)
返回值:若成功,返回0;若出错,返回 -1