1. 程式人生 > 實用技巧 >【STM32F407開發板使用者手冊】第32章 STM32F407的SPI匯流排應用之驅動W25QXX(支援查詢,中斷和DMA)

【STM32F407開發板使用者手冊】第32章 STM32F407的SPI匯流排應用之驅動W25QXX(支援查詢,中斷和DMA)

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

第32章 STM32F407的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的硬體設計如下:


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

  • 當前V5開發板實際外接的晶片是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)

配套例子:

V5-011_序列SPI Flash W25QXX讀寫例程(查詢方式)

V5-012_序列SPI Flash W25QXX讀寫例程(中斷方式)

V5-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暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 
       STM32F407 HAL 庫初始化,此時系統用的還是F407自帶的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)

配套例子:

V5-011_序列SPI Flash W25QXX讀寫例程(查詢方式)

V5-012_序列SPI Flash W25QXX讀寫例程(中斷方式)

V5-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暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 
       STM32F407 HAL 庫初始化,此時系統用的還是F407自帶的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.11 總結

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