51 单片机实现的 485 通讯程序
#ifndef __485_C__
#define __485_C__
#include
#include
#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 使用工作方式 2
TH1 = 250;
// 设置初值
TL1 = 250;
TR1 = 1;
// 开始计时
PCON = 0x80;
// SMOD = 1
SCON = 0x50; // 工作方式 1,波特率 9600bps,允许接收
ES = 0;
// 关闭串口中断
IT0 = 0;
// 外部中断 0 使用电平触发模式
EX0 = 1;
// 开启外部中断 0
EA = 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 表示 0xdb
ecc = ecc^0xdb;
db = 0;
break;
case 0xdc
data_buf[i] = 0xc0;
// 0xdbdc 表示 0xc0
ecc = 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)
// 帧超长,错误,返回
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