串口协议及简单的串口通信(96-N-8-1 格式)
一、串口协议
(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,连接器的尺寸及每个插针的排列位置都有明确的定义,如下图所示:
RS-232C 还有一种 9 针的非标准连接器接口,简称 DB9。串口通信使用的大多都是 DB9 接口。DB25 和 DB9 接头有公头和母头之分,其中带针状的接头是公头,而带孔状的接头是母头。
从图中可以看到公头和母头的管脚定义顺序是不一样,这一点需要特别注意。这些管脚都有什么作用呢?9 针串口常用管脚的功能说明如下图所示:
数据通信最少需要3根线: TXD RXD GND
在串口通信中,通常我们只使用 2、3、5 三个管脚,即 TXD、RXD、GND,其他管脚功能大家看不明白也没关系。
(3)TTL 电平和 RS232 电平
一、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,如下图:
A给B发信息:Hi
ASCII码: H(0x48)i( 0x69)
二进制码:H(0100 1000)i(0110 1001)
串口通信是LSB(小端字节序,从右边开始传),网络socket是MSB(大端字节序)
假如使用RS232 电平标准,A传给B的电平如下:
(1)同步通信:至少要两根线:数据线,时钟线
但是A与B之间只有数据线,没有时钟线,我们要想其他的办法!
(2)异步通信:只有一根数据线
没有时钟线的通信方式,我们就叫做异步通信。这时候靠速率来定义接收到的数据,这个速率叫波特率(Baudrate)。很显然,异步通信发送和接收双方的波特率应该保持一致,如果不一致则会接收异常。
(5)通信协议
总线空闲,一般是高电平; 起始位,低电平,一个位宽;
-
起始位:接收方探测, 默认总线空闲时是高电平,一旦来一个位的低电平,说明一帧数据开始了。紧接着就是数据位,一帧数据有多少个数据位,由通信双方定义。
-
数据位: 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,但是桶盖何时打开,发送方何时发送,这个不好把握。解决方法:接收方接到数据时,要返回一个应答标志,告诉发送方我已经取到数据了,而且是取得到正确数据才应答,否则不理会,继续取数据。或者一直查询,直到与发送方发来的数据一致再停止取数据。
一般的,进行串口通信总有一个是主动方一个是被动方,而且二者传输数据时,会有一定的协商好的数据格式,二者发送接收都按照此数据格式进行。
上一篇: DMA原理解析
下一篇: QT上位机:局域网特定设备ip查询和显示