欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

串口通信协议和Linux下的串口编程

程序员文章站 2022-06-09 08:47:09
...

一、串口通信介绍:
串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节,尽管比按位字节(byte)的并行通信慢,但是串口可以使用一根线发送数据的同时用另一根线接收数据。串口通信属于异步串行通信方式。串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。

二、串口接头:
常见的串口接头有两种,一种是9针串口(简单DB-9),一种是25针串口(简称DB-25)。

以DB9为例为例,如图:
串口通信协议和Linux下的串口编程

母头:泛指所有带孔状的接头(5针朝下,从左到右依次是1~9)
公头:泛指所有带针状的接头(5针朝下,从右到左依次是1~9)

各引脚的功能如下图
串口通信协议和Linux下的串口编程
三、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。

串口通信协议和Linux下的串口编程
串口通信属于异步串行通信方式
异步通信是指发送和接收端使用的是各自的时钟,并且它是一种不连续的传输通信方式,一次通信只能传输一个字符数据(字符帧)。字符帧之间的间隙可以是任意的,下图是异步串行通信帧格式:
串口通信协议和Linux下的串口编程
起始位:接收方探测, 默认总线空闲时是高电平,一旦来一个位的低电平,说明一帧数据开始了。紧接着就是数据位,一帧数据有多少个数据位,由通信双方定义。

数据位:在起始位之后,发送端发出的就是数据位,数据位的位数没有严格限制(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成员可以控制串口波特率、数据位、校验位、停止位以及硬件流控制等等,位成员有:
串口通信协议和Linux下的串口编程串口通信协议和Linux下的串口编程
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串口编程

读串口程序