stm32 CubeMx 实现SD卡/sd nand FATFS读写测试

news2024/11/28 14:32:10

stm32 CubeMx 实现SD卡/SD nand FATFS读写测试

文章目录

  • stm32 CubeMx 实现SD卡/SD nand FATFS读写测试
    • 1. 前言
    • 2. 环境介绍
      • 2.1 软硬件说明
      • 2.2 外设原理图
    • 3. 工程搭建
      • 3.1 CubeMx 配置
      • 3.2 SDIO时钟配置说明
      • 3.2 读写测试
        • 3.2.1 添加读写测试代码
      • 3.3 FATFS文件操作
        • 3.3.1 修改读写测试代码
      • 3.4 配置问题记录
        • 3.4.1 CubeMx生成代码bug
        • 3.4.2 SD插入检测引脚配置
    • 4. 结束语

1. 前言

SD卡/SD nand是嵌入式开发中常为使用的大容量存储设备,SD nand虽然当前价格比SD卡高,但胜在价格、封装以及稳定性上有优势,实际操作和SD卡没什么区别。

关于 SD卡/SDnand 的驱动,有了CubeMx之后其实基本上都自动生成了对应的驱动了,基本上把驱动配置一下之后,自己写一些应用就可以完成基本的读写了,同时关于FATFS文件系统,也可以直接采用CubeMx配置,也不用自己移植,因此使用STM32开发这些还是比较爽的!不过使用过程中也有一些坑,自动生成的驱动有时候也还是有一些bug,因此还是需要大家对对应驱动有一定的了解。

本文将主要分享关于使用 CubeMx 配置 stm32 的工程,通过SDIO总线完成 SD卡/SD nand 的读写,并配置FATFS,采用文件操作实现对 SD卡/SD nand 的读写操作;此外还将分享博主在调试过程中遇到的一些问题,比如CubeMx自动生成的驱动存在的bug等,以及分享关于驱动部分的代码分析!

2. 环境介绍

2.1 软硬件说明

硬件环境:

  • 主控:stm32f103vet6
  • SD nand: CSNPGCR01-AOW

软件环境:

  • CubeMx版本:Version 6.6.1
  • 注意:当前最新版本 V6.8.0,生成的工程配置存在bug,具体细节在后文描述

2.2 外设原理图

SD卡槽原理图部分如下:
在这里插入图片描述

3. 工程搭建

3.1 CubeMx 配置

  1. 选择芯片,ACCESS TO MCU SELECTOR
    在这里插入图片描述

  2. 搜索对应的芯片型号,在对应列表下方选择对应芯片
    在这里插入图片描述

  3. 配置时钟方案,采用外部高速时钟,无源晶振方案
    在这里插入图片描述

  4. 配置调试器,由于我采用SWD调试接口,因此选择 Serial Wrie 串行总线
    在这里插入图片描述

  5. 配置SDIO外设,由于我们所使用的SD nand支持4线传输,因此此处选择4线宽度;如果你所使用的SD nand或SD卡不支持4线传输,此处应选择1线宽度;支持4线宽度的SD卡肯定可以使用1线宽度,因此如果你实在不知道你的SD卡支持几线宽度,你可以直接选择1线宽度!4线和1线宽度的差别也就在于速度上相差了4倍!
    (注意这里暂时不需要对SDIO的参数进行配置,后面我们再回来配置!)
    在这里插入图片描述

  6. 完成时钟树配置:

    • 配置外部晶振频率
    • 调整时钟选择,SYSCLK由PLL产生,PLL由外部时钟倍频产生
    • 配置SDIO外设时钟,注意此处SDIO外设比较特殊,有两个时钟!具体原因见后文!
      在这里插入图片描述
  7. 修改SDIO参数配置,主要是修改SDIOCLK的分频

    • 由于我们上述配置的SDIO时钟为 72M,而SD卡支持的通讯速率在0MHz至25MHz之间,因此我们需要分频,配置 SDIO Clock divider bypassDisable
    • 此处设置 SDIOCLK clock divide factor CLKDIV分频系数为 8,这个受限于具体的SD卡支持的最大速度。如果设置值较小,可能由于SDIO_CK速度过高,SD卡/SDnand不支持,导致通讯失败,因此建议先将此值设大点(或查看SD卡/SDnand手册,或先设一个较大值,软件完成SD信息读取后再配置)
    • 注意这个配置的时钟是用于SD读写通讯时候的时钟,而不是SD卡信息识别过程时的速度!
      在这里插入图片描述
      在这里插入图片描述
  8. 勾选 FATFS 配置,选择 SD Card
    在这里插入图片描述

  9. 配置SD卡检测引脚,有以下两种方案

    • 方案一:选择一个输入IO,作为触发引脚
      在这里插入图片描述

    • 方案二:不配置输入IO,最后生成代码的时候无视警报即可,生成的代码会自动取消输入检测判断
      在这里插入图片描述

  10. 配置调试串口,用来打印信息,此处我选择USART1,大家可根据自己硬件环境自行选择
    在这里插入图片描述

  11. 配置工程信息

    • 配置工程名
    • 选择工程路径
    • 配置应用程序结构,我习惯选择 Basic 结构
    • 选择IDE工具及版本
    • 修改堆栈大小,适当改大一点,怕不够用
      在这里插入图片描述
  12. 勾选将外设初始化放置在独立的.c.h文件,这样每个外设的初始化是独立的,方便阅读移植! 在这里插入图片描述

  13. 生成代码
    在这里插入图片描述

3.2 SDIO时钟配置说明

在上述CubeMx时钟配置中,外设的时钟一般都是只有一路过去,但是在此处我们会发现SDIO的时钟在时钟树中有两个!没弄清楚还会以为这是CubeMx出现bug了!

在这里插入图片描述

其实这是SDIO外设的特殊点,我们查看数据手册上的时钟树,便可以发现,实际上是真的有两路时钟,分别是:1)SDIOCLK;2)至SDIO的AHB接口;

在这里插入图片描述

之后,我们看到数据手册的SDIO章节,我们可以看到SDIO外设分为:1)AHB总线接口 和 2)SDIO适配器两大块,且使用不同的时钟,这也就是我们在时钟树配置中可以看到有两路时钟配置的原因了!

从下图我们可以知道,SDIO外设不同于其他外设,其外设模块部分与中断、DMA是分开的,并采用不同的时钟!

在这里插入图片描述

关于AHB总线接口及SDIO适配器更多细节,大家可自行阅读参考手册部分章节内容,此处不做赘述。

此外,关于时钟配置有一个特别需要注意的,也就是SDIO_CK时钟信号。SDIO_CK时钟,也就是我们SDIO外设与SD卡/SD nand通讯的CLK时钟,从上图我们可知,SDIO_CK时钟来自SDIO适配器,也就是来自SDIOCLK,对应CubeMX时钟配置中的:
在这里插入图片描述
在这里插入图片描述

3.2 读写测试

3.2.1 添加读写测试代码

  1. 使能 MicroLIB 微库,否则调用 printf 函数会卡住
    在这里插入图片描述

  2. 修改编码规则为 UTF-8,这是由于我们CubeMx中配置的FATFS的编码格式为 UTF-8导致,如果不修改为 UTF-8 则部分中文会乱码! //TODO:确认是由FATFS配置导致
    在这里插入图片描述
    在这里插入图片描述

  3. 添加 printf 重映射 (位置可根据自行决定)

#include <stdio.h>
int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
	return (ch);
}
  1. 添加 sdcard 信息打印函数,查看卡片信息
HAL_SD_CardInfoTypeDef  SDCardInfo;    
void printf_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卡信息
	CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize);	//计算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;
		default:break;
	}	
		
    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));		    //SD卡类别
 	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));				//卡容量
}
  1. 添加初始化及读写测试代码,注意此处我们没有直接使用FATFS的读写接口,我们先测试生成的SD驱动函数接口
int main(void)
{
  /* USER CODE BEGIN 1 */
  BYTE send_buf[512];
  DRESULT ret;
  /* USER CODE END 1 */

  /* ...省略若干自动生成代码... */
  
  /* USER CODE BEGIN 2 */

  SD_Driver.disk_initialize(0);
  printf_sdcard_info();

  printf("\r\n\r\n********** 英文读写测试 **********\r\n");

  ret = SD_Driver.disk_write(0,
(BYTE *)"Life is too short to spend time with people who suck the happiness out of you. \
If someone wants you in their life, they’ll make room for you. You shouldn’t have to fight for a spot. Never, ever\
 insist yourself to someone who continuously overlooks your worth. And remember, it’s not the people that stand by \
your side when you’re at your best, but the ones who stand beside you when you’re at your worst that are your true\
 friends",20,2);
  printf("sd write result:%d\r\n", ret);
  ret = SD_Driver.disk_read(0, send_buf, 20, 2);
  printf("sd reak result:%d\r\n", ret);
  printf("sd read content:\r\n%s\r\n", send_buf);
  
  printf("\r\n\r\n********** 中文读写测试 **********\r\n");

  ret = SD_Driver.disk_write(0,
(BYTE *)"开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!\r\n\
创作不易,转载请注明出处~\r\n\
更多文章敬请关注:爱出名的狗腿子\r\n", 22, 2);
  printf("sd write result:%d\r\n", ret);
  ret = SD_Driver.disk_read(0, send_buf, 22, 2);
  printf("sd reak result:%d\r\n", ret);
  printf("sd read content:\r\n%s\r\n", send_buf);
  /* USER CODE END 2 */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
  1. 修改烧录器配置,配置为烧录后自动运行
  2. 下载测试,这里由于我们采用UTF-8编码,所以使用的串口上位机也需要支持UTF-8解析,我们这里使用Mobaxterm上位机,测试结果如下:
    在这里插入图片描述
  3. main.c 文件全部代码如下,供大家参考:
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "sdio.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

HAL_SD_CardInfoTypeDef  SDCardInfo;    
void printf_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卡信息
	CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize);	//计算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;
		default:break;
	}	
		
    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));		    //SD卡类别
 	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));				//卡容量
}

int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
	return (ch);
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  BYTE send_buf[512];
  DRESULT ret;
  /* 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_USART1_UART_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
  
  SD_Driver.disk_initialize(0);
  printf_sdcard_info();

  printf("\r\n\r\n********** 英文读写测试 **********\r\n");

  ret = SD_Driver.disk_write(0,
(BYTE *)"Life is too short to spend time with people who suck the happiness out of you. \
If someone wants you in their life, they’ll make room for you. You shouldn’t have to fight for a spot. Never, ever\
 insist yourself to someone who continuously overlooks your worth. And remember, it’s not the people that stand by \
your side when you’re at your best, but the ones who stand beside you when you’re at your worst that are your true\
 friends",20,2);
  printf("sd write result:%d\r\n", ret);
  ret = SD_Driver.disk_read(0, send_buf, 20, 2);
  printf("sd reak result:%d\r\n", ret);
  printf("sd read content:\r\n%s\r\n", send_buf);
  
  printf("\r\n\r\n********** 中文读写测试 **********\r\n");

  ret = SD_Driver.disk_write(0,
(BYTE *)"开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!\r\n\
创作不易,转载请注明出处~\r\n\
更多文章敬请关注:爱出名的狗腿子\r\n", 22, 2);
  printf("sd write result:%d\r\n", ret);
  ret = SD_Driver.disk_read(0, send_buf, 22, 2);
  printf("sd reak result:%d\r\n", ret);
  printf("sd read content:\r\n%s\r\n", send_buf);
  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  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;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

3.3 FATFS文件操作

移植了FATFS,当然也就可以只用通用的文件系统操作函数完成文件的读写,通用的文件系统操作API 在 ff.c 文件内,声明在 ff.h 文件内,主要使用的API接口如下:

FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode);				/* Open or create a file */
FRESULT f_close (FIL* fp);											/* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br);			/* Read data from a file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw);	/* Write data to a file */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf);	/* Forward data to the stream */
FRESULT f_lseek (FIL* fp, DWORD ofs);								/* Move file pointer of a file object */
FRESULT f_truncate (FIL* fp);										/* Truncate file */
FRESULT f_sync (FIL* fp);											/* Flush cached data of a writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path);						/* Open a directory */
FRESULT f_closedir (DIR* dp);										/* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno);							/* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern);	/* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno);							/* Find next file */
FRESULT f_mkdir (const TCHAR* path);								/* Create a sub directory */
FRESULT f_unlink (const TCHAR* path);								/* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new);	/* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno);					/* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask);			/* Change attribute of the file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno);			/* Change times-tamp of the file/dir */
FRESULT f_chdir (const TCHAR* path);								/* Change current directory */
FRESULT f_chdrive (const TCHAR* path);								/* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len);							/* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs);	/* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn);	/* Get volume label */
FRESULT f_setlabel (const TCHAR* label);							/* Set volume label */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt);			/* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, BYTE sfd, UINT au);				/* Create a file system on the volume */
FRESULT f_fdisk (BYTE pdrv, const DWORD szt[], void* work);			/* Divide a physical drive into some partitions */
int f_putc (TCHAR c, FIL* fp);										/* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp);								/* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...);						/* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp);						/* Get a string from the file */

关于API的使用此处不做过多赘述,大家可以自行上官网查阅 FATFS官网,或者网上搜索,或直接看下述示例亦可。

3.3.1 修改读写测试代码

修改3.2.1章节所使用的读写测试代码,此处我们直接使用FATFS文件系统的读写函数接口,修改主函数如下,注意需要包含fatfs.h头文件!

#include "fatfs.h"
int main()
{
  /* USER CODE BEGIN 1 */
#define USERPath "0:/"
  
  BYTE write_buf[] = "\r\n\r\n\
hello world!\r\n\
开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!\r\n\
创作不易,转载请注明出处~\r\n\
更多文章敬请关注:爱出名的狗腿子\r\n\r\n\
";
  BYTE read_buf[1024] = {0};
  UINT num;
  FRESULT ret;
  /* USER CODE END 1 */
  
  /* ... 省略初始化代码... */
  
  /* USER CODE BEGIN 2 */
  
  /* 挂载文件系统,挂载的时候会完成对应硬件设备(SD卡/SDnand)初始化 */
  ret = f_mount(&SDFatFS, USERPath, 1);
  if (ret != FR_OK) {
    printf("f_mount error!\r\n");
    goto mount_error;
  } else if(ret == FR_NO_FILESYSTEM) {         /* 检测是否存在文件系统,如果没有则进行格式化 */
    printf("未检测到FATFS文件系统,执行格式化...\r\n");
    ret = f_mkfs(USERPath, 0, 0);
    if(ret == FR_OK) {
      printf("格式化成功!\r\n");
      f_mount(NULL, USERPath, 1); /* 先取消挂载,后重新挂载 */
      ret = f_mount(&SDFatFS, USERPath, 1);
    } else {
      printf("格式化失败!\r\n");
      goto mount_error;
    }
  } else {
    printf("f_mount success!\r\n");
  }
  
  /* 读写测试 */
  printf("\r\n ========== write test ==========\r\n");
  ret = f_open(&SDFile, "hello.txt", FA_CREATE_ALWAYS | FA_WRITE);
  if(ret == FR_OK) {
    printf("open file sucess!\r\n");
    ret = f_write(&SDFile, write_buf, sizeof(write_buf), &num);
    if(ret == FR_OK) {
      printf("write \"%s\" success!\r\nwrite len:%d\r\n", write_buf, num);
    } else {
      printf("write error! ret:%d \r\n", ret);
      goto rw_error;
    }
    f_close(&SDFile);
  } else {
    printf("open file error!\r\n");
    goto rw_error;
  }
	
  printf("\r\n ========== read test ==========\r\n");
  ret = f_open(&SDFile, "hello.txt",FA_OPEN_EXISTING | FA_READ); 	 
  if(ret == FR_OK) {
    printf("open file sucess!\r\n");
	ret = f_read(&SDFile, read_buf, sizeof(read_buf), &num); 
    if(ret == FR_OK) {
      printf("read data:\"%s\"!\r\nread len:%d\r\n", read_buf, num);
    } else {
      printf("read error! ret:%d \r\n", ret);
      goto rw_error;
  }
} else {
   printf("open file error!\r\n");
   goto rw_error;
}

rw_error:
  f_close(&SDFile);	
  
mount_error:
  f_mount(NULL, USERPath, 1);
  /* USER CODE END 2 */
  while (1) {
  
  }
}

#define USERPath "0:/" 表示挂载的位置,这是由于FATFS初始化的时候链接的根目录为 0:/ ,所以挂载的文件系统需要在此目录下,当然也可以是此目录下的路径,如0:/hello,但不能是其他目录,如 1:/
在这里插入图片描述
测试结果如下:
在这里插入图片描述
main.c完整内容如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "sdio.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>
#include "fatfs.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

HAL_SD_CardInfoTypeDef  SDCardInfo;    
void printf_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卡信恿
	CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize);	//计算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;
		default:break;
	}	
		
    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));		    //SD卡类刿
 	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));				//卡容釿
}

int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
	return (ch);
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
#define USERPath "0:/"
  
  BYTE write_buf[] = "\r\n\r\n\
hello world!\r\n\
开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!\r\n\
创作不易,转载请注明出处~\r\n\
更多文章敬请关注:爱出名的狗腿子\r\n\r\n\
";
  BYTE read_buf[1024] = {0};
  UINT num;
  FRESULT ret;
  /* 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_USART1_UART_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
  
  /* 挂载文件系统,挂载的时候会完成对应硬件设备(SD卡/SDnand)初始化 */
  ret = f_mount(&SDFatFS, USERPath, 1);
  if (ret != FR_OK) {
    printf("f_mount error!\r\n");
    goto mount_error;
  } else if(ret == FR_NO_FILESYSTEM) {         /* 检测是否存在文件系统,如果没有则进行格式化 */
    printf("未检测到FATFS文件系统,执行格式化...\r\n");
    ret = f_mkfs(USERPath, 0, 0);
    if(ret == FR_OK) {
      printf("格式化成功!\r\n");
      f_mount(NULL, USERPath, 1); /* 先取消挂载,后重新挂载 */
      ret = f_mount(&SDFatFS, USERPath, 1);
    } else {
      printf("格式化失败!\r\n");
      goto mount_error;
    }
  } else {
    printf("f_mount success!\r\n");
  }
  
  /* 读写测试 */
  printf("\r\n ========== write test ==========\r\n");
  ret = f_open(&SDFile, "hello.txt", FA_CREATE_ALWAYS | FA_WRITE);
  if(ret == FR_OK) {
    printf("open file sucess!\r\n");
    ret = f_write(&SDFile, write_buf, sizeof(write_buf), &num);
    if(ret == FR_OK) {
      printf("write \"%s\" success!\r\nwrite len:%d\r\n", write_buf, num);
    } else {
      printf("write error! ret:%d \r\n", ret);
      goto rw_error;
    }
    f_close(&SDFile);
  } else {
    printf("open file error!\r\n");
    goto rw_error;
  }
	
  printf("\r\n ========== read test ==========\r\n");
	ret = f_open(&SDFile, "hello.txt",FA_OPEN_EXISTING | FA_READ); 	 
	if(ret == FR_OK) {
    printf("open file sucess!\r\n");
		ret = f_read(&SDFile, read_buf, sizeof(read_buf), &num); 
    if(ret == FR_OK) {
      printf("read data:\"%s\"!\r\nread len:%d\r\n", read_buf, num);
    } else {
      printf("read error! ret:%d \r\n", ret);
      goto rw_error;
    }
	} else {
    printf("open file error!\r\n");
    goto rw_error;
	}

rw_error:
	f_close(&SDFile);	
  
mount_error:
	f_mount(NULL, USERPath, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  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;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

3.4 配置问题记录

3.4.1 CubeMx生成代码bug

测试发现,使用CubeMx当前最新版本:V6.8.0版本,生成代码会存在以下问题:

  • SD卡/SDnand 卡片信息读取成功,但是读写测试失败

经过仔细分析代码后发现,出现的问题在 MX_SDIO_SD_Init() 此初始化函数内的配置项错误导致,具体分析如下:

  1. 我们在CubeMx里面配置的时候选择的是4线宽度模式 SD 4bit Wide bus

  2. v6.8.0版本CubeMx生成的 MX_SDIO_SD_Init() SD初始化函数内,hsd.Init.BusWide = SDIO_BUS_WIDE_4B;

  3. 看上去没有什么问题,配置4线模式,对应的初始化项也使用4线模式,但是不然,我们继续分析 MX_SDIO_SD_Init() 此初始配置的调用

  4. MX_SDIO_SD_Init() 此函数在main函数内初始化的时候调用,此函数只配置了 hsd 结构体,并未配置给SDIO硬件寄存器

  5. 之后调用 SD_Driver.disk_initialize(0); 函数的时候才真正开始进行SDIO外设配置
    BSP_SD_Init()
      ->HAL_SD_Init()
        ->HAL_SD_InitCard()

    HAL_SD_InitCard() 函数内使用Init结构体配置SDIO外设,总线宽度1bit,时钟速度<400k,以进行卡片的初始化识别。

          -> SD_InitCard()
            -> SDIO_Init(hsd->Instance, hsd->Init)
          -> SDMMC_CmdBlockLength(hsd->Instance, BLOCKSIZE)

    · 在 SD_InitCard() 函数内实现SD卡的初始化识别,之后调用 SDIO_Init()MX_SDIO_SD_Init() 内对 hsd 的配置配置给SDIO外设,此处的作用主要是提升SDIO外设时钟速率为我们配置的速率;
    · v6.8.0版本的代码此时hsd.Init.BusWide = SDIO_BUS_WIDE_4B; ,因此v6.8.0版本代码后续SDIO外设使用4线通讯;
    · 之后调用 SDMMC_CmdBlockLength() 设置块大小,由于SDIO外设已切换到4线模式,而SD卡/SDnand此时仍然处于1线模式,因此配置会出错

       -> HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B)

    根据前面获取到的SD卡SCR寄存器值,判断是否支持4线模式,如果支持则发送配置命令通知SD卡/SDnand进入4线模式,之后修改SDIO外设总线宽度为4线模式

  6. 通过以上分析可知,MX_SDIO_SD_Init() 函数内对 hsd.Init.BusWide = SDIO_BUS_WIDE_4B; 的配置会导致对SD卡块大小的配置失败,从而导致后续读写时失败,报错为块大小设置失败!

  7. 综上,针对当前最新版本 V6.8.0 版本CubeMx的处理方法是:手动修改此 hsd.Init.BusWide 配置为 SDIO_BUS_WIDE_1B 或更换低版本CubeMx,本人更换V6.6.1版本后无此bug。

3.4.2 SD插入检测引脚配置

使用CubeMx配置FATFS 选择 SD Card 之后,有一个配置参数,用来配置SD Card的输入检测引脚。如果我们在硬件上有设计SD卡的卡槽插入检测引脚插入连接到了MCU的IO,则可配置对应IO为输入模式,并设置对应IO为输入检测引脚,比如,我们设置PD12为输入检测引脚,则配置如下:
在这里插入图片描述

对应代码如下,输入检测 IO 低电平有效!
在这里插入图片描述
在这里插入图片描述

如果硬件上,没有此插入检测引脚,则可以在CubeMx内不进行配置,只是在生成代码的时候会提示警报而已,可以不用关心,生成的代码项会自动屏蔽插入检测!
在这里插入图片描述
在这里插入图片描述

4. 结束语

  • 以上便是本文的全部内容了,欢迎大家评论区留言讨论!

  • 关于SD卡/SD nand的其他有关技术文章,推荐大家关注:SD nand专栏(点击跳转!!!)

  • 使用CubeMx虽然能帮助我们快速生成驱动,但是对于SD卡/SD nand的驱动流程,我们还是需要有清晰的认识,推荐阅读: SD Nand 与 SD卡 SDIO模式应用流程(点击跳转!!!)


开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!

创作不易,转载请注明出处~

更多文章敬请关注:爱出名的狗腿子(点击跳转!!!)


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/474386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云计算:数字化转型的利器

随着数字化转型的加速&#xff0c;企业对于信息技术应用的需求越来越大&#xff0c;而云计算作为一种新的基础设施&#xff0c;也逐渐成为了许多企业的首选。那么&#xff0c;云计算究竟有哪些优势&#xff1f;未来发展趋势又是怎样的呢&#xff1f;下面就让我们一起来探讨一下…

深入理解try...catch(字节码层面)

我们工作中常用try...catch来解决程序中出现的异常情况&#xff0c;但是你真的了解它的实现原理吗&#xff1f;今天我就带着大家从字节码层面理解try...catch 一、准备工作 我们首先需要准备好异常类和对应的测试类方便我们观察。 异常类&#xff1a; public class DivideB…

1.软件测试

目录 一、面试重点 1.什么是软件测试&#xff1f; 2.软件测试和软件开发的区别 3.你为什么选择软件测试&#xff1f; 4.什么是需求&#xff1f; 5.软件测试人员如何深入了解需求&#xff1f; 6.什么是内存泄露&#xff1f; 7.什么是测试用例&#xff1f; 8.测试用例有…

【23】linux进阶——linux的软链接和硬链接

大家好&#xff0c;这里是天亮之前ict&#xff0c;本人网络工程大三在读小学生&#xff0c;拥有锐捷的ie和红帽的ce认证。每天更新一个linux进阶的小知识&#xff0c;希望能提高自己的技术的同时&#xff0c;也可以帮助到大家 另外其它专栏请关注&#xff1a; 锐捷数通实验&…

终于成功了,CCED2000后,中文编程软件再次脱颖而出,系出金山

WPS抗衡微软&#xff0c;CCEDE却被淹没&#xff1f; DOS代&#xff0c;我们用WPS来进行文字编辑&#xff0c;CCED来做表格&#xff0c;两者在那个时代可以称得上是国产办公领域的“必装软件”。 如今&#xff0c;30年过去了&#xff0c;WPS一步一步成长为抗衡微软office的国产…

electron入门 | 手把手带electron项目初始化

Electron是一个基于Chromium和 Node.js&#xff0c;可以使用 HTML、CSS和JavaScript构建跨平台应用的技术框架&#xff0c;兼容 Mac、Windows 和 Linux。 目录 1.了解electron 2.开发环境 3.初始化 采坑插曲&#xff1a; 1.了解electron Electron 可以让你使用纯 JavaScrip…

easyexcel读取excel合并单元格数据

普通的excel列表&#xff0c;easyexcel读取是没有什么问题的。但是&#xff0c;如果有合并单元格&#xff0c;那么它读取的时候&#xff0c;能获取数据&#xff0c;但是数据是不完整的。如下所示的单元格数据&#xff1a; 我们通过简单的异步读取&#xff0c;最后查看数据内容&…

symfonos 2

目录 扫描 SMB SSH 提权 扫描 由于端口80是打开的,我们试图在浏览器中打开IP地址,但在网页上没有找到任何有用的信息。我们还尝试了dirb和其他目录暴力工具,但没有找到任何东西。 SMB 为了进一步枚举,我们使用Enum4Linux工具并找到了一些有用的信息。我们发现了一个名…

Microelectronic学习章节总结(1)-- 计算机架构复习

文章目录 Part1. 处理器架构&#xff0c;以及流水线的实现方法part2 DLX架构part3 ULTRA SPARC T2架构part4 PENTIUM 4架构part5 不同架构之间的性能比较 PPT&#xff1a;2&#xff0c;4&#xff0c;5&#xff0c;6 这一章主要对之前的计算机架构一些知识进行复习&#xff0c;因…

数字中国建设峰会|大模型带来产业智能化新机遇

第六届数字中国建设峰会在福建省福州市举办。峰会期间&#xff0c;百度与福州市政府签署战略协议&#xff0c;将基于文心一言为代表的大模型和百度智能云通用AI能力深入合作。未来&#xff0c;双方将聚焦算力产业&#xff0c;共建百度智能云&#xff08;福州&#xff09;智算中…

移动推车定位查找方案

CK_Label_v24 产品型号 CK_Label_v24 尺寸 124x90x12mm&#xff08;不含安装支架&#xff09; 屏幕尺寸 4.2 inch 显示技术 电子墨水屏显示 显示区域面积 (mm) 84.8(H) x 63.6(V) 分辨率 400*300 像素密度 120dpi 显示颜色 黑/白 外观颜色 白色&灰外圏…

Mozilla 自身是如何模糊 Firefox 浏览器的?

导语&#xff1a;Mozilla一直在模糊Firefox及其底层组件&#xff0c;它已被证明是识别质量和安全漏洞的最有效方法之一。 Mozilla一直在模糊Firefox及其底层组件&#xff0c;它已被证明是识别质量和安全漏洞的最有效方法之一。通常&#xff0c;研究人员会在不同级别上应用模糊测…

二叉树的遍历及相关衍生

二叉树的遍历及相关衍生 前言二叉树的遍历建树二叉树的遍历遍历的分类代码部分 遍历根的应用打印树中的每个数据代码部分 遍历计算树节点个数代码部分 计算二叉树的深度思路代码部分 第k层个数 结束 前言 如标题所示&#xff0c;在这里我们要研究的是二叉树的遍历。 为什么不…

java获取星期几

如果你要问 java什么时候学习比较好&#xff0c;那么答案肯定是 java的星期几。 在 Java中&#xff0c;你可以使用 public static void main &#xff08;&#xff09;方法来获取一个类的所有成员变量&#xff0c;然后在所有类中调用这个方法来获取对象的所有成员变量。它能以对…

MCSM面板一键搭建我的世界服务器-外网远程联机【内网穿透】

文章目录 前言1.Mcsmanager安装2.创建Minecraft服务器3.本地测试联机4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射内网端口 5.远程联机测试6. 配置固定远程联机端口地址6.1 保留一个固定TCP地址6.2 配置固定TCP地址 7. 使用固定公网地址远程联机 转载自远程穿透文章&…

【C++】模拟实现map和set

1.关联式容器 关联式容器也是用来存储数据的&#xff0c;与序列式容器不同的是&#xff0c;其里面存储的是结构的 键值对&#xff0c;在数据检索时比序列式容器效率更高。 2 .键值对 用来表示具有一一对应关系的一种结构&#xff0c;该结构中一般只包含两个成员变量key和val…

2023-04-16 学习记录--C/C++-邂逅C/C++(上)

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、固定格式 ⭐️ stdio的理解: abbr.标准输入输出&#xff08;standard input/output&#xff09;。 #include <stdio.h>…

【LeetCode:72. 编辑距离 | 暴力递归=>记忆化搜索=>动态规划 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【Linux篇】Shell脚本语法

前言 在安卓源码里&#xff0c;离不开两个东西&#xff0c;一个就是.sh文件&#xff0c;还有一个就是.mk文件。 这两个文件各持有一个语法&#xff0c;一个是Makefile语法&#xff0c;一个是Shell脚本语法。 这两个是真的让我头疼&#xff0c;就像看天书一样&#xff0c;呜呜…

Mysql为json字段创建索引的两种方式

目录 一、前言二、通过虚拟列添加索引&#xff08;Secondary Indexes and Generated Columns&#xff09;三、多值索引&#xff08;Using multi-valued Indexes&#xff09;四、官网地址 一、前言 JSON 数据类型是在mysql5.7版本后新增的&#xff0c;同 TEXT&#xff0c;BLOB …