之前在学习安信可A7模块时,是在PC上使用串口调试助手做了GPS的坐标数据信息的采集,同时分析了一些语句的含义。在这过程中,涉及到对嵌入式开发人员一个非常重要的知识:串口通信。在前篇也说到,我们将会自己写程序来对GPS数据进行解析,而这些数据正是靠串口来传输的。所以,本篇博文将进行关于串口通信的学习。
首先我们得知道串口长什么样,常用的串口接头有两种,一种是9针串口(简称DB-9),一种是25针串口(简称DB-25)。每种接头都有公头和母头之分,其中带针状的接头是公头,而带孔状的接头是母头。 以DB9为例,如图: 各个针脚功能说明: 在TXD和RXD数据线上: (1)逻辑1为-3~-15V的电压 (2)逻辑0为3~15V的电压 在RTS、CTS、DSR、DTR和DCD等控制线上: (1)信号有效(ON状态)为3~15V的电压 (2)信号无效(OFF状态)为-3~-15V的电压 这是由通信协议RS-232C规定的(请看后文)。
注:一般我们需要的就是2,3,5接口,连接时是TXD接RXD,RXD接TXD,GND接GND。自己的TXD口接RXD口,自发自收,测试串口是否正常。
OK,知道了串口的样子了,接下来就要更进一步,学习串口通信的基础知识了。
串口通信(Serial Communication),是指外设和计算机间,通过数据信号线、地线等,按位进行传输数据的一种通讯方式。 串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。
在串口通信中,常用的协议包括RS-232、RS-422和RS-485。 •RS-232:标准串口,最常用的一种串行通讯接口。有三种类型(A,B和C),它们分别采用不同的电压来表示on和off。最被广泛使用的是RS-232C,它将mark(on)比特的电压定义为-3V到-12V之间,而将space(off)的电压定义到+3V到+12V之间。传送距离最大为约15米,最高速率为20kb/s。RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为3~7kΩ。所以RS-232适合本地设备之间的通信。 •RS-422:最大传输距离为1219米,最大传输速率为10Mb/s。其平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能达到最大传输距离。只有在很短的距离下才能获得最高速率传输。一般100米长的双绞线上所能获得的最大传输速率仅为1Mb/s。 •RS-485:从RS-422基础上发展而来的,最大传输距离约为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能使用规定最长的电缆长度。只有在很短的距离下才能获得最高速率传输。一般100米长双绞线最大传输速率仅为1Mb/s。
同步通信:是一种比特同步通信技术,要求发收双方具有同频同相的同步时钟信号,只需在传送报文的最前面附加特定的同步字符,使发收双方建立同步,此后便在同步时钟的控制下逐位发送/接收。如:SPI总线。 异步通信:指两个互不同步的设备通过计时机制或其他技术进行数据传输。也就是说,双方不需要共同的时钟。发送方可以随时传输数据,而接收方必须在信息到达时准备好接收。如:串口(UART)。 接下来在后续的串口编程中讨论的都是异步通信,所以对同步通信不做过多的赘述了。 这里提一下UART和USART,实际上,从字面意思即可理解: UART:universal asynchronous receiver and transmitter(通用异步收/发器)。 USART:universal synchronous asynchronous receiver and transmitter(通用同步/异步收/发器)。 USART在UART基础上增加了同步功能,即USART是UART的增强型。 我常使用的S3C2440上就是支持的UART。
•单工模式(Simplex Communication):单向的数据传输。通信双方中,一方为发送端,一方则为接收端。信息只能沿一个方向传输,使用一根传输线。双方是固定的。 •半双工模式(Half Duplex):通信使用同一根传输线,既可以发送数据又可以接收数据,但不能同时进行发送和接收。数据传输允许数据在两个方向上传输,但是,在任何时刻只能由其中的一方发送数据,另一方接收数据。 •全双工模式(Full Duplex)通信允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。在全双工模式中,每一端都有发送器和接收器,有两条传输线,信息传输效率高。
我们有必要先弄清楚异步通信的数据格式。 (1)起始位:起始位必须是持续一个比特时间的“0”,标志传输一个字符的开始。 (2)数据位:数据位紧跟在起始位之后,是通信中的真正有效信息。数据位的位数可以由通信双方共同约定,一般可以是5位、7位或8位。传输数据时先传送字符的低位,后传送字符的高位。 (3)奇偶校验位:奇偶校验位仅占一位,用于进行奇校验或偶校验,奇偶检验位不是必须有的。如果是奇校验,需要保证传输的数据总共有奇数个“1”;如果是偶校验,需要保证传输的数据总共有偶数个“1”。 举例来说,假设传输的数据位为01001100,如果是奇校验,则奇校验位为0(要确保总共有奇数个1),如果是偶校验,则偶校验位为1(要确保总共有偶数个1)。 由此可见,奇偶校验位仅是对数据进行简单的置逻辑高位或逻辑低位,不会对数据进行实质的判断,这样做的好处是接收设备能够知道一个位的状态,有可能判断是否有噪声干扰了通信以及传输的数据是否同步。 (4)停止位:停止位可以是是1位、1.5位或2位,可以由软件设定。它一定是“1”,标志着传输一个字符的结束。 (5)空闲位:空闲位是指从一个字符的停止位结束到下一个字符的起始位开始,表示线路处于空闲状态,必须由高电平来填充。
好了,一些基础知识暂时先到这里,更深入的知识得自己自行了解了,接下来便是重头戏,在Linux下的串口编程了。
=========================================== 这里同时可以参考我之前的博文:STM8串口 (有相关经验的话)。对比一下两者的区别来进行学习。
===========================================
1、首先是操作串口需要包含的头文件:
#include <stdio.h> /*标准输入输出的定义*/ #include <errno.h> /*错误号定义*/ #include <sys/stat.h> #include <fcntl.h> /*文件控制定义*/ #include <termios.h> /*PPSIX 终端控制定义*/ #include <stdlib.h> /*标准函数库定义*/ #include <sys/types.h> #include <unistd.h> /*UNIX 标准函数定义*/2、串口相关操作 打开串口: 我们都知道,在Linux下,除了网络设备,其余的都是文件的形式。串口设备也一样在/dev下。 所以我们可以通过open系统调用/函数来访问它。 示例:fd = open("/dev/ttyUSB0",O_RDWR|O_NOCTTY|O_NDELAY); O_NOCTTY:可以告诉Linux这个程序不会成为这个端口上的“控制终端”.如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程。 O_NDELAY:标志则是告诉Linux,这个程序并不关心DCD信号线的状态——也就是不关心端口另一端是否已经连接。
读写串口: 与普通文件一样,使用read,write函数。 示例:read(fd,buff,8); write(fd,buff,8);
串口属性设置: 最基本的设置串口包括波特率设置,效验位和停止位设置。这由通信双方协定。
很多系统都支持POSIX终端(串口)接口.程序可以利用这个接口来改变终端的参数,比如,波特率,字符大小等等.要使用这个端口的话,你必须将<termios.h>头文件包含到你的程序中。这个头文件中定义了终端控制结构体和POSIX控制函数。
最重要的就是这个结构体:
struct termios { tcflag_t c_iflag; //输入选项 tcflag_t c_oflag; //输出选项 tcflag_t c_cflag; //控制选项 tcflag_t c_lflag; //行选项 cc_t c_cc[NCCS]; //控制字符 };其中我们更关注的是c_cflag控制选项。其中包含了波特率、数据位、校验位、停止位的设置。 它可以支持很多常量名称其中设置数据传输率为相应的数据传输率前要加上“B”。 c_cflag成员不能直接对其初始化,而要将其通过与、或操作使用其中的某些选项。 设置串口属性主要是配置termios结构体中的各个变量,大致流程如下:
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.设置奇偶效验位需要用到两个termios中的成员:c_cflag和c_iflag。首先要激活c_cflag中的校验位使能标志PARENB和是否进行奇偶效验,同时还要激活c_iflag中的奇偶效验使能。 设置奇校验: newtio.c_cflag |= PARENB; newtio.c_cflag |= PARODD; newtio.c_iflag |= (INPCK | ISTRIP); 设置偶校验: newtio.c_iflag |= (INPCK|ISTRIP); newtio.c_cflag |= PARENB; newtio.c_cflag |= ~PARODD;
6.激活c_cflag中的CSTOPB设置停止位。若停止位为1,则清除CSTOPB;若停止位为0,则激活CSTOPB。 newtio.c_cflag &= ~CSTOPB;
7.设置最少字符和等待时间。在对接收字符和等待时间没有特别要求的情况下,可以将其设置为0。 newtio.c_cc[VTIME] = 0; newtio.c_cc[VMIN] = 0;
8.调用函数”tcflush(fd,queue_selector)”来处理要写入引用的对象,queue_selector可能的取值有以下几种。 TCIFLUSH:刷新收到的数据但是不读 TCOFLUSH:刷新写入的数据但是不传送 TCIOFLUSH:同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送。 9.激活配置。在完成配置后,需要激活配置使其生效。使用tcsetattr()函数。 int tcsetattr(int filedes,int opt,const struct termios *termptr);
最后贴出串口配置的完整代码:
/********************************************************************************* * Copyright: (C) 2017 TangBin<tangbinmvp@gmail.com> * All rights reserved. * * Filename: s_uart1.c * Description: This file * * Version: 1.0.0(06/04/2017) * Author: TangBin <tangbinmvp@gmail.com> * ChangeLog: 1, Release initial version on "06/04/2017 07:51:59 PM" * ********************************************************************************/ #include <stdio.h> #include <errno.h> #include <sys/stat.h> #include <fcntl.h> #include <termios.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> int set_serial(int fd,int nSpeed,int nBits,char nEvent,int nStop) { struct termios newttys1,oldttys1; /*保存原有串口配置*/ if(tcgetattr(fd,&oldttys1)!=0) { perror("Setupserial 1"); return -1; } bzero(&newttys1,sizeof(newttys1)); newttys1.c_cflag|=(CLOCAL|CREAD ); /*CREAD 开启串行数据接收,CLOCAL并打开本地连接模式*/ newttys1.c_cflag &=~CSIZE;/*设置数据位*/ /*数据位选择*/ switch(nBits) { case 7: newttys1.c_cflag |=CS7; break; case 8: newttys1.c_cflag |=CS8; break; } /*设置奇偶校验位*/ switch( nEvent ) { case '0': /*奇校验*/ newttys1.c_cflag |= PARENB;/*开启奇偶校验*/ newttys1.c_iflag |= (INPCK | ISTRIP);/*INPCK打开输入奇偶校验;ISTRIP去除字符的第八个比特 */ newttys1.c_cflag |= PARODD;/*启用奇校验(默认为偶校验)*/ break; case 'E':/*偶校验*/ newttys1.c_cflag |= PARENB; /*开启奇偶校验 */ newttys1.c_iflag |= ( INPCK | ISTRIP);/*打开输入奇偶校验并去除字符第八个比特*/ newttys1.c_cflag &= ~PARODD;/*启用偶校验*/ break; case 'N': /*无奇偶校验*/ newttys1.c_cflag &= ~PARENB; break; } /*设置波特率*/ switch( nSpeed ) { case 2400: cfsetispeed(&newttys1, B2400); cfsetospeed(&newttys1, B2400); break; case 4800: cfsetispeed(&newttys1, B4800); cfsetospeed(&newttys1, B4800); break; case 9600: cfsetispeed(&newttys1, B9600); cfsetospeed(&newttys1, B9600); break; case 115200: cfsetispeed(&newttys1, B115200); cfsetospeed(&newttys1, B115200); break; default: cfsetispeed(&newttys1, B9600); cfsetospeed(&newttys1, B9600); break; } /*设置停止位*/ if( nStop == 1)/*设置停止位;若停止位为1,则清除CSTOPB,若停止位为2,则激活CSTOPB*/ { newttys1.c_cflag &= ~CSTOPB;/*默认为一位停止位; */ } else if( nStop == 2) { newttys1.c_cflag |= CSTOPB;/*CSTOPB表示送两位停止位*/ } /*设置最少字符和等待时间,对于接收字符和等待时间没有特别的要求时*/ newttys1.c_cc[VTIME] = 0;/*非规范模式读取时的超时时间;*/ newttys1.c_cc[VMIN] = 0; /*非规范模式读取时的最小字符数*/ tcflush(fd ,TCIFLUSH);/*tcflush清空终端未完成的输入/输出请求及数据;TCIFLUSH表示清空正收到的数据,且不读取出来 */ /*激活配置使其生效*/ if((tcsetattr( fd, TCSANOW,&newttys1))!=0) { perror("com set error"); return -1; } return 0; }下一篇将具体结合GPS的数据解析,便能更好从整体上理解、学习了。