手把手教你用STM32F103的USB接口把SD卡变成U盘(附完整代码)
手把手教你用STM32F103的USB接口把SD卡变成U盘附完整代码在嵌入式开发中经常需要实现设备与PC之间的数据交换。传统方式如串口传输速度较慢而使用USB接口将SD卡模拟成U盘不仅传输速度快还能像普通U盘一样即插即用。本文将详细介绍如何利用STM32F103的USB接口实现这一功能。1. 硬件准备与连接要实现这个项目你需要准备以下硬件组件STM32F103C8T6开发板或其他兼容的STM32F103系列Micro SD卡模块支持SPI或SDIO接口USB接口连接线Type-A转Micro-B杜邦线若干硬件连接示意图STM32引脚SD卡模块引脚PA4CSPA5SCKPA6MISOPA7MOSIPA11USB DMPA12USB DP3.3VVCCGNDGND注意不同型号的SD卡模块引脚定义可能略有不同请以实际模块规格书为准。2. 开发环境搭建在开始编码前需要配置好开发环境安装Keil MDK-ARM开发环境建议V5.25以上版本下载STM32CubeMX图形化配置工具获取STM32F1xx HAL库准备USB Mass Storage设备驱动库推荐工具链版本Keil MDK: 5.25 STM32CubeMX: 6.0.1 STM32F1 HAL库: 1.1.43. 工程配置与初始化使用STM32CubeMX进行基础配置可以大幅减少开发时间。以下是关键配置步骤3.1 USB设备配置在Pinout视图中启用USB设备功能选择Device (FS)模式配置USB速度为Full Speed启用VBUS sensing如果开发板支持3.2 SDIO接口配置启用SDIO外设配置时钟分频为SDIO_CK24MHz设置总线宽度为4位模式启用DMA传输以提高性能3.3 时钟树配置确保系统时钟设置为72MHzUSB时钟为48MHz// 时钟配置示例 RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; // 启用HSE振荡器 RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; HAL_RCC_OscConfig(RCC_OscInitStruct); // 配置系统时钟 RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2);4. USB Mass Storage实现USB Mass Storage设备的核心是实现SCSI协议命令处理。以下是关键代码实现4.1 描述符定义USB设备需要通过描述符向主机报告自身能力/* 设备描述符 */ const uint8_t MSD_DeviceDescriptor[MSD_SIZ_DEVICE_DESC] { 0x12, /* bLength */ 0x01, /* bDescriptorType (Device) */ 0x00, /* bcdUSB (LSB) */ 0x02, /* bcdUSB (MSB) */ 0x00, /* bDeviceClass */ 0x00, /* bDeviceSubClass */ 0x00, /* bDeviceProtocol */ 0x40, /* bMaxPacketSize0 (64 bytes) */ 0x83, /* idVendor (LSB) */ 0x04, /* idVendor (MSB) */ 0x20, /* idProduct (LSB) */ 0x57, /* idProduct (MSB) */ 0x00, /* bcdDevice (LSB) */ 0x02, /* bcdDevice (MSB) */ 0x01, /* iManufacturer */ 0x02, /* iProduct */ 0x03, /* iSerialNumber */ 0x01 /* bNumConfigurations */ }; /* 配置描述符 */ const uint8_t MSD_ConfigDescriptor[MSD_SIZ_CONFIG_DESC] { /* 配置描述符 */ 0x09, /* bLength: Configuration Descriptor size */ 0x02, /* bDescriptorType: Configuration */ MSD_SIZ_CONFIG_DESC, /* wTotalLength (LSB) */ 0x00, /* wTotalLength (MSB) */ 0x01, /* bNumInterfaces */ 0x01, /* bConfigurationValue */ 0x00, /* iConfiguration */ 0xC0, /* bmAttributes: Self Powered */ 0x32, /* MaxPower 100 mA */ /* 接口描述符 */ 0x09, /* bLength: Interface Descriptor size */ 0x04, /* bDescriptorType: Interface */ 0x00, /* bInterfaceNumber */ 0x00, /* bAlternateSetting */ 0x02, /* bNumEndpoints */ 0x08, /* bInterfaceClass: MASS STORAGE */ 0x06, /* bInterfaceSubClass: SCSI */ 0x50, /* bInterfaceProtocol: Bulk Only */ 0x00, /* iInterface */ /* 端点描述符 (IN) */ 0x07, /* bLength: Endpoint Descriptor size */ 0x05, /* bDescriptorType: Endpoint */ 0x81, /* bEndpointAddress: IN endpoint 1 */ 0x02, /* bmAttributes: Bulk */ 0x40, /* wMaxPacketSize: 64 bytes */ 0x00, 0x00, /* bInterval */ /* 端点描述符 (OUT) */ 0x07, /* bLength: Endpoint Descriptor size */ 0x05, /* bDescriptorType: Endpoint */ 0x02, /* bEndpointAddress: OUT endpoint 2 */ 0x02, /* bmAttributes: Bulk */ 0x40, /* wMaxPacketSize: 64 bytes */ 0x00, 0x00 /* bInterval */ };4.2 SCSI命令处理SCSI命令处理是Mass Storage设备的核心功能int8_t SCSI_ProcessCmd(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *cmd) { switch(cmd[0]) { case SCSI_TEST_UNIT_READY: return SCSI_TestUnitReady(pdev, lun, cmd); case SCSI_REQUEST_SENSE: return SCSI_RequestSense(pdev, lun, cmd); case SCSI_INQUIRY: return SCSI_Inquiry(pdev, lun, cmd); case SCSI_START_STOP_UNIT: return SCSI_StartStopUnit(pdev, lun, cmd); case SCSI_ALLOW_MEDIUM_REMOVAL: return SCSI_AllowMediumRemoval(pdev, lun, cmd); case SCSI_MODE_SENSE6: return SCSI_ModeSense6(pdev, lun, cmd); case SCSI_MODE_SENSE10: return SCSI_ModeSense10(pdev, lun, cmd); case SCSI_READ_FORMAT_CAPACITIES: return SCSI_ReadFormatCapacity(pdev, lun, cmd); case SCSI_READ_CAPACITY10: return SCSI_ReadCapacity10(pdev, lun, cmd); case SCSI_READ10: return SCSI_Read10(pdev, lun, cmd); case SCSI_WRITE10: return SCSI_Write10(pdev, lun, cmd); default: return -1; } }4.3 SD卡读写实现SD卡读写需要通过SDIO接口实现/* SD卡初始化 */ uint8_t SD_Init(void) { uint8_t sd_state MSD_OK; /* 初始化SDIO外设 */ SDIO_Init(); /* 识别SD卡 */ if(SD_Detect() ! SD_OK) { return MSD_ERROR; } /* 配置SDIO时钟 */ SDIO_Clock_Set(SDIO_TRANSFER_CLK_DIV); /* 获取CID寄存器内容 */ if(SD_GetCIDRegister(CID) ! SD_OK) { return MSD_ERROR; } /* 获取CSD寄存器内容 */ if(SD_GetCSDRegister(CSD) ! SD_OK) { return MSD_ERROR; } /* 获取卡容量 */ CardCapacity (CSD.DeviceSize 1) * 1024 * 1024; return sd_state; } /* SD卡读函数 */ uint8_t SD_ReadBlocks(uint32_t *pBuffer, uint32_t ReadAddr, uint32_t BlockSize, uint32_t NumOfBlocks) { uint8_t sd_state MSD_OK; if(SDIO_ReadBlocks(pBuffer, ReadAddr, BlockSize, NumOfBlocks) ! SD_OK) { sd_state MSD_ERROR; } /* 等待传输完成 */ if(SDIO_WaitReadOperation() ! SD_OK) { sd_state MSD_ERROR; } return sd_state; } /* SD卡写函数 */ uint8_t SD_WriteBlocks(uint32_t *pBuffer, uint32_t WriteAddr, uint32_t BlockSize, uint32_t NumOfBlocks) { uint8_t sd_state MSD_OK; if(SDIO_WriteBlocks(pBuffer, WriteAddr, BlockSize, NumOfBlocks) ! SD_OK) { sd_state MSD_ERROR; } /* 等待传输完成 */ if(SDIO_WaitWriteOperation() ! SD_OK) { sd_state MSD_ERROR; } return sd_state; }5. 完整代码实现与测试将所有模块整合后主函数实现如下#include stm32f1xx_hal.h #include usbd_core.h #include usbd_desc.h #include usbd_msc.h #include sdio_sd.h /* USB设备全局变量 */ USBD_HandleTypeDef hUsbDeviceFS; int main(void) { /* HAL库初始化 */ HAL_Init(); /* 系统时钟配置 */ SystemClock_Config(); /* SD卡初始化 */ if(SD_Init() ! MSD_OK) { Error_Handler(); } /* USB设备初始化 */ USBD_Init(hUsbDeviceFS, FS_Desc, DEVICE_FS); /* 添加Mass Storage类 */ USBD_RegisterClass(hUsbDeviceFS, USBD_MSC); /* 添加存储接口 */ USBD_MSC_RegisterStorage(hUsbDeviceFS, USBD_Storage_Interface_fops); /* 启动USB设备 */ USBD_Start(hUsbDeviceFS); while (1) { /* 主循环中可以添加其他任务 */ } }测试步骤编译并下载程序到STM32开发板插入SD卡已格式化为FAT32文件系统通过USB线连接开发板和PCPC应自动识别到新硬件并安装驱动在我的电脑中可以看到新的可移动磁盘尝试读写文件验证功能提示如果PC无法识别设备可以检查以下几点USB连接线是否正常SD卡是否已正确插入并初始化开发板供电是否稳定USB描述符配置是否正确6. 性能优化与扩展基础功能实现后可以考虑以下优化方向6.1 提高传输速度使用DMA传输减少CPU开销优化SDIO时钟配置实现多块读写减少命令开销// DMA配置示例 hdma_sdio.Init.PeriphInc DMA_PINC_DISABLE; hdma_sdio.Init.MemInc DMA_MINC_ENABLE; hdma_sdio.Init.PeriphDataAlignment DMA_PDATAALIGN_WORD; hdma_sdio.Init.MemDataAlignment DMA_MDATAALIGN_WORD; hdma_sdio.Init.Mode DMA_PFCTRL; hdma_sdio.Init.Priority DMA_PRIORITY_HIGH; hdma_sdio.Init.FIFOMode DMA_FIFOMODE_ENABLE; hdma_sdio.Init.FIFOThreshold DMA_FIFO_THRESHOLD_FULL; hdma_sdio.Init.MemBurst DMA_MBURST_INC4; hdma_sdio.Init.PeriphBurst DMA_PBURST_INC4;6.2 支持多种存储介质通过抽象存储接口可以方便地支持不同类型的存储设备typedef struct { uint8_t (*Init)(uint8_t lun); uint8_t (*GetCapacity)(uint8_t lun, uint32_t *block_num, uint16_t *block_size); uint8_t (*IsReady)(uint8_t lun); uint8_t (*IsWriteProtected)(uint8_t lun); uint8_t (*Read)(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len); uint8_t (*Write)(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len); uint8_t (*GetMaxLun)(void); } USBD_StorageTypeDef;6.3 添加写保护功能通过GPIO检测写保护开关状态并在SCSI命令中返回uint8_t SCSI_CheckWriteProtect(uint8_t lun) { if(HAL_GPIO_ReadPin(WP_GPIO_Port, WP_Pin) GPIO_PIN_SET) { return 1; // 写保护 } return 0; // 可写 }7. 常见问题解决在实际开发中可能会遇到以下问题问题1PC无法识别设备检查USB数据线是否支持数据传输确认USB DP(D)引脚上有1.5kΩ上拉电阻验证USB描述符配置是否正确检查设备枚举过程是否完整问题2SD卡无法识别确认SD卡已正确插入卡槽检查SDIO接口接线是否正确验证SD卡是否已格式化为FAT32测试SD卡在其他设备上是否工作正常问题3传输速度慢优化SDIO时钟配置最高可达24MHz启用DMA传输减少CPU开销使用多块读写命令减少命令开销检查PCB布线质量确保信号完整性问题4文件系统错误确保正确实现了SCSI命令集验证读写地址是否正确对齐检查块大小设置是否正确通常为512字节考虑添加异常处理机制8. 项目扩展思路基于这个基础项目可以进一步扩展以下功能多分区支持通过修改SCSI命令处理实现单个SD卡显示为多个逻辑驱动器加密存储在读写操作中添加加密/解密层保护数据安全自动备份当设备连接到PC时自动备份指定文件状态指示添加LED指示灯显示读写状态功耗优化实现USB挂起模式下的低功耗运行// 低功耗示例 void USB_SuspendMode(void) { /* 进入低功耗模式 */ HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); /* 唤醒后重新初始化USB */ SystemClock_Config(); USBD_Start(hUsbDeviceFS); }通过本文介绍的方法你可以将STM32F103开发板和SD卡组合成一个实用的USB存储设备。这个项目不仅可以帮助理解USB Mass Storage协议也为后续开发更复杂的USB设备打下了基础。