## SD ALIENTKE 战舰 STM32F103 自带了标准的 SD 卡接口 4 位模式,最高通信速度可达 24Mhz,最高每秒可传输数据 12M 字节 支持三种不同的数据总线模式:1 位(默认)、4 位和 8 位。 ***SD SDIO是不同的*** ### 时钟 SDIO_CK时钟是通过PC12引脚连接到SD卡的,是SDIO接口与SD卡用于同步的时钟。 SDIO选配器挂载到AHB总线上,通过HCLK二分频输入到适配器得到SDIO_CK的时钟,这时SDIO_CK = HCLK/(2+CLKDIV)。其中CLKDIV是SDIO_CLK(寄存器)中的CLKDIV位。 另外,SDIO_CK也可以由SDIOCLK通过设置bypass模式直接得到,这时SDIO_CK = SDIOCLK=HCLK 对SD卡的操作一般是大吞吐量的数据传输,所以采用DMA来提高效率,SDIO采用的是DMA2中的通道4。 SDIO_CK = SDIOCLK / (ClockDiv + 2) ***写注意事项*** ### 配置 98 PC8 SDIO_D0 N SD 卡接口的 D0 99 PC9 SDIO_D1 N SD 卡接口的 D1 111 PC10 SDIO_D2 N SD 卡接口的 D2 112 PC11 SDIO_D3 N SD 卡接口的 D3 113 PC12 SDIO_SCK Y SD 卡接口的 SCK 选 SD 4 bits Wide bus SD 1 bits 单线SD模式 SD 4 bits Wide bus 4线SD模式 MMC 1 bit MMC卡单线模式 MMC 4 bits Wide bus MMC卡4线模式 MMC 8 bits Wide bus MMC卡8线模式 第一行相当于是在时钟的上升沿还是下降沿采集数据,一般都是上升沿。 第二行决定是否使用bypass(旁路),即跳过分频直接从HCLK得到时钟,那样频率会非常高,所以也不需要。 第三行空闲时,也就是在没有数据交互的时候是否关闭时钟电源,在不考虑功耗的情况下还是尽量不要用比较好。 第四行硬件流控,可用于解决数据量大或者高速传输时的速度匹配和准确率不足等问题,这里不需要。 第五行,时钟分频,与读写速度有关,这里不多说了,时钟树配好48M的话这里选0就好。 #### SD卡检测引脚 PE4 High PP ***SDIO不需要*** #### SD 初始化代码 没有挂载文件系统之前的 ```C void MX_SDIO_SD_Init(void) { /* USER CODE BEGIN SDIO_Init 0 */ /* USER CODE END SDIO_Init 0 */ /* USER CODE BEGIN SDIO_Init 1 */ /* USER CODE END SDIO_Init 1 */ hsd.Instance = SDIO; hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING; hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE; hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE; hsd.Init.BusWide = SDIO_BUS_WIDE_4B; hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE; hsd.Init.ClockDiv = 0; if (HAL_SD_Init(&hsd) != HAL_OK) { Error_Handler(); } if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN SDIO_Init 2 */ /* USER CODE END SDIO_Init 2 */ } ``` ```C HAL_SD_CardInfoTypeDef SDCardInfo; void show_sdcard_info(void) { uint64_t CardCap; //SD卡容量 HAL_SD_CardCIDTypeDef SDCard_CID; HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //获取SD卡信息 switch(SDCardInfo.CardType) { case CARD_SDSC: { if(SDCardInfo.CardVersion == CARD_V1_X) printf("Card Type:SDSC V1\r\n"); else if(SDCardInfo.CardVersion == CARD_V2_X) printf("Card Type:SDSC V2\r\n"); } break; case CARD_SDHC_SDXC:printf("Card Type:SDHC\r\n");break; } CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算SD卡容量 printf("Card ManufacturerID:%d\r\n",SDCard_CID.ManufacturerID); //制造商ID printf("Card RCA:%d\r\n",SDCardInfo.RelCardAdd); //卡相对地址 printf("LogBlockNbr:%d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //显示逻辑块数量 printf("LogBlockSize:%d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //显示逻辑块大小 printf("Card Capacity:%d MB\r\n",(uint32_t)(CardCap>>20)); //显示容量 printf("Card BlockSize:%d\r\n\r\n",SDCardInfo.BlockSize); //显示块大小 } ``` ### 测试程序 测试SD是否检测到 挂在文件系统测试 ```C int SDTest(void){ // 挂载文件系统 retSD = f_mount(&SDFatFS, SDPath, 1); if(retSD) { SEGGER_RTT_printf(0,"mount error : %d \r\n",retSD); return -1; } else { SEGGER_RTT_printf(0,"mount sucess!!! \r\n"); } HAL_SD_CardCIDTypeDef CID; HAL_StatusTypeDef s1 = HAL_SD_GetCardCID(&hsd, &CID); HAL_SD_CardCSDTypeDef CSD; HAL_StatusTypeDef s2 = HAL_SD_GetCardCSD(&hsd, &CSD); HAL_SD_CardStatusTypeDef status; HAL_StatusTypeDef s3 = HAL_SD_GetCardStatus(&hsd, &status); HAL_SD_CardInfoTypeDef cardInfo; HAL_StatusTypeDef s4 = HAL_SD_GetCardInfo(&hsd, &cardInfo); uint32_t fCardSize = 1.0*cardInfo.BlockNbr*cardInfo.BlockSize/1024/1024; SEGGER_RTT_printf(0,"BlockNum=%d BlockSize=%d \r\n",cardInfo.BlockNbr,cardInfo.BlockSize); SEGGER_RTT_printf(0,"Card Size:%d M\r\n", fCardSize); return 1; } ``` ### SD实例化 attenison: 这个不能配置为 4B, 就是启动的时候,用1B hsd.Init.BusWide = SDIO_BUS_WIDE_1B; 后面配置为4B, 否则这个地方会报错 if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK) { Error_Handler(); } PE SD_Insert Pull_down Input PA15 CDZ 检测不到SD卡 不启动 测试记录: 有无配SDIO的DMA 只要hsd.Init.BusWide = SDIO_BUS_WIDE_1B 都能通过检测 改为4B ,注销HAL_SD_ConfigWideBusOperation后面的修改也可以通过检测,不能获取SD卡状态 优先级 SDIO 》DMA 》 USB ### SDIO USB PA 15 --CDZ GPIO INPUT ,NO PULLUP PULLDOWN SDIO IT SDIO DMA RX --DMA2_3 SDIO DMA TX --DMA2_6 USB FS USB CDC 中断 USB 》 SDIO DMA 》 SDIO ### SD 初始化 ***创建文件失败错误*** cubemx 生成代码后需要修改这里 SDIO.c文件 hsd.Init.BusWide = SDIO_BUS_WIDE_1B; hsd.Init.ClockDiv = SDIO_INIT_CLK_DIV; //SDIO_TRANSFER_CLK_DIV; hsd.Instance = SDIO; hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING; hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE; hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE; hsd.Init.BusWide = SDIO_BUS_WIDE_1B; hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE; hsd.Init.ClockDiv = 2; ### DMA FIFO WORD ### 中断 SDIO 优于 DMA 优于 USB 值正好相反 ### heap stack 0x1000 ox1500 f_mount等要放任务中,增加了堆栈大小 StartDefaultTask 堆栈要调大 1024 ### 最大最小扇区大小 (在cubemx fatfa USB_DEVICE, 可以不改) 512 -> 4096 FATFS 两处地方 USB_Device 两处地方 ### USB 的调整 ***CubeMX 自动将 MX_USB_DEVICE_Init() 函数放到 StartDefaultTask 中 *** 优于定义了 StartDefaultTask 虚函数, 不执行 需要手动移植到实际函数中 USB_Device初始化函数在默认任务中,所以要增加默认任务 StartDefaultTask 的堆栈, MX_USB_DEVICE_Init(); ### 重写usbd_storage_if.c *** 不写的话 读取不到Upan信息, 显示upan大小都不对 *** #define STORAGE_LUN_NBR 1 /* 逻辑单元号,只有一个外部flash,设置为1 */ #define STORAGE_BLK_NBR 2048 /* 扇区的数量,外部flash的大小是8Mbyte,有128块,每块16个扇区,故128*16=2048个扇区 */ #define STORAGE_BLK_SIZ 4096 /* 每个扇区的大小,外部flash扇区的大小为4096byte */ 以上通过输出SD信息改写 因为加入了FreeRTOS,fatfs对sd卡读写用的是发送消息机制, 所以usbd_storage_if.c里的读写不能再去调用bsp_driver_sd.c里的读写方法了, 大家有兴趣的可以对比一下有无FreeRTOS,bsp_driver_sd.c源码的内容 读写方法等 STM32F407 开发板没有用到 VUSB 电压检测,所以要在 usb_conf.h 里面 ( USB_DEVICE\Target\usbd_conf.h) 将宏定义:#define VBUS_SENSING_ENABLED,屏蔽掉。 3,通过修改 usbd_conf.h 里面的 MSC_MEDIA_PACKET 定义值大小,可以一定程度提高 USB 读写速度(越大越快),本例程我们设置 12*1024,也就是 12K 大小。 ***官方例程不支持大于 4G 的 SD 卡*** Middlewares\ST\STM32_USB_Device_Library\Class\MSC\Src 官方例程不支持大于 4G 的 SD 卡,得修改: usbd_msc_scsi.c 里面的 或 usbd_msc.h( Cubemx HAl) SCSI_blk_addr 类型为 uint64_t 才可以支持大于 4G 的卡,官方默认是 uint32_t,最大只能支持 4G 卡。 注意: usbd_msc.h uint64_t scsi_blk_addr; uint64_t scsi_blk_len; usbd_msc_scsi.c 文件,是只读的,得先修改属性,去掉只读属性,才可以更改。 HS FS 标记 #define DEVICE_FS 1 #define DEVICE_HS 0 usb_devioce MSC_MEDIA_PAKCET 改32678 32*1024U #### usb 编译 -DUSE_USB_OTG_FS SD_port.c ```C #include #include "stm32f4xx.h" #include "main.h" // #include "config.h" #include "elog.h" #include "bsp_driver_sd.h" #include "fatfs.h" HAL_SD_CardInfoTypeDef SDCardInfo; extern SD_HandleTypeDef hsd; FATFS fs; //工作空间 FIL fil; // 文件项目 extern void printf_sdcard_info(void); extern int Init_FatFas_Mount(void); extern int creat_file(char * filename); extern uint8_t BSP_SD_Init(void); uint8_t BSP_SD_Init(void) { uint8_t sd_state = MSD_OK; /* Check if the SD card is plugged in the slot */ if (BSP_SD_IsDetected() != SD_PRESENT) { return MSD_ERROR; } /* HAL SD initialization */ sd_state = HAL_SD_Init(&hsd); /* Configure SD Bus width (4 bits mode selected) */ if (sd_state == MSD_OK) { hsd.Init.ClockDiv = 6; // hsd.Init.ClockDiv = SDIO_TRANSFER_CLK_DIV; /* Enable wide operation */ if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK) { sd_state = MSD_ERROR; } } return sd_state; } void printf_sdcard_info(void) { uint64_t CardCap; //SD卡容量 HAL_SD_CardCIDTypeDef SDCard_CID; char * card_type[8] = {0}; HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //获取SD卡信息 CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算SD卡容量 switch(SDCardInfo.CardType) { case CARD_SDSC: { if(SDCardInfo.CardVersion == CARD_V1_X) { memcpy(card_type, (void*)("SDSC V1"),7); // printf("Card Type:SDSC V1\r\n"); } else if (SDCardInfo.CardVersion == CARD_V2_X) { memcpy(card_type, (void*)("SDSC V2"),7); // printf("Card Type:SDSC V2\r\n"); } break; } case CARD_SDHC_SDXC: { memcpy(card_type, (void*)("SDHC"),4); printf("Card Type:SDHC\r\n"); break; } default:break; } #if 1 log_i("Card Type: %s \r\n",card_type); //card 类型 log_i("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID); //制造商ID log_i("CardVersion: %d \r\n",(uint32_t)(SDCardInfo.CardVersion)); //卡版本号 log_i("Class: %d \r\n",(uint32_t)(SDCardInfo.Class)); // log_i("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd); //卡相对地址 log_i("Card BlockNbr: %d \r\n",SDCardInfo.BlockNbr); //块数量 log_i("Card BlockSize: %d \r\n",SDCardInfo.BlockSize); //块大小 log_i("LogBlockNbr: %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //逻辑块数量 log_i("LogBlockSize: %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //逻辑块大小 log_i("Card Capacity: %d MB\r\n",(uint32_t)(CardCap>>20)); //卡容量 #endif #if 0 printf("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID); //制造商ID printf("CardVersion: %d \r\n",(uint32_t)(SDCardInfo.CardVersion)); //卡版本号 printf("Class: %d \r\n",(uint32_t)(SDCardInfo.Class)); // printf("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd); //卡相对地址 printf("Card BlockNbr: %d \r\n",SDCardInfo.BlockNbr); //块数量 printf("Card BlockSize: %d \r\n",SDCardInfo.BlockSize); //块大小 printf("LogBlockNbr: %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //逻辑块数量 printf("LogBlockSize: %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //逻辑块大小 printf("Card Capacity: %d MB\r\n",(uint32_t)(CardCap>>20)); //卡容量 #endif } int Init_FatFas_Mount(void) { int retSD = f_mount(&fs, "0:", 0); return retSD; } /** * @brief 创建文件 * @param [in] filename "0:aa.txt" * * @details * creat_file( "0:aa.txt" ); */ int creat_file(char * filename) { // f_mkfs( ); int retSD = f_open(&fil, filename, FA_CREATE_ALWAYS | FA_WRITE); //打开文件,权限包括创建、写(如果没有该文件,会创建该文件) if(retSD==FR_OK) { // printf("\r\ncreate file success!!! \r\n"); f_close(&fil); //关闭该文件 return retSD; } else { // printf("\r\ncreater file error : %d\r\n",retSD); f_close(&fil); //关闭该文件 return retSD; } // f_close(&fil); //关闭该文件 // HAL_Delay(100); } void write_file( char * data, uint32_t len ) { uint32_t byteswritten; /*##-3- Write data to the text files ###############################*/ int retSD = f_write(&fil, data, len, (void *)&byteswritten); if(retSD) printf(" write file error : %d\r\n",retSD); else { printf(" write file sucess!!! \r\n"); printf(" write Data[%d] : %s\r\n",byteswritten,data); } /*##-4- Close the open text files ################################*/ retSD = f_close(&fil); if(retSD) printf(" close error : %d\r\n",retSD); else printf(" close sucess!!! \r\n"); } void SD_Read_block_Test(void) { // uint8_t read_buf[512]; // int sdcard_status = HAL_SD_ReadBlocks(&hsd, (uint8_t *)read_buf, 0, 1, 0xffff); // if(sdcard_status == HAL_OK) // { // printf("Read block data ok! \r\n"); // } // else // { // printf("Read block data fail! status = %d \r\n", sdcard_status); // } } ``` ***注意CDZ SD_INSERT的检测*** #### USBMSC 新建 usbmsc_port.c 将OTG_FS_IRQHandler(void) 函数提出来 大容量SD ,修改 1. usbd_msc.h uint64_t scsi_blk_addr; uint64_t scsi_blk_len; 2. usbd_msc_scsi.c 文件,是只读的,得先修改属性,去掉只读属性,才可以更改。 (uint32_t)params --> (uint64_t)params 3. usbd_conf.h HS FS 标记 #define DEVICE_FS 1 #define DEVICE_HS 0 4. usb_devioce MSC_MEDIA_PAKCET 改32678 32*1024U, 格式化时分区的大小 如果改成32768 需要至少32K空闲内存 4096 或8192 都改成阻塞式读取数据就可以, 单片机本身就单线程 可以先实验 FATFS DMA读取数据,再实验USBMSC OTG_FS_IRQHandler 中断函数可以等待信号量,如何释放信号量 #### file_list 分析 创建节点文件 : 72字节: FIL : list_head : 32byte used_node_now: 40byte //< 现在指向的链表节点 free_node_now: 40byte 20 00 00 00 00 00 00 00 -->> 32 ??? 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00 00