欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

打造嵌入式智能系统:使用GEC6818实现开关灯、消消乐游戏和蓝牙通信功能

最编程 2024-08-09 08:28:21
...

一、主要功能

进入主界面后,主界面是个人UI设计,有四个功能按钮模块,分别是:信息、灯光、娱乐(消消乐)、蓝牙。

二、涉及知识

C语言,文件io,bmp/jpg图片解析,Linux串口通信,linux基础

三、开发环境

嵌入式开发板型号GEC6818,开发平台Ubuntu18.04,代码编辑器source insight 4.0

四、主要模块

1.LCD显示模块

        通过open打开开发板LCD屏幕驱动,驱动文件(/dev/fb0),进行屏幕初始化。

/***************************************
	显示屏相关的一些功能函数
***************************************/


#include "lcd.h"
static int * plcd=NULL;
static int lcd_fp=-1;
#define LCD_PATH "/dev/fb0" //文件路径
/*
	lcd屏幕初始化
*/
int lcd_Init(void)
{
	lcd_fp=open(LCD_PATH,O_RDWR);
	if(lcd_fp==-1)
	{
		perror("open failed:");
		return -1;
	}
	plcd=mmap(NULL,LCD_HEIGHT*LCD_WIDTH*4,PROT_WRITE|PROT_READ,MAP_SHARED,lcd_fp,0);
	if(plcd == MAP_FAILED)
	{
		perror("MMAP Failed");
		return -1;
	}
	return 0;
}

        开发板是通过像素打点的形式显示图片,于是需要封装一个像素打点函数

/*
	像素打点函数
*/
void Lcd_Draw_Point(int x,int y,int color)
{
		*(plcd + LCD_WIDTH*y + x) = color;
}

        然后通过系统IO(read)和标准IO(fread)读取bmp及jpg图片像素数据。其中jpg文件为压缩文件需先进行解压缩操作,通过write写入屏幕驱动里面。具体操作需要将模块封装成函数Display_Bmp(),以便调用。对JPEG图片的显示通过jpeglib库实现,具体函数已封装为Display_Jpg()。因本工程所使用的素材皆为jpg图片,故这里只放出jpg显示函数。

#include "jpg.h"

/***************************************
	跟JPG图片显示相关的功能模块(函数)
***************************************/

/*
	将jpgpath所指向的JPG图片显示到屏幕的(x0,y0)处
		成功显示返回0,失败返回-1	
*/
int Display_Jpg(int x,int y,const char *jpgpath)
{
	struct jpeg_decompress_struct dinfo;	//定义一个解压对象
	struct jpeg_error_mgr jerr;				//定义一个出错信息的对象

	dinfo.err = jpeg_std_error(&jerr);
	jpeg_create_decompress(&dinfo);

	//用标准IO打开JPG图片
	FILE *infile = fopen(jpgpath,"r");
	if(infile == NULL)
	{
		perror("Fopen JPG Failed");
		return -1;
	}
	jpeg_stdio_src(&dinfo,infile);

	jpeg_read_header(&dinfo,TRUE);

	jpeg_start_decompress(&dinfo);

	unsigned char *buffer = malloc(dinfo.output_width * dinfo.output_components);
	unsigned char a = 0,r,g,b;
	unsigned int color;
	int i = 0;
	//output_scanline保存的是当前已经扫描了多少行,初始化为0
	while(dinfo.output_scanline < dinfo.output_height)
	{
		i = 0;
		//第一个参数表示解压对象
		//第二个参数表示保存解压后数据的二级指针
		//第三个参数表示读取多少行数据来进行解压
		jpeg_read_scanlines(&dinfo,&buffer,1);
		//每扫描一行output_scanline + 1

		int j = 0;
		for(j = 0;j < dinfo.output_width;j++)
		{
			if(dinfo.output_components == 4)
			{
				a = buffer[i++];
			}
			r = buffer[i++];
			g = buffer[i++];
			b = buffer[i++];
			color = (a << 24) | (r << 16) | (g << 8) | b;
			Lcd_Draw_Point(x+j,y+dinfo.output_scanline-1,color);
		}
	}

	jpeg_finish_decompress(&dinfo);

	jpeg_destroy_decompress(&dinfo);

	free(buffer);
	fclose(infile);
	
	return 0;
}

主系统显示效果

2.触摸屏模块

        打开触摸屏的驱动文件 /dev/input/event0,然后调用read从触摸文件读取触摸信息,通过判断触摸坐标来确定触摸点位置,从而使能相关按键

#include "touch.h"
#include "main.h"
/***************************************
	跟触摸屏相关的一些功能函数
***************************************/

int Get_Touch(void)
{
	//打开触摸屏文件
	int fd_touch = open(TOUCH_PATH,O_RDWR);
	if(fd_touch == -1)
	{
		perror("open touch file failed:");
		return -1;
	}
	
	int ret;
	struct input_event ev;
	int Tx=0,Ty=0,Lx=0,Ly=0;
	int x = -1,y = -1,z=-1,i=1,j=3;
		while(1)
		{	
			ret = read(fd_touch,&ev,sizeof(ev));
			if(ret != sizeof(ev))
			{	
				continue;
			}
			if(ev.type == EV_ABS && ev.code == ABS_X)
			{
				//x轴的坐标输入事件
				x = ev.value;
			}
			else if(ev.type == EV_ABS && ev.code == ABS_Y)
			{
				//y轴的坐标输入事件
				y = ev.value;			
			}
			if(j!=0)
			{
				j--;
				continue;
			}
			if (i)
			{
				if(x >= 0 && y >= 0 && ev.value != 0)
				{
					//printf("Touch Point [ %d %d ]\n",x,y);
					Tx=x*800/1024;
					Ty=y*480/600;
					printf("-------------------------");
					printf("Touch Point [ %d %d ]\n",Tx,Ty);
					i--;
				}
			}
			else if(ev.type == EV_KEY && ev.code == BTN_TOUCH && ev.value == 0)
			{
				//手指离开了屏幕
				//printf("Leave Point [ %d %d ]\n",x,y);
				Lx=x*800/1024;
				Ly=y*480/600;
				
				printf("-------------------------");
				printf("Leave Point [ %d %d ]\n",Lx,Ly);
				i=1;				
				j=3;
			}
			
			if(abs(Lx-Tx)<200 && abs(Ly-Ty)<200 && Tx>=200 && Tx<620 && Ty>=30 && Ty<450)//交互判断
			{
				panduan(Tx,Ty,Lx,Ly);
				Tx=0;
				Ty=0;
				Lx=0;
				Ly=0;
			}
			if(Tx>=650&&Ty>=300&&Ty<=360)//new game
			{
				map_interface();
			}
			if(Tx>=650&&Ty>=390&&Ty<=450)//quit game
			{
				//Display_Jpg(0,0,PATH_quitgame);
				printf("游戏结束\n");
				key();
			}
			
		}
	
	return 0;
}

3.串口通信

        由于本项目中需要用到蓝牙模块与开发板通信从而操控响应相关功能按键,所以需要进行串口初始化。串口相关代码如下:

#include "Uart.h"
int Uart_Init(const char * uart_name)
{
	/*设置串口
	  波特率:9600
	  数据位:8
	  校验位:不要
	  停止位:1
	  数据流控制:无
	  */
	
	int   uart_fd= open(uart_name, O_RDWR);//打开串口1设备文件
	if (uart_fd == -1)
	{
		perror("open error:");
		return -1;
	}

	struct termios myserial;
	//清空结构体
	memset(&myserial, 0, sizeof (myserial));
	//O_RDWR               
	myserial.c_cflag |= (CLOCAL | CREAD);
	//设置控制模式状态,本地连接,接受使能
	//设置 数据位
	myserial.c_cflag &= ~CSIZE;   //清空数据位
	myserial.c_cflag &= ~CRTSCTS; //无硬件流控制
	myserial.c_cflag |= CS8;      //数据位:8

	myserial.c_cflag &= ~CSTOPB;//   //1位停止位
	myserial.c_cflag &= ~PARENB;  //不要校验

	cfsetospeed(&myserial, B9600);  //设置波特率,B9600是定义的宏
	cfsetispeed(&myserial, B9600);

	/* 刷新输出队列,清除正接受的数据 */
	tcflush(uart_fd, TCIFLUSH);

	/* 改变配置 */
	tcsetattr(uart_fd, TCSANOW, &myserial);
	return   uart_fd;
}

4.游戏模块(消消乐)

        消消乐小游戏的设计核心

        (1)游戏开始界面设计

         (2)随机游戏界面生成(二维数组)

#include "game.h"

/***************************************
	项目相关的主逻辑函数
***************************************/

int xxl[7][7];			//全局变量,地图数组
int xiao[7][7]={0};		//全局变量,地图标志数组
int count=0;			//全局变量,判断遍历时的相同个数
int direction;			//全局变量,用于记录方向
int fangxiang;			//全局变量,用于记录判断的方向
int score;				//全局变量,用于记录游戏得分


/*
	用来产生随机数,随机数的范围是[0,num-1]
*/

int Get_Random(int num)//生成随机数,形参用于控制随机数范围[0,num]
{
	
	int x=random()%num;
	return x;
}
int map_interface(void)//生成随机界面
{
	/*
	*/
	//用来设置随机数种子
	srandom(time(NULL));
	score=0;
	int i,j,k;
	char JPG_PATH[30];
	printf("游戏开始!\n");	
	for(i=0;i<7;i++)
	{
		for(j=0;j<7;j++)
		{
			xxl[i][j]=Get_Random(7);   //随机数组赋值
			k=xxl[i][j];
			switch (k)
			{
				case 0:strcpy(JPG_PATH,JPG_PATH0);
				break;
				case 1:strcpy(JPG_PATH,JPG_PATH1);
				break;
				case 2:strcpy(JPG_PATH,JPG_PATH2);
				break;
				case 3:strcpy(JPG_PATH,JPG_PATH3);
				break;
				case 4:strcpy(JPG_PATH,JPG_PATH4);
				break;
				case 5:strcpy(JPG_PATH,JPG_PATH5);
				break;
				case 6:strcpy(JPG_PATH,JPG_PATH6);
				break;
				default:return 0 ;
				break;
			}
			Display_Jpg(i*60+60+140,j*60+60-30,JPG_PATH);//显示游戏界面图片
		}
		Display_Jpg(650,300,PATH_newgame);//显示重新开始按钮
		Display_Jpg(650,390,PATH_quit);//显示游戏结束按钮
		Display_Jpg(630,30,SCORE_PATH);//显示分数框图
		Display_Jpg(740,45,PATH0);//初始化分数0
	}
	return 0;
}

        (3)通过触摸屏判断用户滑动方向实现消消乐元素交换

/*
	获取从触摸板获取的坐标
	换算成二维数组坐标以及用户滑动方向
*/
int panduan(int Tx,int Ty,int Lx,int Ly)
{
	int Ti,Tj,Li,Lj;
	Ti=(Tx-200)/60;			//获取二维数组坐标
	Tj=(Ty-30)/60;
	
	if(Lx-Tx>15)			//判断方向
	{
		 Li=Ti+1;
		 fangxiang='r';		//右
	}
	else if(Lx-Tx<-15)
	{
		 Li=Ti-1;
		 fangxiang='l';		//左
	}
	else
	{
		Li=Ti;
	}
	if(Ly-Ty>15)
	{
		 Lj=Tj+1;
		 fangxiang='d';		//下
	}
	else if(Ly-Ty<-15)
	{
		 Lj=Tj-1;
		 fangxiang='u';		//上
	}
	else
	{
		Lj=Tj;
	}
	
	if((xxl[Ti][Tj]==-1)||(xxl[Li][Lj]==-1)||(xxl[Ti][Tj]==xxl[Li][Lj]))//起始点与离开点对应数组元素不能相同,并且不能为被消元素
	{return 0;}
	
	
	
	if((Ti!=Li && Tj!=Lj)|| abs(Ti-Li)>1 || abs(Tj-Lj)>1 ||Li>6 ||Lj>6)//起始点与离开点不能是同一点
	{
		return 0;
	}
	else
	{
		map2(Ti,Tj,Li,Lj);
	}
	
}

        (4)交换后判断是否可消,可消则执行可消操作,否则执行交换元素复原

int map_updatax(void)
{
	int i,j,k;
	for(j=0;j<7;j++)
	{
		for(i=0;i<7;i++)
		{
			if(traverse_map(i,j,0,0)==0)//不能消
			{
				continue;
			}
			else//可消
			{
				map4();//标记
				usleep(250000);
				map3();//重显
				//usleep(100000);
				map_updata();//掉落更新
			}
		}
	}
}
int map_updata(void)//消了以后地图更新
{
	
	int i,j,flag=0;
	
	for(j=0;j<7;j++)
	{
		for(i=0;i<7;i++)
		{
			if((xxl[i][j]==-1)&&j!=0)//若该元素为被消除元素则与上一行进行互换
			{
				map_restore(i,j,i,j-1);
				flag=1;
			}
			if((xxl[i][j]==-1)&&j==0)//若该元素为顶层元素则随机产生新的图片
			{
				xxl[i][j]=Get_Random(7);//给随机数
				flag=1;
			}
		}
		
	}
	if(flag==0)//找不到被消除元素则结束递归
	{
		return 0;
	}
	map3();
	map_updata();//递归调用本身重复遍历是否还有被消除元素
	return 0;
}

 5.控制模块(用于总系统界面控制判定)

#include "Key.h"

int game(void)
{
	//LCD屏幕的初始化
	lcd_Init();
	//清屏
	Init_color(white);
	//获取BMP图片
	//Display_Bmp(0,0,BMP_PATH);
	//获取JPG图片
	Display_Jpg(0,0,JPG_PATHx);
	
	while(1)
	{
		if(Get_Touchx()==1)
		{
			break;
		}
	
	}
	//生成背景图片
	Display_Jpg(0,0,JPG_PATHb);
	//生成随机游戏界面
	map_interface();
	/*
		触摸信息相关函数
	*/
	Get_Touch();
	//LCD屏幕的解初始化
	Lcd_Uinit();
	return 0;
}

int key(void)
{
	Display_Jpg(0,0,PATH_system);
   
   while(1)
    {	
		switch (Get_Touchs())//选择进入不同模块
		{	
	    	case 2: //娱乐		
			game();
			break;
			case 1://灯光
			led();
			break;
			
			
			case 4://蓝牙
			bluetooth();
			break;
			default:return 0;
			break;
		}
	
    }
    return 0;
}
int bluetooth(void)
{	
	Display_Jpg(0,0,PATH_system);
	Display_Jpg(580,315,PATH_blutooth);

	printf("蓝牙模式!\n");
	unsigned char readbuf[256]={0};
	int uart1_fd = Uart_Init("/dev/ttySAC1");
	while(1)
	{
		int r = read(uart1_fd,readbuf,256);
		if(!strcmp(readbuf,"灯光"))//灯光模块
		{
			puts(readbuf);
			led_init();
			while(1)
			{
				read(uart1_fd,readbuf,256);
				if(strcmp(readbuf,"开灯"))
				{
					led_bluetooth(1);
				}
				else if(strcmp(readbuf,"关灯"))
				{
					led_bluetooth(0);
				}
				if(!strcmp(readbuf,"退出灯光"))
				{
					printf("退出灯光\n");
		
					bluetooth();
				}
			}
		}
		if(!strcmp(readbuf,"娱乐"))//娱乐模块
		{
			game();
		}
		if(!strcmp(readbuf,"退出蓝牙"))
		{
			key();
		}
	}
	close(uart1_fd);
	return 0;
}

五、效果图展示

      

完整工程文件下载地址:https://download.****.net/download/weixin_56681901/21614333