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 所示。

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

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

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

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

最后,堆栈设置为 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。

五、注意事项
部分 STM32 的 HAL 库文件存在初始化失败的 bug,解决办法见:https://community.st.com/s/question/0D53W00000cSjbK/fw-v1161-broke-hal-mmc-driver
扫码关注尚为网微信公众号

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