zoukankan      html  css  js  c++  java
  • 【STM32H7教程】第73章 STM32H7的SPI总线应用之驱动W25QXX(支持查询,中断和DMA)

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

    第73章       STM32H7的SPI总线应用之驱动W25QXX(支持查询,中断和DMA)

    本章节为大家讲解标准SPI接线方式驱动W25QXX,实现了查询,中断和DMA三种方式。

    73.1 初学者重要提示

    73.2 W25QXX硬件设计

    73.4 W25QXX关键知识点整理(重要)

    73.5 W25QXX驱动设计

    73.6 SPI总线板级支持包(bsp_spi_bus.c)

    73.7 W25QXX板级支持包(bsp_spi_flash.c)

    73.8 使用例程设计框架

    73.9 实验例程说明(MDK)

    73.10 实验例程说明(IAR)

    73.11 总结

    73.1 初学者重要提示

    1.   学习本章节前,务必优先学习第72章。
    2.   W25Q64FV属于NOR型Flash存储芯片。
    3.   W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。
    4.   本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。
    5.   对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。
    6.   W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。
    7.   文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。
    8.   函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。

    73.2 W25QXX硬件设计

    STM32H7驱动W25QXX的硬件设计如下:

     

    关于这个原理图,要了解到以下几个知识:

    •   V7开发板实际外接的芯片是W25Q64JV。
    •   CS片选最好接上拉电阻,防止意外操作。
    •   这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。
    •   W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。
    •   HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。

    73.3 W25QXX关键知识点整理(重要)

    驱动W25QXX前要先了解下这个芯片的相关信息。

     

    73.3.1 W25QXX基础信息

    •   W25Q64FV的容量是8MB(256Mbit)。
    •   W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。

    支持两线SPI,用到引脚CLK、CS、IO0、IO1

    支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3

    (注:这里几线的意思是几个数据线)。

    •   W25Q64FV支持的最高时钟是133MHz。
    •   每个扇区最少支持10万次擦写,可以保存20年数据。
    •   页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
    •   支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。

    整体框图如下:

     

    W25Q64FV:

    •   有128个Block,每个Block大小64KB。
    •   每个Block有16个Sector,每个Sector大小4KB。
    •   每个Sector有16个Page,每个Page大小是256字节。

    73.3.2 W25QXX命令

    使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:

     

    当前主要用到如下几个命令:

    #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */
    #define CMD_WRSR      0x01  /* 写状态寄存器命令 */
    #define CMD_WREN      0x06    /* 写使能命令 */
    #define CMD_READ      0x03  /* 读数据区命令 */
    #define CMD_RDSR      0x05    /* 读状态寄存器命令 */
    #define CMD_RDID      0x9F    /* 读器件ID命令 */
    #define CMD_SE        0x20    /* 擦除扇区命令 */
    #define CMD_BE        0xC7    /* 批量擦除命令 */
    #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */

    73.3.3 W25QXX页编程和页回卷

    SPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):

    bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    
        for (i = 0; i < _usSize; i++)
        {
            bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
                                                 不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
        }

    页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。

    比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):

    uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
    uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
    •   从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
    •   向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);
    •   向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);
    •   向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)

    下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。

     

    73.3.4 W25QXX扇区擦除

    SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。

    如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。

    73.3.5 W25QXX规格参数

    这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:

     

    •   页编程时间:典型值0.4ms,最大值3ms。
    •   扇区擦除时间(4KB):典型值45ms,最大值400ms。
    •   块擦除时间(32KB):典型值120ms,最大值1600ms。
    •   块擦除时间(64KB):典型值150ms,最大值2000ms。
    •   整个芯片擦除时间:典型值20s,最大值100s。

    支持的速度参数如下:

     

    可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。

    73.4 W25QXX驱动设计

    W25QXX的程序驱动框架设计如下:

     

    有了这个框图,程序设计就比较好理解了。

    73.4.1 第1步:SPI总线配置

    spi总线配置通过如下两个函数实现:

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_InitSPIBus
    *    功能说明: 配置SPI总线。
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_InitSPIBus(void)
    {    
        g_spi_busy = 0;
        
        bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: bsp_InitSPIParam
    *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
    *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:
    *                                 SPI_BAUDRATEPRESCALER_2    2分频
    *                                 SPI_BAUDRATEPRESCALER_4    4分频
    *                                 SPI_BAUDRATEPRESCALER_8    8分频
    *                                 SPI_BAUDRATEPRESCALER_16   16分频
    *                                 SPI_BAUDRATEPRESCALER_32   32分频
    *                                 SPI_BAUDRATEPRESCALER_64   64分频
    *                                 SPI_BAUDRATEPRESCALER_128  128分频
    *                                 SPI_BAUDRATEPRESCALER_256  256分频
    *                                                        
    *             _CLKPhase           时钟相位,支持的参数如下:
    *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
    *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
    *                                 
    *             _CLKPolarity        时钟极性,支持的参数如下:
    *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
    *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
    *
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
    {
        /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
        if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
        {        
            return;
        }
    
        s_BaudRatePrescaler = _BaudRatePrescaler;    
        s_CLKPhase = _CLKPhase;
        s_CLKPolarity = _CLKPolarity;
        
        
        /* 设置SPI参数 */
        hspi.Instance               = SPIx;                   /* 例化SPI */
        hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
        hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */
        hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */
        hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
        hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
        hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */
        hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
        hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
        hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */
        hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */
        hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
        hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
        hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
        hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
        hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */
    
        /* 复位配置 */
        if (HAL_SPI_DeInit(&hspi) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);
        }    
    
        /* 初始化配置 */
        if (HAL_SPI_Init(&hspi) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);
        }    
    }

    关于这两个函数有以下两点要做个说明:

    •   函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
    •   函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。

    73.4.2 第2步:SPI总线的查询,中断和DMA方式设置

    SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:

    /*
    *********************************************************************************************************
    *                                 选择DMA,中断或者查询方式
    *********************************************************************************************************
    */
    //#define USE_SPI_DMA    /* DMA方式  */
    //#define USE_SPI_INT    /* 中断方式 */
    #define USE_SPI_POLL   /* 查询方式 */
    
    /* 查询模式 */
    #if defined (USE_SPI_POLL)
    
    uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
    uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    
    /* 中断模式 */
    #elif defined (USE_SPI_INT)
    
    uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    
    /* DMA模式使用的SRAM4 */
    #elif defined (USE_SPI_DMA)
        #if defined ( __CC_ARM )    /* IAR *******/
            __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
            __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
        #elif defined (__ICCARM__)   /* MDK ********/
            #pragma location = ".RAM_D3"
            uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
            #pragma location = ".RAM_D3"
            uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
        #endif
    #endif
    
    /*
    *********************************************************************************************************
    *    函 数 名: bsp_spiTransfer
    *    功能说明: 启动数据传输
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_spiTransfer(void)
    {
        if (g_spiLen > SPI_BUFFER_SIZE)
        {
            return;
        }
        
        /* DMA方式传输 */
    #ifdef USE_SPI_DMA
        wTransferState = TRANSFER_WAIT;
        
        if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    
        {
            Error_Handler(__FILE__, __LINE__);
        }
        
        while (wTransferState == TRANSFER_WAIT)
        {
            ;
        }
    #endif
    
        /* 中断方式传输 */    
    #ifdef USE_SPI_INT
        wTransferState = TRANSFER_WAIT;
    
        if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    
        {
            Error_Handler(__FILE__, __LINE__);
        }
        
        while (wTransferState == TRANSFER_WAIT)
        {
            ;
        }
    #endif
    
        /* 查询方式传输 */    
    #ifdef USE_SPI_POLL
        if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    
        {
            Error_Handler(__FILE__, __LINE__);
        }    
    #endif
    }

    通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:

    •   通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。
    •   由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
        /* 配置SRAM4的MPU属性为Non-cacheable */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x38000000;
        MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
        HAL_MPU_ConfigRegion(&MPU_InitStruct);

    73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

    首先回忆下STM32H7支持的4种时序配置。

    •   当CPOL = 1, CPHA = 1时

    SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。

    •   当CPOL = 0, CPHA = 1时

    SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。

    •   当CPOL = 1, CPHA = 0时

    SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。

    •  当CPOL = 1, CPHA = 0时

    SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。

     

    有了H7支持的时序配置,再来看下W25Q的时序图:

    Mode0 : 空闲状态的sck是低电平。

    Mode1 : 空闲状态的sck是高电平。

     

    首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:

    CHOL = 1,  CPHA = 1

    CHOL = 0,  CPHA = 0

    对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。

    73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制

    单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:

    /*
    *********************************************************************************************************
    *    函 数 名: sf_SetCS
    *    功能说明: 串行FALSH片选控制函数
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void sf_SetCS(uint8_t _Level)
    {
        if (_Level == 0)
        {
            bsp_SpiBusEnter();    
            bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
            SF_CS_0();
        }
        else
        {        
            SF_CS_1();    
            bsp_SpiBusExit();        
        }
    }

    通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。

    但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。

    73.4.5 第5步:W25QXX的读取实现

    W25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。

    /*
    *********************************************************************************************************
    *    函 数 名: sf_ReadBuffer
    *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    *    形    参:      _pBuf : 数据源缓冲区;
    *                _uiReadAddr :首地址
    *                _usSize :数据个数, 不能超出芯片总容量
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    {
        uint16_t rem;
        uint16_t i;
        
        /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
        if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
        {
            return;
        }
    
        /* 擦除扇区操作 */
        sf_SetCS(0);                                    /* 使能片选 */
        g_spiLen = 0;
        g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */
        g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
        g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */
        g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */
        bsp_spiTransfer();
        
        /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */
        for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
        {
            g_spiLen = SPI_BUFFER_SIZE;
            bsp_spiTransfer();
            
            memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
            _pBuf += SPI_BUFFER_SIZE;
        }
        
        rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */
        if (rem > 0)
        {
            g_spiLen = rem;
            bsp_spiTransfer();
            
            memcpy(_pBuf, g_spiRxBuf, rem);
        }
        
        sf_SetCS(1);                                    /* 禁能片选 */
    }

    这个函数对DMA传输做了特别处理,方便分包进行。

    73.4.6 第6步:W25QXX的扇区擦除实现

    扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。

    /*
    *********************************************************************************************************
    *    函 数 名: sf_EraseSector
    *    功能说明: 擦除指定的扇区
    *    形    参: _uiSectorAddr : 扇区地址
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void sf_EraseSector(uint32_t _uiSectorAddr)
    {
        sf_WriteEnable();                            /* 发送写使能命令 */
    
        /* 擦除扇区操作 */
        sf_SetCS(0);                                /* 使能片选 */
        g_spiLen = 0;
        g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
        g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
        g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */
        g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */    
        bsp_spiTransfer();
        sf_SetCS(1);                                    /* 禁能片选 */
    
        sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    }

    整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:

    /*
    *********************************************************************************************************
    *    函 数 名: sf_EraseChip
    *    功能说明: 擦除整个芯片
    *    形    参:  无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void sf_EraseChip(void)
    {    
        sf_WriteEnable();                                /* 发送写使能命令 */
    
        /* 擦除扇区操作 */
        sf_SetCS(0);        /* 使能片选 */
        g_spiLen = 0;
        g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */
        bsp_spiTransfer();
        sf_SetCS(1);                        /* 禁能片选 */
    
        sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    }

    73.4.7 第7步:W25QXX的编程实现

    W25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:

    /*
    *********************************************************************************************************
    *    函 数 名: sf_WriteBuffer
    *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。
    *    形    参:  _pBuf : 数据源缓冲区;
    *               _uiWrAddr :目标区域首地址
    *               _usSize :数据个数,任意大小,但不能超过芯片容量。
    *    返 回 值: 1 : 成功, 0 : 失败
    *********************************************************************************************************
    */
    uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
    {
        uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    
        Addr = _uiWriteAddr % g_tSF.SectorSize;
        count = g_tSF.SectorSize - Addr;
        NumOfPage =  _usWriteSize / g_tSF.SectorSize;
        NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    
        if (Addr == 0) /* 起始地址是扇区首地址  */
        {
            if (NumOfPage == 0) /* 数据长度小于扇区大小 */
            {
                if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
                {
                    return 0;
                }
            }
            else     /* 数据长度大于等于扇区大小 */
            {
                while (NumOfPage--)
                {
                    if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
                    {
                        return 0;
                    }
                    _uiWriteAddr +=  g_tSF.SectorSize;
                    _pBuf += g_tSF.SectorSize;
                }
                if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
                {
                    return 0;
                }
            }
        }
        else  /* 起始地址不是扇区首地址  */
        {
            if (NumOfPage == 0) /* 数据长度小于扇区大小 */
            {
                if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */
                {
                    temp = NumOfSingle - count;
    
                    if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
                    {
                        return 0;
                    }
    
                    _uiWriteAddr +=  count;
                    _pBuf += count;
    
                    if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)
                    {
                        return 0;
                    }
                }
                else
                {
                    if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
                    {
                        return 0;
                    }
                }
            }
            else    /* 数据长度大于等于扇区大小 */
            {
                _usWriteSize -= count;
                NumOfPage =  _usWriteSize / g_tSF.SectorSize;
                NumOfSingle = _usWriteSize % g_tSF.SectorSize;
                if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
                {
                    return 0;
                }
    
                _uiWriteAddr +=  count;
                _pBuf += count;
    
                while (NumOfPage--)
                {
                    if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
                    {
                        return 0;
                    }
                    _uiWriteAddr +=  g_tSF.SectorSize;
                    _pBuf += g_tSF.SectorSize;
                }
    
                if (NumOfSingle != 0)
                {
                    if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
                    {
                        return 0;
                    }
                }
            }
        }
        return 1;    /* 成功 */
    }

    73.5 SPI总线板级支持包(bsp_spi_bus.c)

    SPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:

    •   bsp_InitSPIBus
    •   bsp_InitSPIParam
    •   bsp_spiTransfer

    73.5.1 函数bsp_InitSPIBus

    函数原型:

    void bsp_InitSPIBus(void)

    函数描述:

    此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。

    73.5.2 函数bsp_InitSPIParam

    函数原型:

    void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)

    函数描述:

    此函数用于SPI总线的配置。

    函数参数:

    •   第1个参数SPI总线的分频设置,支持的参数如下:

    SPI_BAUDRATEPRESCALER_2    2分频

    SPI_BAUDRATEPRESCALER_4    4分频

    SPI_BAUDRATEPRESCALER_8    8分频

    SPI_BAUDRATEPRESCALER_16   16分频

    SPI_BAUDRATEPRESCALER_32   32分频

    SPI_BAUDRATEPRESCALER_64   64分频

    SPI_BAUDRATEPRESCALER_128  128分频

    SPI_BAUDRATEPRESCALER_256  256分频

    •   第2个参数用于时钟相位配置,支持的参数如下:

    SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据

    SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据

    •   第3个参数是时钟极性配置,支持的参数如下:

    SPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平

    SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平

    73.5.3 函数bsp_spiTransfer

    函数原型:

    void bsp_spiTransfer(void)

    函数描述:

    此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。

    73.6 W25QXX板级支持包(bsp_spi_flash.c)

    W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:

    •   sf_ReadBuffer
    •   sf_WriteBuffer
    •   sf_EraseSector
    •   sf_EraseChip
    •   sf_EraseSector

    73.6.1 函数sf_ReadBuffer

    函数原型:

    void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)

    函数描述:

    此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。

    函数参数:

    •   第1个参数用于存储从SPI Flash读取的数据。
    •   第2个参数是读取地址,不可以超过芯片容量。
    •   第3个参数是读取的数据大小,读取范围不可以超过芯片容量。

    73.6.2 函数sf_WriteBuffer(自动执行擦除)

    函数原型:

    uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)

    函数描述:

    此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。

    函数参数:

    •   第1个参数是源数据缓冲区。
    •   第2个参数是目标区域首地址。
    •   第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。
    •   返回值,返回1表示成功,返回0表示失败。

    73.6.3 函数sf_EraseSector

    函数原型:

    void sf_EraseSector(uint32_t _uiSectorAddr)

    函数描述:

    此函数主要用于扇区擦除,一个扇区大小是4KB。

    函数参数:

    •   第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。

    73.6.4 函数sf_EraseChip

    函数原型:

    void sf_EraseChip(void)

    函数描述:

    此函数主要用于整个芯片擦除。

    73.6.5 函数sf_PageWrite(不推荐)

    函数原型:

    void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)

    函数描述:

    此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。

    函数参数:

    •   第1个参数是数据源缓冲区。
    •   第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。
    •   第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。

    73.7 W25QXX驱动移植和使用

    W25QXX移植步骤如下:

    •   第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。
    •   第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
    /*
    *********************************************************************************************************
    *                                时钟,引脚,DMA,中断等宏定义
    *********************************************************************************************************
    */
    #define SPIx                            SPI1
    #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
    #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()
    
    #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
    #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()
    
    #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    #define SPIx_SCK_GPIO                    GPIOB
    #define SPIx_SCK_PIN                    GPIO_PIN_3
    #define SPIx_SCK_AF                        GPIO_AF5_SPI1
    
    #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    #define SPIx_MISO_GPIO                    GPIOB
    #define SPIx_MISO_PIN                     GPIO_PIN_4
    #define SPIx_MISO_AF                    GPIO_AF5_SPI1
    
    #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    #define SPIx_MOSI_GPIO                    GPIOB
    #define SPIx_MOSI_PIN                     GPIO_PIN_5
    #define SPIx_MOSI_AF                    GPIO_AF5_SPI1
    
    #define SPIx_TX_DMA_STREAM               DMA2_Stream3
    #define SPIx_RX_DMA_STREAM               DMA2_Stream2
    
    #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
    #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX
    
    #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn
    #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn
    
    #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
    #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler
    
    #define SPIx_IRQn                        SPI1_IRQn
    #define SPIx_IRQHandler                  SPI1_IRQHandler
    •   第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
    /* 定义串行Flash ID */
    enum
    {
        SST25VF016B_ID = 0xBF2541,
        MX25L1606E_ID  = 0xC22015,
        W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
        W25Q128_ID     = 0xEF4018
    };
    •   第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
    /*
    *********************************************************************************************************
    *    函 数 名: 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 */
                    break;
    
                case MX25L1606E_ID:
                    strcpy(g_tSF.ChipName, "MX25L1606E");
                    g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
                    g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
                    break;
    
                case W25Q64BV_ID:
                    strcpy(g_tSF.ChipName, "W25Q64");
                    g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
                    g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
                    break;
                
                case W25Q128_ID:
                    strcpy(g_tSF.ChipName, "W25Q128");
                    g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */
                    g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
                    break;            
    
                default:
                    strcpy(g_tSF.ChipName, "Unknow Flash");
                    g_tSF.TotalSize = 2 * 1024 * 1024;
                    g_tSF.SectorSize = 4 * 1024;
                    break;
            }
        }
    }
    •   第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。
    /*
    *********************************************************************************************************
    *    函 数 名: sf_SetCS
    *    功能说明: 串行FALSH片选控制函数
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void sf_SetCS(uint8_t _Level)
    {
        if (_Level == 0)
        {
            bsp_SpiBusEnter();    
            bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
            SF_CS_0();
        }
        else
        {        
            SF_CS_1();    
            bsp_SpiBusExit();        
        }
    }
    •   第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。
    /* 串行Flash的片选GPIO端口, PD13  */
    #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()
    #define SF_CS_GPIO                    GPIOD
    #define SF_CS_PIN                    GPIO_PIN_13
    
    #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) 
    #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
    •   第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
    /* 配置SRAM4的MPU属性为Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    •   第8步:初始化SPI。
    /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    bsp_InitSPIBus();    /* 配置SPI总线 */        
    bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    •   第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
    •   第10步:应用方法看本章节配套例子即可。

    73.8 实验例程设计框架

    通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

     

      第1阶段,上电启动阶段:

    • 这部分在第14章进行了详细说明。

      第2阶段,进入main函数:

    • 第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
    • 第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。

    73.9 实验例程说明(MDK)

    配套例子:

    V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)

    V7-050_串行SPI Flash W25QXX读写例程(DMA方式)

    V7-051_串行SPI Flash W25QXX读写例程(中断方式)

    实验目的:

    1. 学习SPI Flash的读写实现,支持查询,中断和DMA方式。

    实验操作:

    1. 支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
    2. printf("请选择操作命令: ");
    3. printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】 ", TEST_ADDR, TEST_SIZE);
    4. printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】 ", TEST_ADDR, TEST_SIZE);
    5. printf("【3 - 擦除整个串行Flash】 ");
    6. printf("【4 - 写整个串行Flash, 全0x55】 ");
    7. printf("【5 - 读整个串行Flash, 测试读速度】 ");
    8. printf("【Z - 读取前1K,地址自动减少】 ");
    9. printf("【X - 读取后1K,地址自动增加】 ");
    10. printf("其他任意键 - 显示命令提示 ");

    上电后串口打印的信息:

    波特率 115200,数据位 8,奇偶校验位无,停止位 1。

     

    程序设计:

      系统栈大小分配:

     

      RAM空间用的DTCM:

     

      硬件外设初始化

    硬件外设的初始化是在 bsp.c 文件实现:

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_Init
    *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_Init(void)
    {
        /* 配置MPU */
        MPU_Config();
        
        /* 使能L1 Cache */
        CPU_CACHE_Enable();
    
        /* 
           STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
           - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
           - 设置NVIV优先级分组为4。
         */
        HAL_Init();
    
        /* 
           配置系统时钟到400MHz
           - 切换使用HSE。
           - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
        */
        SystemClock_Config();
    
        /* 
           Event Recorder:
           - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
           - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
        */    
    #if Enable_EventRecorder == 1  
        /* 初始化EventRecorder并开启 */
        EventRecorderInitialize(EventRecordAll, 1U);
        EventRecorderStart();
    #endif
        
    bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       
        bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
        bsp_InitTimer();       /* 初始化滴答定时器 */
        bsp_InitLPUart();     /* 初始化串口 */
        bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
        bsp_InitLed();         /* 初始化LED */    
    bsp_InitExtSDRAM(); /* 初始化SDRAM */
    
        /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
        bsp_InitSPIBus();    /* 配置SPI总线 */        
        bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    }

      MPU配置和Cache配置:

    数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4

    /*
    *********************************************************************************************************
    *    函 数 名: MPU_Config
    *    功能说明: 配置MPU
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void MPU_Config( void )
    {
        MPU_Region_InitTypeDef MPU_InitStruct;
    
        /* 禁止 MPU */
        HAL_MPU_Disable();
    
        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x24000000;
        MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
        
        
        /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x60000000;
        MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
        
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
        /* 配置SRAM4的MPU属性为Non-cacheable */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x38000000;
        MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
        /*使能 MPU */
        HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: CPU_CACHE_Enable
    *    功能说明: 使能L1 Cache
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void CPU_CACHE_Enable(void)
    {
        /* 使能 I-Cache */
        SCB_EnableICache();
    
        /* 使能 D-Cache */
        SCB_EnableDCache();
    }

      每10ms调用一次按键处理:

    按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_RunPer10ms
    *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_RunPer10ms(void)
    {
        bsp_KeyScan10ms();
    }

      主功能:

    主程序实现如下操作:

    •   启动一个自动重装软件定时器,每100ms翻转一次LED2。
    •   支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
    •   请选择操作命令:
    •   1 - 读串行Flash
    •   2 - 写串行Flash
    •   3 - 擦除整个串行Flash
    •   4 - 写整个串行Flash
    •   5 - 读整个串行Flash
    •   Z - 读取前1K
    •   X - 读取后1K
    /*
    *********************************************************************************************************
    *    函 数 名: DemoSpiFlash
    *    功能说明: 串行EEPROM读写例程
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void DemoSpiFlash(void)
    {
        uint8_t cmd;
        uint32_t uiReadPageNo = 0;
    
        
        /* 检测串行Flash OK */
        printf("检测到串行Flash, ID = %08X, 型号: %s 
    ", g_tSF.ChipID , g_tSF.ChipName);
        printf("    容量 : %dM字节, 扇区大小 : %d字节
    ", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    
        sfDispMenu();        /* 打印命令提示 */
        
        bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
        
        while(1)
        {
            bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
            
            /* 判断定时器超时时间 */
            if (bsp_CheckTimer(0))    
            {
                /* 每隔100ms 进来一次 */  
                bsp_LedToggle(2);
            }
            
            if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
            {
                switch (cmd)
                {
                    case '1':
                        printf("
    【1 - 读串行Flash, 地址:0x%X,长度:%d字节】
    ", TEST_ADDR, TEST_SIZE);
                        sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
                        break;
    
                    case '2':
                        printf("
    【2 - 写串行Flash, 地址:0x%X,长度:%d字节】
    ", TEST_ADDR, TEST_SIZE);
                        sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
                        break;
    
                    case '3':
                        printf("
    【3 - 擦除整个串行Flash】
    ");
                        printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
                        sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
                        break;
    
                    case '4':
                        printf("
    【4 - 写整个串行Flash, 全0x55】
    ");
                        printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
                        sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
                        break;
    
                    case '5':
                        printf("
    【5 - 读整个串行Flash, %dM字节】
    ", g_tSF.TotalSize/(1024*1024));
                        sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
                        break;
    
                    case 'z':
                    case 'Z': /* 读取前1K */
                        if (uiReadPageNo > 0)
                        {
                            uiReadPageNo--;
                        }
                        else
                        {
                            printf("已经是最前
    ");
                        }
                        sfViewData(uiReadPageNo * 1024);
                        break;
    
                    case 'x':
                    case 'X': /* 读取后1K */
                        if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
                        {
                            uiReadPageNo++;
                        }
                        else
                        {
                            printf("已经是最后
    ");
                        }
                        sfViewData(uiReadPageNo * 1024);
                        break;
    
                    default:
                        sfDispMenu();    /* 无效命令,重新打印命令提示 */
                        break;
    
                }
            }
        }
    }

    73.10          实验例程说明(IAR)

    配套例子:

    V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)

    V7-050_串行SPI Flash W25QXX读写例程(DMA方式)

    V7-051_串行SPI Flash W25QXX读写例程(中断方式)

    实验目的:

    1. 学习SPI Flash的读写实现,支持查询,中断和DMA方式。

    实验操作:

    1. 支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
    2. printf("请选择操作命令: ");
    3. printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】 ", TEST_ADDR, TEST_SIZE);
    4. printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】 ", TEST_ADDR, TEST_SIZE);
    5. printf("【3 - 擦除整个串行Flash】 ");
    6. printf("【4 - 写整个串行Flash, 全0x55】 ");
    7. printf("【5 - 读整个串行Flash, 测试读速度】 ");
    8. printf("【Z - 读取前1K,地址自动减少】 ");
    9. printf("【X - 读取后1K,地址自动增加】 ");
    10. printf("其他任意键 - 显示命令提示 ");

    上电后串口打印的信息:

    波特率 115200,数据位 8,奇偶校验位无,停止位 1。

     

    程序设计:

      系统栈大小分配:

     

      RAM空间用的DTCM:

     

      硬件外设初始化

    硬件外设的初始化是在 bsp.c 文件实现:

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_Init
    *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_Init(void)
    {
        /* 配置MPU */
        MPU_Config();
        
        /* 使能L1 Cache */
        CPU_CACHE_Enable();
    
        /* 
           STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
           - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
           - 设置NVIV优先级分组为4。
         */
        HAL_Init();
    
        /* 
           配置系统时钟到400MHz
           - 切换使用HSE。
           - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
        */
        SystemClock_Config();
    
        /* 
           Event Recorder:
           - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
           - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
        */    
    #if Enable_EventRecorder == 1  
        /* 初始化EventRecorder并开启 */
        EventRecorderInitialize(EventRecordAll, 1U);
        EventRecorderStart();
    #endif
        
    bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       
        bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
        bsp_InitTimer();      /* 初始化滴答定时器 */
        bsp_InitLPUart();    /* 初始化串口 */
        bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
        bsp_InitLed();        /* 初始化LED */    
    bsp_InitExtSDRAM(); /* 初始化SDRAM */
    
        /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
        bsp_InitSPIBus();    /* 配置SPI总线 */        
        bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    }

      MPU配置和Cache配置:

    数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

    /*
    *********************************************************************************************************
    *    函 数 名: MPU_Config
    *    功能说明: 配置MPU
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void MPU_Config( void )
    {
        MPU_Region_InitTypeDef MPU_InitStruct;
    
        /* 禁止 MPU */
        HAL_MPU_Disable();
    
        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x24000000;
        MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
        
        
        /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x60000000;
        MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
        
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
        /* 配置SRAM4的MPU属性为Non-cacheable */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x38000000;
        MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
        /*使能 MPU */
        HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: CPU_CACHE_Enable
    *    功能说明: 使能L1 Cache
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void CPU_CACHE_Enable(void)
    {
        /* 使能 I-Cache */
        SCB_EnableICache();
    
        /* 使能 D-Cache */
        SCB_EnableDCache();
    }

      每10ms调用一次按键处理:

    按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_RunPer10ms
    *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_RunPer10ms(void)
    {
        bsp_KeyScan10ms();
    }

      主功能:

    主程序实现如下操作:

    •   启动一个自动重装软件定时器,每100ms翻转一次LED2。
    •   支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
    •   请选择操作命令:
    •   1 - 读串行Flash
    •   2 - 写串行Flash
    •   3 - 擦除整个串行Flash
    •   4 - 写整个串行Flash
    •   5 - 读整个串行Flash
    •   Z - 读取前1K
    •   X - 读取后1K
    /*
    *********************************************************************************************************
    *    函 数 名: DemoSpiFlash
    *    功能说明: 串行EEPROM读写例程
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void DemoSpiFlash(void)
    {
        uint8_t cmd;
        uint32_t uiReadPageNo = 0;
    
        
        /* 检测串行Flash OK */
        printf("检测到串行Flash, ID = %08X, 型号: %s 
    ", g_tSF.ChipID , g_tSF.ChipName);
        printf("    容量 : %dM字节, 扇区大小 : %d字节
    ", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    
        sfDispMenu();        /* 打印命令提示 */
        
        bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
        
        while(1)
        {
            bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
            
            /* 判断定时器超时时间 */
            if (bsp_CheckTimer(0))    
            {
                /* 每隔100ms 进来一次 */  
                bsp_LedToggle(2);
            }
            
            if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
            {
                switch (cmd)
                {
                    case '1':
                        printf("
    【1 - 读串行Flash, 地址:0x%X,长度:%d字节】
    ", TEST_ADDR, TEST_SIZE);
                        sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
                        break;
    
                    case '2':
                        printf("
    【2 - 写串行Flash, 地址:0x%X,长度:%d字节】
    ", TEST_ADDR, TEST_SIZE);
                        sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
                        break;
    
                    case '3':
                        printf("
    【3 - 擦除整个串行Flash】
    ");
                        printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
                        sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
                        break;
    
                    case '4':
                        printf("
    【4 - 写整个串行Flash, 全0x55】
    ");
                        printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
                        sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
                        break;
    
                    case '5':
                        printf("
    【5 - 读整个串行Flash, %dM字节】
    ", g_tSF.TotalSize/(1024*1024));
                        sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
                        break;
    
                    case 'z':
                    case 'Z': /* 读取前1K */
                        if (uiReadPageNo > 0)
                        {
                            uiReadPageNo--;
                        }
                        else
                        {
                            printf("已经是最前
    ");
                        }
                        sfViewData(uiReadPageNo * 1024);
                        break;
    
                    case 'x':
                    case 'X': /* 读取后1K */
                        if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
                        {
                            uiReadPageNo++;
                        }
                        else
                        {
                            printf("已经是最后
    ");
                        }
                        sfViewData(uiReadPageNo * 1024);
                        break;
    
                    default:
                        sfDispMenu();    /* 无效命令,重新打印命令提示 */
                        break;
    
                }
            }
        }
    }

    73.11   总结

    本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。

  • 相关阅读:
    BZOJ 1726: [Usaco2006 Nov]Roadblocks第二短路
    BZOJ 1708: [Usaco2007 Oct]Money奶牛的硬币
    BZOJ 1642: [Usaco2007 Nov]Milking Time 挤奶时间
    BZOJ 1611: [Usaco2008 Feb]Meteor Shower流星雨
    BZOJ 1610: [Usaco2008 Feb]Line连线游戏
    BZOJ 1609: [Usaco2008 Feb]Eating Together麻烦的聚餐
    BZOJ 1607: [Usaco2008 Dec]Patting Heads 轻拍牛头
    BZOJ 1606: [Usaco2008 Dec]Hay For Sale 购买干草
    BZOJ 1083: [SCOI2005]繁忙的都市
    STL set的用法
  • 原文地址:https://www.cnblogs.com/armfly/p/12523868.html
Copyright © 2011-2022 走看看