串口通信
串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。串行接口(Serial Interface) 是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信,大大降低了成本,特别适用于远距离通信,但传送速度较慢。根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。串行接口按电气标准及协议来分包括RS-232-C、RS-422、RS485等。
异步串行是指UART(Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。UART包含TTL电平的串口和RS232电平的串口。 TTL电平是3.3V的,而RS232是负逻辑电平,它定义+5~+12V为低电平,而-12~-5V为高电平。UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输,其中各位的意义如下:
起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。
数据位:紧接在起始位之后,数据位的个数可以是4、5、6、7、8等,构成一个字符,从最低位开始传送,靠时钟定位。
奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。
停止位:它是一个字符数据的结束标志,可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
空闲位:处于逻辑“1”状态,表示当前线路上没有数据传送。
此外,在异步通信中还有一个重要的参数,即波特率,它是衡量数据传送速率的指标,表示每秒钟传送的符号数(symbol)。收发双方的波特率必须保持一致,才能保证数据的正常通信。
在Linux系统中,所有的设备文件都位于/dev目录下,以tty为前缀的文件,是终端设备。以tty加大写S开头的即为串口设备文件,如ttyS0即为串口0(即COM0)。但在嵌入式Linux中,串口是以ttySAC为前缀的,如ttySAC0即代表串口0。Linux系统中,要对文件进行相应地操作,必须先打开它,串口设备文件也不例外,所以需要调用open函数来打开对应的串口端口。另外在打开的同时,还需要设置打开文件的合适属性(如可读可写、不将设备分配为控制终端、无延时模式等)。 比如,可执行语句“open("/dev/ttySAC0", O_RDWR | O_NOCTTY | O_NDELAY);”。
在进行串口通信之前,先要对打开的串口进行配置,其中的重要参数有:波特率、数据位数、停止位位数、奇偶校验、硬件流量控制等。在Linux中,这些配置项目是通过一个名为termios 结构体来实现的。termios提供了一个常规的终端接口,用于控制非同步通信端口,这个结构包含了至少下列成员:
tcflag_t c_iflag; //输入模式
tcflag_t c_oflag; //输出模式
tcflag_t c_cflag; //控制模式
tcflag_t c_lflag; //本地模式
cc_t c_cc[NCCS]; //控制字符
下面就来看看每个成员都有些什么内容,先看c_iflag,它是输入模式标志,控制终端输入方式,具体参数如下表所示。
接下来看c_oflag,它是输出模式标志,控制终端输出方式,具体参数如下表所示。
接下来是c_cflag,它为控制模式标志,指定终端硬件控制信息,具体参数如下表所示。
下面是c_lflag,它为本地模式标志,控制终端编辑功能,具体参数如下表所示。
最后看c_cc[NCCS],它是控制字符,用于保存终端驱动程序中的特殊字符,具体参数如下表所示。
以上就是termios涉及到的全部内容,一般的串口通信,只需要操作c_cflag即可,可设置波特率、数据位、校验位、停止位等,配置时需通过“与”、“或”的方式进行操作。此外,为了方便操作,系统还给出了一些操作函数,下面就是一些常用的操作函数。
1、读取当前参数函数:
int tcgetattr(int fd,struct termios *termios_p)
fd:open操作后返回的文件描述符
*termios_p:为前面介绍的结构体
初始化开始前调用这个函数.
2、获取当前波特率函数:
int speed_t cfgetispeed(const struct termios *termios_p)
int speed_t cfgetospeed(const struct termios *termios_p)
*termios_p:为前面介绍的结构体
成功返回0,失败返回-1
3、波特率设置函数:
int cfsetispeed(struct termios *termios_p,speed_t speed)
int cfsetospeed(struct termios *termios_p,speed_t speed)
*termios_p:为前面介绍的结构体
speed:波特率,常用B2400,B4800,B9600,B115200,B460800
成功返回0,失败返回-1
4、清空buffer数据函数:
int tcflush(int fd,int queue_selector)
queue_selector:有三个常用宏定义
TCIFLUSH:清空正读的数据,且不会读出
TCOFLUSH:清空正写入的数据,且不会发送到终端
TCIOFLUSH:清空所有正在发生的I/O数据.
成功返回0,失败返回-1
5、设置串口参数函数:
int tcsetattr(int fd,int optional_actions,cons struct termios *termios_p)
optional_actions:有三个常用宏定义
TCSANOW:不等数据传输完毕,立即改变属性
TCSADRAIN:等所有数据传输完毕,再改变属性
TCSAFLUSH:清空输入输出缓冲区才改变属性
成功返回0,失败返回-1
普通串口的配置步骤如下:
1、保存原先的串口配置,用tcgetattr()函数。
struct termios newtio,oldtio;
tcgetattr(fd,&oldtio);
2、激活选项有CLOCAL和CREAD,用于本地连接和接收使用。
newtio.c_cflag | = CLOCAL | CREAD;
3、设置波特率,使用函数cfsetispeed、cfsetospeed。
cfsetispeed(&newtio,B115200);
cfsetospeed(&newtio,B115200);
4、设置数据位,需使用掩码设置。
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag |= CS8;
5、设置奇偶校验位,使用c_cflag和c_iflag。
(1)设置奇校验:
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
(2)设置偶校验:
newtio.c_iflag |= (INPCK|ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
(3)设置无校验:
newtio.c_cflag &= ~PARENB;
6、设置停止位,通过激活c_cflag中的CSTOPB实现。若停止位为1,则清除CSTOPB,若停止位为2,则激活CSTOPB。
newtio.c_cflag &= ~CSTOPB;
7、设置最少字符和等待时间,对于接收字符和等待时间没有特别的要求时,可设为0。
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
8、处理要写入的引用对象
tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读)或输出缓存(用户程序已经写,但尚未发送)。
int tcflush(int filedes,int quene)
quene数应当是下列三个常数之一:
TCIFLUSH 刷清输入队列
TCOFLUSH 刷清输出队列
TCIOFLUSH 刷清输入、输出队列
如:tcflush(fd,TCIFLUSH);
9、激活配置,在完成配置后,需要激活配置使其生效。使用tcsetattr()函数。
int tcsetattr(int filedes,int opt,const struct termios *termptr);
opt可以指定在什么时候新的终端属性才起作用。
TCSANOW 更改立即发生
TCSADRAIN 发送了所有输出后更改才发生,若更改输出参数则应使用此选项。
TCSAFLUSH 发送了所有输出后更改才发生,更进一步,在更改发生时未读的所有输入数据都被删除。
如:tcsetattr(fd,TCSANOW,&newtio);
以上设置的顺序也非常重要,若不按上述顺序进行设置,很可能会导致串口通信不正常。
在使用open()函数打开串口之后,紧接着应恢复串口为阻塞状态,用于等待串口数据的读入。可使用fcntl()函数来实现,如下:
fcntl(fd,F_SETFL,0); //F_SETFL:设置文件flag为0,即默认的阻塞状态,返回值应为0
接着应测试打开的文件描述符是否应用一个终端设备,以进一步确认串口是否正确打开。可使用isatty()函数来实现,如下:
isatty(STDIN_FILENO); //返回值为非0即表明成功
串口设置时,应当在完成以上两步之后,再对termios进行设置。以下给出一个用于设置串口波特率等参数的函数,仅供参考。
int Setup_Serial(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0)
{
perror("Setup Serial save error!");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
switch( nBits )
{
case 5:
newtio.c_cflag |= CS5;
break;
case 6:
newtio.c_cflag |= CS6;
break;
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
case 460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("Setup Serial error!");
return -1;
}
// printf("Setup Serial complete!\n");
return 0;
}
在调用时,只需要按照需要填写参数即可,比如:Setup_Serial(fd, 115200, 8, N, 1),即把fd打开的串口设置成波特率115200,数据位8位,无校验,1位停止位的模式。
在完成了以上设置之后,就可以通过读写fd来实现串口的数据通信了,比如要把串口接收到的数据读入Buff中,可执行“read(fd, Buff, 8);”,就会读入8字节数据,要向串口发送Buff中的数据,可执行“write(fd, Buff, 8);”,就会发送出8字节数据。需要注意的是,在嵌入式Linux系统中,受缓冲区的限制,一次只能读取8字节数据,多于8字节的需要分次读取,这就是主程序设计的事情了。