设计NOR Flash(SPI接口)的Flashloader(MCU: stm32f4)

目录

概述

1 软硬件

1.1 软硬件信息表

1.2 NOR Flash芯片(W25Q64BVSSI)

1.2.1 W25Q64BVSSI芯片介绍

1.2.2 NOR Flash接口

1.3 MCU与NOR Flash接口

2 SPI Flash功能实现

2.1 软件框架结构

2.2 代码实现

2.2.1 Dev_Inf文件

2.2.2 W25QXX驱动程序

2.3 Flash loader驱动接口程序

3 Keil中的参数配置

3.1 配置参数

3.2 编译

4 测试

4.1 准备.stldr文件

4.2  STM32CubeProgrammer中测试Flash loader


源代码下载地址:

W25QXX-STM32F4-ALY:设计NORFlash(SPI接口)的Flashloader(MCU:stm32f4)资源-CSDN文库

概述

本文主要介绍基于STM32F407芯片,NOR Flash芯片为W25Q64(SPI接口)。使用其设计一个Flashloader 程序,并且在STM32CubeProgrammer工具中使用该文件,实现NOR Flash擦除数据,编程数据,读取数据的功能。

1 软硬件

1.1 软硬件信息表

软硬件信息版本信息
STM32 MCUSTM32F407IGTx
NOR FlashW25Q64BVSSI
KeilMDK ARM 5.38
调试工具:st-linkST-LINK/V2-1
STM32CubeProgrammerv2.16.0

1.2 NOR Flash芯片(W25Q64BVSSI)

1.2.1 W25Q64BVSSI芯片介绍

W25Q64BVSSI是一款容量为64Mb(8MB)的串行闪存存储器,由Winbond公司生产。它采用了串行外围设备接口(SPI),可用于存储嵌入式系统的代码和数据。

W25Q64BVSSI具有以下特点:

  1. 容量大:64Mb的存储容量可以存储大量的代码和数据。
  2. 高速访问:采用SPI接口,支持快速的读取和写入操作,具有快速的数据传输速度。
  3. 低功耗:W25Q64BVSSI采用低功耗设计,可以有效地节省系统能源。
  4. 可编程:可以通过软件进行编程和擦除操作,方便灵活的存储管理。
  5. 高可靠性:具有内置的错误检测和纠正机制,可以提高存储器的可靠性和数据完整性。
  6. 多种封装形式:W25Q64BVSSI可提供多种封装形式,例如SOIC、WSON和TFBGA等,以满足不同应用的需求。

W25Q64CV(64M位)串行闪存为具有有限空间、引脚和电源。25Q系列提供了远远超出普通串行闪存的灵活性和性能设备。它们非常适合将代码隐藏到RAM,直接从双/四SPI(XIP)执行代码以及存储语音、文本和数据。该设备在单个2.7V至3.6V的电源上运行,并带有电流功耗低至4mA有源和1µA断电。

W25Q64CV阵列被组织成32768个可编程页面,每个页面256字节。最多256字节可以一次编程。页面可以按16组(4KB扇区擦除)、128组擦除(32KB块擦除)、256组(64KB块擦除(block erase))或整个芯片(chip erase)。W25Q64CV分别具有2048个可擦除扇区和128个可擦除块。较小的4KB扇区允许在需要数据和参数存储的应用程序中具有灵活性。

1.2.2 NOR Flash接口

笔者使用的芯片封装为:

IO 接口介绍

1.3 MCU与NOR Flash接口

NOR Flash和MCU之间通过SPI接口通信,选择STM32F407芯片上的SPI1接口,该通信接口特点如下:

1 STM32F4XX 时钟计算.
        HCLK = 168M
        PCLK1 = HCLK / 4 = 42M
        PCLK2 = HCLK / 2 = 84M

        SPI2、SPI3 在 PCLK1, 时钟42M
        SPI1       在 PCLK2, 时钟84M

        STM32F4 支持的最大SPI时钟为 37.5 Mbits/S, 因此需要分频。

2   开发板口线分配:  串行Flash型号为 W25Q64BVSSIG (80MHz)
        PB3/SPI3_SCK/SPI1_SCK
        PB4/SPI3_MISO/SPI1_MISO
        PB5/SPI3_MOSI/SPI1_MOSI
        PF8/SF_CS

        STM32硬件SPI接口 = SPI3 或者 SPI1,由于SPI1的时钟源是84M, SPI3的时钟源是42M。为了获得更快的速度,软件上选择SPI1。
 

MCU接口NOR Flash
GPIOF_PIN6CS
GPIOB_PIN3SCK
GPIOB_PIN4MISO
GPIOB_PIN5MOSI

硬件电路图如下:

2 SPI Flash功能实现

2.1 软件框架结构

Flash loader的代码分为两个部分

1)Loader: Flash leader的功能代码,定义Flash leader的芯片信息;实现该功能的驱动接口

2)STM32F40X:MCU相关的驱动接口,包括时钟驱动、SPI驱动、GPIO驱动。以及NOR Flash的驱动接口

2.2 代码实现

2.2.1 Dev_Inf文件

代码第5行: 定义设备名称

代码第6行:定义Flash的类型

代码第7行:其实地址

代码第8行:芯片空间大小

代码第9行:芯片page空间大小

代码第13行:芯片sector相关的参数

1)Dev_Inf.c 详细代码内容:

#include "Dev_Inf.h"

/* This structure containes information used by ST-LINK Utility to program and erase the device */
struct StorageInfo const StorageInfo  =  {
   "W25QXX_STM32F407ALY",                  // Device Name + version number
   SPI_FLASH,                              // Device Type
   0x00000000,                             // Device Start Address
   WW25QXX_CHIP_SIZE,                      // Device Size in Bytes (8MBytes/64Mbits)
   W25QXX_PAGE_SIZE,                       // Programming Page Size 256 BYTES
   0xFF,                                   // Initial Content of Erased Memory
    
// Specify Size and Address of Sectors (view example below)
   W25Q16J_SEC_NUM, W25QXX_SECTOR_SIZE,    // Sector Num : 128 ,Sector Size: 64KBytes 
   0x00000000, 0x00000000,
}; 


/* End of this file */

 2)Dev_nf.h 详细代码内容:


#define W25QXX_PAGE_SIZE           (             256)
#define W25QXX_SECTOR_SIZE         (        4 * 1024)
#define W25QXX_SUBBLOCK_SIZE       (       32 * 1024)
#define W25QXX_BLOCK_SIZE          (       64 * 1024)
#define WW25QXX_CHIP_SIZE          ( 8 * 1024 * 1024) 
  
#define W25Q16J_SEC_NUM            (WW25QXX_CHIP_SIZE/W25QXX_SECTOR_SIZE) 


#define    MCU_FLASH   1
#define    NAND_FLASH  2
#define    NOR_FLASH   3
#define    SRAM        4
#define    PSRAM       5
#define    PC_CARD     6
#define    SPI_FLASH   7
#define    I2C_FLASH   8
#define    SDRAM       9
#define    I2C_EEPROM  10

#define SECTOR_NUM     10      // Max Number of Sector types

struct DeviceSectors  
{
  unsigned long  SectorNum;     // Number of Sectors
  unsigned long  SectorSize;    // Sector Size in Bytes
};

struct StorageInfo  
{
   char           DeviceName[100];      // Device Name and Description
   unsigned short DeviceType;           // Device Type: ONCHIP, EXT8BIT, EXT16BIT, ...
   unsigned long  DeviceStartAddress;   // Default Device Start Address
   unsigned long  DeviceSize;           // Total Size of Device
   unsigned long  PageSize;             // Programming Page Size
   unsigned char  EraseValue;           // Content of Erased Memory
   struct         DeviceSectors  sectors[SECTOR_NUM];
};

2.2.2 W25QXX驱动程序

1)创建bsp_spi_flash.c文件,实现如下代码

#include "bsp_spi_flash.h"

/*
    STM32F4XX 时钟计算.
        HCLK = 168M
        PCLK1 = HCLK / 4 = 42M
        PCLK2 = HCLK / 2 = 84M

        SPI2、SPI3 在 PCLK1, 时钟42M
        SPI1       在 PCLK2, 时钟84M

        STM32F4 支持的最大SPI时钟为 37.5 Mbits/S, 因此需要分频。
*/

/*
        STM32-V5 开发板口线分配:  串行Flash型号为 W25Q64BVSSIG (80MHz)
        PB3/SPI3_SCK/SPI1_SCK
        PB4/SPI3_MISO/SPI1_MISO
        PB5/SPI3_MOSI/SPI1_MOSI
        PF8/SF_CS

        STM32硬件SPI接口 = SPI3 或者 SPI1

        由于SPI1的时钟源是84M, SPI3的时钟源是42M。为了获得更快的速度,软件上选择SPI1。
*/
//#define SPI_FLASH_PORT            SPI3
#define SPI_FLASH_PORT              SPI1

//#define ENABLE_SPI_RCC()     RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE)
#define ENABLE_SPI_RCC()     RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE)

/*
【SPI时钟最快是2分频,不支持不分频】
如果是SPI1,2分频时SCK时钟 = 42M,4分频时SCK时钟 = 21M
如果是SPI3, 2分频时SCK时钟 = 21M
*/
#define SPI_BAUD                SPI_BaudRatePrescaler_4

/* 片选GPIO端口  */
#define SF_CS_GPIO              GPIOF
#define SF_CS_PIN               GPIO_Pin_8

/* 片选口线置低选中  */
#define SF_CS_LOW()       SF_CS_GPIO->BSRRH = SF_CS_PIN

/* 片选口线置高不选中 */
#define SF_CS_HIGH()      SF_CS_GPIO->BSRRL = SF_CS_PIN

#define CMD_AAI       0xAD        /* AAI 连续编程指令(FOR SST25VF016B) */
#define CMD_DISWR     0x04        /* 禁止写, 退出AAI状态 */
#define CMD_EWRSR     0x50        /* 允许写状态寄存器的命令 */
#define CMD_WRSR      0x01        /* 写状态寄存器命令 */
#define CMD_WREN      0x06        /* 写使能命令 */
#define CMD_READ      0x03        /* 读数据区命令 */
#define CMD_WPAGE     0x02        /* 写page数据区命令 */
#define CMD_RDSR      0x05        /* 读状态寄存器命令 */
#define CMD_RDID      0x9F        /* 读器件ID命令 */
#define CMD_SE        0x20        /* 擦除扇区命令 */
#define CMD_BE        0xC7        /* 批量擦除命令 */
#define DUMMY_BYTE    0xA5        /* 哑命令,可以为任意值,用于读操作 */

#define WIP_FLAG      0x01        /* 状态寄存器中的正在编程标志(WIP) */

SFLASH_T g_tSF;

void sf_ReadInfo(void);
static uint8_t sf_SendByte(uint8_t _ucValue);
static void sf_WriteEnable(void);
static void sf_WriteStatus(uint8_t _ucValue);
static void sf_WaitForWriteEnd(void);
static void bsp_CfgSPIForSFlash(void);

/*
*********************************************************************************************************
*    函 数 名: bsp_InitSpiFlash
*    功能说明: 初始化串行Flash硬件接口(配置STM32的SPI时钟、GPIO)
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitSFlash(void)
{
    /*
        STM32-F407 开发板口线分配:  串行Flash型号为 W25Q64BVSSIG (80MHz)
        PB3/SPI3_SCK
        PB4/SPI3_MISO
        PB5/SPI3_MOSI
        PF8/SF_CS

        STM32硬件SPI接口 = SPI3
    */
    {
        GPIO_InitTypeDef GPIO_InitStructure;

        /* 使能GPIO 时钟 */
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOF, ENABLE);

//         /* 配置 SCK, MISO 、 MOSI 为复用功能 */
//         GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI3);
//         GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI3);
//         GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI3);
        
        /* 配置 SCK, MISO 、 MOSI 为复用功能 */
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1);
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1);

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
        GPIO_Init(GPIOB, &GPIO_InitStructure);

        /* 配置片选口线为推挽输出模式 */
        SF_CS_HIGH();        /* 片选置高,不选中 */
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
        GPIO_Init(GPIOF, &GPIO_InitStructure);
    }

    /* 配置SPI硬件参数用于访问串行Flash */
    bsp_CfgSPIForSFlash();

    sf_ReadInfo();                    /* 自动识别芯片型号 */

    SF_CS_LOW();                      /* 软件方式,使能串行Flash片选 */
    sf_SendByte(CMD_DISWR);        /* 发送禁止写入的命令,即使能软件写保护 */
    SF_CS_HIGH();                      /* 软件方式,禁能串行Flash片选 */

    sf_WaitForWriteEnd();          /* 等待串行Flash内部操作完成 */

    sf_WriteStatus(0);              /* 解除所有BLOCK的写保护 */
}

/*
*********************************************************************************************************
*    函 数 名: bsp_CfgSPIForSFlash
*    功能说明: 配置STM32内部SPI硬件的工作模式、速度等参数,用于访问SPI接口的串行Flash。
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
static void bsp_CfgSPIForSFlash(void)
{
    SPI_InitTypeDef  SPI_InitStructure;

    /* 打开SPI时钟 */
    ENABLE_SPI_RCC();

    /* 配置SPI硬件参数 */
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;     /* 数据方向:2线全双工 */
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                             /* STM32的SPI工作模式 :主机模式 */
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                       /* 数据位长度 : 8位 */
    /* SPI_CPOL和SPI_CPHA结合使用决定时钟和数据采样点的相位关系、
       本例配置: 总线空闲是高电平,第2个边沿(上升沿采样数据)
    */
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;            /* 时钟上升沿采样数据 */
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;        /* 时钟的第2个边沿采样数据 */
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;              /* 片选控制方式:软件控制 */

    /* 设置波特率预分频系数 */
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BAUD;

    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;        /* 数据位传输次序:高位先传 */
    SPI_InitStructure.SPI_CRCPolynomial = 7;                      /* CRC多项式寄存器,复位后为7。本例程不用 */
    SPI_Init(SPI_FLASH_PORT, &SPI_InitStructure);

    SPI_Cmd(SPI_FLASH_PORT, DISABLE);              /* 先禁止SPI  */
    SPI_Cmd(SPI_FLASH_PORT, ENABLE);                /* 使能SPI  */
}

/*
*********************************************************************************************************
*    函 数 名: sf_EraseSector
*    功能说明: 擦除指定的扇区
*    形    参:  _uiSectorAddr : 扇区地址
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_EraseSector(uint32_t _uiSectorAddr)
{
    sf_WriteEnable();                                               /* 发送写使能命令 */

    /* 擦除扇区操作 */
    SF_CS_LOW();                                                      /* 使能片选 */
    sf_SendByte(CMD_SE);                                            /* 发送擦除命令 */
    sf_SendByte((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    sf_SendByte((_uiSectorAddr & 0xFF00) >> 8);          /* 发送扇区地址中间8bit */
    sf_SendByte(_uiSectorAddr & 0xFF);                      /* 发送扇区地址低8bit */
    SF_CS_HIGH();                                                      /* 禁能片选 */

    sf_WaitForWriteEnd();                                          /* 等待串行Flash内部写操作完成 */
}

/*
*********************************************************************************************************
*    函 数 名: sf_EraseChip
*    功能说明: 擦除整个芯片
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_EraseChip(void)
{
    sf_WriteEnable();                                  /* 发送写使能命令 */

    /* 擦除扇区操作 */
    SF_CS_LOW();                                        /* 使能片选 */
    sf_SendByte(CMD_BE);                            /* 发送整片擦除命令 */
    SF_CS_HIGH();                                        /* 禁能片选 */

    sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
}

/*
*********************************************************************************************************
*    函 数 名: sf_PageWrite
*    功能说明: 向一个page内写入若干字节。字节个数不能超出页面大小(4K)
*    形    参:      _pBuf : 数据源缓冲区;
*                _uiWriteAddr :目标区域首地址
*                _usSize :数据个数,不能超过页面大小
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)
{
    uint32_t i, j;

    if (g_tSF.ChipID == SST25VF016B_ID)
    {
        /* AAI指令要求传入的数据个数是偶数 */
        if ((_usSize < 2) && (_usSize % 2))
        {
            return ;
        }

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

        SF_CS_LOW();                                        /* 使能片选 */
        sf_SendByte(CMD_AAI);                            /* 发送AAI命令(地址自动增加编程) */
        
        sf_SendByte((_uiWriteAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
        sf_SendByte((_uiWriteAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */
        sf_SendByte(_uiWriteAddr & 0xFF);                      /* 发送扇区地址低8bit */
        sf_SendByte(*_pBuf++);                            /* 发送第1个数据 */
        sf_SendByte(*_pBuf++);                             /* 发送第2个数据 */
        
        SF_CS_HIGH();                                          /* 禁能片选 */

        sf_WaitForWriteEnd();                              /* 等待串行Flash内部写操作完成 */

        _usSize -= 2;                                          /* 计算剩余字节数 */

        for (i = 0; i < _usSize / 2; i++)
        {
            SF_CS_LOW();                                    /* 使能片选 */
            sf_SendByte(CMD_AAI);                        /* 发送AAI命令(地址自动增加编程) */
            sf_SendByte(*_pBuf++);                  /* 发送数据 */
            sf_SendByte(*_pBuf++);                    /* 发送数据 */
            SF_CS_HIGH();                                    /* 禁能片选 */
            sf_WaitForWriteEnd();                        /* 等待串行Flash内部写操作完成 */
        }

        /* 进入写保护状态 */
        SF_CS_LOW();
        sf_SendByte(CMD_DISWR);
        SF_CS_HIGH();

        sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    }
    else    /* for MX25L1606E 、 W25Q64BV */
    {
        for (j = 0; j < _usSize / 256; j++)
        {
            sf_WriteEnable();                                  /* 发送写使能命令 */

            SF_CS_LOW();                                        /* 使能片选 */
            sf_SendByte(0x02);                                /* 发送AAI命令(地址自动增加编程) */
            sf_SendByte((_uiWriteAddr & 0xFF0000) >> 16);      /* 发送扇区地址的高8bit */
            sf_SendByte((_uiWriteAddr & 0xFF00) >> 8);          /* 发送扇区地址中间8bit */
            sf_SendByte(_uiWriteAddr & 0xFF);                        /* 发送扇区地址低8bit */

            for (i = 0; i < 256; i++)
            {
                sf_SendByte(*_pBuf++);             /* 发送数据 */
            }

            SF_CS_HIGH();                                    /* 禁止片选 */

            sf_WaitForWriteEnd();                        /* 等待串行Flash内部写操作完成 */

            _uiWriteAddr += 256;
        }

        /* 进入写保护状态 */
        SF_CS_LOW();
        sf_SendByte(CMD_DISWR);
        SF_CS_HIGH();

        sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    }
}


/*
*********************************************************************************************************
*    函 数 名: sf_ReadBuffer
*    功能说明: 连续读取若干字节。字节个数不能超出芯片容量。
*    形    参:      _pBuf : 数据源缓冲区;
*                _uiReadAddr :首地址
*                _usSize :数据个数, 可以大于PAGE_SIZE,但是不能超出芯片总容量
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
{
    /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
    {
        return;
    }

    /* 擦除扇区操作 */
    SF_CS_LOW();                                                    /* 使能片选 */
    sf_SendByte(CMD_READ);                                      /* 发送读命令 */
    sf_SendByte((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    sf_SendByte((_uiReadAddr & 0xFF00) >> 8);          /* 发送扇区地址中间8bit */
    sf_SendByte(_uiReadAddr & 0xFF);                      /* 发送扇区地址低8bit */
    while (_uiSize--)
    {
        *_pBuf++ = sf_SendByte(DUMMY_BYTE);                /* 读一个字节并存储到pBuf,读完后指针自加1 */
    }
    SF_CS_HIGH();                                                    /* 禁能片选 */
}

/*
*********************************************************************************************************
*    函 数 名: sf_CmpData
*    功能说明: 比较Flash的数据.
*    形    参:      _ucpTar : 数据缓冲区
*                _uiSrcAddr :Flash地址
*                _uiSize :数据个数, 可以大于PAGE_SIZE,但是不能超出芯片总容量
*    返 回 值: 0 = 相等, 1 = 不等
*********************************************************************************************************
*/
uint8_t sf_CmpData(uint32_t _uiSrcAddr, uint8_t *_ucpTar, uint32_t _uiSize)
{
    uint8_t ucValue;

    /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    if ((_uiSrcAddr + _uiSize) > g_tSF.TotalSize)
    {
        return 1;
    }

    if (_uiSize == 0)
    {
        return 0;
    }

    SF_CS_LOW();                                                    /* 使能片选 */
    sf_SendByte(CMD_READ);                                      /* 发送读命令 */
    sf_SendByte((_uiSrcAddr & 0xFF0000) >> 16);        /* 发送扇区地址的高8bit */
    sf_SendByte((_uiSrcAddr & 0xFF00) >> 8);          /* 发送扇区地址中间8bit */
    sf_SendByte(_uiSrcAddr & 0xFF);                          /* 发送扇区地址低8bit */
    while (_uiSize--)
    {
        /* 读一个字节 */
        ucValue = sf_SendByte(DUMMY_BYTE);
        if (*_ucpTar++ != ucValue)
        {
            SF_CS_HIGH();
            return 1;
        }
    }
    SF_CS_HIGH();
    return 0;
}



/*
*********************************************************************************************************
*    函 数 名: sf_ReadID
*    功能说明: 读取器件ID
*    形    参:  无
*    返 回 值: 32bit的器件ID (最高8bit填0,有效ID位数为24bit)
*********************************************************************************************************
*/
uint32_t sf_ReadID(void)
{
    uint32_t uiID;
    uint8_t id1, id2, id3;

    SF_CS_LOW();                                              /* 使能片选 */
    sf_SendByte(CMD_RDID);                                 /* 发送读ID命令 */
    id1 = sf_SendByte(DUMMY_BYTE);                    /* 读ID的第1个字节 */
    id2 = sf_SendByte(DUMMY_BYTE);                    /* 读ID的第2个字节 */
    id3 = sf_SendByte(DUMMY_BYTE);                    /* 读ID的第3个字节 */
    SF_CS_HIGH();                                              /* 禁能片选 */

    uiID = ((uint32_t)id1 << 16) | ((uint32_t)id2 << 8) | id3;

    return uiID;
}

/*
*********************************************************************************************************
*    函 数 名: sf_ReadInfo
*    功能说明: 读取器件ID,并填充器件参数
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
void sf_ReadInfo(void)
{
    /* 自动识别串行Flash型号 */
    {
        g_tSF.ChipID = sf_ReadID();    /* 芯片ID */

        switch (g_tSF.ChipID)
        {
            case SST25VF016B_ID:
                strcpy(g_tSF.ChipName, "SST25VF016B");
                g_tSF.TotalSize = 2 * 1024 * 1024;      /* 总容量 = 2M */
                g_tSF.SectorSize = 4 * 1024;                  /* 页面大小 = 4K */
                g_tSF.PageSize   = 256;
                break;

            case MX25L1606E_ID:
                strcpy(g_tSF.ChipName, "MX25L1606E");
                g_tSF.TotalSize = 2 * 1024 * 1024;      /* 总容量 = 2M */
                g_tSF.SectorSize = 4 * 1024;                /* 页面大小 = 4K */
             g_tSF.PageSize   = 256;
                break;

            case W25Q64BV_ID:
                strcpy(g_tSF.ChipName, "W25Q64BV");
                g_tSF.TotalSize = 8 * 1024 * 1024;      /* 总容量 = 8M */
                g_tSF.SectorSize = 4 * 1024;                /* 页面大小 = 4K */
              g_tSF.PageSize   = 256;
                break;

            default:
                strcpy(g_tSF.ChipName, "Unknow Flash");
                g_tSF.TotalSize = 2 * 1024 * 1024;
                g_tSF.SectorSize = 4 * 1024;
              g_tSF.PageSize   = 256;
                break;
        }
    }
}

void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
  /*!< Enable the write access to the FLASH */
    sf_WriteEnable();                                    /* 发送写使能命令 */

  /*!< Select the FLASH: Chip Select low */
  SF_CS_LOW();                
  /*!< Send "Write to Memory " instruction */
  sf_SendByte(CMD_WPAGE);
  /*!< Send WriteAddr high nibble address byte to write to */
  sf_SendByte((WriteAddr & 0xFF0000) >> 16);
  /*!< Send WriteAddr medium nibble address byte to write to */
  sf_SendByte((WriteAddr & 0xFF00) >> 8);
  /*!< Send WriteAddr low nibble address byte to write to */
  sf_SendByte(WriteAddr & 0xFF);

  /*!< while there is data to be written on the FLASH */
  while (NumByteToWrite--)
  {
    /*!< Send the current byte */
    sf_SendByte(*pBuffer);
    /*!< Point on the next byte to be written */
    pBuffer++;
  }

  /*!< Deselect the FLASH: Chip Select high */
    SF_CS_HIGH();    

  /*!< Wait the end of Flash writing */
    sf_WaitForWriteEnd();        
}

void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
  uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

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

  if (Addr == 0)             /*!< WriteAddr is sFLASH_PAGESIZE aligned  */
  {
    if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */
    {
      sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */
    {
      while (NumOfPage--)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

      sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  else /*!< WriteAddr is not sFLASH_PAGESIZE aligned  */
  {
    if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */
    {
      if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */
      {
        temp = NumOfSingle - count;

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

        sFLASH_WritePage(pBuffer, WriteAddr, temp);
      }
      else
      {
        sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
      NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

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

      while (NumOfPage--)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

      if (NumOfSingle != 0)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

/*
*********************************************************************************************************
*    函 数 名: sf_SendByte
*    功能说明: 向器件发送一个字节,同时从MISO口线采样器件返回的数据
*    形    参:  _ucByte : 发送的字节值
*    返 回 值: 从MISO口线采样器件返回的数据
*********************************************************************************************************
*/
static uint8_t sf_SendByte(uint8_t _ucValue)
{
    /* 等待上个数据未发送完毕 */
    while (SPI_I2S_GetFlagStatus(SPI_FLASH_PORT, SPI_I2S_FLAG_TXE) == RESET);

    /* 通过SPI硬件发送1个字节 */
    SPI_I2S_SendData(SPI_FLASH_PORT, _ucValue);

    /* 等待接收一个字节任务完成 */
    while (SPI_I2S_GetFlagStatus(SPI_FLASH_PORT, SPI_I2S_FLAG_RXNE) == RESET);

    /* 返回从SPI总线读到的数据 */
    return SPI_I2S_ReceiveData(SPI_FLASH_PORT);
}

/*
*********************************************************************************************************
*    函 数 名: sf_WriteEnable
*    功能说明: 向器件发送写使能命令
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
static void sf_WriteEnable(void)
{
    SF_CS_LOW();                                    /* 使能片选 */
    sf_SendByte(CMD_WREN);              /* 发送命令 */
    SF_CS_HIGH();                                    /* 禁能片选 */
}

/*
*********************************************************************************************************
*    函 数 名: sf_WriteStatus
*    功能说明: 写状态寄存器
*    形    参:  _ucValue : 状态寄存器的值
*    返 回 值: 无
*********************************************************************************************************
*/
static void sf_WriteStatus(uint8_t _ucValue)
{

    if (g_tSF.ChipID == SST25VF016B_ID)
    {
        /* 第1步:先使能写状态寄存器 */
        SF_CS_LOW();                                    /* 使能片选 */
        sf_SendByte(CMD_EWRSR);              /* 发送命令, 允许写状态寄存器 */
        SF_CS_HIGH();                                    /* 禁能片选 */

        /* 第2步:再写状态寄存器 */
        SF_CS_LOW();                                    /* 使能片选 */
        sf_SendByte(CMD_WRSR);                /* 发送命令, 写状态寄存器 */
        sf_SendByte(_ucValue);              /* 发送数据:状态寄存器的值 */
        SF_CS_HIGH();                                    /* 禁能片选 */
    }
    else
    {
        SF_CS_LOW();                                    /* 使能片选 */
        sf_SendByte(CMD_WRSR);              /* 发送命令, 写状态寄存器 */
        sf_SendByte(_ucValue);                /* 发送数据:状态寄存器的值 */
        SF_CS_HIGH();                                    /* 禁能片选 */
    }
}

/*
*********************************************************************************************************
*    函 数 名: sf_WaitForWriteEnd
*    功能说明: 采用循环查询的方式等待器件内部写操作完成
*    形    参:  无
*    返 回 值: 无
*********************************************************************************************************
*/
static void sf_WaitForWriteEnd(void)
{
    SF_CS_LOW();                                         /* 使能片选 */
    sf_SendByte(CMD_RDSR);                         /* 发送命令, 读状态寄存器 */
    while((sf_SendByte(DUMMY_BYTE) & WIP_FLAG) == SET);    /* 判断状态寄存器的忙标志位 */
    SF_CS_HIGH();                                        /* 禁能片选 */
}

/* End of this file */

2)创建bsp_spi_flash.h文件,实现如下代码

#ifndef _BSP_SPI_FLASH_H
#define _BSP_SPI_FLASH_H

#include "stm32f4xx.h"
#include <stdio.h>
#include <string.h>

#define sFLASH_SPI_PAGESIZE       0x100

/* 定义串行Flash ID */
enum
{
    SST25VF016B_ID = 0xBF2541,
    MX25L1606E_ID  = 0xC22015,
    W25Q64BV_ID    = 0xEF4017
};


typedef struct
{
    uint32_t ChipID;          /* 芯片ID */
    char ChipName[16];        /* 芯片型号字符串,主要用于显示 */
    uint32_t TotalSize;       /* 总容量 */
    uint16_t SectorSize;      /* sector size */
    uint16_t PageSize;        /* 页面大小 */
}SFLASH_T;

extern SFLASH_T g_tSF;

void bsp_InitSFlash(void);
uint32_t sf_ReadID(void);
void sf_EraseChip(void);
void sf_EraseSector(uint32_t _uiSectorAddr);
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize);
uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);
void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);


void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);



#endif    /* _BSP_SPI_FLASH_H */

2.3 Flash loader驱动接口程序

在Loader_Src.c程序中实现如下代码

#include "bsp_spi_flash.h"

/**
  * Description :
  * Initilize the MCU Clock, the GPIO Pins corresponding to the
  * device and initilize the FSMC with the chosen configuration 
  * Inputs    :
  *      None
  * outputs   :
  *      R0             : "1"             : Operation succeeded
  *                             "0"             : Operation failure
  * Note: Mandatory for all types of device 
  */
int Init (void)
{  
    /* Set MCU Clock */
    SystemInit();
    bsp_InitSFlash();
    return 1;
}

/**
  * Description :
  * Read data from the device 
  * Inputs    :
  *      Address       : Write location
  *      Size          : Length in bytes  
  *      buffer       : Address where to get the data to write
  * outputs   :
  *      R0             : "1"             : Operation succeeded
  *               "0"             : Operation failure
  * Note: Mandatory for all types except SRAM and PSRAM    
  */
int Read (uint32_t Address, uint32_t Size, uint8_t* Buffer)
{    
    sf_ReadBuffer(Buffer, Address, Size);
    return 1;
} 

/**
  * Description :
  * Write data from the device 
  * Inputs    :
  *      Address       : Write location
  *      Size          : Length in bytes  
  *      buffer        : Address where to get the data to write
  * outputs   :
  *      R0             : "1"             : Operation succeeded
  *               "0"             : Operation failure
  * Note: Mandatory for all types except SRAM and PSRAM    
  */
int Write (uint32_t Address, uint32_t Size, uint8_t* Buffer)
{  
    sFLASH_WriteBuffer(Buffer, Address, Size);
    return 1;
} 

/**
  * Description :
  * Erase a full sector in the device
  * Inputs    :
  *     None
  * outputs   :
  *     R0             : "1" : Operation succeeded
  *              "0" : Operation failure
  * Note: Not Mandatory for SRAM PSRAM and NOR_FLASH
  */
int MassErase (void)
{  
    sf_EraseChip();
    return 1;    
}

/**
  * Description :
  * Erase a full sector in the device
  * Inputs    :
  *      SectrorAddress    : Start of sector
  *      Size          : Size (in WORD)  
  *      InitVal       : Initial CRC value
  * outputs   :
  *     R0             : "1" : Operation succeeded
  *              "0" : Operation failure
  * Note: Not Mandatory for SRAM PSRAM and NOR_FLASH
  */
int SectorErase (uint32_t EraseStartAddress ,uint32_t EraseEndAddress)
{      
  EraseStartAddress = EraseStartAddress -  EraseStartAddress%0x10000;
  while (EraseEndAddress>=EraseStartAddress)
  {
    sf_EraseSector(EraseStartAddress);
    EraseStartAddress += 0x10000;
  }
    
  return 1;    
}


/**
  * Description :
  * Calculates checksum value of the memory zone
  * Inputs    :
  *      StartAddress  : Flash start address
  *      Size          : Size (in WORD)  
  *      InitVal       : Initial CRC value
  * outputs   :
  *     R0             : Checksum value
  * Note: Optional for all types of device
  */
uint32_t CheckSum(uint32_t StartAddress, uint32_t Size, uint32_t InitVal)
{
    uint8_t missalignementAddress = StartAddress%4;
    uint8_t missalignementSize = Size ;
    int cnt;
    uint32_t Val;
    uint8_t value;
    
    StartAddress-=StartAddress%4;
    Size += (Size%4==0)?0:4-(Size%4);

    for(cnt=0; cnt<Size ; cnt+=4)
    {
        sf_ReadBuffer(&value, StartAddress ,1);
        Val = value;
        sf_ReadBuffer(&value, StartAddress + 1,1);
        Val+= value<<8;
        sf_ReadBuffer(&value, StartAddress + 2,1);
        Val+= value<<16;
        sf_ReadBuffer(&value, StartAddress + 3,1);
        Val+= value<<24;
        
        if(missalignementAddress)
        {
              switch (missalignementAddress)
              {
                case 1:
                  InitVal += (uint8_t) (Val>>8 & 0xff);
                  InitVal += (uint8_t) (Val>>16 & 0xff);
                  InitVal += (uint8_t) (Val>>24 & 0xff);
                  missalignementAddress-=1;
                  break;
                case 2:
                  InitVal += (uint8_t) (Val>>16 & 0xff);
                  InitVal += (uint8_t) (Val>>24 & 0xff);
                  missalignementAddress-=2;
                  break;
                case 3:   
                  InitVal += (uint8_t) (Val>>24 & 0xff);
                  missalignementAddress-=3;
                  break;
              }  
        }
        else if((Size-missalignementSize)%4 && (Size-cnt) <=4)
        {
              switch (Size-missalignementSize)
              {
                case 1:
                  InitVal += (uint8_t) Val;
                  InitVal += (uint8_t) (Val>>8 & 0xff);
                  InitVal += (uint8_t) (Val>>16 & 0xff);
                  missalignementSize-=1;
                  break;
                case 2:
                  InitVal += (uint8_t) Val;
                  InitVal += (uint8_t) (Val>>8 & 0xff);
                  missalignementSize-=2;
                  break;
                case 3:   
                  InitVal += (uint8_t) Val;
                  missalignementSize-=3;
                  break;
              } 
        }
        else
        {
              InitVal += (uint8_t) Val;
              InitVal += (uint8_t) (Val>>8 & 0xff);
              InitVal += (uint8_t) (Val>>16 & 0xff);
              InitVal += (uint8_t) (Val>>24 & 0xff);
        }
        StartAddress+=4;
    }

    return (InitVal);
}


/**
  * Description :
  * Verify flash memory with RAM buffer and calculates checksum value of
  * the programmed memory
  * Inputs    :
  *      FlashAddr     : Flash address
  *      RAMBufferAddr : RAM buffer address
  *      Size          : Size (in WORD)  
  *      InitVal       : Initial CRC value
  * outputs   :
  *     R0             : Operation failed (address of failure)
  *     R1             : Checksum value
  * Note: Optional for all types of device
  */
uint64_t Verify (uint32_t MemoryAddr, uint32_t RAMBufferAddr, uint32_t Size, uint32_t missalignement)
{
    uint32_t InitVal = 0;
    uint32_t VerifiedData = 0;
    uint8_t TmpBuffer = 0x00;
    uint64_t checksum;
    Size*=4;
        
    checksum = CheckSum((uint32_t)MemoryAddr + (missalignement & 0xf), Size - ((missalignement >> 16) & 0xF), InitVal);

    while (Size>VerifiedData)
    {
        sf_ReadBuffer(&TmpBuffer, MemoryAddr+VerifiedData, 1);
             
        if (TmpBuffer != *((uint8_t*)RAMBufferAddr+VerifiedData))
          return ((checksum<<32) + MemoryAddr+VerifiedData);
            
        VerifiedData++;  
    }
       
    return (checksum<<32);
}

3 Keil中的参数配置

3.1 配置参数

1)选择MCU内核

2)输出文件的名称

3)复制输出文件的位置

cmd.exe /C copy "!L" "..\..\..\@L.stldr"

4)编译的.c文件配置为Read-only

5)编译的.a文件配置为Read-only

 6) 配置link文件

 3.2 编译

在Keil完成以上配置之后,就可以编译代码,编译成功后会生成一个.stldr文件,该文件就是flashloader。

4 测试

4.1 准备.stldr文件

编译SPI NOR 源代码,并得到.stldr文件,将该文件放在STM32Cube\STM32CubeProgrammer\bin\ExternalLoader

4.2  STM32CubeProgrammer中测试Flash loader

1)打开STM32CubeProgrammer,加载.stldr文件

2)擦除芯片内的数据

 3)确认芯片内的数据已经被擦除

4)烧写测试文件

 5)读出写入的文件

通过比较二者文件,其完全一样。

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

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

相关文章

区间动态规划——最长回文子串(C++)

难得心静。 ——2024年6月30日 什么是区间动态规划&#xff1f; 区间动态规划通常以连续区间的求解作为子问题&#xff0c;例如区间 [i, j] 上的最优解用dp[i][j]表示。先在小区间上进行动态规划得到子问题的最优解&#xff0c;再利用小区间的最优解合并产生大区间的最优解。 …

娱乐圈发生震动,AI大模型技术已经取代了SNH48的小偶像?

自2023年以来&#xff0c;全球都被包裹在AI的惊天大潮之中&#xff0c;所有行业都在主动或被动地迎接改变。目前&#xff0c;各行业已经有大量公司正在把AI作为自身发展的最佳路径。其中&#xff0c;娱乐行业作为最被人们熟知的行业也在面对AI的发展时&#xff0c;发生着巨大变…

视频共享融合赋能平台LntonCVS统一视频接入平台数字化升级医疗体系

医疗健康事关国计民生&#xff0c;然而&#xff0c;当前我国医疗水平的地区发展不平衡、医疗资源分布不均和医疗信息系统老化等问题&#xff0c;制约了整体服务能力和水平的提升。视频融合云平台作为推动数字医疗的关键工具&#xff0c;在医疗领域的广泛应用和普及&#xff0c;…

统计信号处理基础 习题解答11-1

题目 观测到的数据具有PDF 在μ给定的条件下&#xff0c;是相互独立的。均值具有先验PDF&#xff1a; 求μ的 MMSE 和 MAP 估计量。另外&#xff0c;当和时将发生什么情况? 解答 和两者都是独立高斯分布&#xff0c;与例题10.1一致&#xff0c;直接套用&#xff08;10.11&am…

RedisAtomicInteger并发案例

&#x1f370; 个人主页:__Aurora__ &#x1f35e;文章有不合理的地方请各位大佬指正。 &#x1f349;文章不定期持续更新&#xff0c;如果我的文章对你有帮助➡️ 关注&#x1f64f;&#x1f3fb; 点赞&#x1f44d; 收藏⭐️ RedisAtomicInteger 提供了对整数的原子性操作&a…

《昇思25天学习打卡营第14天 | 昇思MindSpore基于MindNLP+MusicGen生成自己的个性化音乐》

14天 本节学了基于MindNLPMusicGen生成自己的个性化音乐。 MusicGen是来自Meta AI的Jade Copet等人提出的基于单个语言模型的音乐生成模型&#xff0c;能够根据文本描述或音频提示生成高质量的音乐样本。 MusicGen模型基于Transformer结构&#xff0c;可以分解为三个不同的阶段…

C++: 如何用C语言实现C++的虚函数机制?

前言 在 googletest的源码中&#xff0c;看到gtest-matchers.h 中实现的MatcherBase 类自定义了一个 VTable&#xff0c;这种设计实现了一种类似于C虚函数的机制。C中的虚函数机制实质上就是通过这种方式实现的&#xff0c;本文用c语言自定义虚函数表VTable实现了一下virtual的…

等保主机测评防骗指南(资产调研)

你是否测评时常被运维给忽悠&#xff1f;是否觉得以下的对话耳熟&#xff1f; 你&#xff1a;您好&#xff0c;请问你们的主机资产有哪些&#xff0c;包括服务器、数据库、中间件、应用系统等。 甲&#xff1a;我们资产就这两台服务器&#xff0c;数据库什么的都这上面&#…

OpenGL3.3_C++_Windows(25)

阴影失真:阴影的不真实感 条纹样式&#xff1a; 首先理解采样原理&#xff1a;同光的视角下&#xff0c;渲染一张深度图&#xff0c;每个像素&#xff0c;存储同一射线下的深度值&#xff08;不断更新深度缓冲的结果&#xff09;&#xff0c;即最近片段的深度。接着&#xff0…

hadoop词频统计

1 Hadoop 安装与伪分布的搭建 2 Hadoop词频统计 此文章基于搭建好hadoop之后做的词频统计实验&#xff0c;以上是链接为搭建hadoop的教程 目录 1 HDFS 文件系统常用命令 2 词频统计实验准备工作 2.1 启动hadoop 关闭防火墙 2.2 查看图形化界面 2.3 文件上传 3 词频统计…

isspace()方法——判断字符串是否只由空格组成

自学python如何成为大佬(目录): https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 isspace()方法用于判断字符串是否只由空格组成。isspace()方法的语法格式如下&#xff1a; str.isspace() 如果字符串中只包含空格&…

【Unity设计模式】✨使用 MVC 和 MVP 编程模式

前言 最近在学习Unity游戏设计模式&#xff0c;看到两本比较适合入门的书&#xff0c;一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》 这两本书介绍了大部分会使用到的设计模式&#xff0c;因此很值得学习 本…

【算法】5分钟了解如何使用PCA主成份分析

本文来自《老饼讲解-BP神经网络》https://www.bbbdata.com/ 目录 一、什么是PCA1.1.PCA的思想1.2.PCA的数学表示 二、什么是PCA的主成份与方差2.1.主成份的方差2.2.主成份的命名 三、如何使用PCA3.1.主成份的代码实现 主成份分析全称为PCA Principle Component Analysis ,它的主…

Linux虚拟串口设置

VSPD虚拟串口软件安装及使用 一、软件安装 1、Configure Virtual Serial Port Driver(VSPD) 1.1 首先下载 Configure Virtual Serial Port Driver(VSPD) 软件 链接&#xff1a;https://pan.baidu.com/s/11aGc2aHGUew5QZ0XhaWXJw 提取码&#xff1a;rmd7 1.2 安装时注意将…

计算机基础之汇编语言学习笔记

学习来源&#xff1a;b站各种学习资料 前置知识&#xff1a;计算机组成原理等知识 学习参考的资源 汇编语言编程的速成指南[上]~从零开始的期末抢救计划 &#xff08;8086汇编&#xff09;_哔哩哔哩_bilibili 链接: https://pan.baidu.com/s/1tg_ZW7VD3TS_s1v_EjS89w?pwdak6…

2029年AI服务器出货量将突破450万台,AI推理服务器即将爆发式增长

在2020年&#xff0c;新冠疫情与远程办公模式的兴起推动了所有类型服务器的出货量达到峰值&#xff0c;随后几年里&#xff0c;除了AI服务器之外的所有类别都回归到了正常水平。 根据Omdia的研究数据&#xff0c;AI服务器的出货量在2020年急剧上升&#xff0c;并且至今未显示出…

运筹系列93:VRP精确算法

1. 基础版本 定义 x i j k x_{ijk} xijk​为边 i j ij ij是否由车辆 k k k去运输。如果有时间窗约束的话&#xff0c;再加上一个变量 c i k c_{ik} cik​即可&#xff0c;表示第k辆车到达节点i时的时间点。 第一类客户流量约束&#xff0c;要求每个点都有1个入度和1个出度&…

ios13多窗口(UIWindowScene)学习笔记

ios13引入了UIWindowScene类、UIWindowSceneDelegate协议以便支持多窗口功能&#xff0c;但其适用于ipad&#xff0c;不适用于iphone&#xff0c;因为iphone不支持多窗口功能。注意&#xff0c;这里说的窗口不是UIWindow&#xff0c;而是UIWindowScene。 ios13前后的app的UI架…

AI陪伴产品的情感设计:从孤独感到恋爱感评分:9/10

本文主要阐述三个话题&#xff1a; 1. 市面上有哪些AI陪伴产品&#xff1f; 2. 我们团队要怎么做&#xff1f; 3. 为什么要做&#xff1f; 市面上有哪些陪伴类产品&#xff1f; Role-play&#xff08;角色扮演&#xff09; 在当前市场上&#xff0c;有不少以角色扮演为核心的…

Wails 安装初体验

文章目录 Wails 安装说明1. 系统要求2. 安装步骤3. 构建应用 结论 Wails 安装说明 Wails 是一个用于构建桌面应用的 Go 框架&#xff0c;结合了现代前端技术。以下是安装步骤&#xff1a; 1. 系统要求 Go 1.16 或更高版本Node.js 和 npm可选&#xff1a;适用于 Windows、mac…