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

串口协议及简单的串口通信(96-N-8-1 格式)

程序员文章站 2024-02-22 10:08:58
...

一、串口协议

(1)串口通信的定义

串口通信(Serial Communication),是指外设和计算机间通过数据信号线、地线等按位进行传输数据的一种通信方式,属于串行通信方式。串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。

(2)接口标准

串口通信的接口标准有很多,有 RS-232C、RS-232、RS-422A、RS-485 等。常用的就是 RS-232 和 RS-485。RS-232其实是 RS-232C 的改进,原理是一样的。这里我们就以 RS-232C 接口进行分析。RS-232C 是 EIA(美国电子工业协会)1969 年修订 RS-232C 标准。

RS-232C定义了数据终端设备(DTE)与数据通信设备(DCE)之间的物理接口标准。RS-232C 接口规定使用 25 针连接器,简称 DB25,连接器的尺寸及每个插针的排列位置都有明确的定义,如下图所示:

串口协议及简单的串口通信(96-N-8-1 格式)
RS-232C 还有一种 9 针的非标准连接器接口,简称 DB9。串口通信使用的大多都是 DB9 接口。DB25 和 DB9 接头有公头和母头之分,其中带针状的接头是公头,而带孔状的接头是母头。

串口协议及简单的串口通信(96-N-8-1 格式)
从图中可以看到公头和母头的管脚定义顺序是不一样,这一点需要特别注意。这些管脚都有什么作用呢?9 针串口常用管脚的功能说明如下图所示:
串口协议及简单的串口通信(96-N-8-1 格式)
数据通信最少需要3根线: TXD RXD GND
在串口通信中,通常我们只使用 2、3、5 三个管脚,即 TXD、RXD、GND,其他管脚功能大家看不明白也没关系。
串口协议及简单的串口通信(96-N-8-1 格式)

(3)TTL 电平和 RS232 电平

串口协议及简单的串口通信(96-N-8-1 格式)
一、TTL 电平标准

  • 输入 L:<1.2V ;H:>2.0V
  • 输出 L:<0.8V ;H:>2.4V

CPU输入低于1.2V就认为是0,高于2.0V就认为是1;输出低电平要小于0.8V,高电平要大于2.4V。

二、RS232 电平标准

在 TXD 和 RXD 数据线上:

  • 逻辑 1 为 -3~-15V 的电压
  • 逻辑 0 为 3~15V 的电压

在 RTS、CTS、DSR、DTR 和 DCD 等控制线上:

  • 信号有效(ON 状态)为 3~15V 的电压
  • 信号无效(OFF 状态)为-3~-15V 的电压

逻辑1的电平为-3~-15V,逻辑0的电平为+3~+15V,注意电平的定义反相了一次。

由此可见,RS-232 是用正负电压来表示逻辑状态,与晶体管-晶体管逻辑集成电路(TTL)以高低电平表示逻辑状态的规定正好相反。而我们 STM32 芯片使用的就是 TTL 电平,所以要实现 STM32 与计算机的串口通信,需要进行 TTL与 RS-232C 电平转换,通常使用的电平转换芯片是 MAX3232。

(4)串口通信

如果你要实现两台计算机串口通信,那么就需要一根交叉串口线,将 2 对 3、3 对 2、5 对 5 连接,交叉串口线一般两头都是母头。串口通信中还需要注意的是,串口数据收发线要交叉连接,计算机的 TXD要对应单片机的 RXD,计算机的 RXD 要对应单片机的 TXD,并且共 GND,如下图:
串口协议及简单的串口通信(96-N-8-1 格式)
A给B发信息:Hi
ASCII码: H(0x48)i( 0x69)
二进制码:H(0100 1000)i(0110 1001)

串口通信是LSB(小端字节序,从右边开始传),网络socket是MSB(大端字节序)

假如使用RS232 电平标准,A传给B的电平如下:
串口协议及简单的串口通信(96-N-8-1 格式)
(1)同步通信:至少要两根线:数据线,时钟线
串口协议及简单的串口通信(96-N-8-1 格式)
但是A与B之间只有数据线,没有时钟线,我们要想其他的办法!

(2)异步通信:只有一根数据线
串口协议及简单的串口通信(96-N-8-1 格式)
没有时钟线的通信方式,我们就叫做异步通信。这时候靠速率来定义接收到的数据,这个速率叫波特率(Baudrate)。很显然,异步通信发送和接收双方的波特率应该保持一致,如果不一致则会接收异常

(5)通信协议

串口协议及简单的串口通信(96-N-8-1 格式)
总线空闲,一般是高电平; 起始位,低电平,一个位宽;

  • 起始位:接收方探测, 默认总线空闲时是高电平,一旦来一个位的低电平,说明一帧数据开始了。紧接着就是数据位,一帧数据有多少个数据位,由通信双方定义。

  • 数据位: 5, 6,7, 8, 一般是8个位。

  • 校验位:数据位发完了,一般会跟奇偶校验位(奇校验、偶校验、无校验),验证收发双方的数据是否正常。

  • 奇校验: 数据位加上校验位保证1的个数为奇数;

  • 偶校验: 数据位加上校验位保证1的个数为偶数;

  • 停止位:数据发完之后,会发一个停止位,停止位的宽度一般是: 1, 1.5, 2

RS232 的通信协议比较简单,通常遵循 96-N-8-1 格式。

“96”表示的是通信波特率为 9600。串口通信中通常使用的是异步串口通信,既没有时钟线,所以两个设备要通信,必须要保持一致的波特率,当然,波特率常用值还有 4800、115200 等。

“N”表示的是无校验位,由于串口通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、 0 校验(space)、1 校验(mark)以及无校验(noparity)。具体的介绍,大家可以百度下串口通信了解。

“8”表示的是数据位数为 8 位,其数据格式在前面介绍异步通信中已讲过。当然数据位数还可以为 5、6、7 位长度。

“1”表示的是 1 位停止位,串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑 0 的数据位表示,而数据包的停止信号可由 0.5、 1、 1.5 或 2 个逻辑 1 的数据位表示,只要双方约定一致即可。

二、简单的串口通信

通过串口读取信息

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>




int main(int argc, char **argv)
{
    int     tty_fd = -1 ;
    int     rv = -1 ;
    char    r_buf[128] ;
    struct termios options;
    fd_set  rset;

    tty_fd = open("/dev/ttyUSB0",O_RDWR|O_NOCTTY|O_NDELAY) ; //打开串口设备
    if(tty_fd < 0)
    {
        printf("open tty failed:%s\n", strerror(errno)) ;
        goto cleanup ;
    }
    printf("open devices sucessful!\n") ;
    
    memset(&options, 0, sizeof(options)) ;
    rv = tcgetattr(tty_fd, &options); //获取原有的串口属性的配置
    if(rv != 0)
    {
        printf("tcgetattr() failed:%s\n",strerror(errno)) ;
        goto cleanup ;
    }
    options.c_cflag|=(CLOCAL|CREAD ); // CREAD 开启串行数据接收,CLOCAL并打开本地连接模式
    options.c_cflag &=~CSIZE;// 先使用CSIZE做位屏蔽  
    options.c_cflag |= CS8; //设置8位数据位
    options.c_cflag &= ~PARENB; //无校验位

    /* 设置115200波特率  */
    cfsetispeed(&options, B9600); 
    cfsetospeed(&options, B9600);

    options.c_cflag &= ~CSTOPB;/* 设置一位停止位; */

    options.c_cc[VTIME] = 0;/* 非规范模式读取时的超时时间;*/
    options.c_cc[VMIN]  = 0; /* 非规范模式读取时的最小字符数*/
    tcflush(tty_fd ,TCIFLUSH);/* tcflush清空终端未完成的输入/输出请求及数据;TCIFLUSH表示清空正收到的数据,且不读取出来 */

    if((tcsetattr(tty_fd, TCSANOW,&options))!=0)
    {
        printf("tcsetattr failed:%s\n", strerror(errno));
        goto cleanup ;
    }

    while(1)
    {
        FD_ZERO(&rset) ;
        FD_SET(tty_fd, &rset) ;
        rv = select(tty_fd+1, &rset, NULL, NULL, NULL) ;
        if(rv < 0)
        {
            printf("select() failed: %s\n", strerror(errno)) ;
            goto cleanup ;
        }
        if(rv == 0)
        {
            printf("select() time out!\n") ;
            goto cleanup ;
        }

        memset(r_buf, 0, sizeof(r_buf)) ;
        rv = read(tty_fd, r_buf, sizeof(r_buf)) ;
        if(rv < 0)
        {
            printf("Read() error:%s\n",strerror(errno)) ;
            goto cleanup ;
        }
        printf("Read from tty: %s\n",r_buf) ;
    }

cleanup:
    close(tty_fd) ;
    return 0 ;
}

通过串口写信息

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>


#define W_BUF   "Hello,comport!"


int main(int argc, char **argv)
{
    int     tty_fd = -1 ;
    int     rv = -1 ;
    struct termios options;

    tty_fd = open("/dev/ttyUSB0",O_RDWR|O_NOCTTY|O_NDELAY) ; //打开串口设备
    if(tty_fd < 0)
    {
        printf("open tty failed:%s\n", strerror(errno)) ;
        goto cleanup ;
    }
    printf("open devices sucessful!\n") ;
    
    memset(&options, 0, sizeof(options)) ;
    rv = tcgetattr(tty_fd, &options); //获取原有的串口属性的配置
    if(rv != 0)
    {
        printf("tcgetattr() failed:%s\n",strerror(errno)) ;
        goto cleanup ;
    }
    options.c_cflag|=(CLOCAL|CREAD ); // CREAD 开启串行数据接收,CLOCAL并打开本地连接模式
    options.c_cflag &=~CSIZE;// 先使用CSIZE做位屏蔽  
    options.c_cflag |= CS8; //设置8位数据位
    options.c_cflag &= ~PARENB; //无校验位

    /* 设置115200波特率  */
    cfsetispeed(&options, B9600); 
    cfsetospeed(&options, B9600);

    options.c_cflag &= ~CSTOPB;/* 设置一位停止位; */

    options.c_cc[VTIME] = 0;/* 非规范模式读取时的超时时间;*/
    options.c_cc[VMIN]  = 0; /* 非规范模式读取时的最小字符数*/
    tcflush(tty_fd ,TCIFLUSH);/* tcflush清空终端未完成的输入/输出请求及数据;TCIFLUSH表示清空正收到的数据,且不读取出来 */

    if((tcsetattr(tty_fd, TCSANOW,&options))!=0)
    {
        printf("tcsetattr failed:%s\n", strerror(errno));
        goto cleanup ;
    }

    while(1)
    {
        rv = write(tty_fd, W_BUF,strlen(W_BUF)) ;
        if(rv < 0)
        {
            printf("Write() error:%s\n",strerror(errno)) ;
            goto cleanup ;
        }
        sleep(3) ;
    }

cleanup:
    close(tty_fd) ;
    return 0 ;
}

总结来看串口通信原理,(也可以说大多数通信原理也是如此)。通信首先要有个通信,可以简单的把通信看成一个小桶,发送方住水桶里装水,接收方从水桶中取水。如果你要和对方通信首先需要将桶盖打开,再将水装入到桶中,这时接收方才能够从桶中取到水。这里就存在着一定的问题,1,如果桶盖还没有打开,发送方已经发送了。这时接收方再从桶中取水,肯定取的水不对,会一部分缺失了。解决方式就是让桶盖打开再往其中加水。2,但是桶盖何时打开,发送方何时发送,这个不好把握。解决方法:接收方接到数据时,要返回一个应答标志,告诉发送方我已经取到数据了,而且是取得到正确数据才应答,否则不理会,继续取数据。或者一直查询,直到与发送方发来的数据一致再停止取数据。
一般的,进行串口通信总有一个是主动方一个是被动方,而且二者传输数据时,会有一定的协商好的数据格式,二者发送接收都按照此数据格式进行。

相关标签: 串口协议