STM32(十):SPI (标准库函数)

前言

上一篇文章已经介绍了如何用STM32单片机中USART通信协议来串口通信,并向XCOM串口助手发送信息。这篇文章我们来介绍一下如何用STM32单片机中SPI接口来实现LED的闪亮并玩转WS2812B灯带。

一、实验原理

串行通信之前的博客里有所介绍,可以查看以下链接

STM32(九):USART串口通信 (标准库函数)-CSDN博客

1.SPI的介绍

SPI(Serial Peripheral interface),顾名思义就是串行外围设备接口。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。

2.SPI的通信原理

通常SPI通过4个引脚与外部器件相连:

● MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。

● MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。

● SCK: 串口时钟,作为主设备的输出,从设备的输入。

● NSS: 从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的NSS引脚可以由主设备的一个标准I/O引脚来驱动。

SPI有主、从两种模式

  • 作为主机时,使用一个IO引脚拉低相应从机的选择引脚(NSS),传输的起始由主机发送数据来启动,时钟(SCK)信号由主机产生。通过MOSI发送数据,同时通过MISO引脚接收从机发出的数据。    
  • 作为从机时,传输在从机选择引脚(NSS)被主机拉低后开始,接收主机输出的时钟信号,在读取主机数据的同时通过MISO引脚输出数据。

下图中简单模拟SPI通信流程,主机拉低NSS片选信号,启动通信,并且产生时钟信号,上升沿触发边沿信号,主机在MOSI线路一位一位发送数据0X53,在MISO线路一位一位接收数据0X46。

3.SPI的时序

数据时钟时序图

通过组合CPOL和CPHA的设置,可以产生四种可能的时序关系,这影响数据的采样和锁存时机。这些设置对SPI主模式和从模式下的设备都适用。

  • CPOL(时钟极性):控制在没有数据传输时SCK(时钟)引脚的空闲状态电平。
  1. CPOL=0:SCK引脚在空闲状态保持低电平。
  2. CPOL=1:SCK引脚在空闲状态保持高电平。
  • CPHA(时钟相位):确定数据位的采样和锁存时机。
  1. CPHA=0:数据在第一个时钟边沿被采样和锁存。第一个边沿是下降沿如果CPOL=0,是上升沿如果CPOL=1。
  2. CPHA=1:数据在第二个时钟边沿被采样和锁存。第二个边沿是下降沿如果CPOL=0,是上升沿如果CPOL=1。

4.SPI中断

SPI共有5种中断事件,如下图所示:

二、实验步骤

1.串行FLASH初始化

void SPI_FLASH_Init(void)
{
  SPI_InitTypeDef  SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;	
  
  /* 使能GPIO和SPI时钟 */
	FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );
  FLASH_SPI_SCK_APBxClock_FUN ( FLASH_SPI_SCK_CLK, ENABLE );
 	FLASH_SPI_MISO_APBxClock_FUN ( FLASH_SPI_MISO_CLK, ENABLE );
  FLASH_SPI_MOSI_APBxClock_FUN ( FLASH_SPI_MOSI_CLK, ENABLE );
  FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK, ENABLE );
  
  /* 配置SPI功能引脚:SCK 时钟引脚 */	
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);

  /* 配置SPI功能引脚:MISO 主机输入从机输出引脚 */	
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
  GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);

  /* 配置SPI功能引脚:MISO 主机输出从机输入引脚 */	
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
  GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
  
  /* 配置SPI功能引脚:CS 串行Flash片选引脚 */	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;	
  GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);

  /* 首先禁用串行Flash,等需要操作串行Flash时再使能即可 */
  FLASH_SPI_CS_DISABLE();

  /* SPI外设配置 */
  /* 
   * FLASH芯片:
	 * 在CLK上升沿时到DIO数据采样输入. 
   * 在CLK下降沿时在DIO进行数据输出。
	 * 据此设置CPOL CPHA 
   */
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  SPI_InitStructure.SPI_CRCPolynomial = 7;
  SPI_Init(FLASH_SPIx , &SPI_InitStructure);

  /* 使能SPI外设  */
  SPI_Cmd(FLASH_SPIx , ENABLE);	
}

2.擦除串行Flash整片空间

void SPI_FLASH_BulkErase(void)
{
 /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

  /* 整片擦除 Erase */
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 发送整片擦除指令*/
  SPI_FLASH_SendByte(W25X_ChipErase);
  /* 禁用串行FLASH: CS高电平 */
  FLASH_SPI_CS_DISABLE();

  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

3.写入数据

调用本函数写入数据前需要先擦除扇区

void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

  Addr = WriteAddr % SPI_FLASH_PageSize;
  count = SPI_FLASH_PageSize - Addr;
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

  if (Addr == 0) /* 若地址与 SPI_FLASH_PageSize 对齐  */
  {
    if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      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);
    }
  }
  else /* 若地址与 SPI_FLASH_PageSize 不对齐 */
  {
    if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
      {
        temp = NumOfSingle - count;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
      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);
      }
    }
  }
}

4.读取数据

void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送 读 指令 */
  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 高电平 */
  FLASH_SPI_CS_DISABLE();
}

5.使能串行Flash写操作

void SPI_FLASH_WriteEnable(void)
{
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

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

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();
}

三、实操代码

程序分为3个文件:bsp_spi_flash.c、bsp_spi_flash.h、main.c

1.bsp_spi_flash.c 

/* 包含头文件 ----------------------------------------------------------------*/
#include "bsp/spi_flash/bsp_spi_flash.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256
#define W25X_WriteEnable		            0x06 
#define W25X_WriteDisable		            0x04 
#define W25X_ReadStatusReg		          0x05 
#define W25X_WriteStatusReg		          0x01 
#define W25X_ReadData			              0x03 
#define W25X_FastReadData		            0x0B 
#define W25X_FastReadDual		            0x3B 
#define W25X_PageProgram		            0x02 
#define W25X_BlockErase			            0xD8 
#define W25X_SectorErase		            0x20 
#define W25X_ChipErase			            0xC7 
#define W25X_PowerDown			            0xB9 
#define W25X_ReleasePowerDown	          0xAB 
#define W25X_DeviceID			              0xAB 
#define W25X_ManufactDeviceID   	      0x90 
#define W25X_JedecDeviceID		          0x9F 

#define WIP_Flag                        0x01  /* Write In Progress (WIP) flag */

#define Dummy_Byte                      0xFF
/* 私有变量 ------------------------------------------------------------------*/
/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/
 
/**
  * 函数功能: 串行FLASH初始化
  * 输入参数: 无
  * 返 回 值: uint32_t:返回串行Flash型号ID
  * 说    明:初始化串行Flash底层驱动GPIO和SPI外设
  */
void SPI_FLASH_Init(void)
{
  SPI_InitTypeDef  SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;	
  
  /* 使能GPIO和SPI时钟 */
	FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );
  FLASH_SPI_SCK_APBxClock_FUN ( FLASH_SPI_SCK_CLK, ENABLE );
 	FLASH_SPI_MISO_APBxClock_FUN ( FLASH_SPI_MISO_CLK, ENABLE );
  FLASH_SPI_MOSI_APBxClock_FUN ( FLASH_SPI_MOSI_CLK, ENABLE );
  FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK, ENABLE );
  
  /* 配置SPI功能引脚:SCK 时钟引脚 */	
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);

  /* 配置SPI功能引脚:MISO 主机输入从机输出引脚 */	
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
  GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);

  /* 配置SPI功能引脚:MISO 主机输出从机输入引脚 */	
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
  GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
  
  /* 配置SPI功能引脚:CS 串行Flash片选引脚 */	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;	
  GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);

  /* 首先禁用串行Flash,等需要操作串行Flash时再使能即可 */
  FLASH_SPI_CS_DISABLE();

  /* SPI外设配置 */
  /* 
   * FLASH芯片:
	 * 在CLK上升沿时到DIO数据采样输入. 
   * 在CLK下降沿时在DIO进行数据输出。
	 * 据此设置CPOL CPHA 
   */
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  SPI_InitStructure.SPI_CRCPolynomial = 7;
  SPI_Init(FLASH_SPIx , &SPI_InitStructure);

  /* 使能SPI外设  */
  SPI_Cmd(FLASH_SPIx , ENABLE);	
}

/**
  * 函数功能: 擦除扇区
  * 输入参数: SectorAddr:待擦除扇区地址,要求为4096倍数
  * 返 回 值: 无
  * 说    明:串行Flash最小擦除块大小为4KB(4096字节),即一个扇区大小,要求输入参数
  *           为4096倍数。在往串行Flash芯片写入数据之前要求先擦除空间。
  */
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();
  SPI_FLASH_WaitForWriteEnd();
  /* 擦除扇区 */
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 发送扇区擦除指令*/
  SPI_FLASH_SendByte(W25X_SectorErase);
  /*发送擦除扇区地址的高位*/
  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
  /* 发送擦除扇区地址的中位 */
  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
  /* 发送擦除扇区地址的低位 */
  SPI_FLASH_SendByte(SectorAddr & 0xFF);
  /* 禁用串行FLASH: CS 高电平 */
  FLASH_SPI_CS_DISABLE();
  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

/**
  * 函数功能: 擦除整片
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:擦除串行Flash整片空间
  */
void SPI_FLASH_BulkErase(void)
{
 /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

  /* 整片擦除 Erase */
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 发送整片擦除指令*/
  SPI_FLASH_SendByte(W25X_ChipErase);
  /* 禁用串行FLASH: CS高电平 */
  FLASH_SPI_CS_DISABLE();

  /* 等待擦除完毕*/
  SPI_FLASH_WaitForWriteEnd();
}

/**
  * 函数功能: 往串行FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
  * 输入参数: pBuffer:待写入数据的指针
  *           WriteAddr:写入地址
  *           NumByteToWrite:写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize
  * 返 回 值: 无
  * 说    明:串行Flash每页大小为256个字节
  */
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

   /* 寻找串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();
  /* 写送写指令*/
  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;
     //printf("Err: SPI_FLASH_PageWrite too large!\n");
  }

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

  /* 禁用串行FLASH: CS 高电平 */
  FLASH_SPI_CS_DISABLE();

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

/**
  * 函数功能: 往串行FLASH写入数据,调用本函数写入数据前需要先擦除扇区
  * 输入参数: pBuffer:待写入数据的指针
  *           WriteAddr:写入地址
  *           NumByteToWrite:写入数据长度
  * 返 回 值: 无
  * 说    明:该函数可以设置任意写入数据长度
  */
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

  Addr = WriteAddr % SPI_FLASH_PageSize;
  count = SPI_FLASH_PageSize - Addr;
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

  if (Addr == 0) /* 若地址与 SPI_FLASH_PageSize 对齐  */
  {
    if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      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);
    }
  }
  else /* 若地址与 SPI_FLASH_PageSize 不对齐 */
  {
    if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */
    {
      if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
      {
        temp = NumOfSingle - count;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;

        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
      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);
      }
    }
  }
}

/**
  * 函数功能: 从串行Flash读取数据
  * 输入参数: pBuffer:存放读取到数据的指针
  *           ReadAddr:读取数据目标地址
  *           NumByteToRead:读取数据长度
  * 返 回 值: 无
  * 说    明:该函数可以设置任意读取数据长度
  */
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送 读 指令 */
  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 高电平 */
  FLASH_SPI_CS_DISABLE();
}

/**
  * 函数功能: 读取串行Flash型号的ID
  * 输入参数: 无
  * 返 回 值: u32:串行Flash的型号ID
  * 说    明:  FLASH_ID      IC型号      存储空间大小         
                0xEF3015      W25X16        2M byte
                0xEF4015	    W25Q16        4M byte
                0XEF4017      W25Q64        8M byte
                0XEF4018      W25Q128       16M byte  (YS-F1Pro开发板默认配置)
  */
u32 SPI_FLASH_ReadID(void)
{
  u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;

  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送命令:读取芯片型号ID */
  SPI_FLASH_SendByte(W25X_JedecDeviceID);

  /* 从串行Flash读取一个字节数据 */
  Temp0 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 从串行Flash读取一个字节数据 */
  Temp1 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 从串行Flash读取一个字节数据 */
  Temp2 = SPI_FLASH_SendByte(Dummy_Byte);

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();
  
  Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
  return Temp;
}

/**
  * 函数功能: 读取串行Flash设备ID
  * 输入参数: 无
  * 返 回 值: u32:串行Flash的设备ID
  * 说    明:
  */
u32 SPI_FLASH_ReadDeviceID(void)
{
  u32 Temp = 0;

  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

  /* 发送命令:读取芯片设备ID * */
  SPI_FLASH_SendByte(W25X_DeviceID);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  
  /* 从串行Flash读取一个字节数据 */
  Temp = SPI_FLASH_SendByte(Dummy_Byte);

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();

  return Temp;
}

/**
  * 函数功能: 启动连续读取数据串
  * 输入参数: ReadAddr:读取地址
  * 返 回 值: 无
  * 说    明:Initiates a read data byte (READ) sequence from the Flash.
  *           This is done by driving the /CS line low to select the device,
  *           then the READ instruction is transmitted followed by 3 bytes
  *           address. This function exit and keep the /CS line low, so the
  *           Flash still being selected. With this technique the whole
  *           content of the Flash is read with a single READ instruction.
  */
void SPI_FLASH_StartReadSequence(u32 ReadAddr)
{
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Read from Memory " instruction */
  SPI_FLASH_SendByte(W25X_ReadData);

  /* Send the 24-bit address of the address to read from -----------------------*/
  /* Send ReadAddr high nibble address byte */
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  /* Send ReadAddr medium nibble address byte */
  SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
  /* Send ReadAddr low nibble address byte */
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
}

/**
  * 函数功能: 从串行Flash读取一个字节数据
  * 输入参数: 无
  * 返 回 值: u8:读取到的数据
  * 说    明:This function must be used only if the Start_Read_Sequence
  *           function has been previously called.
  */
u8 SPI_FLASH_ReadByte(void)
{
  return (SPI_FLASH_SendByte(Dummy_Byte));
}

/**
  * 函数功能: 往串行Flash读取写入一个字节数据并接收一个字节数据
  * 输入参数: byte:待发送数据
  * 返 回 值: u8:接收到的数据
  * 说    明:无
  */
u8 SPI_FLASH_SendByte(u8 byte)
{
  /* 循环等待直到SPI 数据寄存器DR为空,即当DR寄存器不为空时持续等待 */
  while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET);

  /* 通过SPI外设发送一个字节数据 */
  SPI_I2S_SendData(FLASH_SPIx , byte);

  /* 等待接收到数据 */
  while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET);

  /* 读取SPI总线接收到一个字节数据并返回 */
  return SPI_I2S_ReceiveData(FLASH_SPIx );
}

/**
  * 函数功能: 往串行Flash读取写入半字(16bit)数据并接收半字数据
  * 输入参数: byte:待发送数据
  * 返 回 值: u16:接收到的数据
  * 说    明:无
  */
u16 SPI_FLASH_SendHalfWord(u16 HalfWord)
{
  /* 循环等待直到SPI 数据寄存器DR为空,即当DR寄存器不为空时持续等待 */
  while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET);

  /* 通过SPI外设发送半字数据 */
  SPI_I2S_SendData(FLASH_SPIx , HalfWord);

  /* 等待接收到数据 */
  while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET);

  /* 读取SPI总线接收到半字数据并返回 */
  return SPI_I2S_ReceiveData(FLASH_SPIx );
}

/**
  * 函数功能: 使能串行Flash写操作
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_FLASH_WriteEnable(void)
{
  /* 选择串行FLASH: CS低电平 */
  FLASH_SPI_CS_ENABLE();

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

  /* 禁用串行Flash:CS高电平 */
  FLASH_SPI_CS_DISABLE();
}

/**
  * 函数功能: 等待数据写入完成
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:Polls the status of the Write In Progress (WIP) flag in the
  *           FLASH's status  register  and  loop  until write  opertaion
  *           has completed.
  */
void SPI_FLASH_WaitForWriteEnd(void)
{
  u8 FLASH_Status = 0;

  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Read Status Register" instruction */
  SPI_FLASH_SendByte(W25X_ReadStatusReg);

  /* Loop as long as the memory is busy with a write cycle */
  do
  {
    /* Send a dummy byte to generate the clock needed by the FLASH
    and put the value of the status register in FLASH_Status variable */
    FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);	 
  }
  while ((FLASH_Status & WIP_Flag) == SET); /* Write in progress */

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE();
}


/**
  * 函数功能: 进入掉电模式
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_Flash_PowerDown(void)   
{ 
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Power Down" instruction */
  SPI_FLASH_SendByte(W25X_PowerDown);

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE();
}   

/**
  * 函数功能: 唤醒串行Flash
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
void SPI_Flash_WAKEUP(void)   
{
  /* Select the FLASH: Chip Select low */
  FLASH_SPI_CS_ENABLE();

  /* Send "Power Down" instruction */
  SPI_FLASH_SendByte(W25X_ReleasePowerDown);

  /* Deselect the FLASH: Chip Select high */
  FLASH_SPI_CS_DISABLE(); 
}   
   

2.bsp_spi_flash.h

#ifndef __SPI_FLASH_H__
#define __SPI_FLASH_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include <stm32f10x.h>

/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/
//#define  SPI_FLASH_ID                       0xEF3015     //W25X16
//#define  SPI_FLASH_ID                       0xEF4015	    //W25Q16
//#define  SPI_FLASH_ID                       0XEF4017     //W25Q64
#define  SPI_FLASH_ID                       0XEF4018     //W25Q128  YS-F1Pro开发默认使用

/************************** SPI Flash 连接引脚定义********************************/
#define FLASH_SPIx                        SPI1
#define FLASH_SPI_APBxClock_FUN           RCC_APB2PeriphClockCmd
#define FLASH_SPI_CLK                     RCC_APB2Periph_SPI1

#define FLASH_SPI_SCK_APBxClock_FUN       RCC_APB2PeriphClockCmd
#define FLASH_SPI_SCK_CLK                 RCC_APB2Periph_GPIOA   
#define FLASH_SPI_SCK_PORT                GPIOA   
#define FLASH_SPI_SCK_PIN                 GPIO_Pin_5

#define FLASH_SPI_MISO_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define FLASH_SPI_MISO_CLK                RCC_APB2Periph_GPIOA    
#define FLASH_SPI_MISO_PORT               GPIOA 
#define FLASH_SPI_MISO_PIN                GPIO_Pin_6

#define FLASH_SPI_MOSI_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define FLASH_SPI_MOSI_CLK                RCC_APB2Periph_GPIOA    
#define FLASH_SPI_MOSI_PORT               GPIOA 
#define FLASH_SPI_MOSI_PIN                GPIO_Pin_7

#define FLASH_SPI_CS_APBxClock_FUN        RCC_APB2PeriphClockCmd
#define FLASH_SPI_CS_CLK                  RCC_APB2Periph_GPIOA    
#define FLASH_SPI_CS_PORT                 GPIOA
#define FLASH_SPI_CS_PIN                  GPIO_Pin_4

#define FLASH_SPI_CS_ENABLE()             GPIO_ResetBits(FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN)
#define FLASH_SPI_CS_DISABLE()            GPIO_SetBits(FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN)


#define CALIBRATE_DATA_ADDR               2*4096

/* 扩展变量 ------------------------------------------------------------------*/
/* 函数声明 ------------------------------------------------------------------*/
void SPI_FLASH_Init(void);
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
void SPI_FLASH_BulkErase(void);
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
uint32_t SPI_FLASH_ReadID(void);
uint32_t SPI_FLASH_ReadDeviceID(void);
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);

uint8_t SPI_FLASH_ReadByte(void);
uint8_t SPI_FLASH_SendByte(uint8_t byte);
uint16_t SPI_FLASH_SendHalfWord(uint16_t HalfWord);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);

#endif /* __SPI_FLASH_H__ */

3.main.c

  /* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f10x.h"
#include "bsp/led/bsp_led.h"
#include "bsp/usart/bsp_debug_usart.h"
#include "bsp/spi_flash/bsp_spi_flash.h"

/* 私有类型定义 --------------------------------------------------------------*/
typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;
/* 私有宏定义 ----------------------------------------------------------------*/
/* 获取缓冲区的长度 */
#define countof(a)      (sizeof(a) / sizeof(*(a)))
#define TxBufferSize1   (countof(TxBuffer1) - 1)
#define RxBufferSize1   (countof(TxBuffer1) - 1)
#define BufferSize      (countof(Tx_Buffer)-1)

#define  FLASH_WriteAddress     0x00000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress

/* 私有变量 ------------------------------------------------------------------*/
/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = " 感谢您选用硬石stm32开发板\n今天是个好日子";
uint8_t Rx_Buffer[BufferSize];

__IO uint32_t DeviceID = 0;
__IO uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
static void Delay(uint32_t time);
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength);

/* 函数体 --------------------------------------------------------------------*/

/**
  * 函数功能: 主函数.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
int main(void)
{   
  /* 调试串口初始化配置,115200-N-8-1.使能串口发送和接受 */
  DEBUG_USART_Init();  
	/*初始化LED*/
  LED_GPIO_Init();  
  
  /* 调用格式化输出函数打印输出数据 */
  printf("这是一个16M byte串行flash(W25Q128)读写测试实验\n");  
  
  /* 16M串行flash W25Q128初始化 */
	SPI_FLASH_Init();
	
	/* Get SPI Flash Device ID */
	DeviceID = SPI_FLASH_ReadDeviceID();
	
	Delay( 1 );
	
	/* Get SPI Flash ID */
	FlashID = SPI_FLASH_ReadID();
	
	printf("FlashID is 0x%X,  Manufacturer Device ID is 0x%X\n", FlashID, DeviceID);
	
	/* Check the SPI Flash ID */
	if (FlashID == SPI_FLASH_ID)  /* #define  sFLASH_ID  0XEF4018 */
	{	
		printf("检测到华邦串行flash W25Q128 !\n");
		
		/* 擦除SPI的扇区以写入 */
		SPI_FLASH_SectorErase(FLASH_SectorToErase);	 	 
		
		/* 将发送缓冲区的数据写到flash中 */ 	
		SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);
		SPI_FLASH_BufferWrite(Tx_Buffer, 252, BufferSize);
		printf("写入的数据为:\n%s \n", Tx_Buffer);
		
		/* 将刚刚写入的数据读出来放到接收缓冲区中 */
		SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);
		printf("读出的数据为:\n %s\n", Rx_Buffer);
		
		/* 检查写入的数据与读出的数据是否相等 */
		TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
		
		if( PASSED == TransferStatus1 )
		{    
			printf("16M串行flash(W25Q128)测试成功!\r");
			LED1_ON;
		}
		else
		{        
			printf("16M串行flash(W25Q128)测试失败!\r");
			LED2_ON;
		}
	}
	else
	{    
		printf("获取不到 W25Q128 ID!\n");
		LED3_ON;
	}
  
  /* 无限循环 */
  while (1)
  {    
  }
}

/*
 * 函数名:Buffercmp
 * 描述  :比较两个缓冲区中的数据是否相等
 * 输入  :-pBuffer1     src缓冲区指针
 *         -pBuffer2     dst缓冲区指针
 *         -BufferLength 缓冲区长度
 * 输出  :无
 * 返回  :-PASSED pBuffer1 等于   pBuffer2
 *         -FAILED pBuffer1 不同于 pBuffer2
 */
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
  while(BufferLength--)
  {
    if(*pBuffer1 != *pBuffer2)
    {
      return FAILED;
    }

    pBuffer1++;
    pBuffer2++;
  }
  return PASSED;
}


四、实验效果

这边展示的是用SPI点亮WS2812b,展示了红色和青色两种效果,如果对WS2812b有兴趣的同学可以自行去了解一下,点亮方式还可以使用PWM波,可以看下这个。
WS2812B彩灯 STM32HAL库开发:PWM+DMA(stm32f103c8t6)_ws2812编程实例-CSDN博客

参考博客:

代码用的是硬石嵌入式开发团队

结束语

本文以STM32VET6为例讲解了如何用STM32单片机中SPI接口来实现LED的闪亮并玩转WS2812B灯带,并指出其中的易坑点。希望对大家有所帮助!如果还有什么问题,欢迎评论区留言,谢谢!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

回退背包专题

P4141 消失之物 题目意思&#xff0c;就是说有n个物品&#xff0c;然后每个物品都有自己的体积w[i]&#xff0c;然后问你&#xff0c;如果第i个物品丢了之后&#xff0c;还能够装满这个背包的方法&#xff0c;然后遍历一遍i同时也要遍历一遍背包&#xff0c;因为背包的值是在1到…

python数据分析——datetime数据类型2

参考资料&#xff1a;活用pandas库 # 导入pandas库 import pandas as pd # 加载数据集 teslapd.read_csv(r"...\data\tesla_stock_yahoo.csv") # 查看数据 print(tesla.head()) 1、基于日期取数据子集 # 将Date数据列转换为datetime类型 tesla[Date]pd.to_datetime…

【Linux 网络编程】OSI 七层模型初识、网络传输的流程、IP地址和MAC地址!

文章目录 1. OSI七层模型2. TCP/IP五层(或四层)模型3. 网络传输基本流程 &#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#x1f427;&#…

Golang | Leetcode Golang题解之第127题单词接龙

题目&#xff1a; 题解&#xff1a; func ladderLength(beginWord string, endWord string, wordList []string) int {wordId : map[string]int{}graph : [][]int{}addWord : func(word string) int {id, has : wordId[word]if !has {id len(wordId)wordId[word] idgraph a…

Flink系列三:Flink架构、独立集群搭建及Flink on YARN模式详解

一、Flink架构 Flink 是一个分布式系统&#xff0c;需要有效分配和管理计算资源才能执行流应用程序。它集成了所有常见的集群资源管理器&#xff0c;例如Hadoop yarn&#xff0c;但也可以设置作为独立集群甚至库运行。 Flink 集群剖析 Flink 运行时由两种类型的进程组成&…

数据分析常用模型合集(一)AARRR模型和漏斗模型

准备把常用的数据分析模型&#xff0c;像什么AARRR&#xff0c;RFM之类的&#xff0c;逐个全部写一下&#xff1b; 最好能带点案例和代码&#xff0c;搞一个小合集。 最终达到完全不懂的人&#xff0c;看完就能知道得差不多&#xff1b; 数据分析常用模型合集&#xff08;二…

TiDB-从0到1-分布式存储

TiDB从0到1系列 TiDB-从0到1-体系结构TiDB-从0到1-分布式存储TiDB-从0到1-分布式事务TiDB-从0到1-MVCC 一、TiDB-DML语句执行流程&#xff08;增删改&#xff09; DML流程概要 1、协议验证 用户连接到TiDB Server后首先工作的是Protocol Layer模块&#xff0c;该模块会对用…

FuTalk设计周刊-Vol.046

# AI漫谈 热点捕手 1、Stable Diffusion 可以生成透明的 PNG 图片了 用 SD 直接生成透明的 PNG 图片&#xff0c;也可以直接生成带有透明度分层的图片&#xff0c;LayerDiffusion 使得大型已经过预训练的潜在扩散模型&#xff08;latent diffusion model&#xff09;能够创造…

docker学习--最详细的docker run 各子命令解释与应用

文章目录 docker run应用docker run -it那怎样才能退出容器而不用容器关闭呢 docker run -d-p-P--name docker run 容器运行命令 docker run 常见的子命令及其含义 -i 交互式&#xff0c;和-t一起使用 -t 打开一个终端 -d 后台运行 -p/-P 暴露容器中的服务端口 –name 指定容…

计算机组成原理----浮点数的表示和运算

目录 一.浮点数的表示 1.浮点数的作用和基本原理 2.浮点数的规格化 3.浮点数的表示范围 二.IEEE 754标准 三.浮点数的加减运算 1.加减运算 2.强制类型转换 一.浮点数的表示 1.浮点数的作用和基本原理 定点数在字节数固定的情况下&#xff0c;能表示的数字是很有限的&…

C++ | Leetcode C++题解之第128题最长连续序列

题目&#xff1a; 题解&#xff1a; class Solution { public:int longestConsecutive(vector<int>& nums) {unordered_set<int> num_set;for (const int& num : nums) {num_set.insert(num);}int longestStreak 0;for (const int& num : num_set) {…

SAP PP学习笔记15 - MTS(Make-to-Stock) 按库存生产(策略11,策略30)

上一章学习了MTS&#xff08;Make-to-Stock&#xff09;按库存生产&#xff08;策略10&#xff09;。 SAP PP学习笔记14 - MTS&#xff08;Make-to-Stock) 按库存生产&#xff08;策略10&#xff09;&#xff0c;以及生产计划的概要-CSDN博客 本章继续讲MTS&#xff08;Make-t…

Prism 入门01,基础

Prism 框架是支持多平台的一种MVVM框架(Model-View-ViewModel) 除了具备一些基础的属性通知绑定,命令操作,消息聚合器等功能外。还具备一些强大的功能:例如,区域,导航,会话服务,模块注入等特性。 一.如何在WPF 项目中使用Prism 框架 1.打开Visual Studio 2022,选择创…

Java | Leetcode Java题解之第128题最长连续序列

题目&#xff1a; 题解&#xff1a; class Solution {public int longestConsecutive(int[] nums) {Set<Integer> num_set new HashSet<Integer>();for (int num : nums) {num_set.add(num);}int longestStreak 0;for (int num : num_set) {if (!num_set.contai…

边缘计算的AI小板——OrangePi AI Pro

简介 OrangePi AI Pro是一款基于Allwinner H6处理器的嵌入式AI计算设备&#xff0c;适用于物联网和边缘计算。它具有强大的性能、低功耗、多接口和小尺寸。 本文分为三个部分&#xff1a; 一、对该板进行简单的开箱介绍。 二、 将SD卡中的系统迁移到由于该板支持SD卡、SSD…

Python代码关系图生成,帮助快速熟悉一个项目

一、静态代码关系图 工具1、pyreverse pyreverse 是一个由 Logilab 开发的 Python 工具&#xff0c;它能够自动生成 UML (统一建模语言) 类图&#xff0c;这些类图基于 Python 源代码。pyreverse 可以分析 Python 代码&#xff0c;并从中提取出类、模块、函数、方法和它们之间…

Java项目:94 springboot大学城水电管理系统

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本管理系统有管理员和用户。 本大学城水电管理系统管理员功能有个人中心&#xff0c;用户管理&#xff0c;领用设备管理&#xff0c;消耗设备…

docker和docker-compose的安装

docker的安装 1.安装 curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun2.设置开机自启动 systemctl start docker #启动docker systemctl enable docker3.配置阿里云镜像 不配置镜像的话&#xff0c;进行 docker pull 等操作会比较慢。进入阿里云&…

【MySQL】聊聊order by 是如何排序的

CREATE TABLE t (id int(11) NOT NULL,city varchar(16) NOT NULL,name varchar(16) NOT NULL,age int(11) NOT NULL,addr varchar(128) DEFAULT NULL,PRIMARY KEY (id),KEY city (city) ) ENGINEInnoDB;构建一个表结构&#xff0c;以及数据。 本篇主要来分析下order by是如何进…

SpringBoot 定时任务+Quartz

1、分部解释2、整体代码 前言&#xff1a; 1、定时任务技术&#xff1a; JDK 的 Timer&#xff0c; 定义多个定时任务&#xff0c;其中某个任务出现异常&#xff0c;当时整个定时任务终止。Spring Task &#xff0c; 不支持 持久化与分布式部署&#xff0c;所有任务是单线程执行…