485通信实现两个单片机usb通讯有线通讯具体怎么操作

485通讯的时候支持多个单片机同时与电脑之间进行通讯_单片机吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:141,730贴子:
485通讯的时候支持多个单片机同时与电脑之间进行通讯收藏
之前做了多个模块都是用RS232 与电脑通讯的
所以电脑上插了很多串口线
可以实现电脑同时接收到单片机发送过来的信息
如果用485通讯的话
该如何??
之前也没有接触过485
LABVIEW主要以自动化测试软件开发/非标自动化测试软件开发为主,替代组态软件,实现功能最大化,满足测试测量/生产监控/数据存储等功能.
232 也可以连一起用一个串口!加工通讯协议就OK
不要什么硬件, 所有TX接一起,所有RX接一起,单片机和电脑都 加通讯协议,都是软件的事!
多机通讯只能用485,每个分机设一个地址,主机叫到谁,谁回答。
可以在电脑和多个单片机间建立一个中转站,如设立一个主控单片机。电脑发送指令给主控单片机,需求哪个单片机的数据,然后主控单片机会与哪个单片机通讯,之后把接收的数据返回到电脑就行了。若是主控单片机只有一个RX和TX,可以采用传输门,将除通讯单片机以外所有单片机串口通道切断就可以了。
登录百度帐号推荐应用查看: 4041|回复: 11
单片机通过RS485与PLC通信,采用modbus协议
主题帖子精华
初级会员, 积分 60, 距离下一级还需 140 积分
在线时间0 小时
大家好,我是第一次做 单片机(stc89c52)通过RS485与PLC通信,采用modbus协议;结果是通信不上,(呵呵,意料之中的),然后我就检查程序,反复研究了好几天都没个结果,我利用单片机闲置的一个管脚置高或低,来判断出现单片机程序执行的情况,结果发现单片机反复复位,如果不接485的A/B端,单片机不复位(我是这样判断复位的:在main()函数和while(1)之间加了单片机的闲置脚,1s高电平,1s低电平)。硬件连接比较简单,单片机通过485的A/B端连接到RS485/RS422,RS422连接但PLC.请大家帮着看看,是什么问题引起的,谢谢了啊。
#include &AT89X52.H&
#include &intrins.h&
#define Uint16 unsigned int
#define Uint32 unsigned long
#define uchar& unsigned char
unsigned char temp8;
Uint16 temp16;
sbit led2=P1^3;
sbit led1=P1^4;
sbit reving=P1^6;
sbit rw=P1^0;&&&&&& //487收发控制端
sbit SCL2=P1^1;&&//SCL2定义为P1口的第3位脚,连接ADC0831SCL脚
sbit SDA2=P1^2;&&//SDA2定义为P1口的第4位脚,连接ADC0831SDA脚
sbit CS2=P1^0;&&//CS2定义为P1口的第4位脚,连接ADC0831CS脚
void InitUART(void);&&&&&&&&&&& //串口初始化
void SendOneByte(unsigned);&&&& //串口发送数据
void mdproc(uchar);&&&&&&&&&&&& //接收数据处理
unsigned char ad0831read(void);&//定义该函数为读取模数转换的数据
Uint16 Crc16(Uint16 *puchMsg, Uint16 usDataLen); //crc校验
uchar mtx[11],mrx[10];& //从机接收数据存放
uchar send101[9]={0x00,0x03,0x06,0x00,0x00,0x00,0x00,0x00,0x00}; //从机待发数据存放
& //时间间隔
// modbus协议 定义 :
/* CRC 高位字节值表 */
const Uint16 code 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低位字节值表*/
const Uint16 code 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
Uint16 Crc16(uchar *puchMsg, Uint16 usDataLen) //获取crc高8位和低8位
&Uint16 uchCRCHi = 0xFF ;&&&&&&&&&&&&& /* 高CRC字节初始化& */
&Uint16 uchCRCLo = 0xFF ;&&&&&&&&&&&&& /* 低CRC 字节初始化 */
&Uint16 temp16;
&Uint32 uI&&&&&&&&&&&&&&&&&&&&& /* CRC循环中的索引& */
&while (usDataLen--)&&&&&&&&&&&&&&&&& /* 传输消息缓冲区&& */
&&temp16=*puchMsg++;
&&uIndex = uchCRCHi ^ temp16 ; /* 计算CRC&&&&&&&&& */
&&uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;
&&uchCRCLo = auchCRCLo[uIndex] ;
&return (uchCRCHi && 8 | uchCRCLo) ;
void UARTInterrupt(void) interrupt 4& using 2&&& //串口中断处理函数
&&& if(RI==1)
&&&&&&& RI = 0;
&&&&&&& byterev=SBUF;
&&mdproc(byterev);
&&&&&&& TI = 0;
void mdproc(uchar b)& //功能码03处理 ,如果地址正确 ,接收报文并crc检查 , crc检查正确 ,向plc主机发送数据
{&Uint16 temp16_1,temp16_2;
&&if(b==0x12)
&&&datalen=7;
&&&mrx[0]=b;
&&&revptr=1;
&{&mrx[revptr++]=b;
&&if(revptr==8)
&&&temp16_1=Crc16(mrx,6);
&&&temp16_2=mrx[6];
&&&temp16_2&&=8;
&&&temp16_2|=mrx[7];
&&&if(temp16_1==temp16_2)
&&&{&crcok=1;
&&&&&& led2=0;
void SendOneByte(unsigned char c)
&&& SBUF =
&&& while(!TI);
&&& TI = 0;
void InitUART(void)//串口初始化 9.6kb/S
&&& TMOD = 0x20; //T1方式2 (8位T/C,TL用于计数,当TL溢出时将TH中的值自动写入TL)
&&& SCON = 0x50;//串口工作方式1,允许串口接收
&&& TH1 = 256 - (/32)/9600;&//定时器初值高8位设置
&TL1 = TH1;&//定时器初值低8位设置
&&& PCON = 0x00; //频率不加倍
& ET1 = 0;
&&&&&&& EA = 1;//开总中断
&&& ES = 1;//开串口中断
&&& TR1 = 1;//启动T1
void DelayM(unsigned int a){&&&&&&& //延时函数 1MS/次&
&&while( --a != 0){&&
&&&for(i = 0; i & 125; i++);
&&}&& &&&&&&&&&&&&&&&&
unsigned char ad0831read(void)//AD转换值
&unsigned char i=0,tmp=0;
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&_nop_();
&&for(i=0;i&8;i++){
&&&tmp&&=1;
&&&if(SDA2)
&&&&tmp++;&&&
&&&SCL2=1;
&&&_nop_();
&&&_nop_();
&&&SCL2=0;
&&&_nop_();
&&&_nop_();
void main(void)
&&& InitUART();
&nodeok=0;
&timercnt=0x00;
&datalen=0;
&send101[0]=0x12;& //从机地址
&&& DelayM(1000);
&&& led2=1;
&&& DelayM(1000);
&rw=0;& //487收发控制端
&&if(crcok==1)& //接收数据处理时间完毕
&&{&reving=1;
&&&&&&&&&&& led1=1;
&&&send101[3]=0x01;
&&&send101[4]=0x02;
&&send101[5]=ad0831read();& //获取 ADC 数据
&&&for(i=0;i&9;i++){mtx=send101;}
&&&temp16=Crc16(send101,9);
&&&mtx[9]=(temp16&&8)&0x00
&&&mtx[10]=temp16&0x00
&&&rw=1;&&&&&&& //487允许发送
&&&for(i=0;i&11;i++)
&&&{&temp8=mtx;
&&&&SendOneByte(temp8);
&&&crcok=0;
&&&rw=0;&&&&&& //487允许接收
&&&reving=0; //& 收到报文并回复时,点亮led
主题帖子精华
高级会员, 积分 643, 距离下一级还需 357 积分
在线时间27 小时
这个不好定位,先确定你的硬件有没有问题,然后再确定软件的问题
主题帖子精华
初级会员, 积分 60, 距离下一级还需 140 积分
在线时间0 小时
回复【2楼】三叶草:
---------------------------------
恩,硬件是我之前用单片机做的一个产品,用485通信正常的。可能是软件的问题,小老弟我查不出来啊。
主题帖子精华
新手上路, 积分 38, 距离下一级还需 12 积分
在线时间0 小时
我去,这是modbus啊!!!
主题帖子精华
初级会员, 积分 60, 距离下一级还需 140 积分
在线时间0 小时
恩,咋啦兄弟,请指教。
主题帖子精华
新手上路, 积分 38, 距离下一级还需 12 积分
在线时间0 小时
先找个nodbus精灵,先把modbus调通吧,
主题帖子精华
初级会员, 积分 60, 距离下一级还需 140 积分
在线时间0 小时
回复【6楼】张才俊:
---------------------------------
哦,modbus只是一种协议,现在是通讯有问题,不是通讯的格式有问题。
主题帖子精华
新手上路, 积分 38, 距离下一级还需 12 积分
在线时间0 小时
也就是收一帧数据,校验,正确则回应就是啦,注意以下超时,这算是最简单的协议了
主题帖子精华
初级会员, 积分 60, 距离下一级还需 140 积分
在线时间0 小时
回复【8楼】张才俊:
---------------------------------
那哥哥能不能帮我看看,我的程序有啥不对的,或者发一份参考的程序啊
主题帖子精华
新手上路, 积分 38, 距离下一级还需 12 积分
在线时间0 小时
我也有几年没看modbus了,也懒得看代码,你先看一下是否接收到完整的一帧数据,在if(temp16_1==temp16_2){&crcok=1;led2=0;}那里看一下,程序是否运行到那里去了,没有在继续往前查,运行到了说明接收没问题,然后再看发送就不会有问题了,不过我建议你程序重新写一下,你那写的代码实在是有点乱,还有,modbus在网上随便都能找一大堆
主题帖子精华
初级会员, 积分 60, 距离下一级还需 140 积分
在线时间0 小时
回复【10楼】张才俊:
---------------------------------
好的,谢谢您。
主题帖子精华
初级会员, 积分 83, 距离下一级还需 117 积分
在线时间3 小时
学习了!谢谢楼主!
做自己喜欢做的的事,爱自己所爱的人!
Powered by51单片机串口2的RS485通信调试总结 - CSDN博客
51单片机串口2的RS485通信调试总结
过完年,一到公司主管就催我赶紧把这个项目的PC和单片机的RS485通信给调通。这几天,一直在实验室度过的。开始我从单片机简单发送一串数据,用串口调试助手测试。上位机根本没有接收到数据,用示波器测了发送管脚和接收管脚都没有波形,查看了下波特率也是对的。所以初步断定是否硬件电路有问题,检查了ADM2483的DE端初始化时是低电平,我在程序里把这个位置为高电平,再用万用表测量还是低电平。原来DE和RE和地线是连到一块的,问其他同事,原来他们前面把这个地方拉低为了让485模块一直处于接收状态,测试测试能否接收到上位机数据。把这个地方改过来后,继续测试。还是用简单的串口收发数据测试通信,上位机发送数据,下位机接收到数据后紧接着把接收到的数据发给上位机。测试的时候,发送55和00是正确的,但是其他数据都发生了改变。后面用示波器测试单片机TXD2和RXD2的波形,发现下位机发出的数据在RXD2上显示波形,初步断定是否设计原理图的时候这两个引脚是否反了,把电路板割线,重新跳线调换两个引脚后再测试下位机往上位机发送数据,上位机接收正常。紧接着,发送和接收一起测,发送和接收都正常了。调了几天,原来电路板出问题了,可主管一来就说硬件是没有问题的,让我检查软件。哎,干软件的就是弱势群体,特别在我们公司真是这样。所以搞嵌入式开发的最好要软硬件都懂。最后,我把我以前写的程序下载到板子上,发现仍然和上位机不能通信。最后我想是不是在接收中断函数里通信协议解析不对,通过查阅资,改用另一种方式解析,结果可以通过上位机控制下位机输出显示了。最后把自己些调试用的程序附上:
简单的串口2收发程序 #include &reg52.h&
/*******************宏定义***********************/
#define uchar unsigned char
#define uint unsigned int
/*****************串口2特殊功能的声明*************/
sfr AUXR=0x8e;
// 辅助寄存器,用于设定串口2工作波特率、独立波特率发生器的选择
sfr S2CON=0x9a; //串口2串行控制器
sfr S2BUF=0x9b; //串口2数据缓冲寄存器
sfr BRT=0x9c;
//独立波特率产生器,重装数
sfr IE2=0 //串口2中断寄存器
sfr AUXR1=0xa2;// 辅助寄存器1,用于切换端口。以后用,切记!
#define S2RI
0x01 // 串口2接收标志位
#define S2TI
0x02 // 串口2发送标志位
sfr WDT_CONTR = 0xc1;//看门狗
/*******************位定义声明*****************/
sbit EN1=P3^3;//485通信使能端
bit flag = 0;
/********************数据存储、数据缓冲器声明*************/
/**************************************************************/
//++++++++++++++++++++++++++++++++++++++++//
//名称:delay_ms
//作用:延时time毫秒
//说明:延时函数
//输入参数:time
//输出参数:
//++++++++++++++++++++++++++++++++++++++++//
void delay_ms(unsigned int time)
unsigned int i,j;
for(i=i&0;i--)
for(j=112;j&0;j--)
void UartInit(void)
//9600bps@11.0592MHz
AUXR &= 0xf7;
//波特率不倍速
S2CON = 0x50;
//8位数据,可变波特率
BRT = 0xFD;
//设定独立波特率发生器重装值
//独立波特率发生器时钟为Fosc/12,即12T
AUXR |= 0x10;
//启动独立波特率发生器
void send(uint ch)
S2CON&=~S2TI;
while(!(S2CON&S2TI));
S2CON &=~S2TI;
void main()
UartInit();
send(0x55);
delay_ms(1);
send(0xaa);
delay_ms(1);
send(0xff);
delay_ms(1);
void Uart2() interrupt 8
if(S2CON&S2RI)
S2CON&=~S2RI;
delay_ms(1);
num=S2BUF;
send(num);
}通信协议解析部分程序
void Data_analysis(void)
static unsigned int check_sum = 0;
//存放校验和
static unsigned char lencnt = 0;
//数据长度计数器
switch (state_flag)
if(recdata == 0xaa)
// 是否帧头第一个数据
state_flag = 1;
state_flag = 0;
// 标志复位
if(recdata == 0x55)
//是否帧头第二个数据
state_flag = 2;
state_flag = 0;
// 标志复位
if(recdata == addr)
//判断目的地址是否正确
state_flag = 3;
check_sum =
state_flag = 0;
// 标志复位
state_flag = 4;
check_sum ^=
//校验和累加
lencnt = 0;
//数据长度计数器清零
data_count =
//存储数据长度
check_sum ^=
if(data_count != 0)
//后面有数据码
state_flag = 5;
state_flag = 7;
dat[lencnt++] =
//存储数据码
check_sum ^=
if(lencnt == data_count)
state_flag = 7;
lencnt = 0;
state_flag = 6;
if(check_sum == recdata)
//数据校验,判断累加和是否正确
state_flag = 8;
retval = 1;
//置错误标志,数据传输不正确
state_flag=0;
check_sum=0;//累加和清0
if (recdata==0x0d)
retval = 2;
//置接收一帧数据成功标志
state_flag=0;
state_flag=0;
if(timeOutFlag == 1) //超时判断
timeOutFlag = 0;
state_flag=0;
retval = 1;
//置错误标志,数据传输不正确
recdata = 0x00;
本文已收录于以下专栏:
相关文章推荐
这部分本该放到 linux 下才讲的,但是讲到 select 就不得不提到了串口通信。参看:UNIX再学习 -- 函数 select、poll、epoll 那也简单了直接将之前的写好的文章,加以总结吧...
1、物理上的连线
尽量是用双绞线
RS485一种比较常用的现场总线
RS232 标准是诞生于 RS485 之前的,但是 RS232 有几处不足的地方:
接口的信号电平值较高,达到十几 V,使用不当容易损坏接口芯片,电平标准也与TTL 电平不兼容。传输速率有局限,不可...
适合短距离通信,并行通信控制简单、相对传输速度快(8位一起传输)。
只能一位一位的传送。
同步(了解)
建立发送方时钟对接收方时钟的直接控制,使双方达到完全同步。此时...
这里分享一下我在调试程序时常用的方法(一):串口调试
(这部分代码只是作为调试的一种手段,只在需要测试的地方和不影响MCU和控制芯片通信的地方在使用,不可随意使用,随意使用可能会因为串口传输数据而错...
RS485主从式多机通讯协议一、数据传输协议此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样...
0. 前向声明
sizeof,一个其貌不扬的家伙,引无数菜鸟竟折腰,小虾我当初也没少犯迷糊,秉着“辛苦我一个,幸福千万人”的伟大思想,我决定将其尽可能详细的总结一下。
但当我总结的时候才发...
他的最新文章
讲师:王禹华
讲师:宋宝华
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)RS485通信和Modbus协议
查看: 44988|
摘要:   在工业控制、电力通讯、智能仪表等领域,通常情况下是采用串口通信的方式进行数据交换。最初采用的方式是RS232接口,由于工业现场比较复杂,各种电气设备会在环境中产生比较多的电磁干扰,会导致信号传输错误。 ...
  在工业控制、通讯、智能仪表等领域,通常情况下是采用串口通信的方式进行数据交换。最初采用的方式是RS232接口,由于工业现场比较复杂,各种设备会在环境中产生比较多的电磁干扰,会导致信号传输错误。除此之外,RS232接口只能实现点对点通信,不具备联网功能,最大传输距离也只能达到几十米,不能满足远距离通信要求。而RS485则解决了这些问题,数据信号采用差分传输方式,可以有效的解决共模干扰问题,最大距离可以到1200米,并且允许多个收发设备接到同一条总线上。随着工业应用通信越来越多,1979年施耐德电气制定了一个用于工业现场的总线协议Modbus协议,现在工业中使用RS485通信场合很多都采用Modbus协议,本节课我们要讲解一下RS485通信和Modbus协议。
  单单使用一块KST-51开发板是不能够进行RS485实验的,应很多同学的要求,把这节课作为扩展课程讲一下,如果要做本课相关实验,需要自行购买USB转485通信模块。
  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通信,如图1所示。
图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个器件,如图2所示。
图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;&&&//配置串口为模式1
&&&&TMOD&&=&0x0F;&&//清零T1的控制位
&&&&TMOD&|=&0x20;&&//配置T1为模式2
&&&&TH1&=&256&-&(/32)&/&&&//计算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&=&cntR&//读取长度设置为实际接收到的数据长度
&&&&for&(i=0;&i&&i++)&//拷贝接收到的数据
&&&&&&&&*buf&=&bufRxd[i];
&&&&&&&&buf++;
&&&&cntRxd&=&0;&&//清零接收计数器
&&&&return&&&//返回实际读取长度
void&DelayX10us(unsigned&char&t)&&//软件延时函数,延时时间(t*10)us
&&&&&&&&_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++;
&&&&&&&&while&(!flagOnceTxd);
&&&&DelayX10us(5);&&//等待最后的停止位完成,延时时间由波特率决定
&&&&RS485_DIR&=&0;&&//RS485设置为接收
void&UartDriver()&//串口驱动函数,检测接收到的命令并执行相应动作
&&&&unsigned&char&
&&&&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&=&cntR
&&&&&&&&&&&&idletmr&=&0;
&&&&&&&&else
&&&&&&&&&&&&if&(idletmr&&&30)&&//接收计数器未改变,即总线空闲时,累积空闲时间
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&idletmr&+=&
&&&&&&&&&&&&&&&&if&(idletmr&&=&30)&&//空闲时间超过30ms即认为一帧命令接收完毕
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&cmdArrived&=&1;&//设置命令到达标志
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&&&&&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定时1ms
&&&&ConfigUART(9600);&//配置波特率为9600
&&&&while(1)
&&&&&&&&UartDriver();
void&ConfigTimer0(unsigned&int&ms)&&//T0配置函数
&&&&unsigned&long&
&&&&tmp&=&&/&12;&&&&&&//定时器计数频率
&&&&tmp&=&(tmp&*&ms)&/&1000;&&//计算所需的计数值
&&&&tmp&=&65536&-&&&&&&&&&//计算定时器重载值
&&&&tmp&=&tmp&+&34;&&&&&&&&&&&//修正中断响应延时造成的误差
&&&&T0RH&=&(unsigned&char)(tmp&&&&8);&&//定时器重载值拆分为高低字节
&&&&T0RL&=&(unsigned&char)
&&&&TMOD&&=&0xF0;&&&//清零T0的控制位
&&&&TMOD&|=&0x01;&&&//配置T0为模式1
&&&&TH0&=&T0RH;&&&&&//加载T0重载值
&&&&TL0&=&T0RL;
&&&&ET0&=&1;&&&&&&&&//使能T0中断
&&&&TR0&=&1;&&&&&&&&//启动T0
void&InterruptTimer0()&interrupt&1&&//T0中断服务函数
&&&&TH0&=&T0RH;&&//定时器重新加载重载值
&&&&TL0&=&T0RL;
&&&&UartRxMonitor(1);&&//串口接收监控
   现在看这种串口程序,是不是感觉很简单了呢?串口通信程序我们反反复复的使用,加上随着我们学习的模块越来越多,实践的越来越多,原先感觉很复杂的东西,现在就会感到简单了。我们的下载程序模块用的是COM4,而USB转485虚拟的是COM5,通信的时候我们用的是COM5口,如图3所示。
图3&RS485串行通信
  2、Modbus通信协议介绍
  &我们前边学习UART、I2C、SPI这些通信协议,都是最底层的协议,是“位”级别的协议。而我们在学习13章实用串口通信程序的时候,我们通过串口发给单片机三条指令,让单片机做了三件不同的事情,分别是"buzz&on"、"buzz&off"、和"showstr"。随着我们系统复杂性的增加,我们希望可以实现更多的指令。而指令越来越多,带来的后果就是非常杂乱无章,尤其是这个人喜欢写成"buzz&on"、"buzz&off",而另外一个人喜欢写成"on&buzz"、"off&buzz"。导致不同开发人员写出来的代码指令不兼容,不同厂家的产品不能挂到一条总线上通信。
  随着这种矛盾的日益严重,就会有聪明人提出更合理的解决方案,提出一些标准来,今后我们的编程必须按照这个标准来,这种标准也是一种通信协议,但是和UART、I2C、SPI通信协议不同的是,这种通信协议是字节级别的,叫做应用层通信协议。在1979年由Modicon(现为施耐德电气公司的一个品牌)提出了全球第一个真正用于工业的协议,就是Modbus协议。
  2.1&Modbus协议特点
  Modbus协议是应用于控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其他设备之间可以通信,已经成为一种工业标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。这种协议定义了一种控制器能够认识使用的数据结构,而不管它们是经过何种网络进行通信的。它描述了控制器请求访问其他设备的过程,如何回应来自其他设备的请求,以及怎样侦测错误记录,它制定了通信数据的格局和内容的公共格式。
  在进行多机通信的时候,Modbus协议规定每个控制器必须要知道他们的设备地址,识别按照地址发送过来的数据,决定是否要产生动作,产生何种动作,如果要回应,控制器将生成的反馈信息用Modbus协议发出。
  Modbus协议允许在各种网络体系结构内进行简单通信,每种设备(、、控制面板、驱动程序、输入输出设备)都能使用Modbus协议来启动远程操作,一些网关允许在几种使用Modbus协议的总线或网络之间的通信,如图4所示。
图4&Modbus网络体系结构实例
  Modbus协议的整体架构和格式比较复杂和庞大,在我们的课程里,我们重点介绍数据帧结构和控制方式,作为一个入门级别的了解。如果大家要详细了解,或者使用Modbus开发相关设备,可以查阅相关的国标文件再进行深入学习。
  2.2&RTU协议帧数据
  Modbus有两种通信传输方式,一种是ASCII模式,一种是RTU模式。由于ASCII模式的数据字节是7bit数据位,51单片机无法实现,而且应用也相对较少,所以这里我们只用RTU模式。两种模式相似,会用一种另外一种也就会了。一条典型的RTU数据帧如图5所示。
图5&RTU数据帧
  和我们实用串口通信程序类似,我们一次发送的数据帧必须是作为一个连续的数据流进行传输。我们在实用串口通信程序中采用的方法是定义30ms,如果接收到的数据超过了30ms还没有接收到下一个字节,我们就认为这次的数据结束。而Modbus的RTU模式规定不同数据帧之间的间隔是3.5个字节通信时间以上。如果在一帧数据完成之前有超过3.5个字节时间的停顿,接收设备将刷新当前的消息并假定下一个字节是一个新的数据帧的开始。(/版权所有)同样的,如果一个新消息在小于3.5个字节时间内接着前边一个数据开始的,接收的设备将会认为它是前一帧数据的延续。这将会导致一个错误,因此大家看RTU数据帧最后还有16bit的CRC校验。
  起始位和结束符:图18-5上代表的是一个数据帧,前后都至少有3.5个字节的时间间隔,起始位和结束符实际上没有任何数据,T1-T2-T3-T4代表的是时间间隔3.5个字节以上的时间,而真正有意义的第一个字节是设备地址。
  设备地址:很多同学不理解,在多机通信的时候,数据那么多,我们依靠什么判断这个数据帧是哪个设备的呢?没错,就是依靠这个设备地址字节。每个设备都有一个自己的地址,当设备接收到一帧数据后,程序首先对设备地址字节进行判断比较,如果与自己的地址不同,则对这帧数据直接不予理会,如果如果与自己的地址相同,就要对这帧数据进行解析,按照之后的功能码执行相应的功能。如果地址是0x00,则认为是一个广播命令,就是所有的从机设备都要执行的指令。
  功能代码:在第二个字节功能代码字节中,Modbus规定了部分功能代码,此外也保留了一部分功能代码作为备用或者用户自定义,这些功能码大家不需要去记忆,甚至都不用去看,直到你有用到的那天再过来查这个表格即可,如表1所示。
  表1&Modbus功能码
读取线圈状态
取得一组逻辑线圈的当前状态(ON/OFF)
读取输入状态
取得一组开关输入的当前状态(ON/OFF)
读取保持寄存器&
在一个或多个保持寄存器中取得当前的二进制值
读取输入寄存器
在一个或多个输入寄存器中取得当前的二进制值
强置单线圈
强置一个逻辑线圈的通断状态
预置单寄存器
把具体二进值装入一个保持寄存器&
读取异常状态
取得8&个内部线圈的通断状态,这&8&个线圈的地址由控制器决定,用户逻辑可以将这些线圈定义,以说明从机状态,短报文适宜于迅速读取状态&
回送诊断校验
把诊断校验报文送从机,以对通信处理进行评鉴
编程(只用于484)
使主机模拟编程器作用,修改PC从机逻辑
控询(只用于484)
可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码&9&的报文发送后,本功能码才发送&
读取事件计数
可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时&
读取通信事件记录
可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误
编程(184/384&484&584&)
可使主机模拟编程器功能修改PC从机逻辑&
探询(184/384&484&584)
可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能13的报文发送后,本功能码才得发送
强置多线圈
强置一串连续逻辑线圈的通断
预置多寄存器
把具体的二进制值装入一串连续的保持寄存器
报告从机标识
可使主机判断编址从机的类型及该从机运行指示灯的状态
884&和MICRO&84
可使主机模拟编程功能,修改PC状态逻辑
重置通信链路
发生非可修改错误后,是从机复位于已知状态,可重置顺序字节&
读取通用参数(584L)
显示扩展存储器文件中的数据信息
写入通用参数(584L)
把通用参数写入扩展存储文件,或修改
保留作扩展功能备用
保留以备用户功能所用
留作用户功能的扩展编码&
留作内部作用
用于异常应答
  我们程序对功能码的处理,就是程序来检测这个字节的数值,然后根据其数值来做相应的功能处理。
  数据:跟在功能代码后边的是n个8bit的数据。这个n值的到底是多少,是功能代码来确定的,不同的功能代码后边跟的数据数量不同。举个例子,如果功能码是0x03,也就是读保持寄存器,那么主机发送数据n的组成部分就是:2个字节的寄存器起始地址,加2个字节的寄存器数量N*。从机数据n的组成部分是:1个字节的字节数,因为我们回复的寄存器的值是2个字节,所以这个字节数也就是2N*个,再加上2N*个寄存器的值,如图6所示。
图6&读保持寄存器数据结构
  CRC校验:CRC校验是一种数据算法,是用来校验数据对错的。CRC校验函数把一帧数据除最后两个字节外,前边所有的字节进行特定的算法计算,计算完后生成了一个16bit的数据,作为CRC校验码,添加在一帧数据的最后。接收方接收到数据后,同样会把前边的字节进行CRC计算,计算完了再和发过来的CRC的16bit的数据进行比较,如果相同则认为数据正常,没有出错,如果比较不相同,则说明数据在传输中发生了错误,这帧数据将被丢弃,就像没收到一样,而发送方会在得不到回应后做相应的处理错误处理。
  RTU模式的每个字节的位是这样分布的:1个起始位、8个数据位,最小有效位先发送、1个奇偶校验位(如果无校验则没有这一位)、1位停止位(有校验位时)或者2个停止位(无校验位时)。
上一篇:下一篇:
Powered by &
这里是—这里可以学习 —这里是。
栏目导航:

我要回帖

更多关于 单片机串口通讯 的文章

 

随机推荐