STM32驱动TFT播放动画(BADAPPLE)
1.硬件型号及软件资料
最近尝试在做一个stm32驱动tft播放动画的例程,在网上看了一篇博主的文章,对其中部分类容不是很理解,于是自己重新做了下。希望能帮到有需要的人。
原文链接
1.硬件型号:STM32系列(能使用正点原子提供的FATFS实验即可)本例中使用的开发板为启明欣欣的STM32F407ZGT6开发板。
2.软件资料:Image2Lcd、KMP64。后者提取动画的每一帧,前者将提取到的图片文件转换为.bin二进制文件。
本例程播放的动画为BAD APPLE,黑白二色的动画。
软件资料链接:
https://pan.baidu.com/s/1Q2CKXtSdyXa2guuGvDNGAQ
提取码:vycg
2.软件使用-导出.bin文件
一、使用KMPLAYER打开BAD APPLE视频提取视频的每一帧。
1:在软件中打开该视频文件,不用播放。
2:按Alt+V打开高级捕获,按图中步骤开始捕获每一帧图片。
导出后的文件夹中将有很多张图片
二、使用ImgeLcd将图片转为bin文件。
1.打开第一张图片,应该全是黑色的,按图进行配置。(我这里随便选了一张)。
2.点击菜单栏的批量转换,并点击确定。
这里会自动开始将文件夹下的所有图片按顺序转换到batch目录下。(在转换的过程中软件界面可能会卡,直接不用管,多等一会就好了,可以通过看batch目录中的个数确定是否转换完成。正确个数是:6966)
3.将batch文件夹下的所有.bin文件打包为一个。
步骤如图:
弄完后把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