【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.mzph.cn/diannao/60539.shtml

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

相关文章

Three.js 纹理贴图

1. 纹理贴图 在Three.js中&#xff0c;纹理贴图是一种将二维图像贴到三维物体表面的技术&#xff0c;以增强物体的视觉表现。纹理贴图可以使物体表面更加真实、细腻&#xff0c;为场景增色不少。 在Three.js中&#xff0c;纹理贴图的加载主要通过THREE.TextureLoader类实现。…

ArcGIS Pro属性表乱码与字段名3个汉字解决方案大总结

01 背景 我们之前在使用ArcGIS出现导出Excel中文乱码及shp添加字段3个字被截断的情况&#xff0c;我们有以下应对策略&#xff1a; 推荐阅读&#xff1a;ArcGIS导出Excel中文乱码及shp添加字段3个字被截断&#xff1f; 那如果我们使用ArGIS Pro出现上述问题&#xff0c;该如何…

图论-代码随想录刷题记录[JAVA]

文章目录 前言Floyd 算法dijkstra&#xff08;朴素版&#xff09; 前言 新手小白记录第一次刷代码随想录 1.自用 抽取精简的解题思路 方便复盘 2.代码尽量多加注释 3.记录踩坑 4.边刷边记录&#xff0c;更有成就感&#xff01; 5.解题思路绝大部分来自代码随想录 Floyd 算法 【…

anzocapital 昂首资本:外汇机器人趋势判断秘籍

再盲目交易而不借助像 anzocapital 昂首资本所了解的外汇机器人趋势判断方法&#xff0c;投资者在外汇市场将面临亏损的风险&#xff0c;anzocapital 昂首资本深知交易策略的重要性&#xff0c;就像外汇机器人确定趋势方向的方法&#xff0c;对投资者有着非凡的意义。 在外汇交…

【划分型DP-约束划分个数】【hard】力扣410. 分割数组的最大值

给定一个非负整数数组 nums 和一个整数 k &#xff0c;你需要将这个数组分成 k 个非空的连续子数组&#xff0c;使得这 k 个子数组各自和的最大值 最小。 返回分割后最小的和的最大值。 子数组 是数组中连续的部份。 示例 1&#xff1a; 输入&#xff1a;nums [7,2,5,10,8]…

python高级之面向对象编程

一、面向过程与面向对象 面向过程和面向对象都是一种编程方式&#xff0c;只不过再设计上有区别。 1、面向过程pop&#xff1a; 举例&#xff1a;孩子上学 1. 妈妈起床 2. 妈妈洗漱 3. 妈妈做饭 4. 妈妈把孩子叫起来 5. 孩子起床 6. 孩子洗漱 7. 孩子吃饭 8. 妈妈给孩子送学校…

shell脚本(1)

免责声明 学习视频来自B 站up主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 Shell脚本 建立一个sh脚本…

《DiffusionDet: Diffusion Model for Object Detection》ICCV2023

摘要 本文提出了一种新的框架DiffusionDet&#xff0c;它将目标检测任务表述为从带噪声的边界框到目标边界框的去噪扩散过程&#xff08;如图一所示&#xff09;。在训练阶段&#xff0c;目标边界框逐渐扩散到随机分布&#xff0c;模型学习逆转这一加噪过程。在推理阶段&#…

ISAAC SIM踩坑记录--ROS2相机影像发布

其实这个例子官方和大佬NVIDIA Omniverse和Isaac Sim笔记5&#xff1a;Isaac Sim的ROS接口与相机影像、位姿真值发布/保存都已经有详细介绍了&#xff0c;但是都是基于ROS的&#xff0c;现在最新的已经是ROS2&#xff0c;这里把不同的地方简单记录一下。 搭建一个简单的场景&a…

outlook邮箱关闭垃圾邮件——PowerAutomate自动化任务

微软邮箱反垃圾已经很强大了非常敏感&#xff0c;自家的域名的邮件都能给扔到垃圾邮箱里&#xff0c;但还是在本地增加了一层垃圾邮箱功能&#xff0c;然后垃圾邮箱并没有提示&#xff0c;导致错过很多通知&#xff0c;本身并没有提供关闭的功能&#xff0c;但微软有个Microsof…

「Py」Python基础篇 之 Python都可以做哪些自动化?

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「Py」Python程序设计&#x1f4da;全部专栏「Win」Windows程序设计「IDE」集成开发环境「UG/NX」BlockUI集合「C/C」C/C程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「UG/NX」NX定…

candence: 原理图生成网表时报错:Duplicate Pin name “xxx“

原理图生成网表时报错&#xff1a;Duplicate Pin name “xxx” 这个错误的意思是&#xff0c;原理图中管脚命名重复 解决这个问题的方法&#xff1a; 1、绘制元件的时候不要用相同的管脚名&#xff0c;比如GND等的&#xff0c;就稍加个后缀做区分2、就是将管脚属性修改为 &qu…

Diffusion Policy——斯坦福机器人UMI所用的扩散策略:从原理到其编码实现(含Diff-Control、ControlNet详解)

前言 本文一开始是属于此文《UMI——斯坦福刷盘机器人&#xff1a;从手持夹持器到动作预测Diffusion Policy(含代码解读)》的第三部分&#xff0c;考虑后Diffusion Policy的重要性很高&#xff0c;加之后续还有一系列基于其的改进工作 故独立成本文&#xff0c;且写的过程中 …

计算机网络学习笔记-3.2介质访问控制

文章目录 介质访问控制静态划分信道 动态分配信道轮询访问介质访问控制随机访问介质访问控制ALOHA协议简介ALOHA协议的工作原理 介质访问控制 介质访问控制&#xff08;MAC&#xff0c;Medium Access Control&#xff09;&#xff0c;质访问控制的目的是确保多个设备能够高效、…

GitCode光引计划有奖征文大赛

一、活动介绍 GitCode平台汇聚了众多杰出的G-Star项目&#xff0c;它们犹如璀璨星辰&#xff0c;用各自的故事和成就&#xff0c;为后来者照亮前行的道路。我们诚邀广大开发者、项目维护者及爱好者&#xff0c;共同撰写并分享项目在GitCode平台上托管的体验&#xff0c;挖掘平…

深入理解接口测试:实用指南与最佳实践5.0(三)

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

使用electron-egg把vue项目在linux Ubuntu环境下打包并安装运行

electron-egg一个入门简单、跨平台、企业级桌面软件开发框架https://www.kaka996.com/electron-egg 跳转地址 1,使用 git下载代码到本地,如果没有git需要进行安装 # gitee git clone https://gitee.com/dromara/electron-egg.git # github git clone https://github.com/dro…

DAY112代码审计PHP开发框架POP链利用Yii反序列化POP利用链

一、pop1链的跟踪 1、路由关系 2、漏洞触发口unserialize(base64_decode($data)); 2、__destruct()&#xff0c;魔术法方法调用close函数方法 3、未找到利用链&#xff0c;尝试__call魔术方法 4、逆推找call_user_func 函数 第一部分 namespace yii\db; class BatchQueryResu…

Maven 构建项目

Maven 是一个项目管理和构建工具&#xff0c;主要用于 Java 项目。它简化了项目的构建、依赖管理、报告生成、发布等一系列工作。 构建自动化&#xff1a;Maven 提供了一套标准化的构建生命周期&#xff0c;包括编译、测试、打包、部署等步骤&#xff0c;通过简单的命令就可以执…

任务调度中心-XXL-JOB使用详解

目录 详解 调度中心 执行器 原理 快速入门 源码仓库地址 1.初始化数据库 2.配置调度中心 1.解压源码 2.需改配置文件 3.启动调度中心 3.配置执行器 1.引入pom依赖 2.修改配置文件 3.执行器组件配置 4.部署执行器项目 4.开发第一个任务 BEAN模式&#xff08;类…