stm32USB之模拟U盘

2023-11-19

STMF0+W25Q32模拟U盘

1.第一次写博客,如有错误,请及时指正,如有表达不通顺的地方,敬请谅解。
2.本篇文章主要描述如何使用STM32cube配置USB,使用的主控为STM32F072,Flash为W25Q32,使用的主控RAM只有16K,所以不使用太多外设,也没有使用文件系统。
3.此段说明制作U盘的目的,不感兴趣可以忽略。由于主控ROM只有128K(项目需要,不可更换),需要将图片、字库等内容存储在W25Q32中,仅有一个串口,还被WiFi模块占用。因此上网查询资料,查询到了很多关于U盘制作的资料。有的使用原子的编程方式(看不懂);有的使用标准库(我需要使用的是HAL库);有的是STMF1+文件系统+Flash(F1不合适,并且整个工程太大,16KRAM不够用)。综上,选择自己造轮子,别人家法拉利的轮子太奢华,不能装在自家的拖拉机上。不管别人家的轮子多么好看,不适合自己就是不适合自己。但是很感谢这些造轮子的人,让我有了造拖拉机轮子的思路,文末将会贴上他们文章的链接,多看多写多想。

步骤如下

  1. 打开cube软件,芯片选择STM32F072CBT6

  2. 使用内部8M晶振,所以不进行配置RCC。不进行调试,USART也不配置

  3. 配置USB在这里插入图片描述

  4. 配置USB_DEVICE在这里插入图片描述

  5. 时钟树设置未进行配置
    在这里插入图片描述

  6. 生成文件在这里插入图片描述

加入SPI控制程序

没有进行spi配置,毕竟还要加入flash扇区擦除、缓冲区写入等函数。spi控制程序选择野火的,在此感谢野火。在这里插入图片描述
野火的程序都可以在淘宝下载,还有很多资料,有很大的参考价值。

最重要的是在usb文件中加入读写函数

修改部分程序

STORAGE_BLK_SIZ 扇区大小为4096
STORAGE_BLK_NBR 总共有1024个扇区
在这里插入图片描述

加入读取、擦除、写入函数

每次写入数据前,需将对应的扇区进行擦除
在这里插入图片描述

关键代码

main.c

int main(void)
{
  SystemClock_Config();
  MX_GPIO_Init();               //usb引脚使用了PA口
  FLASH_GPIO_Init();            
  SPI_FLASH_Init();
  MX_USB_DEVICE_Init();
  while(1)
  {
  }
}




void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSI48;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL12;
  RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV2;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USB;
  PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;

  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

stm32f0xx_it.h

/* External variables --------------------------------------------------------*/
extern PCD_HandleTypeDef hpcd_USB_FS;

/**
  * @brief This function handles USB global interrupt / USB wake-up interrupt through EXTI line 18.
  */
void USB_IRQHandler(void)
{
  /* USER CODE BEGIN USB_IRQn 0 */

  /* USER CODE END USB_IRQn 0 */
  HAL_PCD_IRQHandler(&hpcd_USB_FS);
  /* USER CODE BEGIN USB_IRQn 1 */

  /* USER CODE END USB_IRQn 1 */
}
/* Includes ------------------------------------------------------------------*/
#include "usbd_storage_if.h"

#include "bsp_spi_flash.h"

//#define STORAGE_LUN_NBR                  1
//#define STORAGE_BLK_NBR                  0x10000
//#define STORAGE_BLK_SIZ                  0x2000     

#define STORAGE_LUN_NBR                  1
#define STORAGE_BLK_NBR                  1024
#define STORAGE_BLK_SIZ                  4096

const int8_t STORAGE_Inquirydata_FS[] = {/* 36 */
  
  /* LUN 0 */
  0x00,
  0x80,
  0x02,
  0x02,
  (STANDARD_INQUIRY_DATA_LEN - 5),
  0x00,
  0x00,	
  0x00,
  'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer : 8 bytes */
  'P', 'r', 'o', 'd', 'u', 'c', 't', ' ', /* Product      : 16 Bytes */
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  '0', '.', '0' ,'1'                      /* Version      : 4 Bytes */
}; 

extern USBD_HandleTypeDef hUsbDeviceFS;


static int8_t STORAGE_Init_FS(uint8_t lun);
static int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size);
static int8_t STORAGE_IsReady_FS(uint8_t lun);
static int8_t STORAGE_IsWriteProtected_FS(uint8_t lun);
static int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_GetMaxLun_FS(void);

/**
  * @}
  */
USBD_StorageTypeDef USBD_Storage_Interface_fops_FS =
{
  STORAGE_Init_FS,
  STORAGE_GetCapacity_FS,
  STORAGE_IsReady_FS,
  STORAGE_IsWriteProtected_FS,
  STORAGE_Read_FS,
  STORAGE_Write_FS,
  STORAGE_GetMaxLun_FS,
  (int8_t *)STORAGE_Inquirydata_FS
};

/* Private functions ---------------------------------------------------------*/
/**
  * @brief  Initializes over USB FS IP
  * @param  lun:
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Init_FS(uint8_t lun)
{
  /* USER CODE BEGIN 2 */
	
  return (USBD_OK);
  /* USER CODE END 2 */
}

/**
  * @brief  .
  * @param  lun: .
  * @param  block_num: .
  * @param  block_size: .
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */
  *block_num  = STORAGE_BLK_NBR;
  *block_size = STORAGE_BLK_SIZ;
  return (USBD_OK);
  /* USER CODE END 3 */
}

/**
  * @brief  .
  * @param  lun: .
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_IsReady_FS(uint8_t lun)
{
  /* USER CODE BEGIN 4 */
//  if(SPI_FLASH_ReadID() == sFLASH_ID)
    return (USBD_OK);
//	else
//		return -1;
  /* USER CODE END 4 */
}

/**
  * @brief  .
  * @param  lun: .
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
  /* USER CODE BEGIN 5 */
  return (USBD_OK);
  /* USER CODE END 5 */
}

/**
  * @brief  .
  * @param  lun: .
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
	SPI_FLASH_BufferRead(buf, blk_addr*STORAGE_BLK_SIZ, blk_len*STORAGE_BLK_SIZ);
  return (USBD_OK);
  /* USER CODE END 6 */
}

/**
  * @brief  .
  * @param  lun: .
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
	SPI_FLASH_SectorErase(blk_addr*STORAGE_BLK_SIZ);
  SPI_FLASH_BufferWrite(buf, blk_addr*STORAGE_BLK_SIZ,blk_len*STORAGE_BLK_SIZ);
  return (USBD_OK);
  /* USER CODE END 7 */
}

/**
  * @brief  .
  * @param  None
  * @retval .
  */
int8_t STORAGE_GetMaxLun_FS(void)
{
  /* USER CODE BEGIN 8 */
  return (STORAGE_LUN_NBR - 1);
  /* USER CODE END 8 */
}

缩减篇幅,删除了一些cube生成的无关注释

SPI程序也添上吧

spi.c

#include "bsp_spi_flash.h"

SPI_HandleTypeDef SpiHandle;
static __IO uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;   

static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);

/**
  * @brief SPI MSP Initialization 
  *        This function configures the hardware resources used in this example: 
  *           - Peripheral's clock enable
  *           - Peripheral's GPIO Configuration  
  * @param hspi: SPI handle pointer
  * @retval None
  */
void FLASH_GPIO_Init(void)
{
  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_FLASH_CS_HIGH();
}


void SPI_FLASH_Init(void)
{
  SpiHandle.Instance               = SPIx;
  SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  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);     
}


 /**
  * @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();
}

 /**
  * @brief  擦除FLASH扇区,整片擦除
  * @brief  页为编程单位,可以一次性编程1个到256个字节
  * @brief  超过256个字节要分次写入
  * @brief  写入数据之前,必须对对应的区域进行擦除操作
  * @brief  擦除的最小单位是“扇区”
  * @param  无
  * @retval 无
  */
void SPI_FLASH_BulkErase(void)
{
  /* 发送FLASH写使能命令 */
  SPI_FLASH_WriteEnable();

  /* 整块 Erase */
  /* 选择FLASH: CS低电平 */
  SPI_FLASH_CS_LOW();
  /* 发送整块擦除指令*/
  SPI_FLASH_SendByte(W25X_ChipErase);
  /* 停止信号 FLASH: CS 高电平 */
  SPI_FLASH_CS_HIGH();

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

 /**
  * @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();
}


 /**
  * @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);
      }
    }
  }
}

 /**
  * @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_ReadByte();
    /* 指向下一个字节缓冲区 */
    pBuffer++;
  }

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

 /**
  * @brief  读取FLASH ID
  * @param 	无
  * @retval FLASH ID
  */
uint32_t SPI_FLASH_ReadID(void)
{
  uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;

  /* 开始通讯:CS低电平 */
  SPI_FLASH_CS_LOW();

	/* 发送JEDEC指令,读取ID */
  SPI_FLASH_SendByte(W25X_JedecDeviceID);
	
	/* 读取一个字节数据 */
	Temp0 = SPI_FLASH_ReadByte();
	
	/* 读取一个字节数据 */
  Temp1 = SPI_FLASH_ReadByte();
	
	/* 读取一个字节数据 */
  Temp2 = SPI_FLASH_ReadByte();

  /* 停止通讯:CS高电平 */
  SPI_FLASH_CS_HIGH();

	/*把数据组合起来,作为函数的返回值*/
  Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;

  return Temp;
}


 /**
  * @brief  读取FLASH Device ID
  * @param 	无
  * @retval FLASH Device ID
  */
uint32_t SPI_FLASH_ReadDeviceID(void)
{
  uint32_t Temp = 0;

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

  /* Send "RDID " instruction */
	SPI_FLASH_SendByte(W25X_DeviceID);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  
  /* Read a byte from the FLASH */
  Temp = SPI_FLASH_ReadByte();
	
  /* Deselect the FLASH: Chip Select high */
  SPI_FLASH_CS_HIGH();

  return Temp;
}
/*******************************************************************************
* Function Name  : SPI_FLASH_StartReadSequence
* Description    : 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.
* Input          : - ReadAddr : FLASH's internal address to read from.
* Output         : None
* Return         : None
*******************************************************************************/
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr)
{
  /* Select the FLASH: Chip Select low */
  SPI_FLASH_CS_LOW();

  /* 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);
}



 /**
  * @brief  使用SPI读取一个字节的数据
  * @param  无
  * @retval 返回接收到的数据
  */
uint8_t SPI_FLASH_ReadByte(void)
{
	uint8_t data = 0xFF;	 
	 
//  SPITimeout = SPIT_FLAG_TIMEOUT;

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

  /* 读取数据寄存器,获取接收缓冲区数据 */
	HAL_SPI_Receive(&SpiHandle,&data,1,0xFFFF);
  return data;
}


 /**
  * @brief  使用SPI发送一个字节的数据
  * @param  byte:要发送的数据
  * @retval 返回接收到的数据
  */
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);
//   }

  /* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
	HAL_SPI_Transmit(&SpiHandle,&byte,1,0xFFFF);
	return NULL;
}


/*******************************************************************************
* Function Name  : SPI_FLASH_SendHalfWord
* Description    : Sends a Half Word through the SPI interface and return the
*                  Half Word received from the SPI bus.
* Input          : Half Word : Half Word to send.
* Output         : None
* Return         : The value of the received Half Word.
*******************************************************************************/
//uint16_t SPI_FLASH_SendHalfWord(uint16_t HalfWord)
//{
//  
//  SPITimeout = SPIT_FLAG_TIMEOUT;

//  /* Loop while DR register in not emplty */
//  while (__HAL_SPI_GET_FLAG( &SpiHandle,  SPI_FLAG_TXE ) == RESET)
//  {
//    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(2);
//   }

//  /* Send Half Word through the SPIx peripheral */
//  WRITE_REG(SpiHandle.Instance->DR, HalfWord);

//  SPITimeout = SPIT_FLAG_TIMEOUT;

//  /* Wait to receive a Half Word */
//  while (__HAL_SPI_GET_FLAG( &SpiHandle, SPI_FLAG_RXNE ) == RESET)
//   {
//    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(3);
//   }
//  /* Return the Half Word read from the SPI bus */
//  return READ_REG(SpiHandle.Instance->DR);
//}


 /**
  * @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();
}

 /**
  * @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_ReadByte();

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

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


//进入掉电模式
void SPI_Flash_PowerDown(void)   
{ 
  /* 选择 FLASH: CS 低 */
  SPI_FLASH_CS_LOW();

  /* 发送 掉电 命令 */
  SPI_FLASH_SendByte(W25X_PowerDown);

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

//唤醒
void SPI_Flash_WAKEUP(void)   
{
  /*选择 FLASH: CS 低 */
  SPI_FLASH_CS_LOW();

  /* 发上 上电 命令 */
  SPI_FLASH_SendByte(W25X_ReleasePowerDown);

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


/**
  * @brief  等待超时回调函数
  * @param  None.
  * @retval None.
  */
static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
  /* 等待超时后的处理,输出错误信息 */
//	FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
  return 0;
}
   
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

spi.h

#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H

#include "stm32f0xx.h"
#include "main.h"
#include <stdio.h>

/* Private typedef -----------------------------------------------------------*/
//#define  sFLASH_ID                       0xEF3015     //W25X16
//#define  sFLASH_ID                       0xEF4015	    //W25Q16
#define  sFLASH_ID                        0XEF4016      //W25Q32
//#define  sFLASH_ID                       0XEF4017     //W25Q64
//#define  sFLASH_ID                       0XEF4018     //W25Q128


//#define SPI_FLASH_PageSize            4096
#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256

/* Private define ------------------------------------------------------------*/
/*命令定义-开头*******************************/
#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
/*命令定义-结尾*******************************/


#define SPIx                             SPI2
#define SPIx_CLK_ENABLE()                __HAL_RCC_SPI2_CLK_ENABLE()
#define SPIx_SCK_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPIx_MISO_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE() 
#define SPIx_MOSI_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE() 
#define SPIx_CS_GPIO_CLK_ENABLE()        __HAL_RCC_GPIOA_CLK_ENABLE() 

#define SPIx_FORCE_RESET()               __HAL_RCC_SPI2_FORCE_RESET()
#define SPIx_RELEASE_RESET()             __HAL_RCC_SPI2_RELEASE_RESET()

/* Definition for SPIx Pins */
#define SPIx_SCK_PIN                     GPIO_PIN_13
#define SPIx_SCK_GPIO_PORT               GPIOB

#define SPIx_MISO_PIN                    GPIO_PIN_14
#define SPIx_MISO_GPIO_PORT              GPIOB

#define SPIx_MOSI_PIN                    GPIO_PIN_15
#define SPIx_MOSI_GPIO_PORT              GPIOB

#define FLASH_CS_PIN                     GPIO_PIN_12               
#define FLASH_CS_GPIO_PORT               GPIOB                  


#define	digitalHi(p,i)			    {p->BSRR=i;}			  //设置为高电平		
#define digitalLo(p,i)			    {p->BSRR=(uint32_t)i << 16;}	//输出低电平
#define SPI_FLASH_CS_LOW()      digitalLo(FLASH_CS_GPIO_PORT,FLASH_CS_PIN )
#define SPI_FLASH_CS_HIGH()     digitalHi(FLASH_CS_GPIO_PORT,FLASH_CS_PIN )
/*SPI接口定义-结尾****************************/

/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))

/*信息输出*/
#define FLASH_DEBUG_ON         1

//#define FLASH_INFO(fmt,arg...)           printf("<<-FLASH-INFO->> "fmt"\n",##arg)
//#define FLASH_ERROR(fmt,arg...)          printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
//#define FLASH_DEBUG(fmt,arg...)          do{\
//                                          if(FLASH_DEBUG_ON)\
//                                          printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
//                                          }while(0)


void FLASH_GPIO_Init(void);
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);

uint32_t HAL_W25QXX_ReadID(void);
#endif /* __SPI_FLASH_H */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

关于堆栈大小

很多文章介绍的是需要修改堆栈大小在这里插入图片描述
我也进行了修改,因为cube配置的工程比较难移植到其他程序中。当我测试完毕后,就将usb所需要的文件进行整理,移植到自己建立的工程当中。此时忘记修改堆栈大小,而U盘还能进行读取,未出现错误,这些问题还将继续探索。

实现效果

据其他博主介绍,没有文件系统,需先将U盘格式化,当时电脑的确提示格式化U盘。
在这里插入图片描述
W25Q32只有4MBYTE、32Mbit大小。
在这里插入图片描述
经测试,断电可保存数据

贴上其他文章

链接: https://blog.csdn.net/a3748622/article/details/80347730.
链接: http://news.eeworld.com.cn/mcu/2018/ic-news070140139.html.
其实还有很多优质文章的,一下子找不着了,之后看下能不能补上。
后续也将多写博客,记录自己的学习过程、疑问。

有些屌丝总是嫉妒我
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

stm32USB之模拟U盘 的相关文章

  • 如何通过http打开远程sqlite数据库?

    是否可以通过 http 打开 sqlite 文件 我只需要读取数据库 并希望我可以做类似的事情 var dbFile File new File http 10 1 1 50 project db sqlite sqlConnection
  • 如何添加链接到 Flash 横幅

    我主要是一名开发人员 不知道如何使用 Adob e Flash CS4 有没有一种简单的方法可以将链接添加到 Flash 横幅 我有 flv 文件 其中包含库和两层中的一些项目 请给我一步一步的指示 编辑 我找到简单的解决方案 步骤如下 G
  • 将 Flash (AS3) 数据保存为 XML

    我在互联网上 包括 Stack Overflow 花了好几个小时 试图找到一个可靠的 可行的示例 将 Flash 中的信息保存到 XML 文件中 我想获取两种不同类型对象的位置并将每个对象的列表导出到 XML 我们将调用这些对象ball a
  • 用于嵌入 flashplayer 的 swfobject 的替代方案

    有谁知道 swfobject 是否有更好的替代品 我实际上很喜欢 swfobject 我只是想听听是否有人找到更好的东西 或者也许这是最好的方法 如果您不知道 swfobject 您可以在这里找到它 http code google com
  • Html5 视频和 Flash 方法

    研究 HTML5 视频标签 并研究哪些浏览器支持哪些视频文件类型 我最初的想法是事情变得比仅仅使用 Flash 更困难 我想知道是否有人已经找到一些骨架代码 与视频的开发方法相结合 来执行以下操作 如果闪光灯可用 请使用它 如果没有 请尝试
  • 使用selenium点击swf

    我正在尝试使用 Selenium IDE 单击 html 页面中的 swf DIV id 和 Embed id 的变化取决于时间戳 例如id FLASH 0 23458974594 我尝试过使用id FLASH 0 9 0 9 0 9 or
  • AsyncTask 也在 flash 中吗?

    我开发了一款大小为 27mb 的游戏 我将其加载到设备中 它的性能会降低 有时会挂起设备 这个游戏中使用了很多媒体资源 如果我在 android 中制作它 那么我会处理和异步任务 http developer android com ref
  • Shape、Sprite、MovieClip 和其他显示对象:何时使用?

    有大量的显示对象flash display包裹 我不清楚在什么情况下应该使用Shape Sprite or MovieClip 使用它们各自的优点和缺点是什么 先感谢您 Shape http help adobe com en US Fla
  • 如何确定应用程序是作为移动应用程序还是桌面应用程序运行?

    我怎样才能知道当前的应用程序类型是什么 即它是在移动设备上运行还是作为桌面 Air 应用程序运行 我试过这个 if FlexGlobals topLevelApplicatoin as WindowedApplication desktop
  • 使用flash动作脚本开发网络游戏

    简而言之 如果我想使用 Flash 技术开发在线游戏 你能告诉我一些很好的学习资源吗 包括3D游戏 您可以从新发布的开始Flash平台游戏技术中心 http www adobe com devnet games 在 Adob e Devne
  • 使用 Actionscript 3 连接到数据库 [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我正在寻找有关如何基于数据库在 Flash 中动态创建内容的建议 最初我想将数据库导出到 XML 文件并使用内置的 Actionscr
  • Flash AS3 - 将多个对象拖放到一个目标?

    标题或多或少是不言自明的 我一直在学习许多不同的教程 而且说实话 我对 AS3 不太擅长 上图显示了我的目标 无论如何 我在我看到的大多数在线教程中注意到 拖放教程要么基于一个对象到一个目标 要么基于多个对象到多个目标 所以我想知道是否有人
  • Flex/AS3很奇怪的简单数字运算问题

    我的问题在 Flex 中描述起来非常简单 0 8 0 2 0 6000000000000001 以前有人得到过这个 我确定前两个成员是 0 8 和 0 2 并且是 Number 类 为什么会发生这种情况 另一件事 我从 像这样输入 var
  • 我应该将 FLV 文件放在哪里才能在本地 Red5 服务器上进行流式传输?

    我安装了最新的 Red5 服务器 但我不确定将 flv 文件放在哪里来进行流式传输 没有像我在网上找到的一些教程那样的 streams 或 ofla 目录 我应该将 flv 文件放在哪里来进行流式传输 Red5 附带了一些演示 但默认情况下
  • 安全沙箱违规

    运行我的 Flash 应用程序时出现以下错误 违反安全沙箱 与 rtmp system ip live 的连接已停止 不允许从 file F Flash 工作 RTS RT vlab BIOTECH NEO 简单神经元的被动属性 vi 特征
  • VideoJs 在 Firefox 中的 Flash 回退问题

    我尝试将 videoJs 添加到我的网站来播放 MP4 文件 所有这些在 Chrome 中都能完美运行 但当我转到 Firefox 不支持 MP4 文件 时 Flash 播放器停留在黑屏上 按钮不执行任何操作 简单的问题 为什么 我不明白
  • ActionScript 3.0 中缺少运算符重载

    我在 ActionScript 中最怀念的事情之一是缺少运算符重载 特别是 我通过在我的类中添加 Compare 方法来解决这个问题 但这在很多情况下没有帮助 比如当你想使用内置字典之类的东西时 有没有好的方法来解决这个问题 Nope 但添
  • 用圆形减去(遮盖掉?)路径

    我正在使用 Spark Path 在 Flex 中绘制一条路径 我想从这条路径中减去一个圆形 如下图所示 道路又黑又宽 有任何想法吗 我尝试使用 Shape 对象创建蒙版 但无法完全创建其中有圆孔的蒙版 找到了 不涉及口罩 我拿了Path并
  • 求反射角的弧度

    我正在编写一个简单的 Flash 游戏 只是为了学习 Flash 并提高我的数学能力 但我对弧度感到非常困惑 因为这对我来说是新的 到目前为止 我所做的是使用鼠标 单击并释放 使用弧度向该方向射出一个球 现在我想要发生的是 当球撞到墙壁时
  • Adobe Flash 项目的版本控制

    我正在处理一个非常复杂的 Flash 项目 该项目是我们为客户使用而部署的全套服务的一部分 对于我们的大多数软件源 Java PHP Javascript HTML 和一些其他语言的支持脚本 我们使用 subversion 进行版本控制和管

随机推荐

  • 如何保持缓存和数据库中的数据一致

    背景 缓存是软件开发中一个非常有用的概念 数据库缓存更是在项目中必然会遇到的场景 而缓存一致性的保证 更是在面试中被反复问到 这里进行一下总结 针对不同的要求 选择恰到好处的一致性方案 缓存是什么 存储的速度是有区别的 缓存就是把低速存储的
  • STM32_GPIO引脚控制(库函数开发)

    目录 在学习GPIO引脚前 先介绍一些函数 库函数 stm32f10x rcc 库函数 stm32f10x gpio 这些函数怎么用呢 那如何使用 完成初始化 初始化完成后便可以进行一些GPIO的一些操作了 如 点亮共阳极LED 如 进行L
  • JavaScript 之 Symbol 数据类型

    一 简介 symbol类型是ES6新引入的一种基本数据类型 该类型具有静态属性和静态方法 其中静态属性暴露了几个内建的成员对象 静态方法暴露了全局的symbol注册 symbol类型具有以下特点 唯一性 每个symbol值都是唯一的 不可变
  • 使用git restore --staged撤销你在暂存区的提交

    我们通过git add命令将文件提交到暂存区之后 发现文件提交错了 就可以通过git restore staged撤销在暂存区提交的文件 通过实例演示一下 当前目录下有三个文件进行了修改 并提交到了暂存区 通过git ls files命令可
  • 以太坊系列之十五: 以太坊数据库

    以太坊数据库中都存了什么 以太坊使用的数据库是一个NOSQL数据库 是谷歌提供的开源数据leveldb 这里尝试通过分析以太坊数据库存储了什么来分析以太坊可能为我们提供哪些关于区块链的API 存储内容 NOSQL是一个key value数据
  • MetaEditor 编译原理之MQ4文件语法解析

    语法解析 顾名思义就是将一个文件或者一段代码 按照语法结构拆分为一个一个的单词 比如 extern int TakeProfit 50 int start int i 0 while i lt TakeProfit i return i 正
  • (附源码)springboot电商系统前端界面设计与浏览器兼容性研究 毕业设计 231058

    基于springboot电商系统前端界面设计 摘 要 随着科学技术的飞速发展 各行各业都在努力与现代先进技术接轨 通过科技手段提高自身的优势 对于电商系统前端界面设计与浏览器兼容性研究当然也不能排除在外 随着网络技术的不断成熟 带动了电商系
  • 运行软件mfc140u.dll丢失怎么办?mfc140u.dll的三个修复方法

    最近我在使用一款软件时遇到了一个问题 提示缺少mfc140u dll文件 这个文件是我在使用某个应用程序时所需要的 但是由于某种原因 它变得无法正常使用了 经过一番搜索和了解 我了解到mfc140u dll是Microsoft Visual
  • Proteus8仿真:51单片机A/D转换(ADC0808)

    51单片机A D转换 元器件 原理图部分 代码 main c 工程文件 元器件 元器件 名称 排阻 RESPACK 8 51单片机 AT89C51 数码管 7SEG MPX4 CA BLUE ADC芯片 ADC0808 滑动变阻器 POT
  • Centos设置limit最大打开文件数和最大进程数

    在 etc security limits conf添加 cat gt etc security limits conf lt
  • 【Ruff学习1】Ruff是一个物联网的操作系统,可以让开发者使用JS高效且迅速地开发物联网应用

    Ruff学习1 Ruff是一个物联网的操作系统 可以让开发者使用JS高效且迅速地开发物联网应用 官网 三分钟 点亮物联网世界的第一盏灯 ready function btn on push function led turnOn Ruff特
  • css ol 序列样式:数字带圆圈、括号

    有序ol基本的网上都有 在这里就不介绍了 1 数字带登号 如标题的这种 2 通过上面的例子可以扩展一下 1 只要修改成下面的代码 其余不变 ol li before content counter sectioncounter counte
  • K8S存储之volume

    K8S存储之volume 容器磁盘上的文件的生命周期是短暂的 这就使得在容器中运行重要应用时会出现一些问题 首先 当容器崩溃时 kubelet会重启它 但是容器中的文件将丢失一一容器以干净的状态 镜像最初的状态 重新启动 其次 在Pod中同
  • 【Python】Jupyter Notebook无法运行代码,不可重命名且提示error和自动保存失败时如何操作?

    Python Jupyter Notebook无法运行代码 且提示error和自动保存失败时如何操作 Anaconda的Jupyter Notebook作为优秀的网页编辑器 非常适用于编写Python程序 但往往可能因安装版本不兼容等原因而
  • Flutter中的依赖注入——get_it

    Flutter社区的一个library get it 视频介绍 Flutter Dependency Injection For Beginners Complete Guide 视频对应的博文 Dependency Injection i
  • JavaWeb开发中出现DataSource读取不到怎么办呢?(详细,适合初入门的程序员)

    这样的问题是怎么产生的呢 其实啊也不难 来吧 跟我走一遍 目录 前言 二 使用步骤 1 基本的JavaWeb项目的结构 1 1 创建一个JavaWeb项目 1 2 配置文件的配置 1 3 重点来了 2 DBUtil的代码内容 3 测试 总结
  • 树的广度优先遍历与深度优先遍历算法

    1 树的广度优先遍历算法 广度优先遍历算法 又叫宽度优先遍历 或横向优先遍历 是从根节点开始 沿着树的宽度遍历树的节点 如果所有节点均被访问 则算法中止 如上图所示的二叉树 A 是第一个访问的 然后顺序是 B C 然后再是 D E F G
  • 数据库实体关系图(ERD)

    数据库是软件系统中不可或缺的一个组成部分 若能在数据库工程中好好利用 ER 图 便能让您生成高质量的数据库设计 用于数据库创建 管理和维护 也为人员间的交流提供了有意义的基础 今天 我们将为你深入介绍 ER 图表 通过阅读本ERD指南 您将
  • Gikee 大数据

    据Gikee数据显示 今日13 58分 地址 1MAhRt279uYmVC1dUxKR6dWwEULBJT34Nh 向地址 1Fc4QQu6nEc4snAe4HAb4Kryd8koH89pYk 转了34010个BTC 价值约2 17亿美元
  • stm32USB之模拟U盘

    STMF0 W25Q32模拟U盘 1 第一次写博客 如有错误 请及时指正 如有表达不通顺的地方 敬请谅解 2 本篇文章主要描述如何使用STM32cube配置USB 使用的主控为STM32F072 Flash为W25Q32 使用的主控RAM只