Linux 串口篇

1 终端 I/O

在 Linux 系统中,串口属于终端 I/O 操作。终端 I/O 有两种不同的工作模式:

  1. 规范模式(canonical mode)。在这种模式中,对终端输入以行为单位进行处理。对于每个读请求,终端驱动程序最多返回一行。
  2. 非规范模式(noncanonical mode)。输入字符不装配成行。串口操作一般都是使用非规范模式。

每个终端设备都有一个输入队列和输出队列,如下图所示:


对此图要说明以下几点:

  1. 如果打开了回显功能,则在输入队列和输出队列之间有一个隐含的连接。
  2. 输入队列的长度 MAX_INPUT 是有限值。当一个特定设备的输入队列已经填满时,系统的行为将依赖于实现。这种情况发生时大多数 UNIX 系统回显响铃字符。
  3. 图中没有显示另一个输入限制 MAX_CANON。这个限制是一个规范输入行的最大字节数。
  4. 虽然输出队列的长度通常也是有限的,但是程序并不能获得这个定义其长度的变量,因为当输出队列将要填满时,内核便直接使写进程休眠,直至写队列中有可用的空间。
  5. 函数 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 */
  1. 输入标志通过终端设备驱动程序控制字符的输入。例如,剥除输入字节的第8位,允许输入奇偶校验。
  2. 输出标志则控制驱动程序输出。例如,执行输出处理、将换行符转换为CR/LF。
  3. 控制标志控制波特率、字节位数、奇偶校验、流控制等。
  4. 本地标志映像驱动程序和用户之间的接口。例如,打开回显或关闭、可视地擦除字符、允许终端产生的信号以及对后台输出的作业控制停止信号。

终端 I/O 的函数包含在头文件 <termios.h>中。具体函数如下表所示:

函数描述
tcgetattrfetch attributes(termios structure)
tcsetattrset attributes(termios structure)
cfgetispeedget input speed
cfgetospeedget output speed
cfsetispeedset input speed
cfsetospeedset output speed
tcdrainwait for all output to be transmitted
tcflowsuspend transmit or receive
tcflushflush pending input and/or input
tcsendbreaksend BREAK character

各函数的关系如下:
在这里插入图片描述
通过关闭 termios 结构中的 c_lflag 字段的 ICANON 标志来指定非规范模式。在非规范模式中,系统如何知道在什么时候将数据返回给我们呢?如果它一次返回一个字节,那么系统的开销就会过大。在启动读数据之前,往往不知道要读多少数据,所以系统不能总是一次返回多个字节。

解决方法是,当已读了指定量的数据后,或者已经超过了给定量的时间后,即通知系统返回。这种技术使用了 termios 结构中的 c_cc 数组的两个变量: MIN 和 TIME。 c_cc 数组中的这两个元素的下标名为 VMIN 和 VTIME。

MIN 指定一个 read 返回前的最小字节数。TIME 指定等待数据到达的分秒数(分秒为秒的 1//10 )。有下列 4 中情形。

  1. MIN > 0, TIME >0
    TIME 指定一个字节间定时器(interbyte timer),它只在第一个字节被接收时启动。在该定时器超时之前,若已接到 MIN 个字节,则 read 函数返回 MIN 个字节。如果在接到 MIN 个字节之前,该定时器已超时,则 read 函数返回已接收到的字节。
  2. MIN > 0, TIME = 0
    read 函数在接收到 MIN 个字节之前不返回。这会造成 read 函数无限期阻塞。
  3. MIN = 0, TIME > 0
    TIME 指定一个调用 read 函数时启动的读定时器。在接到一个字节或该定时器超时时, read 函数即返回。如果是定时器超时,则 read 函数返回 0。
  4. 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

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值