手把手教你学51单片机之十八 RS485通信与Modbus协议

合集下载

51单片机新手入门之Modbus通讯

51单片机新手入门之Modbus通讯

51单片机新手入门之Modbus通讯本文和另一篇----C# WPF新手入门之Modbus通讯为一个系列,所用模块均是气体分析模块,之前用C3做的上位机(主要是方便调试,单片机不好搞),既然电脑上串口通讯代码没问题,那么放到单片机稍加修改也就理所当然肯定可行O(∩_∩)O~。

本文包括单片机的电路设计和软件设计两部分(单片机采用STC12C5A60S2双串口),至此单片机和上位机的串口通讯均成功实现,供需要的同学参考。

1.电路设计Altium Designer原理图如下:电源部分:24V转5V及6VAD及滤波Modbus通讯电脑串口通讯lcd屏显示预留接口,可以另接MAX232用来调试发送给模块数据正确性实际效果如下:电脑串口接收到的数据,和LCD 屏显示的是一样的下载步骤1. 选择芯片下载步骤2. 打开串口 下载步骤3. 打开hex 文件 下载步骤4. MCU 先断电,点击下载,看到提示后上电2.软件设计这里最麻烦的在于模块的Modbus通讯需要偶校验,我是手动添加的校验位,例如字符‘0’换成ASCII码0X30,添加偶校验后就是0X3A,把所有需要发送的数据一个个手动添加了再发送^_^;另外lcd12864屏也搞半天,主要照着时序图写必须sid先发送,然后sclk,不然不行 ̄□ ̄。

气体模块详细的通讯协议参考和上位机通讯那篇,此处不再介绍。

图中电路板没有焊AD芯片和温度芯片,引脚备用,相关代码注释掉了。

代码如下:#include "STC12C5A60S2.H"#include <intrins.H>.//头文件#define uchar unsigned char#define uint unsigned intuchar sendbuffer[17];//发送数据uchar flag=0;uchar re_buffer[32];uchar count=0;uint ad_data=0;double ad_vol=0;uint con_mid=0;uint gascon=0;long sum_o2=0;uint average_counter=20;uint idata oldtemp[21];uchar idata Send_Buff[20]; //moduleuint pre_contemp=0;uint O2_con=0;uchar idata test[21]; //moduleuint temperature=0;uint dat;uint testlcd=0;uchar c[]={0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9};//MAX1241 模数芯片引脚设置,此电路板我没焊^_^//sbit ADC_CS=P0^1;//sbit ADC_CLK=P0^0;//sbit ADC_DOUT=P0^2;sbit ADC_CS=P3^6;sbit ADC_CLK=P3^5;sbit ADC_DOUT=P3^7;//LCD12864 LCD屏幕引脚设置sbit cs=P2^2;sbit sid=P2^1;sbit sclk=P2^0;sbit DQ=P0^1; //DS18B20 温度引脚设置,依然没有…#define N 11#define N2 20void delayms(unsigned char t){unsigned char i;unsigned char j;for(j=t;j;j--)for(i=192;i;i--);/*1ms延时*/}void delayus(uint t){uint i;for(i=0;i<t;i++){_nop_();}}void delay(float sec){unsigned int i;unsigned int j;j=sec*100;while(j--){i=1561;while(--i);}}void UART1_init() //串口1初始化,此串口和电脑通讯{TMOD=0x20;/设置定时器工作方式2TH1=0xfd; //波特率9600TL1=0xfd;TR1=1;REN=1;SM0=0;SM1=1;//ES=1;}void UART2_init()//串口2初始化,和模块通讯 Modbus{S2CON= 0x50; //方式1,允许接收BRT = 0xf4; //波特率2400AUXR = AUXR |0X10; //允许独立波特率允许AUXR1 = AUXR1&0xef; //将uart2切换到P1口IE2 = IE2|0X01; //允许串口2中断}void UART1_Send (unsigned char UART_data)//{//ES=0;SBUF = UART_data; //将接收的数据发送回去while(TI!=1); //检查发送中断标志位TI = 0; //另发送中断标志位为0//ES=1;}void UART2_Send(unsigned char UART_data)//串口2发送{//ES = 0 ;S2BUF = UART_data;while((S2CON&0x02)!=0x02);S2CON &= ~0x02;//ES = 1 ;}void UART1_Send_String (char *str, char len)//串口1发送字符串{unsigned char i;for(i=0;i<=len;i++){UART1_Send(str[i]);}}void UART2_Send_String (char *str, char len) //串口2发送字符串{unsigned char i;for(i=0;i<=len;i++){UART2_Send(str[i]);}}unsigned char Creat_Addr(unsigned char adr, unsigned char position)//计算模块地址{unsigned char hich;unsigned char loch;hich = adr/16;loch = adr%16;if(hich>9)hich+=7;if(loch>9)loch+=7;if(position == 1){return hich+0x30;}else if(position == 0){return loch+0x30;}}unsigned char CheckSum(unsigned char *str, unsigned char position, uchar len)/计算校验码{uchar i;unsigned int sum=0;uchar hi, lo;//uchar len = 12;for(i = 1; i <= len; i ++){*str ++;sum += *str;}sum = 256-(sum%256);hi = sum/16;lo = sum%16;if(hi > 9)hi += 7;if(lo > 9)lo += 7;hi += 0x30;lo += 0x30;if(sum == 256)hi = lo = 0x30;if(position == 1){return hi;}else if(position == 0){return lo;}}void data_init(){sendbuffer[0]=0x5B;sendbuffer[1]=0x30;sendbuffer[2]=0x30;sendbuffer[3]=0x30;sendbuffer[4]=0x30;sendbuffer[5]=0x30;sendbuffer[6]=0x7C;sendbuffer[7]=0x30;sendbuffer[8]=0x30;sendbuffer[9]=0x30;sendbuffer[10]=0x30;sendbuffer[11]=0x30;//TEMsendbuffer[12]=0x30;sendbuffer[13]=0x30;sendbuffer[14]=0x5D;sendbuffer[15]=0x0D;sendbuffer[16]=0x0A;}void calculate_module(unsigned char str[])//lcd屏显示{unsigned int concen;uchar wan,qian,bai,shi,ge;/*uchar d4 = str[7]-48;uchar d3 = str[8]-48;uchar d2 = str[9]-48;uchar d1 = str[10]-48;*/ //浓度只需要后面部分 uchar d4 = str[24]-48;uchar d3 = str[25]-48;uchar d2 = str[26]-48;uchar d1 = str[27]-48;if(d4>9)d4-=7;if(d3>9)d3-=7;if(d2>9)d2-=7;if(d1>9)d1-=7;concen = d4*4096+d3*256+d2*16+d1;gascon=concen;wan=concen/10000;qian=concen%10000/1000;bai=concen%1000/100;shi=concen%100/10;ge=concen%10;//sendbuffer[6]=wan+0x30; //最终显示XXX.X%sendbuffer[7]=qian+0x30;sendbuffer[8]=bai+0x30;sendbuffer[9]=shi+0x30;sendbuffer[10]=ge+0x30;}void module_init()//气体模块初始化{Send_Buff[0] = ':';Send_Buff[3] = '0';Send_Buff[4] = '3';Send_Buff[5] = '0';Send_Buff[6] = '0';Send_Buff[7] = '0';Send_Buff[8] = 'A';Send_Buff[9] = '0';Send_Buff[10] = '0';Send_Buff[11] = '0';Send_Buff[12] = '1';Send_Buff[1] = Creat_Addr(31, 1);Send_Buff[2] = Creat_Addr(31, 0);Send_Buff[13] = CheckSum(Send_Buff, 1, 12);Send_Buff[14] = CheckSum(Send_Buff, 0, 12);Send_Buff[15] = 0x0D;Send_Buff[16] = 0x0A;//手动添加校验,例如字符‘0’换成ASCII码0X30,添加偶校验后就是0X3A,最终发送给模块的以下数据,地址被写死,这个不像C#做的一目了然O(∩_∩)Otest[0]=0X3A;test[1]=0XB1;test[2]=0XC6;test[3]=0X30;test[4]=0X33;test[5]=0X30;test[6]=0X30;test[7]=0X30;test[8]=0X41;test[9]=0X30;test[10]=0X30;test[11]=0X30;test[12]=0XB1;test[13]=0X39;test[14]=0XB4;test[15]=0X8D;test[16]=0X0A;}/*uint read_max1241() AD芯片处理{uint ADC_Data;uchar i;ADC_CLK=0;ADC_CS=0;ADC_Data=0;while(!ADC_DOUT);ADC_CLK=1;ADC_CLK=0;for(i=0;i<12;i++){ADC_CLK=1;ADC_Data<<=1;ADC_Data |= ADC_DOUT;ADC_CLK=0;}ADC_CS=1;ADC_CLK=0;return ADC_Data;}unsigned int ad_filter()//滤波{unsigned int count1,i,j;unsigned int value_buf[N];unsigned int temp;unsigned int sum=0;for (count1=0;count1<N;count1++){value_buf[count1] =read_max1241(); delayms(20);}for (j=0;j<(N-1);j++){for (i=0;i<(N-j);i++){if ( value_buf[i]>value_buf[i+1] ) {temp = value_buf[i];value_buf[i] = value_buf[i+1]; value_buf[i+1] = temp;}}}for(count1=3;count1<(N-3);count1++){sum += value_buf[count1];}return (unsigned int)(sum/(N-6));}void calculate_o2()//测试用{uchar wan,qian,bai,shi,ge;uint con_fin=0;uchar i;ad_data=ad_filter();ad_vol=(ad_data/4096.0)*2500.0;//Voltage ad_data=(uint)ad_vol; //concentrationdelayms(2);if(average_counter>0){sum_o2+= ad_data;oldtemp[average_counter-1]=ad_data;average_counter--;}else{sum_o2 -=oldtemp[19];for(i=20;i>0;i--){oldtemp[i]=oldtemp[i-1];}oldtemp[0]=ad_data;sum_o2+=oldtemp[0];con_fin=(uint)(sum_o2/N2);//O2_con=0.8*con_fin+0.2*pre_contemp;//pre_contemp=O2_con;wan=con_fin/10000;qian=con_fin%10000/1000;bai=con_fin%1000/100;shi=con_fin%100/10;ge=con_fin%10;sendbuffer[1]=wan+0x30;sendbuffer[2]=qian+0x30;sendbuffer[3]=bai+0x30;sendbuffer[4]=shi+0x30;sendbuffer[5]=ge+0x30;delayms(5);}}//-------------18B20 温度显示复位函数--------------- void ow_reset(void){char presence=1;while(presence){while(presence){DQ=1;delayus(2);DQ=0; //delayus(550); // 550usDQ=1; //delayus(66); // 66uspresence=DQ;}delayus(500); //延时500uspresence = ~DQ;}DQ=1;}//-----------18B20写命令函数------------void write_byte(uchar val){uchar i;for (i=8; i>0; i--) //{DQ=1;delayus(2);DQ = 0;delayus(5);//5usDQ = val&0x01;delayus(66); //66usval=val/2;}DQ = 1;delayus(11);}//--------------18B20读一个字节函数---------uchar read_byte(void){uchar i;uchar value = 0;for (i=8;i>0;i--){DQ=1;delayus(2);value>>=1;DQ = 0;delayus(4); //4usDQ = 1;delayus(4); //4usif(DQ)value|=0x80;delayus(66); //66us}DQ=1;return(value);}void Read_Temperature(void){unsigned int Temp1,Temp2;uchar bai,shi,ge;ow_reset(); //DS18B20write_byte(0xCC);write_byte(0x44);ow_reset(); //DS1302复位write_byte(0xCC);write_byte(0xbe);Temp1=read_byte();Temp2=read_byte();ow_reset();temperature=(((Temp2<<8)|Temp1)*0.625); //0.0625=xx, 0.625=xx.x, 6.25=xx.xxbai=temperature/100;shi=temperature%100/10;ge=temperature%10;sendbuffer[11]=bai+0x30;sendbuffer[12]=shi+0x30;sendbuffer[13]=ge+0x30;delayms(5);}* //图中电路板此部分没焊,此部分代码不使用^_^void writecmd_lcd(uchar cmd) //lcd屏写指令{uchar i;uchar cmd1;cmd1=cmd;//----------先写控制,选择写指令,还是写数据11111000 for(i=0;i<5;i++) //必须sid先发送,然后sclk,不然不行 {sid=1;sclk=1;sclk=0;}for(i=0;i<3;i++){sid=0;sclk=1;sclk=0;}//delayms(10);cmd=cmd&0xf0; //先高4位for(i=0;i<8;i++){if(cmd&0x80){sid=1;}else sid=0;sclk=1;sclk=0;cmd=cmd<<1;}//delayms(10);cmd1=((cmd1<<4)&0xf0); //低4位 for(i=0;i<8;i++){if(cmd1&0x80){sid=1;}else sid=0;sclk=1;sclk=0;cmd1=cmd1<<1;}}void writedata_lcd(uchar dat) {uchar i;uchar dat1;dat1=dat;//11111010for(i=0;i<5;i++){sid=1;sclk=1;sclk=0;}sid=0;sclk=1;sclk=0;sid=1;sclk=1;sclk=0;sid=0;sclk=1;sclk=0;//delayms(10);dat=dat&0xf0;for(i=0;i<8;i++)if(dat&0x80){sid=1;}else sid=0;sclk=1;sclk=0;dat=dat<<1;}//delayms(10);dat1=((dat1<<4)&0xf0);for(i=0;i<8;i++){if(dat1&0x80){sid=1;}else sid=0;sclk=1;sclk=0;dat1=dat1<<1;}}void init_lcd()//初始化lcd屏{cs=1;writecmd_lcd(0x30);//设定为8位控制writecmd_lcd(0x0c);//显示打开writecmd_lcd(0x01);//清屏}void gotoxy(uint row, uint col){switch(row){case 1: writecmd_lcd(0x80+col);break; case 2: writecmd_lcd(0x90+col);break; case 3: writecmd_lcd(0x88+col);break; case 4: writecmd_lcd(0x98+col);break;}void clear(){writecmd_lcd(0x01);delayms(10);}void SendStr(uchar *str){uchar i;for(i=0;str[i]!='\0';i++){writedata_lcd(str[i]);}}void lcd_display(uint lcddata)//lcd屏显示浓度{uchar wan,qian,bai,shi,ge;wan=lcddata/10000;qian=lcddata%10000/1000;bai=lcddata%1000/100;shi=lcddata%100/10;ge=lcddata%10;gotoxy(2,1);writedata_lcd(0xa3);writedata_lcd(c[qian]);gotoxy(2,2);writedata_lcd(0xa3);writedata_lcd(c[bai]);gotoxy(2,3);writedata_lcd(0xa3);writedata_lcd(c[shi]);gotoxy(2,4);SendStr(".");gotoxy(2,5);writedata_lcd(0xa3);writedata_lcd(c[ge]);gotoxy(2,6);SendStr("%");}void main()//主函数{delay(2.1);UART1_init();UART2_init();data_init();module_init();init_lcd();EA=1;delay(2.1);while(1){/*calculate_o2();//测试用Read_Temperature();*/ //温度芯片没焊UART2_Send_String(test,16);//串口2:和模块通讯delay(0.8);if(flag==1){calculate_module(re_buffer);delayms(5);UART1_Send_String(sendbuffer,16);//串口1:电脑上可以接收发送的数据 flag=0;delay(0.8);}lcd_display(gascon);//lcd显示浓度delay(0.8);}}/*void uart1_in() interrupt 4/串口1中断,不使用,因为只是发送{RI=0;re_buffer[count]=SBUF;if(re_buffer[0]!=':'){count=0;}else{count++;if(count==10){flag=1;count=0;}}}*/void uart2_in() interrupt 8//串口2中断,需要接受模块返回的数据{if(S2CON&0X01){re_buffer[count]=S2BUF;re_buffer[count]&=0x7f;count++;S2CON&=0XFE;}if(count==32){count=0;flag=1;}}。

485通信程序(51单片机)

485通信程序(51单片机)

485通信程序(51单片机)什么是485通信?RS-485是一种串行通信协议,它使用差分信号传输数据。

485通信支持了在两个或以上设备之间传输数据的需求,比如用于电子计算机、通信设备、工业自动化等等。

RS-485已广泛应用于数控机床、自动化设备控制等领域中。

单纯的485通信包含四种通信模式:点对点、总线形、多主机和简介式通信。

其中,点对点通信指的是一对一的通信方式;总线形通信指的是一对多的群通信方式,所有设备都在同一条总线上发送和接收数据;多主机通信指的是多台主机的通信方式,多个设备都可以同时发送数据;简介式通信是一种用于仅需要发送少量数据的情况的通信方式。

下面介绍一下485通信的部分基本知识1.485通信的传输距离远,一般可以达到1200米。

2.485通信具有较强的抗干扰能力。

3.485通信使用差分信号进行传输,信号稳定,传输速率也比较快。

4.485通信可以支持多个设备同时进行通信,但是在同一时间内只有一个设备可发送数据。

5.在采用485通信时,一定要注意通讯端口的设置,如波特率、数据位、停止位等。

程序实现原理该程序使用了51单片机作为主控制器实现了基本的485通信,具体实现如下:1.通信模式的设置在程序开始时,需要设置通信模式。

如果通信模式为点对点通信,则可以直接使用UART通信模块进行通信;如果是多点通信,则需要使用485通信芯片。

2.通讯端口的配置在进行485通讯时,需要进行通讯端口的配置,包括波特率、数据位、停止位等参数的设定。

在485通信模式下,只有一个设备可为主设备,其他设备均为被设备。

在发送数据时,主设备的TXD口要与外部总线的D+口相连,而D-口不连接,从设备的TXD口要与D-口相连,而D+口不连接。

在接收数据时,主设备的RXD口要与D+口相连,而D-口不连接,从设备的RXD口要与D-口相连,而D+口不连接。

3.数据的发送和接收在发送和接收数据时,需要采用特定的方式进行报文的封装和解析。

关于51单片机上实现modbus协议

关于51单片机上实现modbus协议

关于51单片机上实现modbus协议你找一个MODBUS的协议详细资料好好看看,就是一种通讯约定,你按照它规定的格式通讯就可以了协议发送给询问方。

Modbus协议包括ASCII、RTU、TCP等,并没有规定物理层。

此协议定义了控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的。

标准的Modicon控制器使用RS232C实现串行的Modbus。

Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用Maser/Slave方式,Master端发出数据请求消息,Slave端接收到正确消息后就可以发送数据到Master端以响应请求;Master端也可以直接发消息修改Slave端的数据,实现双向读写。

Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验,但TCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。

另外,Modbus采用主从方式定时收发数据,在实际使用中如果某Slave站点断开后(如故障或关机),Master端可以诊断出来,而当故障修复后,网络又可自动接通。

因此,Modbus协议的可靠性较好。

下面我来简单的给大家介绍一下,对于Modbus的ASCII、RTU和TCP协议来说,其中TCP和RTU协议非常类似,我们只要把RTU协议的两个字节的校验码去掉,然后在RTU协议的开始加上5个0和一个6并通过TCP/IP网络协议发送出去即可。

所以在这里我仅介绍一下Modbus的ASCII和RTU协议。

下表是ASCII协议和RTU协议进行的比较:协议开始标记结束标记校验传输效率程序处理ASCII :(冒号)CR,LF LRC 低直观,简单,易调试RTU 无无CRC 高不直观,稍复杂通过比较可以看到,ASCII协议和RTU协议相比拥有开始和结束标记,因此在进行程序处理时能更加方便,而且由于传输的都是可见的ASCII字符,所以进行调试时就更加的直观,另外它的LRC校验也比较容易。

RS-485和Modbus通信协议及工作原理

RS-485和Modbus通信协议及工作原理

RS-485和Modbus通信协议及工作原理在(工业控制)、电力通讯、(智能)仪表等领域,通常情况下是采用串口(通信)的方式进行数据交换。

最初采用的方式是(RS)232接口,由于(工业)现场比较复杂,各种(电气)设备会在环境中产生比较多的电磁千扰,会导致(信号)传输错误。

1979年施耐德电气制定了一个用于工业现场的总线协议Modbus协议,现在工业中使用RS485通信场合很多都采用Modbus 协议,所以今天我们来了解下RS485通信和Modbus通信协议。

什么是串口通信串口通信(Serial Communication),是指外设和计算机间,通过数据信号线、地线、控制线等,按位进行传输数据的一种通讯方式。

这种通信方式使用的数据线少,在远距离通信中可以节约通信成本,但其传输速度比并行传输低。

由于串行通信是在一根传输线上一位一位的传送信息,所用的传输线少,并目可以借助现成的电话网进行信息传送,因此,特别适合于远距离传输。

(RS-485)协议概述RS-485和RS-232一样,都是审行通信标准,现在的标准名称是(TI)A485/EIA-485-A,但是人们会习惯称为RS485标准,RS-485常用在工业、自动化、汽车和建筑物管理等领域。

RS-485总线弥补了RS-232通信距离短,速率低的缺点,RS-485的速率可高达10Mbit/s,理论通讯距离可达1200米;RS-485和RS-232的单端传输不一样是差分传输,使用一对双绞线,其中一根线定义为A,另一个定义为B。

通常情况下,RS485的信号在传送出去之前会先分解成正负对称的两条线路(即我们常说的A、B信号线),当到达接收端后,再将信号相减还原成原来的信号。

拓扑结构RS485有两线制和四线制两种接线,四线制只能实现点对点的通信方式,现很少采用,多采用的是两线制接线方式,这种接线方式为总线拓扑结构,在同一总线上最多可以挂接32个节点RS-485总线同12C总线一样支持主从模式,支持点对点单从机模式,也支持多从机模式,不支持多主机模式。

手把手教你学51单片机(C语言版)

手把手教你学51单片机(C语言版)
12.3 字符数组和字 符指针

12.3.1 常量和符 号常量
A
12.3.2 字符和字 符串数组实例
B
12 指针基础与1602液晶的初步认识
1
12.4.1 1602液晶的硬件接口 介绍
2
12.4.2 1602液晶的读写时序 介绍
3
12.4.3 1602液晶的指令介绍
4
12.4.4 1602液晶简单实例
01
8.1.1 电 源
02
8.1.2 晶 振
03
8.1.3 复 位电路
8.1 单片机最小系统
0 1
8.4.1 独立按

0 2
8.4.2 矩阵按

0 3
8.4.3 独立按
键的扫描
0 4
8.4.4 按键消

0 5
8.4.5 矩阵按
键的扫描
8 函数进阶与按键
8.4 按键
10
Part One
9 步进电机与蜂鸣器
11.3 USB转串口 通信
11.4 IO口模拟 UART串口通信
11.5 UART串口 通信的基本应用
11.6 通信实例与 ASCII码
11 UART串口通 信
11.7 练习题
11.5.1 通信的三种基本类 型
11.5.3 UART串口程序
11 UART串口通信
11.5 UART串口通信的基本应用
10.1.3 字节操作修改位的 技巧
10.1.5 秒表程序
10 实例练习与经验积累
10.1 数字秒表实例
10.1.2 定时时间精准性调 整
10.1.4 数码管扫描函数算 法改进
12
Part One

485通讯协议程序怎么写(51单片机的485通信程序案例)

485通讯协议程序怎么写(51单片机的485通信程序案例)

485通讯协议程序怎么写(51单片机的485通信程序案例)
RS-485总线接口是一种常用的串口,具有网络连接方便、抗干扰性能好、传输距离远等优点。

RS-485收发器采用平衡发送和差分接收,因此具有抑制共模干扰的能力,加上收发器具有高的灵敏度,能检测到低达200mv的电压,可靠通信的传输距离可达数千米。

使用RS-485总线组网,只需一对双绞线就可实现多系统联网构成分布式系统、设备简单、价格低廉、通信距离长。

51单片机的485通信程序
#ifndef __485_C__ #define __485_C__
#include 《reg51.h》
#include 《string.h》
#define unsigned char uchar
#define unsigned int uint
/* 通信命令*/
#define __ACTIVE_ 0x01 // 主机询问从机是否存在
#define __GETDATA_ 0x02 // 主机发送读设备请求
#define __OK_ 0x03 // 从机应答
#define __STATUS_ 0x04 // 从机发送设备状态信息
#define __MAXSIZE 0x08 // 缓冲区长度
#define __ERRLEN 12 // 任何通信帧长度超过12则表示出错
uchar dbuf[__MAXSIZE]; // 该缓冲区用于保存设备状态信息
uchar dev; // 该字节用于保存本机设备号
sbit M_DE = P1。

51单片机新手入门之Modbus通讯

51单片机新手入门之Modbus通讯

51单片机新手入门之Modbus通讯本文和另一篇----C# WPF新手入门之Modbus通讯为一个系列,所用模块均是气体分析模块,之前用C3做的上位机(主要是方便调试,单片机不好搞),既然电脑上串口通讯代码没问题,那么放到单片机稍加修改也就理所当然肯定可行O(∩_∩)O~。

本文包括单片机的电路设计和软件设计两部分(单片机采用STC12C5A60S2双串口),至此单片机和上位机的串口通讯均成功实现,供需要的同学参考。

1.电路设计Altium Designer原理图如下:电源部分:24V转5V及6VAD及滤波Modbus通讯电脑串口通讯lcd屏显示预留接口,可以另接MAX232用来调试发送给模块数据正确性实际效果如下:电脑串口接收到的数据,和LCD 屏显示的是一样的下载步骤1. 选择芯片下载步骤2. 打开串口 下载步骤3. 打开hex 文件 下载步骤4. MCU 先断电,点击下载,看到提示后上电2.软件设计这里最麻烦的在于模块的Modbus通讯需要偶校验,我是手动添加的校验位,例如字符‘0’换成ASCII码0X30,添加偶校验后就是0X3A,把所有需要发送的数据一个个手动添加了再发送^_^;另外lcd12864屏也搞半天,主要照着时序图写必须sid先发送,然后sclk,不然不行 ̄□ ̄。

气体模块详细的通讯协议参考和上位机通讯那篇,此处不再介绍。

图中电路板没有焊AD芯片和温度芯片,引脚备用,相关代码注释掉了。

代码如下:#include "STC12C5A60S2.H"#include <intrins.H>.//头文件#define uchar unsigned char#define uint unsigned intuchar sendbuffer[17];//发送数据uchar flag=0;uchar re_buffer[32];uchar count=0;uint ad_data=0;double ad_vol=0;uint con_mid=0;uint gascon=0;long sum_o2=0;uint average_counter=20;uint idata oldtemp[21];uchar idata Send_Buff[20]; //moduleuint pre_contemp=0;uint O2_con=0;uchar idata test[21]; //moduleuint temperature=0;uint dat;uint testlcd=0;uchar c[]={0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9};//MAX1241 模数芯片引脚设置,此电路板我没焊^_^//sbit ADC_CS=P0^1;//sbit ADC_CLK=P0^0;//sbit ADC_DOUT=P0^2;sbit ADC_CS=P3^6;sbit ADC_CLK=P3^5;sbit ADC_DOUT=P3^7;//LCD12864 LCD屏幕引脚设置sbit cs=P2^2;sbit sid=P2^1;sbit sclk=P2^0;sbit DQ=P0^1; //DS18B20 温度引脚设置,依然没有…#define N 11#define N2 20void delayms(unsigned char t){unsigned char i;unsigned char j;for(j=t;j;j--)for(i=192;i;i--);/*1ms延时*/}void delayus(uint t){uint i;for(i=0;i<t;i++){_nop_();}}void delay(float sec){unsigned int i;unsigned int j;j=sec*100;while(j--){i=1561;while(--i);}}void UART1_init() //串口1初始化,此串口和电脑通讯{TMOD=0x20;/设置定时器工作方式2TH1=0xfd; //波特率9600TL1=0xfd;TR1=1;REN=1;SM0=0;SM1=1;//ES=1;}void UART2_init()//串口2初始化,和模块通讯 Modbus{S2CON= 0x50; //方式1,允许接收BRT = 0xf4; //波特率2400AUXR = AUXR |0X10; //允许独立波特率允许AUXR1 = AUXR1&0xef; //将uart2切换到P1口IE2 = IE2|0X01; //允许串口2中断}void UART1_Send (unsigned char UART_data)//{//ES=0;SBUF = UART_data; //将接收的数据发送回去while(TI!=1); //检查发送中断标志位TI = 0; //另发送中断标志位为0//ES=1;}void UART2_Send(unsigned char UART_data)//串口2发送{//ES = 0 ;S2BUF = UART_data;while((S2CON&0x02)!=0x02);S2CON &= ~0x02;//ES = 1 ;}void UART1_Send_String (char *str, char len)//串口1发送字符串{unsigned char i;for(i=0;i<=len;i++){UART1_Send(str[i]);}}void UART2_Send_String (char *str, char len) //串口2发送字符串{unsigned char i;for(i=0;i<=len;i++){UART2_Send(str[i]);}}unsigned char Creat_Addr(unsigned char adr, unsigned char position)//计算模块地址{unsigned char hich;unsigned char loch;hich = adr/16;loch = adr%16;if(hich>9)hich+=7;if(loch>9)loch+=7;if(position == 1){return hich+0x30;}else if(position == 0){return loch+0x30;}}unsigned char CheckSum(unsigned char *str, unsigned char position, uchar len)/计算校验码{uchar i;unsigned int sum=0;uchar hi, lo;//uchar len = 12;for(i = 1; i <= len; i ++){*str ++;sum += *str;}sum = 256-(sum%256);hi = sum/16;lo = sum%16;if(hi > 9)hi += 7;if(lo > 9)lo += 7;hi += 0x30;lo += 0x30;if(sum == 256)hi = lo = 0x30;if(position == 1){return hi;}else if(position == 0){return lo;}}void data_init(){sendbuffer[0]=0x5B;sendbuffer[1]=0x30;sendbuffer[2]=0x30;sendbuffer[3]=0x30;sendbuffer[4]=0x30;sendbuffer[5]=0x30;sendbuffer[6]=0x7C;sendbuffer[7]=0x30;sendbuffer[8]=0x30;sendbuffer[9]=0x30;sendbuffer[10]=0x30;sendbuffer[11]=0x30;//TEMsendbuffer[12]=0x30;sendbuffer[13]=0x30;sendbuffer[14]=0x5D;sendbuffer[15]=0x0D;sendbuffer[16]=0x0A;}void calculate_module(unsigned char str[])//lcd屏显示{unsigned int concen;uchar wan,qian,bai,shi,ge;/*uchar d4 = str[7]-48;uchar d3 = str[8]-48;uchar d2 = str[9]-48;uchar d1 = str[10]-48;*/ //浓度只需要后面部分 uchar d4 = str[24]-48;uchar d3 = str[25]-48;uchar d2 = str[26]-48;uchar d1 = str[27]-48;if(d4>9)d4-=7;if(d3>9)d3-=7;if(d2>9)d2-=7;if(d1>9)d1-=7;concen = d4*4096+d3*256+d2*16+d1;gascon=concen;wan=concen/10000;qian=concen%10000/1000;bai=concen%1000/100;shi=concen%100/10;ge=concen%10;//sendbuffer[6]=wan+0x30; //最终显示XXX.X%sendbuffer[7]=qian+0x30;sendbuffer[8]=bai+0x30;sendbuffer[9]=shi+0x30;sendbuffer[10]=ge+0x30;}void module_init()//气体模块初始化{Send_Buff[0] = ':';Send_Buff[3] = '0';Send_Buff[4] = '3';Send_Buff[5] = '0';Send_Buff[6] = '0';Send_Buff[7] = '0';Send_Buff[8] = 'A';Send_Buff[9] = '0';Send_Buff[10] = '0';Send_Buff[11] = '0';Send_Buff[12] = '1';Send_Buff[1] = Creat_Addr(31, 1);Send_Buff[2] = Creat_Addr(31, 0);Send_Buff[13] = CheckSum(Send_Buff, 1, 12);Send_Buff[14] = CheckSum(Send_Buff, 0, 12);Send_Buff[15] = 0x0D;Send_Buff[16] = 0x0A;//手动添加校验,例如字符‘0’换成ASCII码0X30,添加偶校验后就是0X3A,最终发送给模块的以下数据,地址被写死,这个不像C#做的一目了然O(∩_∩)Otest[0]=0X3A;test[1]=0XB1;test[2]=0XC6;test[3]=0X30;test[4]=0X33;test[5]=0X30;test[6]=0X30;test[7]=0X30;test[8]=0X41;test[9]=0X30;test[10]=0X30;test[11]=0X30;test[12]=0XB1;test[13]=0X39;test[14]=0XB4;test[15]=0X8D;test[16]=0X0A;}/*uint read_max1241() AD芯片处理{uint ADC_Data;uchar i;ADC_CLK=0;ADC_CS=0;ADC_Data=0;while(!ADC_DOUT);ADC_CLK=1;ADC_CLK=0;for(i=0;i<12;i++){ADC_CLK=1;ADC_Data<<=1;ADC_Data |= ADC_DOUT;ADC_CLK=0;}ADC_CS=1;ADC_CLK=0;return ADC_Data;}unsigned int ad_filter()//滤波{unsigned int count1,i,j;unsigned int value_buf[N];unsigned int temp;unsigned int sum=0;for (count1=0;count1<N;count1++){value_buf[count1] =read_max1241(); delayms(20);}for (j=0;j<(N-1);j++){for (i=0;i<(N-j);i++){if ( value_buf[i]>value_buf[i+1] ) {temp = value_buf[i];value_buf[i] = value_buf[i+1]; value_buf[i+1] = temp;}}}for(count1=3;count1<(N-3);count1++){sum += value_buf[count1];}return (unsigned int)(sum/(N-6));}void calculate_o2()//测试用{uchar wan,qian,bai,shi,ge;uint con_fin=0;uchar i;ad_data=ad_filter();ad_vol=(ad_data/4096.0)*2500.0;//Voltage ad_data=(uint)ad_vol; //concentrationdelayms(2);if(average_counter>0){sum_o2+= ad_data;oldtemp[average_counter-1]=ad_data;average_counter--;}else{sum_o2 -=oldtemp[19];for(i=20;i>0;i--){oldtemp[i]=oldtemp[i-1];}oldtemp[0]=ad_data;sum_o2+=oldtemp[0];con_fin=(uint)(sum_o2/N2);//O2_con=0.8*con_fin+0.2*pre_contemp;//pre_contemp=O2_con;wan=con_fin/10000;qian=con_fin%10000/1000;bai=con_fin%1000/100;shi=con_fin%100/10;ge=con_fin%10;sendbuffer[1]=wan+0x30;sendbuffer[2]=qian+0x30;sendbuffer[3]=bai+0x30;sendbuffer[4]=shi+0x30;sendbuffer[5]=ge+0x30;delayms(5);}}//-------------18B20 温度显示复位函数--------------- void ow_reset(void){char presence=1;while(presence){while(presence){DQ=1;delayus(2);DQ=0; //delayus(550); // 550usDQ=1; //delayus(66); // 66uspresence=DQ;}delayus(500); //延时500uspresence = ~DQ;}DQ=1;}//-----------18B20写命令函数------------void write_byte(uchar val){uchar i;for (i=8; i>0; i--) //{DQ=1;delayus(2);DQ = 0;delayus(5);//5usDQ = val&0x01;delayus(66); //66usval=val/2;}DQ = 1;delayus(11);}//--------------18B20读一个字节函数---------uchar read_byte(void){uchar i;uchar value = 0;for (i=8;i>0;i--){DQ=1;delayus(2);value>>=1;DQ = 0;delayus(4); //4usDQ = 1;delayus(4); //4usif(DQ)value|=0x80;delayus(66); //66us}DQ=1;return(value);}void Read_Temperature(void){unsigned int Temp1,Temp2;uchar bai,shi,ge;ow_reset(); //DS18B20write_byte(0xCC);write_byte(0x44);ow_reset(); //DS1302复位write_byte(0xCC);write_byte(0xbe);Temp1=read_byte();Temp2=read_byte();ow_reset();temperature=(((Temp2<<8)|Temp1)*0.625); //0.0625=xx, 0.625=xx.x, 6.25=xx.xxbai=temperature/100;shi=temperature%100/10;ge=temperature%10;sendbuffer[11]=bai+0x30;sendbuffer[12]=shi+0x30;sendbuffer[13]=ge+0x30;delayms(5);}* //图中电路板此部分没焊,此部分代码不使用^_^void writecmd_lcd(uchar cmd) //lcd屏写指令{uchar i;uchar cmd1;cmd1=cmd;//----------先写控制,选择写指令,还是写数据11111000 for(i=0;i<5;i++) //必须sid先发送,然后sclk,不然不行 {sid=1;sclk=1;sclk=0;}for(i=0;i<3;i++){sid=0;sclk=1;sclk=0;}//delayms(10);cmd=cmd&0xf0; //先高4位for(i=0;i<8;i++){if(cmd&0x80){sid=1;}else sid=0;sclk=1;sclk=0;cmd=cmd<<1;}//delayms(10);cmd1=((cmd1<<4)&0xf0); //低4位 for(i=0;i<8;i++){if(cmd1&0x80){sid=1;}else sid=0;sclk=1;sclk=0;cmd1=cmd1<<1;}}void writedata_lcd(uchar dat) {uchar i;uchar dat1;dat1=dat;//11111010for(i=0;i<5;i++){sid=1;sclk=1;sclk=0;}sid=0;sclk=1;sclk=0;sid=1;sclk=1;sclk=0;sid=0;sclk=1;sclk=0;//delayms(10);dat=dat&0xf0;for(i=0;i<8;i++)if(dat&0x80){sid=1;}else sid=0;sclk=1;sclk=0;dat=dat<<1;}//delayms(10);dat1=((dat1<<4)&0xf0);for(i=0;i<8;i++){if(dat1&0x80){sid=1;}else sid=0;sclk=1;sclk=0;dat1=dat1<<1;}}void init_lcd()//初始化lcd屏{cs=1;writecmd_lcd(0x30);//设定为8位控制writecmd_lcd(0x0c);//显示打开writecmd_lcd(0x01);//清屏}void gotoxy(uint row, uint col){switch(row){case 1: writecmd_lcd(0x80+col);break; case 2: writecmd_lcd(0x90+col);break; case 3: writecmd_lcd(0x88+col);break; case 4: writecmd_lcd(0x98+col);break;}void clear(){writecmd_lcd(0x01);delayms(10);}void SendStr(uchar *str){uchar i;for(i=0;str[i]!='\0';i++){writedata_lcd(str[i]);}}void lcd_display(uint lcddata)//lcd屏显示浓度{uchar wan,qian,bai,shi,ge;wan=lcddata/10000;qian=lcddata%10000/1000;bai=lcddata%1000/100;shi=lcddata%100/10;ge=lcddata%10;gotoxy(2,1);writedata_lcd(0xa3);writedata_lcd(c[qian]);gotoxy(2,2);writedata_lcd(0xa3);writedata_lcd(c[bai]);gotoxy(2,3);writedata_lcd(0xa3);writedata_lcd(c[shi]);gotoxy(2,4);SendStr(".");gotoxy(2,5);writedata_lcd(0xa3);writedata_lcd(c[ge]);gotoxy(2,6);SendStr("%");}void main()//主函数{delay(2.1);UART1_init();UART2_init();data_init();module_init();init_lcd();EA=1;delay(2.1);while(1){/*calculate_o2();//测试用Read_Temperature();*/ //温度芯片没焊UART2_Send_String(test,16);//串口2:和模块通讯delay(0.8);if(flag==1){calculate_module(re_buffer);delayms(5);UART1_Send_String(sendbuffer,16);//串口1:电脑上可以接收发送的数据 flag=0;delay(0.8);}lcd_display(gascon);//lcd显示浓度delay(0.8);}}/*void uart1_in() interrupt 4/串口1中断,不使用,因为只是发送{RI=0;re_buffer[count]=SBUF;if(re_buffer[0]!=':'){count=0;}else{count++;if(count==10){flag=1;count=0;}}}*/void uart2_in() interrupt 8//串口2中断,需要接受模块返回的数据{if(S2CON&0X01){re_buffer[count]=S2BUF;re_buffer[count]&=0x7f;count++;S2CON&=0XFE;}if(count==32){count=0;flag=1;}}。

手把手教你学单片机之十八RS通信和Modbus协议样本

手把手教你学单片机之十八RS通信和Modbus协议样本

手把手教你学单片机之十八RS通信和Modbus协议样本手把手教你学单片机之十八S RS通信和和s Modbus协议本文档所提供的信息仅供参考之用,不能作为科学依据,请勿模仿。

文档如有不当之处,请联系本人或网站删除。

在工业控制、电力通讯、智能仪表等领域,通常情况下是采用串口通信的方是式进行数据交换。

最初采用的方式是RS232接口,由于工业现场比较复杂,各种电气设备会在环境中产生比较多的电磁干扰,会导致信号传输错误。

除此之外,RS232接口只能实现点对点通信,不具备联网功能,最大传输距离也只能而达到几十米,不能满足远距离通信要求。

而RS485则解决了这些问题,数据信到号采用差分传输方式,能够有效的解决共模干扰问题,最大距离能够到1200米,并且允许多个收发设备接到同一条总线上。

随着工业应用通信越来越多,1979议年施耐德电气制定了一个用于工业现场的总线协议Modbus协议,现在工业中用使用RS485通信场合很多都采用Modbus协议,本节课我们要讲解一下RS485和通信和Modbus协议。

块单单使用一块KST-51开发板是不能够进行RS485实验的,应很多同学的要求,把这节课作为扩展课程讲一下,如果要做本课相关实验,需买要自行购买USB转转485通信模块。

5RS485通信在实际上在RS485之前RS232就已经诞生,但是RS232有几处不足的地方::1、接口的信号电平值较高,达到十几V,容易和损坏接口电路的芯片,而且和TTL电平不兼容,因此和单片机电路接起来的话必须加转换电路。

2、传输速率有局限,不能够过高,一般到几十Kb/s就到极限了。

3、接口使用信号线和GND与其它设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。

4、传输距离有限,最多只能通信几十米。

本文档所提供的信息仅供参考之用,不能作为科学依据,请勿模仿。

文档如有不当之处,请联系本人或网站删除。

5、通信的时候只能两点之间进行通信,不能够实现多机联网通信。

RS485与Modbus通信协议教程

RS485与Modbus通信协议教程

RS485与Modbus通信协议教程1979年施耐德电气订立了一个用于工业现场的总线协议Modbus协议,现在工业中使用RS485通信场合很多都采纳Modbus协议,所以今日我们来了解下RS485通信和Modbus通信协议。

【一】/前言在工业掌控、电力通讯、智能仪表等领域,通常情况下是采纳串口通信的方式进行数据交换。

最初采纳的方式是RS232接口,由于工业现场比较多而杂,各种电气设备会在环境中产生比较多的电磁干扰,会导致信号传输错误。

1979年施耐德电气订立了一个用于工业现场的总线协议Modbus协议,现在工业中使用RS485通信场合很多都采纳Modbus协议,所以今日我们来了解下RS485通信和Modbus通信协议。

【二】/RS485通信1、实际上在RS485之前RS232就已经诞生,但是RS232也有不足:1)接口的信号电平值较高,达到十几V,简单损坏接口电路的芯片,而且和TTL电平不兼容,因此和单片机电路接起来的话必需加转换电路。

2)接口使用的信号线与其他设备形成共地模式的通信,这种共地模式传输简单产生干扰,并且抗干扰性能也比较弱。

3)传输距离、速率都有限,最多只能通信几十米;只能两点之间进行通信,不能够实现多机联网通信。

2、针对RS232接口以上不足,显现了RS485等新的接口标准,RS485具备以下的特点:1)逻辑“1”以两线间的电压差为+(2—6)V表示;逻辑“0”以两线间的电压差为(2—6)V表示。

接口信号电平比RS232降低了,不易损坏电路的芯片,且该电平与TTL电平兼容,可便利与TTL电路连接。

2)RS485通信速度快,数据最高传输速率为10Mbps以上;其内部的物理结构,采纳的是平衡驱动器和查分接收器的组合,抗干扰本领大大加添。

3)传输距离最远可达到1200米左右,但传输速率和传输距离是成反比的,只有在100KB/s以下的传输速率,才能达到最大的通信距离,假如需要传输更远距离可以使用中继。

RS485和MODBUS通讯协议,每个工程师都应该掌握

RS485和MODBUS通讯协议,每个工程师都应该掌握

RS485和MODBUS通讯协议,每个工程师都应该掌握RS485是一个物理接口,简单的说是硬件。

MODBUS是一种国际标准的通讯协议,用于不同厂商之间的设备交换数据(一般是工业用途);所谓的协议也可以理解为上面所说的“语言”,或者简称为软件。

在正常情况下,有两个设备通过MODBUS协议传输数据:首先使用RS232-C作为硬件接口(即普通计算机上的串行通信端口(串行端口))。

也使用RS422,还有常用的RS485,该接口传输距离长,在一般工业场合使用较多。

Modbus特点(1)标准开放,用户可以免费,可以放心地使用Modbus协议,而无需支付许可证费,并且不会侵犯知识产权。

当前,有400多家支持Modbus的制造商和600多种支持Modbus的产品。

(2)Modbus可以支持各种电气接口,例如RS-232,RS-485等,也可以在双绞线,光纤,无线等各种介质上传输。

(3)Modbus的帧格式简单、紧凑,通俗易懂。

用户使用容易,厂商开发简单。

RS485特点1. RS485的电气特性:使用差分信号正逻辑,逻辑“ 1”由两根线之间的电压差表示为+(2?6)V;逻辑“ 0”由两根线之间的电压差表示为-(2?6)V表示。

接口信号电平低于RS-232-C,不容易损坏接口电路芯片,并且该电平与TTL电平兼容,与TTL电路连接方便。

2. RS485的最高数据传输速率为10Mbps。

3. RS485接口是平衡驱动器和差分接收器的组合,具有增强的抗共模干扰能力,即良好的抗噪声干扰。

4.RS-485的最大通讯距离约为1219m,最大传输速率为10Mbps。

传输速率与传输距离成反比。

传输速率越低,传输距离越长。

如果您需要传输的距离超过RS-485最大通信距离,则需要485中继器。

RS-485总线通常最多支持32个节点。

如果使用特殊的485芯片,则可以达到128或256个节点,最大可以支持400个节点。

MODBUS协议分为三种模式:MODBUS RTU,MODBUS ASCII 和后来开发的MODBUS TCP:前两个(MODBUS RTU,MODBUS ASCII)使用的物理硬件接口是串行通信端口(RS232,RS422,RS485)。

51单片机实现RS485

51单片机实现RS485

default: // 普通数据则直接发送TI = 0;SBUF = da;while(!TI);TI = 0;}}#endifRS-232接口实现计算机和单片机通信程序作者:佚名来源:本站原创点击数:…更新时间:2008年07月10日【字体:大中小】//此程序通过RS-232接口来完成计算机和单片机通信(程序已用p1 8f458试验板上调试通过)//程序的调试可以用"串口调试助手V2.1"辅助完成,此程序可在htt p:// 下载//此程序首先发送测试数据55H,再通过中断实现数据的接收和发送#include "p18f458.h"void InterruptHandlerHigh(void);//初始化程序void initial(){SPBRG=0X19; //选择传输波特率为9600bpsTXSTA=0X04; //选择异步高速方式传输8位数据RCSTA=0X80; //允许同步串行口工作TRISC=0X80; //将RC7,RC6设置为输入,断绝与外接电路的连接 TXSTAbits.TXEN=1; //发送允许RCSTAbits.CREN=1; //接受数据允许PIE1bits.RCIE=1; //接收中断使能INTCON=0XC0; //总中断和外围中断允许}//高优先级中断向量#pragma code InterruptVectorHigh=0x08void InterruptVectorHigh (void){_asmgoto InterruptHandlerHigh //跳到中断程_endasm}//高优先级中断服务程序#pragma code#pragma interrupt InterruptHandlerHighvoid InterruptHandlerHigh (){while(PIR1bits.RCIF==1) //若接收中断标志不为1,则为误操作,返回{TXREG=RCREG; //将接收到的数据放入发送寄存器,并启动发送 }}//主程序main(){initial(); //系统初始化TXREG=0X55; //发送数据55H进行测试for(;;);}------------------------------------------汇编语言版本的RS-232接口实现计算机和单片机通信程序------------;此程序通过RS-232接口来完成计算机和单片机通讯(程序以在p18 f458试验板上调通);本单片机程序由提供;此程序首先发送测试数据55H,再通过中断实现数据的接收和发送;程序的调试可以用"串口调试助手V2.1"辅助完成LIST P=18f458INCLUDE "P18f458.INC"ORG 0x00GOTO MAINORG 0x08GOTO INTSERVEORG 0X30;**************中断服务子程序***************INTSERVEBTFSS PIR1,RCIF ;接收中断标志为1?GOTO ERR_RE ;误操作,返回MOVF RCREG,0 ;否则,将接收到的数据通过W寄存器MOVWF TXREG ;放入发送寄存器,并启动发送ERR_RE NOPRETFIE;****************初始化程序***************INITIAL NOPMOVLW 0X19 ;选择传输波特率为9600bpsMOVWF SPBRGMOVLW 0X04 ;选择异步高速方式传输8位数据MOVWF TXSTAMOVLW 0X80 ;允许同步串行口工作MOVWF RCSTAMOVLW 0X80 ;将RC7,RC6设置为输入,断绝与外接电路的连接MOVWF TRISCBSF TXSTA,TXEN ;发送允许BSF RCSTA,CREN ;接受数据允许BSF PIE1,RCIE ;接收中断使能MOVLW 0XC0 ;总中断和外围中断允许MOVWF INTCONRETURN;**********************主程序********************* MAIN NOPCLRWDTCALL INITIALMOVLW 0X55 ;发送数据55H进行测试MOVWF TXREGLOOPGOTO LOOPEND两片51单片机互相通信的串行通信程序(一个发送程序,一个接收程序)2007-05-27 08:27;系统晶振是 11.0592 MHz;51单片机发送单片机程序;此程序用Proteus仿真通过;此程序在硬件上测试通过;2007-05-27;附有简化电路图;为了使初学者能看懂,程序与图尽可能的简单扼要;实验现象为,发送端的P1口的哪个键被接下,接收端的哪个灯对应着亮;如果把两个单片机的T和R通过无线模块(如基于MCP2120芯片的模块)来扩充,便可做成无线通信ORG0000HAJMPSTARTORG0040HSTART:MOVSP,#60HMOVSCON,#50H;串口方式 1MOVTMOD,#20H;T1 方式2MOVTL1,#0FDH;波特率 9600 的常数MOVTH1,#0FDHSETBTR1movr5,#00hWAIT:movp1,#0ffhmova ,p1movr5,alcalldelay ;读键盘,这里去抖动,还要加几句话mova ,p1nopCJNEA,5,WAIT ;是否有键输入MOVSBUF,a;串口输出键盘输入的值NOPSS: JBCTI,WAIT;是否发送完毕SJMPSSDELAY:;延时子程序PUSH;保存现场PUSH1MOV0,#06HDELAY1: MOV 1,#0HDJNZ1,$DJNZ0,DELAY1POP1;恢复现场POPRETEND;系统晶振是 11.0592 MHz;51单片机接收单片机程序;此程序用Proteus仿真通过;此程序在硬件上测试通过;2007-05-27;附有简化电路图;为了使初学者能看懂,程序与图尽可能的简单扼要;实验现象为,发送端的P1口的哪个键被接下,接收端的哪个灯对应着亮;如果把两个单片机的T和R通过无线模块(如基于MCP2120芯片的模块)来扩充,便可做成无线通信ORG0000HAJMPSTARTORG0040HSTART:MOVSCON,#50H;串口方式 1MOVTMOD,#20H;T1 方式 2MOVTL1,#0FDH;波特率 9600 的常数MOVTH1,#0FDHSETBTR1WAIT:JBCRI,DIS_REC;是否接收到数据sjmpwaitDIS_REC:MOVA,SBUF;读串口接收到的数据movp1,aSJMPwaitend51单片机串行口通信程序设计例子时间:2009-03-06 17:13来源:未知作者:牛牛点击: 768次串行口方式0应用编程 8051单片机串行口方式0为移位寄存器方式,外接一个串入并出的移位寄存器,就能扩展一个并行口。

关于51单片机上实现modbus协议

关于51单片机上实现modbus协议

关于51单片机上实现modbus协议你找一个MODBUS的协议详细资料好好看看,就是一种通讯约定,你按照它规定的格式通讯就可以了协议发送给询问方。

Modbus协议包括ASCII、RTU、TCP等,并没有规定物理层。

此协议定义了控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的。

标准的Modicon控制器使用RS232C实现串行的Modbus。

Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用Maser/Slave方式,Master端发出数据请求消息,Slave端接收到正确消息后就可以发送数据到Master端以响应请求;Master端也可以直接发消息修改Slave端的数据,实现双向读写。

Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验,但TCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。

另外,Modbus采用主从方式定时收发数据,在实际使用中如果某Slave站点断开后(如故障或关机),Master端可以诊断出来,而当故障修复后,网络又可自动接通。

因此,Modbus协议的可靠性较好。

下面我来简单的给大家介绍一下,对于Modbus的ASCII、RTU和TCP协议来说,其中TCP和RTU协议非常类似,我们只要把RTU协议的两个字节的校验码去掉,然后在RTU协议的开始加上5个0和一个6并通过TCP/IP网络协议发送出去即可。

所以在这里我仅介绍一下Modbus的ASCII和RTU协议。

下表是ASCII协议和RTU协议进行的比较:协议开始标记结束标记校验传输效率程序处理ASCII :(冒号)CR,LF LRC 低直观,简单,易调试RTU 无无CRC 高不直观,稍复杂通过比较可以看到,ASCII协议和RTU协议相比拥有开始和结束标记,因此在进行程序处理时能更加方便,而且由于传输的都是可见的ASCII字符,所以进行调试时就更加的直观,另外它的LRC校验也比较容易。

手把手教你学51单片机(C语言版)

手把手教你学51单片机(C语言版)
10.1 数字秒表实例
10.1.2 定时时间精准性调 整
10.1.4 数码管扫描函数算 法改进
12
Part One
11 UART串口通信
11 UART串口通信
11.1 串行通信的 初步认识
11.2 RS-232通 信接口
11.3 USB转串口 通信
11.4 IO口模拟 UART串口通信
11.5 UART串口 通信的基本应用
A
C
E
13.2 1602整屏 移动
13.4 计算器实 例
13.6 练习题
13.1 通信时序 解析
13.3 多.c文件 的初步认识
B
13.5 串口通信机制 和实用的串口例程
D
F
15
Part One
14 I^2C总线与E^2PROM
14 I^2C总线与E^2PROM
14.1 I2C时 序初步 认识
14.2 I2C寻 址模式
18 RS-485通信与Modbus协议
01
18.1 RS485通信
02
18.2 Modbus 通信协议介绍
18.2.1 Modbus协议 特点 18.2.2 RTU协议帧数 据
03
18.3 Modbus 多机通信例程
04
18.4 练习题
20
Part One
19 实践项目开发——多功能电子钟
19 实践项目开发——多功能电子钟
1.6 答读者问
03
Part One
2 点亮你的LED
2 点亮你的LED
2.1 单片机的 内部资源
2.2 单片机最 小系统
2.3 LED小灯
2.6 练习题
2.5 程序下载

485通信和modbus协议

485通信和modbus协议

{ TI = 0; //手动清零发送中断标志位
flagOnceTxd = 1; //设置单次发送完成标志
}
} /***********************main.c 文件程序源代码*************************/
cntbkp = cntRxd; idletmr = 0; } else { if (idletmr < 30) //接收计数器未改变,即总线空闲时,累积空闲时间 {
idletmr += ms; if (idletmr >= 30) //空闲时间超过 30ms 即认为一帧命令接收完毕 {
cmdArrived = 1; //设置命令到达标志 } } } } else { cntbkp = 0; } } void InterruptUART() interrupt 4 //UART 中断服务函数 { if (RI) //接收到字节 { RI = 0; //手动清零接收中断标志位 if (cntRxd < sizeof(bufRxd)) //接收缓冲区尚未用完时, { bufRxd[cntRxd++] = SBUF; //保存接收字节,并递增计数器 } } if (TI) //字节发送完毕
#include <intrins.h> sbit RS485_DIR = P1^7; //RS485 方向选择引脚
bit flagOnceTxd = 0; //单次发送完成标志,即发送完一个字节
bit cmdArrived = 0; //命令到达标志,即接收到上位机下发的命令
unsigned char cntRxd = 0; unsigned char pdata bufRxd[40]; //串口接收缓冲区

手把手教你学51单片机之十八RS485通信与Modbus协议

手把手教你学51单片机之十八RS485通信与Modbus协议

在工业控制、电力通讯、智能仪表等领域,通常情况下是采用串口通信的方式进行数据交换。

最初采用的方式是 RS232 接口,由于工业现场比较复杂,各种电气设备会在环境中产生比较多的电磁干扰,会导致信号传输错误。

除此之外,RS232 接口只能实现点对点通信,不具备联网功能,最大传输距离也只能达到几十米,不能满足远距离通信要求。

而 RS485 则解决了这些问题,数据信号采用差分传输方式,可以有效的解决共模干扰问题,最大距离可以到1200 米,并且允许多个收发设备接到同一条总线上。

随着工业应用通信越来越多, 1979 年施耐德电气制定了一个用于工业现场的总线协议 Modbus 协议,现在工业中使用RS485 通信场合很多都采用 Modbus 协议,本节课我们要讲解一下 RS485 通信和Modbus 协议。

单单使用一块KST-51 开发板是不能够进行RS485 实验的,应很多同学的要求,把这节课作为扩展课程讲一下,如果要做本课相关实验,需要自行购买USB 转 485 通信模块。

18.1 RS485通信实际上在 RS485 之前 RS232 就已经诞生,但是RS232 有几处不足的地方:1、接口的信号电平值较高,达到十几V ,容易损坏接口电路的芯片,而且和TTL 电平不兼容,因此和单片机电路接起来的话必须加转换电路。

2、传输速率有局限,不可以过高,一般到几十Kb/s 就到极限了。

3、接口使用信号线和GND 与其他设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。

4、传输距离有限,最多只能通信几十米。

5、通信的时候只能两点之间进行通信,不能够实现多机联网通信。

针对 RS232 接口的不足,就不断出现了一些新的接口标准,RS485 就是其中之一,他具备以下的特点:1、我们在讲A/D 的时候,讲过差分信号输入的概念,同时也介绍了差分输入的好处,最大的优势是可以抑制共模干扰。

尤其工业现场的环境比较复杂,干扰比较多,所以通信如果采用的是差分方式,就可以有效的抑制共模干扰。

modbus协议及modbusRTU的C51单片机程序

modbus协议及modbusRTU的C51单片机程序

modbus协议及modbusRTU的C51单片机程序modbus 协议及modbus RTU 的C51 单片机程序Modbus通讯协议Modbus协议最初由Modicon公司开发出来,在1979年末该公司成为施耐德自动化(Schneider Automation)部门的一部分,现在Modbus已经是工业领域全球最流行的协议。

此协议支持传统的RS-232、RS-422、RS-485和以太网设备。

许多工业设备,包括PLC,DCS,智能仪表等都在使用Modbus协议作为他们之间的通讯标准。

有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。

当在网络上通信时,Modbus协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。

如果需要回应,控制器将生成应答并使用Modbus协议发送给询问方。

Modbus 协议包括ASCII、RTU、TCP等,并没有规定物理层。

此协议定义了控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的。

标准的Modicon控制器使用RS232C实现串行的Modbus。

Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用Maser/Slave方式,Master端发出数据请求消息,Slave端接收到正确消息后就可以发送数据到Master 端以响应请求;Master端也可以直接发消息修改Slave端的数据,实现双向读写。

Modbus 协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验,但TCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。

另外,Modbus采用主从方式定时收发数据,在实际使用中如果某Slave 站点断开后(如故障或关机),Master端可以诊断出来,而当故障修复后,网络又可自动接通。

因此,Modbus协议的可靠性较好。

51单片机_步进机_直流电机,RS485通信

51单片机_步进机_直流电机,RS485通信

本程序实现RS485多机通信步进机控制直流电机控制LED等控制开发环境keil4/***************************************************************************//******** main.c ***************//**************************************************************************/#include<reg52.h>#include<stdio.h>#include<stdlib.h>#include"CRC16.h"#define uint unsigned int#define uchar unsigned charsbit SENDMODE=P3^2; //收发控制sbit PWM=P2^4; //控制直流电机PWMsbit LED1=P2^5;sbit LED0=P2^6;uchar Rxbuf[12]; //数据接收缓冲区uchar PWMvalue=0;//控制直流电机转动速度uchar stmotorvalue=1;//控制步进机转动角度static unsigned int stmotor=0;static uchar Rxfinish=0; //接收完毕标志位void delay(uint n); //1ms延时函数static const char stepperForeward[8]={0x01,0x03,0x02,0x06,0x04,0x0c,0x08,0x09};//eigth pat Forewardstatic const char stepperRollback[8]={0x09,0x08,0x0c,0x04,0x06,0x02,0x03,0x01};//eigth pat Rollbackstatic const char stepperStop[8] ={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};//eigth pat Pausechar stCtlFlag=2;//步进机direction contorlvoid inti() //波特率设计为9600{TMOD=0x21;TL1=250;TH1=250;TR1=1;PCON=0x80;SCON=0x50;EA=1;ES=1;TI=0;RI=0;TR0=1;ET0=1;TH0=(65535-100)/256;TL0=(65535-100)%256;SENDMODE=0;//485设计为接收模式LED0=1;LED1=1;}/*******************************************//*******************************************//****************************/void transmit(uint length,uchar *ptr) //发送函数目的当小车转弯时通知430单片机记录当前的方位,开始计算走过的路程{uint x=0;TI=0;SENDMODE=1;//把P3^2口拉高开始数据通信回复终端for(x=0;x<length;x++) //逐步把数据放到发送缓冲区{SBUF=ptr[x];while(!TI); //等待数据发送完成TI=0;}SENDMODE=0; //恢复接收模式sendOK=1;//发送完毕后把发送完毕标志位置1}void bzero(int length,uchar *ptr){int x;for(x=0;x<length;x++){*ptr++='\0';}}void UART0()interrupt 4{static unsigned char Rxnum=0;static unsigned char OK=0;if(RI==1){RI=0;LED0=1;if(OK==0){if(SBUF==0xff){Rxnum=0;OK=1;}}Rxbuf[Rxnum]=SBUF;Rxnum++;if(Rxnum==Rxbuf[1]||Rxbuf[Rxnum-1]==0xfe){if(Rxbuf[0]==0xff){Rxfinish=1;//一帧接收没错ES=0;//关中断}else{Rxfinish=0;//一帧接收出错}OK=0;Rxnum=0;}if(Rxnum>12){OK=0;Rxnum=0;}}}void time0()interrupt 1{static uchar count0=0;static int count1=0;static uchar stnum=0;TH0=(65535-100)/256;TL0=(65535-100)%256;count0++;if(count0<=PWMvalue){PWM=1;}else{PWM=0;if(count0>=10){count0=0;}}count1++;if(count1==10) //步进机控制{P2&=0xf0;count1=0;if(stCtlFlag==0){P2|=stepperForeward[stnum];}else if(stCtlFlag==1){P2|=stepperRollback[stnum];}else{P2|=stepperStop[stnum] ;}stnum++;if(stnum>=8){stnum=0;if(stmotor>=stmotorvalue*60){stCtlFlag=2;}else stmotor++;}}}void macontrol(int pwm,uchar ctldr,uchar stmotor)//电机控制函数{PWMvalue=pwm;stCtlFlag=ctldr;stmotorvalue=stmotor;}void main(){static uint CRC16value=0;static uchar temp=0;inti();while(1){if(Rxfinish!=0){Rxfinish=0;if((Rxbuf[1]-4)>0){CRC16value=crc16(&Rxbuf[1],Rxbuf[1]-4);//CRC16value=crc16(&Rxbuf[1],Rxbuf[1]-4);transmit(2,&CRC16value);if((CRC16value/256==Rxbuf[Rxbuf[1]-3])&&(CRC16value%256==Rxbuf[Rxbuf[1]-2])&&Rxb uf[2]==0x01&&Rxbuf[Rxbuf[1]-1]==0xfe&&Rxbuf[0]==0xff){LED0=(bit)(Rxbuf[3]&0x01);LED1=(bit)(Rxbuf[4]&0x01);stmotor=0;macontrol(Rxbuf[7],Rxbuf[5],Rxbuf[6]);temp=Rxbuf[7];}else PWMvalue=temp;}bzero(sizeof(Rxbuf),Rxbuf);ES=1; //开中断}else PWMvalue=temp;}}/*****************************************************************************/ /*******CRC16.h *****//***************************************************************************/ #ifndef __CRC16_H#define __CRC16_Hcode unsigned char const auchCRCHi[] = {0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40/* CRC低位字节值表*/code unsigned char const auchCRCLo[] = {0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,0x43, 0x83, 0x41, 0x81, 0x80, 0x40} ;/***************************************************************************/ //计算CRC16程序/***************************************************************************/ unsigned short int crc16(unsigned char *puchMsg, unsigned short int usDataLen){unsigned char uchCRCHi = 0xFF ; /* 高CRC字节初始化*/unsigned char uchCRCLo = 0xFF ; /* 低CRC 字节初始化*/unsigned long uIndex ; /* CRC循环中的索引*/while (usDataLen--) /* 传输消息缓冲区*/{uIndex = uchCRCHi ^ *puchMsg++ ; /* 计算CRC */uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;uchCRCLo = auchCRCLo[uIndex] ;if(uchCRCHi==0xff||uchCRCHi==0xfe||uchCRCHi==0xfd||uchCRCHi==0xfc) uchCRCHi=0xfb;if(uchCRCLo==0xff||uchCRCLo==0xfe||uchCRCLo==0xfd||uchCRCLo==0xfc) uchCRCLo=0xfb;return (uchCRCHi << 8 | uchCRCLo) ;}#endif。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

在工业控制、电力通讯、智能仪表等领域,通常情况下是采用串口通信的方式进行数据交换。

最初采用的方式是RS232接口,由于工业现场比较复杂,各种电气设备会在环境中产生比较多的电磁干扰,会导致信号传输错误。

除此之外,RS232接口只能实现点对点通信,不具备联网功能,最大传输距离也只能达到几十米,不能满足远距离通信要求。

而RS485则解决了这些问题,数据信号采用差分传输方式,可以有效的解决共模干扰问题,最大距离可以到1200米,并且允许多个收发设备接到同一条总线上。

随着工业应用通信越来越多,1979年施耐德电气制定了一个用于工业现场的总线协议Modbus协议,现在工业中使用RS485通信场合很多都采用Modbus协议,本节课我们要讲解一下RS485通信和Modbus协议。

单单使用一块KST-51开发板是不能够进行RS485实验的,应很多同学的要求,把这节课作为扩展课程讲一下,如果要做本课相关实验,需要自行购买USB转485通信模块。

18.1 RS485通信实际上在RS485之前RS232就已经诞生,但是RS232有几处不足的地方:1、接口的信号电平值较高,达到十几V,容易损坏接口电路的芯片,而且和TTL电平不兼容,因此和单片机电路接起来的话必须加转换电路。

2、传输速率有局限,不可以过高,一般到几十Kb/s就到极限了。

3、接口使用信号线和GND与其他设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。

4、传输距离有限,最多只能通信几十米。

5、通信的时候只能两点之间进行通信,不能够实现多机联网通信。

针对RS232接口的不足,就不断出现了一些新的接口标准,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是差分通信,因此接收数据和发送数据是不能同时进行的,也就是说它是一种半双工通信。

那我们如何判断什么时候发送,什么时候接收呢?RS485类的芯片很多,这节课我们以MAX485为例讲解RS485通信,如图18-1所示。

图18-1 MAX485硬件接口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都可以。

在这里我们还要介绍一下如何使用KST-51单片机开发板进行外围扩展实验。

我们的开发板只能把基本的功能给同学们做出来提供实验练习,但是同学们学习的脚步不应该停留在这个实验板上。

如果想进行更多的实验,就可以通过单片机开发板的扩展接口进行扩展实验。

大家可以看到蓝绿色的单片机座周围有32个插针,这32个插针就是把单片机的32个IO引脚全部都引出来了。

在原理图上体现出来的就是我们的J4、J5、J6、J7这4个器件,如图18-2所示。

图18-2 单片机扩展接口这32个IO口不是所有的IO口都可以用来对外扩展,其中既作为数据输出,又可以作为数据输入的引脚是不可以用的,比如P3.2、P3.4、P3.6引脚,这三个引脚是不可用的。

比如P3.2这个引脚,如果我们用来扩展,发送的信号如果和DS18B20的时序吻合,会导致DS18B20拉低引脚,影响通信。

除这3个IO口以外的其他29个IO口,都可以使用杜邦线接上插针,扩展出来使用。

当然了,如果把当前的IO口应用于扩展功能了,板子上的相应的功能就实现不了了,也就是说需要扩展功能和板载功能二选一。

在进行RS485实验中,我们通信用的引脚必须是P3.0和P3.1,此外还有一个方向控制引脚,我们使用杜邦线将其连接到P1.7上去。

RS485的另外一端,大家可以使用一个USB 转485模块,用双绞线把开发板和模块上的A和B分别对应连起来,USB那头插入电脑,然后就可以进行通信了。

学习了第13章的实用串口通信的方法和程序后,做这种串口通信的方法就很简单了,基本是一致的。

我们使用实用串口通信的思路,做了一个简单的程序,通过串口调试助手下发任意个字符,单片机接收到后在末尾添加“回车+换行”符后再送回,在调试助手上重新显示出来,先把程序贴出来。

程序中需要注意的一点是:因为平常都是将485设置为接收状态,只有在发送数据的时候才将485改为发送状态,所以在UartWrite()函数开头将485方向引脚拉高,函数退出前再拉低。

但是这里有一个细节,就是单片机的发送和接收中断产生的时刻都是在停止位的一半上,也就是说每当停止位传送了一半的时候,RI或TI就已经置位并且马上进入中断(如果中断使能的话)函数了,接收的时候自然不会存在问题,但发送的时候就不一样了:当紧接这向SBUF写入一个字节数据时,UART硬件会在完成上一个停止位的发送后,再开始新字节的发送,但如果此时不是继续发送下一个字节,而是已经发送完毕了,要停止发送并将485方向引脚拉低以使485重新处于接收状态时就有问题了,因为这时候最后的这个停止位实际只发送了一半,还没有完全完成,所以就有了UartWrite()函数内DelayX10us(5)这个操作,这是人为的增加了延时50us,这50us的时间正好让剩下的一半停止位完成,那么这个时间自然就是由通信波特率决定的了,为波特率周期的一半。

/***********************RS485.c文件程序源代码*************************/#include <reg52.h>#include <intrins.h>sbit RS485_DIR = P1^7; //RS485方向选择引脚bit flagOnceTxd = 0; //单次发送完成标志,即发送完一个字节bit cmdArrived = 0; //命令到达标志,即接收到上位机下发的命令unsigned char cntRxd = 0;unsigned char pdata bufRxd[40]; //串口接收缓冲区void ConfigUART(unsigned int baud) //串口配置函数,baud为波特率{RS485_DIR = 0; //RS485设置为接收方向SCON = 0x50; //配置串口为模式1TMOD &= 0x0F; //清零T1的控制位TMOD |= 0x20; //配置T1为模式2TH1 = 256 - (11059200/12/32) / baud; //计算T1重载值TL1 = TH1; //初值等于重载值ET1 = 0; //禁止T1中断ES = 1; //使能串口中断TR1 = 1; //启动T1}unsigned char UartRead(unsigned char *buf, unsigned char len) //串口数据读取函数,数据接收指针buf,读取数据长度len,返回值为实际读取到的数据长度{unsigned char i;if (len > cntRxd) //读取长度大于接收到的数据长度时,{len = cntRxd; //读取长度设置为实际接收到的数据长度}for (i=0; i<len; i++) //拷贝接收到的数据{*buf = bufRxd[ i];buf++;}cntRxd = 0; //清零接收计数器return len; //返回实际读取长度}void DelayX10us(unsigned char t) //软件延时函数,延时时间(t*10)us{do {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();} while (--t);}void UartWrite(unsigned char *buf, unsigned char len) //串口数据写入函数,即串口发送函数,待发送数据指针buf,数据长度len{RS485_DIR = 1; //RS485设置为发送while (len--) //发送数据{flagOnceTxd = 0;SBUF = *buf;buf++;while (!flagOnceTxd);}DelayX10us(5); //等待最后的停止位完成,延时时间由波特率决定RS485_DIR = 0; //RS485设置为接收}void UartDriver() //串口驱动函数,检测接收到的命令并执行相应动作{unsigned char len;unsigned char buf[30];if (cmdArrived) //有命令到达时,读取处理该命令{cmdArrived = 0;len = UartRead(buf, sizeof(buf)-2); //将接收到的命令读取到缓冲区中buf[len++] = '\r'; //在接收到的数据帧后添加换车换行符后发回buf[len++] = '\n';UartWrite(buf, len);}}void UartRxMonitor(unsigned char ms) //串口接收监控函数{static unsigned char cntbkp = 0;static unsigned char idletmr = 0;if (cntRxd > 0) //接收计数器大于零时,监控总线空闲时间{if (cntbkp != cntRxd) //接收计数器改变,即刚接收到数据时,清零空闲计时 {cntbkp = cntRxd;idletmr = 0;}else{if (idletmr < 30) //接收计数器未改变,即总线空闲时,累积空闲时间{idletmr += ms;if (idletmr >= 30) //空闲时间超过30ms即认为一帧命令接收完毕{cmdArrived = 1; //设置命令到达标志}}}}else{cntbkp = 0;}}void InterruptUART() interrupt 4 //UART中断服务函数{if (RI) //接收到字节{RI = 0; //手动清零接收中断标志位if (cntRxd < sizeof(bufRxd)) //接收缓冲区尚未用完时,{bufRxd[cntRxd++] = SBUF; //保存接收字节,并递增计数器 }}if (TI) //字节发送完毕{TI = 0; //手动清零发送中断标志位flagOnceTxd = 1; //设置单次发送完成标志}}/***********************main.c文件程序源代码*************************/ #include <reg52.h>unsigned char T0RH = 0; //T0重载值的高字节unsigned char T0RL = 0; //T0重载值的低字节void ConfigTimer0(unsigned int ms);extern void ConfigUART(unsigned int baud);extern void UartRxMonitor(unsigned char ms);extern void UartDriver();void main (){EA = 1; //开总中断ConfigTimer0(1); //配置T0定时1msConfigUART(9600); //配置波特率为9600while(1){UartDriver();}}void ConfigTimer0(unsigned int ms) //T0配置函数{unsigned long tmp;tmp = 11059200 / 12; //定时器计数频率tmp = (tmp * ms) / 1000; //计算所需的计数值tmp = 65536 - tmp; //计算定时器重载值tmp = tmp + 34; //修正中断响应延时造成的误差T0RH = (unsigned char)(tmp >> 8); //定时器重载值拆分为高低字节T0RL = (unsigned char)tmp;TMOD &= 0xF0; //清零T0的控制位TMOD |= 0x01; //配置T0为模式1TH0 = T0RH; //加载T0重载值TL0 = T0RL;ET0 = 1; //使能T0中断TR0 = 1; //启动T0}void InterruptTimer0() interrupt 1 //T0中断服务函数{TH0 = T0RH; //定时器重新加载重载值TL0 = T0RL;UartRxMonitor(1); //串口接收监控}现在看这种串口程序,是不是感觉很简单了呢?串口通信程序我们反反复复的使用,加上随着我们学习的模块越来越多,实践的越来越多,原先感觉很复杂的东西,现在就会感到简单了。

相关文档
最新文档