#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_gpio.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/ssi.h"
#include "driverlib/pin_map.h"
// OLED屏幕尺寸定义
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_PAGES (OLED_HEIGHT/8)
// 蛇游戏参数
#define SNAKE_MAX_LENGTH 100
#define INITIAL_SPEED 150
#define FOOD_SIZE 2
// 方向控制
typedef enum {
UP,
DOWN,
LEFT,
RIGHT
} Direction;
// 游戏状态
typedef struct {
uint8_t snakeX[SNAKE_MAX_LENGTH];
uint8_t snakeY[SNAKE_MAX_LENGTH];
uint8_t length;
Direction direction;
uint8_t foodX;
uint8_t foodY;
uint32_t speed;
bool gameOver;
} GameState;
// OLED命令/数据定义
#define OLED_CMD 0
#define OLED_DAT 1
// 系统时钟频率
uint32_t g_ui32SysClock;
// 游戏状态
GameState game;
// OLED初始化命令序列
const uint8_t oledInitCmds[] = {
0xAE, // 关闭显示
0xD5, 0x80, // 设置显示时钟分频
0xA8, 0x3F, // 设置复用率
0xD3, 0x00, // 设置显示偏移
0x40, // 设置起始行
0x8D, 0x14, // 电荷泵设置
0x20, 0x00, // 内存地址模式
0xA1, // 段重映射
0xC8, // COM扫描方向
0xDA, 0x12, // COM硬件引脚配置
0x81, 0xCF, // 对比度设置
0xD9, 0xF1, // 预充电周期
0xDB, 0x40, // VCOMH设置
0xA4, // 显示全部打开
0xA6, // 正常显示
0xAF // 开启显示
};
// 函数原型声明
void OLED_Write(uint8_t data, uint8_t mode);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_DrawPixel(uint8_t x, uint8_t y, bool color);
void OLED_DrawRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height, bool color);
void InitGame(void);
void GenerateFood(void);
void UpdateSnake(void);
void RenderGame(void);
void ProcessInput(void);
void DelayMs(uint32_t ui32Ms);
void DrawGameOver(void);
// OLED写函数
void OLED_Write(uint8_t data, uint8_t mode) {
uint32_t dummy;
// 设置DC引脚(命令/数据)
GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0, mode ? GPIO_PIN_0 : 0);
// 等待SSI发送完成
while(SSIBusy(SSI0_BASE));
// 发送数据
SSIDataPut(SSI0_BASE, data);
// 等待发送完成
while(SSIBusy(SSI0_BASE));
// 读取当前数据
SSIDataGet(SSI0_BASE, &dummy);
}
// OLED初始化
void OLED_Init(void) {
uint8_t i;
// 复位OLED
GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_1, 0);
DelayMs(10);
GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_1, GPIO_PIN_1);
for(i = 0; i < sizeof(oledInitCmds); i++) {
OLED_Write(oledInitCmds[i], OLED_CMD);
}
}
// OLED清屏
void OLED_Clear(void) {
uint8_t page, col;
for(page = 0; page < OLED_PAGES; page++) {
OLED_Write(0xB0 | page, OLED_CMD);
OLED_Write(0x00, OLED_CMD);
OLED_Write(0x10, OLED_CMD);
for(col = 0; col < OLED_WIDTH; col++) {
OLED_Write(0x00, OLED_DAT);
}
}
}
// OLED绘制像素
void OLED_DrawPixel(uint8_t x, uint8_t y, bool color) {
uint8_t page, bitMask;
if(x >= OLED_WIDTH || y >= OLED_HEIGHT) return;
page = y / 8;
bitMask = 1 << (y % 8);
OLED_Write(0xB0 | page, OLED_CMD);
OLED_Write(0x00 | (x & 0x0F), OLED_CMD);
OLED_Write(0x10 | (x >> 4), OLED_CMD);
OLED_Write(color ? bitMask : 0x00, OLED_DAT);
}
// OLED绘制矩形
void OLED_DrawRect(uint8_t x, uint8_t y, uint8_t width, uint8_t height, bool color) {
uint8_t i, j;
for(i = x; i < x + width; i++) {
for(j = y; j < y + height; j++) {
OLED_DrawPixel(i, j, color);
}
}
}
// 游戏结束显示
void DrawGameOver(void) {
// 绘制游戏结束背景框
OLED_DrawRect(OLED_WIDTH/2 - 25, OLED_HEIGHT/2 - 8, 50, 16, false);
// 绘制边框
OLED_DrawRect(OLED_WIDTH/2 - 25, OLED_HEIGHT/2 - 8, 50, 1, true);
OLED_DrawRect(OLED_WIDTH/2 - 25, OLED_HEIGHT/2 + 7, 50, 1, true);
OLED_DrawRect(OLED_WIDTH/2 - 25, OLED_HEIGHT/2 - 8, 1, 16, true);
OLED_DrawRect(OLED_WIDTH/2 + 24, OLED_HEIGHT/2 - 8, 1, 16, true);
// 绘制"GAME OVER"文字(简化表示)
OLED_DrawRect(OLED_WIDTH/2 - 20, OLED_HEIGHT/2 - 4, 5, 2, true); // G
OLED_DrawRect(OLED_WIDTH/2 - 14, OLED_HEIGHT/2 - 4, 5, 2, true); // A
OLED_DrawRect(OLED_WIDTH/2 - 8, OLED_HEIGHT/2 - 4, 5, 2, true); // M
OLED_DrawRect(OLED_WIDTH/2 - 2, OLED_HEIGHT/2 - 4, 5, 2, true); // E
OLED_DrawRect(OLED_WIDTH/2 + 6, OLED_HEIGHT/2 - 4, 5, 2, true); // O
OLED_DrawRect(OLED_WIDTH/2 + 12, OLED_HEIGHT/2 - 4, 5, 2, true); // V
OLED_DrawRect(OLED_WIDTH/2 + 18, OLED_HEIGHT/2 - 4, 5, 2, true); // E
OLED_DrawRect(OLED_WIDTH/2 + 24, OLED_HEIGHT/2 - 4, 5, 2, true); // R
}
// 初始化游戏状态
void InitGame(void) {
uint8_t i;
game.length = 3;
game.direction = RIGHT;
game.speed = INITIAL_SPEED;
game.gameOver = false;
// 初始化蛇的位置(居中)
for(i = 0; i < game.length; i++) {
game.snakeX[i] = OLED_WIDTH/2 - i*2;
game.snakeY[i] = OLED_HEIGHT/2;
}
// 生成食物
GenerateFood();
}
// 生成食物
void GenerateFood(void) {
// 改进的随机算法,避免出现在边界上
game.foodX = 2 + (rand() % (OLED_WIDTH - 4 - FOOD_SIZE));
game.foodY = 2 + (rand() % (OLED_HEIGHT - 4 - FOOD_SIZE));
}
// 更新蛇的位置
void UpdateSnake(void) {
uint8_t i;
uint8_t headX, headY;
// 保存蛇头位置
headX = game.snakeX[0];
headY = game.snakeY[0];
// 根据方向移动蛇头
switch(game.direction) {
case UP: headY--; break;
case DOWN: headY++; break;
case LEFT: headX--; break;
case RIGHT: headX++; break;
}
// 检查碰撞边界(保留1像素边界)
if(headX < 1 || headX >= OLED_WIDTH-1 || headY < 1 || headY >= OLED_HEIGHT-1) {
game.gameOver = true;
return;
}
// 检查碰撞自身(跳过蛇尾)
for(i = 1; i < game.length; i++) {
if(game.snakeX[i] == headX && game.snakeY[i] == headY) {
game.gameOver = true;
return;
}
}
// 移动蛇身
for(i = game.length - 1; i > 0; i--) {
game.snakeX[i] = game.snakeX[i-1];
game.snakeY[i] = game.snakeY[i-1];
}
// 更新蛇头位置
game.snakeX[0] = headX;
game.snakeY[0] = headY;
// 检查是否吃到食物(考虑食物尺寸)
if(headX >= game.foodX && headX < game.foodX + FOOD_SIZE &&
headY >= game.foodY && headY < game.foodY + FOOD_SIZE) {
// 增加蛇的长度
if(game.length < SNAKE_MAX_LENGTH) {
game.length++;
}
// 生成新食物
GenerateFood();
// 增加游戏速度
if(game.speed > 50) game.speed -= 5;
}
}
// 渲染游戏
void RenderGame(void) {
uint8_t i;
OLED_Clear();
// 绘制边界
OLED_DrawRect(0, 0, OLED_WIDTH, 1, true);
OLED_DrawRect(0, OLED_HEIGHT-1, OLED_WIDTH, 1, true);
OLED_DrawRect(0, 0, 1, OLED_HEIGHT, true);
OLED_DrawRect(OLED_WIDTH-1, 0, 1, OLED_HEIGHT, true);
// 绘制蛇
for(i = 0; i < game.length; i++) {
// 蛇头用不同颜色
bool isHead = (i == 0);
OLED_DrawRect(game.snakeX[i], game.snakeY[i], 2, 2, isHead);
}
// 绘制食物
OLED_DrawRect(game.foodX, game.foodY, FOOD_SIZE, FOOD_SIZE, true);
// 游戏结束显示
if(game.gameOver) {
DrawGameOver();
}
}
// 处理按键输入
void ProcessInput(void) {
// 读取按键状态(PF0:左, PF1:下, PF2:上, PF3:右)
uint8_t buttons = GPIOPinRead(GPIO_PORTF_BASE,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
// 根据按键改变方向(防止180度转向)
if((buttons & GPIO_PIN_2) == 0 && game.direction != DOWN) { // 上键
game.direction = UP;
}
else if((buttons & GPIO_PIN_1) == 0 && game.direction != UP) { // 下键
game.direction = DOWN;
}
else if((buttons & GPIO_PIN_0) == 0 && game.direction != RIGHT) { // 左键
game.direction = LEFT;
}
else if((buttons & GPIO_PIN_3) == 0 && game.direction != LEFT) { // 右键
game.direction = RIGHT;
}
}
// 毫秒级延时函数
void DelayMs(uint32_t ui32Ms) {
uint32_t ui32Delay = g_ui32SysClock / 3000 * ui32Ms;
while(ui32Delay--) {
__asm("nop");
}
}
int main(void) {
// 配置系统时钟为80MHz
g_ui32SysClock = SysCtlClockFreqSet(
(SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480),
80000000);
// 启用GPIO端口F(按键)
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF)) {}
// 解锁PF0(Tiva C系列特殊处理)
HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY;
HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= 0x01;
// 配置四个方向按键
GPIOPinTypeGPIOInput(GPIO_PORTF_BASE,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
GPIOPadConfigSet(GPIO_PORTF_BASE,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3,
GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
// 启用GPIO端口A(SSI0)
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA)) {}
// 配置SSI0引脚
GPIOPinConfigure(GPIO_PA2_SSI0CLK);
GPIOPinConfigure(GPIO_PA3_SSI0FSS);
GPIOPinConfigure(GPIO_PA5_SSI0TX);
GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_5);
// 启用SSI0外设
SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0)) {}
// 配置SSI0
SSIConfigSetExpClk(SSI0_BASE, g_ui32SysClock,
SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 1000000, 8);
SSIEnable(SSI0_BASE);
// 配置OLED控制引脚(PB0: DC, PB1: RST)
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)) {}
GPIOPinTypeGPIOOutput(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1);
GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1, GPIO_PIN_0 | GPIO_PIN_1);
// 初始化OLED
OLED_Init();
OLED_Clear();
// 初始化游戏
InitGame();
// 主游戏循环
while(1) {
if(!game.gameOver) {
ProcessInput();
UpdateSnake();
RenderGame();
DelayMs(game.speed);
} else {
// 游戏结束状态,等待重启
uint8_t buttons = GPIOPinRead(GPIO_PORTF_BASE,
GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
if((buttons & (GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3)) == 0) {
InitGame();
}
}
}
}
DY-Tiva-PB为自带lcd显示单元,那么上述程序要如何修改才能完成贪吃蛇小游戏,并给出完整的游戏代码