RS485串口编程 您所在的位置:网站首页 sfwr指令怎么编程 RS485串口编程

RS485串口编程

2023-09-07 16:39| 来源: 网络整理| 查看: 265

1.1 单工、半双工、全双工

首先,我使用的是芯片为 SP3485E 为半双工通信。那么先要明确什么是单工、半双工、全双工。 单工数据传输只支持数据在一个方向上传输; 半双工数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;全双工数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。  网卡的全双工(Full Duplex)是指网卡在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的网卡一般都支持全双工。

 

提到全双工,就不能不提与之密切对应的另一个概念,那就是“半双工(Half Duplex)”,所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过, 当目前有两量车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台。

 

1.2 关于RS485通信

RS232 标准是诞生于 RS485 之前的,但是 RS232 有几处不足的地方: 接口的信号电平值较高,达到十几 V,使用不当容易损坏接口芯片,电平标准也与TTL 电平不兼容。传输速率有局限,不可以过高,一般到一两百千比特每秒(Kb/s)就到极限了。 接口使用信号线和 GND 与其它设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。传输距离有限,最多只能通信几十米。 通信的时候只能两点之间进行通信,不能够实现多机联网通信。 针对 RS232 接口的不足,就不断出现了一些新的接口标准,RS485 就是其中之一,它具备以下的特点:采用差分信号。我们在讲 A/D 的时候,讲过差分信号输入的概念,同时也介绍了差分输入的好处,最大的优势是可以抑制共模干扰。 尤其当工业现场环境比较复杂,干扰比较多时,采用差分方式可以有效的提高通信可靠性。RS485 采用两根通信线,通常用 A 和 B 或者 D+和 D-来表示。逻辑“1”以两线之间的电压差为+(0.2~6)V 表示,逻辑“0”以两线间的电压差为-(0.2~6)V 来表示,是一种典型的差分通信。 RS485 通信速率快,最大传输速度可以达到 10Mb/s 以上。 RS485 内部的物理结构,采用的是平衡驱动器和差分接收器的组合,抗干扰能力也大大增加。传输距离最远可以达到 1200 米左右,但是它的传输速率和传输距离是成反比的,只有在 100Kb/s 以下的传输速度,才能达到最大的通信距离,如果需要传输更远距离可以使用中继。 可以在总线上进行联网实现多机通信,总线上允许挂多个收发器,从现有的 RS485芯片来看,有可以挂 32、64、128、256 等不同个设备的驱动器。 RS485 的接口非常简单,与 RS232 所使用的 MAX232 是类似的,只需要一个 RS485转换器,就可以直接与单片机的 UART 串口连接起来,并且使用完全相同的异步串行通信协议。 但是由于 RS485 是差分通信,因此接收数据和发送数据是不能同时进行的,也就是说它是一种半双工通信。RS485为差分通信:最大的优势是可以抑制共模干扰。

 

 

1.3  关于sp3485硬件分析

 

                                                                上图为SP3485原理图 

(1)引脚说明: 

Pin1 - RO: 接收器输出

Pin2 - RE#:接收器输出使能 (低电平有效)

Pin3 - DE: 驱动器输出使能 (高电平有效)

Pin4 - DI: 驱动器输入

Pin5 - GND: 连接地

Pin6 - A: 驱动器输出/接收器输入 (同相)

Pin7 - B: 驱动器输出/接收器输入 (反相)

Pin8 - Vcc       

注意将AB间120欧姆去掉,如果采用阻抗匹配的电缆,300米以内几乎可以不用加终端电阻。加终端电阻的缺点就是增大了线路的无用功耗,尤其是电池系统供电时,降低了电池的续航能力。

PS: 我一开始没有将它去掉,导致只能发送数据,无法接收数据。

(2)电气特性

RS-232电平的电气特性

EIA电平(串口)

逻辑1:-3V~-15V

逻辑0:+3V~+15V

 

TTL电平(TPAD)

逻辑1:+2V~+5V

逻辑0:+0V~+0.8V

 

接收数据:EIA->TTL  232转TTL

发送数据:TTL->EIA  TTL转232

 

串口异步通信的重要参数:

波特率: bps (bit per second)

数据位的个数: 5 6 7 8

校验方式: 奇校验 偶校验 无校验

停止位: 1bit  2bit

 

RS485电平 和RS422电平 由于两者均采用 差分传输(平衡传输)的方式,所以他们的电平方式,一般有两个引脚 A,B

发送端 AB间的电压差

+2 ~ +6v 1

-2 ~ -6v   0

接收端 AB间的电压差

大于 +200mv   1

小于 -200mv 0

 

定义逻辑1为B>A的状态

定义逻辑0为A>B的状态

AB之间的电压差不小于200mv

 

一对一的接头的情况下:

RS232 可做到双向传输,全双工通讯   最高传输速率 20kbps

422    只能做到单向传输,半双工通讯,最高传输速率10Mbps

485    双向传输,半双工通讯, 最高传输速率10Mbps

 

(3)串行数据的格式

异步串行数据的一般格式是:起始位+数据位+停止位,(8-N-1格式) 其中起始位1 位,数据位可以是5、6、7、8位,停止位可以是1、1.5、2位。

 

起始位是一个值为0的位,所以对于正逻辑的TTL电平,起始位是一位时间的低电平;停止位是值为1的位,所以对于正逻辑的TTL电平,停止位是高电平。线路路空闲或者数据传输结束,对于正逻辑的TTL电平,线路总是1。对于负逻辑(如RS-232电平)则相反。

例如,对于16进制数据55aaH,当采用8位数据位、1位停止位传输时,它在信号线上的波形如图1(TTL电平)和图2(RS-232电平)所示。 (先传第一个字节55,再传第二个字节aa,每个字节都是从低位向高位逐位传输)

                   图1 TTL电平的串行数据帧格式(55aah)

 

                    图2 RS-232电平的串行数据帧格式(55aah)

 

(4)根据波形图计算波特率

如图3是图1在示波器中的显示示意,其中灰色线是示波器的时间分度线,此时假设是200us/格。                      图3 波特率计算示意图

 

可以看了,第一个字节的10位(1位起始位,8位数据位和1位停止位)共占约1.05ms,这样可计算出其波特率约为:

10bit / 1.05ms X 1000 ≈ 9600 bit/s

如果上图中的时间轴是100us/格,同样可以计算出波特率应是19200bit/s。

当通讯不正常,又能观察到波形时,就可根据上述方法,从波形图计算一下波特率是否正确。

 

(5)根据波形图判断RS-485收发数据的正确与否

RS-485是一种半双工的串行通讯方式(RS-422为全双工),485电平芯片所以要正确接收和发送数据,必需保证控制信号和数据的同步,否则要么发送数据丢失,要么接收数据可能丢失。

 

RS-485发送数据时的正确时序如图4所示。

              图4 RS-485的正确发送数据时序

在图4中,发送控制信号的宽度基本与数据信号的宽度一致,所以能保证发送数据的正确和发送后及时转为接收。

图5 和图6 分别是控制信号太短和控制信号太长的情况。

                图5 RS-485控制信号太短时的时序

 

                图6 RS-485控制信号太短时的时序

在图5中,由于控制信号关闭过早,则第二个字节的后两位将发送错误;在图6中,由于控制信号关闭过迟,使485芯片在发送数据后,不能及时转到接收状态,此时总线若有数据过来,则本单元将不能正确接收。

总结:只要掌握上述波形分析方法,任何异步串行数据的接收和发送问题,基本都可以得到解决。

二、串口通信 2.1 串口的操作一般都通过四个步骤来完成:

1、打开串口 2、配置串口:对串口的波特率、数据位、停止位、校验码、等进行设置。 3、读写串口 4、关闭串口

2.2 完整代码: #include //文件控制定义 #include //标准输入输出定义 #include //标准函数库定义 #include //Unix标准函数定义 #include //错误好定义 #include //POSIX终端控制定义 #include //ioctl函数定义 #include //字符操作 #include #include #include #include //时间戳 long long getSystemTime() { struct timeb t; ftime(&t); return 1000 * t.time + t.millitm; } long long start; long long end; //定义互斥量 pthread_mutex_t mutex; int fd_gpio; struct termios newtio, oldtio; typedef struct { int pin_idx; int pin_dir; int pin_sta; } davinci_gio_arg; typedef enum { AT91PIO_DIR_OUT = 0, AT91PIO_DIR_INP } davinci_gio_dir; //驱动判断输入输出模式 davinci_gio_arg arg; #define DEV_PIO_LED "/dev/pio" // 需要手动添加设备号 mknod /dev/pio c 203 0 #define PIO_NUM 47 // 47pin 为控制输入输出方向引脚 #define DEV_UART "/dev/ttyS1" // /dev/ttyS1 为串口设备 #define IOCTL_PIO_SETDIR 1 //set gpio direct #define IOCTL_PIO_GETDIR 2 //get gpio direct #define IOCTL_PIO_SETSTA 3 //set gpio status #define IOCTL_PIO_GETSTA 4 //get gpio status //保存信息 int log_init( const char *strFileName ) { int fdLog = -1; if( -1 == (fdLog = open( strFileName, O_CREAT|O_TRUNC ) ) ) { } close( fdLog ); } int log_out( const char *strFileName, const char * szLog ) { int fdLog = -1; if( -1 == ( fdLog = open( strFileName, O_CREAT|O_WRONLY|O_APPEND ) ) ) { printf( "LOG (%s) open error!\n", strFileName ); return -1; } write( fdLog, szLog, strlen( szLog ) ); close( fdLog ); return 0; } //配置串口 /* 参数说明:fd 设备文件描述符,nspeed 波特率,nbits 数据位数(7位或8位), parity 奇偶校验位('n'或'N'为无校验位,'o'或'O'为偶校验,'e'或'E'奇校验), nstop 停止位(1位或2位) 成功返回1,失败返回-1。 */ int set_com_opt( int fd, int nspeed, int nbits, char parity, int nstop ) { char szTmp[128]; //打印配置信息 sprintf( szTmp, "set_com_opt - speed:%d,bits:%d,parity:%c,stop:%d\n", nspeed, nbits, parity, nstop ); log_out( "./485.log", szTmp ); //保存并测试现在有串口参数设置,在这里如果串口号等出错,会有相关的出错信息 if( tcgetattr( fd, &oldtio ) != 0 ) { sprintf( szTmp, "SetupSerial 1" ); log_out( "./485.log", szTmp ); perror( "SetupSerial 1" ); return -1; } //修改输出模式,原始数据输出 bzero( &newtio, sizeof( newtio )); newtio.c_cflag &=~(OPOST); //屏蔽其他标志位 newtio.c_cflag |= (CLOCAL | CREAD ); newtio.c_cflag &= ~CSIZE; //设置数据位 switch( nbits ) { case 7: newtio.c_cflag |= CS7; break; case 8: newtio.c_cflag |= CS8; break; default: perror("Unsupported date bit!\n"); return -1; } //设置校验位 switch( parity ) { case 'n': case 'N': //无奇偶校验位 newtio.c_cflag &= ~PARENB; newtio.c_iflag &= ~INPCK; break; case 'o': case 'O': //设置为奇校验 newtio.c_cflag |= ( PARODD | PARENB ); newtio.c_iflag |= ( INPCK | ISTRIP ); break; case 'e': case 'E': //设置为偶校验 newtio.c_iflag |= ( INPCK |ISTRIP ); newtio.c_cflag |= PARENB; newtio.c_cflag &= ~PARODD; break; default: perror("unsupported parity\n"); return -1; } //设置停止位 switch( nstop ) { case 1: newtio.c_cflag &= ~CSTOPB; break; case 2: newtio.c_cflag |= CSTOPB; break; default : perror("Unsupported stop bit\n"); return -1; } //设置波特率 switch( nspeed ) { case 2400: cfsetispeed( &newtio, B2400 ); cfsetospeed( &newtio, B2400 ); break; case 4800: cfsetispeed( &newtio, B4800 ); cfsetospeed( &newtio, B4800 ); break; case 9600: cfsetispeed( &newtio, B9600 ); cfsetospeed( &newtio, B9600 ); break; case 115200: cfsetispeed( &newtio, B115200 ); cfsetospeed( &newtio, B115200 ); break; case 460800: cfsetispeed( &newtio, B460800 ); cfsetospeed( &newtio, B460800 ); break; default: cfsetispeed( &newtio, B9600 ); cfsetospeed( &newtio, B9600 ); break; } //设置等待时间和最小接收字符 newtio.c_cc[VTIME] = 0; newtio.c_cc[VMIN] = 0; //VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回。 //输入模式 newtio.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG); //设置数据流控制 newtio.c_iflag &= ~(IXON|IXOFF|IXANY); //使用软件流控制 //如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读 tcflush( fd, TCIFLUSH ); //激活配置 (将修改后的termios数据设置到串口中) if( tcsetattr( fd, TCSANOW, &newtio ) != 0 ) { sprintf( szTmp, "serial set error!\n" ); log_out( "./485.log", szTmp ); perror( "serial set error!" ); return -1; } log_out( "./485.log", "serial set ok!\n" ); return 1; } //打开串口并返回串口设备文件描述 int open_com_dev( char *dev_name ) { int fd; char szTmp[128]; log_init( "./485.log" ); if(( fd = open( dev_name, O_RDWR|O_NOCTTY|O_NDELAY)) == -1 ) { perror("open\n"); //printf("Can't open Serial %s Port!\n", dev_name ); sprintf( szTmp, "Can't open Serial %s Port!\n", dev_name ); log_out( "./485.log", szTmp ); return -1; } sprintf( szTmp, "open %s ok!\n", dev_name ); log_out( "./485.log", szTmp ); if(fcntl(fd,F_SETFL,0) 0) { //打印接收数据 for (i = 0; i < nread; i++) { printf ("%02X ", buf[i]); } //退出循环, 这里有点疑问 res1 += nread; if (res1 == 18) { memset (buf, 0, sizeof (buf)); printf ("\n"); break; } } } close (fd_r); pthread_mutex_unlock (&mutex); end=getSystemTime(); printf("time: %lld ms\n", end-start); usleep (200000); } } int main (void) { int error = 0, error1 = 0; arg.pin_idx = PIO_NUM; arg.pin_dir = AT91PIO_DIR_OUT; //打开/dev/pio设备 fd_gpio = open(DEV_PIO_LED, O_RDWR); if(fd_gpio < 0) { perror("fd_gpio open err"); exit (-1); } //初始化互斥量 pthread_mutex_init (&mutex, 0); pthread_t tid, tid1; //创建线程 error = pthread_create (&tid, NULL, task, NULL); error1 = pthread_create (&tid1, NULL, task1, NULL); //等待线程结束 pthread_join (tid, NULL); pthread_join (tid1, NULL); //销毁互斥量 pthread_mutex_destroy(&mutex); //关闭设备 close (fd_gpio); return 0; }

 

执行结果:

三、串口通信总结

虽然以上代码只有三百多行,但是其包含的内容确是很多的,下面就一一的来总结。

一般招聘信息上 都会有这样一项要求。了解Modbus基于RS485,RS232,以太网等总线的通讯协议,熟练操作Modbus相关软件。

上面我们对RS485,RS232硬件做了分析,接下来我们看一下软件上面该如何处理。 主要分为下面部分来讲:

(1)串口编程详解

参看:Linux串口编程详解 

参看:Linux下的串口编程(二)

参看:Linux 串口编程

前面已经提到过Linux下皆为文件,这当然也包括我们今天的主角 UART0 串口。因此对他的一切操作都和文件的操作一样(涉及到了open,read,write,close等文件的基本操作)。

(一)Linux下的串口编程又那几部分组成

 

 

1.    打开串口

2.    串口初始化

3.    读串口或写串口

4.    关闭串口

(二)串口的打开

既然串口在linux中被看作了文件,那么在对文件进行操作前先要对其进行打开操作。

1.在Linxu中,串口设备是通过串口终端设备文件来访问的

即通过访问/dev/ttyS0,/dev/ttyS1,/dev/ttyS2这些设备文件实现对串口的访问。

==============================

这里有个问题:

你怎么知道访问的是哪个串口?

可以进行一下测试,echo hello > /dev/ttyS0  

看看是否有hello输出。

如果串口使用不对,会出现错误:

setup serial:bad file descriptor set parity Error

==============================

2.调用open()函数来代开串口设备,对于串口的打开操作,必须使用O_NOCTTY参数。

l  O_NOCTTY:表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务一个输入(eg:键盘中止信号等)都将影响进程。

l  O_NDELAY:表示不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。

3.打开串口模块有那及部分组成

1> 调用open()函数打开串口,获取串口设备文件描述符

2> 获取串口状态,判断是否阻塞

3> 测试打开的文件描述符是否为终端设备

 

4程序:

/*****************************************************************  

* 名称: UART0_Open

* 功能: 打开串口并返回串口设备文件描述

* 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1,ttyS2)

* 出口参数: 正确返回为1,错误返回为0

*****************************************************************/

int UART0_Open(int fd,char* port)

{

 

fd = open( port, O_RDWR|O_NOCTTY|O_NDELAY);

if (FALSE == fd)

{

perror("Can't Open Serial Port");

return(FASLE);

}

//判断串口的状态是否为阻塞状态

if(fcntl(fd, F_SETFL, 0) < 0)

{

printf("fcntl failed!/n");

return(FALSE);

}

else

{

printf("fcntl=%d/n",fcntl(fd, F_SETFL,0));

}

//测试是否为终端设备

if(0 == isatty(STDIN_FILENO))

{

printf("standard input is not a terminal device/n");

return(FALSE);

}

else

{

printf("isatty success!/n");

}

printf("fd->open=%d/n",fd);

return fd;

}



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有