基于51单片机和OLED12864、独立按键的小游戏《贪吃蛇》

系列文章目录


前言

本代码使用的是普中A2开发板。

【单片机】STC8G1K17A
【频率】35.0000MHz
【外设】OLED12864、独立按键

有两个版本:一位独立按键、两位独立按键。

本文代码对应的是一位独立按键的版本。

效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。

一、效果展示

在这里插入图片描述
在这里插入图片描述

二、各模块代码

1、OLED12864

h文件

#ifndef __OLED_FONT_H__
#define __OLED_FONT_H__

/** 数据存储格式:
  * 
  * 纵向8点,高位在下,先从左到右,再从上到下
  * 每一个Bit对应一个像素点
  * 
  *       B0 B0            B0 B0
  *       B1 B1            B1 B1
  *       B2 B2            B2 B2
  *       B3 B3  ------->  B3 B3 ---
  *       B4 B4            B4 B4    |
  *       B5 B5            B5 B5    |
  *       B6 B6            B6 B6    |
  *       B7 B7            B7 B7    |
  *                                 v
  *   <----------------------------
  *  |    
  *  |    B0 B0            B0 B0
  *  |    B1 B1            B1 B1
  *  v    B2 B2            B2 B2
  *   --> B3 B3  ------->  B3 B3
  *       B4 B4            B4 B4
  *       B5 B5            B5 B5
  *       B6 B6            B6 B6
  *       B7 B7            B7 B7
  */

//ASCII字模数据
unsigned char code OLED_F8x16[][16]={	//宽8像素,高16像素
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//   0
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,// ! 1
0x00,0x16,0x0E,0x00,0x16,0x0E,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// " 2
0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,// # 3
0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,// $ 4
0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,// % 5
0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,// & 6
0x00,0x00,0x00,0x16,0x0E,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ' 7
0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,// ( 8
0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,// ) 9
0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,// * 10
0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,// + 11
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,// , 12
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,// - 13
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,// . 14
0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,// / 15
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,// 0 16
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// 1 17
0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,// 2 18
0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,// 3 19
0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,// 4 20
0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,// 5 21
0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,// 6 22
0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,// 7 23
0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,// 8 24
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,// 9 25
0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,// : 26
0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,// ; 27
0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,// < 28
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,// = 29
0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,// > 30
0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,// ? 31
0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,// @ 32
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,// A 33
0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,// B 34
0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,// C 35
0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,// D 36
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,// E 37
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,// F 38
0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,// G 39
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,// H 40
0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// I 41
0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,// J 42
0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,// K 43
0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,// L 44
0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,// M 45
0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,// N 46
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,// O 47
0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,// P 48
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,// Q 49
0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,// R 50
0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,// S 51
0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,// T 52
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,// U 53
0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,// V 54
0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,// W 55
0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,// X 56
0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,// Y 57
0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,// Z 58
0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,// [ 59
0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,// \ 60
0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,// ] 61
0x00,0x20,0x10,0x08,0x04,0x08,0x10,0x20,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ^ 62
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,// _ 63
0x00,0x02,0x04,0x08,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ` 64
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,// a 65
0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,// b 66
0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,// c 67
0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,// d 68
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,// e 69
0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// f 70
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,// g 71
0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,// h 72
0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// i 73
0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,// j 74
0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,// k 75
0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// l 76
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,// m 77
0x00,0x80,0x80,0x00,0x80,0x80,0x00,0x00,
0x00,0x20,0x3F,0x21,0x00,0x20,0x3F,0x20,// n 78
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,// o 79
0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,// p 80
0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,// q 81
0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,// r 82
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,// s 83
0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,// t 84
0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,// u 85
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,// v 86
0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,// w 87
0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,// x 88
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,// y 89
0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,// z 90
0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,// { 91
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,// | 92
0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,// } 93
0x00,0x80,0x40,0x40,0x80,0x00,0x00,0x80,
0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,// ~ 94
};

unsigned char code OLED_F6x8[][6]={	//宽6像素,高8像素
0x00,0x00,0x00,0x00,0x00,0x00,//   0
0x00,0x00,0x00,0x2F,0x00,0x00,// ! 1
0x00,0x00,0x07,0x00,0x07,0x00,// " 2
0x00,0x14,0x7F,0x14,0x7F,0x14,// # 3
0x00,0x24,0x2A,0x7F,0x2A,0x12,// $ 4
0x00,0x23,0x13,0x08,0x64,0x62,// % 5
0x00,0x36,0x49,0x55,0x22,0x50,// & 6
0x00,0x00,0x00,0x07,0x00,0x00,// ' 7
0x00,0x00,0x1C,0x22,0x41,0x00,// ( 8
0x00,0x00,0x41,0x22,0x1C,0x00,// ) 9
0x00,0x14,0x08,0x3E,0x08,0x14,// * 10
0x00,0x08,0x08,0x3E,0x08,0x08,// + 11
0x00,0x00,0x00,0xA0,0x60,0x00,// , 12
0x00,0x08,0x08,0x08,0x08,0x08,// - 13
0x00,0x00,0x60,0x60,0x00,0x00,// . 14
0x00,0x20,0x10,0x08,0x04,0x02,// / 15
0x00,0x3E,0x51,0x49,0x45,0x3E,// 0 16
0x00,0x00,0x42,0x7F,0x40,0x00,// 1 17
0x00,0x42,0x61,0x51,0x49,0x46,// 2 18
0x00,0x21,0x41,0x45,0x4B,0x31,// 3 19
0x00,0x18,0x14,0x12,0x7F,0x10,// 4 20
0x00,0x27,0x45,0x45,0x45,0x39,// 5 21
0x00,0x3C,0x4A,0x49,0x49,0x30,// 6 22
0x00,0x01,0x71,0x09,0x05,0x03,// 7 23
0x00,0x36,0x49,0x49,0x49,0x36,// 8 24
0x00,0x06,0x49,0x49,0x29,0x1E,// 9 25
0x00,0x00,0x36,0x36,0x00,0x00,// : 26
0x00,0x00,0x56,0x36,0x00,0x00,// ; 27
0x00,0x08,0x14,0x22,0x41,0x00,// < 28
0x00,0x14,0x14,0x14,0x14,0x14,// = 29
0x00,0x00,0x41,0x22,0x14,0x08,// > 30
0x00,0x02,0x01,0x51,0x09,0x06,// ? 31
0x00,0x3E,0x49,0x55,0x59,0x2E,// @ 32
0x00,0x7C,0x12,0x11,0x12,0x7C,// A 33
0x00,0x7F,0x49,0x49,0x49,0x36,// B 34
0x00,0x3E,0x41,0x41,0x41,0x22,// C 35
0x00,0x7F,0x41,0x41,0x22,0x1C,// D 36
0x00,0x7F,0x49,0x49,0x49,0x41,// E 37
0x00,0x7F,0x09,0x09,0x09,0x01,// F 38
0x00,0x3E,0x41,0x49,0x49,0x7A,// G 39
0x00,0x7F,0x08,0x08,0x08,0x7F,// H 40
0x00,0x00,0x41,0x7F,0x41,0x00,// I 41
0x00,0x20,0x40,0x41,0x3F,0x01,// J 42
0x00,0x7F,0x08,0x14,0x22,0x41,// K 43
0x00,0x7F,0x40,0x40,0x40,0x40,// L 44
0x00,0x7F,0x02,0x0C,0x02,0x7F,// M 45
0x00,0x7F,0x04,0x08,0x10,0x7F,// N 46
0x00,0x3E,0x41,0x41,0x41,0x3E,// O 47
0x00,0x7F,0x09,0x09,0x09,0x06,// P 48
0x00,0x3E,0x41,0x51,0x21,0x5E,// Q 49
0x00,0x7F,0x09,0x19,0x29,0x46,// R 50
0x00,0x46,0x49,0x49,0x49,0x31,// S 51
0x00,0x01,0x01,0x7F,0x01,0x01,// T 52
0x00,0x3F,0x40,0x40,0x40,0x3F,// U 53
0x00,0x1F,0x20,0x40,0x20,0x1F,// V 54
0x00,0x3F,0x40,0x38,0x40,0x3F,// W 55
0x00,0x63,0x14,0x08,0x14,0x63,// X 56
0x00,0x07,0x08,0x70,0x08,0x07,// Y 57
0x00,0x61,0x51,0x49,0x45,0x43,// Z 58
0x00,0x00,0x7F,0x41,0x41,0x00,// [ 59
0x00,0x02,0x04,0x08,0x10,0x20,// \ 60
0x00,0x00,0x41,0x41,0x7F,0x00,// ] 61
0x00,0x04,0x02,0x01,0x02,0x04,// ^ 62
0x00,0x40,0x40,0x40,0x40,0x40,// _ 63
0x00,0x00,0x01,0x02,0x04,0x00,// ` 64
0x00,0x20,0x54,0x54,0x54,0x78,// a 65
0x00,0x7F,0x48,0x44,0x44,0x38,// b 66
0x00,0x38,0x44,0x44,0x44,0x20,// c 67
0x00,0x38,0x44,0x44,0x48,0x7F,// d 68
0x00,0x38,0x54,0x54,0x54,0x18,// e 69
0x00,0x08,0x7E,0x09,0x01,0x02,// f 70
0x00,0x18,0xA4,0xA4,0xA4,0x7C,// g 71
0x00,0x7F,0x08,0x04,0x04,0x78,// h 72
0x00,0x00,0x44,0x7D,0x40,0x00,// i 73
0x00,0x40,0x80,0x84,0x7D,0x00,// j 74
0x00,0x7F,0x10,0x28,0x44,0x00,// k 75
0x00,0x00,0x41,0x7F,0x40,0x00,// l 76
0x00,0x7C,0x04,0x18,0x04,0x78,// m 77
0x00,0x7C,0x08,0x04,0x04,0x78,// n 78
0x00,0x38,0x44,0x44,0x44,0x38,// o 79
0x00,0xFC,0x24,0x24,0x24,0x18,// p 80
0x00,0x18,0x24,0x24,0x18,0xFC,// q 81
0x00,0x7C,0x08,0x04,0x04,0x08,// r 82
0x00,0x48,0x54,0x54,0x54,0x20,// s 83
0x00,0x04,0x3F,0x44,0x40,0x20,// t 84
0x00,0x3C,0x40,0x40,0x20,0x7C,// u 85
0x00,0x1C,0x20,0x40,0x20,0x1C,// v 86
0x00,0x3C,0x40,0x30,0x40,0x3C,// w 87
0x00,0x44,0x28,0x10,0x28,0x44,// x 88
0x00,0x1C,0xA0,0xA0,0xA0,0x7C,// y 89
0x00,0x44,0x64,0x54,0x4C,0x44,// z 90
0x00,0x00,0x08,0x7F,0x41,0x00,// { 91
0x00,0x00,0x00,0x7F,0x00,0x00,// | 92
0x00,0x00,0x41,0x7F,0x08,0x00,// } 93
0x00,0x08,0x04,0x08,0x10,0x08,// ~ 94
};

#endif

h文件

#ifndef __OLED_H__
#define __OLED_H__

void OLED_WriteCommand(unsigned char Command);
void OLED_WriteData(unsigned char Data);
void OLED_SetCursor(unsigned char Y, unsigned char X);
void OLED_Clear(void);
void OLED_Init(void);
void OLED_ShowChar(unsigned char FontSize, unsigned char Line, unsigned char Column, char Char);
void OLED_ShowString(unsigned char FontSize, unsigned char Line, unsigned char Column, char *String);
void OLED_ShowNum(unsigned char FontSize, unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length);
void OLED_ShowSignedNum(unsigned char FontSize, unsigned char Line, unsigned char Column, int Number, unsigned char Length);
void OLED_ShowHexNum(unsigned char FontSize, unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length);
void OLED_ShowBinNum(unsigned char FontSize, unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length);

#endif

c文件

#include <STC8G.H>
#include "OLED_Font.h"

//引脚配置
sbit OLED_SDA=P3^3;
sbit OLED_SCL=P3^2;

/**
  * 函    数:OLED私有延时函数,延时9/35=0.257us
  * 参    数:无
  * 返 回 值:无
  */
void OLED_Delay(void)	//1T@35.0000MHz	
{
	unsigned char i;
	i=1;
	while(--i);
}

/**
  * 函    数:引脚初始化
  * 参    数:无
  * 返 回 值:无
  * 说    明:单片机较快时,需要考虑有没有超过I2C的最大传输速率,如果有,则要加延时
  */
void OLED_I2C_Init(void)
{
	OLED_SCL=1;OLED_Delay(); 
	OLED_SDA=1;OLED_Delay(); 
}

/**
  * 函    数:I2C开始
  * 参    数:无
  * 返 回 值:无
  * 说    明:单片机较快时,需要考虑有没有超过I2C的最大传输速率,如果有,则要加延时
  */
void OLED_I2C_Start(void)
{
	OLED_SDA=1;OLED_Delay(); 
	OLED_SCL=1;OLED_Delay(); 
	OLED_SDA=0;OLED_Delay(); 
	OLED_SCL=0;OLED_Delay(); 
}

/**
  * 函    数:I2C停止
  * 参    数:无
  * 返 回 值:无
  * 说    明:单片机较快时,需要考虑有没有超过I2C的最大传输速率,如果有,则要加延时
  */
void OLED_I2C_Stop(void)
{
	OLED_SDA=0;OLED_Delay(); 
	OLED_SCL=1;OLED_Delay(); 
	OLED_SDA=1;OLED_Delay(); 
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  * 说    明:单片机较快时,需要考虑有没有超过I2C的最大传输速率,如果有,则要加延时
  */
void OLED_I2C_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		OLED_SDA=Byte&(0x80>>i);OLED_Delay(); 
		OLED_SCL=1;OLED_Delay(); 
		OLED_SCL=0;OLED_Delay(); 
	}
	OLED_SCL=1;OLED_Delay(); 	//额外的一个时钟,不处理应答信号
	OLED_SCL=0;OLED_Delay(); 
}

/**
  * 函    数:OLED写命令
  * 参    数:Command 要写入的命令
  * 返 回 值:无
  */
void OLED_WriteCommand(unsigned char Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);	//从机地址
	OLED_I2C_SendByte(0x00);	//写命令
	OLED_I2C_SendByte(Command); 
	OLED_I2C_Stop();
}

/**
  * 函    数:OLED写数据
  * 参    数:Data 要写入的数据
  * 返 回 值:无
  */
void OLED_WriteData(unsigned char Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78);	//从机地址
	OLED_I2C_SendByte(0x40);	//写数据
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
  * 函    数:OLED设置光标位置
  * 参    数:Y 以左上角为原点,向下方向的坐标,范围:0~7
  * 参    数:X 以左上角为原点,向右方向的坐标,范围:0~127
  * 返 回 值:无
  */
void OLED_SetCursor(unsigned char Y, unsigned char X)
{
	OLED_WriteCommand(0xB0|Y);				//设置Y位置
	OLED_WriteCommand(0x10|((X&0xF0)>>4));	//设置X位置高4位
	OLED_WriteCommand(0x00|(X&0x0F));		//设置X位置低4位
}

/**
  * 函    数:OLED清屏
  * 参    数:无
  * 返 回 值:无
  */
void OLED_Clear(void)
{  
	unsigned char i, j;
	for(j=0;j<8;j++)	//共8页
	{
		OLED_SetCursor(j,0);
		for(i=0;i<128;i++)	//每页对应128个字节
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
  * 函    数:OLED初始化
  * 参    数:无
  * 返 回 值:无
  * 说    明:写入一系列的命令,对OLED进行初始化配置
  */
void OLED_Init(void)
{
	unsigned char i,j;
	for(i=0;i<200;i++)	//在初始化前,加入适量延时,待OLED供电稳定
	{
		for(j=0;j<200;j++);
	}
	
	OLED_I2C_Init();			//端口初始化
	
	OLED_WriteCommand(0xAE);	//设置显示开启/关闭,0xAE关闭,0xAF开启
	
	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_WriteCommand(0x80);	//0x00~0xFF
	
	OLED_WriteCommand(0xA8);	//设置多路复用率
	OLED_WriteCommand(0x3F);	//0x0E~0x3F
	
	OLED_WriteCommand(0xD3);	//设置显示偏移
	OLED_WriteCommand(0x00);	//0x00~0x7F
	
	OLED_WriteCommand(0x40);	//设置显示开始行,0x40~0x7F
	
	OLED_WriteCommand(0xA0);	//设置左右方向,0xA1正常,0xA0左右反置
//	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常,0xA0左右反置
	
	OLED_WriteCommand(0xC0);	//设置上下方向,0xC8正常,0xC0上下反置
//	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常,0xC0上下反置

	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
	OLED_WriteCommand(0x12);	//设置COM引脚硬件配置
	
	OLED_WriteCommand(0x81);	//设置对比度
	OLED_WriteCommand(0xCF);	//0x00~0xFF

	OLED_WriteCommand(0xD9);	//设置预充电周期
	OLED_WriteCommand(0xF1);	//设置预充电周期

	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
	OLED_WriteCommand(0x30);	//设置VCOMH取消选择级别

	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭

	OLED_WriteCommand(0xA6);	//设置正常/反色显示,0xA6正常,0xA7反色

	OLED_WriteCommand(0x8D);	//设置充电泵
	OLED_WriteCommand(0x14);	//设置充电泵

	OLED_Clear();				//OLED清屏

	OLED_WriteCommand(0xAF);	//开启显示
}

/**
  * 函    数:OLED显示一个字符
  * 参    数:FontSize 字符宽度,取值:6(宽6像素高8像素)或8(宽8像素高16像素)
  * 参    数:Line 行位置,范围:0~7(字符宽度为6时)或0~3(字符宽度为8时)
  * 参    数:Column 列位置,范围:0~127
  * 参    数:Char 要显示的一个字符,范围:ASCII可见字符
  * 返 回 值:无
  * 说    明:字符跟OLED的页对齐
  * 说    明:超出屏幕区域的字符不显示
  */
void OLED_ShowChar(unsigned char FontSize, unsigned char Line, unsigned char Column, char Char)
{      	
	unsigned char i;
	if(FontSize==6)	//如果字符宽度为6
	{
		if(Line>=0 && Line<=7 && Column>=0 &&Column<=123)	//超出屏幕区域的字符不显示
		{
			OLED_SetCursor(Line,Column);	//设置光标位置
			for(i=0;i<6;i++)
			{
				OLED_WriteData(OLED_F6x8[Char-' '][i]);
			}
		}
	}
	else if(FontSize==8)	//如果字符宽度为8
	{
		if(Line>=0 && Line<=3 && Column>=0 &&Column<=121)	//超出屏幕区域的字符不显示
		{
			OLED_SetCursor(Line*2,Column);	//设置光标位置在上半部分
			for(i=0;i<8;i++)
			{
				OLED_WriteData(OLED_F8x16[Char-' '][i]);			//显示上半部分内容
			}
			OLED_SetCursor(Line*2+1,Column);	//设置光标位置在下半部分
			for(i=0;i<8;i++)
			{
				OLED_WriteData(OLED_F8x16[Char-' '][i+8]);		//显示下半部分内容
			}
		}
	}
}

/**
  * 函    数:OLED显示字符串
  * 参    数:FontSize 字符宽度,取值:6(宽6像素高8像素)或8(宽8像素高16像素)
  * 参    数:Line 行位置,范围:0~7(字符宽度为6时)或0~3(字符宽度为8时)
  * 参    数:Column 列位置,范围:0~127
  * 参    数:String 要显示的字符串,范围:ASCII可见字符
  * 返 回 值:无
  * 说    明:字符跟OLED的页对齐
  * 说    明:超出屏幕区域的字符不显示
  */
void OLED_ShowString(unsigned char FontSize, unsigned char Line, unsigned char Column, char *String)
{
	unsigned char i;
	for(i=0;String[i]!='\0';i++)
	{
		OLED_ShowChar(FontSize,Line,Column+i*FontSize,String[i]);
	}
}

/**
  * 函    数:幂函数/指数函数
  * 参    数:X 底
  * 参    数:Y 幂
  * 返 回 值:返回值等于X的Y次方
  */
unsigned int OLED_Pow(unsigned int X, unsigned int Y)
{
	unsigned int Result = 1;
	while(Y--)
	{
		Result*=X;
	}
	return Result;
}

/**
  * 函    数:OLED显示数字(十进制,正数)
  * 参    数:FontSize 字符宽度,取值:6(宽6像素高8像素)或8(宽8像素高16像素)
  * 参    数:Line 行位置,范围:0~7(字符宽度为6时)或0~3(字符宽度为8时)
  * 参    数:Column 列位置,范围:0~127
  * 参    数:Number 要显示的数字,范围:0~65535
  * 参    数:Length 要显示数字的长度,范围:1~5
  * 返 回 值:无
  * 说    明:字符跟OLED的页对齐
  * 说    明:超出屏幕区域的字符不显示
  */
void OLED_ShowNum(unsigned char FontSize, unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length)
{
	unsigned char i;
	for(i=0;i<Length;i++)							
	{
		OLED_ShowChar(FontSize,Line,Column+i*FontSize,Number/OLED_Pow(10,Length-i-1)%10+'0');
	}
}

/**
  * 函    数:OLED显示数字(十进制,带符号数)
  * 参    数:FontSize 字符宽度,取值:6(宽6像素高8像素)或8(宽8像素高16像素)
  * 参    数:Line 行位置,范围:0~7(字符宽度为6时)或0~3(字符宽度为8时)
  * 参    数:Column 列位置,范围:0~127
  * 参    数:Number 要显示的数字,范围:-32768~32767
  * 参    数:Length 要显示数字的长度,范围:1~5
  * 返 回 值:无
  * 说    明:字符跟OLED的页对齐
  * 说    明:超出屏幕区域的字符不显示
  */
//void OLED_ShowSignedNum(unsigned char FontSize, unsigned char Line, unsigned char Column, int Number, unsigned char Length)
//{
//	unsigned char i;
//	unsigned int Number1;
//	if(Number >= 0)
//	{
//		OLED_ShowChar(FontSize,Line,Column,'+');
//		Number1=Number;
//	}
//	else
//	{
//		OLED_ShowChar(FontSize,Line,Column,'-');
//		Number1=-Number;
//	}
//	for(i=0;i<Length;i++)							
//	{
//		OLED_ShowChar(FontSize,Line,Column+(i+1)*FontSize,Number1/OLED_Pow(10,Length-i-1)%10+'0');
//	}
//}	//本案例用不到,注释掉,节省空间

/**
  * 函    数:OLED显示数字(十六进制,正数)
  * 参    数:FontSize 字符宽度,取值:6(宽6像素高8像素)或8(宽8像素高16像素)
  * 参    数:Line 行位置,范围:0~7(字符宽度为6时)或0~3(字符宽度为8时)
  * 参    数:Column 列位置,范围:0~127
  * 参    数:Number 要显示的数字,范围:0~0xFFFF
  * 参    数:Length 要显示数字的长度,范围:1~4
  * 返 回 值:无
  * 说    明:字符跟OLED的页对齐
  * 说    明:超出屏幕区域的字符不显示
  */
//void OLED_ShowHexNum(unsigned char FontSize, unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length)
//{
//	unsigned char i, SingleNumber;
//	for(i=0;i<Length;i++)							
//	{
//		SingleNumber=Number/OLED_Pow(16,Length-i-1)%16;
//		if(SingleNumber<10)
//		{
//			OLED_ShowChar(FontSize,Line,Column+i*FontSize,SingleNumber+'0');
//		}
//		else
//		{
//			OLED_ShowChar(FontSize,Line, Column+i*FontSize,SingleNumber-10+'A');
//		}
//	}
//}

/**
  * 函    数:OLED显示数字(二进制,正数)
  * 参    数:FontSize 字符宽度,取值:6(宽6像素高8像素)或8(宽8像素高16像素)
  * 参    数:Line 行位置,范围:0~7(字符宽度为6时)或0~3(字符宽度为8时)
  * 参    数:Column 列位置,范围:0~127
  * 参    数:Number 要显示的数字,范围:0~1111 1111 1111 1111
  * 参    数:Length 要显示数字的长度,范围:1~16
  * 返 回 值:无
  * 说    明:字符跟OLED的页对齐
  * 说    明:超出屏幕区域的字符不显示
  */
//void OLED_ShowBinNum(unsigned char FontSize, unsigned char Line, unsigned char Column, unsigned int Number, unsigned char Length)
//{
//	unsigned char i;
//	for(i=0;i<Length;i++)							
//	{
//		OLED_ShowChar(FontSize,Line,Column+i*FontSize,Number/OLED_Pow(2,Length-i-1)%2+'0');
//	}
//}

2、独立按键

h文件

/*方法来源:B站江协科技的编程技巧(第二期)*/
#ifndef __KEY_H__
#define __KEY_H__

#define KEY_QUANTITY	1	//按键总数量
//#define KEY_QUANTITY	8	//按键总数量

//按键对应数组的索引
#define K1	0
//#define K2	1
//#define K3	2
//#define K4	3
//#define K5	4
//#define K6	5
//#define K7	6
//#define K8	7

//标志位掩码
#define HOLD	0x01	//按住
#define DOWN	0x02	//按下
#define UP		0x04	//松开
#define SINGLE	0x08	//单击
#define DOUBLE	0x10	//双击
#define LONG	0x20	//长按
#define REPEAT	0x40	//重复

void Key_Clear(void);
unsigned char Key(unsigned char n,unsigned char Flag);
void Key_Tick(void);

#endif

c文件

/*方法来源:B站江协科技的编程技巧(第二期)*/
#include <STC8G.H>
#include "Key.h"

#define KEY_PRESSED		1	//按键已按下
#define KEY_UNPRESSED	0	//按键未按下

//数值单位:定时器中断函数中相邻两次调用函数Key_Tick的时间间隔
#define KEY_TIME_DOUBLE		10	//双击判定的等待时长
#define KEY_TIME_LONG		25	//长按判定的等待时长
#define KEY_TIME_REPEAT		0	//长按后,重复标志位再次置1的时长
//本案例中,以上数值的单位是:20ms

//引脚配置
sbit Key1=P5^5;
//sbit Key2=P1^1;
//sbit Key3=P1^2;
//sbit Key4=P1^3;
//sbit Key5=P1^4;
//sbit Key6=P1^5;
//sbit Key7=P1^6;
//sbit Key8=P1^7;

/** 按键标志数组
  * 
  * 一个字节对应8位,分别为:B7 B6 B5 B4 B3 B2 B1 B0
  * 【B0】1:一直按住,0:未按下
  * 【B1】1:按下瞬间,0:不是按下瞬间
  * 【B2】1:松开瞬间,0:不是松开瞬间
  * 【B3】1:单击,0:不是单击
  * 【B4】1:双击,0:不是双击
  * 【B5】1:长按,0:不是长按
  * 【B6】1:重复,0:未重复
  * 【B7】保留位
  * 其中单击、双击、长按互斥(即这三个对应的标志位最多只能有一个是1)
  * 除B0外,其他标志位在读取后置0
  * xdata:变量保存在片外RAM
  */
unsigned char xdata Key_Flag[KEY_QUANTITY];	//按键总数量在KeyScan.h中进行宏定义

/**
  * 函    数:获取按键状态(检测按键是否按下)
  * 参    数:n 按键索引,范围:0~KEY_QUANTITY-1
  * 返 回 值:按下返回KEY_PRESSED(1),未按下返回KEY_UNPRESSED(0)
  */
unsigned char Key_GetState(unsigned char n)
{
	if(n==K1)
	{
		if(Key1==0)
		{
			return KEY_PRESSED;
		}
	}
//	else if(n==K2)
//	{
//		if(Key2==0)
//		{
//			return KEY_PRESSED;
//		}
//	}
//	else if(n==K3)
//	{
//		if(Key3==0)
//		{
//			return KEY_PRESSED;
//		}
//	}
//	else if(n == K4)
//	{
//		if(Key4==0)
//		{
//			return KEY_PRESSED;
//		}
//	}
//	else if(n==K5)
//	{
//		if(Key5==0)
//		{
//			return KEY_PRESSED;
//		}
//	}
//	else if(n == K6)
//	{
//		if(Key6==0)
//		{
//			return KEY_PRESSED;
//		}
//	}
//	else if(n==K7)
//	{
//		if(Key7==0)
//		{
//			return KEY_PRESSED;
//		}
//	}
//	else if(n == K8)
//	{
//		if(Key8==0)
//		{
//			return KEY_PRESSED;
//		}
//	}
	return KEY_UNPRESSED;
}

/**
  * 函    数:清空所有按键的所有标志位
  * 参    数:无
  * 返 回 值:无
  * 说    明:防止切换模式的时候受上一模式所按按键的影响
  */
void Key_Clear(void)
{
	unsigned char i;
	for(i=0;i<KEY_QUANTITY;i++)
	{
		Key_Flag[i]=0;
	}
}

/**
  * 函    数:按键检测
  * 参    数:n 按键索引,范围:0~KEY_QUANTITY-1
  * 参    数:Flag 标志位掩码,用来获取标志的某一位的值为1还是0
  * 返 回 值:1或者0
  */
unsigned char Key(unsigned char n, unsigned char Flag)
{
	if(Key_Flag[n] & Flag)
	{
		if(Flag != HOLD)
		{
			Key_Flag[n] &= ~Flag;
		}
		return 1;
	}
	return 0;
}

/**
  * 函    数:按键检测驱动函数,在定时器中断函数中使用
  * 参    数:无
  * 返 回 值:无
  * 说    明:最后的else的目的是防止S[i]的初值在0~4之外导致检测不了单击、双击、长按、重复
  */
void Key_Tick(void)
{
	static unsigned char xdata i;
	static unsigned char xdata CurrState[KEY_QUANTITY];
	static unsigned char xdata PrevState[KEY_QUANTITY];
	static unsigned char xdata S[KEY_QUANTITY];
	static unsigned int xdata KeyTime[KEY_QUANTITY];

	for(i=0;i<KEY_QUANTITY;i++)
	{
		if(KeyTime[i]>0)
		{
			KeyTime[i]--;
		}
	}

	for(i=0;i<KEY_QUANTITY;i++)
	{
		PrevState[i]=CurrState[i];
		CurrState[i]=Key_GetState(i);
		
		if(CurrState[i] == KEY_PRESSED)
		{
			Key_Flag[i] |= HOLD;
		}
		else
		{
			Key_Flag[i] &= ~HOLD;
		}
		
		if(CurrState[i] == KEY_PRESSED && PrevState[i] == KEY_UNPRESSED)
		{
			Key_Flag[i] |= DOWN;
		}
		
		if(CurrState[i] == KEY_UNPRESSED && PrevState[i] == KEY_PRESSED)
		{
			Key_Flag[i] |= UP;
		}
		
		if(S[i] == 0)
		{
			if(CurrState[i] == KEY_PRESSED)
			{
				KeyTime[i]=KEY_TIME_LONG;
				S[i]=1;
			}
		}
		else if(S[i] == 1)
		{
			if(CurrState[i] == KEY_UNPRESSED)
			{
				KeyTime[i]=KEY_TIME_DOUBLE;
				S[i]=2;
			}
			else if(KeyTime[i] == 0)
			{
				KeyTime[i]=KEY_TIME_REPEAT;
				Key_Flag[i] |= LONG;
				S[i]=4;
			}
		}
		else if(S[i]==2)
		{
			if(CurrState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= DOUBLE;
				S[i]=3;
			}
			else if(KeyTime[i] == 0)
			{
				Key_Flag[i] |= SINGLE;
				S[i]=0;
			}
		}
		else if(S[i] == 3)
		{
			if(CurrState[i] == KEY_UNPRESSED)
			{
				S[i]=0;
			}
		}
		else if(S[i]==4)
		{
			if(CurrState[i] == KEY_UNPRESSED)
			{
				S[i]=0;
			}
			else if(KeyTime[i] == 0)
			{
				KeyTime[i]=KEY_TIME_REPEAT;
				Key_Flag[i] |= REPEAT;
				S[i]=4;
			}
		}
		else
		{
			S[i]=0;
		}
	}
}

3、定时器0

h文件

#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);

#endif

c文件

#include <STC8G.H>

/**
  * 函    数:定时器0初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer0_Init(void)
{	
	AUXR|=0x80;	//定时器时钟1T模式
	TMOD&=0xF0;	//设置定时器模式为16位不自动重装模式
	TMOD|=0x01;	//设置定时器模式为16位不自动重装模式
	TL0=0x48;	//设置定时初值,定时1ms,1T@35.0000MHz
	TH0=0x77;	//设置定时初值,定时1ms,1T@35.0000MHz
	TF0=0;		//清除TF0标志
	TR0=1;		//定时器0开始计时
	ET0=1;		//打开定时器0中断允许
	EA=1;		//打开总中断
	PT0=0;		//设置定时器0的优先级
}

/*定时器中断函数模板
void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	static unsigned int T0Count;	//定义静态变量
	TL0=0x48;	//设置定时初值,定时1ms,1T@35.0000MHz
	TH0=0x77;	//设置定时初值,定时1ms,1T@35.0000MHz
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		
	}
}
*/

三、主函数

main.c

/*by甘腾胜@20250709
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC8G1K17A
【频率】1T@35.0000MHz
【外设】OLED12864、独立按键
【接线】
(1)OLED12864:GND接P30,VDD接P31,SCK接P32,SDA接P33
(2)独立按键:K1接P55
【操作说明】
(1)显示游戏名称的界面单击K1、K2切换难度,长按K1或长按K2开始游戏
(2)游戏界面,单击K1蛇头左转,单击K2蛇头右转,长按K1或长按K2暂停或继续
(3)游戏结束全屏闪烁界面长按K1或长按K2返回显示游戏名称的界面
*/

#include <STC8G.H>		//51单片机头文件
#include <STDLIB.H>		//随机函数
#include "OLED.h"		//液晶显示屏
#include "Key.h"		//独立按键
#include "Timer0.h"		//定时器0

#define Total 96	//蛇的总长度

/** 屏幕游戏区域坐标轴定义:
  * 
  * 左上角为原点(0,0)
  * 横向向右为X轴,取值范围:0~11
  * 纵向向下为Y轴,取值范围:0~7
  * 每一个坐标点对应的是一个8X8像素的区域
  * 
  * 	0		X轴    11
  * 	.————————————————>
  *   0 |
  * 	|
  * 	|
  * Y轴	|
  * 	|
  * 	|
  *   7 |
  * 	v
  */

/** 游戏模式说明
  * 
  * 0:显示游戏名称和选择难度
  * 1:游戏进行中
  * 2:游戏结束全屏闪烁
  */
unsigned char Mode;
unsigned char LastMode;	//上一次的游戏模式,用来切换模式时清空所有按键的所有标志位
bit OnceFlag=1;	//各模式中(切换为其他模式前)只执行一次的标志,类似于主函数主循环前的那部分,用于该模式的初始化,1:执行,0:不执行
bit FlashFlag;	//闪烁的标志,1:不显示,0:显示
bit PauseFlag;	//游戏时暂停的标志,1:暂停,0:不暂停
bit GameOverFlag;	//游戏结束的标志,1:游戏结束,0:游戏未结束
bit MoveFlag;	//蛇移动的标志,1:移动,0:不移动
unsigned char Score;	//游戏得分,范围:2~96
/** 蛇身数据数组说明
  * 
  * 总共12X8个最小单元(8X8像素),需要12X8个字节保存蛇身数据
  * 如果用两个数组来分别存储横坐标和纵坐标,会导致RAM空间不够用
  * 高四位存储横坐标,范围:0~11(对应1~12列)
  * 低四位存储纵坐标,范围:0~7(对应1~8行)
  * 循环滚动使用数组数据
  * 即蛇头位置用索引为95的数据记录后,如果蛇再移动一次,则移动后用索引为0的数据记录
  * xdata:变量保存在片外扩展RAM
  */
unsigned char xdata SnakeBody[Total];
unsigned char Difficulty=1;	//游戏难度,范围:1~5
unsigned int Duration=1000;	//蛇移动的时间间隔,初始值为1000,单位:ms
unsigned char NowDirection;	//蛇头现在移动的方向,0:向右,1:向上,2:向左,3:向下
unsigned char LastDirection;	//蛇头上一次移动的方向,0:向右,1:向上,2:向左,3:向下
unsigned char Length;	//蛇的长度,范围:2~Total
unsigned char Head;	//蛇头对应数组SnakeBody中的数据的索引,范围:0~Total-1
unsigned char Food;	//保存创造出来的食物的位置,高四位存储列位置,范围:0~11(对应1~12列),低四位存储行位置,范围:0~7(对应1~8行)
unsigned int T0Count_0;	//定时器0全局计数变量,控制蛇的移动
unsigned int T0Count_1;	//定时器0全局计数变量,控制闪烁
unsigned int T0Count_2;	//定时器0全局计数变量,计时
unsigned int GameTime;	//游戏时间,范围:0~65535,单位:s
unsigned int GameTime1;	//游戏时间,用来跟GameTime比较,判断时间有没有变化,有则更新显示
unsigned int Steps;	//蛇前进的步数,范围:0~65535
/** 蛇的类型的缓存数组
  * 
  * 用来存储每个8X8的区域应该显示数组SnakeType中的哪种类型
  * 低四位存储应该显示哪种类型
  * 高四位存储蛇头在此位置的方向,用来控制显示蛇尾来到此位置时的方向
  * 游戏区域大小:12X8个8X8像素的区域
  * 
  * 每一个数据对应屏幕游戏区域的一个8X8像素的最小单元
  * 
  * TypeBuffer[0][0]    TypeBuffer[1][0]                TypeBuffer[11][0]
  * TypeBuffer[0][1]    TypeBuffer[1][1]                TypeBuffer[11][1]
  * TypeBuffer[0][2]    TypeBuffer[1][2]                TypeBuffer[11][2]
  * TypeBuffer[0][3]    TypeBuffer[1][3]    ------->    TypeBuffer[11][3]
  * TypeBuffer[0][4]    TypeBuffer[1][4]                TypeBuffer[11][4]
  * TypeBuffer[0][5]    TypeBuffer[1][5]                TypeBuffer[11][5]
  * TypeBuffer[0][6]    TypeBuffer[1][6]                TypeBuffer[11][6]
  * TypeBuffer[0][7]    TypeBuffer[1][7]                TypeBuffer[11][7]
  */
unsigned char xdata TypeBuffer[12][8];

/** 蛇的类型的取模
  * 
  * 以8X8像素的区域为最小单位进行显示
  * 每个8X8区域的取模设置:纵向取模,高位在下,亮点为1
  */
unsigned char code SnakeType[][8]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,	//无显示,0
0x3C,0x7E,0x7E,0xFF,0xFF,0x5A,0x3C,0x18,	//蛇头方向,右,1
0x18,0x7C,0xFA,0xFF,0xFF,0xFA,0x7C,0x18,	//蛇头方向,上,2
0x18,0x3C,0x5A,0xFF,0xFF,0x7E,0x7E,0x3C,	//蛇头方向,左,3
0x18,0x3E,0x5F,0xFF,0xFF,0x5F,0x3E,0x18,	//蛇头方向,下,4	
0x00,0x7E,0x7F,0x67,0x67,0x7F,0x7E,0x3C,	//蛇身连接,右上,5
0x3C,0x7E,0x7E,0x66,0x66,0x7E,0x7E,0x3C,	//蛇身连接,右左,6
0x00,0x7E,0xFE,0xE6,0xE6,0xFE,0x7E,0x3C,	//蛇身连接,右下,7
0x3C,0x7E,0x7F,0x67,0x67,0x7F,0x7E,0x00,	//蛇身连接,上左,8
0x00,0x7E,0xFF,0xE7,0xE7,0xFF,0x7E,0x00,	//蛇身连接,上下,9
0x3C,0x7E,0xFE,0xE6,0xE6,0xFE,0x7E,0x00,	//蛇身连接,左下,10
0x00,0x18,0x18,0x3C,0x3C,0x7E,0x7E,0x3C,	//蛇尾方向,左,11
0x00,0x06,0x1F,0x7F,0x7F,0x1F,0x06,0x00,	//蛇尾方向,下,12
0x3C,0x7E,0x7E,0x3C,0x3C,0x18,0x18,0x00,	//蛇尾方向,右,13
0x00,0x60,0xF8,0xFE,0xFE,0xF8,0x60,0x00,	//蛇尾方向,上,14
0x00,0x7E,0x42,0x5A,0x5A,0x42,0x7E,0x00,	//食物,15
};
unsigned char code Table1[]={	//游戏标题“《贪吃蛇》”:逐列式取模,高位在下,亮点为1,高16像素,宽80像素
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x40,0x01,0x20,0x02,0x90,0x04,
0x48,0x09,0x24,0x12,0x12,0x24,0x09,0x48,0x04,0x10,0x02,0x20,0x00,0x00,0x00,0x00,/*"《",0*/
0x20,0x00,0x20,0x80,0x10,0x80,0x10,0x9F,0x28,0x41,0x24,0x41,0x22,0x21,0x29,0x1D,
0xB2,0x01,0x64,0x21,0x28,0x21,0x10,0x5F,0x10,0x40,0x20,0x80,0x20,0x00,0x00,0x00,/*"贪",1*/
0x00,0x00,0xFC,0x0F,0x04,0x04,0x04,0x04,0xFC,0x0F,0x20,0x00,0x10,0x30,0x4C,0x48,
0x4B,0x44,0x48,0x42,0x48,0x42,0x48,0x41,0xC8,0x40,0x08,0x40,0x08,0x70,0x00,0x00,/*"吃",2*/
0x00,0x20,0xF8,0x63,0x08,0x21,0xFF,0x1F,0x08,0x11,0xF8,0x19,0x20,0x30,0x18,0x00,
0xC8,0x3F,0x08,0x44,0x09,0x42,0x0E,0x41,0x88,0x40,0x28,0x40,0x18,0x78,0x00,0x00,/*"蛇",3*/
0x00,0x00,0x00,0x00,0x02,0x20,0x04,0x10,0x09,0x48,0x12,0x24,0x24,0x12,0x48,0x09,
0x90,0x04,0x20,0x02,0x40,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"》",4*/
};
unsigned char code Table2[]={	//游戏标题“《SNAKE》”:逐列式取模,高位在下,亮点为1,高16像素,宽72像素
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x40,0x01,0x20,0x02,0x90,0x04,
0x48,0x09,0x24,0x12,0x12,0x24,0x09,0x48,0x04,0x10,0x02,0x20,0x00,0x00,0x00,0x00,/*"《",0*/
0x00,0x00,0x70,0x38,0x88,0x20,0x08,0x21,0x08,0x21,0x08,0x22,0x38,0x1C,0x00,0x00,/*"S",1*/
0x08,0x20,0xF8,0x3F,0x30,0x20,0xC0,0x00,0x00,0x07,0x08,0x18,0xF8,0x3F,0x08,0x00,/*"N",2*/
0x00,0x20,0x00,0x3C,0xC0,0x23,0x38,0x02,0xE0,0x02,0x00,0x27,0x00,0x38,0x00,0x20,/*"A",3*/
0x08,0x20,0xF8,0x3F,0x88,0x20,0xC0,0x01,0x28,0x26,0x18,0x38,0x08,0x20,0x00,0x00,/*"K",4*/
0x08,0x20,0xF8,0x3F,0x88,0x20,0x88,0x20,0xE8,0x23,0x08,0x20,0x10,0x18,0x00,0x00,/*"E",5*/
0x00,0x00,0x00,0x00,0x02,0x20,0x04,0x10,0x09,0x48,0x12,0x24,0x24,0x12,0x48,0x09,
0x90,0x04,0x20,0x02,0x40,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"》",6*/
};
unsigned char code Table3[]={	//“难度选择:”:逐列式取模,高位在下,亮点为1,高16像素,宽64像素
0x04,0x20,0x24,0x10,0xC4,0x0C,0x04,0x03,0xE4,0x04,0x5C,0x18,0x20,0x00,0xF8,0xFF,
0x4F,0x22,0x48,0x22,0x49,0x22,0xFA,0x3F,0x48,0x22,0x48,0x22,0x08,0x20,0x00,0x00,/*"难",0*/
0x00,0x40,0x00,0x30,0xFC,0x8F,0x24,0x80,0x24,0x84,0x24,0x4C,0xFC,0x55,0x25,0x25,
0x26,0x25,0x24,0x25,0xFC,0x55,0x24,0x4C,0x24,0x80,0x24,0x80,0x04,0x80,0x00,0x00,/*"度",1*/
0x40,0x00,0x40,0x40,0x42,0x20,0xCC,0x1F,0x00,0x20,0x50,0x50,0x4E,0x4C,0xC8,0x43,
0x48,0x40,0x7F,0x40,0xC8,0x4F,0x48,0x50,0x48,0x50,0x40,0x5C,0x00,0x40,0x00,0x00,/*"选",2*/
0x10,0x42,0x10,0x82,0xFF,0x7F,0x10,0x01,0x00,0x00,0x82,0x10,0x86,0x12,0x4A,0x12,
0x52,0x12,0xA2,0xFF,0x52,0x12,0x4A,0x12,0x86,0x12,0x80,0x10,0x80,0x00,0x00,0x00,/*"择",3*/
0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*":",4*/
};
unsigned char code Table4[]={	//难度选择的数字的字模,逐列式取模,高位在下,亮点为1,每个数字的字模都是高16像素宽16像素
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x10,0x20,0x10,0x20,0xF0,0x3F,
0xF8,0x3F,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,/*"1",0*/
0x00,0x00,0x00,0x00,0x30,0x30,0x70,0x30,0x28,0x28,0x08,0x24,0x08,0x24,0x08,0x22,
0x08,0x22,0x08,0x21,0x08,0x21,0xD8,0x20,0xF0,0x30,0x20,0x18,0x00,0x00,0x00,0x00,/*"2",1*/
0x00,0x00,0x00,0x00,0x30,0x18,0x30,0x18,0x28,0x28,0x08,0x20,0x08,0x20,0x08,0x21,
0x08,0x21,0x88,0x21,0x88,0x21,0x70,0x13,0x70,0x1E,0x00,0x0C,0x00,0x00,0x00,0x00,/*"3",2*/
0x00,0x00,0x00,0x04,0x00,0x06,0x00,0x05,0x80,0x04,0x80,0x04,0x40,0x24,0x20,0x24,
0x10,0x24,0xF0,0x3F,0xF8,0x3F,0xF8,0x3F,0x00,0x24,0x00,0x24,0x00,0x24,0x00,0x00,/*"4",3*/
0x00,0x00,0x00,0x00,0x00,0x18,0xF8,0x19,0x08,0x29,0x88,0x20,0x88,0x20,0x88,0x20,
0x88,0x20,0x88,0x20,0x88,0x20,0x88,0x11,0x08,0x1F,0x00,0x0E,0x00,0x00,0x00,0x00,/*"5",4*/
};

/**
  * 函    数:显示一幅宽Width像素高16像素的图片(跟屏幕对应的字节对齐)
  * 参    数:Page 页位置,范围:0~7
  * 参    数:X 列位置,范围:0~127
  * 参    数:Width 图片宽度,单位:像素
  * 参    数:Image 传递过来的数组地址(即指针),数组名就是数组首地址
  * 返 回 值:无
  * 说    明:取模要求:逐列式取模,高位在下,亮点为1,
  */
void ShowImage_H16(unsigned char Page,unsigned char X,unsigned char Width,unsigned char *Image)
{
	unsigned char i;
	if(Page>=0 && Page<=7)	//超出屏幕部分不显示
	{
		OLED_SetCursor(Page,X);
		for(i=0;i<Width;i++)
		{
			if(X+i>=0 && X+i <=127)	//超出屏幕部分不显示
			{
				OLED_WriteData(*(Image+2*i));
			}
		}
	}
	if(Page+1>=0 && Page+1<=7)	//超出屏幕部分不显示
	{
		OLED_SetCursor(Page+1,X);
		for(i=0;i<Width;i++)
		{
				if(X+i>=0 && X+i <=127)	//超出屏幕部分不显示
				{
					OLED_WriteData(*(Image+2*i+1));
				}
		}
	}
}

/**
  * 函    数:更新游戏区域(局部更新)的显示
  * 参    数:X 横坐标,范围:0~11
  * 参    数:Y 纵坐标,范围:0~7
  * 返 回 值:无
  * 说    明:每一个坐标点对应一个8X8像素的区域
  * 说    明:更新一个8X8像素的区域的显示
  */
void UpdateGameArea(unsigned char X,unsigned char Y)
{
	unsigned char i;
	OLED_SetCursor(Y,8*X);
	for(i=0;i<8;i++)
	{
		if(X==0 && i==0)
		{
				OLED_WriteData(0xFF);
		}
		else if(X==11 && i==7)
		{
				OLED_WriteData(0xFF);
		}
		else if(Y==0)
		{
			OLED_WriteData(SnakeType[ TypeBuffer[X][Y]%16 ][i] | 0x01);
		}
		else if(Y==7)
		{
			OLED_WriteData(SnakeType[ TypeBuffer[X][Y]%16 ][i] | 0x80);
		}
		else
		{
			OLED_WriteData(SnakeType[ TypeBuffer[X][Y]%16 ][i]);
		}
	}
}

/**
  * 函    数:根据现在蛇头方向和上一次的蛇头方向来确定蛇头的后一格的连接方式
  * 参    数:Now 现在的蛇头方向,范围:0~3
  * 参    数:Last 上一次的蛇头方向,范围:0~3
  * 返 回 值:蛇身连接情况对应的数值,范围:5~10
  */
unsigned char GetConnection(unsigned char Now,unsigned char Last)
{
	unsigned char Temp;
	if( (Now==0 && Last==3) || (Now==1 && Last==2) )
	{
		Temp=5;
	}
	else if( (Now==0 && Last==0) || (Now==2 && Last==2) )
	{
		Temp=6;
	}
	else if( (Now==0 && Last==1) || (Now==3 && Last==2) )
	{
		Temp=7;
	}
	else if( (Now==1 && Last==0) || (Now==2 && Last==3) )
	{
		Temp=8;
	}
	else if( (Now==1 && Last==1) || (Now==3 && Last==3) )
	{
		Temp=9;
	}
	else if( (Now==2 && Last==1) || (Now==3 && Last==0) )
	{
		Temp=10;
	}
	return Temp;
}

/**
  * 函    数:显示一个矩形
  * 参    数:无
  * 返 回 值:无
  */
void ShowRectangle(void)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		OLED_SetCursor(i,0);
		OLED_WriteData(0xFF);
		OLED_SetCursor(i,95);
		OLED_WriteData(0xFF);
	}
	OLED_SetCursor(0,1);
	for(i=1;i<95;i++)
	{
		OLED_WriteData(0x01);
	}
	OLED_SetCursor(7,1);
	for(i=1;i<95;i++)
	{
		OLED_WriteData(0x80);
	}
}

/**
  * 函    数:在随机位置创造一个食物
  * 参    数:无
  * 返 回 值:食物的位置信息
  * 说    明:高四位存储横坐标,范围:0~11(对应1~12列),低四位存储纵坐标,范围:0~7(对应1~8行)
  */
unsigned char CreateFood(void)
{
	unsigned char Temp;
	unsigned char i,j,m,n;
	m=rand()%12;	//产生一个0~11的随机数
	n=rand()%8;	//产生一个0~7的随机数
	
	//产生一个随机位置,判断该位置是否是蛇身,如果不是蛇身,就返回该位置所对应的数据
	//如果是蛇身的位置,则从该点向周围寻找不是蛇身的空位置
	for(i=0;i<12;i++)
	{
		for(j=0;j<8;j++)
		{
			if( TypeBuffer[(m+i)%12][(n+j)%8] == 0 )
			{
				Temp=(m+i)%12*16+(n+j)%8;
				return Temp;	//找到了空位置就返回
			}
		}
	}
	return Temp;	//正常情况下不会在此处返回
}

/**
  * 函    数:主函数(有且仅有一个)
  * 参    数:无
  * 返 回 值:无
  * 说    明:主函数是程序执行的起点,负责执行整个程序的主要逻辑‌
  */
void main()
{
	unsigned char i,j;	//For循环用到的临时变量
	unsigned char Temp;	//临时变量

	P3M1=0x00;P3M0=0x02;	//P31设置为推挽输出,P30设置为准双向口
	P5M1=0x00;P5M0=0x00;	//P5设置为准双向口

	P30=0;	//P30作为OLED12864的电源负极
	P31=1;	//P31作为OLED12864的电源正极

	Timer0_Init();  //定时器0初始化
	OLED_Init();	//液晶显示屏初始化
	Key_Clear();	//进主循环前清空所有按键的所有标志位

	while(1)
	{
		/*按键处理*/
		if(Mode != LastMode)	//如果模式发生改变
		{
			Key_Clear();	//每次切换模式都要清空所有按键的所有标志位,防止本模式置1的标志位影响下一模式
			LastMode=Mode;
		}
		if(Mode==0)	//如果是显示游戏名称的界面
		{
			if(Key(K1,SINGLE))	//如果单击K1
			{
				srand(TL0);
				Difficulty--;
				if(Difficulty==0)
				{
					Difficulty=5;
				}
			}
			if(Key(K1,DOUBLE))	//如果双击K1
			{
				srand(TL0);
				Difficulty++;
				if(Difficulty==6)
				{
					Difficulty=1;
				}
			}
			if(Key(K1,LONG))	//如果长按K1
			{
				srand(TL0);
				Mode=1;	//切换到模式1
				OnceFlag=1;
			}
		}
		else if(Mode==1)	//如果是游戏进行中
		{
			if(Key(K1,LONG))	//如果长按K1
			{
				srand(TL0);
				PauseFlag=!PauseFlag;	//切换:暂停/继续
				if(PauseFlag==0)	//由暂停切换为继续时,显示食物
				{
					T0Count_1=0;
					FlashFlag=0;
				}
			}
			if(PauseFlag==0)	//如果不是暂停
			{
				if(LastDirection==0)
				{
					if(Key(K1,SINGLE))	//如果单击K1
					{
						srand(TL0);
						NowDirection=1;
					}
					if(Key(K1,DOUBLE))	//如果双击K1
					{
						srand(TL0);
						NowDirection=3;
					}
				}
				else if(LastDirection==1)
				{
					if(Key(K1,SINGLE))	//如果单击K1
					{
						srand(TL0);
						NowDirection=2;
					}
					if(Key(K1,DOUBLE))	//如果双击K1
					{
						srand(TL0);
						NowDirection=0;
					}
				}
				else if(LastDirection==2)
				{
					if(Key(K1,SINGLE))	//如果单击K1
					{
						srand(TL0);
						NowDirection=3;
					}
					if(Key(K1,DOUBLE))	//如果双击K1
					{
						srand(TL0);
						NowDirection=1;
					}
				}
				else if(LastDirection==3)
				{
					if(Key(K1,SINGLE))	//如果单击K1
					{
						srand(TL0);
						NowDirection=0;
					}
					if(Key(K1,DOUBLE))	//如果双击K1
					{
						srand(TL0);
						NowDirection=2;
					}
				}
			}
		}
		else if(Mode==2)	//如果是游戏结束全屏闪烁的界面
		{
			if(Key(K1,LONG))	//如果长按K1
			{
				srand(TL0);
				Mode=0;	//返回显示游戏名称的界面,即返回难度选择界面
				OnceFlag=1;
			}
		}

		/*游戏处理*/
		if(Mode==0)	//显示游戏名称
		{
			if(OnceFlag)	//切换到其他模式前,此if中的代码只执行1次
			{
				OnceFlag=0;	//只执行一次的标志置0
				OLED_Clear();	//清屏
				OLED_WriteCommand(0xAF);	//开启显示	
				ShowImage_H16(1,24,80,Table1);
				ShowImage_H16(3,27,72,Table2);
				ShowImage_H16(5,16,80,Table3);
			}
			ShowImage_H16(5,96,16,Table4+32*(Difficulty-1));	//显示难度对应的数字
			switch(Difficulty)
			{
				case 1:Duration=1000;break;	//每隔1000ms移动一次
				case 2:Duration= 500;break;	//每隔 500ms移动一次
				case 3:Duration= 330;break;	//每隔 330ms移动一次
				case 4:Duration= 250;break;	//每隔 250ms移动一次
				case 5:Duration= 200;break;	//每隔 200ms移动一次
				default:break;
			}
		}
		else if(Mode==1)	//游戏进行中
		{
			if(OnceFlag)
			{	//游戏初始化
				OnceFlag=0;
				OLED_Clear();	//清屏
				Score=2;	//得分重置
				PauseFlag=0;	//暂停的标志置0
				GameOverFlag=0;	//游戏结束的标志置0
				NowDirection=0;	//蛇头默认向右移动
				LastDirection=0;	//蛇头默认向右移动
				Length=2;	//蛇的初始长度为2
				Head=1;	//初始时蛇头对应数组SnakeBody中的索引为1的数据
				SnakeBody[1]=2*16+1;	//数组SnakeBody中写入蛇身初始的两个数据,开始时蛇头在3列2行
				SnakeBody[0]=1*16+1;	//数组SnakeBody中写入蛇身初始的两个数据,开始时蛇尾在2列2行
				for(i=0;i<12;i++)	//清空存储蛇的类型的数组
				{
					for(j=0;j<8;j++)
					{
						TypeBuffer[i][j]=0;
					}
				}
				TypeBuffer[2][1]=1;	//TypeBuffer中记录蛇头信息
				UpdateGameArea(2,1);	//屏幕显示蛇头
				TypeBuffer[1][1]=11;	//TypeBuffer中记录蛇头信息
				UpdateGameArea(1,1);	//屏幕显示蛇尾
				Food=CreateFood();	//进入游戏前,先创造出一个食物
				TypeBuffer[Food/16][Food%16]=15;	//TypeBuffer中记录蛇头信息
				UpdateGameArea(Food/16,Food%16);	//屏幕显示食物
				ShowRectangle();	//显示矩形
				OLED_ShowString(6,0,98,"SCORE");	//显示“SCORE”
				OLED_ShowNum(6,1,107,Score,2);	//显示两位数分数,分数范围:2~Total
				Steps=0;	//步数清零
				OLED_ShowString(6,3,98,"STEPS");	//显示“STEPS”
				OLED_ShowNum(6,4,98,Steps,5);	//显示五位数步数
				GameTime=0;	//游戏用时清零
				GameTime1=0;	//游戏用时清零
				OLED_ShowString(6,6,101,"TIME");	//显示“TIME”
				OLED_ShowNum(6,7,98,GameTime/60%60,2);	//分
				OLED_ShowString(6,7,110,":");	//显示分和秒之间的冒号
				OLED_ShowNum(6,7,116,GameTime%60,2);	//秒
				T0Count_0=0;	//定时器0全局计数变量清零
				MoveFlag=0;	//蛇移动的标志置0
				GameTime=0;	//游戏用时再次清零
				T0Count_2=0;	//定时器0全局计数变量清零
			}
			if(GameOverFlag==0)	//如果游戏未结束
			{
				if(PauseFlag==0)	//如果不是暂停
				{
					if(MoveFlag)	//如果蛇移动的标志为真
					{
						MoveFlag=0;	//蛇移动的标志置0

						if(NowDirection==0)	//如果向右移动
						{
							if(SnakeBody[Head]/16==11)	//移动前判断一下移动后是否撞墙,如果是,则游戏结束,游戏结束的标志置1
							{
								GameOverFlag=1;
							}
							else	//如果没撞墙,计算出准新蛇头的位置,方便后面判断有没有撞到蛇身或吃到食物
							{
								SnakeBody[(Head+1)%Total]=SnakeBody[Head]+16;	//准新蛇头的数据在现在蛇头数据的基础上,高四位加1
							}
						}
						else if(NowDirection==1)	//如果向上移动
						{
							if(SnakeBody[Head]%16==0)
							{
								GameOverFlag=1;
							}
							else
							{
								SnakeBody[(Head+1)%Total]=SnakeBody[Head]-1;
							}
						}
						else if(NowDirection==2)	//如果向左移动
						{
							if(SnakeBody[Head]/16==0)
							{
								GameOverFlag=1;
							}
							else
							{
								SnakeBody[(Head+1)%Total]=SnakeBody[Head]-16;
							}
						}
						else if(NowDirection==3)	//如果向下移动
						{
							if(SnakeBody[Head]%16==7)
							{
								GameOverFlag=1;
							}
							else
							{
								SnakeBody[(Head+1)%Total]=SnakeBody[Head]+1;
							}
						}

						Head++;	//蛇头对应数组SnakeBody的索引加1
						Head%=Total;	//防止越界,即索引为63后再加1,则变成0,循环利用数组数据

						if(GameOverFlag==0)	//如果没撞墙
						{
							if(SnakeBody[Head]==Food)	//如果蛇头移动后的位置是食物
							{
								Length++;	//蛇的长度加1
								Score++;	//分数加1
								OLED_ShowNum(6,1,107,Score,2);	//更新分数显示

								//食物不再闪烁,变成了蛇头
								TypeBuffer[Food/16][Food%16]=NowDirection+1;
								UpdateGameArea(Food/16,Food%16);
								
								
								Temp=SnakeBody[(Head+Total-1)%Total];
								TypeBuffer[Temp/16][Temp%16] = 16*(NowDirection+1);	//记录蛇头在此处的前进方向,用来控制蛇尾来到此处的摆向
								TypeBuffer[Temp/16][Temp%16] += GetConnection(NowDirection,LastDirection);	//更新原蛇头的显示
								UpdateGameArea(Temp/16,Temp%16);	//更新原蛇头的显示

								if(Length<Total)	//如果蛇的长度没有达到最大值
								{
									Food=CreateFood();	//重新创造一个食物
								}
								else	//如果蛇的长度达到了最大值
								{
									GameOverFlag=1;	//游戏结束
								}
								T0Count_1=0;	//定时器全局计数变量清零
								FlashFlag=0;	//创造出新的食物时,食物是显示的
							}
							else if( TypeBuffer[SnakeBody[Head]/16][SnakeBody[Head]%16] && 
								SnakeBody[Head]!=SnakeBody[(Head+Total-Length)%Total] )	//如果蛇头移动后的位置是蛇身,且不是蛇尾(蛇不会撞到自己尾巴)
							{
								GameOverFlag=1;	//游戏结束
							}
							else	//如果蛇移动后没撞墙,也没吃到食物,也不是撞到蛇身
							{
								//清除移动前的蛇尾的显示
								Temp=SnakeBody[(Head+Total-Length)%Total];
								TypeBuffer[Temp/16][Temp%16]=0;
								UpdateGameArea(Temp/16,Temp%16);

								//记录蛇头在此处的前进方向,用来控制蛇尾来到此处的摆向,并更新原蛇头的位置的显示
								Temp=SnakeBody[(Head+Total-1)%Total];
								TypeBuffer[Temp/16][Temp%16] = 16*(NowDirection+1);
								TypeBuffer[Temp/16][Temp%16] += GetConnection(NowDirection,LastDirection);
								if(Length>2)
								{
									UpdateGameArea(Temp/16,Temp%16);
								}

								//显示新蛇尾
								Temp=SnakeBody[(Head+Total-Length+1)%Total];
								TypeBuffer[Temp/16][Temp%16] >>= 4;
								TypeBuffer[Temp/16][Temp%16] += 10;
								UpdateGameArea(Temp/16,Temp%16);
								
								//显示新蛇头
								Temp=SnakeBody[Head];
								TypeBuffer[Temp/16][Temp%16]=NowDirection+1;
								UpdateGameArea(Temp/16,Temp%16);
								
							}
						}
						LastDirection=NowDirection;	//更新方向变量
						if(GameOverFlag==0)	//如果游戏未结束
						{
							Steps++;	//步数加1
							OLED_ShowNum(6,4,98,Steps,5);	//更新步数显示	
						}
					}
					if(GameOverFlag==0)	//如果游戏未结束
					{
						if(FlashFlag)	//游戏时食物闪烁
						{
							TypeBuffer[Food/16][Food%16]=0;	//屏幕不显示食物
							UpdateGameArea(Food/16,Food%16);	//屏幕不显示食物
						}
						else
						{
							TypeBuffer[Food/16][Food%16]=15;	//屏幕显示食物
							UpdateGameArea(Food/16,Food%16);	//屏幕显示食物
						}
					}
				}
				else	//如果是暂停状态
				{
					TypeBuffer[Food/16][Food%16]=15;	//屏幕显示食物,不闪烁
					UpdateGameArea(Food/16,Food%16);	//屏幕显示食物,不闪烁
				}
				if(GameTime1!=GameTime)	//如果游戏时间有变化
				{	//则更新时间显示
					OLED_ShowNum(6,7,98,GameTime/60%60,2);	//分
					OLED_ShowNum(6,7,116,GameTime%60,2);	//秒
					GameTime1=GameTime;	//更新变量
				}
			}
			else	//如果游戏结束
			{
				if(Length<Total)	//如果蛇没达到最大长度
				{
					TypeBuffer[Food/16][Food%16]=15;	//屏幕显示食物,游戏结束后蛇和食物一起闪烁
					UpdateGameArea(Food/16,Food%16);	//屏幕显示食物,游戏结束后蛇和食物一起闪烁
				}
				Mode=2;	//切换到模式4
			}
		}
		else if(Mode==2)	//游戏结束全屏闪烁
		{
			if(FlashFlag)
			{
				OLED_WriteCommand(0xAE);	//关闭显示
			}
			else
			{
				OLED_WriteCommand(0xAF);	//开启显示	
			}
		}
	}
}

/**
  * 函    数:定时器0中断函数
  * 参    数:无
  * 返 回 值:无
  */
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count0;	//定义计时器静态计数变量
	TL0=0x48;	//设置定时初值,定时1ms,1T@35.0000MHz
	TH0=0x77;	//设置定时初值,定时1ms,1T@35.0000MHz
	T0Count0++;
	if(PauseFlag==0)	//不暂停时才计数
	{
		T0Count_0++;
		T0Count_2++;
	}
	T0Count_1++;
	if(T0Count0>=20)	//每隔20ms检测一次按键
	{
		T0Count0=0;
		Key_Tick();
	}
	if(T0Count_0>=Duration)	//每隔 Duration ms 蛇移动一次
	{
		T0Count_0=0;
		MoveFlag=1;
	}
	if(T0Count_1>=330)	//每隔330ms置反闪烁的标志FlashFlag
	{
		T0Count_1=0;
		FlashFlag=!FlashFlag;
	}
	if(T0Count_2>=1000)	//每隔1000ms游戏时间变量加1
	{
		T0Count_2=0;
		GameTime++;
	}
}

总结

之前做过128像素X64像素屏幕的贪吃蛇,是全屏的,现在修改了一下,改成用3/4屏幕作为游戏区域,1/4屏幕显示游戏相关信息:游戏时间、游戏得分、移动步数,并且只用1个或者2个独立按键进行控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值