赞
踩
有个屏幕,掌机的可玩性可以大大增强,打地鼠玩腻了,可以玩个贪吃蛇,俄罗斯方块,以及其他需要开动想象力的游戏。并且,以前总是玩别人的游戏,现在可以自己写游戏,岂不美哉。
后来我感觉0.96的OLED屏幕太小了,搞个更大的全彩屏,换成STM32F4系列单片机,跑个ucos,运行NES模拟器,然后我是不是就能拳打任天堂,脚踢PSP了?哈哈哈
打地鼠的游戏,可以显示生命值,得分,关卡或难度。所以要定义几个变量。
//main.c
//默认参数
#define LIFE_NUM 3 // 默认几条命
//全局变量定义
u8 life = LIFE_NUM; //生命
u32 score = 0; //得分记录
u8 level = 1; //当前难度,数字越大难度越高
一般来说,显示英文字符都会有配套的显示函数。我参考的代码也提供了这些函数。直接调用即可。formatScreen
用于清屏,就像老师板书之前要擦黑板一样。showString
用于在指定的坐标显示英文字符,每个参数的含义可以跳转,看函数说明。
int main(void) { LED_Init(); KEY_Init(); delay_init(); initIIC(); initOLED(); formatScreen(0x00); showString(0,0,"yoodao",FONT_16_EN); showString(0,2,"life:",FONT_16_EN); showString(0,4,"level:",FONT_16_EN); showString(0,6,"score:",FONT_16_EN); showNumber(56,2,life,DEC,8,FONT_16_EN); showNumber(56,4,level,DEC,8,FONT_16_EN); showNumber(56,6,score,DEC,8,FONT_16_EN); while(1) { score++; showNumber(56,6,score,DEC,8,FONT_16_EN); delay_ms(1000); } }
汉字的显示可能就稍微复杂些,因为我们选用的屏幕没有中文字库,所以要自行取模。在取模之前,我先试了试人家显示汉字的函数showCNString
与显示图片的函数showImage’’,成功。
在此感谢风媒电子。
//main.c
formatScreen(0x00);
showImage(0,0,128,8,FM_LOGO_ENUM);
delay_ms(1000);
// showString(0,0,"yoodao",FONT_16_EN);
showCNString(0,0,"风媒电子",FONT_16_CN);
然后把显示的LOGO和汉字改一改。
formatScreen(0x00);
// showImage(0,0,128,8,Y_LOGO_ENUM);
// delay_ms(1000);
// formatScreen(0x00);
// showString(0,0,"yoodao",FONT_16_EN);
showCNString(0,0,"小极客打地鼠掌机",FONT_16_CN);
然后用取模软件,生成“小极客打地鼠掌机”的字模
取模软件的使用:
取得字模以后,替换原先的汉字数组。
/************************************16*16 汉字************************************/ const unsigned char CN1616[8][32] = { {0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x20,0x40,0x80,0x00,0x00,0x08,0x04,0x03,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00,0x01,0x0E,0x00},/*"小",0*/ {0x10,0x10,0xD0,0xFF,0x90,0x10,0x02,0x02,0xFE,0x02,0x02,0x62,0x5A,0xC6,0x00,0x00,0x04,0x03,0x00,0xFF,0x00,0x43,0x30,0x8F,0x80,0x43,0x2C,0x10,0x2C,0x43,0x80,0x00},/*"极",1*/ {0x10,0x0C,0x84,0x44,0xB4,0xA4,0x25,0x26,0x24,0xA4,0x64,0x24,0x04,0x14,0x0C,0x00,0x04,0x04,0x04,0xFA,0x4A,0x4A,0x49,0x49,0x49,0x4A,0x4A,0xFA,0x04,0x04,0x04,0x00},/*"客",2*/ {0x10,0x10,0x10,0xFF,0x10,0x90,0x04,0x04,0x04,0x04,0xFC,0x04,0x04,0x04,0x04,0x00,0x04,0x44,0x82,0x7F,0x01,0x00,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00},/*"打",3*/ {0x20,0x20,0x20,0xFF,0x20,0x20,0x80,0xF8,0x80,0x40,0xFF,0x20,0x10,0xF0,0x00,0x00,0x10,0x30,0x10,0x0F,0x08,0x08,0x00,0x3F,0x40,0x40,0x5F,0x42,0x44,0x43,0x78,0x00},/*"地",4*/ {0x00,0x00,0x7E,0x4A,0x4A,0x49,0x40,0x40,0x40,0x4A,0x4A,0x4A,0x7E,0x00,0x00,0x00,0x00,0x00,0xFF,0x80,0x49,0x12,0x00,0xFF,0x80,0x49,0x12,0x00,0x3F,0x40,0xF0,0x00},/*"鼠",5*/ {0x00,0x10,0x0C,0x05,0x76,0x54,0x54,0x57,0xD4,0xD4,0xF6,0x85,0x14,0x0C,0x00,0x00,0x00,0x10,0x15,0x15,0x15,0x55,0x95,0x7F,0x14,0x14,0x14,0x14,0x14,0x10,0x00,0x00},/*"掌",6*/ {0x10,0x10,0xD0,0xFF,0x90,0x10,0x00,0xFE,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x04,0x03,0x00,0xFF,0x00,0x83,0x60,0x1F,0x00,0x00,0x00,0x3F,0x40,0x40,0x78,0x00},/*"机",7*/ }; unsigned char CN1616_Index[] = "小极客打地鼠掌机"; //16*16中文字库索引表
编译,下载程序,可以看出汉字能够正常显示出来。
这里有个注意事项,工程中所有文件设置的编码格式必须统一。因为编译器不认识汉字,只认识汉字的编码,同一个汉字,其UTF-8与GB2312的编码是不一样。不同的编码在编译器看来,就是不同的汉字。所以要保证,工程里所有文件的编码方式都一致。我发的源码工程采用UTF-8的编码。
显示图片与显示汉字的原理是一样的,毕竟,,,,汉字也是被当成图片处理的嘛。话说,外国人眼里的汉字,可能就是图片。
我打算显示的图片是我自己的LOGO,像是刻在石碑上的字母Y。也需要借助取模软件把图片变成一个数组。
然后模仿人家的程序,把数组名字和枚举类型修改下。
//main.c showImage(0,0,128,8,Y_LOGO_ENUM); //OLED.c /** * 功能:在制定区域显示图片 * 参数: * x:x轴坐标 0-127 * y:y轴坐标 0-7 * x_len:显示区域横坐标长度 0-128 * y_len:显示区域纵坐标长度 0-8 * image_index:图片枚举索引 * 说明:该函数一般用于显示全屏LOGO,另外灵活运用可以显示PPT切换特效 * * 返回值:None */ void showImage(u8 xpos, u8 ypos,u8 x_len, u8 y_len,IMAGE_INDEX image_index) { u16 i,j; for(i=0;i<y_len;++i) //页地址控制 { setPos(xpos,ypos++); for(j=i*128+xpos;j<i*128+x_len;++j) //列地址控制 { switch(image_index) { case Y_LOGO_ENUM :writeData(Y_LOGO[j]); break; default : break; } } } }
显示大LOGO一次成功。
接着我设计了几个位图,都是32像素见方的,分别是上下左右,ABCD,圆和圆圈。万一以后要扩展炫舞或者太鼓达人的游戏呢,
代码也相应修改了下。数组里内容太长,就不列出来了。数组和位图都上传了。
//main.c
//显示几个小LOGO
formatScreen(0x00);
showImage(0,0,32,4,UP_LOGO_ENUM);
showImage(32,0,32,4,DOWN_LOGO_ENUM);
showImage(64,0,32,4,LEFT_LOGO_ENUM);
showImage(96,0,32,4,RIGHT_LOGO_ENUM);
showImage(0,4,32,4,A_LOGO_ENUM);
showImage(32,4,32,4,B_LOGO_ENUM);
showImage(64,4,32,4,C_LOGO_ENUM);
showImage(96,4,32,4,D_LOGO_ENUM);
delay_ms(1000);
显示图片的函数也做了修改
void showImage(u8 xpos, u8 ypos,u8 x_len, u8 y_len,IMAGE_INDEX image_index) { u16 i,j; for(i=0;i<y_len;++i) //页地址控制 { setPos(xpos,ypos++); for(j=i*128+xpos;j<i*128+x_len;++j) //列地址控制 { switch(image_index) { case Y_LOGO_ENUM :writeData(Y_LOGO[j]); break; case UP_LOGO_ENUM :writeData(UP_LOGO[j]); break; case DOWN_LOGO_ENUM :writeData(DOWN_LOGO[j]); break; case LEFT_LOGO_ENUM :writeData(LEFT_LOGO[j]); break; case RIGHT_LOGO_ENUM :writeData(RIGHT_LOGO[j]); break; case A_LOGO_ENUM :writeData(A_LOGO[j]); break; case B_LOGO_ENUM :writeData(B_LOGO[j]); break; case C_LOGO_ENUM :writeData(C_LOGO[j]); break; case D_LOGO_ENUM :writeData(D_LOGO[j]); break; case CIRCLE_LOGO_ENUM :writeData(CIRCLE_LOGO[j]); break; case EMPTY_LOGO_ENUM :writeData(EMPTY_LOGO[j]); break; default : break; }//switch }//for j }//for i }
结果却出现了乱码,这是怎么回事?
我首先分析了showImage
函数。函数的说明中,这句话引起了我的注意:
说明:该函数一般用于显示全屏LOGO,另外灵活运用可以显示PPT切换特效
全屏LOGO?全屏是128×64像素 ,我的Y-LOGO显示没有问题。而箭头是32×32像素的图片,显示出来却有问题,这说明,此函数可以显示128×64的图片,不能显示32×32的图片。
然后我开始分析显示函数的坐标体系。屏幕是黑白的,共有128×64个像素,每个像素有黑白两种状态,正好对应0和1两种状态,所以一个图片需要128×64个bit(二进制位),也就是需要128×8个byte(字节)来表示。x坐标取值范围是0-127,y坐标的取值范围是0-7,所以能猜出来,竖直方向上的8个二进制位,组成了一个字节。这对应了在使用取模软件时的一个细节:取模方式为列行式。先取最左边一列上的8个点,再往右取一列上的8个点。
还有一个需要说明的点:取字方向是低位在前。为了说明位图与数组的关系,我们来分析一下上箭头的图与数组的关系。
取模结果的前16位是
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFC,0xFC,
为了看的更清楚,我把位图与取模得到的数组结合在一起了。已经具体到这种地步了,就不用再说明图片是怎么变成数组了吧?
接下来把数组还原为图片。
首先要明确一个问题:数组是一维的,图片是二维的。是否可以使用二维数组?可以,但没有必要。用一维的数组储存二维的图片,假设图片的尺寸是x×y,那么图片中个点的坐标系是这样的。
0 | 1 | 。 | 。 | x-2 |
x | x+1 | 。 | 。 | 2x-2 |
。 | 。 | 。 | 。 | 。 |
(y-1)x | (y-1)x+1 | 。 | 。 | 。 |
最后一个点也可以写为(y-1)x+(x-1),与xy-1在数学上等价。
储存到数组中是这样的:
0 | 1 | 。。。 | x-1 | x | x+1 | 。。。 | (y-1)x | (y-1)x+1 | 。。。 |
就像大家去操作做广播体操,做操时,排成阵列。然而操场的门很小,只能让一个人通过,因此离开操场时,要排成一队。
明白这一点以后,来分析代码
for(i=0;i<y_len;++i) //1 //页地址控制
{
setPos(xpos,ypos++); //2
for(j=i*128+xpos;j<i*128+x_len;++j) //3 //列地址控制
{
switch(image_index)
{
case Y_LOGO_ENUM :writeData(Y_LOGO[j]); break;//4
}//switch
}//for j
代码1,i<y_len
说明一次显示一横行。但由于竖直方向上的8个二进制位,组成了一个字节,为了方便描述,我们假设数值方向上的8个二进制位是一个排,所以i<y_len
实际上显示了一横“排”。
代码2,设置原点,直接使用了传入的坐标系,没毛病
代码4,把参数j作为数组索引来显示,如果想显示第一个“像素排”,那么j = 0,如果想显示第二排的第一个“像素排”,那么j = 0+x_len。
那么代码3,为什么i要×128?
问题就出现在这里了。显示大LOGO的时候,这个函数是可以用的,因为大LOGO的宽度正好是128。显示32×32的小图片的时候,函数就用不了了,因为,图片的宽度是32啊!
稍作修改,用传入的图片宽度参数替换掉128。大功告成。
//for(j=i*128+xpos;j<i*128+x_len;++j) //3 //列地址控制
for(j=i*x_len;j<i*x_len+x_len;++j) //列地址控制
我发现屏幕刷新的速度太慢了,玩游戏的朋友都知道,FPS太低,画面看起来就卡顿。这怎么能忍,FPS太低会影响我超神的啊!
只显示分数当然没问题,但如果需要玩太鼓达人,或者炫舞之类,依赖屏幕刷新图片的游戏,肯定会卡顿。我分析了一下,可能是IIC总线太慢。代码里IIC操作的延迟都是1us,OLED屏幕最小支持350ns。但是STM32做纳秒级的延时,理论计算并不可靠,需要实测,且提升有限,未做尝试。
也考虑过使用SPI,理论上快不少。用一个大数组把屏幕上所有点的信息都记下来,数组也就1024个元素就能存储所有的的信息,然后使用定时器+DMA+SPI,每隔一小段时间就刷新一下屏幕,只修改有变化的像素点,理论上速度快很多。暂时先做打地鼠的游戏,有空再折腾吧。
最终代码在这里。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。