系列文章目录
前言
《就是哪吒》水墨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.如果液晶屏使用没问题的话,强烈建议不要拆开。