单片机:STM32F407VET6
TFT-LCD控制器:RA8875
SD卡:金士顿4GB
文件系统:FATFS R0.11
BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选lbit、4bit、8bit、24bit及32Bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。
BMP虽然是图片格式,但底层仍然是二进制文件。若要将二进制文件解析成图片,需要明确每一个二进制位代表什么含义!
#pragma pack (1)
/**
* 位图 = 文件头 + 信息头 + 调色板(可选) + 数据体
*/
/* 位图文件头 */
typedef struct tagBITMAP_FILE_HEADER{
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
}BITMAP_FILE_HEADER;
/* 位图信息头 */
typedef struct tagBITMAP_INFO_HEADER{
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
}BITMAP_INFO_HEADER;
typedef struct tagRGBQUAD{
uint8_t rgbBlue;
uint8_t rgbGreen;
uint8_t rgbRed;
uint8_t rgbReserved;
}RGBQUAD;
#pragma pack ()
1、文件头的结构定义了该图片的框架信息,占14个字节:
(1)bfType:指定文件类型,必须是 0x424D,即字符串”BM”,也就是说.bmp文件的关键字都是“BM”。
(2)bfSize:指定文件大小。
(3)bfOffBits:实际数据占文件头的偏移量。
2、信息头的结构定义了该图片的具体信息,占40个字节:
(1)biWidth:指定图像宽度,单位:像素
(2)biHeight:指定图像高度,单位:像素
(3)biBitCount:指定颜色位数,常用的值为1(灰度图),4(16色图),8(256色图),24(真彩色图),32(真彩色图,增加ALPHA通道)
3、调色板
对于8位图及以下才有调色板部分,对于24位、32位图像调色板位置直接就是数据部分。
调色板的目的,就是把一张图片所有用得到的颜色依次编号,每种颜色都有一个唯一的编号。在数据区域,只显示一种颜色在调色板区域的索引值。
以8位图为例,原来一个像素需要3个字节(RGB分量各占一个字节),现在只需要1个字节即可,大大节省了存储空间。
为什么对于24位图及以上,不使用调色板了呢?以24位图、240*320分辨率的图片为例:
调色板占空间:(2^24) * 4字节
图片占空间:240 * 320 * 3 字节
相比于不使用调色板,不但没有减小图像体积,反而多了一块巨大的调色板!
bmp.h
/*
*********************************************************************************************************
* @file bmp.h
* @author SY
* @version V1.0.0
* @date 2016-8-26 09:40:47
* @IDE V5.18.0.0
* @Chip STM32F407VE
* @brief BMP图片头文件
*********************************************************************************************************
* @attention
*
*
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Define to prevent recursive inclusion
*********************************************************************************************************
*/
#ifndef __BMP_H
#define __BMP_H
/*
*********************************************************************************************************
* Exported Includes
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Exported define
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Exported types
*********************************************************************************************************
*/
typedef enum
{
eErrTypeBMP_NONE = 0,
eErrTypeBMP_OPEN_FILE,
eErrTypeBMP_FILE_HEAD,
eErrTypeBMP_MALLOC,
eErrTypeBMP_NOT_SUPPORT,
}BMP_ERR_TypeDef;
/*
*********************************************************************************************************
* Exported constants
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Exported macro
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Exported variables
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Exported functions
*********************************************************************************************************
*/
BMP_ERR_TypeDef LCD_ShowBMP( uint16_t x, uint16_t y, const char *bmpPathPtr );
#endif
/************************ (C) COPYRIGHT STMicroelectronics **********END OF FILE*************************/
bmp.c
/*
*********************************************************************************************************
* @file bmp.c
* @author SY
* @version V1.0.0
* @date 2016-8-26 09:40:47
* @IDE V5.18.0.0
* @Chip STM32F407VE
* @brief BMP图片源文件
*********************************************************************************************************
* @attention
*
*
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Private Includes
*********************************************************************************************************
*/
#include "bsp.h"
#include "bmp.h"
/*
*********************************************************************************************************
* Private define
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Private typedef
*********************************************************************************************************
*/
#pragma pack (1)
/**
* 位图 = 文件头 + 信息头 + 调色板(可选) + 数据体
*/
/* 位图文件头 */
typedef struct tagBITMAP_FILE_HEADER{
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
}BITMAP_FILE_HEADER;
/* 位图信息头 */
typedef struct tagBITMAP_INFO_HEADER{
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
}BITMAP_INFO_HEADER;
typedef struct tagRGBQUAD{
uint8_t rgbBlue;
uint8_t rgbGreen;
uint8_t rgbRed;
uint8_t rgbReserved;
}RGBQUAD;
#pragma pack ()
/*
*********************************************************************************************************
* Private constants
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Private macro
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Private variables
*********************************************************************************************************
*/
extern uint16_t file_buff[];
/*
*********************************************************************************************************
* Private function prototypes
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Private functions
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Function Name : LCD_ShowBMP
* Description : 显示BMP图片(支持8、24、32位格式)
* Input : None
* Output : None
* Return : None
*********************************************************************************************************
*/
BMP_ERR_TypeDef LCD_ShowBMP( uint16_t x, uint16_t y, const char *bmpPathPtr )
{
FRESULT fresult;
FIL file_obj;
uint32_t br;
/* 打开文件 */
fresult = f_open(&file_obj,bmpPathPtr,FA_OPEN_EXISTING | FA_READ);
if (fresult != FR_OK)
{
return eErrTypeBMP_OPEN_FILE;
}
/* 打开文件头 */
BITMAP_FILE_HEADER fileHead;
fresult = f_read(&file_obj,&fileHead,sizeof(BITMAP_FILE_HEADER),&br);
if (fresult != FR_OK)
{
f_close(&file_obj);
return eErrTypeBMP_OPEN_FILE;
}
/* 验证"BM" */
if (fileHead.bfType != 0x4D42)
{
f_close(&file_obj);
return eErrTypeBMP_FILE_HEAD;
}
/* 打开信息头 */
BITMAP_INFO_HEADER infoHead;
fresult = f_read(&file_obj,&infoHead,sizeof(BITMAP_INFO_HEADER),&br);
if (fresult != FR_OK)
{
f_close(&file_obj);
return eErrTypeBMP_OPEN_FILE;
}
/* 行必须是4的倍数 */
const uint32_t MULTIPLE = 4;
uint32_t originBiWidth = infoHead.biWidth;
uint8_t rowRemain = infoHead.biWidth % MULTIPLE;
if (rowRemain != 0)
{
uint32_t div = infoHead.biWidth / MULTIPLE;
infoHead.biWidth = (div + 1) * MULTIPLE;
}
uint32_t virtualWidth = 0;
switch ( infoHead.biBitCount )
{
case 8:
virtualWidth = infoHead.biWidth;
break;
case 24:
virtualWidth = 3 * infoHead.biWidth;
break;
case 32:
virtualWidth = 4 * infoHead.biWidth;
break;
default:
f_close(&file_obj);
return eErrTypeBMP_NOT_SUPPORT;
}
/* 行、列合法性 */
if ((infoHead.biHeight==0) || (infoHead.biWidth==0))
{
f_close(&file_obj);
return eErrTypeBMP_NONE;
}
/* 是否存在调色板 */
bool isIncludePalette = true;
if (fileHead.bfOffBits == sizeof(BITMAP_FILE_HEADER) + sizeof(BITMAP_INFO_HEADER))
{
isIncludePalette = false;
}
/* 为调色板申请内存 */
RGBQUAD *palettePtr = NULL;
uint32_t paletteNums = 0;
if (isIncludePalette == true)
{
paletteNums = fileHead.bfOffBits - \
sizeof(BITMAP_FILE_HEADER) - sizeof(BITMAP_INFO_HEADER);
palettePtr = (RGBQUAD *)calloc(1,paletteNums);
if (palettePtr == NULL)
{
f_close(&file_obj);
return eErrTypeBMP_MALLOC;
}
}
/* 读取调色板数据 */
if (isIncludePalette == true)
{
fresult = f_read(&file_obj,palettePtr,paletteNums,&br);
if (fresult != FR_OK)
{
f_close(&file_obj);
if (isIncludePalette == true)
{
free(palettePtr);
}
return eErrTypeBMP_OPEN_FILE;
}
}
/*
* 显示图像数据
* 注:
* 1、BMP文件数据顺序为: 从左到右,从下向上。而液晶屏加载显示顺序为:从左到右,从上向下。需要重新定位!
* 2、无论是否含有调色板,数据区的起始地址(fileHead.bfOffBits)都不是4的倍数。
* 3、由于底层使用DMA读取数据,必须保证 f_read() 函数起始地址是4的倍数。
*/
const uint8_t OPPSET_POS = 2;
const uint16_t BMP_BUFF_SIZE = 4000;
uint8_t *bmpBuffPtr = (uint8_t *)&file_buff;
uint32_t maxBuffSize = BMP_BUFF_SIZE - OPPSET_POS; //由于后面会多读取 OPPSET_POS 个字节,因此需要留有余量
uint32_t maxReadLenth = maxBuffSize;
if (maxBuffSize % virtualWidth != 0)
{
uint32_t div = maxBuffSize / virtualWidth;
maxReadLenth = div * virtualWidth;
}
uint32_t maxColumnNums = maxReadLenth / virtualWidth;
uint32_t remainColumnNums = infoHead.biHeight;
uint16_t start_x = x;
uint16_t start_y = y + infoHead.biHeight;
/* 从该地址开始读取数据,保证读取的起始地址是4的倍数 */
uint32_t offset = fileHead.bfOffBits - OPPSET_POS;
while (1)
{
f_lseek(&file_obj,offset);
fresult = f_read(&file_obj,bmpBuffPtr,maxReadLenth+OPPSET_POS,&br);
br -= OPPSET_POS;
offset += br;
if (fresult != FR_OK)
{
f_close(&file_obj);
if (isIncludePalette == true)
{
free(palettePtr);
}
return eErrTypeBMP_OPEN_FILE;
}
if (br == 0)
{
break;
}
memmove(bmpBuffPtr,bmpBuffPtr+OPPSET_POS,maxReadLenth);
/* 设置像素窗口 */
if (remainColumnNums >= maxColumnNums)
{
remainColumnNums -= maxColumnNums;
start_x = x;
start_y -= maxColumnNums;
}
else
{
remainColumnNums = 0;
start_x = x;
start_y = y;
}
Active_Window(start_x,start_x+originBiWidth-1,start_y,start_y+maxColumnNums-1);
XY_Coordinate(start_x,start_y);
WriteCommand(0x02);
uint16_t pixel = 0;
RGBQUAD tempQuad;
switch (infoHead.biBitCount)
{
case 8:
{
uint32_t increase = 0;
uint32_t initValue = br - virtualWidth;
for (int32_t i=initValue; i>=0; ++i)
{
tempQuad = *(RGBQUAD *)(palettePtr + *(uint8_t *)(bmpBuffPtr + i));
pixel = RGB(tempQuad.rgbRed, tempQuad.rgbGreen, tempQuad.rgbBlue);
*(__IO uint16_t *)(LCD_DATA_ADD) = pixel;
if (++increase >= originBiWidth)
{
increase = 0;
initValue -= virtualWidth;
i = initValue - 1;
}
}
break;
}
case 24:
{
uint32_t increase = 0;
uint32_t initValue = br - virtualWidth;
for (int32_t i=initValue; i>=0; i+=3)
{
tempQuad.rgbBlue = *(uint8_t *)(bmpBuffPtr+i+0);
tempQuad.rgbGreen = *(uint8_t *)(bmpBuffPtr+i+1);
tempQuad.rgbRed = *(uint8_t *)(bmpBuffPtr+i+2);
tempQuad.rgbReserved = 0x00;
pixel = RGB(tempQuad.rgbRed, tempQuad.rgbGreen, tempQuad.rgbBlue);
*(__IO uint16_t *)(LCD_DATA_ADD) = pixel;
if (++increase >= originBiWidth)
{
increase = 0;
initValue -= virtualWidth;
i = initValue - 3;
}
}
break;
}
case 32:
{
uint32_t increase = 0;
uint32_t initValue = br - virtualWidth;
for (int32_t i=initValue; i>=0; i+=4)
{
tempQuad.rgbBlue = *(uint8_t *)(bmpBuffPtr+i+0);
tempQuad.rgbGreen = *(uint8_t *)(bmpBuffPtr+i+1);
tempQuad.rgbRed = *(uint8_t *)(bmpBuffPtr+i+2);
tempQuad.rgbReserved = *(uint8_t *)(bmpBuffPtr+i+3);
pixel = RGB(tempQuad.rgbRed, tempQuad.rgbGreen, tempQuad.rgbBlue);
*(__IO uint16_t *)(LCD_DATA_ADD) = pixel;
if (++increase >= originBiWidth)
{
increase = 0;
initValue -= virtualWidth;
i = initValue - 4;
}
}
break;
}
default:
{
break;
}
}
}
/* 关闭文件 */
f_close(&file_obj);
if (isIncludePalette == true)
{
free(palettePtr);
}
return eErrTypeBMP_NONE;
}
/************************ (C) COPYRIGHT STMicroelectronics **********END OF FILE*************************/
要点:
(1)BMP的文件顺序为:从左到右,从下到上。而液晶屏显示顺序为:从左到右,从上到下。因此,代码中需要调换BMP图像显示顺序。
(2)由于内存中BMP缓存只有4000字节,对于较大尺寸的图片,需要分段加载。
(3)BMP协议规定,行长度必须为4的倍数,如果不足,则填充0!
(4)在调试过程中遇到最难的并不是协议上的,我遇到一个问题。
显示:
将读取的数据与原始图片的数据作对比,发现在某一行数据之后,数据均发生2个字节的偏移!这是分段加载的结果,如果我将图片缓存区加大,一次性将数据全部读取出来,图片显示正常,因此我推测可能是文件系统的问题。
顿时灵光一现!特么我不是遇到过类似问题吗!
将数据写入SD卡正确,从SD卡读取到内存发现数据发生偏移,后来发现是因为SD卡读写数据是使用DMA传输数据,DMA要求内存起始地址必须4字节对齐!
计算数据起始地址,文件头(14) + 信息头(40) + 调色板(8位数据,1024) + 数据体(若干),14 + 40 + 1024 = 1078,
1078 / 4 = 269.5,不能被4整除,也就是说可能问题就是出在这里,这样使用f_read()读取的数据,第一次读取是正确的,在之后的某个地方只要一读取就偏移2个字节。结论:使用 f_read() 必须保证4字节对齐!
因此,每次分段读取数据时,使用 f_lseek() 向前偏移2个字节,保证4字节对齐,这样显示就OK啦!
看一下显示效果,Perfect!