串口通信协议和Linux下的串口编程
一、串口通信介绍:
串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节,尽管比按位字节(byte)的并行通信慢,但是串口可以使用一根线发送数据的同时用另一根线接收数据。串口通信属于异步串行通信方式。串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。
二、串口接头:
常见的串口接头有两种,一种是9针串口(简单DB-9),一种是25针串口(简称DB-25)。
以DB9为例为例,如图:
母头:泛指所有带孔状的接头(5针朝下,从左到右依次是1~9)
公头:泛指所有带针状的接头(5针朝下,从右到左依次是1~9)
各引脚的功能如下图
三、TTL电平和RS232电平:
1、TTL(Transistor-Transistor Logic),即晶体管-晶体管逻辑的简称,它是计算机处理器控制的设备内部各部分之间通信的标准技术。TTL电平信号应用广泛,是因为其数据表示采用二进制规定,+5V等价于逻辑”1”,0V等价于逻辑”0”。
数字电路中,由TTL电子元器件组成电路的电平是个电压范围,规定:
输出高电平>=2.4V,输出低电平<=0.4V;
输入高电平>=2.0V,输入低电平<=0.8V。
2、RS232电平
RS232电平是串口的一个标准。
在TXD和RXD数据线上:
(1)逻辑1为-3~-15V的电压
(2)逻辑0为3~15V的电压
在RTS、CTS、DSR、DTR和DCD等控制线上:
(1)信号有效(ON状态)为3~15V的电压
(2)信号无效(OFF状态)为-3~-15V的电压
这是由通信协议RS-232规定的。
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-232 是用正负电压来表示逻辑状态,与晶体管-晶体管逻辑集成电路(TTL)以高低电平表示逻辑状态的规定正好相反。而我们 STM32 芯片使用的就是 TTL 电平,所以要实现 STM32 与计算机的串口通信,需要进行 TTL与 RS-232C 电平转换,通常使用的电平转换芯片是 MAX3232
四、串口通信
两台计算机进行通信时,最少可以只要三根线,分别为RXD,TXD GND。
串口通信属于异步串行通信方式
异步通信是指发送和接收端使用的是各自的时钟,并且它是一种不连续的传输通信方式,一次通信只能传输一个字符数据(字符帧)。字符帧之间的间隙可以是任意的,下图是异步串行通信帧格式:
起始位:接收方探测, 默认总线空闲时是高电平,一旦来一个位的低电平,说明一帧数据开始了。紧接着就是数据位,一帧数据有多少个数据位,由通信双方定义。
数据位:在起始位之后,发送端发出的就是数据位,数据位的位数没有严格限制(5-8位都可以)。低位在前,高位在后。由低位向高位逐位发送。
校验位:数据位发完了,一般会跟奇偶校验位(奇校验、偶校验、无校验),验证收发双方的数据是否正常。
奇校验: 数据位加上校验位保证1的个数为奇数;
偶校验: 数据位加上校验位保证1的个数为偶数
停止位:数据发完之后,会发一个停止位,停止位的宽度一般是: 1, 1.5, 2
空闲位:空闲位是指从一个字符的停止位结束到下一个字符的起始位开始,表示线路处于空闲状态,必须由高电平来填充。
五、Linux串口编程
在Linux系统中,一切皆文件,所以串口设备也是一类文件,学习过Linux驱动程序的学员都知道,Linux有三类设备:字符设备,块设备,网络设备。那么串口设备属于字符设备。所以串口设备的命名一般为/dev/ttySn(n = 0、1、2…),如果该串口为USB转串口,可能名称为/dev/ttyUSBn(n = 0、1、2…),不同的平台下串口的名称是不同的,且串口的名称也是可以更改的。如何更改?在板卡对应的Linux驱动中更改。
在Linux下操作串口,那么也就是跟操作一个文件一样,既然是文件,也就可以使用标准的文件操作API来操作。
在进行文件操作时,我们先看一下操作串口需要包含的有文件:
#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 标准函数定义*/
1、打开串口
fd = open("/dev/ttyUSB0",O_RDWR|O_NOCTTY|O_NDELAY);
open的第二个参数可以设置为如下:
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以读写方式打开文件
(O_RDONLY和O_WRONLY和O_RDWR三个中必选一个)
O_APPEND:写入数据时添加到文件末尾
O_CREATE:如果文件不存在则产生该文件,使用该标志需要设置访问权限位mode_t
O_EXCL:指定该标志,并且指定了O_CREATE标志,如果打开的文件存在则会产生一个错误
O_TRUNC:如果文件存在并且成功以写或者只写方式打开,则清除文件所有内容,使得文件长度变为0
O_NOCTTY:如果打开的是一个终端设备,这个程序不会成为对应这个端口的控制终端,如果没有该标志,任何一个输入,例如键盘中止信号等,都将影响进程。
O_NONBLOCK:该标志与早期使用的O_NDELAY标志作用差不多。程序不关心DCD信号线的状态,如果指定该标志,进程将一直在休眠状态,直到DCD信号线为0。
2、写串口
write(fd, buf,sizeof(buf) );
和写入其他设备文件的方式相同,write函数也会返回发送数据的字节数或者发生错误的时候返回-1。通常,发送数据最常见的错误就是EIO,当调制解调器或者数据链路将Data Carrier Detect(DCD)信号线弄掉了,就会发生这个错误,而且,直至关闭端口这个情况会一直持续。
3、读串口
read( fd, buf, sieof(buf));
当端口在raw data mode操作模式下,那么read系统调用将返回从串口输入缓冲区中实际得到的字节数,如果没有数据可读,那么该系统调用将会被阻塞(block)直到有数据为止,如果超过一定时间仍然没有数据可读,那么将返回一个错误(读错误)。read函数也可以立即返回(即在没有数据可读的情况下也能立即返回,而不是被阻塞),需要做如下工作:
fcntl(fd, F_SETFL, FNDELAY);
FNDELAY选项的意思是read函数在没有数据可读的情况下立即返回0。需要重新回到阻塞模式(blocking)下,那么在调用fcntl()的时候不要加上FNDELAY这个选项:
fcntl(fd, F_SETFL, 0);
4、关闭串口
关闭串口使用close系统调用,例如:
close(fd);
5、串口属性配置
如果不设置串口的波特率,数据位,停止位,校验位的情况下,Linux下默认设置的属性值为:
波特率:9600
数据位:8
校验位:n(表示无)
停止位:1
在不设置串口属性值的情况下,也可以读写串口值。
但是在实际产品开发过程中,还是会根据不同的应用场景来设置串口的属性。很多系统都支持POSIX终端(串口)接口,程序可以利用这个接口来改变终端的参数,比如波特率,字符大小等,要利用这个端口的话,你必须将<termios.h>头文件包含到你的程序中,这个头文件中定义了终端控制结构体和POSIX控制函数。
设置串口属性先要tcgetattr()用来取得设备终端属性,然后中间设置波特率,设置停止位,校验位,数据位等,最后用tcsetattr()设置终端的属性。调用这两个函数的时候,需要提供一个包含着所有串口选项的tremios结构体:
struct termios
{
tcflag_t c_iflag; //输入选项
tcflag_t c_oflag; //输出选项
tcflag_t c_cflag; //控制选项
tcflag_t c_lflag; //行选项
cc_t c_cc[NCCS]; //控制字符
};
通过termios结构体的c_cflag成员可以控制串口波特率、数据位、校验位、停止位以及硬件流控制等等,位成员有:
1)设置串口波特率
使用函数cfsetispeed设置输入波特率和cfsetospeed设置输出波特率
struct termios options;
tcgetattr(fd, &options);
cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);
2)通过位掩码的方式**本地连接和接受使能选项:CLOCAL和CREAD
options.c_cflag | = CLOCAL | CREAD;
3)设置数据位
数据位指的是每字节中实际数据所占的比特数。要修改数据位可以通过修改termios结构体中c_cflag成员来实现。CS5、CS6、CS7和CS8分别表示数据位为5、6、7和8。值得注意的是,在设置数据位时,必须先使用CSIZE做位屏蔽。
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
4)设置校验位
奇偶校验可以选择偶校验、奇校验等方式,也可以不使用校验。如果要设置为偶校验的话,首先要将termios结构体中c_cflag设置PARENB标志,并清除PARODD标志。如果要设置奇校验,要同时设置termios结构体中c_cflag设置PARENB标志和PARODD标志。**c_iflag中的奇偶效验使能,如果不想使用任何校验的话,清除termios结构体中c_cflag的PARENB位。
设置奇校验
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
options.c_iflag |= (INPCK | ISTRIP); //INPCK 打开输入奇偶校验,ISTRIP 去掉字符第8位
设置偶校验
options.c_iflag |= (INPCK|ISTRIP);
options.c_cflag |= PARENB;
options.c_cflag |= ~PARODD;
设置无校验位
options.c_cflag &= ~PARENB;
5)设置停止位
用CSTOPB只能设置1位或2位,通过设置c_cflag中的CSTOPB设置停止位。若停止位为1,则清除CSTOPB;若停止位为2,则**CSTOPB。
一位停止位
options.c_cflag &=~CSTOP;
两位停止位
options.c_cflag |=CSTOPB;
6)设置最少字符和等待时间
在对接收字符和等待时间没有特别要求的情况下,可以将其设置位0.
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
7)调用函数”tcflush(fd,queue_selector)”来处理要写入引用的对象
queue_selector可能的取值有以下几种
TCIFLUSH:刷新收到的数据但是不读
TCOFLUSH:刷新写入的数据但是不传送
TCIOFLUSH:同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送。
7)最后tcsetattr()设置终端的属性
*int tcsetattr(int fd, int optional_actions, const struct termios termios_p);
第一个参数:fd为打开的终端文件描述符
第二个参数:optional_actions用于控制修改起作用的时间。
可以取如下值:
optional_actions可以取如下的值:
TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:等待所有数据传输结束,清空输入输出缓冲区才改变属性。
第三个参数:结构体termios_p中保存了要修改的参数。
调用该函数出现的错误信息:
EBADF:非法的文件描述符。
EINTR:tcsetattr函数调用被信号中断。
EINVAL:参数optional_actions使用了非法值,或参数termios中使用了非法值。
六、简单的Linux串口编程
读串口程序
推荐阅读