一、
PICO+LCD屏幕开发彩屏贪吃蛇小游戏,总的开发思路如下:
首先确定游戏规则为小蛇有自己的位置、长度属性,食物只有位置属性。规定当小蛇首部节点与食物坐标相同时,食物被小蛇吃掉,同时小蛇自身长度+1,这时候生成新的食物。新的食物坐标这里使 用到随机数来生成,注意随机数的生成范围应与屏幕尺寸相当。当小蛇首部节点接触到屏幕边缘视为游戏结束。
本次拿到的LCD模块除了板载彩屏LCD外也板载5+2按键,5按键为JoyTick按键,包含上下左右按键、中部控制按键,KeyA、KeyB普通按键。小蛇的方向运动可由上下左右按键控制,KeyA、KeyB用于控制小蛇运动速度,控制按键用于控制游戏重新载入。
二、
2.1
1.14英寸屏幕使用到SPI通信,属于高速型同步全双工通信。来自主机或从机的数据在时钟上升沿或下降沿同步。主机和从机可以同时传输数据。SPI接口可以是3线式或4线式。这里说明常用的4线SPI接口。
4线SPI器件有四个信号:
时钟(SPI CLK, SCLK)
片选(CS)
主机输出、从机输入(MOSI)
主机输入、从机输出(MISO)
产生时钟信号的器件称为主机。主机和从机之间传输的数据与主机产生的时钟同步。同I2C接口相比,SPI器件支持更高的时钟频率。用户应查阅产品数据手册以了解SPI接口的时钟频率规格。
SPI接口只能有一个主机,但可以有一个或多个从机。图1显示了主机和从机之间的SPI连接。
来自主机的片选信号用于选择从机。这通常是一个低电平有效信号,拉高时从机与SPI总线断开连接。当使用多个从机时,主机需要为每个从机提供单独的片选信号。本文中的片选信号始终是低电平有效信号。MOSI和MISO是数据线。MOSI将数据从主机发送到从机,MISO将数据从从机发送到主机。
数据传输
要开始SPI通信,主机必须发送时钟信号,并通过使能CS信号选择从机。片选通常是低电平有效信号。因此,主机必须在该信号上发送逻辑0以选择从机。SPI是全双工接口,主机和从机可以分别通过MOSI和MISO线路同时发送数据。在SPI通信期间,数据的发送(串行移出到MOSI/SDO总线上)和接收(采样或读入总线(MISO/SDI)上的数据)同时进行。串行时钟沿同步数据的移位和采样。SPI接口允许用户灵活选择时钟的上升沿或下降沿来采样或移位数据。
2.2
PICO 硬件SPI结构
从上图可以看到其内部结构有AMBA APB总线接口,用于访问和控制状态寄存器并传输数据,Register Block寄存器块,通过APB接口写入或读取数据。clock prescaler时钟分频器提供时钟信号给发送与接收逻辑电路。还有FIFO先进先出存储器等。
2.3 PICO常用SPI函数
spi_init(spi端口,速率)
参数:
spi端口:rp2040有两个spi端口,此处参数可以为spi0或spi1
用于初始化spi,在生成项目时勾选SPI,生成的项目自带了这条代码。
spi_write_blocking(spi端口,数据,长度)
参数:
spi端口:spi0或spi1
发送数据:该参数是个指针,参数一般为数组
长度:参数发送数据的长度
spi_read_blocking(spi端口,发送数据,接收数据,长度)
参数:
spi端口:spi0或spi1
发送数据:在读取数据时发送的数据,一般为0
接收数据:将读取的数据放到这个参数,这个参数一般为数组
长度:参数接收数据的长度
2.4 SPI彩屏驱动原理
LCD彩屏引脚定义
这里使用到的彩屏LCD分辨率240*135,最高支持16位真彩(RGB565),可以认为一个像素的颜色状态由三个数值共同决定,光的三原色原理大家应该不陌生,大多数的颜色可以通过红、绿、蓝三色按照不同的比例合成产生。同样,绝大多数单色光也可以分解成红、绿、蓝三种色光。IPS屏幕上的每个像素点都由一组红绿蓝遮光片组成,白色的背光透过遮光片就呈现出红绿蓝三种颜色。改变对应遮光片的角度就能改变该颜色的强度,不同强度的红绿蓝三色混合在一起就组成了屏幕上显示的65535种颜色。
一个像素点需要占用16个bit(即2Byte)的数据,整个屏幕就需240x240x2=115200Byte(即115.2KB)数据。基本工程内已经编写好了基本的屏幕驱动库,在屏幕上可以对任意一个像素点驱动显示想要的颜色,支持的颜色梯级有2^16种。
2.5 彩屏驱动函数
LCD_1IN3_Init(UBYTE Scan_dir);
彩屏初始化函数,输入参数为水平、垂直参数
LCD_1IN3_Clear(UWORD Color);
彩屏清屏函数,输入参数为清屏后的屏幕颜色
LCD_1IN3_Display(UWORD *Image);
图片显示函数,输入参数为图片数组
LCD_1IN3_DisplayWindows(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD *Image);
窗口显示函数,输入参数有坐标x,y,窗口大小,图片数组
三、贪吃蛇数据结构
前面提到,贪吃蛇可以看成由一连串的节点组成,上图定义的数据结构为:
小蛇变量my_snake_body,my_snake_body内含有小蛇节点坐标、颜色属性。
结构体变量my_eat_snake变量则除了含有my_snake_body变量,还有小蛇身体长度变量,小蛇吃掉食物后自身长度+1则通过这个变量自增完成。小蛇身体宽度变量,这决定了屏幕上显示的小蛇尺寸大小。最后一个是小蛇移动速度变量。
四、示例程序
小蛇向上移动函数
void snake_move_up(){ uint16_t i; for( i = my_eat_snake.snake_body_len - 1; i > 0; i--){ my_eat_snake.my_snake_body[i].iX = my_eat_snake.my_snake_body[i-1].iX; my_eat_snake.my_snake_body[i].iY = my_eat_snake.my_snake_body[i-1].iY; } my_eat_snake.my_snake_body[0].iY -= my_eat_snake.snake_body_width; if(my_eat_snake.my_snake_body[0].iY==0){ snake_game.game_run=0; } my_eat_snake.my_snake_body[0].iX += 0; }
食物新坐标生成函数
void food_gen(){ uint16_t n1=241-60; uint16_t n2=136-40; uint32_t rand_ix=((rand()%n1)/my_eat_snake.snake_body_width)*my_eat_snake.snake_body_width; uint32_t rand_iy=((rand()%n2)/my_eat_snake.snake_body_width)*my_eat_snake.snake_body_width; while(my_snake_food.food.iX==rand_ix&&my_snake_food.food.iY==rand_iy){ uint32_t rand_ix=((rand()%n1)/my_eat_snake.snake_body_width)*my_eat_snake.snake_body_width; uint32_t rand_iy=((rand()%n2)/my_eat_snake.snake_body_width)*my_eat_snake.snake_body_width; } my_snake_food.food.iX = rand_ix; my_snake_food.food.iY = rand_iy; my_snake_food.food.color = GLCD_COLOR_RED; my_snake_food.width = 5;//10 }
判断食物是否被吃掉
void food_eat(){ if(my_snake_food.food.iX==my_eat_snake.my_snake_body[0].iX&&my_snake_food.food.iY==my_eat_snake.my_snake_body[0].iY){ //加分 my_eat_snake.snake_body_len++; food_gen(); } }
按键检测函数
//按键初始化 void Key_Init(uint pin){ gpio_init(pin); gpio_set_dir(pin,GPIO_IN); gpio_set_pulls(pin,1,0);//引脚编号 上拉标志 下拉标志 } void user_key_init(){ Key_Init(PIN_DIRECTION_KEY_A); Key_Init(PIN_DIRECTION_KEY_B); Key_Init(PIN_DIRECTION_CTRL); Key_Init(PIN_DIRECTION_RIGHT); Key_Init(PIN_DIRECTION_DOWN); Key_Init(PIN_DIRECTION_LEFT); Key_Init(PIN_DIRECTION_UP); }
游戏运行函数
void snake_run(){ if(snake_game.game_run==1){ switch(snake_dir.dir){ case 0: snake_move_right(); break; case 1: snake_move_left(); break; case 2: snake_move_up(); break; case 3: snake_move_down(); break; } my_eat_snake.snake_speed=snake_dir.snake_speed; draw_snake(); food_eat(); draw_snake_food(); DEV_Delay_ms(30*my_eat_snake.snake_speed); } }
五、实验效果
硬件连接图