• 【MM32F5270开发板试用】移植Google Chrome小恐龙游戏到MM32F5270


    本篇文章来自极术社区与灵动组织的MM32F5270开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:曾是一颗薏米

    一、项目背景

    在几年前,Google 给 Chrome 浏览器加了一个有趣的彩蛋:如果你在未联网的情况下访问网页,弹出的错误界面会出现一只小恐龙。许多人可能觉得这只恐龙只是一个可爱的小图标,在断网的时候陪伴用户。但是后来有人按下空格键,小恐龙开始奔跑!
    这个小彩蛋成为深受人们喜爱的小游戏,风靡至今。

    二、体验Google Chrome小恐龙游戏

    在电脑上只要安装了Google Chrome浏览器并在网址栏输入:chrome://dino/,然后按下空格就能开始小恐龙游戏。

    三、移植工作

    前段时间在b站看到up主(hj240)移植到单片机上,想到刚好可以在MM32上玩一下!于是,开始了移植工作。
    硬件使用:0.96寸的oled屏(4线,i2c接口)

    使用杜邦线按下面方法连接:

    VCC ----> 板载3.3V
    GND ----> 板载GND
    SLC ----> PF0
    SDA ----> PF1
    
    • 1
    • 2
    • 3
    • 4

    MM32的i2c数据手册有使用dma的描述,但例程只提供轮询和中断两种实现。鉴于程序较简单,用软件模拟i2c接口方便移植~之前在使用stm32硬件i2c有时出现总线挂死,看了手册MM32支持SDA恢复,后续可考虑使用硬件i2c。

    1. 使能相应时钟。因为使用S3、S4作为按键,与之对应GPIOH/I时钟需要使能。GPIOF口用于软件模拟i2c也要使能该时钟。
    void BOARD_InitBootClocks(void)
    {
     CLOCK_ResetToDefault();
     CLOCK_BootToHSE120MHz();
    
     /* UART1. */
     RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_UART1, true);
     RCC_ResetAPB2Periphs(RCC_APB2_PERIPH_UART1);
    
     /* GPIOB. */
     RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOB, true);
     RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOB);
    
     /* GPIOC. */
     RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOC, true);
     RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOC);
    
     /* GPIOD. */
     RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOD, true);
     RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOD);
    
     /* GPIOI. */
     RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOI, true);
     RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOI);
    
         /* GPIOF. */
     RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOF, true);
     RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOF);
    
         /* GPIOH. */
     RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOH, true);
     RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOH);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    1. 设置gpio,用于按键和软件模拟i2c。
      由原理图可知,按键有外部上拉,因此io模式配置为上拉,当按下时检测低电平即可。

    void BOARD_InitPins(void)
    {
    GPIO_Init_Type gpio_init;

    /* PB6 - UART1_TX. */
    gpio_init.Pins = GPIO_PIN_6;
    gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &gpio_init);
    GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_7);

    /* PB7 - UART1_RX. */
    gpio_init.Pins = GPIO_PIN_7;
    gpio_init.PinMode = GPIO_PinMode_In_Floating;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &gpio_init);
    GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_7);

    /* LED0. */
    gpio_init.Pins = GPIO_PIN_0;
    gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOI, &gpio_init);

    /* LED1. */
    gpio_init.Pins = GPIO_PIN_2;
    gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOD, &gpio_init);

    /* SCLK */
    gpio_init.Pins = GPIO_PIN_0;
    gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOF, &gpio_init);

    /* SDA */
    gpio_init.Pins = GPIO_PIN_1;
    gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOF, &gpio_init);

    /* KEY2. */
    gpio_init.Pins = GPIO_PIN_15;
    gpio_init.PinMode = GPIO_PinMode_In_PullUp;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &gpio_init);

    /* KEY1. */
    gpio_init.Pins = GPIO_PIN_2;~~~~
    gpio_init.PinMode = GPIO_PinMode_In_PullUp;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOH, &gpio_init);
    }

    1. 在oled12864.h修改软件模拟i2c的引脚
      //-----------------OLED IIC端口定义----------------

      #define OLED_SCLK_Clr() GPIO_WriteBit(GPIOF, GPIO_PIN_0, 0u)
      #define OLED_SCLK_Set() GPIO_WriteBit(GPIOF, GPIO_PIN_0, 1u)

      #define OLED_SDIN_Clr() GPIO_WriteBit(GPIOF, GPIO_PIN_1, 0u)
      #define OLED_SDIN_Set() GPIO_WriteBit(GPIOF, GPIO_PIN_1, 1u)

    2. 实现软件延时函数用于调节游戏难度。

      void HAL_Delay(uint32_t ms)
      {
      uint32_t ms_cnt = ms;
      for (uint32_t i = 0u; i < ms_cnt; i++)
      {
      for (uint32_t j = 0u; j < (CLOCK_SYS_FREQ / 10000u); j++)
      {
      __NOP();
      }
      }
      }

    3. main函数主要逻辑

      int main(void)
      {
      unsigned char key_num = 0;
      unsigned char cactus_category = 0;
      unsigned char cactus_length = 8;
      unsigned int score = 0;
      unsigned int highest_score = 0;
      int height = 0;
      int cactus_pos = 128;
      unsigned char cur_speed = 30;
      char failed = 0;
      char reset = 0;

      BOARD_Init();
      printf(“\r\initiliazed OK!\r\n”);
      OLED_Init();
      OLED_DrawCover();
      HAL_Delay(100);
      while(get_key_val()!=2);
      HAL_Delay(100);
      OLED_Clear();

      printf(“\r\nDinosaur game initiliazed OK!\r\n”);

      while (1)
      {
      if (failed == 1)
      {
      OLED_DrawRestart();

      key_num = get_key_val();
      if (key_num == 2)
      {
      if (score > highest_score) highest_score = score;
      score = 0;
      failed = 0;
      height = 0;
      reset = 1;
      OLED_DrawDinoJump(reset);
      OLED_DrawCactusRandom(cactus_category, reset);
      OLED_Clear();
      }
      continue;
      }

    score ++;
             if (height <= 0) key_num = get_key_val();
        
             OLED_DrawGround();
             OLED_DrawCloud();
        
             if (height>0 || key_num == 1) height = OLED_DrawDinoJump(reset);
             else OLED_DrawDino();
        
             cactus_pos = OLED_DrawCactusRandom(cactus_category, reset);
             if(cactus_category == 0) cactus_length = 8;
             else if(cactus_category == 1) cactus_length = 16;
             else cactus_length = 24;
        
             if (cactus_pos + cactus_length < 0)
             {
               cactus_category = rand()%4;
                 OLED_DrawCactusRandom(cactus_category, 1);
             }
        
             if ((height < 16) && ( (cactus_pos>=16 && cactus_pos <=32) || (cactus_pos + cactus_length>=16 && cactus_pos + cactus_length <=32)))
             {
                 failed = 1;
             }
        
             OLED_ShowString(35, 0, "HI:", 12);
             OLED_ShowNum(58, 0, highest_score, 5, 12);
             OLED_ShowNum(98, 0, score, 5, 12);
        
             reset = 0;
        
             cur_speed = score/50;
             if (cur_speed > 29) cur_speed = 29;
             HAL_Delay(30 - cur_speed);
             key_num = 0;
         }
         
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    四、oled驱动原理 & 游戏实现讲解

    有兴趣的可以看下面的链接,这里不再赘述。

    链接1:【手把手讲解在51单片机实现小恐龙游戏-上集】

    链接2:【手把手讲解在51单片机实现小恐龙游戏-下集】

    五、玩法介绍

    程序开始运行后,串口会初始化成功的打印信息。然后出现游戏界面按下开始游戏的按键即可开始游戏。游戏过程中,需控制小恐龙跃过障碍物,如果触碰障碍物则游戏结束。

    六、游戏效果

    由于oled屏的缘故,图片不能反应真正的游戏效果,建议自己烧录体检下。 原本担心软件i2c会比较慢导致卡顿,实际游戏效果非常丝滑。

    手机录制效果比较差,难以反馈真实效果,我就不录了。大家可以参考移植到stm32的视频:

    七、开源

    欢迎大家沟通学习,另外感谢原作者hj240的开源。
    我的gitee仓库地址:
    https://gitee.com/sakura96888/mm32-f5270\_-dinosaur-game-m
    第一次编译后,需自行选择下载器,如开发板标配的:CMSIS-DAP。下载后,按下复位键即可。

    八、总结

    MM32是国产MCU崛起的缩影,开发板硬件资源丰富,有很强的可玩性。MindSDK提供了模板用于快速开发,总体来说体验不错,但例程有些简单不够吸引人,也没有充分发挥硬件性能,希望以后MM32的生态能更丰富些。
    另外,在使用时发现开发板自带的CMSIS-DAP下载器进入调试后,无法正常查看栈中的变量,能正常查看寄存器的值(原因未知,猜测下载器固件不支持Armv8-M 架构?)

  • 相关阅读:
    面试题:CSS 怎样实现动画?
    【java】基础知识(1)
    自定义 Hook(State Hook)_web前端培训
    求中位数,方差(小E的动态序列)
    【MySQL数据库笔记 - 进阶篇】(五)锁
    Linux命令-文件展示
    9.4-学习ing
    7.zigbee开发,低功耗,通信加密开发
    互联网Java工程师面试题·Memcached 篇·第二弹
    redis之集群
  • 原文地址:https://blog.csdn.net/weixin_47569031/article/details/127441031