文章目录

  • 一、485 简介
  • 二、 硬件设计
  • 三、软件设计

  • 一、485 简介

    485(一般称作 RS485/EIA-485)是隶属于 OSI 模型物理层的电气特性规定为 2 线,半双工,多点通信的标准。它的电气特性和 RS-232 大不一样。用缆线两端的电压差值来表示传递信号。

    RS485 仅仅规定了接受端和发送端的电气特性。它没有规定或推荐任何数据协议。RS485 的特点包括:
    (1) 接口电平低,不易损坏芯片。RS485 的电气特性:逻辑“1”以两线间的电压差为+(2~ 6)V表示;逻辑“0”以两线间的电压差为-(2~6)V 表示。接口信号电平比 RS232 降低了,不易损坏接口电路的芯片,且该电平与 TTL 电平兼容,可方便与 TTL 电路连接。

    (2) 传输速率高。10 米时,RS485 的数据最高传输速率可达 35Mbps,在 1200m 时,传输速度可达 100Kbps。

    (3) 抗干扰能力强。RS485 接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。

    (4) 传输距离远,支持节点多。RS485 总线最长可以传输 1200m 以上(速率≤100Kbps)一般最大支持 32 个节点,如果使用特制的 485 芯片,可以达到 128 个或者 256 个节点,最大的可以支持到 400 个节点。

    RS485 推荐使用在点对点网络中,线型,总线型,不能是星型,环型网络。理想情况下 RS485需要 2 个匹配电阻,其阻值要求等于传输电缆的特性阻抗(一般为 120Ω)。没有特性阻抗的话,当所有的设备都静止或者没有能量的时候就会产生噪声,而且线移需要双端的电压差。没有终接电阻的话,会使得较快速的发送端产生多个数据信号的边缘,导致数据传输出错。485 推荐的连接方式如下图所示:

    在上面的连接中,如果需要添加匹配电阻,我们一般在总线的起止端加入,也就是主机和设备 4 上面各加一个 120Ω的匹配电阻。
    由于 RS485 具有传输距离远、传输速度快、支持节点多和抗干扰能力更强等特点,所以RS485 有很广泛的应用。

    本节我以原子哥的战舰STM32开发板为例进行485通讯

    战舰 STM32 开发板采用 SP3485 作为收发器,该芯片支持 3.3V 供电,最大传输速度可达10Mbps,支持多达 32 个节点,并且有输出短路保护。该芯片的框图如下图所示:

    图中 A、B 总线接口,用于连接 485 总线。RO 是接收输出端,DI 是发送数据收入端,RE是接收使能信号(低电平有效),DE 是发送使能信号(高电平有效)。

    本节我们通过该芯片连接 STM32 的串口 2,实现两个开发板之间的 485 通信。本节将实现这样的功能:通过连接两个战舰 STM32 开发板的 RS485 接口,然后由 KEY0 控制发送,当按下一个开发板的 KEY0 的时候,就发送 5 个数据给另外一个开发板,并在两个开发板上分别显示发送的值和接收到的值。

    本节,我们只需要配置好串口 2,就可以实现正常的 485 通信了,串口 2 的配置和串口 1基本类似,只是串口的时钟来自 APB1,最大频率为 36Mhz。

    二、 硬件设计

    本次实验需要用到的硬件资源如下:
    (1) 指示灯 DS0
    (2) KEY0 按键
    (3) TFTLCD 模块
    (4) 串口 2
    (5) RS485 收发芯片

    前面 3 个在上几节中都有详细介绍,这里我主要介绍 RS485 和串口 2 的连接关系,如下图所示:

    从上图可以看出:STM32F1 的串口 2 通过 P7 端口设置,连接到 SP3485,通过 STM32F1的 PD7 控制 SP3485 的收发,当 PD7=0 的时候,为接收模式;当 PD7=1 的时候,为发送模式。

    这里需要注意,RS485_RE 信号和 DM9000_RST 共用 PD7,所以他们也不可以同时使用,只能分时复用。
    另外,图中的 R19 和 R22 是两个偏置电阻,用来保证总线空闲时,A、B 之间的电压差都会大于 200mV(逻辑 1)。从而避免因总线空闲时,A、B 压差不定,引起逻辑错乱,可能出现的乱码。

    然后,我们要设置好开发板上P7排针的连接,通过跳线帽将PA2和PA3分别连接到485_RX和 485_TX 上面,如图 下所示:

    最后,用 2 根导线将两个开发板 RS485 端子的 A 和 A,B 和 B 连接起来。这里注意不要接反了(A 接 B),接反了会导致通讯异常。

    三、软件设计

    rs485.c

    #include "sys.h"		    
    #include "rs485.h"	 
    #include "delay.h"
    
    #ifdef EN_USART2_RX   	//如果使能了接收
    
    
    //接收缓存区 	
    u8 RS485_RX_BUF[64];  	//接收缓冲,最大64个字节.
    //接收到的数据长度
    u8 RS485_RX_CNT=0;   		  
      
    void USART2_IRQHandler(void)
    {
    	u8 res;	    
     
     	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
    	{	 
    	 			 
    		res =USART_ReceiveData(USART2); 	//读取接收到的数据
    		if(RS485_RX_CNT<64)
    		{
    			RS485_RX_BUF[RS485_RX_CNT]=res;		//记录接收到的值
    			RS485_RX_CNT++;						//接收数据增加1 
    		} 
    	}  											 
    } 
    #endif										 
    //初始化IO 串口2
    //pclk1:PCLK1时钟频率(Mhz)
    //bound:波特率	  
    void RS485_Init(u32 bound)
    {  
      GPIO_InitTypeDef GPIO_InitStructure;
      USART_InitTypeDef USART_InitStructure;
     	NVIC_InitTypeDef NVIC_InitStructure;
     
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE);//使能GPIOA,D时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
    	
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;				 //PD7端口配置
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
     	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_Init(GPIOD, &GPIO_InitStructure);
     
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;	//PA2
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽
      GPIO_Init(GPIOA, &GPIO_InitStructure);
       
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
      GPIO_Init(GPIOA, &GPIO_InitStructure);  
    
    	RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,ENABLE);//复位串口2
    	RCC_APB1PeriphResetCmd(RCC_APB1Periph_USART2,DISABLE);//停止复位
     
    	
     #ifdef EN_USART2_RX		  	//如果使能了接收
    	USART_InitStructure.USART_BaudRate = bound;//波特率设置
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据长度
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
    	USART_InitStructure.USART_Parity = USART_Parity_No;///奇偶校验位
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
    	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
    
      USART_Init(USART2, &USART_InitStructure); ; //初始化串口
      
    	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //使能串口2中断
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级2级
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级2级
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
    	NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
     
      USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断
       
      USART_Cmd(USART2, ENABLE);                    //使能串口 
    
     #endif
    
      RS485_TX_EN=0;			//默认为接收模式
     
    }
    
    //RS485发送len个字节.
    //buf:发送区首地址
    //len:发送的字节数(为了和本代码的接收匹配,这里建议不要超过64个字节)
    void RS485_Send_Data(u8 *buf,u8 len)
    {
    	u8 t;
    	RS485_TX_EN=1;			//设置为发送模式
      	for(t=0;t<len;t++)		//循环发送数据
    	{		   
    		while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);	  
    		USART_SendData(USART2,buf[t]);
    	}	 
     
    	while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);		
    	RS485_RX_CNT=0;	  
    	RS485_TX_EN=0;				//设置为接收模式	
    }
    //RS485查询接收到的数据
    //buf:接收缓存首地址
    //len:读到的数据长度
    void RS485_Receive_Data(u8 *buf,u8 *len)
    {
    	u8 rxlen=RS485_RX_CNT;
    	u8 i=0;
    	*len=0;				//默认为0
    	delay_ms(10);		//等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束
    	if(rxlen==RS485_RX_CNT&&rxlen)//接收到了数据,且接收完成了
    	{
    		for(i=0;i<rxlen;i++)
    		{
    			buf[i]=RS485_RX_BUF[i];	
    		}		
    		*len=RS485_RX_CNT;	//记录本次数据长度
    		RS485_RX_CNT=0;		//清零
    	}
    }
    

    此部分代码总共 4 个函数,其中 RS485_Init 函数为 485 通信初始化函数,其实基本上就是在配置串口 2,只是把 PD7 也顺带配置了,用于控制 SP3485 的收发。同时如果使能中断接收的话,会执行串口 2 的中断接收配置。USART2_IRQHandler 函数用于中断接收来自 485 总线的数据,将其存放在 RS485_RX_BUF 里面。
    最后 RS485_Send_Data 和RS485_Receive_Data 这两个函数用来发送数据到 485 总线和读取从 485 总线收到的数据。

    在头文件 rs485.h 中开启串口 2 的中断接收。

    main.c

    #include "led.h"
    #include "delay.h"
    #include "key.h"
    #include "sys.h"
    #include "lcd.h"
    #include "usart.h"	 
    #include "rs485.h"
     
     int main(void)
     {	 
    	u8 key;
    	u8 i=0,t=0;
    	u8 cnt=0;
    	u8 rs485buf[5]; 
    	 
    	delay_init();	    	 //延时函数初始化	  
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
    	uart_init(115200);	 	//串口初始化为115200
    	LED_Init();		  		//初始化与LED连接的硬件接口
    	LCD_Init();			   	//初始化LCD 	
    	KEY_Init();				//按键初始化		 	 
    	RS485_Init(9600);	//初始化RS485
     	POINT_COLOR=RED;//设置字体为红色 
    	LCD_ShowString(30,50,200,16,16,"WarShip STM32");
    	LCD_ShowString(30,70,200,16,16,"RS485 TEST");	
    	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
    	LCD_ShowString(30,110,200,16,16,"2015/1/15");	
    	LCD_ShowString(30,130,200,16,16,"KEY0:Send");	//显示提示信息		
     
     	POINT_COLOR=BLUE;//设置字体为蓝色	  
    	LCD_ShowString(30,150,200,16,16,"Count:");			//显示当前计数值	
    	LCD_ShowString(30,170,200,16,16,"Send Data:");		//提示发送的数据	
    	LCD_ShowString(30,210,200,16,16,"Receive Data:");	//提示接收到的数据		
     									  
    	while(1)
    	{
    		key=KEY_Scan(0);
    		if(key==KEY0_PRES)//KEY0按下,发送一次数据
    		{
    			for(i=0;i<5;i++)
    			{
    				rs485buf[i]=cnt+i;//填充发送缓冲区
    				LCD_ShowxNum(30+i*32,190,rs485buf[i],3,16,0X80);	//显示数据
     			}
    			RS485_Send_Data(rs485buf,5);//发送5个字节 									   
    		}		 
    		RS485_Receive_Data(rs485buf,&key);
    		if(key)//接收到有数据
    		{
    			if(key>5)key=5;//最大是5个数据.
     			for(i=0;i<key;i++)LCD_ShowxNum(30+i*32,230,rs485buf[i],3,16,0X80);	//显示数据
     		}
    		t++; 
    		delay_ms(10);
    		if(t==20)
    		{
    			LED0=!LED0;//提示系统正在运行	
    			t=0;
    			cnt++;
    			LCD_ShowxNum(30+48,150,cnt,3,16,0X80);	//显示数据
    		}		   
    	} 
    }
    

    此部分代码, cnt 是一个累加数,一旦 KEY0 按下按下,就以这个数位基准连续发送 5 个数据。当 485 总线收到数据的时候,就将收到的数据直接显示在 LCD 屏幕上。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 485通讯:深入了解

    发表评论