STM32掌机教程4,STM32驱动OLED屏幕

版权声明:希望能帮助你,也希望你帮助别人,哪怕一点点 https://blog.csdn.net/geek_monkey/article/details/86538774

屏幕显示英文字符

  有个屏幕,掌机的可玩性可以大大增强,打地鼠玩腻了,可以玩个贪吃蛇,俄罗斯方块,以及其他需要开动想象力的游戏。并且,以前总是玩别人的游戏,现在可以自己写游戏,岂不美哉。
  后来我感觉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,每隔一小段时间就刷新一下屏幕,只修改有变化的像素点,理论上速度快很多。暂时先做打地鼠的游戏,有空再折腾吧。
最终代码在这里。

猜你喜欢

转载自blog.csdn.net/geek_monkey/article/details/86538774