Proteus仿真二,通过51单片机来实现虚拟串口通信,基于MODBUS_RTU协议(1)
这一章,主要介绍的是实现一帧报文的接收内容,下一章将将讲解功能码的实现问题。
本文要用到的软件需要MODBUS三套工具:
首先搭建电路图:
进行配置,首先是51单片机的晶振要选用11.0592MHz,因为这样波特率才是精准的。
然后就是虚拟串口对接通过 “virtual serial port driver”来进行配置
连接COM1 和 COM2。
然后配置端口,由于电脑是主机,51单片机是从机。因此,需要用“Modbus PULL”这个软件来进行配置。
而51单片机的串口号为COM2
正文:
Modbus协议其实就是通信的规则,就好比马路上的红绿灯。就拿功能码0x03来举例,0x03功能码的作用是读多个寄存器的值,如果我要读取寄存器3的值,则主机发送的报文则是01 03 00 03 00 01 xx xx,其中01我们称为从站地址,即从机的地址。03则是功能码,00 03是寄存器的地址,00 01则是你想读取的寄存器数量(因为0x03的功能就是读取多个寄存器),而xx xx是报文的校验位一般是RTU校验。好,那么如果我不仅仅想读取寄存器3的值我还想读取4,5,6寄存器的值那么报文则是01 03 00 03 00 04 xx xx。知道功能码的用途后,我们需要的就是进行编程对这些报文的每个值进行分析,并作出相应的动作,之后再发送报文给上位机这样就实现了通讯。本文的目的是实现一帧报文的接收,并且实现值得分析。
首先我们要了解什么叫帧,了解帧之前先了解RS232,这是一个硬件协议,也就是一根线
通过这根线实现TTL电平的转换,能和笔记本相通讯。而数据通讯的格式则是
我们可以看到有8个数据位,而在我们的51单片机里面是集成了UART,
可以通过SCON来进行配置
一般是使用模式1。
而帧就是用这样的格式信息的数字字符。一般都是无奇偶校验,无极性,一位停止位,波特率9600,俗称96-n-8-1。而Modbus则是再这样的格式下面再规定书写规范。波特率9600的意思是1秒钟有9600个二进制位的数据传输,这么快的速度下如何才能让51单片机识别一帧的内容呢?这是最重要的一点。
可以从图中可以看出每一帧之间其实隔着3.5个字符,所以可以理解为只要单片机没有介绍到数据达到3.5个字符就可以断定1帧报文接收完成。那3.5个字符到底是多久呢?我们以波特率9600来进行计算,我们从上文可知一字符有1个起始位,8个数据位,1个停止位,所以至少有10个位,那么3.5个字符就是35个位。又波特率是9600,所以一位的速度是1/9600 s/bit。所以
35*(1/9600)=0.003645s=3.645ms,就算他4ms。所以我们编制时只需要通过定时器定时,当没有接收到数据达到4ms以上则可断定一帧接收完成。
原理理解和硬件调试好后就可以进行软件的编写了:
main.c
#include <REGX52.H>
#include "uart.h"
#include "modbus.h"
#include "time.h"
void main(void)
{
UART_Init(4800);
Timer0_Init(1000); //1ms
Modbus_Init();
while(1)
{
UART_Echo();
}
}
modbus.c
#include "modbus.h"
struct MODBUS_DEF Modbus;
void Modbus_Init(void)
{
Modbus.SlaveID=0x01; //从站地址
Modbus.Cnt=0; //接收的位数
Modbus.Flag=0; //启动定时器标志位
Modbus.ReFlag=0; //完成一帧的标志位
Modbus.Tim=0; //计数
}
modbus.h
#ifndef __MODBUS_H__
#define __MODBUS_H__
#include <REGX52.H>
void Modbus_Init(void);
struct MODBUS_DEF{
unsigned char SlaveID;
unsigned char Cnt;
unsigned char ReBuff[50];
unsigned char Flag;
unsigned char Tim;
unsigned char ReFlag;
};
extern struct MODBUS_DEF Modbus;
#endif
uart.c
#include"uart.h"
#include "modbus.h"
void UART_Init(unsigned int fre)
{
SCON=0x50;
PCON=0x00;
TMOD&=0X0F;
TMOD|=0X20;
TH1=256-11059200/12/2/16/fre;
TL1=256-11059200/12/2/16/fre;
TR1=1;
//EA=1;
//ES=1;
}
void UART_Echo(void)
{
while(RI==1)
{
if(Modbus.ReFlag==1)
return;
Modbus.ReBuff[Modbus.Cnt++]=SBUF;
Modbus.Tim=0;
if(Modbus.Cnt==1)
{
Modbus.Flag=1;
}
RI=0;
}
}
uart.h
#ifndef __UART_H__
#define __UART_H__
#include <REGX52.H>
void UART_Init(unsigned int fre);
void UART_Echo(void);
void UART_SendByte(unsigned int num);
#endif
time.c
#include "time.h"
#include "Modbus.h"
void Timer0_Init(unsigned int time)
{
TMOD&=0XF0;
TMOD|=0X01;
TH0=(65525-time)/256;
TL0=(65525-time)%256;
TR0=1;
TF0=0;
EA=1;
ET0=1;
}
void Timer0_ISR(void) interrupt 1
{
if(Modbus.Flag==1)
{
Modbus.Tim++;
if(Modbus.Tim>=8)
{
Modbus.Flag=0;
Modbus.ReFlag=1;
}
}
}
time.h
#ifndef __TIME_H__
#define __TIME_H__
#include <REGX52.H>
void Timer0_Init(unsigned int time);
#endif
实验结果:
上位机发送的报文 01 03 00 03 00 04 B4 09
下位机接收的报文
所以实现了一帧报文的接收。