STM32之HAL开发——SPI写Flash源码

news2024/11/27 12:43:58

SPI通信收发原理框图(F1系列)在这里插入图片描述

SPI 初始化结构体详解

typedef struct {
uint32_t Mode; /* 设置 SPI 的主/从机端模式 */
uint32_t Direction; /* 设置 SPI 的单双向模式 */
uint32_t DataSize; /* 设置 SPI 的数据帧长度,可选 8/16 位 */
uint32_t CLKPolarity;/* 设置时钟极性 CPOL,可选高/低电平 */
uint32_t CLKPhase; /* 设置时钟相位,可选奇/偶数边沿采样 */
uint32_t NSS; /* 设置 NSS 引脚由 SPI 硬件控制还是软件控制 */
uint32_t BaudRatePrescaler; /* 设置时钟分频因子, fpclk/分频数 =fSCK */
uint32_t FirstBit; /* 设置 MSB/LSB 先行 */
uint32_t TIMode; /* 指定是否启用 TI 模式 */
uint32_t CRCCalculation; /* 指定是否启用 CRC 计算 */
uint32_t CRCPolynomial; /* 设置 CRC 校验的表达式 */
} SPI_InitTypeDef;

这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 标准库中定义的宏:
(1) Mode: 本成员设置 SPI 工作在主机模式 (SPI_MODE_MASTER) 或从机模式 (SPI_MODE_SLAVE ),这两个模式的最大区别为 SPI 的 SCK 信号线的时序, SCK 的时序是由通讯中的主机产生的。若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。

(2) Direction:本成员设置 SPI 的通讯方向,可设置为双线全双工 (SPI_DIRECTION_2LINES),双线只接收 (SPI_DIRECTION_2LINES_RXONLY),单线 SPI_DIRECTION_1LINE。

(3) DataSize: 本成员可以选择 SPI 通讯的数据帧大小是为 8 位 (SPI_DATASIZE_8BIT) 还是 16位 (SPI_DATASIZE_16BIT)。

(4) CLKPolarity 和 CLKPhase:这两个成员配置 SPI 的时钟极性 CLKPolarity 和时钟相位CLKPhase ,这两个配置影响到 SPI 的通讯模式,关于 CLKPolarity 和 CLKPhase 的说明参考前面“通讯模式”小节。时钟极性 CLKPolarity 成员,可设置为高电平 (SPI_POLARITY_HIGH)或低电平 (SPI_POLARITY_LOW)。时钟相位 CPHA 则可以设置为 SPI_PHASE_1EDGE(在SCK 的奇数边沿采集数据) 或 SPI_P HASE_2EDGE(在 SCK 的偶数边沿采集数据) 。

(5) NSS: 本成员配置 NSS 引脚的使用模式,可以选择为硬件模式 (SPI_NSS_HARD ) 与软件模式 ( SPI_NSS_SOFT ),在硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。

(6) BaudRatePrescaler: 本成员设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。这个成员参数可设置为 fpclk 的 2、 4、 6、 8、 16、 32、 64、 128、 256 分频。

(7) FirstBit: 所有串行的通讯协议都会有 MSB 先行 (高位数据在前) 还是 LSB 先行 (低位数据在前) 的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。

(8) TIMode :指定是否启用 TI 模式。可选择为使能 SPI_TIMO DE_ENABLE与不是能SPI_TIMODE_DISABLE

(9) CRCCalculation :指定是否启用 CRC 计算

(10) SPI_CRCPolynomial: 这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这
个成员的参数 (多项式),来计算 CRC 的值。

配置完这些结构体成员后,我们要调用 HAL_SPI_Init 函数把这些参数写入到寄存器中,实现 SPI的初始化,然后调用 __HAL_SPI_ENABLE 来使能 SPI 外设。

SPI 初始化代码

时钟使能以及引脚配置代码

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
  GPIO_InitTypeDef  GPIO_InitStruct;
  
  /*##-1- Enable peripherals and GPIO Clocks #################################*/
  /* Enable GPIO TX/RX clock */
  SPIx_SCK_GPIO_CLK_ENABLE();
  SPIx_MISO_GPIO_CLK_ENABLE();
  SPIx_MOSI_GPIO_CLK_ENABLE();
  SPIx_CS_GPIO_CLK_ENABLE();
  /* Enable SPI clock */
  SPIx_CLK_ENABLE(); 
  
  /*##-2- Configure peripheral GPIO ##########################################*/  
  /* SPI SCK GPIO pin configuration  */
  GPIO_InitStruct.Pin       = SPIx_SCK_PIN;
  GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull      = GPIO_PULLUP;
  GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;

  
  HAL_GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStruct);
    
  /* SPI MISO GPIO pin configuration  */
  GPIO_InitStruct.Pin = SPIx_MISO_PIN;  
  HAL_GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStruct);
  
  /* SPI MOSI GPIO pin configuration  */
  GPIO_InitStruct.Pin = SPIx_MOSI_PIN; 
  HAL_GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStruct);   

  GPIO_InitStruct.Pin = FLASH_CS_PIN ;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  HAL_GPIO_Init(FLASH_CS_GPIO_PORT, &GPIO_InitStruct); 
}

SPI配置代码

void SPI_FLASH_Init(void)
{
  SpiHandle.Instance               = SPIx;
  SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  SpiHandle.Init.Direction         = SPI_DIRECTION_2LINES;
  SpiHandle.Init.CLKPhase          = SPI_PHASE_2EDGE;
  SpiHandle.Init.CLKPolarity       = SPI_POLARITY_HIGH;
  SpiHandle.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE;
  SpiHandle.Init.CRCPolynomial     = 7;
  SpiHandle.Init.DataSize          = SPI_DATASIZE_8BIT;
  SpiHandle.Init.FirstBit          = SPI_FIRSTBIT_MSB;
  SpiHandle.Init.NSS               = SPI_NSS_SOFT;
  SpiHandle.Init.TIMode            = SPI_TIMODE_DISABLE;
  
  SpiHandle.Init.Mode = SPI_MODE_MASTER;

  HAL_SPI_Init(&SpiHandle); 
  
  __HAL_SPI_ENABLE(&SpiHandle);     
}

Flash写使能

 /**
  * @brief  向FLASH发送 写使能 命令
  * @param  none
  * @retval none
  */
void SPI_FLASH_WriteEnable(void)
{
  /* 通讯开始:CS低 */
  SPI_FLASH_CS_LOW();

  /* 发送写使能命令*/
  SPI_FLASH_SendByte(W25X_WriteEnable);

  /*通讯结束:CS高 */
  SPI_FLASH_CS_HIGH();
}

Flash写等待函数

 /**
  * @brief  等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕
  * @param  none
  * @retval none
  */
void SPI_FLASH_WaitForWriteEnd(void)
{
  uint8_t FLASH_Status = 0;

  /* 选择 FLASH: CS 低 */
  SPI_FLASH_CS_LOW();

  /* 发送 读状态寄存器 命令 */
  SPI_FLASH_SendByte(W25X_ReadStatusReg);

  SPITimeout = SPIT_FLAG_TIMEOUT;
  /* 若FLASH忙碌,则等待 */
  do
  {
    /* 读取FLASH芯片的状态寄存器 */
    FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);	 

    {
      if((SPITimeout--) == 0) 
      {
        SPI_TIMEOUT_UserCallback(4);
        return;
      }
    } 
  }
  while ((FLASH_Status & WIP_Flag) == SET); /* 正在写入标志 */

  /* 停止信号  FLASH: CS 高 */
  SPI_FLASH_CS_HIGH();
}

SPI交换一个字节代码

uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
  SPITimeout = SPIT_FLAG_TIMEOUT;

  /* 等待发送缓冲区为空,TXE事件 */
  while (__HAL_SPI_GET_FLAG( &SpiHandle, SPI_FLAG_TXE ) == RESET)
   {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
   }

  /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
  WRITE_REG(SpiHandle.Instance->DR, byte);

  SPITimeout = SPIT_FLAG_TIMEOUT;

  /* 等待接收缓冲区非空,RXNE事件 */
  while (__HAL_SPI_GET_FLAG( &SpiHandle, SPI_FLAG_RXNE ) == RESET)
   {
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
   }

  /* 读取数据寄存器,获取接收缓冲区数据 */
  return READ_REG(SpiHandle.Instance->DR);
}

Flash扇区擦除函数

 /**
  * @brief  擦除FLASH扇区
  * @param  SectorAddr:要擦除的扇区地址
  * @retval 无
  */
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();
  SPI_FLASH_WaitForWriteEnd();
  /* 擦除扇区 */
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();
  /* 发送扇区擦除指令*/
  SPI_FLASH_SendByte(W25X_SectorErase);
  /*发送擦除扇区地址的高位*/
  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
  /* 发送擦除扇区地址的中位 */
  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
  /* 发送擦除扇区地址的低位 */
  SPI_FLASH_SendByte(SectorAddr & 0xFF);
  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();
  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

Flash页写入函数

/**
  * @brief  对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
  * @param	pBuffer,要写入数据的指针
  * @param WriteAddr,写入地址
  * @param  NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize
  * @retval 无
  */
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();
  /* 写页写指令*/
  SPI_FLASH_SendByte(W25X_PageProgram);
  /*发送写地址的高位*/
  SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  /*发送写地址的中位*/
  SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  /*发送写地址的低位*/
  SPI_FLASH_SendByte(WriteAddr & 0xFF);

  if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
  {
     NumByteToWrite = SPI_FLASH_PerWritePageSize;
     FLASH_ERROR("SPI_FLASH_PageWrite too large!");
  }

  /* 写入数据*/
  while (NumByteToWrite--)
  {
    /* 发送当前要写入的字节数据 */
    SPI_FLASH_SendByte(*pBuffer);
    /* 指向下一字节数据 */
    pBuffer++;
  }

  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();

  /* 等待写入完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

SPI缓冲区写入Flash函数

 /**
  * @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
  * @param	pBuffer,要写入数据的指针
  * @param  WriteAddr,写入地址
  * @param  NumByteToWrite,写入数据长度
  * @retval 无
  */
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
	
	/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
  Addr = WriteAddr % SPI_FLASH_PageSize;
	
	/*差count个数据值,刚好可以对齐到页地址*/
  count = SPI_FLASH_PageSize - Addr;	
	/*计算出要写多少整数页*/
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
	/*mod运算求余,计算出剩余不满一页的字节数*/
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

	 /* Addr=0,则WriteAddr 刚好按页对齐 aligned  */
  if (Addr == 0) 
  {
		/* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0) 
    {
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
			/*先把整数页都写了*/
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
			
			/*若有多余的不满一页的数据,把它写完*/
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
    }
  }
	/* 若地址与 SPI_FLASH_PageSize 不对齐  */
  else 
  {
		/* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0) 
    {
			/*当前页剩余的count个位置比NumOfSingle小,写不完*/
      if (NumOfSingle > count) 
      {
        temp = NumOfSingle - count;
				
				/*先写满当前页*/
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;
				
				/*再写剩余的数据*/
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
      {				
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
			/*地址不对齐多出的count分开处理,不加入这个运算*/
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

      SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
      WriteAddr +=  count;
      pBuffer += count;
			
			/*把整数页都写了*/
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
			/*若有多余的不满一页的数据,把它写完*/
      if (NumOfSingle != 0)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

SPI缓冲区读Flash函数

 /**
  * @brief  读取FLASH数据
  * @param 	pBuffer,存储读出数据的指针
  * @param   ReadAddr,读取地址
  * @param   NumByteToRead,读取数据长度
  * @retval 无
  */
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();

  /* 发送 读 指令 */
  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++;
  }

  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();
}

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

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

相关文章

vscode连接服务器步骤

一 、下载vscode 下载vscode 在官网&#xff08;https://code.visualstudio.com/&#xff09;下载VsCode安装vscode 放到自己想安装的盘&#xff0c;最好不要C盘安装中文插件 安装完成后后下角会有提示说重启&#xff0c;点击重启就行。 4. 设置自动保存 点击右上角的文件—…

红杉资本:2024年关于AI的4大预测

四大预测 预测一&#xff1a;Copilot 将逐渐向 AI Agent 转变。 2024 年&#xff0c;AI 将从辅助人类的 Copilot 转变为真正能替代一些人类工作的Agent。AI 将更像是一个同事&#xff0c;而不仅仅是一个工具&#xff0c;这点在软件工程、客服等行业已经初步显现。 预测二&…

快速入门Linux,Linux岗位有哪些?(一)

文章目录 Linux与Linux运维操作系统&#xff1f;操作系统图解 认识LinuxLinux受欢迎的原因什么是Linux运维Linux运维岗位Linux运维岗位职责Linux运维架构师岗位职责Linux运维职业发展路线计算机硬件分类运维人员的三大核心职责 运维人员工作&#xff08;服务器&#xff09;什么…

【自我提升】一、Hyperledger Fabric 概念梳理

写在前面&#xff1a;最近因为业务需要&#xff0c;开始学习Hyperledger Fabric了&#xff0c;做java全栈工程师可真难搞。现在算是啥类型的都在涉及了&#xff0c;现在这个技术啥都不懂&#xff0c;就先开个学习专栏&#xff0c;记录记录。顺带也给各位道友参考参考。 目录 …

crypto-聪明的小

如图 暗示为栅栏密码 差行输入得到flag

【Android Studio3.5.2安装以及错误错误解决】

前言 下面是博主在安装Android studio时遇到的一些问题&#xff0c;并且花费很长时间寻找解决方法&#xff0c;经过了血和泪的教训下面将自己在安装过程中遇到的查看的资料贴出来&#xff08;感谢各位大佬的文章帮助本闲狗解答疑惑&#xff0c;此处贴出原文链接&#xff0c;如…

用python,将有道词典中的生词导入扇贝单词

我试过有道词典和扇贝单词&#xff0c;个人感觉扇贝单词记忆功能非常好用&#xff0c;但是扇贝单词没有pc版&#xff0c;而有道在这方面就做的很好。博主平时都是用有道查生词&#xff0c;那有没有办法将有道词典中的生词导入扇贝中呢&#xff1f;下面的过程看上去很复杂&#…

一文搞定防盗链设计

大家好&#xff0c;我是蓝胖子&#xff0c;在涉及到图片或者视频链接时&#xff0c;通常都会提到防盗链&#xff0c;这一节我将会从防盗链的含义到落地&#xff0c;向大家展示如何设计资源的防盗链。 防盗链的含义与作用 防盗链&#xff0c;顾名思义&#xff0c;是为了防止资…

差分与前缀和

目录 差分法 例题&#xff1a;大学里的树木要打药 前缀和 例题&#xff1a;大学里的树木要维护 差分法 差分法的应用主要是用于处理区间问题&#xff0c;当一个数组要在很多不确定的区间&#xff0c;加上相同的一个数&#xff0c;我们如果每个数都进行加法操作的话&#x…

数据结构01 线性表

#include<stdio.h>/* 如果没有使用&符 void test(int x){ */ void test(int & x){x 1024;printf("test函数内部 x %d\n", x);} int main(){int x 1;printf("调用test前 x %d\n", x);test(x);printf("调用test后 x %d\n", …

4.机器学习-十大算法之一线性回归算法(LinearRegression)案例讲解

机器学习-十大算法之一线性回归算法案例讲解 一摘要二个人简介三什么是线性回归四LinearRegression使用方法五糖尿病数据线性回归预测1.数据说明2.导包3.导入数据4.脱敏处理5.抽取训练数据和预测数据6.创建模型7.预测8.线性回归评估指标9.研究每个特征和标记结果之间的关系.来分…

网络体系结构概述

目录 1. OSI/RM参考模型1.1. 物理层1.2. 数据链路层1.3. 网络层1.4. 传输层1.5. 会话层1.6. 表示层1.7. 应用层 2. TCP/IP参考模型3. 理解OSI七层模型 网络体系结构是线代网络技术的整体蓝图。 1. OSI/RM参考模型 开放互联参考模型&#xff08;Open System Interconnection/…

CH347

动态库封装实例 import ctypes# Load the CH347DLL library ch347dll ctypes.WinDLL(CH347DLLA64.dll) # Update the filename if necessary# Define the argument and return types for CH347OpenDevice ch347dll.CH347OpenDevice.argtypes [ctypes.c_ulong] ch347dll.CH3…

Selinux安全策略文件

在Selinux框架中&#xff0c;安全策略都是写在te文件中&#xff0c;以adb.te 文件为例 allow adbd shell_data_file:dir create_dir_perms;策略的基本格式是&#xff1a; rule_name source_type target_type :object_class perm_setrule_name 规则名。常见的规则名有allow,ne…

无问芯穹 MaaS AI 平台公测免费试用笔记:一

本篇文章聊聊正在公开测试的平台&#xff0c;无问芯穹的 MaaS 服务&#xff0c;包含了平台使用体验和一些小技巧。 因为测试给的免费卡时比较少&#xff0c;估计想完成完整测试或许需要一些时间&#xff0c;额外用一些账号进行。就先记录下常规折腾过程吧&#xff0c;让再次“…

element-ui message 组件源码分享

今日简单分享 message 组件的源码&#xff0c;主要从以下四个方面来分享&#xff1a; 1、message 组件的页面结构 2、message 组件的 options 配置 3、mesage 组件的方法 4、个人总结 一、message 组件的页面结构 二、message 组件的 options 配置 前置说明&#xff1a;m…

Centos7 安装 Oracle19c

下载oracle预安装包 wget http://yum.oracle.com/repo/OracleLinux/OL7/latest/x86_64/getPackage/oracle-database-preinstall-19c-1.0-1.el7.x86_64.rpm 下载19c安装包 https://www.oracle.com/cn/database/technologies/oracle-database-software-downloads.html#19c 选择…

OpenLayers6实战,OpenLayers实现鼠标拖拽绘制三角形,OpenLayers自定义绘制特殊图形

专栏目录: OpenLayers实战进阶专栏目录 前言 本章讲解使用OpenLayers如何绘制三角形。 OpenLayers本身是可以通过多边形绘制来绘制自行绘制三角形的,但是这种绘制方式是通过鼠标点击每个点来实现线条链接的,不支持固定的三角形这种特殊图形绘制的。 因此本章我们通过自定义…

Express.js项目实战(1)—— 我的藏书馆

首先新建文件夹——myLibrary 在vscode中点击文件>点击 Duplicate Workspace(以工作区的方式打开文件夹myLibrary) 点击duplicate Workspace&#xff08;打开工作区&#xff09; 之后&#xff0c;会出现以下界面 点击打开文件夹&#xff0c;选择新建的文件夹&#xff0c;会出…

服务器托管让服务器管理更轻松高效

在信息化飞速发展的今天&#xff0c;服务器作为企业数据处理和信息存储的核心设备&#xff0c;其管理的重要性日益凸显。服务器托管&#xff0c;作为一种高效、专业的服务器管理方式&#xff0c;正逐渐成为众多企业的首选。那么&#xff0c;服务器托管究竟是如何让服务器管理更…