51单片机实现的485通讯程序
- 格式:doc
- 大小:60.50 KB
- 文档页数:11
1 问题的提出在应用系统中,RS-485半双工异步通信总线是被各个研发机构广泛使用的数据通信总线,它往往应用在集中控制枢纽与分散控制单元之间。
系统简图如图1所示。
图1. RS-485系统示意图由于实际应用系统中,往往分散控制单元数量较多,分布较远,现场存在各种干扰,所以通信的可靠性不高,再加上软硬件设计的不完善,使得实际工程应用中如何保障RS-485总线的通信的可靠性成为各研发机构的一块心病。
在使用RS-485总线时,如果简单地按常规方式设计电路,在实际工程中可能有以下两个问题出现。
一是通信数据收发的可靠性问题;二是在多机通信方式下,一个节点的故障(如死机),往往会使得整个系统的通信框架崩溃,而且给故障的排查带来困难。
针对上述问题,我们对485总线的软硬件采取了具体的改进措施2 硬件电路的设计现以8031单片机自带的异步通信口,外接75176芯片转换成485总线为例。
其中为了实现总线与单片机系统的隔离,在8031的异步通信口与75176之间采用光耦隔离。
电路原理图如图2所示。
图2 改进后的485通信口原理图充分考虑现场的复杂环境,在电路设计中注意了以下三个问题。
2.1 SN75176 485芯片DE控制端的设计由于应用系统中,主机与分机相隔较远,通信线路的总长度往往超过400米,而分机系统上电或复位又常常不在同一个时刻完成。
如果在此时某个75176的DE端电位为“1”,那么它的485总线输出将会处于发送状态,也就是占用了通信总线,这样其它的分机就无法与主机进行通信。
这种情况尤其表现在某个分机出现异常情况下(死机),会使整个系统通信崩溃。
因此在电路设计时,应保证系统上电复位时75176的DE端电位为“0”。
由于8031在复位期间,I/O口输出高电平,故图2电路的接法有效地解决复位期间分机“咬”总线的问题。
2.2 隔离光耦电路的参数选取在应用系统中,由于要对现场情况进行实时监控及响应,通信数据的波特率往往做得较高(通常都在4800波特以上)。
单片机485烧程序方法
1. 准备工作,首先,你需要准备一台支持单片机485烧录的编程器,以及单片机的开发板和对应的编程软件。
确保你有正确的单片机型号和对应的烧录工具。
2. 连接硬件,将编程器通过USB接口连接到计算机上,然后将编程器的接口与开发板上的对应接口相连。
确保连接的稳固性和正确性。
3. 打开编程软件,打开单片机的编程软件,一般来说,这些软件会提供一个界面,你可以在界面中选择单片机型号和相应的烧录方式。
4. 选择文件,在编程软件中选择你要烧录的程序文件,通常是一个.hex文件或者.bin文件。
5. 设置参数,根据你的需要,设置好烧录的参数,比如时钟频率、烧录方式等。
6. 烧录程序,点击软件界面上的烧录按钮,软件会开始向单片
机烧录程序。
在烧录过程中,要确保编程器和单片机的连接稳定,避免操作过程中的干扰。
7. 验证烧录,烧录完成后,一般软件会提示烧录成功。
此时可以进行一次验证,确认程序是否成功烧录到单片机中。
总的来说,单片机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^0。
// 驱动器使能,1有效sbit M_RE = P1^1。
// 接收器使能,0有效void get_status(>。
// 调用该函数获得设备状态信息,函数代码未给出void send_data(uchar type, uchar len, uchar *buf>。
// 发送数据帧bit recv_cmd(uchar *type>。
// 接收主机命令,主机请求仅包含命令信息void send_byte(uchar da>。
// 该函数发送一帧数据中的一个字节,由send_data(>函数调用void main(>{uchar type。
uchar len。
/* 系统初始化 */P1 = 0xff。
// 读取本机设备号dev = (P1>>2>。
TMOD = 0x20。
在工业控制、电力通讯、智能仪表等领域,通常情况下是采用串口通信的方式进行数据交换。
最初采用的方式是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的时候,讲过差分信号输入的概念,同时也介绍了差分输入的好处,最大的优势是可以抑制共模干扰。
尤其工业现场的环境比较复杂,干扰比较多,所以通信如果采用的是差分方式,就可以有效的抑制共模干扰。
题目:基于51单片机的RS485从机系统设计单片机接口电路设计及单片机资源配置:1.上电复位电路;2.晶振电路采用11.0592Mhz晶振;3.485接口电路(P3.7用于485芯片的收发控制,收发管脚接单片机的rxd和txd);4.P2口通过外部跳线接相应的高低电平,配置从机地址为学生学号(取值范围:0x01-0x80);5.P3.6外接一发光二极管(注意串联电阻进行限流);6.P3.2外接一按键,断开高电平,按下地电平;7.按键检测采用外部中断方式,下跳沿触发;8.单片机定时器0以模式1(16位模式)工作,产生50ms的定时中断,并在此基础上设计一单片机内部时钟(24小时制,能计量时、分、秒、50ms值);9.单片机串行通信采用模式1非多机通信方式,采用9600波特率以串行中断方式进行通信,主机地址为0xF0,广播地址为0xFF系统功能需求:1.系统自检功能:系统上电后,初始化时以每半秒闪烁一次的频率点亮发光二极管;按下按键触发外部中断过程中熄灭发光二极管,同时禁止外部中断防止按键重复触发;2.数据接收功能:在接收到主机发来的按键允许命令帧后开外部中断,允许按键按下产生外部中断,同时点亮发光二极管进行按键允许显示;此后按下按键产生外部中断并同时在程序的数组变量中记下当前的时钟数据(定时器的低8位、定时器的高8位、50ms值、秒、分、小时),并同时禁止外部中断防止按键重复触发;3.数据发送功能:接收到主机发来的时钟数据搜索命令帧后将前面记下的时钟数据按(定时器的低8位、定时器的高8位、50ms值、秒、分、小时)的顺序组成时钟数据返回帧回送给主机,同时熄灭发光二极管;4.校验和生成和检测功能:发送数据帧时自动生成校验和;接收数据帧时能检测校验和并判断接收数据是否正确;每帧数据在发送帧尾前,发送一字节的当前帧数据的校验和(校验和的计算不包含帧头和帧尾),另外帧长不包含帧头、帧尾和校验和字节。
附录:时钟数据搜索命令帧:时钟数据返回帧:帧结构头文件frame.h(内容如下)#define FRAME_HEAD 0xAA //帧头#define FRAME_TAIL 0x66 //帧尾#define FRAME_LEN 0x00 //帧长#define FRAME_DST_ADR 0x01 //目的地址#define FRAME_SRC_ADR 0x02 //源地址#define FRAME_CMD 0x03 //命令字#define FRAME_DATA 0x04 //帧数据起始#define READY 0x01 //按键允许命令#define TIME_SERCH 0x03 //时钟数据轮询命令#define TIME_BACK 0x07 //时钟数据返回命令#define BROAD_ADR 0xFF //广播地址#define MASTER_ADR 0xF0 //主机地址。
--------以上部分请勿修改!-------------提高485总线的可靠性摘要:就485总线应用中易出现的问题,分析了产生的原因并给出解决问题的软硬件方案和措施。
关键词:RS-485总线、串行异步通信--------------------------------------------------------------------------------1 问题的提出在应用系统中,RS-485半双工异步通信总线是被各个研发机构广泛使用的数据通信总线,它往往应用在集中控制枢纽与分散控制单元之间。
系统简图如图1所示。
图1. RS-485系统示意图由于实际应用系统中,往往分散控制单元数量较多,分布较远,现场存在各种干扰,所以通信的可靠性不高,再加上软硬件设计的不完善,使得实际工程应用中如何保障RS-485总线的通信的可靠性成为各研发机构的一块心病。
在使用RS-485总线时,如果简单地按常规方式设计电路,在实际工程中可能有以下两个问题出现。
一是通信数据收发的可靠性问题;二是在多机通信方式下,一个节点的故障(如死机),往往会使得整个系统的通信框架崩溃,而且给故障的排查带来困难。
针对上述问题,我们对485总线的软硬件采取了具体的改进措施2 硬件电路的设计现以8031单片机自带的异步通信口,外接75176芯片转换成485总线为例。
其中为了实现总线与单片机系统的隔离,在8031的异步通信口与75176之间采用光耦隔离。
电路原理图如图2所示。
图2 改进后的485通信口原理图充分考虑现场的复杂环境,在电路设计中注意了以下三个问题。
2.1 SN75176 485芯片DE控制端的设计由于应用系统中,主机与分机相隔较远,通信线路的总长度往往超过400米,而分机系统上电或复位又常常不在同一个时刻完成。
如果在此时某个75176的DE端电位为“1”,那么它的485总线输出将会处于发送状态,也就是占用了通信总线,这样其它的分机就无法与主机进行通信。
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.数据的发送和接收在发送和接收数据时,需要采用特定的方式进行报文的封装和解析。
/* 以下为单片机串口485通讯程序,从机程序(当然也适用于主机程序),主机发送可以先用串口帮手软件来调试,经过Keil uVision4实际测试,测试效果如结尾图片所示, 大部分来自网络,只是改了两个地方: len = sizeof(dbuf),if(i >=( __ERRLEN+1)) // 帧超长,错误,返回,就可以实现了,其中的原因自已体会吧*/#ifndef __485_C__#define __485_C__#include <reg51.h>#include <string.h>#include <stdio.h>#include <intrins.h>#define uchar unsigned char#define uint unsigned int/* 通信命令*/#define __ACTIVE_ 0x01 // 主机询问从机是否存在#define __GETDATA_ 0x02 // 主机发送读设备请求#define __OK_ 0x03 // 从机应答#define __STATUS_ 0x04 // 从机发送设备状态信息#define __MAXSIZE 0x08 // 缓冲区长度#define __ERRLEN 12 // 任何通信帧长度超过12则表示出错//uchar dbuf[__MAXSIZE]; // 该缓冲区用于保存设备状态信息uchar dbuf[__MAXSIZE];//={0,1,2,3,4,5,6,7}; // 该缓冲区用于保存设备状态信息uchar dev; // 该字节用于保存本机设备号sbit M_DE = P1^0; // 驱动器使能,1有效sbit M_RE = P1^1; // 接收器使能,0有效void get_status(); // 调用该函数获得设备状态信息,函数代码未给出void send_data(uchar type, uchar len, uchar *buf); // 发送数据帧bit recv_cmd(uchar *type); // 接收主机命令,主机请求仅包含命令信息void send_byte(uchar da); // 该函数发送一帧数据中的一个字节,由send_data()函数调用void main(){uchar type;uchar len;/* 系统初始化*/P1 = 0xff; // 读取本机设备号//dev = (P1>>2);dev = 0x01;TMOD = 0x20; // 定时器T1使用工作方式2TH1 = 250; // 设置初值TL1 = 250;TR1 = 1; // 开始计时PCON = 0x80; // SMOD = 1SCON = 0x50; // 工作方式1,波特率9600bps,允许接收ES = 0; // 关闭串口中断//IT0 = 0; // 外部中断0使用电平触发模式//EX0 = 1; // 开启外部中断0EA = 1; // 开启中断/* 主程序流程*/while(1) // 主循环{if(recv_cmd(&type) == 0) // 发生帧错误或帧地址与本机地址不符,丢弃当前帧后返回continue;switch(type){case __ACTIVE_: // 主机询问从机是否存在send_data(__OK_, 0, dbuf); // 发送应答信息,这里buf的内容并未用到break;case __GETDA TA_:// len = strlen(dbuf);//在C51中不能这个函数计算unsigned char型,这个函数只能计算char型len = sizeof(dbuf);// len =0x08;send_data(__STA TUS_, len, dbuf); // 发送设备状态信息break;default:break; // 命令类型错误,丢弃当前帧后返回}}}void READSTATUS() interrupt 0 using 1 // 产生外部中断0时表示设备状态发生改变,该函数使用寄存器组1{get_status(); // 获得设备状态信息,并将其存入dbuf指向的存储区,数据最后一字节置0表示数据结束}/* 该函数接收一帧数据并进行检测,无论该帧是否错误,函数均会返回* 函数参数type保存接收到的命令字* 当接收到数据帧错误或其地址位不为0时(非主机发送帧),函数返回0,反之返回1*/bit recv_cmd(uchar *type){bit db = 0; // 当接收到的上一个字节为0xdb时,该位置位bit c0 = 0; // 当接收到的上一个字节为0xc0时,该位置位uchar data_buf[__ERRLEN]; // 保存接收到的帧__ERRLEN=12;uchar tmp;uchar ecc = 0;uchar i;M_DE = 0; // 置发送禁止,接收允许M_RE = 0;/* 接收一帧数据*/i = 0;while(!c0) // 循环直至帧接收完毕{RI = 0;while(!RI);tmp = SBUF;RI = 0;if(db == 1) // 接收到的上一个字节为0xdb{switch(tmp){case 0xdd:data_buf[i] = 0xdb; // 0xdbdd表示0xdbecc = ecc^0xdb;db = 0;break;case 0xdc:data_buf[i] = 0xc0; // 0xdbdc表示0xc0ecc = ecc^0xc0;db = 0;break;default:return 0; // 帧错误,返回}i++;}switch(tmp) // 正常情况{case 0xc0: // 帧结束c0 = 1;break;case 0xdb: // 检测到转义字符db = 1;break;default: // 普通数据data_buf[i] = tmp; // 保存数据ecc = ecc^tmp; // 计算校验字节i++;}//if(i == __ERRLEN) // 帧超长,错误,返回if(i >=( __ERRLEN+1)) // 帧超长,错误,返回return 0;}/* 判断帧是否错误*/if(i<4) // 帧过短,错误,返回return 0;if(ecc != 0) // 校验错误,返回return 0;if(data_buf[0] != dev) // 非访问本机命令,错误,返回return 0;*type = data_buf[1]; // 获得命令字return 1; // 函数成功返回}/* 该函数发送一帧数据帧,参数type为命令字、len为数据长度、buf为要发送的数据内容*/void send_data(uchar type, uchar len, uchar *buf){uchar i;uchar ecc = 0; // 该字节用于保存校验字节M_DE = 1; // 置发送允许,接收禁止M_RE = 1;send_byte(dev); // 发送本机地址ecc = dev;send_byte(type); // 发送命令字ecc = ecc^type;send_byte(len); // 发送长度ecc = ecc^len;for(i=0; i<len; i++) // 发送数据{send_byte(*buf);ecc = ecc^(*buf);buf++;}send_byte(ecc); // 发送校验字节TI = 0; // 发送帧结束标志SBUF = 0xc0;while(!TI);TI = 0;}/* 该函数发送一个数据字节,若该字节为0xdb,则发送0xdbdd,若该字节为0xc0则,发送0xdbdc */void send_byte(uchar da){switch(da){case 0xdb: // 字节为0xdb,发送0xdbdd TI = 0;SBUF = 0xdb;while(!TI);TI = 0;SBUF = 0xdd;while(!TI)TI = 0;break;case 0xc0: // 字节为0xc0,发送0xdbdcTI = 0;SBUF = 0xdb;while(!TI);TI = 0;SBUF = 0xdc;while(!TI)TI = 0;break;default: // 普通数据则直接发送TI = 0;SBUF = da;while(!TI);TI = 0;}}#endif/* 调试结果*/。
本实验实现的功能和12.5节的完全相同,也是通过串行口接收上位机的启、停协议,然后根据协议来控制计数的启动和暂停,单片机每次计数过0向上位机返回过0消息。
本实验的主要代码如下:main.c源文件//main.c#include <reg51.h> //包含头文件#include "fun.h"void Timer0_interrupt() interrupt 1{TH0 = T0_50ms >> 8; //重装初值TL0 = T0_50ms;if ((++ count_in_T0) == 20) //count_in_T0自加到20,计时1s{count_in_T0 = 0;if ( (++ display_num) ==60){ //display_num自加1后判断是否等于60display_num = 0;//上发过0消息send(0xf0);send(0x01);send(0xf1);}}}void USART_interrupt() interrupt 4{char checkXOR;if (RI) //接收中断{RI = 0;reciev[rec_num] = SBUF;rec_num ++;if ((rec_num == 3) && (reciev[0] == 0x0f) )//接收到3字节,并且包头正确{checkXOR = reciev[0] ^ reciev[1];if (checkXOR == reciev[2] ) //如果异或校验正确,判断命令 {switch (reciev[1]){case 0x01:TR0 = 1; //启动rec_num = 0; //指令正确,清空缓冲区break;case 0x02:TR0 = 0; //暂停rec_num = 0; //指令正确,清空缓冲区break;default: //如果指令不正确,缓冲区左移reciev[0] = reciev[1];reciev[1] = reciev[2];rec_num --;break;}}}}if (TI){TI = 0;}}main(){unsigned char shi, ge; //定义十位、个位要输出的数据 ms_delay(100);init_port();init_usart();init_timer();dir = 0; //485接收数据rec_num = 0;display_num = 0;count_in_T0 = 0;//把两个数码管都关闭en2 = 1;en1 = 1;EA = 1; //开总中断while(1){shi = display_num / 10;ge = display_num % 10;display(shi,ge);}}fun.h头文件代码如下://fun.h//定义端口寄存器sfr P0M0 = 0X93;sfr P0M1 = 0X94;sfr P1M0 = 0X91;sfr P1M1 = 0X92;sfr P2M0 = 0X95;sfr P2M1 = 0X96;sfr P3M0 = 0Xb1;sfr P3M1 = 0Xb2;#define fosc 11059200L#define T0_50ms (65536 - (fosc/12/50000)) //50ms定时参数sbit en1 = P2^6;sbit en2 = P2^7;sbit dir = P3^2; //485方向控制unsigned char display_num,count_in_T0; //计数值、进入定时器的次数char reciev[3], rec_num; //保存接收到的数据/*共阴极数码管0~9的字形码"0" 3FH "5" 6DH"1" 06H "6" 7DH"2" 5BH "7" 07H"3" 4FH "8" 7FH"4" 66H "9" 6FH*/const unsigned char seg7[10] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f};void init_port(){P0M1 = 0xff;P0M0 = 0xff;P2M1 |= 0xc0;P2M0 |= 0xc0;P3M1 = 0x00; //P3.2准双向口模式P3M0 = 0x00;}void init_usart(){SM0 = 0;SM1 = 1;REN = 1;ES = 1;}void init_timer(){TMOD |= 0X01; //定时器0模式1,16bitTH0 = T0_50ms >> 8; //TH0 = T0_50ms / 256TL0 = T0_50ms; //TL0 = T0_50ms % 256ET0 = 1; //允许T0中断TMOD |= 0X20; //定时器1模式2,8bit自动重装 TH0 = 0xfd; //对应波特率9600TL0 = 0xfd;ET1 = 1; //允许T1中断TR1 = 1;}//定义延时函数void ms_delay(unsigned int t){unsigned int i;for (t; t > 0; t--) //外层循环t次for (i = 110;i > 0; i--) //内层循环110次;}void display(unsigned char c2, unsigned char c1) {P0 = seg7[c2]; //送入十位的段码en2 = 0; //显示DS2ms_delay(10);en2 = 1; //关闭DS2P0 = seg7[c1]; //送入个位的段码en1 = 0; //显示DS1ms_delay(10);en1 = 1; //关闭DS1}void send(char c){dir = 1; //485发送数据SBUF = c;while(!TI); //等待发完dir = 0; //485接收数据}讲解:本实验和12.5节的串行口收发实验基本相同,单片机串行口经由MAX1487芯片收发数据的过程对编程人员来说是完全透明的,我们只用跟平时操作串行口一样的方法来控制就可以了。
标签:modbus8051源程序modbus协议--51端程序的实现RTU需要一个定时器来判断3.5个流逝时间。
#define ENABLE 1#define DISABLE 0#define TRUE 1#define FAULT 0#define RECEIVE_EN 0#define TRANSFER_EN 1#define MAX_RXBUF 0x20extern unsigned char emissivity;extern unsigned char tx_count,txbuf[15];extern unsigned char rx_count,rxbuf[15];extern unsigned char tx_number,rx_number;extern bit rx_ok;unsigned char rx_temp;void InitTimer1() //针对标准8051{TMOD=(TMOD|0xf0)&0x1f; //将T1设为16位定时器TF1=0;TH1=0x62; //设T1位3.5位的接收时间35bit/9600bit/s=3.646msTL1=0x80;//晶振为11.0592MHz,T=65535-3.646ms*11.0592MHz/12=0xf2df//0x6280是22.1184M下LPC9XX下的值。
ET1=1;//允许T1中断TR1=1;//T1开始计数}void timer1() interrupt 3 using 2 //定时器中断{TH1=0x62; //3.646ms interruptTL1=0x80;if(rx_count>=5) //超时后,若接收缓冲区有数则判断为收到一帧{rx_ok=TRUE;}}void scomm() interrupt 4 using 3 //modbus RTU模式{if(TI){TI = 0;if(tx_count < tx_number) //是否发送结束{SBUF = txbuf[tx_count];}tx_count++;}if(RI){rx_temp=SBUF;if(rx_ok==FAULT) //已接收到一帧数据,在未处理之前收到的数舍弃{if(rx_countrxbuf[rx_count]=rx_temp;rx_count++;}TH1=0x62; //timer1 reset,count againTL1=0x80;RI=0;}}在主循环中判断标志rx_ok来执行帧处理。
if(rx_ok){ParseFrame();KB0=1;REN=0;tx_count=0;TI=1; //启动发送响应帧rx_count=0;rx_ok=0;}WORD MAKEWORD(a, b){int_byte itemp;itemp.items.high=a;itemp.items.low=b;return (itemp.item);// 解析帧并发送响应帧 (在帧完整的前提下调用)bit ParseFrame(){unsigned char byAddr ; // 地址unsigned char byFunCode ; // 功能代码int_byte wCRC;wCRC.item = MAKEWORD(rxbuf[rx_count-1], rxbuf[rx_count-2]);if(wCRC.item != CRC(rxbuf, rx_count-2)) // 判断校验是否正确return FALSE;// 正式解析byAddr = rxbuf[0]; // 地址byFunCode = rxbuf[1]; // 功能代码// 如果地址不对if( (byAddr != m_byAddress) && (byAddr != 0) )return FALSE;if(byAddr == m_byAddress){AddSendByte(m_byAddress) ; // 地址switch( byFunCode ){case 3: // 读保持寄存器Fun3(3);break;....// 添加命令散转......default:ErroRespond(1);return FALSE;break;}}wCRC.item = CRC(txbuf,tx_number);AddSendByte(wCRC.items.low);AddSendByte(wCRC.items.high);return TRUE;// 根据接收帧模式发送相应,模式的数据BOOL AddSendByte(const BYTE byData){txbuf[tx_number]=byData;tx_number++;if(tx_number>30)return FALSE;return TRUE;}// 异常响应描述响应解释// 01 无效功能变送器不允许执行收到的功能// 02 无效地址数据栏中的地址是不允许的// 03 无效数据数据栏中的数据是不允许的// 06 忙收到的消息没错,但从机正在执行一个长的程序命令bit ErroRespond(const unsigned char byErroCode){// printf("\nErroRespond%02X \n", byErroCode);if( !AddSendByte(rxbuf[1] | 0x80) )return FALSE;return AddSendByte(byErroCode);}//***CRC Calculation for MODBUS Protocol for VC++***////数组snd为地址等传输字节,num为字节数//unsigned int CRC(unsigned char *snd, unsigned char num){unsigned char i, j;unsigned int c,crc=0xFFFF;for(i = 0; i < num; i ++){c = snd[i] & 0x00FF;crc ^= c;for(j = 0;j < 8; j ++){if (crc & 0x0001){crc>>=1;crc^=0xA001;}else crc>>=1;}}return(crc);}51单片机实现的485通讯程序单片机2009-08-31 14:17:48 阅读722 评论0 字号:大中小订阅#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^0; // 驱动器使能,1有效sbit M_RE = P1^1; // 接收器使能,0有效void get_status(); // 调用该函数获得设备状态信息,函数代码未给出void send_data(uchar type, uchar len, uchar *buf); // 发送数据帧bit recv_cmd(uchar *type); // 接收主机命令,主机请求仅包含命令信息void send_byte(uchar da); // 该函数发送一帧数据中的一个字节,由send_data()函数调用void main(){uchar type;uchar len;/* 系统初始化*/P1 = 0xff; // 读取本机设备号dev = (P1>>2);TMOD = 0x20; // 定时器T1使用工作方式2TH1 = 250; // 设置初值TL1 = 250;TR1 = 1; // 开始计时PCON = 0x80; // SMOD = 1SCON = 0x50; // 工作方式1,波特率9600bps,允许接收ES = 0; // 关闭串口中断IT0 = 0; // 外部中断0使用电平触发模式EX0 = 1; // 开启外部中断0EA = 1; // 开启中断/* 主程序流程*/while(1) // 主循环{if(recv_cmd(&type) == 0) // 发生帧错误或帧地址与本机地址不符,丢弃当前帧后返回continue;switch(type){case __ACTIVE_: // 主机询问从机是否存在send_data(__OK_, 0, dbuf); // 发送应答信息,这里buf的内容并未用到break;case __GETDATA_:len = strlen(dbuf);send_data(__STATUS_, len, dbuf); // 发送设备状态信息break;default:break; // 命令类型错误,丢弃当前帧后返回}}}void READSTATUS() interrupt 0 using 1 // 产生外部中断0时表示设备状态发生改变,该函数使用寄存器组1{get_status(); // 获得设备状态信息,并将其存入dbuf指向的存储区,数据最后一字节置0表示数据结束}/* 该函数接收一帧数据并进行检测,无论该帧是否错误,函数均会返回* 函数参数type保存接收到的命令字* 当接收到数据帧错误或其地址位不为0时(非主机发送帧),函数返回0,反之返回1*/bit recv_cmd(uchar *type)bit db = 0; // 当接收到的上一个字节为0xdb时,该位置位bit c0 = 0; // 当接收到的上一个字节为0xc0时,该位置位uchar data_buf[__ERRLEN]; // 保存接收到的帧uchar tmp;uchar ecc = 0;uchar i;M_DE = 0; // 置发送禁止,接收允许M_RE = 0;/* 接收一帧数据*/i = 0;while(!c0) // 循环直至帧接收完毕{RI = 0;while(!RI);tmp = SBUF;RI = 0;if(db == 1) // 接收到的上一个字节为0xdb{switch(tmp){case 0xdd:data_buf[i] = 0xdb; // 0xdbdd表示0xdbecc = ecc^0xdb;db = 0;break;case 0xdcdata_buf[i] = 0xc0; // 0xdbdc表示0xc0ecc = ecc^0xc0;db = 0;break;defaultreturn 0; // 帧错误,返回}i++;}switch(tmp) // 正常情况{case 0xc0: // 帧结束c0 = 1;break;case 0xdb: // 检测到转义字符break;default: // 普通数据data_buf[i] = tmp; // 保存数据ecc = ecc^tmp; // 计算校验字节i++;}if(i == __ERRLEN) // 帧超长,错误,返回return 0;}/* 判断帧是否错误*/if(i<4) // 帧过短,错误,返回return 0;if(ecc != 0) // 校验错误,返回return 0;if(data_buf[0] != dev) // 非访问本机命令,错误,返回return 0;*type = data_buf[1]; // 获得命令字return 1; // 函数成功返回}/* 该函数发送一帧数据帧,参数type为命令字、len为数据长度、buf为要发送的数据内容*/ void send_data(uchar type, uchar len, uchar *buf){uchar i;uchar ecc = 0; // 该字节用于保存校验字节M_DE = 1; // 置发送允许,接收禁止M_RE = 1;send_byte(dev); // 发送本机地址ecc = dev;send_byte(type); // 发送命令字ecc = ecc^type;send_byte(len); // 发送长度ecc = ecc^len;for(i=0; i<len; i++) // 发送数据{send_byte(*buf);ecc = ecc^(*buf);buf++;}send_byte(ecc); // 发送校验字节TI = 0; // 发送帧结束标志while(!TI);TI = 0;}/* 该函数发送一个数据字节,若该字节为0xdb,则发送0xdbdd,若该字节为0xc0则,发送0xdbdc */void send_byte(uchar da){switch(da){case 0xdb: // 字节为0xdb,发送0xdbddTI = 0;SBUF = 0xdb;while(!TI);TI = 0;SBUF = 0xdd;while(!TI)TI = 0;break;case 0xc0: // 字节为0xc0,发送0xdbdcTI = 0;SBUF = 0xdb;while(!TI);TI = 0;SBUF = 0xdc;while(!TI)TI = 0;break;default: // 普通数据则直接发送TI = 0;SBUF = da;while(!TI);TI = 0;}}#endif关于CT极性的若干相关问题看到旺友leilin0902关于线圈极性测试方法的帖子,有一小小想法,开新贴讨论CT极性的相关问题,我会慢慢提问,欢迎大家讨论。