本帖最后由 xld0932 于 2022-5-22 23:03 编辑
#申请原创# @21小跑堂
介绍 MM32F3270系列MCU带有FSMC控制总线和SDIO接口;FSMC是一种灵活的存储器控制器控制接口,需要注意的是MM32F3270系列MCU的FSMC功能是需要芯片引脚封装在100PIN及以上时才具备的,MM32F3270系列MCU的FSMC支持SRAM、NOR FLASH等静态存储接口,还支持Inter的I8080协议和Motorola的M6800协议,通过FSMC这一功能可以和当前市面上很多的MCU接口屏进行连接,实现人机交互界面的功能;SDIO接口则是用于控制外部的SD卡、MMC卡、或者是SDIO接口的外设设备,兼容SD卡的1.0、1.1(高速)以及2.0(SDHC)等规格,兼容MMC卡的2.0~4.2等规格,以及安全兼容SDIO存储卡规格1.1.0和标准的MMC模式接口支持,通过MM32F3270系列MCU的SDIO接口可以实现对TF卡底层的通讯及操作,再结合FatFs软件库,可以实现文件系统的读写等操作。
实现功能 基于MM32-EVBoard(MB-039)开发板,实现数码相框显示的功能。通过SDIO接口以及FatFs软件库实现文件系统管理,可以自动识别TF卡中Image文件夹下的所有文件,自动判断BMP、JPG、BIN等文件格式,将图片通过MCU的解码操作后,显示在TFT LCD液晶显示屏上。其中对于BMP格式的图片支持单色位图、16色位图、256色位图和24位真彩色位图的显示;JPG格式的图片通过TJpgDec软件库进行软件解码后在液晶屏上显示;另外还支持包含图片头信息数据的BIN格式文件,它是通过上位机软件Image2Lcd将图片数据提取后生成的BIN格式文件,在本系统中要求转换包含图像头数据和高位在前这两项设置。根据上述功能要求,需要实现的技术点如下: - TFT LCD的I8080总线接口与MM32F3270系列MCU的FSMC接口进行连接,显示相应的内容,包含ASCII码英文字符和GBK编码汉字字符;
- SPI FLASH用于存放中文GBK编码字库,通过板载的UART1接口结合Xmodem串行文件传输协议,从电脑端把GBK_FONT.BIN文件传输并写入到板载的SPI FLASH芯片中;
- 通过MCU的SDIO接口来操作TF卡,移植FatFs软件库,实现文件系统的读写等操作;
- 自动识别TF卡Image文件夹中的BMP、JPG和BIN格式文件,对BMP文件的单色位图、16色位图、256色位图和24位真彩色位图的自动识别、解码和显示;对JPG文件通过TJpgDec软件库进行软件解码并显示;
软件环境 - Keil MDK开发软件
- MobaXterm终端软件,用于监控程序运行及代码中SHELL功能的操作
- SecureCRT终端软件,通过Xmodem传输协议结合MCU功能代码实现,用于将GBK汉字字库编码数据下载到板载的SPI FLASH芯片中
- Beyond Compare比较软件,用于比较GBK汉字字编码下载和上传数据的一致性
- Image2Lcd软件,用于将图片生成包含图像头数据和高位在前格式的BIN文件
硬件环境 - MM32-EVBoard(MB-039)开发板
- 8GB容量的TF卡及读卡器,通过读卡器连接电脑,将图片文件存放在TF卡中
- Micro USB数据线
- USB转TTL调试工具
BMP图片文件 BMP图片文件格式可以参考附件中的《BMP文件格式》文档,里面有详细的描述,本文不再重复介绍了;下面就BMP文件格式在实际代码实现和应用上的实操,或者说是注意事项来做一些分享,这些对于了解和掌握BMP文件很有帮助哦…… - BMP文件结构是由文件信息、图片信息、调色板数据和图片数据这4部分组成的,但调色板数据是根据图片信息中中的biBitCount值来决定的。当biBitCount = 1时,图片为单色位图,有2组调色板数据;当biBitCount = 4时,图片为16色位图,有64组调色板数据;当biBitCount = 8时,图片为256色位图,有256组调色板数据;而当当biBitCount = 24时,图片则为真彩色位图,此时是没有调色板数据的,图片信息之后跟随的都是图片数据。
- BMP文件结构中的文件信息占固定14字节空间,图片信息占固定的40字节空间,1组调色板数据占固定的4字节空间,而图片数据的大小则是由图片信息中的biImageSize给出了;数据数据的相对于BMP文件来说的数据偏移地址由文件信息中的bfOffBits指定了。如下图分别列举出了单色、16色、256色和24位色位图的文件信息和图片信息部分:
- 图片数据中是以行为单位,并且以4字节对齐的方式取数据的;也就是说图片数据中存在真实有效的显示数据,同时也存在着了用于字节对齐的填充数据;以24位色图片来说,宽高为192*320像素图片,每行的数据为192 * 3字节 = 576字节,而576字节正好是以4字节对齐的,所以这时整个图片占用的字节数为192 * 3字节 * 320 = 184320字节;那如果宽高为211*320像素图片,每行的数据为211 * 3字节 = 633字节,而633字节却不是以4字节对齐的,需要进行字节填充,此时一行数据就需要占用636字节,所以这时整个图片占用的字节数就不是633字节 * 320了,而应该是636字节 * 320 = 203520字节。不管是24位还是其它位色的图片数据都遵循这样的取数据原则。
- 图片数据的内容根据biBitCount的值会有所不同;当biBitCount = 24时,图片数据中每3个字节数据表示一个像素点,这3个数据分别指示了蓝色、绿色和红色的分量值;而当biBitCount = 1/4/8的情况时,图片数据中存储的某一组调色板数据的下标号,通过下标号找到对应的调色板颜色数据,再进行显示;每一个调色板数据占用4字节空间,且格式固定,由蓝色、绿色、红色和预留值组成,各占1个字节,其中预留值固定为0x00。
- 一般来说在生成图片文件时,Windows是从图片的左下角开始进行逐行扫描图片,即BMP文件的数据是按照从下到上,从左到右的顺序进行扫描和存储的;从图片文件中最先读到的是图片最下面一行的左边第一个像素点数据,然后是左边第二个像素点数据……倒数第二行的左边第一个像素点数据,然后是第二个像素点数据……依此类推,最后读取到的是最上面一行的最右边一个像素点数据。虽然BMP图片数据有RLE4/RLE8等压缩格式,但都很少有人使用,几乎所有的BMP图片都是采用没有压缩的格式来存储图片数据的。
BMP文件格式定义 typedef __packed struct
{
uint16_t bfType; /* 文件类型 */
uint32_t bfSize; /* 文件大小,以字节为单位 */
uint16_t bfReserved1; /* 保留 */
uint16_t bfReserved2; /* 保留 */
uint32_t bfOffBits; /* 图片数据的起始位置相对于文件开头的偏移量 */
} BITMAP_FileHeader_TypeDef;
typedef __packed struct
{
uint32_t biSize; /* 图片信息的大小,以字节为单位 */
uint32_t biWidth; /* 图片的水平宽度,以像素为单位 */
uint32_t biHeight; /* 图片的垂直高度,以像素为单位 */
uint16_t biPlanes; /* 目标设备说明位面数,定值为01 */
uint16_t biBitCount; /* 比特位/像素,其值为1/4/8/16/24或者32 */
uint32_t biCompression; /* 图片数据压缩类型,0(不压缩)/1(RLE8)/2(RLE4) */
uint32_t biSizeImage; /* 图片数据的大小,以字节为单位 */
uint32_t biXPelsPerMeter; /* 目标设备的水平分辨率(像素/米) */
uint32_t biYPelsPerMeter; /* 目标设备的垂直分辨率(像素/米) */
uint32_t biClrUsed; /* 位图实际使用的彩色表中的颜色索引数 */
uint32_t biClrImportant; /* 图片中重要的颜色数,0表示都重要 */
} BITMAP_InfoHeader_TypeDef;
typedef __packed struct
{
uint8_t rgbBlue;
uint8_t rgbGreen;
uint8_t rgbRed;
uint8_t rgbReserved;
}BITMAP_ColorTable_TypeDef;
BMP图片数据中计算一行像素点数 如下函数的参数部分含义为:几个字节表示几个像素;当biBitCount = 1时,图像数据中的每个字节可绘制出8个像素;当biBitCount = 4时,图片数据中的每个字节可绘制出2个像素;当biBitCount = 8时,图片数据中的每个字节可绘制出1个像素;而当biBitCount = 24时,图片数据中就需要3个字节才给绘制出一个像素;所以在实际显示判断的时候,下面这个函数很重要,可以用来判断出哪些是显示数据,而哪些又是填充数据: uint16_t BMP_CalcPixelPerLine(uint16_t nPixel, uint16_t nByte)
{
uint16_t PixelPerLine = 0;
uint16_t BytePerWidth = 0;
if(BMP_InfoHeader->biSizeImage != 0)
{
PixelPerLine = (BMP_InfoHeader->biSizeImage / BMP_InfoHeader->biHeight) * nPixel / nByte;
}
else
{
BytePerWidth = BMP_InfoHeader->biWidth * nByte / nPixel;
if((BytePerWidth % 4) == 0)
{
PixelPerLine = BytePerWidth / nByte;
}
else
{
PixelPerLine = (((BytePerWidth + 4) / 4) * 4) / nByte;
}
}
return PixelPerLine;
}
BMP绘制图片函数 可根据文件信息、图片信息、调色板数据和图片数据进行自动识别并绘制出单色位图、16色位图、256色位图和24位真彩色位图: void BMP_DrawImage(char *FilePath)
{
BMP_RES = f_open(&BMP_File, FilePath, FA_READ);
if(BMP_RES != RES_OK)
{
printf("\r\nFile Open Fail! Result = %d\r\n", BMP_RES);
}
else
{
BMP_RES = f_read(&BMP_File, BMP_Buffer, sizeof(BITMAP_FileHeader_TypeDef), &BMP_BR);
if((BMP_RES == RES_OK) && (BMP_BR != 0))
{
BMP_FileHeader = (BITMAP_FileHeader_TypeDef *)BMP_Buffer;
}
else
{
f_close(&BMP_File); return;
}
BMP_RES = f_read(&BMP_File, BMP_Buffer, sizeof(BITMAP_InfoHeader_TypeDef), &BMP_BR);
if((BMP_RES == RES_OK) && (BMP_BR != 0))
{
BMP_InfoHeader = (BITMAP_InfoHeader_TypeDef *)BMP_Buffer;
}
else
{
f_close(&BMP_File); return;
}
LCD_SetWindow(0, 0, BMP_InfoHeader->biWidth - 1, BMP_InfoHeader->biHeight - 1);
switch(BMP_InfoHeader->biBitCount)
{
case 1: /* 单色位图 */
BMP_RES = f_read(&BMP_File, ColorTable, sizeof(BITMAP_ColorTable_TypeDef) * 2, &BMP_BR);
if((BMP_RES == RES_OK) && (BMP_BR != 0))
{
BMP_ColorTable = (BITMAP_ColorTable_TypeDef *)ColorTable;
BMP_PixelOfWidth = BMP_InfoHeader->biWidth;
BMP_PixelCounter = 0;
BMP_PixelPerLine = BMP_CalcPixelPerLine(8, 1);
do
{
BMP_RES = f_read(&BMP_File, BMP_Buffer, sizeof(BMP_Buffer), &BMP_BR);
for(uint32_t i = 0; i < BMP_BR; i++)
{
uint32_t j = 0;
for(uint8_t k = 0; k < 8; k++)
{
if(BMP_Buffer[i] & (0x80 >> k)) j = 1;
else j = 0;
if(BMP_PixelCounter < BMP_PixelOfWidth)
{
LCD_WR_DAT(RGB(BMP_ColorTable[j].rgbRed,
BMP_ColorTable[j].rgbGreen,
BMP_ColorTable[j].rgbBlue));
}
BMP_PixelCounter = (BMP_PixelCounter + 1) % BMP_PixelPerLine;
}
}
} while(BMP_BR != 0);
}
break;
case 4: /* 16色位图 */
BMP_RES = f_read(&BMP_File, ColorTable, sizeof(BITMAP_ColorTable_TypeDef) * 16, &BMP_BR);
if((BMP_RES == RES_OK) && (BMP_BR != 0))
{
BMP_ColorTable = (BITMAP_ColorTable_TypeDef *)ColorTable;
BMP_PixelOfWidth = BMP_InfoHeader->biWidth;
BMP_PixelCounter = 0;
BMP_PixelPerLine = BMP_CalcPixelPerLine(2, 1);
do
{
BMP_RES = f_read(&BMP_File, BMP_Buffer, sizeof(BMP_Buffer), &BMP_BR);
for(uint32_t i = 0; i < BMP_BR; i++)
{
uint8_t Hi = (BMP_Buffer[i] >> 4) & 0x0F;
uint8_t Lo = (BMP_Buffer[i] >> 0) & 0x0F;
if(BMP_PixelCounter < BMP_PixelOfWidth)
{
LCD_WR_DAT(RGB(BMP_ColorTable[Hi].rgbRed,
BMP_ColorTable[Hi].rgbGreen,
BMP_ColorTable[Hi].rgbBlue));
}
BMP_PixelCounter = (BMP_PixelCounter + 1) % BMP_PixelPerLine;
if(BMP_PixelCounter < BMP_PixelOfWidth)
{
LCD_WR_DAT(RGB(BMP_ColorTable[Lo].rgbRed,
BMP_ColorTable[Lo].rgbGreen,
BMP_ColorTable[Lo].rgbBlue));
}
BMP_PixelCounter = (BMP_PixelCounter + 1) % BMP_PixelPerLine;
}
} while(BMP_BR != 0);
}
break;
case 8: /* 256色位图 */
BMP_RES = f_read(&BMP_File, ColorTable, sizeof(BITMAP_ColorTable_TypeDef) * 256, &BMP_BR);
if((BMP_RES == RES_OK) && (BMP_BR != 0))
{
BMP_ColorTable = (BITMAP_ColorTable_TypeDef *)ColorTable;
BMP_PixelOfWidth = BMP_InfoHeader->biWidth;
BMP_PixelCounter = 0;
BMP_PixelPerLine = BMP_CalcPixelPerLine(1, 1);
do
{
BMP_RES = f_read(&BMP_File, BMP_Buffer, sizeof(BMP_Buffer), &BMP_BR);
for(uint32_t i = 0; i < BMP_BR; i++)
{
if(BMP_PixelCounter < BMP_PixelOfWidth)
{
LCD_WR_DAT(RGB(BMP_ColorTable[BMP_Buffer[i]].rgbRed,
BMP_ColorTable[BMP_Buffer[i]].rgbGreen,
BMP_ColorTable[BMP_Buffer[i]].rgbBlue));
}
BMP_PixelCounter = (BMP_PixelCounter + 1) % BMP_PixelPerLine;
}
} while(BMP_BR != 0);
}
break;
case 24: /* 24位 位图 */
BMP_PixelOfWidth = BMP_InfoHeader->biWidth;
BMP_PixelCounter = 0;
BMP_PixelPerLine = BMP_CalcPixelPerLine(1, 3);
do
{
BMP_RES = f_read(&BMP_File, BMP_Buffer, sizeof(BMP_Buffer), &BMP_BR);
for(uint32_t i = 0; i < BMP_BR; i+=3)
{
if(BMP_PixelCounter < BMP_PixelOfWidth)
{
LCD_WR_DAT(RGB(BMP_Buffer[i+2], BMP_Buffer[i+1], BMP_Buffer[i+0]));
}
BMP_PixelCounter = (BMP_PixelCounter + 1) % BMP_PixelPerLine;
}
} while(BMP_BR != 0);
break;
default:
break;
}
f_close(&BMP_File);
}
}
JPG图片文件 JPG图片是一种对图片数据进行压缩进行存储的图片文件格式,相比于BMP文件来说JPG文件占用更少的空间,可以达到存储更多的显示内容。虽然BMP也有RLE压缩算法,但使用却极其的少;当然对于JPG格式的图片来说,其文件结构定义也相对复杂很多,但是我们可以不去深入研究JPG格式的组成以及细节,因为在嵌入式应用领域,已经有很多巨人的肩膀可以借鉴,比如TJpgDec软件库、libjpeg软件库等等,可以对JPG文件格式进行软件解码操作;我们只需要掌握其使用方法,了解每个软件库的应用特点即可;通过软件解码的方式将解码的数据通过我们显示接口函数实现在液晶显示屏上的显示。
通过软件解码JPG文件,需要一定的解码时间,需要一定的解码缓存空间,这些是都是必须的消耗。当然有些MCU芯片内部已经集成了JPG硬件解码,这样对于JPG显示来说会更加的顺畅。本文选用的是TJpgDec软件库的方式来对JPG格式文件进行解码,TJpgDec与libjpeg相比而言,TJpgDec占用的资源会少很多,但解码速度却不及libjpeg,所以在选用哪个解码软件库的时候,可以根据需要适当选择。
TJpgDec软件解码库的应用实现 size_t MM32_infunc(JDEC *jd, BYTE *buff, UINT nd)
{
UINT rb;
FIL *fp = (FIL*)jd->device;
if(buff)
{
f_read(fp, buff, nd, &rb);
return rb;
}
else
{
return (f_lseek(fp, f_tell(fp) + nd) == FR_OK) ? nd : 0;
}
}
void MM32_Display(int left, int right, int top, int bottom, const uint16_t *buffer)
{
int jpeg_height = 0, jpeg_width = 0;
int lines_width = 0, lines_offset = 0;
uint32_t clip_width = 0;
if((left > right) || (top > bottom))
{
return;
}
if((left > LCD_RIGHT ) ||
(right < LCD_LEFT ) ||
(top > LCD_BOTTOM) ||
(bottom < LCD_TOP ) )
{
return;
}
/* 开始对图片的宽度和高度进行裁剪和修改 */
jpeg_height = bottom - top + 1; /* 计算图像的高度 */
jpeg_width = right - left + 1; /* 计算图像的宽度 */
/* 裁减掉在图片顶部超出显示范围的部分数据 */
if(top < LCD_TOP)
{
/* 计算出需要删除的数据量 */
buffer += jpeg_width * (LCD_TOP - top);
jpeg_height -= LCD_TOP - top;
top = LCD_TOP;
}
/* 裁减掉在图片底部超出显示范围的部分数据 */
if(bottom > LCD_BOTTOM)
{
jpeg_height -= bottom - LCD_BOTTOM;
bottom = LCD_BOTTOM;
}
/* 裁减掉在图片左边超出显示范围的部分数据 */
if(left < LCD_LEFT)
{
buffer += LCD_LEFT - left; /* 水平方向的数据指针右移 */
jpeg_width -= LCD_LEFT - left; /* 水平宽度减小 */
clip_width += LCD_LEFT - left; /* 记录裁减掉的宽度 */
left = LCD_LEFT;
}
/* 裁减掉在图片右边超出显示范围的部分数据 */
if(right > LCD_RIGHT)
{
jpeg_width -= right - LCD_RIGHT;
clip_width += right - LCD_RIGHT;
right = LCD_RIGHT;
}
if(JPG_Height < 320)
{
lines_offset = (320 - JPG_Height) / 2;
}
LCD_SetWindow(left, top + lines_offset, right, bottom + lines_offset);
while(jpeg_height--)
{
lines_width = jpeg_width;
while(lines_width--)
{
LCD_WR_DAT(*buffer++);
}
buffer += clip_width;
}
}
int MM32_outfunc(JDEC *jd, void *bitmap, JRECT *rect)
{
jd = jd;
MM32_Display(rect->left, rect->right,
rect->top, rect->bottom,
(uint16_t *)bitmap);
return 1;
}
void JPG_DrawImage(char *FilePath)
{
if(f_open(&JPG_File, FilePath, FA_READ) == FR_OK)
{
JDEC jd;
JRESULT rc;
BYTE scale;
rc = jd_prepare(&jd, MM32_infunc, JPG_Buffer, sizeof(JPG_Buffer), &JPG_File);
if(rc == JDR_OK)
{
JPG_Height = jd.height;
printf("\r\nJPG Width : %d, Height : %d", jd.width, jd.height);
for(scale = 0; scale < 3; scale++)
{
if(((jd.width >> scale) <= 240) && ((jd.height >> scale) <= 320))
{
break;
}
}
if(scale) scale--;
printf("\r\nJPG Scale : %d\r\n", scale);
rc = jd_decomp(&jd, MM32_outfunc, scale);
}
f_close(&JPG_File);
}
}
BIN图片数据 BIN图片数据的应用场合更多是将数据数据存放在代码空间,或者SPI FLASH中的形式;需要配合上位软件Image2Lcd将图片数据提取出来,在软件显示图片时候,不需要再对图片进行软件解码操作,直接取显示数据即可。Image2Lcd软件提供图片信息插入的功能,这样可以在软件显示图片时,根据图片信息(宽、高)来做相应的处理;而不同的取数据方式,也对应了不同的软件实现方式和在LCD液晶显示屏上的呈现效果,参考设置及软件代码实现如下所示:
void BIN_DrawImage(char *FilePath)
{
FIL BIN_File;
UINT BIN_BR;
FRESULT BIN_RES;
uint8_t BIN_Buffer[240];
BIN_RES = f_open(&BIN_File, FilePath, FA_READ);
if(BIN_RES != RES_OK)
{
printf("\r\nFile Open Fail! Result = %d\r\n", BIN_RES);
}
else
{
uint16_t Width = 0, Height = 0;
BIN_RES = f_read(&BIN_File, BIN_Buffer, 8, &BIN_BR);
Width = BIN_Buffer[2];
Width <<= 8;
Width |= BIN_Buffer[3];
Height = BIN_Buffer[4];
Height <<= 8;
Height |= BIN_Buffer[5];
printf("\r\nBIN Width : %d, Height : %d\r\n", Width, Height);
LCD_SetWindow(0, 0, Width - 1, Height - 1);
do
{
BIN_RES = f_read(&BIN_File, BIN_Buffer, 240, &BIN_BR);
for(uint32_t i = 0; i < BIN_BR; i+=2)
{
LCD_WR_DAT((((uint16_t)BIN_Buffer[i+0]) << 8) + BIN_Buffer[i+1]);
}
} while(BIN_BR != 0);
f_close(&BIN_File);
}
}
实现显示效果
显示JPG格式图片、BIN格式图片数据
显示BMP格式图片:24位色位图、256色位图、16色位图、单色位图
附件
演示视频 - 基于MM32实现数码相框功能的应用实例:https://www.bilibili.com/video/BV1rt4y1W7LP/
|