• 写个俄罗斯方块,感受嵌入式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

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

    1. #ifndef _INPUT_MANAGER_H_
    2. #define _INPUT_MANAGER_H_
    3. typedef struct s_input_event {
    4. int control;//0=pause,1=change,2=move_down,3=move_left,4=move_right,5=quit
    5. }s_input_event,*ps_input_event;
    6. typedef struct s_input_device {
    7. char *name;
    8. int (*get_input_event)(ps_input_event input_enent);
    9. int (*input_device_init)(void);
    10. void (*input_device_exit)(void);
    11. struct s_input_device *ps_next;
    12. }s_input_device,*ps_input_device;
    13. int init_input_device(char *name);
    14. void exit_input_device(void);
    15. int get_input_event(ps_input_event input_event);
    16. int register_input_device(ps_input_device input_device);
    17. #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函数,让设备把自己注册进管理器

    1. int register_input_device(ps_input_device input_device)
    2. {
    3. input_device->ps_next = ps_input_device_head;
    4. ps_input_device_head = input_device;
    5. return 0;
    6. }

    3.1 触摸屏

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

    1. static s_input_device s_input_device_ts = {
    2. .name = "ts",
    3. .get_input_event = get_input_event_ts,
    4. .input_device_init = input_device_init_ts,
    5. .input_device_exit = input_device_exit_ts,
    6. .ps_next = NULL,
    7. };
    8. void register_ts(void)
    9. {
    10. register_input_device(&s_input_device_ts);
    11. }

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

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

    1. static int input_device_init_ts(void)
    2. {
    3. int fd_fb;
    4. static struct fb_var_screeninfo var;
    5. g_ts = ts_setup(NULL, 1);
    6. if (!g_ts)
    7. {
    8. printf("ts_setup err\n");
    9. return -1;
    10. }
    11. fd_fb = open("/dev/fb0", O_RDWR);
    12. if (fd_fb < 0)
    13. {
    14. printf("can't open /dev/fb0\n");
    15. return -1;
    16. }
    17. if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
    18. {
    19. printf("can't get var\n");
    20. return -1;
    21. }
    22. xres = var.xres;
    23. yres = var.yres;
    24. one_third_x = xres/3;
    25. one_third_y = yres/3;
    26. two_third_x = xres*2/3;
    27. two_third_y = yres*2/3;
    28. quit_x = xres*4/5;
    29. quit_y = yres/5;
    30. close(fd_fb);
    31. return 0;
    32. }

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

    1. static int get_input_event_ts(ps_input_event input_event)
    2. {
    3. struct ts_sample samp;
    4. int ret;
    5. ret = ts_read(g_ts, &samp, 1);
    6. if (ret != 1)
    7. return -1;
    8. if (samp.pressure == 0) {
    9. if (samp.x<two_third_x&&samp.x>one_third_x&&samp.y<two_third_y&&samp.y>one_third_y) {
    10. input_event->control = cmd_pause;
    11. return 0;
    12. } else if (samp.x<one_third_x&&samp.y<two_third_y&&samp.y>one_third_y) {
    13. input_event->control = cmd_mv_left;
    14. return 0;
    15. } else if (samp.x>two_third_x&&samp.y<two_third_y&&samp.y>one_third_y) {
    16. input_event->control = cmd_mv_right;
    17. return 0;
    18. } else if (samp.y<one_third_y&&samp.x<two_third_x&&samp.x>one_third_x) {
    19. input_event->control = cmd_rotate;
    20. return 0;
    21. } else if (samp.y>two_third_y&&samp.x<two_third_x&&samp.x>one_third_x) {
    22. input_event->control = cmd_mv_down;
    23. return 0;
    24. } else if (samp.x>quit_x&&samp.y<quit_y) {
    25. input_event->control = cmd_quit;
    26. return 0;
    27. } else
    28. return -1;
    29. }
    30. else
    31. return -1;
    32. }

    3.2 串口终端

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

    1. static s_input_device s_input_device_terminal = {
    2. .name = "terminal",
    3. .get_input_event = get_input_event_terminal,
    4. .input_device_init = input_device_init_terminal,
    5. .input_device_exit = input_device_exit_terminal,
    6. .ps_next = NULL,
    7. };
    8. void register_terminal(void)
    9. {
    10. register_input_device(&s_input_device_terminal);
    11. }

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

    1. static int input_device_init_terminal(void)
    2. {
    3. tcgetattr(STDIN_FILENO, &oldtermset);
    4. newtermset = oldtermset;
    5. newtermset.c_lflag &= ~ICANON;
    6. newtermset.c_lflag &= ~ECHO;
    7. newtermset.c_cc[VMIN] = 1;
    8. tcsetattr(STDIN_FILENO, TCSANOW, &newtermset);
    9. return 0;
    10. }

    获取输入事件

    1. static int get_input_event_terminal(ps_input_event input_event)
    2. {
    3. char c;
    4. c = getchar();
    5. fflush(NULL);
    6. switch(c) {
    7. case 'w':
    8. input_event->control = cmd_rotate;
    9. return 0;
    10. case 'a':
    11. input_event->control = cmd_mv_left;
    12. return 0;
    13. case 's':
    14. input_event->control = cmd_mv_down;
    15. return 0;
    16. case 'd':
    17. input_event->control = cmd_mv_right;
    18. return 0;
    19. case 'q':
    20. input_event->control = cmd_quit;
    21. return 0;
    22. case 'p':
    23. input_event->control = cmd_pause;
    24. return 0;
    25. default:
    26. break;
    27. }
    28. return -1;
    29. }

    4. diaplay_text显示文本

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

    1. static void put_char(int x, int y, int c, unsigned int color, int rotation)
    2. {
    3. int i, j, bits,x2,y2,tmp;
    4. for (i = 0; i < font_vga_8x8.height; i++) {
    5. bits = font_vga_8x8.data[font_vga_8x8.height * c + i];
    6. for (j = 0; j < font_vga_8x8.width; j++, bits <<= 1)
    7. if (bits & 0x80) {
    8. x2 = x+j;
    9. y2 = y+i;
    10. switch (rotation) {
    11. case 0:
    12. break;
    13. case 1:
    14. tmp = y2;
    15. y2 = x2;
    16. x2 = xres_orig - tmp - 1;
    17. break;
    18. default :
    19. break;
    20. }
    21. lcd_draw_pixel(x2, y2, color);
    22. }
    23. }
    24. }

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

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

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

    5.多线程

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

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

    5.1 主线程

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

    1. int main(int argc,char **argv)
    2. {
    3. pthread_t tid;
    4. int control;
    5. int a;
    6. int b;
    7. pthread_mutex_init(&mutex,NULL);
    8. if (open_framebuffer() != 0)
    9. goto error;
    10. if(ts)
    11. if (init_input_device("ts") != 0)
    12. goto error;
    13. else;
    14. else
    15. if (init_input_device("terminal") != 0)
    16. goto error;
    17. else;
    18. input_event.control = 10;
    19. initGame(xres_orig,yres_orig);
    20. SetTimer(1,alarm_handler);
    21. pthread_create(&tid,NULL,get_input,NULL);
    22. while(1) {
    23. pthread_mutex_lock(&mutex);
    24. a = read;
    25. b = write;
    26. pthread_mutex_unlock(&mutex);
    27. if (a != b) {
    28. pthread_mutex_lock(&mutex);
    29. control = buffer[read];
    30. pthread_mutex_unlock(&mutex);
    31. switch(control) {
    32. case cmd_rotate:
    33. if(game_rotate&&ts)
    34. moveLeft();
    35. else
    36. rotate();
    37. break;
    38. case cmd_mv_left:
    39. if(game_rotate&&ts)
    40. moveDown();
    41. else
    42. moveLeft();
    43. break;
    44. case cmd_mv_down:
    45. if(game_rotate&&ts)
    46. moveRight();
    47. else
    48. moveDown();
    49. break;
    50. case cmd_mv_right:
    51. if(game_rotate&&ts)
    52. rotate();
    53. else
    54. moveRight();
    55. break;
    56. case cmd_quit:
    57. printf("Game Aborted!!!\n");
    58. printf("Your score:%d\n",getScore());
    59. goto error;
    60. default:
    61. break;
    62. }
    63. pthread_mutex_lock(&mutex);
    64. read = (read+1)%2;
    65. pthread_mutex_unlock(&mutex);
    66. }
    67. }
    68. error:
    69. exit_input_device();
    70. close_framebuffer();
    71. exitGame();
    72. return 0;
    73. }

    5.2 输入检测线程

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

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

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

    1. void *get_input(void *arg)
    2. {
    3. while (1) {
    4. if (get_input_event(&input_event) == 0) {
    5. pthread_mutex_lock(&mutex);
    6. if((write+1)%2 != read) {
    7. buffer[write] = input_event.control;
    8. write = (write+1)%2;
    9. }
    10. pthread_mutex_unlock(&mutex);
    11. }
    12. }
    13. }

     

  • 相关阅读:
    算法基础入门 - 2.栈、队列、链表
    Jenkins安装
    Win11声卡驱动如何更新?Win11声卡驱动更新方法
    它突然就不好了 ----Go 调用C++异常
    【数学+贪心】第十三届蓝桥杯省赛C++ B组《X 进制减法》(C++)
    怎样才能在网上快速赚到钱?
    单调栈: 接雨水
    除法求值00
    UE5 官方案例Lyra 全特性详解 12.背包系统Inventory System 1添加物品到背包
    nacos配置中心
  • 原文地址:https://blog.csdn.net/freestep96/article/details/127853353