基于51单片机的一主多从交互通信方案

概要

基于51单片机的一主多从交互通信方案。使用protues仿真成功,暂未实物验证。

整体架构流程

在平常使用串口时,经常觉得串口不够使用,特别是在多硬件外设的情况下。随着现在社会发展,一些单片机价格非常便宜(stc8只要8毛一片)

那我们是不是能使用多片芯片来减少我们主机负担或者不用采购高价格的多串口芯片呢?

所以我按照这个想法想了一种通过软件和多单片机的方案。

 

上图中更改自定义命令字的定义即可自定义其更多功能,实现相互通信只要使用一位标志位即可。

其中发送函数和接收函数都是主机软件应该解决的,接收中断则时从机要干的事情!

主机可接收特定从机信息,从机也可接收主机的数据和命令。

我认为如果你要改善应该注意主机占有绝对领导权,这个问题。小心处理出现多个大脑这种危险操作。

具体代码实现可以看下面的技术细节

 技术细节

主机代码: 

#include <STC89C5xRC.H>
#include "UART.h"
#include "Timer0.h"
#include "Key.h"
#include "Delay.h"

unsigned char R_Slave_Num=0;//标志接收命令已经发出准备好接受从机的数据
unsigned char R_Buff[]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};//判断发送的状态存放数据
unsigned char KeyNum=0,temp;

/**
  * @brief  主机向指定从机发送一个字节数据
  * @param Slave_Num-从机的编号*****范围1~16
	* @param message-要发送的数据
  * @retval 无
  */
void Master_SendByte(unsigned char Slave_Num,unsigned char message)
{
	 UART_SendByte(Slave_Num<<4);
		Delay(20);
	 UART_SendByte(message);
}
/**
  * @brief  主机接收从机的一个字节数据
  * @param Slave_Num-从机的编号*****范围1~16
  * @retval 无
  */
void Master_ReciveByte(unsigned char Slave_Num)
{
	 R_Slave_Num=Slave_Num;
	 UART_SendByte((Slave_Num<<4)|0x08);
}


void keyscan(void)
{
	KeyNum=Key();
	if(KeyNum)
		temp=KeyNum;
	switch (temp)
	{
		case 1:Master_SendByte(1,0x55);temp=0;break;
		case 2:Master_ReciveByte(4);temp=0;break;
		case 3:Master_SendByte(3,0x55);temp=0;break;
		case 4:Master_SendByte(3,0xaa);temp=0;break;
	}

}

void main(void)
{
	UART_Init();
	Timer0_Init();
	while(1)
	{
		keyscan();
		P2=R_Buff[4];

}
	}

	void UART_Routine()  interrupt 4 //串口中断
{
	if(RI==1)//过滤发出中断干扰
{
	if(R_Slave_Num)
	{
		R_Buff[R_Slave_Num]=SBUF;
		R_Slave_Num=0;
	}
	RI=0;//软件复位	
}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1,T0Count2;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
		T0Count1++;T0Count2++;//每1ms进行一次++;
		if(T0Count1>=20)//20ms调用一次Key_Loop()函数
		{
			T0Count1=0;
			Key_Loop();
		}
		
}

 从机代码:

#include <STC89C5xRC.H>
#include "UART.h"

unsigned char temp,R_Buff=0xff,T_Buff=0x33;//存放串口接收到的数据
unsigned char uart_flag=0;//0代表收命令1代表接收数据

void main(void)
{
	UART_Init();
	while(1)
	{
		P2=R_Buff;
	}

}

  void UART_Routine()  interrupt 4 //串口中断
{
	if(RI==1)//过滤发出中断干扰
	{
		temp=SBUF;
		
		if(uart_flag==1)//如果接收标志为1,则把数据接收到R—buff里
		{
			R_Buff=SBUF;
			uart_flag=0;//并把标志位置0,为下一次主机命令做准备
		}
		
		else//否则接收标志为0,表示当前状态为收命令
		{
			if((temp&0xF8)==0x48)//判断命令为本地址且为发数据
			{
				UART_SendByte(T_Buff);
			}
			else if((temp&0xF8)==0x40)//判断命令为本地址且为收数据则置标志位
			{												//为下一次做准备
				uart_flag=1;
			}
		
		}
	}	
		RI=0;//软件复位
}

Timer0.c内容:

/**
  * @brief  定时器初始化
  * @param 无
  * @retval 无
  */
	void Timer0_Init(void)//1毫秒@11.0592MHz
	{
		AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
		ET0=1;
		EA=1;
		PT0=0;
	}

Uart.c内容:

/**
  * @brief  串口初始化
  * @param  无
  * @retval 无
*/
void UART_Init()//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x20;		//设置定时器模式
	TL1 = 0xFD;		//设置定时初始值
	TH1 = 0xFD;		//设置定时重载值
	ET1 = 0;				//禁止定时器%d中断
	TR1 = 1;				//定时器1开始计时	
	EA=1;						//启动所有中断
	ES=1;						//启动串口中断
}
/**
* @brief  串口发送一个字节数据
* @param  Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);//检测发送完成标志位
	TI=0;//该位必须软件复位
}

小结

该方式只是我短暂思考后得出的方法,我觉得肯定是有更优的方案解决这个问题。在单片机越来越便宜的趋势下,我觉得除了增加主机的引脚这种多从机的方案也是能占一方势力!希望我的想法能给大家带来一丝帮助或者是灵感,欢迎讨论交流!

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
要设计一个基于51单片机的多功能日历时钟,需要考虑以下几个方面: 1. 时钟模块:可以使用DS1302或DS1307实时时钟模块,通过SPI接口与单片机通信,获取当前时间。 2. 显示模块:可以使用数码管、LCD液晶显示屏或OLED显示屏等显示模块,用于显示时间、日期、闹钟等信息。 3. 输入模块:可以使用按键或旋转编码器等输入模块,用于设置时间、日期、闹钟等信息。 4. 控制模块:主要是单片机,负责控制时钟模块、显示模块和输入模块之间的交互,以及处理闹钟等功能。 下面是一个简单的基于51单片机的多功能日历时钟的程序框架: ``` #include <reg52.h> #include <intrins.h> #include <DS1302.h> //时钟模块库 #include <LCD1602.h> //LCD液晶显示屏库 #include <key.h> //按键库 sbit LED = P1^0; //LED灯引脚 unsigned char year,month,day,week,hour,minute,second; //时间参数 unsigned char alarm_hour,alarm_minute; //闹钟参数 void main() { Init_DS1302(); //初始化时钟模块 Init_LCD1602(); //初始化LCD液晶显示屏 Init_Key(); //初始化按键 while(1) { Get_Time(); //获取当前时间 Show_Time(); //显示当前时间 if(Check_Key() == 1) //检测到按键按下 { Set_Time(); //设置时间 } Check_Alarm(); //检测闹钟 Delayms(500); //延时500ms LED = ~LED; //LED闪烁 } } void Get_Time() { year = DS1302_Read(0x8d); //读取年份 month = DS1302_Read(0x89); //读取月份 day = DS1302_Read(0x87); //读取日期 week = DS1302_Read(0x8b); //读取星期 hour = DS1302_Read(0x85); //读取小时 minute = DS1302_Read(0x83); //读取分钟 second = DS1302_Read(0x81); //读取秒钟 } void Show_Time() { LCD_Clear(); //清屏 LCD_Write_String("20"); //显示年份 LCD_Write_Char(year/10 + '0'); //显示十位数 LCD_Write_Char(year%10 + '0'); //显示个位数 LCD_Write_Char('-'); //显示分隔符 LCD_Write_Char(month/10 + '0'); //显示月份 LCD_Write_Char(month%10 + '0'); LCD_Write_Char('-'); LCD_Write_Char(day/10 + '0'); //显示日期 LCD_Write_Char(day%10 + '0'); LCD_Write_Char(' '); LCD_Write_Char(hour/10 + '0'); //显示小时 LCD_Write_Char(hour%10 + '0'); LCD_Write_Char(':'); LCD_Write_Char(minute/10 + '0'); //显示分钟 LCD_Write_Char(minute%10 + '0'); LCD_Write_Char(':'); LCD_Write_Char(second/10 + '0'); //显示秒钟 LCD_Write_Char(second%10 + '0'); } void Set_Time() { unsigned char temp; LCD_Clear(); //清屏 LCD_Write_String("Set Time"); //显示设置时间 LCD_Set_Cursor(0,1); //设置光标位置 LCD_Write_String("Year:20"); temp = Read_Key(); //读取按键 year = temp*10 + Read_Key(); //计算年份 LCD_Set_Cursor(8,1); //设置光标位置 LCD_Write_Char(year/10 + '0'); LCD_Write_Char(year%10 + '0'); LCD_Set_Cursor(0,2); //设置光标位置 LCD_Write_String("Month:"); temp = Read_Key(); //读取按键 month = temp*10 + Read_Key(); //计算月份 LCD_Set_Cursor(7,2); //设置光标位置 LCD_Write_Char(month/10 + '0'); LCD_Write_Char(month%10 + '0'); LCD_Set_Cursor(0,3); //设置光标位置 LCD_Write_String("Day:"); temp = Read_Key(); //读取按键 day = temp*10 + Read_Key(); //计算日期 LCD_Set_Cursor(5,3); //设置光标位置 LCD_Write_Char(day/10 + '0'); LCD_Write_Char(day%10 + '0'); LCD_Set_Cursor(0,4); //设置光标位置 LCD_Write_String("Hour:"); temp = Read_Key(); //读取按键 hour = temp*10 + Read_Key(); //计算小时 LCD_Set_Cursor(6,4); //设置光标位置 LCD_Write_Char(hour/10 + '0'); LCD_Write_Char(hour%10 + '0'); LCD_Set_Cursor(0,5); //设置光标位置 LCD_Write_String("Minute:"); temp = Read_Key(); //读取按键 minute = temp*10 + Read_Key(); //计算分钟 LCD_Set_Cursor(8,5); //设置光标位置 LCD_Write_Char(minute/10 + '0'); LCD_Write_Char(minute%10 + '0'); LCD_Set_Cursor(0,6); //设置光标位置 LCD_Write_String("Save?"); //询问是否保存 temp = Read_Key(); //读取按键 if(temp == 1) //保存时间 { DS1302_Write(0x8e,0x00); //禁止写保护 DS1302_Write(0x8d,year); //写入年份 DS1302_Write(0x89,month); //写入月份 DS1302_Write(0x87,day); //写入日期 DS1302_Write(0x8b,week); //写入星期 DS1302_Write(0x85,hour); //写入小时 DS1302_Write(0x83,minute); //写入分钟 DS1302_Write(0x81,second); //写入秒钟 DS1302_Write(0x8e,0x80); //恢复写保护 } } void Check_Alarm() { if(hour == alarm_hour && minute == alarm_minute) //检测到闹钟 { //触发闹钟 //... } } ``` 以上是一个简单的程序框架,具体实现还需要根据具体的硬件和需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SFR-小曾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值