STM32读取bmp图片 条纹 错色 以及FatFs读写错误

前言

从SD卡读取BMP图片到屏幕,用的SDIO加DMA的方式。运行了freertos实时系统,fatfs文件系统。

硬件平台:STM32F429IGT6

屏幕尺寸:1024*600 图像缓存在外部sdram中,16位色

目标:解决一些贴子,描述读取BMP图像问题 和一些帖子fatfs读写出错问题

问题

BMP一种简单的文件格式,尤其是24位色彩图像,就是像素的阵列在内存中的顺序排列,最前面14字节的fileHeader, 接着40字节infoHeader,(选项的颜色表),BGR BGR BGR....more...

 图像的尺寸在infoHeader中,其中要注意的是每一row 都是4 字节对齐。格式详情就不再描述了,可以自行搜索。最近看到一些帖子,说用fatfs文件系统读取BMP图像,有条纹,变色,错乱等等,还有一些帖子说fatfs文件系统超过512左右读写出错,发生的数据的整体偏移,f_read的字节流偶尔被 多插入或缺失了 一个 或 几个 字节。比如这张图片,在阿莫电子论坛看到的Fatfs中F_read(),为什么读超过500字节就出问题??? (amobbs.com 阿莫电子论坛)

 注意背景的黑点开头的蓝色条纹

原因

        先说原因,是因为fatfs对SD的访问,通过DMA读取要4字节对齐,就是说介绍DMA数据的Buffer要4字节对齐。不然(SDIO与DMA)的FIFO对齐缘故,会有一或几个错误字节插入字节流,导致f_read读取的字节流,会发生整体性偏移一个或两个字节。不要怀疑文件系统。ST官方的解决办法是,官方知道fatfs文件系统对SD的访问可能不会遵守4字节对齐。它方案是当用户传入pBuffer指针是4字节对齐,直接用DMA读取多个sector。不是4字节对齐的,就用4字节对齐的scratch[BLOCKSIZE]先读取一个sector,然后copy到pBuffer中。写SD也类似。并且在cubeMX生成的SD与Fatfs的read 和write 接口代码中,加入了一个定义开关ENABLE_SCRATCH_BUFFER 如下

/*
* Some DMA requires 4-Byte aligned address buffer to correctly read/wite data,
* in FatFs some accesses aren't thus we need a 4-byte aligned scratch buffer to correctly
* transfer data
*/
/* USER CODE BEGIN enableScratchBuffer */
#define ENABLE_SCRATCH_BUFFER
/* USER CODE END enableScratchBuffer */

/* Private variables ---------------------------------------------------------*/
#if defined(ENABLE_SCRATCH_BUFFER)
...
__ALIGN_BEGIN static uint8_t scratch[BLOCKSIZE] __ALIGN_END;//BLOCKSIZE =512 
#endif

我于是用硬件平台 搭了个范例,参数在前言中,先看的用于测试的原始图片pic004.bmp,如下

图片 没选好,四周都是白的,但是不影响。

平台正确显示,的图片如下:小黄人 是黄的,苹果是红的。

 图片下面 的条子 是未覆盖完全,显示完全正确。

然后下面自己的代码,我将移除对对ENABLE_SCRATCH_BUFFER 的支持,直接通过DMA对SD读写数据。调用HAL_SD_ReadBlocks_DMA(&hsd,pData,BlockAdd,NumberOfBlocks);不管pData是否4字节对齐。pData 的空间pvPortMalloc()函数分配的,对齐是否看运气。

然后错误图片来了,什么都没有变,唯一变得是pData 4字节对齐是否。

 4字节对齐是否 还是有影响的,颜色变了,右边还有一条裂纹,很明显BGR发生了字节的整体错位,再一些技巧把pData对齐到其他 非4字节对齐的位置。图片如下代码后

prow = pvPortMalloc(rowlen+8);//多要8字节好对齐
prow4align = prow;
if(((uint32_t)prow4align)&0x03ul)
{prow4align = (uint8_t *)((((uint32_t)prow4align)+3)&(~0x03ul));}
//先4字节对齐,然后偏移1 2 3等等
prow4align +=3;/* advance 3byte */
//如果改变这个advance 值,对图片有影响,恭喜你,你找到原因了,4字节对齐故障

还是 ,颜色变了,右边还有一条裂纹。

 下面是我的 BMP图片读取显示参考代码。

/**
  ******************************************************************************
  * @file    :
  * @author  :@GAOJUN
  * @date    :
  * @brief   :
  ******************************************************************************
	*/
/* Includes ------------------------------------------------------------------*/
/****** include begin ******/
#include "user_lib.h"

/****** include  end  ******/


/**
  * @brief  
  * @note   
  * @param  
  * @param  
  * @retval None
  */
void user_bmp_trans_row(uint8_t *prow,uint16_t rowWidth,uint16_t yloc)
{
	uint8_t *sbuf =prow;
	uint8_t *dbuf =(uint8_t *)&frame01[0][0];
	uint16_t *desbuf;
	uint16_t wdes = 1024;
	uint8_t  bppd = 2;
	uint16_t dcolor =0u;
	uint32_t scolor =0u;
	uint32_t index;
	
	/* line_comment */
	dbuf = dbuf +(((uint32_t)(wdes*bppd))*(yloc));
	desbuf = (uint16_t *)dbuf;
	
	for(index =0;index<rowWidth;index++)
	{
		/* R */
		scolor = sbuf[3*index+2];
		scolor >>=3;
		scolor <<=8;
		/* G */
		scolor |= sbuf[3*index+1];
		scolor >>=2;
		scolor <<=8;
		/* B */
		scolor |= sbuf[3*index];
		scolor >>=3;
		/* color */
		dcolor = scolor;
		
		desbuf[index] = dcolor;
	}
}
/**
  * @brief  
  * @note   
  * @param  
  * @param  
  * @retval None
  */
void user_sd_bmp_test_main(void)
{
	FATFS fatfs;
	FIL infile;
	uint8_t retSD;
	FRESULT fr;
	uint32_t br;
	BMP_TypeDef bmp;
	uint8_t *prow;
	uint8_t *prow4align;
	uint32_t rowlen;
	
	retSD = f_mount(&fatfs,"0:",1);
//	if(retSD){}
//	else{}
	
	fr = f_open(&infile, "0:/pics/pic004.bmp",FA_READ);
	if(fr){}
	else
	{
		f_read(&infile,(void *)&bmp.fHeader.bfType,sizeof(bmp),&br);
		if(br != sizeof(bmp))
		{
			return ;
		}
		/* continue,tag check "BM" */
		if(bmp.fHeader.bfType != 0x4D42u)
		{
			return ;
		}
		/* bit check */
		if(bmp.iHeader.biBitCount != 24u)
		{return ;}
		/* fetch image data */
		f_rewind(&infile);
		f_lseek(&infile,bmp.fHeader.bfOffBits);
		/* aligned 4 byte */
		rowlen = bmp.iHeader.biWidth*3;
		if(rowlen%4){rowlen += 4- rowlen%4;}
		
		prow = pvPortMalloc(rowlen+8);
		
		prow4align = prow;
//		if(((uint32_t)prow4align)&0x03ul)
//    {prow4align = (uint8_t *)((((uint32_t)prow4align)+3)&(~0x03ul));}
//		prow4align +=3;/* advance 3byte */
		
		if(prow ==NULL){return ;}
		
		for(uint16_t i=0;i< bmp.iHeader.biHeight;i++)
		{
			/* read a row */
			f_read(&infile,prow4align,rowlen,&br);
			/* translate a row */
			user_bmp_trans_row(prow4align,bmp.iHeader.biWidth,(bmp.iHeader.biHeight-i-1));
			if(br !=rowlen){break;}
		}
		vPortFree(prow);
	}
	f_close(&infile);	
}

参考头文件如下:

/**
  ******************************************************************************
  * @file    :
  * @author  :@GAOJUN
  * @date    :
  * @brief   :
  ******************************************************************************
	*/
/* Includes ------------------------------------------------------------------*/
/****** include begin ******/
#include "user_lib_def.h"

/****** include  end  ******/

/** 
  * @brief  XXX_handle Structure definition  
  */
typedef __packed struct
{
	uint16_t bfType;
	uint32_t bfSize;
	uint16_t bfReserved1;
	uint16_t bfReserved2;
	uint32_t bfOffBits;
}BMP_FHEADER_TypeDef;

/** 
  * @brief  XXX_handle Structure definition  
  */
typedef __packed struct
{
	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;
}BMP_IHEADER_TypeDef;
/** 
  * @brief  color table rgb item Structure definition  
  */
typedef __packed struct
{
	uint8_t rgbBlue;
	uint8_t rgbGreen;
	uint8_t rgbRed;
	uint8_t rgbReserved;
}BMP_TBITEM_TypeDef;
/** 
  * @brief  XXX_handle Structure definition  
  */
typedef __packed struct
{
	BMP_FHEADER_TypeDef fHeader;
	BMP_IHEADER_TypeDef iHeader;
	
}BMP_TypeDef;


/*----------------------------------------------------------------------------*/
void user_sd_bmp_test_main(void);

图片尺寸1024*575。上面代码,先读取54字节的头信息,然后偏移到f_lseek(&infile,bmp.fHeader.bfOffBits);后开始读取BGRBGR等等,512-54=458(456+2),456%4=0 如果我们在54字节处开始4字节对齐的Buffer的读取一横线1024*3。如上代码,将在文件系统读取第2个512字节块时,无法4字节对齐。图案(54-|-456-|-2)_(2-510),这里2)_(2就开始没有对齐。为了f_read不在乎对齐,就必须修改文件系统的read write驱动去主动对齐。

参考如下

/**
  ******************************************************************************
  * @file    :
  * @author  :@GAOJUN
  * @date    :
  * @brief   :
  ******************************************************************************
	*/
/* Includes ------------------------------------------------------------------*/
/****** include begin ******/
#include "user_lib.h"
#include <string.h>
/****** include  end  ******/



/*
* Some DMA requires 4-Byte aligned address buffer to correctly read/wite data,
* in FatFs some accesses aren't thus we need a 4-byte aligned scratch buffer to correctly
* transfer data
*/
#define ENABLE_SCRATCH_BUFFER




/* Private variables ---------------------------------------------------------*/
/****** Private variables begin ******/
osSemaphoreId_t SDBinarySemRxHandle;
const osSemaphoreAttr_t SDBinarySemRx_attributes = {
  .name = "SDBinarySemRx"
};

osSemaphoreId_t SDBinarySemTxHandle;
const osSemaphoreAttr_t SDBinarySemTx_attributes = {
  .name = "SDBinarySemTx"
};
#if defined(ENABLE_SCRATCH_BUFFER)
static uint8_t scratch[BLOCKSIZE] __ALIGNED(4);
#endif

static volatile DSTATUS Stat = STA_NOINIT;
extern volatile uint16_t run_led_period;

/****** Private variables  end  ******/

/**
  * @brief  
  * @note   
  * @param  
  * @param  
  * @retval None
  */
DSTATUS user_sd_check_status(void)
{
	Stat = STA_NOINIT;
	
	if(HAL_SD_GetCardState(&hsd) ==HAL_SD_CARD_TRANSFER)
	{
		Stat &= ~STA_NOINIT;
	}
	
	return Stat;
}

/**
  * @brief  
  * @note   
  * @param  
  * @param  
  * @retval None
  */
DSTATUS user_sd_init(void)
{
	Stat = STA_NOINIT;
	
	/* related semaphore */
	SDBinarySemRxHandle = osSemaphoreNew(1, 0, &SDBinarySemRx_attributes);
	SDBinarySemTxHandle = osSemaphoreNew(1, 0, &SDBinarySemTx_attributes);
	
	/* low level hardware */
	user_sdio_sd_init();
	
	/* check status */
	Stat = user_sd_check_status();
	
	return Stat;
}
/**
  * @brief Tx Transfer completed callbacks
  * @param hsd: Pointer to SD handle
  * @retval None
  */
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
  osSemaphoreRelease(SDBinarySemTxHandle);
}

/**
  * @brief Rx Transfer completed callbacks
  * @param hsd: Pointer SD handle
  * @retval None
  */
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
  osSemaphoreRelease(SDBinarySemRxHandle);
}
/**
  * @brief  
  * @note   
  * @param  
  * @param  
  * @retval None
  */
DRESULT user_SD_ReadBlocks_DMA(uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)
{
	uint16_t timeout =3000;
	DRESULT res = RES_ERROR;
	
#if defined(ENABLE_SCRATCH_BUFFER)
	if (!((uint32_t)pData & 0x3))
	{
#endif
		/* Fast path cause destination buffer is correctly aligned */
		/* prepare semaphore */
		osSemaphoreAcquire(SDBinarySemRxHandle,0);
		
		/* start DMA stream */
		HAL_SD_ReadBlocks_DMA(&hsd,pData,BlockAdd,NumberOfBlocks);
		
		/* wait cplt semaphore release */
		osSemaphoreAcquire(SDBinarySemRxHandle,800);
		
		/* and then wait for card state ok */
		while(timeout)
		{
			///
			if(HAL_SD_GetCardState(&hsd) ==HAL_SD_CARD_TRANSFER)
			{
				res = RES_OK;
				break;
			}
			
			/* wait a moment and release cpu also */
			timeout--;
			osDelay(1);
		}
		if(timeout == 0u)/* timeout occur  */
		{
			res = RES_ERROR;
		}
#if defined(ENABLE_SCRATCH_BUFFER)
	}
	else
	{
		/* Slow path, fetch each sector a part and memcpy to destination buffer */
		uint32_t i;
		
		for(i=0;i<NumberOfBlocks;i++)
		{
			/* prepare semaphore */
			osSemaphoreAcquire(SDBinarySemRxHandle,0);
			
			/* start DMA stream */
			HAL_SD_ReadBlocks_DMA(&hsd,scratch,BlockAdd++,1);
			
			/* wait cplt semaphore release */
			osSemaphoreAcquire(SDBinarySemRxHandle,800);
			
			/* move the data */
			memcpy(pData,scratch, BLOCKSIZE);
      pData += BLOCKSIZE;
			
			/* reset */
			timeout =3000;
			/* and then wait for card state ok */
			while(timeout)
			{
				///
				if(HAL_SD_GetCardState(&hsd) ==HAL_SD_CARD_TRANSFER)
				{
					res = RES_OK;
					break;
				}
				
				/* wait a moment and release cpu also */
				timeout--;
				osDelay(1);
			}
			if(timeout == 0u)/* timeout occur  */
			{
				res = RES_ERROR;
				break;
			}
		}
		if(i != NumberOfBlocks)
		{
			res = RES_ERROR;
		}
	}
#endif
	/* return state */
	return res;
}
/**
  * @brief  
  * @note   
  * @param  
  * @param  
  * @retval None
  */
DRESULT user_SD_WriteBlocks_DMA(uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)
{
	uint16_t timeout =3000;
	DRESULT res = RES_ERROR;
	
#if defined(ENABLE_SCRATCH_BUFFER)
	if (!((uint32_t)pData & 0x3))
	{
#endif
		/* Fast path cause destination buffer is correctly aligned */
		/* prepare semaphore */
		osSemaphoreAcquire(SDBinarySemTxHandle,0);
		
		/* start DMA stream */
		HAL_SD_WriteBlocks_DMA(&hsd,pData,BlockAdd,NumberOfBlocks);
		
		/* wait cplt semaphore release */
		osSemaphoreAcquire(SDBinarySemTxHandle,800);
		
		/* and then wait for card state ok */
		while(timeout)
		{
			///
			if(HAL_SD_GetCardState(&hsd) ==HAL_SD_CARD_TRANSFER)
			{
				res = RES_OK;
				break;
			}
			
			/* wait a moment and release cpu also */
			timeout--;
			osDelay(1);
		}
		if(timeout == 0u)/* timeout occur  */
		{
			res = RES_ERROR;
		}
#if defined(ENABLE_SCRATCH_BUFFER)
	}
	else
	{
		/* Slow path, fetch each sector a part and memcpy to destination buffer */
		uint32_t i;
		
		for(i=0;i<NumberOfBlocks;i++)
		{
			/* prepare semaphore */
			osSemaphoreAcquire(SDBinarySemTxHandle,0);
			
			/* move data */
			memcpy(scratch, pData, BLOCKSIZE);
      pData += BLOCKSIZE;
			
			/* start DMA stream */
			HAL_SD_WriteBlocks_DMA(&hsd,scratch,BlockAdd++,1);
			
			/* wait cplt semaphore release */
			osSemaphoreAcquire(SDBinarySemTxHandle,800);
			
			/* reset */
			timeout =3000;
			/* and then wait for card state ok */
			while(timeout)
			{
				///
				if(HAL_SD_GetCardState(&hsd) ==HAL_SD_CARD_TRANSFER)
				{
					res = RES_OK;
					break;
				}
				
				/* wait a moment and release cpu also */
				timeout--;
				osDelay(1);
			}
			if(timeout == 0u)/* timeout occur  */
			{
				res = RES_ERROR;
				break;
			}
		}
		if(i != NumberOfBlocks)
		{
			res = RES_ERROR;
		}
	}
#endif

	/* return state */
	return res;
}
/**
  * @brief  
  * @note   
  * @param  
  * @param  
  * @retval None
  */
DRESULT user_SD_ioctl(uint8_t cmd,void *buff)
{
	DRESULT res = RES_ERROR;
	HAL_SD_CardInfoTypeDef CardInfo;
	
	switch (cmd)
  {
  	case CTRL_SYNC:
			res = RES_OK;
  		break;
  	case GET_SECTOR_SIZE:
			HAL_SD_GetCardInfo(&hsd, &CardInfo);
		  *(WORD*)buff = CardInfo.LogBlockSize;
      res = RES_OK;
  		break;
		case GET_BLOCK_SIZE:
			HAL_SD_GetCardInfo(&hsd, &CardInfo);
			*(DWORD*)buff = CardInfo.LogBlockSize / 512u;
      res = RES_OK;
  		break;
  	case GET_SECTOR_COUNT:
			*(DWORD*)buff = CardInfo.LogBlockNbr;
      res = RES_OK;
  		break;
		
  	default:
			res = RES_PARERR;
  		break;
  }
	return res;
}

最后,无论你怎么修改下面,advance 字节数,都不会影响,显示。如果你的图片尺寸不是1024 宽度就会出现条纹,或是轻度错位,你可以修改试试,前提是你的read write 驱动不支持主动对齐,而且对f_read访问的 pbuffer不要对齐了,字节从54偏移处直接读取一row,就像首图所示

prow = pvPortMalloc(rowlen+8);
prow4align = prow;
if(((uint32_t)prow4align)&0x03ul)
{prow4align = (uint8_t *)((((uint32_t)prow4align)+3)&(~0x03ul));}
//先4字节对齐,后偏移0 1 2 3字节
//prow4align +=0;/* advance 0byte */
//prow4align +=1;/* advance 1byte */
//prow4align +=2;/* advance 2byte */
//prow4align +=3;/* advance 3byte */

刚刚移植好的 文件系统,最好用位图非对齐访问,来测试一下是否可靠,这很有帮助,可通过眼睛看每次的读取结果正确与否,1个字节的多余缺失,图都会变色。因为也有很多人说 移植的文件系统,读取偶有错误,缺失字节,多余字节,对于STM32八九不离10,就是这个原因。

这就是对DMA读取SD卡的4字节对齐的讨论。希望对初学者 有所帮助! !!

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值