【STM32】基于SPI协议读写SD,详解!

文章目录

    • 0 前言
    • 1 SD卡的种类和简介
      • 1.1 SD卡的种类
      • 1.2 SD卡的整体结构
      • 1.3 SD卡运行机制——指令和响应
    • 2 SD卡的通信总线
      • 2.1 SDIO
      • 2.2 SPI
    • 3 硬件连接
    • 4 代码实践【重点】
      • 4.1 HAL库移植
      • 4.2 标准库移植
      • 4.3 遇到的问题和解决方案
    • 5 扩展阅读

0 前言

  因为项目需要,使用stm32读写sd卡,这一块网上的资料很多,但是比较杂乱。有些是不能跑,有些是代码可以跑,但是相关的注释或者配置方法、流程不够清晰明确,于是花了几天时间,研究了几个成功案例之后,总结出一个相对明确的流程。【基于STM32F103C8T6

网上有各种流传的例程,经过测试确实可以用,但是魔改得有点多,个人觉得不是很便于理解,所以想着能不能从最开始的FATFS包来自己手动移植一个,最好是这个流程完全可复制,操作也非常简单,就像一个插件一样,基本实现模块化。

1 SD卡的种类和简介

  既然要读写SD卡,那首先要对SD卡的底层有一定的了解,这样才能够真正理解后面的代码。

1.1 SD卡的种类

  首先需要明确的是,SD卡指的是那种大的卡,一般用在相机里面,如下图所示:

在这里插入图片描述在这里插入图片描述

而这种卡:

在这里插入图片描述在这里插入图片描述
一般叫microSD卡TF卡,二者其实相差不大,只是引脚略微不同,其实读写都是一样的,也可以考虑买一个TF卡转SD卡的卡套,来适应两种接口。

  相比于这个SD卡的名字,另一个SD卡的标准显得更加重要。所谓的标准,差别主要体现在容量上面,这个需要在使用前明确。目前仍然有很多老年手机不支持大容量的TF卡,其本质就是因为不支持更高的标准。常见的SD卡标准如下图所示。
在这里插入图片描述
这个标准SD卡和TF卡是一样的,只是名字不同。

参考链接

  另外,根据这个链接, 实际上SD的通信协议也有多个版本,最早支持的版本是1.x,在SDHC之后,基本都是使用2.0版本,来兼容FAT32格式(原来都是FAT和FAT16),这两个协议的区别在驱动方面主要体现在指令上(2.0版本的指令更多,且兼容1.x版本的指令),这个后面有相关介绍,先埋个伏笔。

1.2 SD卡的整体结构

  理解了SD卡的种类,再来看看结构,主要是以下这张图
在这里插入图片描述
简单来说,就是除了存储单元外,还有好几个寄存器用于存放卡相关的信息,这些信息可以通过一些特定的指令读写。

1.3 SD卡运行机制——指令和响应

  SD卡的核心就是存储,那外部的主机如何对这个进行读写呢?就是通过指令。主机发送一条指令,然后SD卡会发送响应,让主机知道指令执行情况。
  每一条指令都是6个字节(48bit),其结构如下所示:
在这里插入图片描述

其中,Command占6位,所以一共有64个指令,从0-63,依次叫CMD0,CMD1,。。。CMD63,但是因为一次性是发送一个字节,也就是8位,所以会加上前面的两位,即0x40+CMDx才是指令。
  紧接着的是32位指令执行的参数,一般是存储地址或者寄存器值等,不是所有指令都有参数,对于没有参数的指令,直接传0即可。
  最后是校验值,这里采用的是循环校验,计算有点复杂,这个其实在后续的代码中,都是把部分常用的指令对应的校验计算出来给他传过去,并没有现场计算。

  指令发出之后,主机要等待SD卡的响应,其响应有很多类型,长度也各自不一样。短的响应只有一个字节,长的响应可以有多个字节。大部分的指令都是R1类型,即只有一个字节,R2表示响应有两个字节,还有一种类型是R1b,即在R1的基础上,后面紧跟着busy信号,可能有多个字节,一般不怎么使用。R1响应的结构各个位都有单独的含义,如下图所示。
在这里插入图片描述
可以看到,第6-1位都是错误,为1表示错误(“有效”),为0表示没有错误;第0位表示卡是否处于空闲状态,一般是发送进入IDLE指令(CMD0)之后会响应,也就是0x01。

  以上就是SD卡使用的基本讨论,即写入一个6字节的指令,然后读取响应的1-2个字节,并判断指令执行状态。时序图如下所示。
在这里插入图片描述
  接下来就是重点:SD卡数据的读写。和上面一样,读写数据之前,需要先发送一个指令,然后再读入或写入数据。对应的指令主要是这几个:
在这里插入图片描述
分别有读单块、读多块、写单块、写多块四个指令。其中,读写多块貌似需要使用到ACMD指令,所以用得比较少,可以通过多次调用读写单块的函数达到读写多块的目的。【一般SD卡一块(block)是512 Byte】
  根据官方的手册,读数据的流程大概是这样:
在这里插入图片描述
即先发送读的指令,然后等待sd卡响应指令后(根据上图,读单块和多块的响应都是R1类型),再读取数据块。
类似地,写指令的操作流程时序如下所示。
在这里插入图片描述
和上面不一样的是,在数据写入完毕后,还会有一个响应(Data Response),表示数据写入的情况,由SD卡传输给主机,是一个字节,其格式如下所示

在这里插入图片描述

  但是,这个时序图中并没有对“Data Block”部分进行展开叙述,但其实其内部结构同样重要,这里根据官方的描述和可行代码自行绘制了这张图:
在这里插入图片描述
其中,First Byte类似于一个启动符号,告知后面有数据来了,然后是一个block的数据,一般是512字节,最后是两位校验码。
  对于读数据,首先要读第一个字节,判断是不是0xFE,如果是,表示后面是数据,要把后面的数据给收了,收完512字节之后,最后的两位校验码可以忽略;对于写数据,是在发完写指令之后,手动写入0xFE,作为写数据的第一个字节,然后再写入512字节数据,最后两位校验码一般直接传0xFF即可。

2 SD卡的通信总线

  上面介绍的是SD卡的运行机制,从上面的结构图可以看出,这个运行机制到MCU控制端还需要一个通信协议,来约定这些数据该如何传输。常见的SD卡通信协议主要有两种:SPI模式SD模式(SDIO),其中两种通信协议下的引脚定义如下图所示。
在这里插入图片描述

在SPI协议中,SD卡扮演的角色是Slave,即从机,故其中MOSI和MISO中“M”指的控制数据读写的芯片,如MCU等;“S”从机是指SD卡。

参考链接

关于引脚的理解:以SPI为例,MCU对SD卡的控制指令都是通过CMD引脚串行传输的,所以CMD引脚是MOSI;而SD卡返回的数据是通过D0传输,所以D0是MISO。而SDIO数据传输可以选定多个引脚,常见的有只使用D0,和使用D0~D3四个引脚,并行传输。

2.1 SDIO

  在STM32F10x系列型号中,只有大容量的芯片才支持这个协议,没有实践过,这里只放一个网上的教程:

  • SDIO—SD卡读写测试

值得一提的是,不同协议其实只是传输方式不一样,底层的那些逻辑是差不多的,当然有些指令SPI协议不支持,只支持SDIO协议。

2.2 SPI

  • 概述
      SPI是四线协议:SCK(同步时钟),MOSI(主机到从机的数据),MISO(从机到主机的数据),CS(片选)。和IIC类似,也是一个串行协议,因为有时钟信号,所以是一个同步传输的协议(UART是异步协议)。但是,值得一提的是,因为收发数据是两根线,所以SPI是全双工协议,而IIC因为只有SCK和SDA,所以是半双工协议。

  • 运行模式
      SPI比较特殊的地方在于,它的电平和采样边沿可以额外设置,也就是设置不同的传输模式,这个设置由两个变量来确定:CPOL(Clock Polarity)、CPHA(Clock Phase),这两个变量分别可以设置0或1,因此组合起来有四种模式:

    • 0 0 CLK空闲时为低电平,CLK上升沿(第一个边沿)采样数据。
    • 0 1 CLK空闲时为低电平,CLK下降沿(第二个边沿)采样数据。
    • 1 0 CLK空闲时为高电平,CLK下降沿(第一个边沿)采样数据。
    • 1 1 CLK空闲时为高电平,CLK上升沿(第二个边沿)采样数据。
  • 数据同步
      由于SPI是全双工协议,且时钟只能是主设备发出,所以在主设备看来,不管是发送还是接收数据,都必须提供时钟,加上数据发送和接收是分开的两根线,所以数据在发送时也需要接收,或者说,接收时因为需要时钟,所以其实接收缓冲区也会新增数据,只是用不用的问题。
      那问题来了,如果我要收一个数据,必须发一个数据,那对方因为该数据误操作了怎么办?所以在接收数据时,要发送一个对从机设备来说无效的数据,也就是所谓的dummy data,这样就不会误响应了。

  • 代码配置
      网上有很多流传的软件SPI,即在理解SPI协议的基础上,使用IO口实现这个时序,但是这样一方面是代码比较麻烦,另外就是时钟配置难以掌握,所以这种只适用于硬件SPI没有或者被用完的情况,在有硬件SPI外设的前提下,还是用硬件比较方便。
      这里以一个标准库下的SPI外设初始化为例理解一下SPI配置的方法:

    void SD_SPI_Init(void)
    {GPIO_InitTypeDef GPIO_InitStructure;//使能时钟——宏定义实现ENABLE_SD_SPI_GPIO_CLK();ENABLE_SD_SPI_CLK();//GPIO初始化GPIO_InitStructure.GPIO_Pin = SD_SPI_MOSI_PIN | SD_SPI_SCK_PIN;   //MOSI & SCK: AFIO,OutputGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(SD_SPI_GPIO_PORT, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = SD_SPI_MISO_PIN;                    //MISO: InputGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //输入GPIO_Init(SD_SPI_GPIO_PORT, &GPIO_InitStructure);//SPI外设初始化SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPISPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//选择了串行时钟的稳态:时钟悬空高SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//数据捕获于第二个时钟沿SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//定义波特率预分频的值:波特率预分频值为256SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式SPI_Init(SD_SPI, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器SPI_Cmd(SD_SPI, ENABLE); //使能SPI外设SD_SPI_ReadWriteByte(0xff);//启动传输
    }
    

    重点是GPIO口输出和输入要分别配置。

3 硬件连接

  SD卡电路设计如下图所示,在画电路板时,记得在几个sd卡的引脚上加上上拉电阻:

在这里插入图片描述

CD引脚全称是Card Detect,用于检测卡是否插入,在一些开发板的原理图中有类似的做法,但是软件其实也可以判断出来,所以必要性不强

4 代码实践【重点】

  在使用SD卡时,建议在充分理解上述展示的SD卡运行原理后先实现存储的访问,比如先写入一段,然后再去读取,串口输出读取的内容,对比一下是否一致。然后再考虑加上FATFS,实现基本的读写文件功能。
  很显然,我其实并没有按照这个流程学习,而是先找了网上的一个可运行的代码(已经带了FATFS),然后在此基础上不断尝试新的操作,在这个尝试的过程中对SD卡运行原理有了比较深刻的认识。

  言归正传,如果以实用为主,建议直接使用HAL库,如果愿意折腾,可以自己尝试在标准库实现,建议在HAL库的基础上再去移植标准库。由于这两个步骤我都实践了一遍,后文都有介绍。

参考链接:

  • 国外的一个教程,基于CubeIDE实现
  • 从大容量移植到中等容量的成功案例

4.1 HAL库移植

  这部分内容基本参考自上面的教程,只做了一些小的修改,让这个部分集成度更高。

  • 首先先设置一些系统参数,不设置其实问题也不大,但是设置全面,不留风险是编程开发的一个好习惯:
    在这里插入图片描述

    在这里插入图片描述

  • 然后使能SPI外设

    在这里插入图片描述
    这里简单介绍一下NSS,所谓硬件NSS类似于串口的硬件流控一样,即通过实际的引脚来实现片选,这样就可以直接调用SPI的函数来进行控制,而所谓软件(即下面 NSS Signal Type: Software)即是额外再初始化一个引脚来控制。

    这里其实个人觉得两者是差不多的,只是硬件是芯片指定的引脚,而软件则可以随便指定,相对自由一些。代码上其实差别不大,只是一个调SPI库的函数,一个调GPIO库的函数。但是网上相关的代码基本都是使用软件形式,所以这里也跟风一下。

  • 然后再添加FATFS,这里只改动两个设置:

    • USE_LFN:Enable with static working buffer on the BSS
    • MAX_SS:4096

    如下图所示
    在这里插入图片描述

  • 项目配置那块,需要把堆栈加大

    在这里插入图片描述
    分文件显示,模块化更容易理解:
    在这里插入图片描述

    私以为将不同外设分为不同文件是一个很好的习惯

最后,生成代码即可,代码方面主要修改3个文件:

fat_sd_card.c【额外添加的一个文件】

#define TRUE  1
#define FALSE 0
#define bool BYTE#include "fatfs_sd_card.h"static volatile DSTATUS Stat = STA_NOINIT;  /* Disk Status */
static uint8_t CardType;                    /* Type 0:MMC, 1:SDC, 2:Block addressing */
static uint8_t PowerFlag = 0;               /* Power flag *//**************************************** SPI functions**************************************//* slave select */
static void SELECT(void)
{HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_RESET);HAL_Delay(1);
}/* slave deselect */
static void DESELECT(void)
{HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_SET);HAL_Delay(1);
}/* SPI transmit a byte */
static void SPI_TxByte(uint8_t data)
{while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));HAL_SPI_Transmit(HSPI_SDCARD, &data, 1, SPI_TIMEOUT);
}/* SPI transmit buffer */
static void SPI_TxBuffer(uint8_t* buffer, uint16_t len)
{while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));HAL_SPI_Transmit(HSPI_SDCARD, buffer, len, SPI_TIMEOUT);
}/* SPI receive a byte */
static uint8_t SPI_RxByte(void)
{uint8_t dummy, data;dummy = 0xFF;while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));HAL_SPI_TransmitReceive(HSPI_SDCARD, &dummy, &data, 1, SPI_TIMEOUT);return data;
}/* SPI receive a byte via pointer */
static void SPI_RxBytePtr(uint8_t* buff)
{*buff = SPI_RxByte();
}/**************************************** SD functions**************************************//* wait SD ready */
static uint8_t SD_ReadyWait(void)
{uint8_t res;/* timeout 500ms */int32_t Timer2 = 0xffffff;/* if SD goes ready, receives 0xFF */do{res = SPI_RxByte();Timer2--;}while((res != 0xFF) && Timer2 > 0);return res;
}/* power on */
static void SD_PowerOn(void)
{uint8_t args[6];uint32_t cnt = 0x1FFF;/* transmit bytes to wake up */DESELECT();for(int i = 0; i < 10; i++){SPI_TxByte(0xFF);}/* slave select */SELECT();/* make idle state */args[0] = CMD0;   /* CMD0:GO_IDLE_STATE */args[1] = 0;args[2] = 0;args[3] = 0;args[4] = 0;args[5] = 0x95;   /* CRC */SPI_TxBuffer(args, sizeof(args));/* wait response */while((SPI_RxByte() != 0x01) && cnt){cnt--;}DESELECT();SPI_TxByte(0XFF);PowerFlag = 1;
}/* power off */
static void SD_PowerOff(void)
{PowerFlag = 0;
}/* check power flag */
static uint8_t SD_CheckPower(void)
{return PowerFlag;
}/* receive data block */
static bool SD_RxDataBlock(BYTE* buff, UINT len)
{uint8_t token;/* timeout 200ms */int32_t Timer1 = 0xffffff;/* loop until receive a response or timeout */do{token = SPI_RxByte();Timer1--;}while((token == 0xFF) && Timer1 > 0);/* invalid response */if(token != 0xFE) return FALSE;/* receive data */do{SPI_RxBytePtr(buff++);}while(len--);/* discard CRC */SPI_RxByte();SPI_RxByte();return TRUE;
}/* transmit data block */
#if _USE_WRITE == 1
static bool SD_TxDataBlock(const uint8_t* buff, BYTE token)
{uint8_t resp;uint8_t i = 0;/* wait SD ready */if(SD_ReadyWait() != 0xFF) return FALSE;/* transmit token */SPI_TxByte(token);/* if it's not STOP token, transmit data */if(token != 0xFD){SPI_TxBuffer((uint8_t*)buff, 512);/* discard CRC */SPI_RxByte();SPI_RxByte();/* receive response */while(i <= 64){resp = SPI_RxByte();/* transmit 0x05 accepted */if((resp & 0x1F) == 0x05) break;i++;}/* recv buffer clear */while(SPI_RxByte() == 0);}/* transmit 0x05 accepted */if((resp & 0x1F) == 0x05) return TRUE;return FALSE;
}
#endif /* _USE_WRITE *//* transmit command */
static BYTE SD_SendCmd(BYTE cmd, uint32_t arg)
{uint8_t crc, res;/* wait SD ready */if(SD_ReadyWait() != 0xFF) return 0xFF;/* transmit command */SPI_TxByte(cmd);          /* Command */SPI_TxByte((uint8_t)(arg >> 24));   /* Argument[31..24] */SPI_TxByte((uint8_t)(arg >> 16));   /* Argument[23..16] */SPI_TxByte((uint8_t)(arg >> 8));  /* Argument[15..8] */SPI_TxByte((uint8_t)arg);       /* Argument[7..0] *//* prepare CRC */if(cmd == CMD0) crc = 0x95; /* CRC for CMD0(0) */else if(cmd == CMD8) crc = 0x87;  /* CRC for CMD8(0x1AA) */else crc = 1;/* transmit CRC */SPI_TxByte(crc);/* Skip a stuff byte when STOP_TRANSMISSION */if(cmd == CMD12) SPI_RxByte();/* receive response */uint8_t n = 10;do{res = SPI_RxByte();}while((res & 0x80) && --n);return res;
}/**************************************** user_diskio.c functions**************************************//* initialize SD */
DSTATUS SD_disk_initialize(BYTE drv)
{uint8_t n, type, ocr[4];/* single drive, drv should be 0 */if(drv) return STA_NOINIT;/* no disk */if(Stat & STA_NODISK) return Stat;/* power on */SD_PowerOn();/* slave select */SELECT()<

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件测试面试题(800道)【附带答案】持续更新...

&#xff08;2024版&#xff09;面经 详细笔记部分展示&#xff1a; 测试基础&#xff08;102页&#xff09; Linux基础&#xff08;38页&#xff09; MySQL&#xff08;63页&#xff09; web测试&#xff08;21页&#xff09; API测试&#xff08;46页&#xff09; APP测试&…

带你掌握springboot集成SpringSecurity安全框架

前言&#xff1a; Spring Security 是 Spring 家族中的一个框架&#xff0c;提供了一套 Web 应用安全性的完整解决方案。一般来说&#xff0c;系统的安全性包括用户认证&#xff08;Authentication&#xff09;和用户授权&#xff08;Authorization&#xff09;两个部分。 认证…

【2024最新】基于springboot+vue的闲一品交易平台lw+ppt

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

修改yolo格式的labels类别、删除yolo格式的labels类别

1、项目中&#xff0c;数据集的类别顺序有时会改变&#xff0c;例如a项目类别1是b项目的类别3&#xff0c;&#xff0c;需要用a项目的数据集&#xff0c;只需要改类别就行&#xff0c;就不需要重新标注了&#xff0c;例如a项目的classes是 b项目的classes是 a项目的数据可以用…

Java项目实战II基于微信小程序的个人行政复议在线预约系统微信小程序(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 基于微信小…

Kubernetes在容器编排中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 Kubernetes在容器编排中的应用 Kubernetes在容器编排中的应用 Kubernetes在容器编排中的应用 引言 Kubernetes 概述 定义与原理 …

D67【python 接口自动化学习】- python基础之数据库

day67 Python操作MySQL基础使用 学习日期&#xff1a;20241113 学习目标&#xff1a;MySQL数据库-- 136 Python操作MySQL基础使用 学习笔记&#xff1a; pymysql 创建MySQL的数据库链接 执行sql语句 总结 Python中使用第三方库&#xff1a;pymysql来操作MySQL&#xff0c;…

GitLab基于Drone搭建持续集成(CI/CD)

本文介绍了如何为 Gitee 安装 Drone 服务器。服务器打包为在 DockerHub 上分发的最小 Docker 映像。 1. 准备工作 创建OAuth应用 创建 GitLab OAuth 应用。Consumer Key 和 Consumer Secret 用于授权访问极狐GitLab 资源。 ps:授权回调 URL 必须与以下格式和路径匹配&…

python实战(八)——情感识别(多分类)

一、任务目标 本文使用的是来自Kaggle的一个情感识别数据集&#xff0c;这个数据集的总数据量是5934条&#xff0c;标签为anger、fear、joy三种情感的其中一种&#xff0c;很明显是一个多分类任务。这里&#xff0c;我们将使用微调技巧进行深度学习建模&#xff0c;同时我们会比…

【学习笔记】数据结构(七)

图 文章目录 图7.1 图的定义和术语7.2 图的存储结构7.2.1 数组表示法 - 邻接矩阵&#xff08;Adjacency Matrix)7.2.2 邻接表 -&#xff08;链式&#xff09;表示法(Adjacency List&#xff09;7.2.3 十字链表(Orthogonal List)7.2.4 邻接多重表(Adjacent MultiList) 7.3 图的遍…

scrapy爬取中信证券销售金融产品信息

import scrapyclass CsProductSpider(scrapy.Spider):name "cs_product"allowed_domains ["www.cs.ecitic.com"]start_urls ["http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index.html"]def parse(self, response):# 提取数据的逻辑…

power bi中的related函数解析

在Power BI中&#xff0c;RELATED函数是一种用于检索相关表中数据的函数。它用于在一个表中检索与当前行相关联的另一个表中的数据。 销售成本 [销售数量]*related(商品表[进价])

Ollama的安装以及大模型下载教程

简介 Ollama是一个开源的大型语言模型服务工具&#xff0c;它帮助用户快速在本地运行大模型。通过简单的安装指令&#xff0c;用户可以执行一条命令就在本地运行开源大型语言模型&#xff0c; Ollama极大地简化了在Docker容器内部署和管理LLM的过程&#xff0c;使得用户能够快…

算法演练----24点游戏

给定4个整数&#xff0c;数字范围在1~13之间任意使用-*/&#xff08;&#xff09;&#xff0c;构造出一个表达式&#xff0c;使得最终结果为24&#xff0c; 方法一 算法分析&#xff1a;加括号和取出重复表达式 # 导入精确除法模块&#xff0c;使得在Python2中除法运算的行为更…

YUM 的使用

YUM 是一个用于 Fedora 和 Red Hat 以及 CentOS 操作系统的前端软件包管理器&#xff0c;它可以自动处理依赖关系并一次性安装所有必需的软件包。 镜像站点选择 1. 备份原有的镜像源配置文件 系统默认的 yum 镜像源配置文件存储在 /etc/yum.repos.d/ 目录下&#xff0c;可以…

第三十六章 Vue之路由重定向/404页面设置/路径模式设置

目录 一、路由重定向 1.1. 使用方式 1.2. 完整代码 1.2.1. main.js 1.2.2. App.vue 1.2.3. index.js 1.2.4. Search.vue 1.2.5. Home.vue 1.3. 运行效果 二、设定404错误页面 2.1. 使用方式 2.2. 完整代码 2.2.1. index.js 2.2.2. NotFound.vue 2.2.3. 运行效…

鸿蒙进阶篇-属性动画-animateTo转场动画

大家好啊&#xff0c;这里是鸿蒙开天组&#xff0c;今天我们来学习属性动画-animateTo&转场动画&#xff0c;咱们先来学习属性动画-animateTo 属性动画-animateTo 属性动画 animation是作为属性使用&#xff0c;而animateTo显示动画是一个系统的内置函数&#xff0c;可以…

[CKS] K8S ServiceAccount Set Up

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于Rolebinding的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[CKS] K8S Netwo…

Autosar CP DDS规范导读

Autosar CP DDS 主要用途 数据通信 中间件协议&#xff1a;作为一种中间件协议&#xff0c;DDS实现了应用程序之间的高效数据通信&#xff0c;能够在不同的软件组件和ECU之间传输数据&#xff0c;确保数据的实时性和可靠性。跨平台通信&#xff1a;支持在AUTOSAR CP平台上的不同…

wafw00f源码详细解析

声明 本人菜鸟一枚&#xff0c;为了完成作业&#xff0c;发现网上所有的关于wafw00f的源码解析都是这抄那那抄这的&#xff0c;没有新东西&#xff0c;所以这里给出一个详细的源码解析&#xff0c;可能有错误&#xff0c;如果有大佬发现错误&#xff0c;可以在评论区平和的指出…