stm32cubeMX学习、SD卡虚拟U盘实验

简介: stm32cubeMX学习、SD卡虚拟U盘实验

本程序编写基于秉火霸道STM32F103ZET6运行环境。跑这个实验之前吃了一些亏,让我一一道来!

640.jpg

1、软件写好了,没发现插入USB线连接到电脑后USB居然没有枚举

解决方法:

640.png

野火的这款开发板上做了一个USB上电使能IO,也就是说,当PD3为低电平时,USB才能正常工作,如果不去使能这个管脚的话,USB自然就不工作了。

2、HAL库读写SD卡API版本问题

解决方法:


我用的是1.8.0的HAL库,这个库和老版本的HAL库在API上有重大的变更,接口的参数也不一样,含义也有区别。

640.png

老版本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使能管脚配置

640.png

640.png

PD3管脚,默认输出电平为低电平,也就是D+管脚为使能状态,USB供上了电。


PB1是我拿来做调试的灯。

二、RCC时钟

640.png

640.png

这里要切记一点,因为这里我们用到了USB,USB的时钟要配置为48MHz,具体看手册。而SDIO是时钟是HCLK的二分频。

三、调试接口

640.png

这里选择串行调试。

四、SDIO配置

640.png

这里,我们配置模式为4位宽总线的SD卡模式,时钟分频因子之前在步骤二中我们已经知道了,SDIO的时钟频率是HCLK的二分频,所以SDIOCLK clock divide factor这个选项我们设置为2。

640.png

开启SDIO全局中断。

640.png

640.png

五、USB配置

640.png

640.png

这里将USB设备配置为大容量存储,其余默认即可。

六、生成并添加代码逻辑

640.png

这里我们把栈的大小稍微调大一点,以便我们后期在代码里进行测试。


在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文件系统,使之支持文件操作。

640.jpg

640.png

我用的是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;所以你的卡越大差距就会越大,这点在电脑硬盘上感觉会更明显一些。

往期精彩

嵌入式系统软件架构设计(长篇深度好文)


专为MCU项目开发提速的代码框架BabyOS


嵌入式C语言代码优化方案(深度好文,建议花时间研读并收藏)


分享一个在Keil开发环境中配置代码格式化工具Astyle(美化代码风格)


stm32cubeMX学习、USB DFU(Download Firmware Update)固件更新

目录
相关文章
|
4月前
stm32f407探索者开发板(十九)——外部中断实验-EXIT
stm32f407探索者开发板(十九)——外部中断实验-EXIT
252 0
|
30天前
stm32学习 3-2 LED流水灯
stm32学习 3-2 LED流水灯
61 4
|
30天前
stm32学习3-1 LED闪烁
stm32学习3-1 LED闪烁
32 4
|
5月前
|
存储 数据采集 数据安全/隐私保护
使用STM32F103读取TF卡并模拟U盘:使用标准库实现
通过以上步骤,你可以实现用STM32F103将TF卡内容变成U盘进行读取。这种功能在数据采集、便携式存储设备等应用中非常有用。如果你有更多的需求,可以进一步扩展此项目,例如添加文件管理功能、加密存储等。希望这篇博客能帮到你,如果有任何问题,欢迎在评论区留言讨论!
201 1
|
4月前
stm32f407探索者开发板(二十三)——定时器中断实验
stm32f407探索者开发板(二十三)——定时器中断实验
295 0
|
4月前
|
芯片
stm32f407探索者开发板(二十)——独立看门狗实验
stm32f407探索者开发板(二十)——独立看门狗实验
272 0
|
4月前
|
监控
stm32f407探索者开发板(十八)——串口通信实验讲解(USART_RX_STA流程图详解)
stm32f407探索者开发板(十八)——串口通信实验讲解(USART_RX_STA流程图详解)
219 0
|
4月前
stm32f407探索者开发板(八)——按键输入实验--GPIO做输入
stm32f407探索者开发板(八)——按键输入实验--GPIO做输入
|
6月前
|
IDE 编译器 开发工具
学习STM32,该用哪款开发工具?
学习STM32,该用哪款开发工具?
128 1
|
6月前
stm32f4外设学习篇(代码集合)(三)
stm32f4外设学习篇(代码集合)
113 0