第六讲 单片机之c语言RS485通信

前面我介绍了51单片机的串口通信协议, 其核心是操作单片机的SCON,SBUF和定时器1,通过外部引脚Tx与Rx来实现与外部的数据交换。现在加入我们要实现两个mcu之间的远程通信,显然直接连接他们的Tx与Rx脚是不可行的。因为TTL通信容易受噪声干扰,其次线路过长本身也会有压降,再次信号线与地线之间形成一个电容,我们知道电容两端电压不能突变,因为TTL电平容易变形进而导致传输错误。

RS485通信

因此我们引入一种差分传输接口标准RS485,它具备以下特点。

1、我们在讲A/D的时候,讲过差分信号输入的概念,同时也介绍了差分输入的好处,最大的优势是可以抑制共模干扰。尤其工业现场的环境比较复杂,干扰比较多,所以通信如果采用的是差分方式,就可以有效的抑制共模干扰。而RS485就是一种差分通信方式,它的通信线路是两根,通常用A和B或者D+和D-来表示。逻辑“1”以两线之间的电压差为+(0.2~6)V表示,逻辑“0”以两线间的电压差为-(0.2~6)V来表示,是一种典型的差分通信。
2、RS485通信速度快,最大传输速度可以达到10Mb/s以上。
3、RS485内部的物理结构,采用的是平衡驱动器和差分接收器的组合,抗干扰能力也大大增加。
4、传输距离最远可以达到1200米左右,但是他的传输速率和传输距离是成反比的,只有在100Kb/s以下的传输速度,才能达到最大的通信距离,如果需要传输更远距离可以使用中继。
5、可以在总线上进行联网实现多机通信,总线上允许挂多个收发器,从现有的RS485芯片来看,有可以挂32、64、128、256等不同个设备的驱动器。

RS485的接口非常简单,和RS232所使用的MAX232是类似的,只需要一个RS485转换器,就可以直接和我们单片机的UART串行接口连接起来,并且完全使用的是和UART一致的异步串行通信协议。但是由于RS485是差分通信,因此接收数据和发送数据是不能同时进行的,也就是说它是一种半双工通信。那我们如何判断什么时候发送,什么时候接收呢?MAX485是美信(Maxim)推出的一款常用RS485转换器。其中5脚和8脚是电源引脚,6脚和7脚就是485通信中的A和B两个引脚,而1脚和4脚分别接到我们单片机的RXD和TXD引脚上,直接使用单片机UART进行数据接收和发送。而2脚和3脚就是方向引脚了,其中2脚是低电平使能接收器,3脚是高电平使能输出驱动器。我们把这两个引脚连到一起,平时不发送数据的时候,保持这两个引脚是低电平,让MAX485处于接收状态,当需要发送数据的时候,把这个引脚拉高,发送数据,发送完毕后再拉低这个引脚就可以了。为了提高RS485的抗干扰性能,需要在靠近MAX485的A和B引脚之间并接一个电阻,这个电阻阻值从100欧到1K都可以。

RS485的单工通信协议

好了,现在我们先来用485做一次单向的传递,一块MCU做主机发送一个片上AD读取的数字量给另一块MCU。我们可以定义一个简单的单工通信协议。即主机发送的数据需遵守如下格式:

0xAA adH adL adsum

其中0XAA为包头,表示数据包的开始;adH为AD转换结果的高8位,adL位AD转换结果的低8位,adsum为(adH+adL)%256为校验码。为什么需要校验码?这是因为我们要发送的是一个占两个字节的数ad,而SBUF是一个字节的寄存器,因此一个ad要分两次发。如果发送过程传输中断,正巧只发了高8位,那么下一次再开始发送的时候重新开始,这个高8位便会和另外一个8位组成一个数。因此需要校验,当然这种校验方法是不准确的。好了。直接上代码吧。

RS485主机代码

第六讲 单片机之c语言RS485通信_第1张图片

先看主机的代码。main.c

#include "12864.h"
#include "stc_adc.h"
#include "uart.h"
#include "timer.h"
#include "rs485.h"

void Isr_Init()
{
  EA=1;
  ES=1;
  ET0=1;

}

void main()
{
  LCD_Init();
  STCADC_Init();
  Timer0_Init();
  RS485_Init();
  Isr_Init();
  Show_String(0x80,"RS485 主机"); 
  while(1)
  {
    ad=Read_StcAdc(5); 
	RS485_Send();  
  	Show_Number(0x88,ad);
    //...
  }
}

rs485.c

#include"rs485.h"
#include "stc_adc.h"
#include "uart.h"
#include "timer.h" 

sbit RT485=P1^0;//MAX485的收发状态控制位
				//1:发送    0:接收
void RS485_Init()
{
 Uart_Init();
 RT485=0;  //初始化485为接收状态

}

void RS485_Send()   //将当前的数字量发送给从机
{
  u8 buf[4],i;
  if(ms.ms1<200)
  {
    return ;
  }  

  buf[0]=0xaa;
  buf[1]=ad/256;
  buf[2]=ad%256;
  buf[3]=(buf[1]+buf[2])%256;

  RT485=1;  //发送状态
  for(i=0;i<4;i++)
  {
   Send_Byte(buf[i]);
  }
  RT485=0;  //接收状态

  ms.ms1=0;
}
rs485.h

#ifndef _485_
#define _485_

#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int
 void RS485_Init();
 void RS485_Send();   
#endif

uart.c

#include "uart.h"

void Uart_Init()
{
  TMOD=0X20;
  SCON=0X50;
  TH1=253; //9600bit/s-->11.0592MHZ
  TR1=1;
}
void Send_Byte(u8 dat)
{
  SBUF=dat;
  while(TI==0);
   TI=0;
}

void Isr_uart() interrupt 4	  //串口中断处理
{
  u8 t;
  if(RI==1)
  {
    RI=0;
	t=SBUF;
	//....

  }
}
uart.h

#ifndef _uart_
#define _uart_

#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int

void Uart_Init();
void Send_Byte(u8 dat);


#endif

timer.c

#include "timer.h"
TMS ms;  //
void Timer0_Init()   //1ms
{
  TMOD|=0X01;
  TH0=64614/256;
  TL0=64614%256;
  TR0=1;
}
void Timer0_Isr() interrupt 1   //t0  1ms
{
   TH0=64614/256;
   TL0=64614%256;
   ms.ms1++;
   ms.ms2++;
   ms.ms3++;
   ms.ms4++;
   //...
}
timer.h

 #ifndef _TIMER_
#define _TIMER_

#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int

typedef  struct
{
  u16 ms1;
  u16 ms2;
  u16 ms3;
  u16 ms4;
  //...
}TMS;

extern TMS ms;  //

void Timer0_Init();

#endif

另外12864和stc-adc的程序和前面一讲是一模一样的,这里就不再给出。

RS485从机代码

第六讲 单片机之c语言RS485通信_第2张图片

main.c

#include "12864.h"
#include "uart.h"
#include "timer.h"
#include "rs485.h"

u16 ad;  //当前数字量
void Isr_Init()
{
  EA=1;
  ES=1;
  ET0=1;

}

void main()
{
  LCD_Init();
  Timer0_Init();
  RS485_Init();
  Isr_Init();
  Show_String(0x80,"RS485 从机"); 
  while(1)
  { 
  	Show_Number(0x88,ad);
    //...
  }
}
rs485.c

#include"rs485.h"
#include "uart.h"
#include "timer.h" 

u8 Rs485buf[4];  //Rs485接收缓冲区

sbit RT485=P1^0;//MAX485的发送接收状态控制位定义
/*
  0XAA ADH ADL SUM%256
*/				
void RS485_Init()
{
 Uart_Init();
 RT485=0;  //初始化MAX485为接收状态

}
rs485.h

#ifndef _485_
#define _485_

#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int
 void RS485_Init();  

#endif

uart.c

#include "uart.h"
void Uart_Init()
{
  TMOD=0X20;
  SCON=0X50;
  TH1=253; //9600bit/s-->11.0592MHZ
  TR1=1;
}
void Send_Byte(u8 dat)
{
  SBUF=dat;
  while(TI==0);
   TI=0;
}

void Isr_uart() interrupt 4	  //串口中断处理
{
  u8 t;
  static u8 i;
  if(RI==1)
  {
    RI=0;
	t=SBUF;
    Rs485buf[i++]=t;
	if(Rs485buf[0]==0xaa)
	{
	   if(i>=4)
	   {
	   	  if((Rs485buf[1] + Rs485buf[2])%256 == Rs485buf[3])
		  {
		    ad=Rs485buf[1]*256+Rs485buf[2];
		  }
	     i=0;
	   }
	}
	else
	{
	  i=0;
	}

  }


}

uart.h

#ifndef _uart_
#define _uart_

#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int
extern u8 Rs485buf[4];  //Rs485½ÓÊÕ»º³åÇø
void Uart_Init();
void Send_Byte(u8 dat);


#endif
另外的12864和timer就不列出了,复制主机里面的代码即可。

RS485多机通信

第六讲 单片机之c语言RS485通信_第3张图片

如图给出了主从机的框图,图中主机每100ms轮询一个从机。主机发送的寻址命令帧包含:

(1)本次轮询的从机地址

(2)本次轮询该从机的目的

(3)本次轮询该从机的附加信息

(4)本寻址帧的校验信息

从机收到寻址帧后:

(1)校验数据包的正确性

(2)检验数据包中地址部分是否与自己的地址相等

(3)对数据包进行处理

(4)对主机发回响应数据包

主机寻址帧的结构:

第六讲 单片机之c语言RS485通信_第4张图片

从机发回数据包结构:

第六讲 单片机之c语言RS485通信_第5张图片


你可能感兴趣的:(C51实践篇)