基于51单片机的RS485从机系统设计

单片机接口资源配置:

  1. 上电复位电路;
  2. 晶振电路采用11.0592Mhz晶振;
  3. 485接口电路(P3.7用于485芯片的收发控制,收发管脚接单片机的rxd和txd);
  4. P2口通过外部跳线接相应的高低电平,配置从机地址为组号;
  5. P3.6外接一发光二极管(注意串联电阻进行限流);
  6. P3.2外接一按键,断开高电平,按下低电平;
  7. 按键检测采用外部中断方式,下跳沿触发;
  8. 单片机定时器0以模式1(16位模式)工作,产生50ms的定时中断,并在此基础上设计一单片机内部时钟(24小时制,能计数时、分、秒、50ms值);
  9. 单片机串行通信采用模式1非多机通信方式,采用9600波特率以串行中断方式进行数据的收发通信,主机地址为0xF0,广播地址为0xFF。

系统功能需求:

  1. 系统配置和自检功能:
     从机上电后进行初始化,通过读取P2口进行从机地址配置;
     发光二极管以每秒一次的频率闪烁(亮0.5秒,灭0.5秒);
     检测到一次按键按下操作后,熄灭发光二极管。

  2. 数据接收和按键计时功能:
     从机接收主机程序(PC机上的串口调试程序)的按键允许命令帧并进行校验;
     校验正确并且目的地址是广播地址或者本从机的地址,通过发光二极管长亮指示,并允许按键操作;
     按键按下后,尽可能准确记录按键的动作时点(定时器的低8位、定时器的高8位、50ms值、秒、分、小时);
     按键操作只能响应一次,重复按键操作不响应;
     按键的动作时点记录后,发光二极管以每秒一次的频率闪烁(亮0.5秒,灭0.5秒)。

  3. 数据发送功能:
     从机接收主机程序发来的时钟数据搜索命令帧并进行校验;
     如果校验正确并且数据帧的目的地址是本从机的地址,从机将前面记录的按键动作时点数据(定时器的低8位、定时器的高8位、50ms值、秒、分、小时)按附录中的时钟数据返回帧的帧格式回传给主机;
     时钟数据返回帧回传结束后,熄灭发光二极管。

  4. 校验和生成和检测功能:
     发送数据帧时能自动生成数据帧校验和;
     每帧数据在发送帧尾前,发送一字节的当前帧数据的校验和;
     接收数据帧时能检测校验和并判断接收数据是否正确。

附录:帧定义

校验和的计算:除去帧头和帧尾后将帧中的其他数据求和并取低8位;
帧长:不计帧头、帧尾和校验和字节。

按键允许命令帧:
帧头 帧长 目的地址 源地址 命令字 校验和 帧尾
AA 04 FF F0 01 F4 66

时钟数据搜索命令帧:
帧头 帧长 目的地址 源地址 命令字 保留字 校验和 帧尾
AA 05 01 F0 03 00 F9 66

时钟数据返回帧:
帧头 帧长 目的地址 源地址 命令字 TL0 TH0 50ms 秒 分 时 校验和 帧尾
AA 0A F0 01 07 01 B6 09 03 00 00 C5 66

扫描二维码关注公众号,回复: 11282053 查看本文章

帧结构头文件frame.h(内容如下)
//帧格式定义

#define FRAME_HEAD 0xAA //帧头

set.h

#define FRAME_FOOT 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		//主机地址

**

接口配置:略
原理图如下
在这里插入图片描述

串口状态机:
在这里插入图片描述

#include<reg52.h>
#include<set.h>

unsigned char time_h = 0,time_m = 0,time_s = 0,time_ms = 0,vis_p36 = 1;
unsigned char send[11]={0xaa,0x0a,0xf0};
sbit p36 = P3^6;
sbit p37 = P3^7;
unsigned char js_data[20],len = 0,vis_key = 0,key_down = 0,init_key = 0;
unsigned char data_from_sbuf = 0,stare = 0,head = 0,js_t = 0;


void interrupt_T0() interrupt 1{//定时器T0中断 没过50毫秒进入中断
	TH0 = 0x4b;
	TL0 = 0xfc;
	//因为50000/1.085 = 46083 65535-46083=19452=0x4bfc TH0=0x4b TL0=0xfc
	time_ms += 1;//50毫秒计数加一
	time_s += time_ms / 20;//若50毫秒计数加到20 也就是过去里1秒 秒数加一
	time_m += time_s / 60;//秒数加到60 分钟数加一
	time_h += time_m / 60;//分钟数加到60 小时数加一
	time_ms %= 20;//数加到20复位0
	time_s %= 60;//秒数加到60复位
	time_m %= 60;//
	time_h %=24;//
	if(vis_p36 && time_ms == 10)p36 = 1;// 
	if(vis_p36 && time_ms == 0)p36 = 0;//当VIS_p36为1时 LED灯就会闪烁
	if(head == 1){//这段代码意思是:当接收到帧头后 在0.5秒之内没有接收到帧尾 那么0.5秒之后就将状态机复位 就是发生了数据丢失
		js_t ++;
		if(js_t == 10){
			js_t = 0;head = 0;
			stare = 0;
		}
	}
}


void interrupt_EX0()interrupt 0{//外部中断0 P32口下降沿触发中断
	EX0 = 0;//当发生中断后关闭外部中断0
	vis_p36 = 0;//将LED闪烁标志位置0
	init_key = 1;//初始化标志位 第一次按下后就表示初始化完成 可以开始接受数据了
	if(vis_key == 1){//当接收到允许按键命令后 vis_key=1 之后按下按键就会将数据记录到数组中去
		send[3] = Local_address;//记录源地址 就是本机地址(P2口的数据)
		send[4] = TIME_BACK;
		send[5] = TL0;
		send[6] = TH0;
		send[7] = time_ms;
		send[8] = time_s;
		send[9] = time_m;
		send[10] = time_h;
		vis_p36 = 1;//按下按键后LED灯闪烁
		key_down = 1;//接收到按键命令后 按键按下标志位
	}
}


/*
发送数据
*/
void send_data(){
	unsigned char i,k = 0;
	if(key_down == 0 || vis_key == 0)return;//表示就收到时钟数据返回命令 但是还没有接收到按键允许命令或者接收到了按键允许命令但按键还没有按下 那么就退出
	p37 = 1;//将收发控制位置1表示发送数据
	ES = 0;//关闭串口中断 就是发送的时候不在接收数据
	TI = 0;//TI位置0 当有一条写SBUF操作就启动发送
	for(i = 0;i < 11;i++){//循环发送
		//p37 = 1;
		if(i)k += send[i];//计算校验和 不计算帧头 因为i=0不执行该语句
		SBUF = send[i];//写SBUF操作
		while(TI==0);//查询TI位 因为发送完成后硬件对对TI置1
		TI = 0;//置0开始发送下一个数据
	}
	SBUF = k;//发送校验和
	while(TI==0);
	TI = 0;
	SBUF = FRAME_FOOT;//发送帧尾
	while(TI==0);
	TI = 0;
	ES = 1;//开启串口中断 可以接收数据了
	
	p37  = 0;//接收控制位
	p36 = 0;//数据发送后熄灭Led
	vis_p36 = 0;//LED闪烁标志位置0
	vis_key = 0;//将按键允许清0
	key_down = 0;//按键按下清0
}


void data_handle1(){
	unsigned char i = 0,k = 0;
	P1 = ~P1;
	if(js_data[3] == 0x01){//按键允许命令
		for(i = 0;i < 4;i++)k += js_data[i];//计算校验和
		if(k != js_data[4])return;//校验和错误 退出
		//下面是校验正确后的操作
		p36 = 1;//LED长亮
		EX0 = 1;//开启外部中断 就是按下按键后就会进入中断
		vis_key = 1;//把按键允许开启 就是按下按键后就会记录数据了
	}
	if(js_data[4] == 0x03){//时钟数据返回命令
		for(i = 0;i < 5;i++)k += js_data[i];//计算校验和
		if(k != js_data[5])return;//校验和错误 退出
		TI = 0;
		send_data();//去发送数据	
	}
}


void data_headle2(){//状态机函数
//此函数不做解释 请看图理解
	switch(stare){
		case 0x00:{
			if(data_from_sbuf == FRAME_HEAD){
				stare = 0x01;
				head = 1;
			}else stare = 0x00;
			break;
		}
		case 0x01:{
			if(data_from_sbuf == 0x04){
				stare = 0x02;
				js_data[0] = 0x04;
			}else if(data_from_sbuf == 0x05){
				stare = 0x06;
				js_data[0] = 0x05;
			}else stare = 0x00;
			break;
		}
		case 0x02:{
			if(data_from_sbuf == BROAD_ADR || data_from_sbuf == Local_address){
				stare = 0x03;
				js_data[1] = data_from_sbuf;
			}else stare = 0x00;
			break;
		}
		case 0x03:{
			if(data_from_sbuf == MASTER_ADR){
				stare = 0x04;
				js_data[2] = MASTER_ADR;
			}else stare = 0x00;
			break;
		}
		case 0x04:{
			if(data_from_sbuf == 0x01){
				stare = 0x0a;
				js_data[3] = 0x01;
				len = 4;
			}else stare = 0x00;
			break;
		}
		case 0x0a:{
			js_data[len] = data_from_sbuf;
			stare = 0x0b;
			break;
		}
		case 0x0b:{
			if(data_from_sbuf == FRAME_FOOT){
				head = 0;
				js_t = 0;
				data_handle1();
			}
			stare = 0x00;
			break;
		}
		case 0x06:{
			if(data_from_sbuf == Local_address){
				stare = 0x07;
				js_data[1] = Local_address;
			}else stare = 0x00;
			break;
		}
		case 0x07:{
			if(data_from_sbuf == MASTER_ADR){
				stare = 0x08;
				js_data[2] = MASTER_ADR;
			}else stare = 0x00;
			break;
		}
		case 0x08:{
			if(data_from_sbuf == 0x03){
				stare = 0x09;
				js_data[3] = 0x03;
			}else stare = 0x00;
			break;
		}
		case 0x09:{
			js_data[4] = data_from_sbuf;
			stare = 0x0a;
			len = 5;
			break;
		}
		default:{
			stare = 0x00;
		}
	}
}


void funins()interrupt 4 {
	data_from_sbuf = SBUF;//将接收到的数据送给data_from_sbuf
	data_headle2();//进入状态机 判断当前状态和进入下一个状态
	RI = 0;//因为接受到数据后硬件会对RI位置1  清0准备接收下一个数据
}


void init(){//初始化函数
	p36 = 0;
	EA = 1;//开启总总段
	ET0 = 1;//允许定时器T0中断
	TMOD = 0x21; //T0:16位定时模式  T1:8位自动重装模式
	TH0 = 0x4b;
	TL0 = 0xfc;//因为50000/1.085 = 46083 65535-46083=19452=0x4bfc TH0=0x4b TL0=0xfc
	TR0 = 1;//开启定时器T0
	
	EX0 = 1;//允许外部中断0
	IT0 = 1;//开启外部中断0
	
	//设置定时器T1
	//波特率 = 2^SMON * 11.0592M /32*12*(256 - T0初)    T0 = 253=0xfd;
	TH1 = TL1 = 0xfd;
	TR1 = 1;//开启T1
	
	ES = 1;//允许串口中断
	SM0 = 0;
	SM1 = 1;//工作于方式一 8位异步通信
	RI = 0;
	SM2 = 1;
	REN = 1;//允许接收数据
	p37 = 0;
}


void main(){
	init();
	while(init_key == 0);
	p36 = 0;
	while(1){
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_44873932/article/details/106380895