【STM32】文件系统FATFS与Flash的初步使用

news2024/10/5 22:23:40

文件系统简介

简介可以不看,直接看移植步骤
在这里插入图片描述
文件系统是介于应用层和底层间的模糊层。底层提供API,比如说使用SDIO或者SPI等读写一个字节。文件系统把这些API组合包装起来,并且提供一些列函数,我们可以使用这些函数进行更进一步的对存储设备的操作。

底层:操作单片机外设,读写。需要我们进行配置。
中层:中间层 FATFS 模块,实现了 FAT 文件读/写协议。一般不管
顶层:文件系统提供给我们的函数库,我们就是要使用这些。
FATFS的实现过程(白色框中的内容是需要我们自己实现的也就是底层设备的输入输出以及最高层的用户应用程序,蓝色框由fatfs提供)
在这里插入图片描述

源码获取

FATFS 的源码及英文详述,大家可以在:http://elm-chan.org/fsw/ff/00index_e.html 这个网站下载。下载解压之后的根目录下有这么两个文件夹,源码和帮助文档。
在这里插入图片描述
DOC文件夹下有Fatfs提供的具体函数的使用方法
SRC文件的构成如下
在这里插入图片描述

在这里插入图片描述
Fatfs的源码阅读可以参考《零死角玩转 STM32F103—指南者》中的阅读提示,如果只是想使用,那么只需要看如何移植

移植FATFS 准备工作

背景:两个文件 ffconf.h diskio.c

FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.hdiskio.c
ffconf.h :FATFS模块的所有配置项都是存放在 ffconf.h 里面,我们可以通过配置里面的一些选项,来满足自己的需求.FATFS 的说明文档里面有很详细的介绍
在这里插入图片描述

以spi读写flash为例,在实现了spi往flash中读写单个字节的底层代码之后,就可以进行fatfs的移植了。

0.创建一个新的工程,并且实现简单的点灯,spi操作等功能。

1.准备一份工程源码称为SPI—FatFs。将。将 FatFs 源码中的 src 文件夹整个文件夹拷贝一份至“SPI—FatFs 文件系统\USER\”文件夹下并修改名为“FATFS”。文件夹名字没有固定名称,这里只是方便书写。

2.打开工程文件,并将 FatFs 组件文件添加到工程中,需要添加有 ff.c、diskio.c 和cc936.c 三个文件.
在这里插入图片描述
3.添加 FATFS 文件夹到工程的 include 选项中。打开工程选项对话框,选择“C/C++”选项下的“Include Paths”项目,在弹出路径设置对话框中选择添加“FATFS”文件夹
在这里插入图片描述
4.修改 diskio.c 文件和 ffconf.h 文件,diskio.c 文件内容是与底层设备输入输出接口函数文件,不同硬件设计驱动就不同,需要的文件也不同;FatFs默认使用日语,我们想要支持简体中文需要
修改 FatFs 的配置,即修改 ffconf.h 文件。

FatFs 底层设备驱动函数

文件系统实现中间模糊层,提供上层函数给用户使用。需要用户实现底层接口。diskio.c 文件内容就是与底层接口相关的。

移植需要用户支持函数,一般只有前六个需要使用
在这里插入图片描述
disk_status,disk_initialize,disk_read是必要配置的。
disk_write,get_fattime,disk_ioctl (CTRL_SYNC)是实现创建文件、修改文件需要的。
为支持简体中文长文件名称需要添加 ff_convert 和 ff_wtoupper 函数,实际这两个已经在 cc936.c 文件中实现,我们只要直接把 cc936.c 文件添加到工程中就可以。

移植FATFS 主要步骤

1.配置数据类型:在 integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数据类型,并根据编译器定义好数据类型。

2.配置:通过 ffconf.h 配置 FATFS 的相关功能,以满足你的需要。

3.函数编写:打开 diskio.c,进行底层驱动编写,需要编写 5 个接口函数。
在这里插入图片描述

配置integer.h 以定义数据类型(一般不需要)

我们使用的是 MDK5.34 编译器,数据类型和 integer.h 里面定义的一致,所以此步,我们不需要做任何改动。

配置 ffconf.h 选择模式

关于 ffconf.h 里面的相关配置,配置修改为我们需要的值即可,其他的配置用默认配置。

1 #define _USE_MKFS 1	//格式化功能选择,为使用 FatFs 格式化功能,需要把它设置为 1。
2 #define _CODE_PAGE 936	//语言功能选择,并要求把相关语言文件添加到工程宏。为支持简体中文文件名需要使用“936” 指的是把 cc936.c 文件添加到工程中
3 #define _USE_LFN 2	//长文件名支持,默认不支持长文件名,这里配置为 2,支持长文件名,并指定使用栈空间为缓冲区。
4 #define _VOLUMES 2	//指定物理设备数量
5 #define _MIN_SS 512	//指定扇区大小的最小值和最大值。SD 卡扇区大小一般都为 512字节,SPI Flash芯片扇区大小一般设置为 4096字节
6 #define _MAX_SS 4096	//指定扇区大小的最小值和最大值

在这里插入图片描述

为每个设备定义一个物理编号

 #define ATA 	0 	// 预留 SD 卡使用
 #define SPI_FLASH 	1 // 外部 SPI Flash

实现五个函数

设备状态获取(disk_status)、设备初始化(disk_initialize)、扇区读取(disk_read)、扇区写(disk_write)、其他控制(disk_ioctl)。

disk_initialize

函数名称disk_initialize
函数原型DSTATUS disk_initialize(BYTE Drive)
功能描述初始化磁盘驱动器
函数参数Drive:指定要初始化的逻辑驱动器号,即盘符,应当取值 0~9
返回值函数返回一个磁盘状态作为结果,对于磁盘状态的细节信息,请参考 disk_status函数
所在文件ff.c
实例disk_initialize(0); /* 初始化驱动器 0 */
注意事项disk_initialize 函数初始化一个逻辑驱动器为读/写做准备,函数成功时,返回值的 STA_NOINIT 标志被清零;应用程序不应调用此函数,否则卷上的 FAT 结构可能会损坏;如果需要重新初始化文件系统,可使用 f_mount 函数;在 FatFs 模块上卷注册处理时调用该函数可控制设备的改变;此函数在 FatFs 挂在卷时调用,应用程序不应该在 FatFs 活动时使用此函数
DSTATUS disk_status (BYTE pdrv /* 物理编号 */)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
	case ATA: /* SD CARD 预留,也可以*/
		 break;
 	case SPI_FLASH: /* SPI Flash 状态检测:读取 SPI Flash 设备 ID */
		 if (sFLASH_ID == SPI_FLASH_ReadID()) {/* 设备 ID 读取结果正确 */
 				status &= ~STA_NOINIT;
 			} else { /* 设备 ID 读取结果错误 */
				 status = STA_NOINIT;
			 }
		 break;
 	default:
		 status = STA_NOINIT;
		 }
	 return status;
	  }

注意点
SPI_FLASH_ReadID函数由用户自己实现,目的是检测设备是否已经就绪。

disk_status

函数名称disk_status
函数原型DRESULT disk_status (BYTE Drive)
功能描述返回当前磁盘驱动器的状态
函数参数Drive:指定要确认的逻辑驱动器号,即盘符,应当取值 0~9
返回值磁盘状态返回下列标志的组合,FatFs 只使用 STA_NOINIT 和 STA_PROTECTEDSTA_NOINIT: 表明磁盘驱动未初始化,下面列出了产生该标志置位或清零的原因:置位:系统复位,磁盘被移除和磁盘初始化函数失败。清零:磁盘初始化函数成功.STA_NODISK:表明驱动器中没有设备,安装磁盘驱动器后总为0 STA_PROTECTED:表明设备被写保护,不支持写保护的设备总为 0,当STA_NODISK 置位时非法
所在文件ff.c
实例disk_status(0); /* 获取驱动器 0 的状态 */
DSTATUS disk_initialize (BYTE pdrv /* 物理编号 */)
{	uint16_t i;
	DSTATUS status = STA_NOINIT;
	switch (pdrv) {
		case ATA: /* SD CARD */
			break;
 
	case SPI_FLASH: /* SPI Flash */
				SPI_FLASH_Init(); /* 初始化 SPI Flash */
		 		i=500;/* 延时一小段时间 */
				while (--i);
				SPI_Flash_WAKEUP();/* 唤醒 SPI Flash */
				status = disk_status(SPI_FLASH);/* 获取 SPI Flash 芯片状态 */
				break;
	default:
		 status = STA_NOINIT;
		}
 return status;
}

注意点
SPI_FLASH_Init();SPI_Flash_WAKEUP();都是由用户自己实现的底层功能,具体需要参照Flash对应的要求进行编写(一般也就是按着一定的规则发送数据字节过去,所以一般编程顺序是,线实现单字节的spi收发,再按照要求编写多字节的功能,再嵌入文件系统)

disk_read

函数名称disk_read
函数原型DRESULT disk_read (BYTE Drive, BYTE* Buffer, DWORD SectorNumber, BYTE SectorCount)
功能描述从磁盘驱动器上读取扇区
函数参数Drive:指定逻辑驱动器号,即盘符,应当取值 0~9 Buffer:指向存储读取数据字节数组的指针,需要为所读取字节数的大小,扇区统计的扇区大小是需要的(注:FaFts 指定的内存地址并不总是字对齐的,如果硬件不支持不对齐的数据传输,函数里需要进行处理)SectorNumber:指定起始扇区的逻辑块(LBA)上的地址SectorCount:指定要读取的扇区数,取值 1~128
返回值RES_OK(0):函数成功RES_ERROR:读操作期间产生了任何错误且不能恢复它RES_PARERR:非法参数RES_NOTRDY:磁盘驱动器没有初始化
所在文件ff.c
DRESULT disk_read (
	BYTE pdrv, /* 设备物理编号(0..) */
	BYTE *buff, /* 数据缓存区 */
	DWORD sector, /* 扇区首地址 */
	UINT count /* 扇区个数(1..128) */
	)
{
	DRESULT status = RES_PARERR;
	switch (pdrv) {
		 case ATA: /* SD CARD */
			break;
 
		 case SPI_FLASH:
			sector+=512;/* 扇区偏移 2MB,外部 Flash 文件系统空间放在 SPI Flash 后面 6MB 空间 */
			SPI_FLASH_BufferRead(buff, sector <<12, count<<12);
			status = RES_OK;
			break;
 
		default:
			status = RES_PARERR;
				 }
		 return status;
 }

注意点
参数pdrv 为设备物理编号,用户自定义,buff:BYTE 类型指针变量,buff指向用来存放读取到数据的存储区首地址,sector 是一个 DWORD 类型变量,指定要读取数据的扇区首地址。count 是一个 UINT 类型变量,指定扇区数量。
数据类型:BYTE 类型实际是 unsigned char 类型,DWORD 类型实际是 unsigned long 类型,UINT类型实际是 unsigned int 类型,类型定义在 integer.h 文件中。
偏移 :板使用的 SPI Flash 芯片型号为 W25Q64FV,每个扇区大小为 4096 个字节(4KB),总共有8M字节空间若。要从Flash的首地址开始存放文件系统则不需要偏移。 只将后部分6MB空间分配给FatFs使用,即 FatFs 是从 2MB 空间开始,为实现这个效果需要将所有的读写地址都偏移 512 个扇区空间
SPI_FLASH_BufferRead():读flash的底层程序,实现在指定地址读取指定长度的数据,示例

/**
* @brief 读取 FLASH 数据
* @param pBuffer,存储读出数据的指针
* @param ReadAddr,读取地址
* @param NumByteToRead,读取数据长度
* @retval 无
*/
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{

 SPI_FLASH_CS_LOW(); /* 选择 FLASH: CS 低电平 */
 
 SPI_FLASH_SendByte(W25X_ReadData); /* 发送 读 指令 */
 SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16) /* 发送 读 地址高位 */
 SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8); /* 发送 读 地址中位 */
 SPI_FLASH_SendByte(ReadAddr & 0xFF); /* 发送 读 地址低位 */
  
 while (NumByteToRead--)/* 读取数据 */
 {
	  *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节*/
	  pBuffer++;/* 指向下一个字节缓冲区 */
 }
	SPI_FLASH_CS_HIGH();/* 停止信号 FLASH: CS 高电平 */
}

disk_write

函数名称disk_write
函数原型DRESULT disk_write (BYTE Drive, const BYTE* Buffer, DWORD SectorNumber, BYTE SectorCount)
功能描述向磁盘写入一个或多个扇区
函数参数Drive:指定逻辑驱动器号,即盘符,应当取值 0~9Buffer:指向要写入字节数组的指针(注:FaFts 指定的内存地址并不总是字对齐的,如果硬件不支持不对齐的数据传输,函数里需要进行处理)SectorNumber:指定起始扇区的逻辑块(LBA)上的地址SectorCount:指定要写入的扇区数,取值 1~128
返回值RES_OK(0):函数成功RES_ERROR:写操作期间产生了任何错误且不能恢复它RES_WRPER:媒体被写保护RES_PARERR:非法参数RES_NOTRDY:磁盘驱动器没有初始化
所在文件ff.c
注意事项只读配置中不需要此函数
DRESULT disk_write (
	BYTE pdrv, /* 设备物理编号(0..) */
	const BYTE *buff, /* 欲写入数据的缓存区 */
	DWORD sector, /* 扇区首地址 */
	UINT count /* 扇区个数(1..128) */
)
{
	uint32_t write_addr;
	DRESULT status = RES_PARERR;
 if (!count) {
		 return RES_PARERR; /* Check parameter */
			}
 
		switch (pdrv) {
			case ATA: /* SD CARD */
			 break;
			case SPI_FLASH:
			/* 扇区偏移 2MB,外部 Flash 文件系统空间放在 SPI Flash 后面 6MB 空间 */
			 sector+=512;
			 write_addr = sector<<12;
			 SPI_FLASH_SectorErase(write_addr);//对于Flash 先擦除再写
			 SPI_FLASH_BufferWrite((u8 *)buff,write_addr,count<<12);//写
			 status = RES_OK;
			break;
 
			default:
			status = RES_PARERR;
				}
			return status;
 }

SPI_FLASH_SectorErase SPI_FLASH_BufferWrite用户实现

disk_ioctl

函数名称disk_ioctl
函数原型DRESULT disk_ioctl (BYTE Drive, BYTE Command, void* Buffer)
功能描述控制设备指定特性和除了读/写外的杂项功能
函数参数Drive:指定逻辑驱动器号,即盘符,应当取值 0~9Command:指定命令代码Buffer:指向参数缓冲区的指针,取决于命令代码,不使用时,指定一个 NULL指针
返回值RES_OK(0):函数成功RES_ERROR:写操作期间产生了任何错误且不能恢复它RES_PARERR:非法参数RES_NOTRDY:磁盘驱动器没有初始化
所在文件ff.c
注意事项CTRL_SYNC:确保磁盘驱动器已经完成了写处理,当磁盘 I/O 有一个写回缓存,立即刷新原扇区,只读配置下不适用此命令GET_SECTOR_SIZE:返回磁盘的扇区大小,只用于 f_mkfs()GET_SECTOR_COUNT:返回可利用的扇区数,_MAX_SS ≥ 1024 时可用GET_BLOCK_SIZE:获得擦除块大小,只用于 f_mkfs()CTRL_ERASE_SECTOR:强制擦除一块的扇区,_USE_ERASE > 0 时可用
DRESULT disk_ioctl (
BYTE pdrv, /* 物理编号 */
BYTE cmd, /* 控制指令 */
void *buff /* 写入或者读取数据地址指针 */
)
{
	DRESULT status = RES_PARERR;
	switch (pdrv) {
		case ATA: /* SD CARD */
			break;
 
		case SPI_FLASH:
			switch (cmd) {
			/* 扇区数量:1536*4096/1024/1024=6(MB) */
			case GET_SECTOR_COUNT:
					*(DWORD * )buff = 1536;
					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;
 }

参数pdrv 为设备物理编号,cmd 为控制指令,包括发出同步信号、获取扇区数目、获取扇区大小、获取擦除块数量等等指令,buff 为指令对应的数据指针。
cmd:对 于 SPI Flash 芯 片 , 为 支 持 FatFs 格 式 化 功 能 , 需 要 用 到 获 取 扇 区 数 量(GET_SECTOR_COUNT)指令和获取擦除块数量(GET_BLOCK_SIZE)。另外,SD 卡扇区大小为 512 字节,SPI Flash 芯片一般设置扇区大小为 4096 字节,所以需要用到获取扇区大小(GET_SECTOR_SIZE)指令。

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

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

相关文章

开发指导—利用组件插值器动画实现 HarmonyOS 动效

一. 组件动画 在组件上创建和运行动画的快捷方式。具体用法请参考通用方法。 获取动画对象 通过调用 animate 方法获得 animation 对象&#xff0c;animation 对象支持动画属性、动画方法和动画事件。 <!-- xxx.hml --><div class"container"> <di…

IntelliJ IDEA中用git提交代码时忽略文件的设置

设置IDEA自动过滤掉不需要提交的文件或文件夹&#xff1a;如*.iml, .idea,target 文件夹 1、进入idea设置界面 Windows环境&#xff1a;File - Settings - Editor - File Types Mac环境&#xff1a;Preferences… - Editor - File Types 2、在下面的ignore files and folders…

SPA和MPA

SPA与MPA是什么 **SPA&#xff08;Single Page Application&#xff0c;单页应用&#xff09;**在首次加载时会下载一个单独的HTML文件&#xff0c;然后通过JavaScript动态加载内容&#xff0c;无需每次页面刷新时重新加载整个页面。**MPA&#xff08;Multi-Page Application&…

【校招VIP】java语言考点之关键字string

考点介绍&#xff1a; string作为一个特殊类&#xff0c;正常情况下&#xff0c;是遵循对象的值和引用的使用。有一定的考察频度&#xff0c;但有的时候也能代表相等&#xff0c;与常量区的插入有关。 java语言考点之关键字string-相关题目及解析内容可点击文章末尾链接查看&a…

redis缓存失效时间没到,数据莫名丢失问题排查

述&#xff1a;redis缓存了token&#xff0c;失效时间为24小时&#xff0c;可是每次不到多久&#xff0c;就提示token失效&#xff0c;重新登录后&#xff0c;没用多久&#xff0c;又提示token失效。查看了下缓存&#xff0c;发现数据全部没掉了&#xff0c;并且多了几个back1&…

电脑提示“系统找不到指定的文件”怎么办?

“系统找不到指定的文件”对于Windows用户来说是一个很常见的错误&#xff0c;尤其是Win10用户&#xff0c;经常会遇到Win10提示找不到指定文件。在此错误后面有时还会出现错误代码&#xff1a;0x80070002&#xff0c;但是&#xff0c;故障类型或代码在不同的操作系统规范上是不…

史上最全PMP学习资料、项目管理资料、备考经验包,3A一次通过

你是否也有过类似的经历&#xff1f; 为了获取备考资料&#xff0c;有的同学在论坛、知乎或者相关垂直类网站下载了很多的资料&#xff0c;这些资料大部分是机构进行获客引流的资料&#xff0c;没有真正的干货。 经常会看到10G、20G的资料包&#xff0c;感觉内容很丰富&#xf…

Spring学习|Spring简介、IOC控制反转理解、IOC创建对象方式

Spring Spring:春天------>给软件行业带来了春天! 2002&#xff0c;首次推出了Spring框架的雏形: interface21框架! Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日发布了1.0正式版。 RodJohnson&#xff0c;Spring Framework创始人&…

关于某次授权的大型内网渗透测试

背景&#xff1a; 接到朋友邀请&#xff0c;要进行一个授权站点的渗透&#xff0c;但是进去实际环境才发现是多域控主机。也学习了很多后渗透手法&#xff0c;比较受益匪浅。 前期渗透&#xff1a; 打点&#xff1a;&#xff08;任意文件上传&#xff09; 直接发现头像处任…

目录与文件系统

无论在哪个计算机系统中&#xff0c;文件系统结构都应该是一样的一层或者几层的话太多太乱不适用用目录实现一个树状结构&#xff0c;划分后层次清晰如果把目录下所有的文件的FCB都存取&#xff0c;然后对比没必要太麻烦了 所以数据盘块集合中存放目录下文件字符串和对应的编号…

Rocky(Centos)安装中文字体(防止中文乱码)

1、查看字体列表 运行下列命令 fc-list 若出现&#xff0c;下面截图&#xff0c;则需要安装字体管理软件 安装字体库&#xff0c;运行&#xff1a; yum -y install fontconfig 当看到下图的提示信息时说明已安装成功&#xff1a; 二、添加中文字体 1&#xff09;window…

文件夹怎么安全加密?文件夹加密软件怎么样?

我们在使用电脑的过程中&#xff0c;习惯用文件夹来管理电脑数据&#xff0c;为了文件夹数据安全&#xff0c;我们需要使用加密的方式来进行保护。那么&#xff0c;文件夹该怎么安全加密呢&#xff1f;下面我们就来了解一下。 文件夹加密软件安全吗&#xff1f; 文件夹加密软…

功率放大器主要作用是什么呢

功率放大器是一种电子设备&#xff0c;主要作用是将输入信号的功率增加到更高的水平&#xff0c;以便能够驱动高功率负载。在许多应用中&#xff0c;信号源产生的信号往往具有较低的功率&#xff0c;无法直接满足一些要求较高的设备或系统的需求。而功率放大器则可以增强信号的…

当面试被问到 Java 内存模型,不妨反问面试官:您问得是 Java Memory Model 呢?还是 JVM 运行时数据区?

目录 1. JVM 运行时数据区 2. Java 内存模型 最近在牛客上看到这样一个帖子&#xff0c;大概就是在面试中呢&#xff0c;被面试官问到了 Java 内存模型&#xff0c;面试的这位小伙呢&#xff0c;也是掌握了 JVM 内存布局的相关知识&#xff0c;但是不知道面试官问的 Java 内存…

行测图形推理规律(一)元素组成

题库&#xff1a;粉笔网题库 (fenbi.com) 不知道和测评的行测题库是不是一样的&#xff0c;但是总结的规律应该是一样的。 规律并不唯一&#xff0c;题库的答案也只是参考答案&#xff0c;切勿当杠精&#xff0c;你觉得你的规律更合适就别管。本人所归纳的规律仅代表本人想法…

淘宝数据库,主键如何设计的?

聊一个实际问题&#xff1a;淘宝的数据库&#xff0c;主键是如何设计的&#xff1f; 某些错的离谱的答案还在网上年复一年的流传着&#xff0c;甚至还成为了所谓的 MySQL 军规。其中&#xff0c;一个最明显的错误就是关于MySQL 的主键设计。 大部分人的回答如此自信&#xff…

uni-app点击复制指定内容(点击复制)

官方api uni.setClipboardData(OBJECT) uni.setClipboardData({data: 要被复制的内容,success: function () {console.log(success);} });

虹科分享 | MKA:基于先进车载网络安全解决方案的密钥协议

MKA作为MACsec的密钥协议&#xff0c;具有安全、高效、针对性强的特点&#xff0c;为您的汽车ECU通讯创建了一个安全的通信平台&#xff0c;可以助力您的各种汽车创新项目&#xff01; 虹科方案 | 什么是基于MACsec的汽车MKA 一、MACsec在汽车行业的应用 在以往的文章中&#…

AutoSAR-BSW层概述

一.什么是BSW BSW层全称为Basic Software &#xff08;基础软件层&#xff09;&#xff0c;顾名思义&#xff0c;该层主要是为应用层提供基础服务。 这里可以看到 BSW 主要提供了&#xff1a;看门狗服务 &#xff0c;存储服务&#xff0c;通信和诊断服务&#xff0c;OS服务&a…

select 语句执行顺序

sql 样例 select t_students.name as 姓名, sum(case when b.cname语文 then c.sc_val else 0 end) As 语文 ,sum(case when b.cname数学 then c.sc_val else 0 end) As 数学,sum(case when b.cname英语 then c.sc_val else 0 end) As 英语, From t_students a, t_corses b, t…