【STM32F429開發板用戶手冊】第32章 STM32F429的SPI總線應用之驅動W25QXX(支持查詢,中斷和DMA)


最新教程下載:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255

第32章       STM32F429的SPI總線應用之驅動W25QXX(支持查詢,中斷和DMA)

本章節為大家講解標准SPI接線方式驅動W25QXX,實現了查詢,中斷和DMA三種方式。

32.1 初學者重要提示

32.2 W25QXX硬件設計

32.4 W25QXX關鍵知識點整理(重要)

32.5 W25QXX驅動設計

32.6 SPI總線板級支持包(bsp_spi_bus.c)

32.7 W25QXX板級支持包(bsp_spi_flash.c)

32.8 使用例程設計框架

32.9 實驗例程說明(MDK)

32.10 實驗例程說明(IAR)

32.11 總結

 

 

32.1 初學者重要提示

  1.   學習本章節前,務必優先學習第31章。
  2.   W25Q64FV屬於NOR型Flash存儲芯片。
  3.   W25Q64FV手冊下載地址:鏈接 (這是一個超鏈接),當前章節配套例子的Doc文件件里面也有存放。
  4.   本章第3小節整理的知識點比較重要,務必要了解下,特別是頁編程和頁回卷。
  5.   對SPI Flash W25QXX的不同接線方式(1線,2線或者4線,這里的線是指的數據線),編程命令是不同的。
  6.   W25Q64FV最高支持104MHz,但最高讀命令03H速度是50MHz。
  7.   文件bsp_spi_bus.c文件公共的總線驅動文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI設備的配置。
  8.   函數sf_WriteBuffer不需要用戶做擦除,會自動執行擦除功能,支持任意大小,任意地址,不超過芯片容量即可。

32.2 W25QXX硬件設計

STM32F4驅動W25QXX的硬件設計如下:

 

關於這個原理圖,要了解到以下幾個知識:

  •   當前V6開發板實際外接的芯片是W25Q64FV。
  •   CS片選最好接上拉電阻,防止意外操作。
  •   這里的PB3,PB4和PB5引腳可以復用SPI1,SPI3。實際應用中是復用的SPI1。
  •   W25Q64的WP引腳用於寫保護,低電平有效性,當前是直接高電平。
  •   HOLD引腳也是低電平有效,當前是將其接到高電平。此引腳的作用是CS片選低電平時,DO引腳輸出高阻,忽略CLK和DI引腳上的信號。

32.3 W25QXX關鍵知識點整理(重要)

驅動W25QXX前要先了解下這個芯片的相關信息。

 

32.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字節。

32.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) */

32.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字節。

 

32.3.4 W25QXX扇區擦除

SPI Flash的擦除支持扇區擦除(4KB),塊擦除(32KB或者64KB)以及整個芯片擦除。對於扇區擦除和塊擦除,使用的時候要注意一點,一般情況下,只需用戶給出扇區或者塊的首地址即可。

如果給的不是扇區或者塊的首地址也沒有關系的,只要此地址是在扇區或者塊的范圍內,此扇區或者塊也可以被正確擦除。不過建議使用時給首地址,方便管理。

32.3.5 W25QXX規格參數

這里我們主要了解擦寫耗時和支持的時鍾速度,下面是擦寫時間參數:

 

  •   頁編程時間:典型值0.45ms,最大值3ms。
  •   扇區擦除時間(4KB):典型值45-60ms,最大值400ms。
  •   塊擦除時間(32KB):典型值120ms,最大值1600ms。
  •   塊擦除時間(64KB):典型值150ms,最大值2000ms。
  •   整個芯片擦除時間:典型值20s,最大值100s。

 

支持的速度參數如下:

 

可以看到最高支持的讀時鍾(使用命令03H)速度是50MHz,其它命令速度可以做到104MHz。

32.4 W25QXX驅動設計

W25QXX的程序驅動框架設計如下:

有了這個框圖,程序設計就比較好理解了。

32.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.NSS               = SPI_NSS_SOFT;            /* 使用軟件方式管理片選引腳 */
    hspi.Init.Mode                  = SPI_MODE_MASTER;    /* SPI工作在主控模式 */

    /* 復位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接口上接了多個不同類型的芯片時,通過此函數可以方便的切換配置。

32.4.2 第2步:SPI總線的查詢,中斷和DMA方式設置

SPI驅動的查詢,中斷和DMA方式主要通過函數bsp_spiTransfer實現數據傳輸:

/*
*********************************************************************************************************
*                                 選擇DMA,中斷或者查詢方式
*********************************************************************************************************
*/
//#define USE_SPI_DMA    /* DMA方式  */
//#define USE_SPI_INT    /* 中斷方式 */
#define USE_SPI_POLL   /* 查詢方式 */

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/*
*********************************************************************************************************
*    函 數 名: 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方式。

32.4.3 第3步:W25QXX的時鍾極性和時鍾相位配置

首先回憶下STM32F4支持的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 = 0, CPHA = 0時

SCK引腳在空閑狀態處於低電平,SCK引腳的第1個邊沿捕獲傳輸的第1個數據。

 

有了F4支持的時序配置,再來看下W25Q的時序圖:

Mode0 : 空閑狀態的sck是低電平。

Mode1 : 空閑狀態的sck是高電平。

 

首先W25Q是上升沿做數據采集,所以STM32F4的可選的配置就是:

CHOL = 1,  CPHA = 1

CHOL = 0,  CPHA = 0

對於這兩種情況,具體選擇哪種,繼續往下看。W25Q有兩種SCK模式,分別是Mode0和Mode3,也就是空閑狀態下,SCK既可以是高電平也可以是低電平。這樣的話,這兩種情況都可以使用,經過實際測試,STM32F4使用這兩個配置均可以配置驅動W25Q。

32.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_2, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
        SF_CS_0();
    }
    else
    {        
        SF_CS_1();    
        bsp_SpiBusExit();        
    }
}

通過這種方式就有效的解決了單SPI接口管理多設備的問題。因為給每個設備都配了一個獨立的片選引腳,這樣就可以為每個設備都配置這么一個片選配置。

但是頻繁配置也比較繁瑣,所以函數bsp_InitSPIParam里面做了特別處理。當前配置與之前配置相同的情況下無需重復配置。

32.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傳輸做了特別處理,方便分包進行。

32.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內部寫操作完成 */
}

32.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;    /* 成功 */
}

32.5 SPI總線板級支持包(bsp_spi_bus.c)

SPI總線驅動文件bsp_spi_bus.c主要實現了如下幾個API供用戶調用:

  •   bsp_InitSPIBus
  •   bsp_InitSPIParam
  •   bsp_spiTransfer

32.5.1 函數bsp_InitSPIBus

函數原型:

void bsp_InitSPIBus(void)

函數描述:

此函數主要用於SPI總線的初始化,在bsp.c文件調用一次即可。

32.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引腳在空閑狀態處於高電平

32.5.3 函數bsp_spiTransfer

函數原型:

void bsp_spiTransfer(void)

函數描述:

此函數用於啟動SPI數據傳輸,支持查詢,中斷和DMA方式傳輸。

32.6 W25QXX板級支持包(bsp_spi_flash.c)

W25QXX驅動文件bsp_spi_flash.c主要實現了如下幾個API供用戶調用:

  •   sf_ReadBuffer
  •   sf_WriteBuffer
  •   sf_EraseSector
  •   sf_EraseChip
  •   sf_EraseSector

32.6.1 函數sf_ReadBuffer

函數原型:

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

函數描述:

此函數主要用於從SPI Flash讀取數據,支持任意大小,任意地址,不超過芯片容量即可。

函數參數:

  •   第1個參數用於存儲從SPI Flash讀取的數據。
  •   第2個參數是讀取地址,不可以超過芯片容量。
  •   第3個參數是讀取的數據大小,讀取范圍不可以超過芯片容量。

 

32.6.2 函數sf_WriteBuffer(自動執行擦除)

函數原型:

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

函數描述:

此函數主要用於SPI Flash讀取數據,支持任意大小,任意地址,不超過芯片容量即可。特別注意,此函數會自動執行擦除,無需用戶處理。

函數參數:

  •   第1個參數是源數據緩沖區。
  •   第2個參數是目標區域首地址。
  •   第3個參數是數據個數,支持任意大小,但不能超過芯片容量,單位字節個數。
  •   返回值,返回1表示成功,返回0表示失敗。

32.6.3 函數sf_EraseSector

函數原型:

void sf_EraseSector(uint32_t _uiSectorAddr)

函數描述:

此函數主要用於扇區擦除,一個扇區大小是4KB。

函數參數:

  •   第1個參數是扇區地址,比如擦除扇區0,此處填0x0000,擦除扇區1,此處填0x1000,擦除扇區2,此處填0x2000,以此類推。

32.6.4 函數sf_EraseChip

函數原型:

void sf_EraseChip(void)

函數描述:

此函數主要用於整個芯片擦除。

32.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字節的整數倍,單位字節個數。

32.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_CHANNEL         DMA_CHANNEL_3
#define SPIx_TX_DMA_STREAM          DMA2_Stream3
#define SPIx_RX_DMA_CHANNEL         DMA_CHANNEL_3
#define SPIx_RX_DMA_STREAM          DMA2_Stream0


#define SPIx_IRQn                   SPI1_IRQn
#define SPIx_IRQHandler             SPI1_IRQHandler
#define SPIx_DMA_TX_IRQn            DMA2_Stream3_IRQn
#define SPIx_DMA_RX_IRQn            DMA2_Stream0_IRQn
#define SPIx_DMA_TX_IRQHandler      DMA2_Stream3_IRQHandler
#define SPIx_DMA_RX_IRQHandler      DMA2_Stream0_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_2, 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步:初始化SPI。
/* 針對不同的應用程序,添加需要的底層驅動模塊初始化函數 */
bsp_InitSPIBus();    /* 配置SPI總線 */        
bsp_InitSFlash();    /* 初始化SPI 串行Flash */
  •   第8步:SPI Flash驅動主要用到HAL庫的SPI驅動文件,簡單省事些可以添加所有HAL庫C源文件進來。
  •   第9步:應用方法看本章節配套例子即可。

32.8 實驗例程設計框架

通過程序設計框架,讓大家先對配套例程有一個全面的認識,然后再理解細節,本次實驗例程的設計框架如下:

  第1階段,上電啟動階段:

  • 這部分在第14章進行了詳細說明。

  第2階段,進入main函數:

  •   第1部分,硬件初始化,主要是MPU,Cache,HAL庫,系統時鍾,滴答定時器和LED。
  •   第2部分,應用程序設計部分,實現SPI Flash的中斷,查詢和DMA方式操作。

32.9 實驗例程說明(MDK)

配套例子:

V6-011_串行SPI Flash W25QXX讀寫例程(查詢方式)

V6-012_串行SPI Flash W25QXX讀寫例程(中斷方式)

V6-013_串行SPI Flash W25QXX讀寫例程(DMA方式)

實驗目的:

  1. 學習SPI Flash的讀寫實現,支持查詢,中斷和DMA方式。

實驗操作:

  1. 支持以下7個功能,用戶通過電腦端串口軟件發送命令給開發板即可
  2. printf("請選擇操作命令:\r\n");
  3. printf("【1 - 讀串行Flash, 地址:0x%X,長度:%d字節】\r\n", TEST_ADDR, TEST_SIZE);
  4. printf("【2 - 寫串行Flash, 地址:0x%X,長度:%d字節】\r\n", TEST_ADDR, TEST_SIZE);
  5. printf("【3 - 擦除整個串行Flash】\r\n");
  6. printf("【4 - 寫整個串行Flash, 全0x55】\r\n");
  7. printf("【5 - 讀整個串行Flash, 測試讀速度】\r\n");
  8. printf("【Z - 讀取前1K,地址自動減少】\r\n");
  9. printf("【X - 讀取后1K,地址自動增加】\r\n");
  10. printf("其他任意鍵 - 顯示命令提示\r\n");

上電后串口打印的信息:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1。

 

程序設計:

  系統棧大小分配:

 

  硬件外設初始化

硬件外設的初始化是在 bsp.c 文件實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次
*    形    參:無0
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 
       STM32H429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鍾:
       - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。
       - 設置NVIV優先級分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鍾到168MHz
       - 切換使用HSE。
       - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默認不開啟,如果要使能此選項,務必看V5開發板用戶手冊第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
    bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();        /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化擴展IO */
    bsp_InitLed();        /* 初始化LED */    
    BEEP_InitHard();    /* 初始化蜂鳴器 */

    /* 針對不同的應用程序,添加需要的底層驅動模塊初始化函數 */
    bsp_InitSPIBus();    /* 配置SPI總線 */        
    bsp_InitSFlash();    /* 初始化SPI 串行Flash */
}

  主功能:

主程序實現如下操作:

  •   啟動一個自動重裝軟件定時器,每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 \r\n", g_tSF.ChipID , g_tSF.ChipName);
    printf("    容量 : %dM字節, 扇區大小 : %d字節\r\n", 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("\r\n【1 - 讀串行Flash, 地址:0x%X,長度:%d字節】\r\n", TEST_ADDR, TEST_SIZE);
                    sfReadTest();    /* 讀串行Flash數據,並打印出來數據內容 */
                    break;

                case '2':
                    printf("\r\n【2 - 寫串行Flash, 地址:0x%X,長度:%d字節】\r\n", TEST_ADDR, TEST_SIZE);
                    sfWriteTest();    /* 寫串行Flash數據,並打印寫入速度 */
                    break;

                case '3':
                    printf("\r\n【3 - 擦除整個串行Flash】\r\n");
                    printf("整個Flash擦除完畢大概需要20秒左右,請耐心等待");
                    sfErase();        /* 擦除串行Flash數據,實際上就是寫入全0xFF */
                    break;

                case '4':
                    printf("\r\n【4 - 寫整個串行Flash, 全0x55】\r\n");
                    printf("整個Flash寫入完畢大概需要20秒左右,請耐心等待");
                    sfWriteAll(0x55);/* 擦除串行Flash數據,實際上就是寫入全0xFF */
                    break;

                case '5':
                    printf("\r\n【5 - 讀整個串行Flash, %dM字節】\r\n", g_tSF.TotalSize/(1024*1024));
                    sfTestReadSpeed(); /* 讀整個串行Flash數據,測試速度 */
                    break;

                case 'z':
                case 'Z': /* 讀取前1K */
                    if (uiReadPageNo > 0)
                    {
                        uiReadPageNo--;
                    }
                    else
                    {
                        printf("已經是最前\r\n");
                    }
                    sfViewData(uiReadPageNo * 1024);
                    break;

                case 'x':
                case 'X': /* 讀取后1K */
                    if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
                    {
                        uiReadPageNo++;
                    }
                    else
                    {
                        printf("已經是最后\r\n");
                    }
                    sfViewData(uiReadPageNo * 1024);
                    break;

                default:
                    sfDispMenu();    /* 無效命令,重新打印命令提示 */
                    break;

            }
        }
    }
}

32.10          實驗例程說明(IAR)

配套例子:

V6-011_串行SPI Flash W25QXX讀寫例程(查詢方式)

V6-012_串行SPI Flash W25QXX讀寫例程(中斷方式)

V6-013_串行SPI Flash W25QXX讀寫例程(DMA方式)

實驗目的:

  1. 學習SPI Flash的讀寫實現,支持查詢,中斷和DMA方式。

實驗操作:

  1. 支持以下7個功能,用戶通過電腦端串口軟件發送命令給開發板即可
  2. printf("請選擇操作命令:\r\n");
  3. printf("【1 - 讀串行Flash, 地址:0x%X,長度:%d字節】\r\n", TEST_ADDR, TEST_SIZE);
  4. printf("【2 - 寫串行Flash, 地址:0x%X,長度:%d字節】\r\n", TEST_ADDR, TEST_SIZE);
  5. printf("【3 - 擦除整個串行Flash】\r\n");
  6. printf("【4 - 寫整個串行Flash, 全0x55】\r\n");
  7. printf("【5 - 讀整個串行Flash, 測試讀速度】\r\n");
  8. printf("【Z - 讀取前1K,地址自動減少】\r\n");
  9. printf("【X - 讀取后1K,地址自動增加】\r\n");
  10. printf("其他任意鍵 - 顯示命令提示\r\n");

上電后串口打印的信息:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1。

 

程序設計:

  系統棧大小分配:

 

  硬件外設初始化

硬件外設的初始化是在 bsp.c 文件實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次
*    形    參:無0
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 
       STM32H429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鍾:
       - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。
       - 設置NVIV優先級分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鍾到168MHz
       - 切換使用HSE。
       - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默認不開啟,如果要使能此選項,務必看V5開發板用戶手冊第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
    bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();   /* 初始化擴展IO */
    bsp_InitLed();        /* 初始化LED */    
    BEEP_InitHard();   /* 初始化蜂鳴器 */
}

  主功能:

主程序實現如下操作:

  •   啟動一個自動重裝軟件定時器,每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 \r\n", g_tSF.ChipID , g_tSF.ChipName);
    printf("    容量 : %dM字節, 扇區大小 : %d字節\r\n", 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("\r\n【1 - 讀串行Flash, 地址:0x%X,長度:%d字節】\r\n", TEST_ADDR, TEST_SIZE);
                    sfReadTest();    /* 讀串行Flash數據,並打印出來數據內容 */
                    break;

                case '2':
                    printf("\r\n【2 - 寫串行Flash, 地址:0x%X,長度:%d字節】\r\n", TEST_ADDR, TEST_SIZE);
                    sfWriteTest();    /* 寫串行Flash數據,並打印寫入速度 */
                    break;

                case '3':
                    printf("\r\n【3 - 擦除整個串行Flash】\r\n");
                    printf("整個Flash擦除完畢大概需要20秒左右,請耐心等待");
                    sfErase();        /* 擦除串行Flash數據,實際上就是寫入全0xFF */
                    break;

                case '4':
                    printf("\r\n【4 - 寫整個串行Flash, 全0x55】\r\n");
                    printf("整個Flash寫入完畢大概需要20秒左右,請耐心等待");
                    sfWriteAll(0x55);/* 擦除串行Flash數據,實際上就是寫入全0xFF */
                    break;

                case '5':
                    printf("\r\n【5 - 讀整個串行Flash, %dM字節】\r\n", g_tSF.TotalSize/(1024*1024));
                    sfTestReadSpeed(); /* 讀整個串行Flash數據,測試速度 */
                    break;

                case 'z':
                case 'Z': /* 讀取前1K */
                    if (uiReadPageNo > 0)
                    {
                        uiReadPageNo--;
                    }
                    else
                    {
                        printf("已經是最前\r\n");
                    }
                    sfViewData(uiReadPageNo * 1024);
                    break;

                case 'x':
                case 'X': /* 讀取后1K */
                    if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
                    {
                        uiReadPageNo++;
                    }
                    else
                    {
                        printf("已經是最后\r\n");
                    }
                    sfViewData(uiReadPageNo * 1024);
                    break;

                default:
                    sfDispMenu();    /* 無效命令,重新打印命令提示 */
                    break;

            }
        }
    }
}

32.11   總結

本章節就為大家講解這么多,實際應用中根據需要選擇DMA,中斷和查詢方式。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM