基于51单片机和LCD12864的哪吒2片尾水墨MV《就是哪吒》播放

系列文章目录


前言

《就是哪吒》水墨MV总共有26张图片,每张图片的数据大小是1KB,总共需要27KB左右的Flash空间,用STC12C5A60S2(总共有60KB的Flash空间)很合适,有足够的存储空间。

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

一、效果展示

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

二、原理分析

1、截屏和BMP文件制作

方法来源:江协科技的OLED使用教程

(1)因LCD12864屏幕的宽128像素,高64像素,所以屏幕截图要按2:1的比例进行截屏(或者截屏后再调整画布比例为2:1)。
在这里插入图片描述
请添加图片描述

(2)用PS打开截屏的图片,如果不是2:1的比例,则通过画布大小调整为2:1的比例
在这里插入图片描述
在这里插入图片描述

(3)删掉图片上的歌词和其他多余部分
在这里插入图片描述

(4)通过滤镜查找边缘让轮廓更清晰
在这里插入图片描述

(5)将图像二值化,通过调整阈值来达到理想的效果
在这里插入图片描述
在这里插入图片描述

(6)调整图片大小,调成128X64像素
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(7)调整图片大小后再次调整阈值,使图片二值化

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

(8)另存为BMP图片
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、图片的取模
根据LCD12864液晶屏的硬件特点,用软件PCtoLCD2002取模的时候需要设置为:逐行式取模,高位在左,阴码(亮点为1)。用PCtoLCD2002取模软件打开BMP图片,设置好之后点击“生成字模”就行了。

(1)用PCtoLCD2002取模软件打开BMP图片
在这里插入图片描述

(2)取模软件打不开BMP图片的处理方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(3)取模设置
在这里插入图片描述

(4)不使用滤镜“查找边缘”的效果
不使用滤镜的话,黑白会成块出现,轮廓不明显。本代码没有用滤镜。大家也可以尝试一下其他滤镜的效果。
在这里插入图片描述

3、图片数据的写入

普通模式只能显示汉字或ASCII字符,想要显示图片数据的话,需要切换为绘图模式。整个屏幕(128X64像素)其实是两个128X32像素的屏幕上下叠加而成,参照手册和例程将1KB的数据写入就可以显示一幅图片了。

4、每张图片延时时间
将《就是哪吒》MV下载后,通过PotPlay播放器找出图片切换的时刻,再利用Excel算出每张图片持续的时间,用数组保存。再在主程序中通过查表的方法进行每张图片的延时,会有点偏差,需要微调一下。

三、各模块代码

1、延时函数

h文件

#ifndef	__DELAY_H__
#define	__DELAY_H__
 
void Delay(unsigned int xms);
 
#endif

c文件

/**
  * @brief	延时函数,延时xms毫秒
  * @param	xms 延时的时间,范围:0~65535
  * @retval	无
  */
void Delay(unsigned int xms)	//1T@12.0000MHz
{
	unsigned char i,j;
	while(xms)
	{
		i=12;
		j=680;
		do
		{
			while(--j);
		} while(--i);
		xms--;
	}
}

2、液晶显示屏LCD12864

h文件

#ifndef __LCD12864_H__
#define __LCD12864_H__

void LCD_WriteCommand(unsigned char Command);
void LCD_WriteData(unsigned char Data);
void LCD_SetCursor(unsigned char Line,unsigned char Column);
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowChinese(unsigned char Line,unsigned char Column,unsigned char Code1,unsigned char Code2);
void LCD_ShowImage(unsigned char *Image,unsigned char Offset);
void LCD_Clear(void);
void LCD_GraphicMode(void);
void LCD_NormalMode(void);

#endif

c文件

#include <STC12C5A60S2.H>

/*引脚配置*/
sbit LCD_RS=P2^5;
sbit LCD_RW=P2^6;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

/*函数定义*/
/**
  * @brief  LCD12864私有延时函数,延时25us
  * @param  无
  * @retval 无
  */
void LCD_Delay25us(void)	//1T@12.0000MHz
{
	unsigned char i;
	i=72;
	while(--i);
}

/**
  * @brief  LCD12864私有延时函数,延时2ms
  * @param  无
  * @retval 无
  */
void LCD_Delay2ms(void)	//1T@12.0000MHz
{
	unsigned char i,j;
	i=24;
	j=84;
	do
	{
		while(--j);
	}while(--i);
}

/**
  * @brief  LCD12864写指令
  * @param  Command 要写入的指令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay25us();	//不检查忙碌状态,但要进行适当的延时
	LCD_EN=0;	//下降沿锁存数据
	LCD_Delay25us();
}

/**
  * @brief  LCD12864写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay25us();
	LCD_EN=0;
	LCD_Delay25us();
}

/**
  * @brief  LCD12864使用普通指令时设置光标位置
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~8
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	Line--;
	Column--;
	switch(Line)
	{
		case 0: Column|=0x80;break;
		case 1: Column|=0x90;break;
		case 2: Column|=0x88;break;
		case 3: Column|=0x98;break;
	}
	LCD_WriteCommand(Column);
}

/**
  * @brief  LCD12864初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x30);	//普通模式,基本指令操作
	LCD_WriteCommand(0x0C);	//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);	//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);	//光标复位,清屏
	LCD_Delay2ms();	//LCD操作指令0x01需要的时间较长,要延时久点
}

/**
  * @brief  在LCD12864指定位置上显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~8
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD12864指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~8
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	LCD_SetCursor(Line,Column);
	while(*String!='\0')
	{
		LCD_WriteData(*String);
		String++;		
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD12864指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~8
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD12864指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~8
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD12864指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~8
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD12864指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~8
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

/**
  * @brief  在LCD12864指定位置显示指定编码的汉字
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~8
  * @param  Code1 要显示汉字的第一个字节,范围:0xA1~0xF7
  * @param  Code2 要显示汉字的第一个字节,范围:0xA0~0xFF
  * @retval 无
  */
void LCD_ShowChinese(unsigned char Line,unsigned char Column,unsigned char Code1,unsigned char Code2)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Code1);
	LCD_WriteData(Code2);
}

/**
  * @brief  将自定义的图片数据写入GDRAM,显示一幅图片,向右为X轴正方向,向下为Y轴正方向
  * @brief  图片大小要求:宽128像素,高64像素
  * @brief  取模设置:逐行式取模,高位在左,阴码(亮点为1)
  * @param  Image 要显示的图片的数组首地址
  * @param  Offset 偏移量,显示第Offset幅图片(共N幅),范围:0~N-1
  * @retval 无
  */
void LCD_ShowImage(unsigned char *Image,unsigned char Offset)
{
	unsigned char i,j;
	Image+=1024*Offset;
	for(i=0;i<64;i++)
	{
		LCD_WriteCommand(0x80+i%32);	//先写入Y的地址,上半屏和下半屏的Y地址是一样的,都是从0x80开始
		if(i<32){LCD_WriteCommand(0x80);}	//再写入上半屏X的首地址0x80,写入2次数据后地址计数器会自动加一,连续写入16次数据即可
		else{LCD_WriteCommand(0x88);}	//下半屏X的首地址0x88(16*8=128,对应LCD宽度的像素)
		for(j=0;j<16;j++)
		{
			LCD_WriteData(*Image);
			Image++;	//地址自增
		}
	}
}

/**
  * @brief  清空GDRAM(绘图模式下没有清空GDRAM的指令,需要自己写函数清空)
  * @param  无
  * @retval 无
  */
void LCD_Clear(void)
{
	unsigned char i,j;
	LCD_WriteCommand(0x34);	//绘图模式,显示关
	for(i=0;i<32;i++)
	{
		LCD_WriteCommand(0x80+i);	//先写Y地址
		LCD_WriteCommand(0x80);	//再写X地址,写入2次数据后地址计数器自动加一,i为0时,先写第1行,再写第32行
		for(j=0;j<32;j++)		//整个屏幕相当于是2个128*32的屏幕上下叠起来
		{
			LCD_WriteData(0x00);	//将0x00写入GDRAM
		}
	}
	LCD_WriteCommand(0x36);	//绘图模式,显示开
}

/**
  * @brief  设置到绘图模式
  * @param  无
  * @retval 无
  */
void LCD_GraphicMode(void)
{
	LCD_WriteCommand(0x01);	//光标复位,清屏(如果不清屏,由普通模式转成绘图模式时,普通模式下的数据还会显示)
	LCD_Delay2ms();
	LCD_WriteCommand(0x34);	//绘图模式,显示关,扩充指令
	LCD_Clear();	//清空GDRAM的数据
}

/**
  * @brief  设置到普通模式
  * @param  无
  * @retval 无
  */
void LCD_NormalMode(void)
{
	LCD_WriteCommand(0x30);	//普通模式,基本指令
	LCD_WriteCommand(0x01);	//光标复位,清屏(如果不清屏,由绘图模式转成普通模式时,绘图模式下的数据还会显示)
	LCD_Delay2ms();
}

3、图片数据

h文件

//略(太多了)

四、主函数

main.c

/*说明
by甘腾胜@20250307
【效果展示】可以在B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC12C5A60S2
【晶振】1T@12.0000MHz
【外设】LCD12864
【接线】
LCD12864:RS接P25,RW接P26,EN接P27,数据口接P0
【说明】
每张照片按照2:1的比例截屏,再通过PS处理,二值化处理(图像->调整->阈值),
调整图像大小(图像->图像大小)为128X64像素,再一次二值化处理(图像->调整->阈值),
保存为BMP图片,最后通过PCtoLCD2002取模软件进行取模,如果取模软件不能正常打开BMP图片,
则用电脑自带的画图软件打开后保存一下。
*/

#include <STC12C5A60S2.H>
#include "Delay.h"
#include "LCD12864.h"
#include "Image.h"

/*每一幅图片延时的时间*/
unsigned int code DelayTime[]={
1374,3792,2540,4751,4084,3165,2459,2667,2333,3042,2499,2210,2999,
3002,2333,2916,3125,4374,3791,3667,3168,4541,3459,3165,2750,2502,
};

/**
  * @brief  主函数,main函数是程序执行的起点,负责执行整个程序的主要逻辑
  * @param  无
  * @retval 无
  */
void main()
{
	unsigned char i;
	LCD_Init();
	LCD_GraphicMode();
	while(1)
	{
		for(i=0;i<26;i++)
		{
			LCD_ShowImage(MyImage,i);
			Delay(DelayTime[i]/81*80);
		}
	}
}

总结

屏幕那里有点小瑕疵,可能是被较大的力压过导致的,我就把屏幕拆开来看一下是怎么回事,看能不能修好(强迫症患者)。拆了后发现处理不了,就装回去,装的时候有预感,可能不能正常显示了,因为有太多触点了,估计某些触点会接触不良,装了后果然不能正常显示了,直接报废。又上淘宝买了一块黄绿屏和蓝屏的LCD12864.如果液晶屏使用没问题的话,强烈建议不要拆开。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值