STM32H743使用SDMMC接口读写eMMC存储器

STM32H743 使用 SDMMC 接口读写 eMMC 存储器,与 STM32 驱动 SD 类似。通过官方 HAL 库中 stm32h7xx_hal_mmc.h 文件驱动,采用 8bit 数据传输模式,可以很方便的实现,这篇文章就简单记录一下操作过程,实现裸机下的 STM32H743 读写 eMMC,带文件系统的实现则在后续的文章中查看。

参照 JEDEC eMMC 标准:JESD84-B51 (Revision of JESD84-B50.1, July 2014)

一、开发平台

  • 开发环境:MDK5.30
  • 移植驱动:STM32Cube_FW_H7_V1.9.0
  • 硬件平台:STM32H743VITX + eMMC(型号:MTFC4GACAJCN-1M WT)

二、STM32CubeMX 配置

2.1 RCC、SWD 配置

这两项的配置略,正常配置就行,无特别之处。

2.2 SDMMC 接口配置

(1)这里采用的是 SDMMC2 的接口,配置成 MMC 8bits Wide bus 模式。GPIO Setting 选项卡,将 GPIO 都设置成 Very High,如图 1 所示。

STM32H743使用SDMMC接口读写eMMC存储器
图 1 配置 GPIO 为 Very High

(2)NVIC Setting 选项卡使能 SDMMC2 全局中断,如图 2 所示。

STM32H743使用SDMMC接口读写eMMC存储器
图 2 使能 SDMMC2 全局中断

(3)分频系数配置,就是时钟树配置的 SDMMC1,2_CLK 的分频系数。SDMMC1,2_CLK 配置成了 80MHz,分频系数配置成 10,实测输出频率只有 4MHz。看来是 80/(10*2)MHz 的结果,实验了多个参数,均是如此,待查手册。注意:如果输出频率太高,会造成 eMMC 的读写功能不支持,具体设置可结合 eMMC 芯片手册来定。

STM32H743使用SDMMC接口读写eMMC存储器
图 3 配置 SDMMC 分频系数

(4)配置 FATFS,此篇文章暂未用到文件系统,留待后续实现。

STM32H743使用SDMMC接口读写eMMC存储器
图 4 配置 FATFS

(5)配置 SDMMC1,2 时钟频率

STM32H743使用SDMMC接口读写eMMC存储器
图 5 配置 SDMMC 时钟频率

最后,堆栈设置为 0x1000,生成工程。按照上面的配置生成的代码基本是可以用起来的。下面添加一些读写功能测试代码,以便验证。

三、添加 eMMC 读写功能测试代码

3.1 宏定义及变量的声明

/* USER CODE BEGIN PD */
typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus;
#define BLOCK_SIZE            512         // eMMC Block Size
#define NUMBER_OF_BLOCKS      8           // Test Block Number(<15)
#define WRITE_READ_ADDRESS    0x00000000  // Test RD Address
/* USER CODE END PD */

/* USER CODE BEGIN PV */
HAL_MMC_CardCIDTypeDef SD_CardCID;
uint32_t Buffer_Block_Tx[BLOCK_SIZE*NUMBER_OF_BLOCKS]; 					//Write Buffer
uint32_t Buffer_Block_Rx[BLOCK_SIZE*NUMBER_OF_BLOCKS] = {1,2}; 	//Read Buffer
HAL_StatusTypeDef sd_status;    	// eMMC status
TestStatus test_status;           // eMMC Test Result
/* USER CODE END PV */

/* USER CODE BEGIN PFP */
void Fill_Buffer(uint32_t *pBuffer, uint32_t BufferLength, uint32_t Offset);
TestStatus Buffercmp(uint32_t* pBuffer1, uint32_t* pBuffer2, uint32_t BufferLength);
TestStatus eBuffercmp(uint32_t* pBuffer, uint32_t BufferLength);

void SD_EraseTest(void);
void SD_Write_Read_Test(void);
void eMMC_Test(void);
/* USER CODE END PFP */

3.2 对主函数代码的添加

	HAL_MMC_CardStateTypeDef State;
	State = HAL_MMC_GetCardState(&hmmc2);
	if(State == HAL_MMC_CARD_TRANSFER)
	{
		HAL_Delay(10000);	//delay 10s for usb virtual com connect and open in XCOM
		HAL_MMC_GetCardCID(&hmmc2,&SD_CardCID);
		usb_printf("\r\n Initialize SD card successfully!\r\n\r\n");
		HAL_Delay(1);	
		usb_printf(" SD card information! \r\n");
		HAL_Delay(1);	
		usb_printf(" CardCapacity  : %llu \r\n",((unsigned long long)hmmc2.MmcCard.BlockSize*hmmc2.MmcCard.BlockNbr));
		HAL_Delay(1);	
		usb_printf(" CardBlockSize : %d \r\n",hmmc2.MmcCard.BlockSize);
		HAL_Delay(1);	
		usb_printf(" RCA           : %d \r\n",hmmc2.MmcCard.RelCardAdd);
		HAL_Delay(1);	
		usb_printf(" CardType      : %d \r\n",hmmc2.MmcCard.CardType);
		HAL_Delay(1);	
		usb_printf(" ManufacturerID: %d \r\n",SD_CardCID.ManufacturerID);
		HAL_Delay(1);	
	}
	else
	{
		usb_printf("SD init failed\n" );
		while(1); 
	}
	/* erase */
  SD_EraseTest();
  /* WR */
  SD_Write_Read_Test();

3.3 主要代码段修改与添加

/* USER CODE BEGIN 4 */
/**
  * 函数功能: SD 卡擦除测试
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SD_EraseTest(void)
{
	/* 第 1 个参数为 SD 卡句柄,第 2 个参数为擦除起始地址,第 3 个参数为擦除结束地址 */
	sd_status=HAL_MMC_Erase(&hmmc2,WRITE_READ_ADDRESS,WRITE_READ_ADDRESS+NUMBER_OF_BLOCKS*4);
	usb_printf("erase status:%d\r\n",sd_status);
	HAL_Delay(100);
  if (sd_status == HAL_OK)
  {	
    /* 读取刚刚擦除的区域 */
    sd_status = HAL_MMC_ReadBlocks(&hmmc2,(uint8_t *)Buffer_Block_Rx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS,0xffff);
    usb_printf("erase read status:%d\r\n",sd_status);
    /* 把擦除区域读出来对比 */
    test_status = eBuffercmp(Buffer_Block_Rx,BLOCK_SIZE*NUMBER_OF_BLOCKS);

    if(test_status == PASSED)
      usb_printf("》擦除测试成功!\r\n" ); 
    else	  
      usb_printf("》擦除不成功,数据出错!\r\n" );      
  }
  else
  {
    usb_printf("》擦除测试失败!部分 SD 不支持擦除,只要读写测试通过即可\r\n" );
  }
}

/**
  * 函数功能: SD 卡读写测试
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SD_Write_Read_Test(void)
{  
	int i,j = 0;
  /* 填充数据到写缓存 */
  Fill_Buffer(Buffer_Block_Tx,BLOCK_SIZE*NUMBER_OF_BLOCKS, 0x32F1);
  
  /* 往 SD 卡写入数据 */
  sd_status = HAL_MMC_WriteBlocks_DMA(&hmmc2,(uint8_t *)Buffer_Block_Tx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS);
  usb_printf("write status:%d\r\n",sd_status);
  HAL_Delay(10);
  /* 从 SD 卡读取数据 */
  sd_status = HAL_MMC_ReadBlocks_DMA(&hmmc2,(uint8_t *)Buffer_Block_Rx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS);
  usb_printf("read status:%d\r\n",sd_status);
	HAL_Delay(10);	
  
  /* 比较数据 */
  test_status = Buffercmp(Buffer_Block_Tx, Buffer_Block_Rx, BLOCK_SIZE*NUMBER_OF_BLOCKS/4);	//比较
  if(test_status == PASSED)
	{
    usb_printf("》读写测试成功!\r\n" );
//		HAL_Delay(1);	
		
		for(i=0;i<BLOCK_SIZE*NUMBER_OF_BLOCKS/4;i++)
		{
			if(j==8)
			{
				usb_printf("\r\n");
				HAL_Delay(1);	
				j=0;
			}
			
			usb_printf("%08x   ",Buffer_Block_Rx[i]);
			HAL_Delay(1);	
			j++;
		}
		usb_printf("\r\n");
		HAL_Delay(1);	
	}
  else  
  	usb_printf("》读写测试失败!\r\n " );
}

/**
  * 函数功能: 在缓冲区中填写数据
  * 输入参数: pBuffer:要填充的缓冲区
  *           BufferLength:要填充的大小
  *           Offset:填在缓冲区的第一个值 
  * 返 回 值: 无
  * 说    明: 无
  */
void Fill_Buffer(uint32_t *pBuffer, uint32_t BufferLength, uint32_t Offset)
{
  uint32_t index = 0;
  /* 填充数据 */
  for (index = 0; index < BufferLength; index++ )
  {
    pBuffer[index] = index + Offset;
  }
}

/**
  * 函数功能: 比较两个缓冲区中的数据是否相等
  * 输入参数: pBuffer1:要比较的缓冲区 1 的指针
  *           pBuffer2:要比较的缓冲区 2 的指针
  *           BufferLength:缓冲区长度
  * 返 回 值: PASSED:相等
  *           FAILED:不等
  * 说    明: 无
  */
TestStatus Buffercmp(uint32_t* pBuffer1, uint32_t* pBuffer2, uint32_t BufferLength)
{
  while (BufferLength--)
  {
    if(BufferLength%50==0)
    {
      usb_printf("buf:0x%08X - 0x%08X\r\n",*pBuffer1,*pBuffer2);
			HAL_Delay(1);	
    }
    if (*pBuffer1 != *pBuffer2)
    {
      return FAILED;
    }
    pBuffer1++;
    pBuffer2++;
  }
  return PASSED;
}

/**
  * 函数功能: 检查缓冲区的数据是否为 0xff 或 0
  * 输入参数: pBuffer:要比较的缓冲区的指针
  *           BufferLength:缓冲区长度
  * 返 回 值: PASSED:缓冲区的数据全为 0xff 或 0
  *           FAILED:缓冲区的数据至少有一个不为 0xff 或 0 
  * 说    明: 无
  */
TestStatus eBuffercmp(uint32_t* pBuffer, uint32_t BufferLength)
{
  while (BufferLength--)
  {
    /* SD 卡擦除后的可能值为 0xff 或 0 */
    if ((*pBuffer != 0xFFFFFFFF) && (*pBuffer != 0))
    {
      return FAILED;
    }
    pBuffer++;
  }
  return PASSED;
}
/* USER CODE END 4 */

3.4 几点说明

(1)由于目前使用的 STM32H743 硬件没有引出串口功能,所以使用了 USB 虚拟串口功能,并重定向到打印函数,也就是:usb_printf(); 每次打印之后,要增加一个延时,防止数据被下一次的打印数据所覆盖。另外,每次 STM32H743 下载程序时,USB 虚拟串口都会掉线重连,为方便观看数据,可在打印之前增加 10s 的延迟,便于手动打开串口助手观察数据。

PS:如果有串口功能,直接使用串口打印还是相对方便些。

(2)擦除函数和读写函数部分都要增加延时函数,为了方便测试,直接使用了 HAL_Delay()函数,延时的时间需要注意,如果太短,会造成读写测试报错。

(3)此次验证 eMMC 的读写函数使用了 DMA 功能,根据STM32H7 总线架构和内部 SRAM描述,需将 DMA 的收发 buffer 指向 D1 域或者 D2 域的 SRAM 地址(具体是 SDMMC1 仅指向 D1 域 SRAM 地址,SDMMC2 可指向 D1 域和 D2 域 SRAM 地址),否则会出现读写失败。

四、实测结果

通过串口可看到,STM32H743 能够正确地擦除及读写 eMMC。

STM32H743使用SDMMC接口读写eMMC存储器
图 6 STM32H743 读写 eMMC 测试结果

五、注意事项

部分 STM32 的 HAL 库文件存在初始化失败的 bug,解决办法见:https://community.st.com/s/question/0D53W00000cSjbK/fw-v1161-broke-hal-mmc-driver

附件仅博主测试所用,暂不提供下载

扫码关注尚为网微信公众号

尚为网微信公众号
每天学习电路设计嵌入式系统的专业知识,关注一波,没准就用上了。

原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/937.html

(1)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2021年3月25日 20:28
下一篇 2021年3月29日 17:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注