基于51单片机的通讯聊天系统

一、实践的目的

通过编写51单片机通讯聊天程序,掌握51单片机的各个模块的知识点和运用C语言编写51单片机的能力。

二、实践内容

用51单片机与口袋开发板设计实现一个单片机与电脑串口助手实现通讯功能的程序。程序的主要功能如下:
1 OLED初始化显示Welcome to Bluetooth communication! 字符串。
2 程序开始时处于接收信息模式, 电脑串口助手发送信息时,将从初始化字符第一个开始显示,电脑串口助手发送信息后,蜂鸣器响铃2秒左右。
3 每次同时按下左右键时,可以轮流切换接收/发送信息模式
4 在发送信息模式时,可以显示光标(第一次处于发送信息模式时光标显示在OLED的左上角),可以通过左右键、上下键控制光标移动
5 在发送信息模式下, 每次同时按下上下键, 可以将光标选中的字符发送到电脑串口助手,发送完字符后, led流水灯流动两次。

三、实践依托的实验设备与软件

  1. 口袋实验室
  2. Aultium Designer
  3. 使用的开发软件 : keil4、stc-isp-15xx-v6.28烧录软件
  4. PC电脑

四、硬件原理图

在这里插入图片描述在这里插入图片描述

二、相关元器件特性分析

原器件名称元器件特性简介
单片机 Stc89c52系统运行的的核心
手柄板系统运行的基础
OLED可以显示字符或字符串
蜂鸣器接收信息时,可以响铃
LED灯 D1-D8发送信息后,流水灯流动两次后熄灭
独立按键切换接收/发送模式,发送信息时控制光标移动
蓝牙模块与手机实现通讯,显示单片机发送的字符或字符串

五、主要算法流程图及描述

在这里插入图片描述

六、问题的分析与编写程序步骤与实现的过程

1、通过蓝牙模块与手机APP“SPP蓝牙串口”实现连接

将HC蓝牙模块通过杜邦线与开发板进行接线的连接。接线的方式是:将蓝牙模块RXD、TXD、GND、VCC分别与单片机的TX(单片机的串口发送脚)、RX(单片机串口接收脚)、GND、+5V相连接。接好线后如下图所示

在这里插入图片描述
打开手机蓝牙串口APP选择蓝牙HC-5,连接成功后如下图所示
在这里插入图片描述

蓝牙模块的指示灯没有快闪,说明连接成功了。

2、创建工程与编写OLED显示字符串的程序

通过软件Keil uVision4创建51单片机实现通讯工程后,新建两个文件,分别命名为Oled.h头文件与Oled.c源文件。
Oled.h头文件将变量的定义户函数的声明包含进来。因为OLED已经有示例程序提供给我们了,所以由我们系统要实现的功能,将相关的函数声明从示例程序赋值过来。如下图所示
在这里插入图片描述
OLED_Init()、OLED_Fill()、OLED_CLS()、OLED_WrCmd(byte)、OLED_WrDat(byte)是OLED工作的重要函数。这些函数是OLED液晶显示屏显示文字或图像等的基础。我们系统要显示的是文字,所以用void OLED_P8x16Str(byte, byte, byte[])函数将要显示的数据写入OLED中就可以显示我们想要显示的数据了。void OLED_Set_Pos(byte, byte )函数是用来设置数据显示在OLED屏幕的何处。它可以将我们要显示的数据放在指定的位置显示。
Oled.c源文件是将Oled.h头文件的声明的函数实现实现具体的功能。
现在新建两个文件保存为Main.c的源文件盒Main.h的头文件。在Main.h头文件中编写如下的代码

#ifndef _Main_H_
#define _Main_H_

#include <reg52.h> 

#define UINT unsigned int
#define UCHAR unsigned char

UINT i, index = 0;

UCHAR dataChar;		 		 
UCHAR dataString[64] = {"Welcome to Bluetooth communication!"}; 	// 单片机接收信息时, 用数组存放接收的信息

#endif

在Main.c源文件中编写如下代码

#include "Main.h"
#include "Oled.h"

void main()
{
	OLED_Init();  		  // OLED初始化

	OLED_Fill(0x00);	  // OLED全屏

	while(1)
	{
		OLED_P8x16Str(0, 0, dataString);   // 显示单片机接收的字符或字符串
}
}

编译程序,将生成的.hex文件用STC烧写软件-ISP-V6.82E软件将烧入到单片机中,单片机运行的效果如下图所示
在这里插入图片描述
现在已经成功实现OLED显示字符串了。

3、通过串口通断实现发送信息于OLED屏幕显示

上一步我们实现了OLED显示字符串的功能。现在我们将用串口中断实现电脑串口助手发送字符或字符串信息后,OLED屏幕显示发送的字符或字符串,同时蜂鸣器响铃1秒左右。
首先进行编写的是串口中断处理的初始化程序。在Main.h中声明void timer_portInit(); 函数。然后在Main.c中编写void timer_portInit()函数,如下所示

void timer_portInit()
{
	SM0 = 0;				//设置串口为工作方式1
	SM1 = 1;

	TMOD=0X20;			//设置计数器工作方式2
	PCON=0X80;			//波特率加倍

	TH1=0XFa;			//计数器初始值设置,注意波特率是9600的
	TL1=0XFa;

	EA=1;						// 打开总中断

	ES=1;						// 打开串口中断
	REN = 1;						// 允许串口接收数据

	TR1=1;						// 打开定时器T1	
}

串口中断初始化程序已经编写完成了,现在我们编写串口中断处理的函数

void serialPort() interrupt 4
{
	dataChar = SBUF;	  			// 读入串口接收到的数据
	RI = 0;		  				// 中断标志位置零
	
dataString[index] = dataChar;		// 单片机接收的数据存放与数组中
	index++;
	
	}
}

中断处理程序编写完成,现在我们进行蜂鸣器在单片机接收信息后响铃2秒左右。在Main.h中定义sbit beep = P1^5; // 蜂鸣器,定义UINT beepTime = 300; UINT flagReceive = 0; 两个变量,分别对蜂鸣器响铃的时间进行控制、串口中断接收信息的标志控制蜂鸣器响铃,当flagReceive为1时就让蜂鸣器响铃。现在要改变串口中断处理程序的代码,将
dataString[index] = dataChar; // 单片机接收的数据存放与数组中
index++;
改成如下代码

if(funcSelect == 0)			// 单片机接收的数据存放与数组中
{
	dataString[index] = dataChar;	
	index++;
	
	flagReceive = 1;  		
}

现在在main函数中编写如下代码:在OLED_Fill(0x00); 语句的后面加入timer_portInit();语句,完成定时串口中断初始化。然后在while(1)循环中加入如下的代码

while(flagReceive == 1 && beepTime > 0)		 // 单片机接收信息后, 蜂鸣器响铃
	{
		beep = ~beep;      //字符串接收完成,蜂鸣器响
		delay(1);			   

		beepTime--;
	}
	beepTime = 300; 
	flagReceive = 0;

现在编译程序,将生成的hex文件烧入单片机中,电脑串口助手发送信息后的效果如下图所示
在这里插入图片描述

现在已经实现了通过串口通断实现发送信息于OLED屏幕显示,并且蜂鸣器响铃的功能了。

4、 OLED显示光标与按键控制光标移动

上一步我们通过实现了串口通断发送信息于OLED屏幕显示的功能,现在我们要实现系统处于发送模式时:OLED 屏幕显示光标;用按键进行接收/发送模式的切换;用按键控制光标的移动来选择我们要发送给电脑串口助手的字符数据。
首先是要实现的是发送/接收信息模式的功能,这个功能的实现是要用按键来处理模式转换的功能。在Main.h中我们定义按键的处理变量如下

sbit left = P2^1;	//向左按键
sbit right = P2^2;	//向右按键
sbit up = P2^0;     //向上按键
sbit down = P3^3;   //向下按键

这里我们定义了开发板上的按键A4、A5、A6、A7分别是变量left、right、up、down,完成了左右、上下键的的定义。进行模式切换时,是每次通过同时按下左右键可以进行接收/发送模式的转换。
现在我们要实现的是OLED屏幕显示光标。显示光标的功能是通过调用Oled.c文件中的OLED_WrDat(0xFF)函数来实现的。0xFF表示写入OLED的数据是在OLED屏幕上显示的是白底。它与OLED_Fill(0xFF);写入OLED命令的功能是一样的,在OLED屏幕上全屏显示白底的功能。用这个OLED_WrDat来显示光标的功能,提前就要设置光标显示在OLED 屏幕的何处。比如通过调用Oled.c文件中的OLED_Set_Pos(0,0)函数就设置了OLED的坐标于屏幕的左上角的开始位置处。然后通过调用OLED_WrDat(0xFF)就可以在屏幕的左上角的位置处显示一条细细的竖直线。我们显示的字符的宽度是8*16的字体,所以我们通过for循环来调用OLED_WrDat()函数就可以在OLED屏幕的显示字符处显示与字符同样大小的光标了。现在我们在Main.h文件中定义如下的数组变量并初始化

UCHAR code closeCursor[8]=					// 清除光标
{
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

UCHAR code openCursor[8]=					// 显示光标
{
	0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};

我们通过调用OLED_WrDat(openCursor[i])函数就可以将数组中的数据写入OLED中了。
现在要实现的是按键处理的功能,在Main.h中定义如下的变量和声明函数

char funcSelect = 0;   			// 非 0 时发送信息模式
  char flagSend = 0;				// 非 0 时单片机发送信息
char keyX = 0, keyY = 0;   			// 光标显示的坐标

void keyLeftRight();	 			// 按键处理
void keyUpDown();

void sendData();  				// 单片机发送数据
void sendMode();				// 发送信息模式

keyX与keyY变量都初始化为0 ,第一次模式切换到发送模式时,通过调用OLED_Set_Pos(keyX, keyY); 语句就可以在OLED 屏幕左上角的位置处显示光标了,keyX与keyY变量的值是在按键处理中,每次通过按键进行改变上下左右移动改变时改变的。
funcSelect变量初始化为0,当我们每次同时按下左右键时,通过执行funcSelect = ~funcSelect;语句后,可以在0与非0之间变换。

现在进行左右键功能的编写,在Main.c中实现keyLeftRight的功能,其代码如下所示

void keyLeftRight()
{
	if(left == 0)  //按往左键时
	{
		delay(10);
		if(left == 0 && right == 0)	 	//左右键同时按下时, 进行接收/发送模式切换
		{							 	// funcSelect: 为0 是接收信息模式, 非0是发送信息模式
			funcSelect = ~funcSelect;
			if(funcSelect == 0)
			{
				OLED_CLS();
			}
		}
		else if(left == 0 && right != 0 && funcSelect != 0)  
		{
			OLED_Set_Pos(keyX, keyY);	
			for(i = 0; i < 8; i++)
				OLED_WrDat(closeCursor[i]); 	// 关闭当前的光标
	
			OLED_Set_Pos(keyX, keyY + 1);	
			for(i = 0; i < 8; i++)
				OLED_WrDat(closeCursor[i]);
	
			keyX -= 8;							// 改变光标X轴的位置
	
			if(keyX < 0)						
			{
				keyX = 120;
			}
		}
		while(!left); //消抖 
	}
	
	if(right == 0)  //按往右键时
	{
		if(funcSelect != 0)
		{
			OLED_Set_Pos(keyX, keyY);	
			for(i = 0; i < 8; i++)
				OLED_WrDat(closeCursor[i]); 
	
			OLED_Set_Pos(keyX, keyY + 1);	
			for(i = 0; i < 8; i++)
				OLED_WrDat(closeCursor[i]);
	
			keyX += 8;
	
			if(keyX > 120)
			{
				keyX = 0;
			}
		}
		while(!right); //消抖
	}	
}

keyLeftRight()函数的功能就是:通过每次按下左右的时,可以通过改变funcSelect的值来控制sendMode()函数功能的实现;分别按下左右键的时候就可以实现光标的左右移动和关闭移动之前显示的光标位置处的光标;执行keyX += 8; 语句之后,就可以改变keyX的值了,在主函数里面通过死循环不断的调用sendMode()函数就可显示keyX改变之后的坐标处的光标了。

现在进行上下键功能的编写,在Main.c中实现keyUpDown的功能,其代码如下所示

void keyUpDown()
{
	if(up == 0)  //按往上键时
	{
		if(up == 0 && down == 0)	//上下键同时按下时控制单片机发送字符
		{
			flagSend = 1;
		}
		else if(up == 0 && down != 0 && funcSelect != 0) 
		{
			OLED_Set_Pos(keyX, keyY);	
			for(i = 0; i < 8; i++)
				OLED_WrDat(closeCursor[i]); 
	
			OLED_Set_Pos(keyX, keyY + 1);	
			for(i = 0; i < 8; i++)
				OLED_WrDat(closeCursor[i]);
	
			keyY -= 2;
	
			if(keyY < 0)
			{
				keyY = 6;
			} 
		}
	}
	while(!up); //消抖
	
	if(down == 0)  //按往下键时
	{
		if(funcSelect != 0)
		{
			OLED_Set_Pos(keyX, keyY);	
			for(i = 0; i < 8; i++)
				OLED_WrDat(closeCursor[i]); 
	
			OLED_Set_Pos(keyX, keyY + 1);	
			for(i = 0; i < 8; i++)
				OLED_WrDat(closeCursor[i]);
	
			keyY += 2;
	
			if(keyY > 6)
			{
				keyY = 0;
			}	
		}
	}
	while(!down); //消抖	
}

上下键改变光标的位置的原理和左右键改变光标的位置的原理是一样的。上下键还要实现的功能就是每次同时按下上下键时,可以将光标处的字符发送给电脑串口调式助手。发送字符是通过flagSend变量的值进行控制的。显示光标的功能是通过sendMode函数实现的,具体的代码与功能的实现是在下一步的讲解中。

主函数中,在OLED_P8x16Str(0, 0, dataString); 后面编写如下代码

keyLeftRight(); // 按键处理
keyUpDown();
sendMode(); // 模式转换检测

在主函数中通过函数调用实现按键处理的功能。
编译程序,将生成的hex文件烧入单片机中。OLED 屏幕屏幕显示光标如下图所示
在这里插入图片描述
现在我们已经实现了通过按键进行模式切换、OLED 屏幕显示光标、按键控制光标移动的功能了.

5、光标处的字符发送给电脑串口助手显示与流水灯

上一步我们实现了OLED显示光标与按键控制光标移动的功能,现在要实现的功能是通过同时按下上下键将光标处的字符发送给电脑串口调式助手于接收区内显示,在发送完字符后8个led灯实现流水灯流动两次的效果。
首先是实现发送光标处的字符功能。在上一步我们已经在Main.h中声明了sendData()与sendMode()函数。sendData()函数是实现单片机发送数据的功能,sendMode()函数是实现单片机处于发送模式与调用sendData()函数发送数据的功能。

现在实现sendMode函数的功能,在Main.c中编写代码如下

void sendMode()
{
	if(funcSelect)		//发送模式时
	{
		while(flagSend == 1) 		// 发送字符标志
		{
			sendData();				// 发送数据给电脑串口助手
			flagSend = 0;
		}

		OLED_Set_Pos(keyX, keyY);			// 打开光标
		for(i = 0; i < 8; i++)
			OLED_WrDat(openCursor[i]); 
	
		OLED_Set_Pos(keyX, keyY + 1);	
		for(i = 0; i < 8; i++)
			OLED_WrDat(openCursor[i]);
	}
	else
		flagSend = 0;
}

当处于发送模式时,funcSelect变量是非零执行if语句之后就可以显示光标了。每次按上下左右键之后,也是在主函数中通过死循环不断调用sendMode()函数实现的。当处于发送模式时,同时按下上下键时,flagSend变量的值就变为1了。执行if条件语句中的while语句,调用sendData()函数将光标处的字符发送给单片机。
现在实现sendData函数的功能,在Main.c中编写代码如下

void sendData()
{
	dataChar = keyX / 8 + keyY * 8;	   // 获取光标处的字符

	SBUF = dataString[dataChar];    //发送光标处的字符
	while(!TI);

	flagLed = 1;

	TI = 0;
}

dataChar = keyX / 8 + keyY * 8; 语句是用dataChar变量来存放光标处的字符在dataString数组中的下标位置,然后通过dataString[dataChar]就可以访问dataString数组中的字符数据了,也就是处于光标处的字符数据。
SBUF = dataString[dataChar]; //发送光标处的字符
while(!TI);
这两行代码的功能是将光标处的字符存放于寄存器SBUF中,然后发送给电脑串口调式助手与手机的SSP蓝牙串口APP上显示。每次发送完数据后,要将TI中断标志置零,为下一次的串口中断成功发送数据做好准备。光标处的字符发送给电脑串口调试助手与手机的SSP蓝牙串口APP上的效果如下图所示
在这里插入图片描述
在这里插入图片描述
现在要实现的功能是单片机发送完数据后,LED灯实现流水灯的功能。在sendData()函数中,当每次发送完数据之后,flagLed变量的值就为1。
现在在Main.h中定义流水灯初始化的数组和变量如下

UINT ledData[8] = 							// led流水灯初始化
{
	0xFE, 0XFD, 0XFB, 0XF7,
	0xEF, 0XDF, 0XBF, 0X7F
};

UINT ledTime = 2; // ledTime: 控制流水灯的次数
0xFE是表示8个LED灯中的D1灯为低电平,其他的七个LED灯处于高电平,所以访问ledData数组中的第一个数据时,只有第一个led灯点亮,其他的七个LED灯熄灭。

现在在主函数中的while(1)语句中编写如下代码实现流水灯的功能

if(flagLed == 1)						  // 单片机发送信息后, 流水灯流动两次
{
	while(ledTime > 0  )
	{
		for(i = 0; i < 8; i++)
		{
			P1 =ledData[i];
			delay(100);
		}
		ledTime--;	
	}

	flagLed = 0;
	ledTime = 2;
					// 流水灯时, led和OLED共用P1^2和P1^3引脚
	OLED_Init();		// 流水灯流动完后, 防止OLED出现乱码, 所以重新初始化OLED
	OLED_Fill(0x00);
}

在实现流水灯流动两次之后,将flagLed变量赋值0和ledTime变量赋值2,为下一次的发送数据后实现流水灯的效果做准备。因为流水灯时,led和OLED共用P12和P13引脚,所以流水灯流动完后, 防止OLED屏幕出现乱码, 要重新初始化OLED,OLED屏幕就可以正常显示字符数据了。
编译程序,将生成的hex文件烧入单片机中。运行单片机,发送数据后流水灯流动的效果录制了“发送数据后LED实现流水灯”的视频文件与程序文件夹中。
现在已经实现了发送光标处的数据、发送数据后流水灯流动的效果了。

到这一步,我们的通讯功能的系统已经全部完成了。

七、总结

学习51单片机提高了我们对于C语言的编程能力,从最简单的点亮LED灯开始,就用到了C语言的循环的知识点。在LED流水灯中,我们组运用了C语言的变量、数组存储我们的数据、用顺序结构与循环结构相结合进行了不同算法的编程实现同一种功能。在许多的简单的编程中我们运用了C语言函数进行模块化的编程,将实现一种功能分为几个小的功能进行模块化编程。在进行51单片机的过程中,让我们可以分析实际的问题,并运用C语言解决实际的问题。让我们对于C语言有了进一步的掌握与C语言编程的能力。
在这里插入图片描述

  • 22
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程爱好者-阿新

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

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

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

打赏作者

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

抵扣说明:

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

余额充值