俄罗斯方块是一个简单的小游戏,完全可以采用单片机,裸机完成。但在嵌入式linux环境下实现,可以充分感受linux应用开发,同样是写一个程序,但功力可能大不相同。由单片机转嵌入式linux应用,框架感很重要,好的框架方便扩展,修改,可移植性强。
本文实现的俄罗斯方块可以切换横屏竖屏,支持触摸屏控制,串口终端控制。会做这个其实就已经具备基本的嵌入式linux应用开发能力了。
竖屏
横屏:
动图
重点只有一个,抽象出输入设备管理器,因为我们支持触摸屏控制,串口终端控制,抽象出设备管理器后,可以继续添加其他设备如鼠标,直连键盘等,修改极其方便。
整个程序主要有四大部分组成framebuffer、input_managner、控制逻辑、display_text。游戏控制逻辑并不是重点,从github上随便下载,然后进行魔改。
整个工程结构:
可以参考嵌入式Linux入门-Framebuffer应用编程在Linux系统下画个点
获取lcd长宽,bpp
以draw_pixel画一个像素为基础,封装出draw_line画一条线,draw_rect画框,fill_rect填充一个框
输入管理,输入采用触摸屏或者串口终端,需要抽象出输入管理器,来管理两个设备。
- #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;
- }
触摸屏直接采用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;
- }
重点部分和触摸屏是一样的,因为是自己抽象出来的设备。
- 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;
- }
之前教过,可以用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上画一个点。
由此函数封装出显示字符串,显示数字函数。
对于俄罗斯方块,并不需要使用多线程,但为了练习,创建多线程。
嵌入式Linux入门—Linux多线程编程、互斥量、信号量、条件变量
初始化输入输出,控制逻辑,创建输入检测线程。
- 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;
-
- }
获取输入事件,采用环形缓冲,读写同步。
将输入事件放入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);
- }
- }
- }