STM32之SPI读写W25Q128芯片

ops/2024/9/19 8:38:48/ 标签: 单片机, 嵌入式硬件
  1. SPI简介

    STM32的SPI是一个串行外设接口。它允许STM32微控制器与其他设备(如传感器、存储器等)进行高速、全双工、同步的串行通信。通常包含SCLK(串行时钟)、MOSI(主设备输出/从设备输入Master Output Slave Input)、MISO(主设备输入/从设备输出Master Input Slave Output)和NSS/CS片选信号Chip Select)这4条线,支持多个从设备连接到一个主设备上。

SPI,是一种高速的,全双工同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROMFLASH实时时钟AD转换器,还有数字信号处理器和数字信号解码器之间。

2. SPI使用步骤

我们将利用 STM32 的 SPI 来读取外部 SPI FLASH 芯片(W25Q128)为例,学习SPI。

2.1 SPI时钟SCLK

   SPI时钟特点主要包括:时钟速率时钟极性时钟相位三方面。

   时钟速率

SPI总线上的主设备必须在通信开始时候配置并生成相应的时钟信号。从理论上讲,只要实际可行,时钟速率就可以是你想要的任何速率,当然这个速率受限于每个系统能提供多大的系统时钟频率,以及最大的SPI传输速率。

时钟极性

根据硬件制造商的命名规则不同,时钟极性通常写为CKPCPOL。时钟极性和相位共同决定读取数据的方式,比如信号上升沿读取数据还是信号下降沿读取数据。

CKP可以配置为1或0,这意味着可根据需要将时钟的默认状态(IDLE)设置为高或低。极性反转可以通过简单的逻辑逆变器实现。须参考设备的数据手册才能正确设置CKPCKE

CKP = 0:时钟空闲IDLE为低电平0;

CKP = 1:时钟空闲IDLE为高电平1。

时钟相位

根据硬件制造商的不同,时钟相位通常写为CKECPHA。顾名思义,时钟相位/边沿,也就是采集数据时是在时钟信号的具体相位或者边沿;

CKE = 0:在时钟信号SCK的第一个跳变沿采样

CKE = 1:在时钟信号SCK的第二个跳变沿采样

2.2四种操作根据

SPI的时钟极性时钟相位特性可以设置4不同的SPI通信操作模式,它们的区别是定义了在时钟脉冲的哪条边沿转换(toggles)输出信号,哪条边沿采样输入信号,还有时钟脉冲的稳定电平值(就是时钟信号无效时是高还是低),详情如下所示:

Mode0:CKP=0,CKE=0当空闲态时,SCK处于低电平,数据采样是在第1个边沿,也就是SCK由低电平到高电平的跳变,所以数据采样是在上升沿(准备数据),(发送数据)数据发送是在下降沿。

Mode1:CKP=0,CKE=1当空闲态时,SCK处于低电平,数据发送是在第2个边沿,也就是SCK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。

Mode2:CKP=1,CKE=0当空闲态时,SCK处于高电平,数据采集是在第1个边沿,也就是SCK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。

Mode3:CKP=1,CKE=1当空闲态时,SCK处于高电平,数据发送是在第2个边沿,也就是SCK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

图中黑线采样数据的时刻蓝线SCK时钟信号

举个例子,下图是SPI Mode0读/写时序,可以看出SCK空闲状态为低电平,主机输出数据在第一个跳变沿被从机采样,主机输入数据同理。

3.STM32相关的SPI

    STM32的SPI外设可用作通讯的主机及从机, 支持最高的SCK时钟频率为fpclk/2 (STM32F103型号的芯片默认fpclk1为36MHz, fpclk2为72MHz),完全支持SPI协议的4种模式,数据帧长度可设置为8位或16位, 可设置数据MSB先行或LSB先行。它还支持双线全双工、双线单向以及单线模式。 其中双线单向模式可以同时使用MOSI及MISO数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线, 当然这样速率会受到影响。我们只讲解双线全双工模式

3.1时钟控制逻辑

    时钟由寄存器控制。SCK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对fpclk时钟的分频因子, 对fpclk的分频结果就是SCK引脚的输出时钟频率,计算方法见表 BR位对fpclk的分频。

其中的fpclk频率是指SPI所在的APB总线频率, APB1为fpclk1,APB2为fpckl2。

通过配置“控制寄存器CR”的“CPOL位”及“CPHA”位可以把SPI设置成前面分析的4种SPI模式。

实际应用中,我们一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。

3.2通讯过程

STM32使用SPI外设通讯时,在通讯的不同阶段它会对“状态寄存器SR”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。主发送器通讯过程中的是“主模式”流程,即STM32作为SPI通讯的主机端时的数据收发过程。

3.3从代码层面理解SPI

我们将利用 STM32 的 SPI 来读取外部 SPI FLASH 芯片(W25Q128),实现类似IIC的功能。

    使能 SPI2 的时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PORTB 时钟使能

RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE );//SPI2 时钟使能

GPIOB端口挂在STM32的APB2时钟线上。SPI2是挂在STM32的APB1时钟线上。

配置相关引脚的复用功能

这里使用 PB13、14、15 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式),所以设置这三个为复用 IO。

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15 复用推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 GPIOB

SPI2的引脚在PB上,可以参考W25Q128硬件连接图。

初始化 SPI2, 设置 SPI2 工作模式

库函数中是通过 SPI_Init 函数来实现。

void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)

SPI_InitTypeDef 的定义:

typedef struct

{

uint16_t SPI_Direction;

uint16_t SPI_Mode;

uint16_t SPI_DataSize;

uint16_t SPI_CPOL;

uint16_t SPI_CPHA;

uint16_t SPI_NSS;

uint16_t SPI_BaudRatePrescaler;

uint16_t SPI_FirstBit;

uint16_t SPI_CRCPolynomial;

}SPI_InitTypeDef;

SPI_BaudRatePrescaler设置 SPI 波特率预分频值决定 SPI 的时钟的参数,从不分频道 256 分频 8 个可选值

SPI_BaudRatePrescaler_256 //256 分频值

传输速度为 72M/256=281.25KHz。

初始化代码:

SPI_InitTypeDef SPI_InitStructure;

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线双向全双工

SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主 SPI

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // SPI 发送接收 8 位帧结构

SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二个跳变沿数据被采样

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由软件控制

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频 256

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始

SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式

SPI_Init(SPI2, &SPI_InitStructure); //根据指定的参数初始化外设 SPIx 寄存器

使能SPI2

SPI_Cmd(SPI2, ENABLE); //使能 SPI 外设

SPI2_ReadWriteByte(0xff); //④启动传输,主机发一个字节,进行一次传输,可以启动传输

SPI传输数据

发送数据函数

void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)

接收数据函数

uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)

查看 SPI 传输状态函数

判断数据是否传输完成,发送区是否为空
判断接收是否完成,接收区是否空

接收

SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)

发送

SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE)

设置SPI2速度函数

单独的设置分频系数的函数

//SPI 速度设置函数

//SpeedSet://SPI_BaudRatePrescaler_256 256 分频 (SPI 281.25K@sys 72M)

void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)

{

    assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));

    SPI2->CR1&=0XFFC7;

    SPI2->CR1|=SPI_BaudRatePrescaler; //设置 SPI2 速度

    SPI_Cmd(SPI2,ENABLE);

}

读写一个字节

u8 SPI2_ReadWriteByte(u8 TxData)

{

    u8 retry=0;

    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //等待发送区空

    {

       retry++;//重试

       if(retry>200)return 0;

    } //读取两百次还没有值,说明无效,返回

    SPI_I2S_SendData(SPI2, TxData); //通过外设 SPIx 发送一个数据

    retry=0;

    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //等待接收完一个 byte

    {

       retry++;

       if(retry>200)return 0;

    }

    return SPI_I2S_ReceiveData(SPI2); //返回通过 SPIx 最近接收的数据

}

W25Q128

• W25Q128 是华邦公司推出的大容量 SPI FLASH 产品,W25Q128 的容量为 128Mb,该系列还有 W25Q80/16/32/64等。

擦除

W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。
    这样要求芯片必须有 4K 以上 SRAM 才能很好的操作。

W25QXX驱动解读

W25QXX.h

W25QXX_CS片选,值0选定,1取消

初始化SPI

读取状态寄存器

写状态寄存器

擦除一个扇区

读取 SPI FLASH

//在指定地址开始读取指定长度的数据

//pBuffer:数据存储区

//ReadAddr:开始读取的地址(24bit)

//NumByteToRead:要读取的字节数(最大 65535)

void W25QXX_Read (u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)

{

u16 i;

SPI_FLASH_CS=0; //使能器件

SPI2_ReadWriteByte(W25X_ReadData); //发送读取命令

SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址

SPI2_ReadWriteByte((u8)((ReadAddr)>>8));

SPI2_ReadWriteByte((u8)ReadAddr);

for(i=0;i<NumByteToRead;i++)

{

pBuffer[i]=SPI2_ReadWriteByte(0XFF); //循环读数

}

SPI_FLASH_CS=1;

}

无检查写函数

//在指定地址开始写入指定长度的数据,但是要确保地址不越界!

//pBuffer:数据存储区

//WriteAddr:开始写入的地址(24bit)

//NumByteToWrite:要写入的字节数(最大65535)

//CHECK OK

void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)  

{                    

    u16 pageremain;   

    pageremain=256-WriteAddr%256; //单页剩余的字节数              

    if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节,这也是结束标识

    while(1)

    {     

        W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);

        if(NumByteToWrite==pageremain)break;//写入结束了

        else //NumByteToWrite>pageremain

        {

            pBuffer+=pageremain;

            WriteAddr+=pageremain; 

            NumByteToWrite-=pageremain;          //减去已经写入了的字节数

            if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节

            else pageremain=NumByteToWrite;       //不够256个字节了

        }

        //按照页剩余写一次,然后256个字节的写,然后写最后一页多出来的。

    };     

}

NoCheck是说可以跨扇区的写
下方表示写了一个扇区

W25QXX_Write函数

作用与 W25QXX_Flash_Read 的作用类似,不过是用来写数据到 W25Q128 里面的,其代码如下:

u8 W25QXX_BUFFER[4096];

void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)

{

    u32 secpos;

    u16 secoff;

    u16 secremain;

    u16 i;

    u8 * W25QXX_BUF;

    W25QXX_BUF=W25QXX_BUFFER;

    secpos=WriteAddr/4096;//扇区地址,每个扇区是4096,所以除以4096得到的整数就是扇区的地址标号

    secoff=WriteAddr%4096;//在扇区内的偏移

    secremain=4096-secoff;//扇区剩余空间大小

    //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用

    if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于 4096 个字节

    while(1)

    {

        W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容

        //secpos*4096是该扇区的起始地址

        for(i=0;i<secremain;i++)//校验数据

        {

            if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除,偏移地址内有数据

//擦除后的默认值是0xFFF

        }

        if(i<secremain)//需要擦除

        {

            W25QXX_Erase_Sector(secpos); //擦除这个扇区

            for(i=0;i<secremain;i++) //复制

            {

                W25QXX_BUF[i+secoff]=pBuffer[i];

            }

            W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区

        }

        else

            W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);

        //如果扇区剩余空间足够,直接写入扇区剩余区间.

        //是否需要写入下一个扇区

        if(NumByteToWrite==secremain)break;//写入结束了

        else//写入未结束

        {

            secpos++;//扇区地址增 1

            secoff=0;//偏移位置为 0

            pBuffer+=secremain; //指针偏移

            WriteAddr+=secremain; //写地址偏移

            NumByteToWrite-=secremain; //字节数递减

            if(NumByteToWrite>4096)secremain=4096;//下一个扇区还是写不完

            else secremain=NumByteToWrite; //下一个扇区可以写完了

        }

    };

}

//跟无检查页写入的逻辑一致。

该函数可以在 W25Q128 的任意地址开始写入任意长度(必须不超过 W25Q128 的容量)的数据。

先获得首地址(WriteAddr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。

擦除的最小单位是扇区,也就是4K。所以在擦除之前我们先将这个扇区的数据读取出来,保存在缓存区。在缓存中将对应的地址更新之后,一次性将数据写到对应的sector之中。

当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。

    我理解,整个STM32读写W25Q128是不断封装SPI_I2S_SendData和ReadData的一个过程,先封装到读写一个字节ReadWriteByte,再封装到读写一个扇区W25QXX_Write_NoCheck,W25QXX_Write。整个过程比较标准,无需太多改动。

4.STM32之SPI实战

    在实现STM32的SPI通讯之前,先做个小实验,实现USART串口通讯。因为调试STM32的SPI通讯可以把结果通过串口打印到电脑上显示,方便观察结果。

STM32CubeMX学习笔记(6)——USART串口使用_unused(huart)-CSDN博客

5.Keil调试代码

    今天学了Keil的debug功能,刚开始程序卡在了HAL_INIT这里,这是个很奇怪的问题。CubeMx生成的代码段,什么都没有做就是无法调试。原来要在CubeMx里面勾选一个调试功能。

在KEIL中勾选Use-MicroLib库

因为调用了printf,这是一个C++里面的功能,需要重映射。代码如下。

新建一个retarget.c文件。

#include "stdio.h"

#include "stm32f1xx_hal.h"

#include "usart.h"

#pragma import(__use_no_semihosting_swi)

#pragma import(__use_no_semihosting)

void _sys_exit(int x) {

    x = x;

}

struct __FILE  {

    int handle;

    /* Whatever you require here. If the only file you are using is */

    /* standard output using printf() for debugging, no file handling */

    /* is required. */

};

/* FILE is typedef’ d in stdio.h. */

FILE __stdout;

void _ttywrch(int ch){};

   

int fputc(int ch, FILE *f)

{

    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);

    return ch;

}

这段代码也是调了好久,网站上的或多或少不太对。

有了上面的代码,main函数就可以调用printf函数,打印到串口显示出来。

Stm32 debug停留在"BKPT 0xAB"或者"SWI 0xAB"的解决办法。

通过百度网盘分享的文件:SPI
链接:https://pan.baidu.com/s/1qzSFFV8-Vhrb0NzqbBAeCg?pwd=sshc 
提取码:sshc 


http://www.ppmy.cn/ops/97181.html

相关文章

Godot关于fbx格式文件导入

查看文档fbx格式是支持&#xff0c;看我的文件也是存在&#xff0c;就是在编辑器中文件系统找不到。解决方案如下 确保你开启了fbx导入&#xff0c;之后自动重启就可以导入了&#xff0c;unity的模型也可以用。什么立方体胶囊之类的。

水利机械5G智能制造工厂物联数字孪生平台,推进制造业数字化转型

在当今这个科技日新月异的时代&#xff0c;水利机械行业正经历着一场深刻的变革&#xff0c;其中5G智能制造工厂物联数字孪生平台的引入&#xff0c;无疑是推动制造业数字化转型的重要驱动力。工业物联数字孪生平台是智能制造工厂的核心组成部分&#xff0c;它基于物理世界的真…

高性能日志系统 代理模式构建全局日志器获取接口

日志器获取接口 通过两个函数&#xff0c;用于获取指定名称的日志器和root日志器。两个函数分别通过调用loggerManager的getInstance方法&#xff0c;获取单例的日志管理器对象&#xff0c;通过这种方式&#xff0c;借助日志管理器获取具体的日志器对象。 设计的主要目的&…

打卡学习Python爬虫第三天|python的re模块的使用

如何在python程序中使用正则表达式&#xff1f;就是使用re模块 re模块使用&#xff1a; 1、findall查找所有&#xff0c;返回list list re.findall("n","I love learning English and Chinese!") print(list) # 输出结果&#xff1a;[n,n,n,n,n] list…

Epic Games 商店面向欧盟 iPhone 用户上线

Epic Games Store 终于在欧盟推出&#xff0c;为玩家提供了不通过 App Store 就能在 iPhone上访问游戏的途径。在经历了漫长而昂贵的关于支付和竞争对手应用程序店面的法律战&#xff0c;以及公证方面的麻烦之后&#xff0c;Epic Games 成功地为App Store 带来了一个数字店面。…

【pyqt5】QLineEdit中的文本输入限制方式,输入校验规则的应用详解

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【Pyspark-驯化】一文搞懂Pyspark中表连接的使用技巧

【Pyspark-驯化】一文搞懂Pyspark中表连接的使用技巧 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 相关内容文档获取 微信公众号 &…

重复的DNA序列

题目链接 重复的DNA序列 题目描述 注意点 0 < s.length < 10^5s[i]‘A’、‘C’、‘G’ or ‘T’返回所有在 DNA 分子中出现不止一次的 长度为 10 的序列(子字符串) 解答思路 使用一个大小为10的滑动窗口存储该区间内的字符组成的字符串&#xff0c;使用哈希表存储任…

如何评估Redis的性能

导语 Redis是一款高性能的内存数据库&#xff0c;被广泛用于缓存、持久化、消息队列等各种场景。为了确保Redis的高性能运行&#xff0c;评估Redis的性能是非常重要的。本文将介绍如何评估Redis的性能&#xff0c;并从问题解决的角度探讨如何优化Redis的性能。 1. 性能评估指…

【前端基础篇】HTML零基础速通

文章目录 前言HTML结构认识HTML标签 HTML文件基本结构标签层次结构 快速生成代码框架HTML常见标签注释标签标题标签段落标签换行标签格式化标签图片标签超链接标签表格标签基本使用合并单元格 列表标签表单标签form标签input标签 label标签select标签textarea标签无语义标签 HT…

基于Python flask的岗位招聘数据分析系统,应用Python、Flask框架、Pyecharts、Wordcloud等技术

基于Python Flask的岗位招聘数据分析系统旨在为企业人力资源部门和求职者提供一个全面的数据分析平台&#xff0c;通过对招聘数据的深度挖掘和可视化展示&#xff0c;帮助用户做出更明智的决策。该系统采用了Python、Flask框架&#xff0c;并结合Pyecharts、Wordcloud等技术&am…

雪花算法理解(1高位+41位时间戳+10位机器位+12位自增序号) 及其使用豆包帮助下一个解决了时钟回拨的代码

背景&#xff1a; 为啥需要雪花算法呢&#xff1f; 1.我们是不希望用UUID的&#xff0c;因为它是字符串&#xff0c;不利于索引的建立。 2.字符串内存占用大。 3.游戏中&#xff0c;我们希望生成的id是有意义的&#xff0c;我们可以根据id去反推出一些业务信息。所以根据唯一的…

三生随记——暴雨之夜

第一章&#xff1a;暴雨将至 乌云如墨般在天空中翻涌&#xff0c;沉闷的雷声从远方传来&#xff0c;预示着一场暴雨即将降临。在这个繁华都市的角落里&#xff0c;年轻的画家林晓独自坐在她的工作室里&#xff0c;望着窗外阴沉的天色&#xff0c;心中涌起一股莫名的不安。 林…

微信小程序的遍历和事件的简单案例

遍历和事件的简单案例 在微信小程序中&#xff0c;你可以通过为组件添加事件来实现交互功能。当用户触发这些事件时&#xff0c;小程序会执行相应的处理函数。下面是一个示例&#xff0c;展示如何在微信小程序中遍历标签并为其添加点击事件&#xff1a; <view><view …

一起学习LeetCode热题100道(49/100)

49.二叉树的最近公共祖先(学习) 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#…

@DateTimeFormat和@JsonFormat

DateTimeFormat 用于接收前端传入的参数&#xff0c;变成自己想要的格式 JsonFormat用于格式化后端返回给前端的参数 DateTimeFormat(pattern "yyyy-MM-dd HH:mm:ss") JsonFormat(pattern "yyyy-MM-dd HH:mm:ss", timezone "GMT8") private …

微服务架构

应用架构的变迁 谈到微服务之前&#xff0c;首先我们通过下面图片简单了解一下应用架构的变迁 单体架构在小型项目和早期阶段可能非常有效&#xff0c;但随着应用程序的增长和复杂性的增加&#xff0c;它逐渐暴露出可扩展性差、可维护性差、持续集成和持续部署&#xff08;CI…

【K8S系列】Kubernetes基础介绍

一、前言 搭建完k8s集群后&#xff0c;正式进入k8s相关知识点的理论了解。并结合官方文档逐步总结涉及k8s各类知识点&#xff0c;希望能对正在学习的或将要学习得到小伙伴有所帮助。 二、系统部署历程回顾 传统部署时代&#xff1a; 早期&#xff0c;各个组织是在物理服务器…

无人机的无刷电机详解!!!

一、工作原理 无人机无刷电机的工作原理基于电磁感应和换向技术。它主要由转子和定子两部分组成。转子通常由永磁体制成&#xff0c;而定子则包含电磁线圈和磁传感器。当电流通过定子线圈时&#xff0c;会产生一个旋转磁场。磁传感器检测转子上的磁场位置&#xff0c;并通过与…

遗传算法与深度学习实战(7)——使用遗传算法解决N皇后问题

遗传算法与深度学习实战&#xff08;7&#xff09;——使用遗传算法解决N皇后问题 0. 前言1. N 皇后问题2. 解的表示3. 遗传算法解决 N 皇后问题小结系列链接 0. 前言 进化算法 (Evolutionary Algorithm, EA) 和遗传算法 (Genetic Algorithms, GA) 已成功解决了许多复杂的设计…