STM32驱动TFTLCD播放动画

1.硬件型号及软件资料

最近尝试在做一个stm32驱动tft播放动画的例程,在网上看了一篇博主的文章,对其中部分类容不是很理解,于是自己重新做了下。希望能帮到有需要的人。
原文链接
1.硬件型号:STM32系列(能使用正点原子提供的FATFS实验即可)本例中使用的开发板为启明欣欣的STM32F407ZGT6开发板。
2.软件资料:Image2Lcd、KMP64。后者提取动画的每一帧,前者将提取到的图片文件转换为.bin二进制文件。
本例程播放的动画为BAD APPLE,黑白二色的动画。
软件资料链接:
https://pan.baidu.com/s/1Q2CKXtSdyXa2guuGvDNGAQ
提取码:vycg

Imge2Lcd

KMP64

2.软件使用-导出.bin文件

、使用KMPLAYER打开BAD APPLE视频提取视频的每一帧。
1:在软件中打开该视频文件,不用播放。
2:按Alt+V打开高级捕获,按图中步骤开始捕获每一帧图片。
捕获动画帧
导出后的文件夹中将有很多张图片
导出图片文件夹内容
、使用ImgeLcd将图片转为bin文件。
1.打开第一张图片,应该全是黑色的,按图进行配置。(我这里随便选了一张)。
在这里插入图片描述
2.点击菜单栏的批量转换,并点击确定。
在这里插入图片描述
这里会自动开始将文件夹下的所有图片按顺序转换到batch目录下。(在转换的过程中软件界面可能会卡,直接不用管,多等一会就好了,可以通过看batch目录中的个数确定是否转换完成。正确个数是:6966)
3.将batch文件夹下的所有.bin文件打包为一个。
步骤如图:
win+r输出cmd进入控制台
弄完后把batch文件夹最后一个.bin文件(也就是合成的那个.bin文件,会比其他文件大很多)复制到sd卡根目录中。

3.原理分析及代码实现

一、.bin文件图片数据分析。
1、.bin文件数据说明。
可以打开ImageLcd软件帮助界面查看说明:在单色模式下,输出是按照白色为0,黑色为1,每个像素点对应1位数据,8位数据合成一个字节,当图片的宽度不是8的倍数时会在最后一个字节中没有数据的位置补0,如该例图片的宽度为300,300\8=37(字节)余4(位),则第38个字节的前4位有数据,后4位没有数据,会往其中添加4个0凑成一个字节。所以我们需要根据这个原理对我们图片的显示设计相关的算法,避免后4位的数据添加到了头部。
一行像素最后一个字节的数据
2、编写.c文件检查数据。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>


int main() {

    // 定义文件指针
    FILE* file;

    // 以二进制读取方式打开文件
    file = fopen("E:\\Bad Apple\\batch\\Bad Apple but it's in 4k 60fps.mp4_20230726091226_0001.bin", "rb");

    if (file == NULL) {
        // 打开文件失败
        printf("无法打开文件.\n");
        return 1;
    }


    // 读取文件内容并逐字节输出到控制台
    int byte;
    int length = 0;
    int flag = 8;
    while ((byte = fgetc(file)) != EOF) {
        length++;
        flag = 8;
        //printf("%02X ", byte); // 以16进制格式输出,可根据需求修改输出格式
        //读取每行的最后一个字节以2进制输出,验证后4四位是否补0
        if (length % 38 == 0) {
            while (flag) {
                printf("%c", (byte & 0x80) ? '1' : '0');
                byte = byte << 1;
                flag--;
            }
            printf("\n");
        }
    }
    printf("\n");
    printf("总字节=%d", length);
    printf("\n");
	return 0;
}

打开第一张图片,因为第一张全黑,所以有效数据必然全是1,观察每一行最后一个字节的最后4位可以看到填充了0,数据得到了验证。最后输出了一下300*230像素下所占的总字节数为:8740(这个数据后面写代码会用到)
数据验证图

3、以正点原子FATAS实验为基础修改代码(只需要改亿点点)
因为我使用的是启明欣欣的开发板,和正点原子驱动屏幕的代码不兼容,所以我改的有点多,最后整理的时候自己弄成增加内容了:
(1)lcd.h

//添加如下宏定义
//=======================================================================================
#define INIT_X 10	//起始x坐标
#define INIT_Y 5	//起始y坐标
#define	PICTURE_WIDTH 300
#define PICTURE_HEIGHT	230
#define TOTAL_BYTE	8740	//一张图片存储的总字节数,一个字节中有8个像素
#define BUFF_TOTAL_BYTE 512
//一张图片存储的总位数,即总像素,以300*230的图片为例,像素总个数为:300*230=69000
//但是为什么软件输出的图像8740*8=69920
//原因:图像的宽度300,即300个像素点,8个像素点对应一个字节,300\8=37余4,余下的4个像素被装入了一个字节的前四位中,后四位补0
//因此:一行相当于38个字节一共230行,总字节数:38*230=8740,多出来了:230*4=920补0像素
#define TOTAL_BIT		TOTAL_BYTE*8
//=======================================================================================
//声名函数
void LCD_Draw_Picture_Pro(u16 *xstr,u16 *ystr,u16 sendbyte,u8 * pic);

(2)lcd.c(自己写的算法绘图,有兴趣可以自己研究下)

/****************************************************************************
* 名    称: void LCD_Draw_Picture_Pro(u16 *xstr,u16 *ystr,u16 sendbyte,u8 * pic)
* 功    能:根据传入的字节数据画图片
* 入口参数:xstr,ystr:起始坐标,该坐标会一直变化,因为一次传入的数据有限
            sendbyte:传入的字节大小
						pic:数据的指针,指向buff缓冲数组的第一个元素的地址
* 返回参数:无
* 说    明:该字模取模方向为先从左到右,再从上到下  低位在前  
****************************************************************************/
void LCD_Draw_Picture_Pro(u16 *xstr,u16 *ystr,u16 sendbyte,u8 * pic)
{
    
    u8 temp,t1;
    u16 width_byte;
    u16 height_total,y_t;
    u16 x_t = *xstr;
		u8 *pusMsk=pic;
    u16 width_total = INIT_X + PICTURE_WIDTH;
    u16 flag = 0;
		
    width_byte = PICTURE_WIDTH % 8 ? (PICTURE_WIDTH / 8 + 1) : (PICTURE_WIDTH / 8);//宽度是否是8的整数倍,不是对一行的最后一个字节做特殊处理
    height_total = sendbyte % width_byte ? (sendbyte / width_byte + 1) : (sendbyte / width_byte);//计算一共有多少行
    
    
    for (y_t = *ystr; y_t < height_total+*ystr+1;) {
				temp = *pusMsk;
        flag++;
				if (flag>sendbyte) {
            break;
        }
				
        if (x_t >= width_total - (PICTURE_WIDTH % 8)) {
            if (PICTURE_WIDTH % 8) {
                for (t1 = 0; t1 < 4; t1++) {
                    //判断画点
									if (temp & 0x80) {
                        LCD_Color_DrawPoint(x_t, y_t, BLACK);
                    }else{
                        LCD_Color_DrawPoint(x_t, y_t, WHITE);
                    }
                    temp <<= 1;
                    x_t++;
                }
                x_t = INIT_X;
                if(++y_t>=INIT_Y+PICTURE_HEIGHT){*ystr=INIT_Y;y_t=*ystr;}
								
            
            }
            else
            {
                x_t = INIT_X;
                if(++y_t>=INIT_Y+PICTURE_HEIGHT){*ystr=INIT_Y;y_t=*ystr;}
								
                for (t1 = 0; t1 < 8; t1++) {
                   
                    //判断画点
									if (temp & 0x80) {
                        LCD_Color_DrawPoint(x_t, y_t, BLACK);
                    }else{
                        LCD_Color_DrawPoint(x_t, y_t, WHITE);
                    }
                    temp <<= 1;
                    x_t++;

                }
            }
        }
        else {
            for (t1 = 0; t1 < 8; t1++) {
                
                //判断画点
								if (temp & 0x80) {
                        LCD_Color_DrawPoint(x_t, y_t, BLACK);
                    }else{
                        LCD_Color_DrawPoint(x_t, y_t, WHITE);
                    }
                    temp <<= 1;
                x_t++;

            }
        
        }
				pusMsk++;
        
    }
    *xstr = x_t;
    *ystr = y_t;
 
}

(3)main.c

int main(void)
{   
	FIL text;
	DWORD fileSize;  // 文件大小,方便验证
	u8 buff[BUFF_TOTAL_BYTE];
	u8 res=0, x=0;	
	u16 xstr=INIT_X,ystr=INIT_Y;
	UINT brr;
	u32 move=0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);  //初始化延时函数
	uart_init(115200);			//初始化串口波特率为115200
	LED_Init();					//初始化LED 
 	LCD_Init();					//LCD初始化  
 	KEY_Init();					//按键初始化 
	W25QXX_Init();				//初始化W25Q128
	my_mem_init(SRAMIN);		//初始化内部内存池 
	my_mem_init(SRAMCCM);		//初始化CCM内存池
	Set_Display_Mode(1);		//屏幕设置为横屏
	LCD_Clear(GRAY);			//屏幕颜色设置为灰色
 	BRUSH_COLOR=BLUE;  			//设置字体为蓝色	 
	
 	while(SD_Init())//检测不到SD卡
	{
	
		LCD_ShowString(10,160,16,"SD Card Error!");
		delay_ms(500);					
		LCD_ShowString(10,180,16,"Please Check! ");
		delay_ms(500);
		LED0=!LED0;//DS0闪烁
	}
 	exfuns_init();							//为fatfs相关变量申请内存				 
  	f_mount(fs[0],"0:",1); 					//挂载SD卡 
 	res=f_mount(fs[1],"1:",1); 				//挂载FLASH.	
	/*if(res==0X0D)//FLASH磁盘,FAT文件系统错误,重新格式化FLASH
	{
		
		LCD_ShowString(10,180,16,"Flash Disk Formatting...! ");
		res=f_mkfs("1:",1,4096);//格式化FLASH,1,盘符;1,不需要引导区,8个扇区为1个簇
		if(res==0)
		{
			f_setlabel((const TCHAR *)"1:ALIENTEK");	//设置Flash磁盘的名字为:ALIENTEK
			//LCD_ShowString(30,150,200,16,16,"Flash Disk Format Finish");	//格式化完成
			LCD_ShowString(10,200,16,"Flash Disk Format Finish");
		}else LCD_ShowString(10,200,16,"Flash Disk Format Error");//格式化失败
		delay_ms(1000);
	}*/											    
	//LCD_Fill(30,150,240,150+16,WHITE);		//清除显示			  
	/*while(exf_getfree("0",&total,&free))	//得到SD卡的总容量和剩余容量
	{
		
		LCD_ShowString(10,220,16,"SD Card Fatfs Error!");
		delay_ms(200);
		//LCD_Fill(30,150,240,150+16,WHITE);	//清除显示			  
		delay_ms(200);
		LED0=!LED0;//DS0闪烁
	}*/												  			    
 	//BRUSH_COLOR=BLUE;//设置字体为蓝色	   
	//LCD_ShowString(10,240,16,"FATFS OK!");
	//LCD_ShowString(10,260,16,"SD Total Size:     MB");
	//LCD_ShowString(10,280,16,"SD Free  Size:     MB");
 	//LCD_DisplayNum(10+8*14,260,total>>10,5,16,0);				//显示SD卡总容量 MB
 	//LCD_DisplayNum(10+8*14,280,free>>10,5,16,0);					//显示SD卡剩余容量 MB	
	while (1)
    {
        res = f_open(&text, "0:/badapple.bin", FA_READ);
        if (res == FR_OK)
        {
            printf("ok\n");
            // 文件打开成功,进行读取操作.
            fileSize = f_size(&text);
            printf("File size: %lu bytes\n", (unsigned long)fileSize);
            while (1)
            {
                for (x = 0; x < (TOTAL_BYTE % BUFF_TOTAL_BYTE ? (TOTAL_BYTE / BUFF_TOTAL_BYTE) + 1 : (TOTAL_BYTE / BUFF_TOTAL_BYTE)); x++)
                {
                    f_lseek(&text, move);
                    res = f_read(&text, buff, BUFF_TOTAL_BYTE, &brr);
                    LCD_Draw_Picture_Pro(&xstr, &ystr, brr, buff);
                    move += brr;
                }
                //printf("xstr=%d,ystr=%d\n",xstr,ystr);
                //printf("move=%d\n",move);
                if (brr < BUFF_TOTAL_BYTE) break;
            }

            f_close(&text);  // 关闭文件
            move = 0;//复位

        }
        else {
            // 文件打开失败,进行错误处理
            printf("no ok=%d\n", res);
            // ...
        }
    }
}

如果程序运行后没有效果可以通过串口输出看看是什么原因,选中FR_OK右键"GO TO DEFINITION```"这里把各种原因列出来方便大家看:

typedef enum {
	FR_OK = 0,				/* (0) Succeeded */
	FR_DISK_ERR,			/* (1) A hard error occurred in the low level disk I/O layer */
	FR_INT_ERR,				/* (2) Assertion failed */
	FR_NOT_READY,			/* (3) The physical drive cannot work */
	FR_NO_FILE,				/* (4) Could not find the file */
	FR_NO_PATH,				/* (5) Could not find the path */
	FR_INVALID_NAME,		/* (6) The path name format is invalid */
	FR_DENIED,				/* (7) Access denied due to prohibited access or directory full */
	FR_EXIST,				/* (8) Access denied due to prohibited access */
	FR_INVALID_OBJECT,		/* (9) The file/directory object is invalid */
	FR_WRITE_PROTECTED,		/* (10) The physical drive is write protected */
	FR_INVALID_DRIVE,		/* (11) The logical drive number is invalid */
	FR_NOT_ENABLED,			/* (12) The volume has no work area */
	FR_NO_FILESYSTEM,		/* (13) There is no valid FAT volume */
	FR_MKFS_ABORTED,		/* (14) The f_mkfs() aborted due to any parameter error */
	FR_TIMEOUT,				/* (15) Could not get a grant to access the volume within defined period */
	FR_LOCKED,				/* (16) The operation is rejected according to the file sharing policy */
	FR_NOT_ENOUGH_CORE,		/* (17) LFN working buffer could not be allocated */
	FR_TOO_MANY_OPEN_FILES,	/* (18) Number of open files > _FS_SHARE */
	FR_INVALID_PARAMETER	/* (19) Given parameter is invalid */
} FRESULT;

4.实现效果

1、300*230动画效果

300*230

可以看到动画看起来有点卡,这是因为处理一张图片的数据过大,这个板子能力就这样,没事我们可以改小点,使用128*64大小的动画,需要更改的地方:
(1)lcd.h的宏定义改为如下

//==================================================================
#define INIT_X 100	//起始x坐标
#define INIT_Y 90	//起始y坐标
#define	PICTURE_WIDTH 128	//图片的宽度
#define PICTURE_HEIGHT	64	//图片的高度
#define TOTAL_BYTE	1024	//一张图片存储的总字节数,一个字节中有8个像素
#define BUFF_TOTAL_BYTE 512
//一张图片存储的总位数,即总像素,以300*230的图片为例,像素总个数为:300*230=69000
//但是为什么软件输出的图像8740*8=69920
//原因:图像的宽度300,即300个像素点,8个像素点对应一个字节,300\8=37余4,余下的4个像素被装入了一个字节的前四位中,后四位补0
//因此:一行相当于38个字节一共230行,总字节数:38*230=8740,多出来了:230*4=920补0像素
#define TOTAL_BIT		TOTAL_BYTE*8
//==================================================================

(2)main.c

//将while(1)循环中的代码改一行
res = f_open(&text, "0:/apple.bin", FA_READ);//apple.bin是新的文件名

2、128*64动画效果

128*64

5.友情提示

代码中和LCD有关的函数可能正点原子的板子没法直接使用,不过都能找到类似的函数,例如:

Set_Display_Mode(1);		//屏幕设置为横屏
BRUSH_COLOR=BLUE;  			//设置字体为蓝色	 
LCD_ShowString(10,160,16,"SD Card Error!");//在指定区域显示字符串

这些函数和正点原子提供的不一样,不过都能找到类似的,把这些换了就行。
.bin文件,程序源码文件链接:
https://pan.baidu.com/s/1yOi5OjCNlIxvN9StlOX_dQ
提取码:ydgq

  • 16
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值