写个俄罗斯方块,感受嵌入式linux应用开发

俄罗斯方块是一个简单的小游戏,完全可以采用单片机,裸机完成。但在嵌入式linux环境下实现,可以充分感受linux应用开发,同样是写一个程序,但功力可能大不相同。由单片机转嵌入式linux应用,框架感很重要,好的框架方便扩展,修改,可移植性强。

本文实现的俄罗斯方块可以切换横屏竖屏,支持触摸屏控制,串口终端控制。会做这个其实就已经具备基本的嵌入式linux应用开发能力了。

竖屏

横屏: 

动图

1. 程序框架

重点只有一个,抽象出输入设备管理器,因为我们支持触摸屏控制,串口终端控制,抽象出设备管理器后,可以继续添加其他设备如鼠标,直连键盘等,修改极其方便。

整个程序主要有四大部分组成framebuffer、input_managner、控制逻辑、display_text。游戏控制逻辑并不是重点,从github上随便下载,然后进行魔改。

整个工程结构:

2. frambuffer

可以参考嵌入式Linux入门-Framebuffer应用编程在Linux系统下画个点

2.1 打开与关闭framebuffer函数

获取lcd长宽,bpp

2.2 各种画图函数

以draw_pixel画一个像素为基础,封装出draw_line画一条线,draw_rect画框,fill_rect填充一个框

3. 输入管理器input_manager

输入管理,输入采用触摸屏或者串口终端,需要抽象出输入管理器,来管理两个设备。

#ifndef _INPUT_MANAGER_H_
#define _INPUT_MANAGER_H_

typedef struct s_input_event {
	int control;//0=pause,1=change,2=move_down,3=move_left,4=move_right,5=quit
}s_input_event,*ps_input_event;

typedef struct s_input_device {
	char *name;
	int (*get_input_event)(ps_input_event input_enent);
	int (*input_device_init)(void);
	void (*input_device_exit)(void);
	struct s_input_device *ps_next;
}s_input_device,*ps_input_device;

int init_input_device(char *name);
void exit_input_device(void);

int get_input_event(ps_input_event input_event);

int register_input_device(ps_input_device input_device);

#endif

抽象出输入事件s_input_event,control为控制指令,0为暂停,1为变换,2为下移,3为左移,4为右移。

抽象出输入设备s_input_device,成员为名字name,利用名字来匹配设备,每个设备必须实现三个函数:

1.get_input_device获取输入事件

2.input_device_init初始化设备

3.input_device_exit退出设备

还需要一个指针,把设备串联起来,方便切换。

input_manager需要提供register_input_device函数,让设备把自己注册进管理器

int register_input_device(ps_input_device input_device)
{
	input_device->ps_next = ps_input_device_head;
	ps_input_device_head = input_device;
	return 0;
}

3.1 触摸屏

触摸屏直接采用tslib,Tslib是一个开源的程序,能够为触摸屏驱动获得的采样提供诸如滤波、去抖、校准等功能。关键函数如下:

static s_input_device s_input_device_ts =  {

	.name				= "ts",
	.get_input_event 	= get_input_event_ts,
	.input_device_init 	= input_device_init_ts,
	.input_device_exit 	= input_device_exit_ts,
	.ps_next 			= NULL,
};

void register_ts(void)
{
	register_input_device(&s_input_device_ts);
}

实现输入设备结构体,并实现注册进输入管理器函数。

初始化调用ts_setup,并获取一些数据,这些数据用于判定,按触摸屏的哪个位置是上下左右、暂停退出功能。

static int input_device_init_ts(void)
{
	int fd_fb;
	static struct fb_var_screeninfo var;
	
	g_ts = ts_setup(NULL, 1);
	if (!g_ts)
	{
		printf("ts_setup err\n");
		return -1;
	}

	fd_fb = open("/dev/fb0", O_RDWR);
	if (fd_fb < 0)
	{
		printf("can't open /dev/fb0\n");
		return -1;
	}
	if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
	{
		printf("can't get var\n");
		return -1;
	}

	xres = var.xres;
	yres = var.yres;
	one_third_x = xres/3;
	one_third_y = yres/3;
	two_third_x = xres*2/3;
	two_third_y = yres*2/3;
	quit_x = xres*4/5;
	quit_y = yres/5;	
	close(fd_fb);	
	return 0;
}

 获取输入事件,ts_read读取屏幕并根据位置判定是上下左右、暂停退出中的哪一个功能。

static int get_input_event_ts(ps_input_event input_event)
{
	struct ts_sample samp;
	int ret;

	ret = ts_read(g_ts, &samp, 1);
	
	if (ret != 1)
		return -1;

	if (samp.pressure == 0) {
		if (samp.x<two_third_x&&samp.x>one_third_x&&samp.y<two_third_y&&samp.y>one_third_y) {
			input_event->control = cmd_pause;
			return 0;
		} else if (samp.x<one_third_x&&samp.y<two_third_y&&samp.y>one_third_y) {
			input_event->control = cmd_mv_left;
			return 0;
		} else if (samp.x>two_third_x&&samp.y<two_third_y&&samp.y>one_third_y) {
			input_event->control = cmd_mv_right;
			return 0;
		} else if (samp.y<one_third_y&&samp.x<two_third_x&&samp.x>one_third_x) {
			input_event->control = cmd_rotate;
			return 0;
		} else if (samp.y>two_third_y&&samp.x<two_third_x&&samp.x>one_third_x) {
			input_event->control = cmd_mv_down;
			return 0;
		} else if (samp.x>quit_x&&samp.y<quit_y) {
			input_event->control = cmd_quit;
			return 0;
		} else
			return -1;
	}
	else
		return -1;
}

3.2 串口终端

重点部分和触摸屏是一样的,因为是自己抽象出来的设备。

static s_input_device s_input_device_terminal =  {

	.name				= "terminal",
	.get_input_event 	= get_input_event_terminal,
	.input_device_init 	= input_device_init_terminal,
	.input_device_exit 	= input_device_exit_terminal,
	.ps_next 			= NULL,
};

void register_terminal(void)
{
	register_input_device(&s_input_device_terminal);
}

初始化,重定向终端输入。把键盘输入,重定向到我们写的程序。

static int input_device_init_terminal(void)
{
	tcgetattr(STDIN_FILENO, &oldtermset);
	newtermset = oldtermset;
	newtermset.c_lflag &= ~ICANON;
	newtermset.c_lflag &= ~ECHO;
	newtermset.c_cc[VMIN] = 1;
	tcsetattr(STDIN_FILENO, TCSANOW, &newtermset);
	return 0;
}

获取输入事件

static int get_input_event_terminal(ps_input_event input_event)
{
	char c;
	c = getchar();
	fflush(NULL);
	switch(c) {
	case 'w':
		input_event->control = cmd_rotate;
		return 0;
	case 'a':
		input_event->control = cmd_mv_left;
		return 0;
	case 's':
		input_event->control = cmd_mv_down;
		return 0;
	case 'd':
		input_event->control = cmd_mv_right;
		return 0;
	case 'q':
		input_event->control = cmd_quit;
		return 0;
	case 'p':
		input_event->control = cmd_pause;
		return 0;
	default:
		break;	
	}
		return -1;
}

4. diaplay_text显示文本

之前教过,可以用freetype矢量字体,这里采用点阵。

static void put_char(int x, int y, int c, unsigned int color, int rotation)
{

	int i, j, bits,x2,y2,tmp;
	for (i = 0; i < font_vga_8x8.height; i++) {
		bits = font_vga_8x8.data[font_vga_8x8.height * c + i];
		for (j = 0; j < font_vga_8x8.width; j++, bits <<= 1)
			if (bits & 0x80) {
				x2 = x+j;
				y2 = y+i;
				switch (rotation) {
				case 0:
					break;
				case 1:
					tmp = y2;
					y2 = x2;
					x2 = xres_orig - tmp - 1;
					break;
				default :
					break;
				}
				lcd_draw_pixel(x2, y2, color);
			}
	}
}

核心函数为显示一个字符,如上,rotation为旋转标记,将横屏显示变为竖屏显示。

最底层lcd_draw_pixel(x2, y2, color);为在framebuffer中实现的函数,即在lcd上画一个点。

由此函数封装出显示字符串,显示数字函数。

5.多线程

对于俄罗斯方块,并不需要使用多线程,但为了练习,创建多线程。

嵌入式Linux入门—Linux多线程编程、互斥量、信号量、条件变量

5.1 主线程

初始化输入输出,控制逻辑,创建输入检测线程。

int main(int argc,char **argv)
{
	pthread_t tid;
	int control;
	int a;
	int b;
	pthread_mutex_init(&mutex,NULL);
	if (open_framebuffer() != 0)
		goto error;
	if(ts)
		if (init_input_device("ts") != 0)
			goto error;
		else;
	else
		if (init_input_device("terminal") != 0)
			goto error;
		else;

	input_event.control = 10;

	initGame(xres_orig,yres_orig);
	SetTimer(1,alarm_handler);
	
	pthread_create(&tid,NULL,get_input,NULL);

	while(1) {
		pthread_mutex_lock(&mutex);
		a = read;
		b = write;
		pthread_mutex_unlock(&mutex);

		if (a != b) {
			pthread_mutex_lock(&mutex);
			control = buffer[read];
			pthread_mutex_unlock(&mutex);
			switch(control) {
			case cmd_rotate:
				if(game_rotate&&ts)
					moveLeft();
				else
					rotate();
				break;
			case cmd_mv_left:
				if(game_rotate&&ts)
					moveDown();
				else				
					moveLeft();
				break;
			case cmd_mv_down:
				if(game_rotate&&ts)
					moveRight();
				else
					moveDown();
				break;
			case cmd_mv_right:
				if(game_rotate&&ts)
					rotate();
				else
					moveRight();
				break;
			case cmd_quit:
				printf("Game Aborted!!!\n");
				printf("Your score:%d\n",getScore());
				goto error;
			default:
				break;			
			}
			pthread_mutex_lock(&mutex);
			read = (read+1)%2;
			pthread_mutex_unlock(&mutex);
		}

	}	

error:
	exit_input_device();
	close_framebuffer();
	exitGame(); 
	return 0;
	
}

5.2 输入检测线程

获取输入事件,采用环形缓冲,读写同步。 

将输入事件放入buffer[]数组,设置read,write指针。

read=(read+1)%buffer长度,构成环形,write同理。

void *get_input(void *arg)
{
	while (1) {
		if (get_input_event(&input_event) == 0) {
			
			pthread_mutex_lock(&mutex);
			if((write+1)%2 != read) {
				buffer[write] = input_event.control;
				write = (write+1)%2;
			}
			pthread_mutex_unlock(&mutex);
		}
	}
}

猜你喜欢

转载自blog.csdn.net/freestep96/article/details/127853353