本程序编写基于秉火霸道STM32F103ZET6运行环境。跑这个实验之前吃了一些亏,让我一一道来!
1、软件写好了,没发现插入USB线连接到电脑后USB居然没有枚举
解决方法:
野火的这款开发板上做了一个USB上电使能IO,也就是说,当PD3为低电平时,USB才能正常工作,如果不去使能这个管脚的话,USB自然就不工作了。
2、HAL库读写SD卡API版本问题
解决方法:
我用的是1.8.0的HAL库,这个库和老版本的HAL库在API上有重大的变更,接口的参数也不一样,含义也有区别。
老版本HAL库读写SD卡的接口
我们来看下HAL库旧版本的读SD卡接口
/** * @brief Reads block(s) from a specified address in a card. The Data transfer * is managed by polling mode. * @param hsd: SD handle * @param pReadBuffer: pointer to the buffer that will contain the received data * @param ReadAddr: Address from where data is to be read * @param BlockSize: SD card Data block size (in bytes) * This parameter should be 512 * @param NumberOfBlocks: Number of SD blocks to read * @retval SD Card error state */ HAL_SD_ErrorTypedef HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint32_t *pReadBuffer, uint64_t ReadAddr, uint32_t BlockSize, uint32_t NumberOfBlocks)
我们看到,这里的BlockSize是以字节为单位进行读的,最小应当是512,因为SD卡一个块的大小是512,所以,NumberOfBlocks表示块数,读一块,那么就是BlockSize512,读N块,那么就是BlockSize512*N。
再来看看老版本HAL库的写SD卡接口
/** * @brief Allows to write block(s) to a specified address in a card. The Data * transfer is managed by polling mode. * @param hsd: SD handle * @param pWriteBuffer: pointer to the buffer that will contain the data to transmit * @param WriteAddr: Address from where data is to be written * @param BlockSize: SD card Data block size (in bytes) * This parameter should be 512. * @param NumberOfBlocks: Number of SD blocks to write * @retval SD Card error state */ HAL_SD_ErrorTypedef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint32_t *pWriteBuffer, uint64_t WriteAddr, uint32_t BlockSize, uint32_t NumberOfBlocks)
写也是一样的,这里的BlockSize是以字节为单位进行读的,最小应当是512,因为SD卡一个块的大小是512,所以,NumberOfBlocks表示块数,写一块,那么就是BlockSize512,写N块,那么就是BlockSize512*N。
新版本HAL库读写SD卡的接口
然而在最新的HAL库上,是不用乘以512的,我们来看一下1.8.0版本HAL库关于这两个函数的描述:
/** * @brief Reads block(s) from a specified address in a card. The Data transfer * is managed by polling mode. * @note This API should be followed by a check on the card state through * HAL_SD_GetCardState(). * @param hsd: Pointer to SD handle * @param pData: pointer to the buffer that will contain the received data * @param BlockAdd: Block Address from where data is to be read * @param NumberOfBlocks: Number of SD blocks to read * @param Timeout: Specify timeout value * @retval HAL status */ HAL_StatusTypeDef HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)
我们看到,这里传入的BlockAdd,也就是块的起始地址,NumberOfBlocks表示的是多少块,所以本来就是以块为单位进行读的,所以也就不用去乘512。
/** * @brief Allows to write block(s) to a specified address in a card. The Data * transfer is managed by polling mode. * @note This API should be followed by a check on the card state through * HAL_SD_GetCardState(). * @param hsd: Pointer to SD handle * @param pData: pointer to the buffer that will contain the data to transmit * @param BlockAdd: Block Address where data will be written * @param NumberOfBlocks: Number of SD blocks to write * @param Timeout: Specify timeout value * @retval HAL status */ HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)
写SD卡的接口也是一样的,这里传入的BlockAdd,也就是块的起始地址,NumberOfBlocks表示的是多少块,所以本来就是以块为单位进行写的,所以也就不用去乘512。
所以,在实现USB大容量存储设备接口的时候,我应该这么来实现:
/** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 6 */ if(HAL_OK != HAL_SD_ReadBlocks(&hsd,(uint8_t *)buf, blk_addr , blk_len, 1000)) return USBD_FAIL ; return (USBD_OK); /* USER CODE END 6 */ } /** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 7 */ if(HAL_OK != HAL_SD_WriteBlocks(&hsd, (uint8_t *)buf, blk_addr , blk_len, 1000)) return USBD_FAIL ; return (USBD_OK); /* USER CODE END 7 */ }
好,这两个问题解决了,一下来看看这么做的。
一、USB使能管脚配置
PD3管脚,默认输出电平为低电平,也就是D+管脚为使能状态,USB供上了电。
PB1是我拿来做调试的灯。
二、RCC时钟
这里要切记一点,因为这里我们用到了USB,USB的时钟要配置为48MHz,具体看手册。而SDIO是时钟是HCLK的二分频。
三、调试接口
这里选择串行调试。
四、SDIO配置
这里,我们配置模式为4位宽总线的SD卡模式,时钟分频因子之前在步骤二中我们已经知道了,SDIO的时钟频率是HCLK的二分频,所以SDIOCLK clock divide factor这个选项我们设置为2。
开启SDIO全局中断。
五、USB配置
这里将USB设备配置为大容量存储,其余默认即可。
六、生成并添加代码逻辑
这里我们把栈的大小稍微调大一点,以便我们后期在代码里进行测试。
在usbd_storage_if.c中实现如下接口:
STORAGE_GetCapacity_FS 获取U盘容量信息 STORAGE_IsReady_FS 获取U盘状态 STORAGE_Read_FS 读U盘 STORAGE_Write_FS 写U盘
通过接口获取有多少块以及块的大小。
/** * @brief . * @param lun: . * @param block_num: . * @param block_size: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size) { /* USER CODE BEGIN 3 */ *block_num = hsd.SdCard.BlockNbr ; *block_size = hsd.SdCard.BlockSize ; return (USBD_OK); /* USER CODE END 3 */ }
判断SD卡的状态是否已经准备好了,状态的描述如下:
typedef enum { HAL_SD_STATE_RESET = ((uint32_t)0x00000000U), /*!< SD not yet initialized or disabled */ HAL_SD_STATE_READY = ((uint32_t)0x00000001U), /*!< SD initialized and ready for use */ HAL_SD_STATE_TIMEOUT = ((uint32_t)0x00000002U), /*!< SD Timeout state */ HAL_SD_STATE_BUSY = ((uint32_t)0x00000003U), /*!< SD process ongoing */ HAL_SD_STATE_PROGRAMMING = ((uint32_t)0x00000004U), /*!< SD Programming State */ HAL_SD_STATE_RECEIVING = ((uint32_t)0x00000005U), /*!< SD Receiving State */ HAL_SD_STATE_TRANSFER = ((uint32_t)0x00000006U), /*!< SD Transfert State */ HAL_SD_STATE_ERROR = ((uint32_t)0x0000000FU) /*!< SD is in error state */ }HAL_SD_StateTypeDef; /** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_IsReady_FS(uint8_t lun) { /* USER CODE BEGIN 4 */ uint8_t state = 0; state = HAL_SD_GetState(&hsd) ; if(HAL_SD_STATE_READY != state) return USBD_FAIL ; return (USBD_OK); /* USER CODE END 4 */ }
实现USB读写SD卡
/** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 6 */ if(HAL_OK != HAL_SD_ReadBlocks(&hsd,(uint8_t *)buf, blk_addr , blk_len, 1000)) return USBD_FAIL ; return (USBD_OK); /* USER CODE END 6 */ } /** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 7 */ if(HAL_OK != HAL_SD_WriteBlocks(&hsd, (uint8_t *)buf, blk_addr , blk_len, 1000)) return USBD_FAIL ; return (USBD_OK); /* USER CODE END 7 */ }
在主函数的while循环里添加调试闪烁灯,标记当前系统是处于一个正常的运行状态。
/** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SDIO_SD_Init(); MX_USART2_UART_Init(); MX_USB_DEVICE_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ //LED调试灯以500ms的频率进行翻转 HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin); HAL_Delay(1000); } /* USER CODE END 3 */ }
七、运行结果
将USB线插入USB DEVICE的接口,并连接到PC端的USB口,在PC端弹出可移动磁盘,实验成功。
后期我们可以再移植一个fatfs文件系统,使之支持文件操作。
我用的是4GB的内存卡,PC端显示3.68GB,为什么呢?
度娘一下:https://zhidao.baidu.com/question/74222131.html
你电脑的算法是1024MB=1GB U盘厂家的算法是1000MB=1GB
还要加上法律允许的产品误差,一般厂家会取最小值,不会多给你空间的。
https://zhidao.baidu.com/question/559657182.html
硬件厂商为了计算方便采用的是十进制,也就是满1000字节算1K,满1000K算1M,以此类推。
软件设计上由于计算机采用的是二进制所以是满1024字节即2的10次方算1K,1024K算1M;所以你的卡越大差距就会越大,这点在电脑硬盘上感觉会更明显一些。