完整教程下載下傳位址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第79章 STM32H7的QSPI總線應用之驅動W25QXX(支援查詢和MDMA)
本章節為大家講解标準QSPI接線方式驅動W25QXX,實作了查詢和MDMA兩種方式。
79.1 初學者重要提示
79.2 W25QXX硬體設計
79.3 W25QXX關鍵知識點整理(重要)
79.4 W25QXX驅動設計
79.5 W25QXX闆級支援包(bsp_spi_flash.c)
79.6 W25QXX驅動移植和使用
79.7 使用例程設計架構
79.8 實驗例程說明(MDK)
79.9 實驗例程說明(IAR)
79.10 總結
1、 學習本章節前,務必優先學習第79章。
2、 W25Q256JV屬于NOR型Flash存儲晶片。
3、 W25Q256JV手冊下載下傳位址:連結 (這是一個超連結),目前章節配套例子的Doc檔案件裡面也有存放。

4、 本章第3小節整理的知識點比較重要,務必要了解下,特别是頁程式設計和頁回卷。
5、 對QSPI Flash W25Q256JV的不同接線方式(1線,2線或者4線,這裡的線是指的資料線),程式設計指令是不同的。
6、 W25Q256JV最高支援133MHz。
7、 STM32H7驅動QSPI Flash的4線DMA模式,讀速度48MB/S左右。http://www.armbbs.cn/forum.php?mod=viewthread&tid=91616 。
8、 記憶體映射模式下,最後一個位元組無法正常讀取的解決辦法:http://www.armbbs.cn/forum.php?mod=viewthread&tid=96726 。
9、 本章配套例子的DMA是采用性能最強的MDMA。
STM32H7驅動W25Q256JV的硬體設計如下:
關于這個原理圖,要了解到以下幾個知識:
- V7開發闆實際外接的晶片是W25Q256JV。
- CS片選最好接上拉電阻,防止意外操作。
- W25Q256的WP引腳用于寫保護,低電平有效性,目前是将其作為4方式的IO2。
- HOLD引腳也是低電平有效,目前是将其接到高電平。此引腳的作用是CS片選低電平時,DO引腳輸出高阻,忽略CLK和DI引腳上的信号。目前是将其作為4線方式的IO3。
驅動W25QXX前要先了解下這個晶片的相關資訊。
79.3.1 W25QXX基礎資訊
- W25Q256JV是32MB(256Mbit)。
- W25Q256JV支援标準SPI(單線SPI),用到引腳CLK、CS,DI和DO引腳。
支援兩線SPI,用到引腳CLK、CS、IO0、IO1 。
支援四線SPI,用到引腳CLK、CS、IO0、IO1,IO2、IO3。
(注:這裡幾線的意思是幾個資料線)。
- W25Q256JV支援的最高時鐘是133MHz。
- 每個扇區最少支援10萬次擦寫,可以儲存20年資料。
- 頁大小是256位元組,支援頁程式設計,也就是一次編寫256個位元組,也可以一個一個編寫。
- 支援4KB為機關的扇區擦除,也可以32KB或者64KB為機關的擦除。
整體框圖如下:
W25Q256JV:
- 有512個Block,每個Block大小64KB。
- 每個Block有16個Sector,每個Sector大小4KB。
- 每個Sector有16個Page,每個Page大小是256位元組。
79.3.2 W25QXX指令
使用W25Q256的接線方式不同,使用的指令也有所不同,使用的時候務必要注意,目前我們使用的QSPI,即4線SPI,并且用的4位元組位址模式,使用的指令如下:
目前主要用到如下幾個指令:
#define WRITE_ENABLE_CMD 0x06 /* 寫使能指令 */
#define READ_ID_CMD2 0x9F /* 讀取ID指令 */
#define READ_STATUS_REG_CMD 0x05 /* 讀取狀态指令 */
#define BULK_ERASE_CMD 0xC7 /* 整個晶片擦除指令 */
#define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD 0x21 /* 32bit位址扇區擦除指令, 4KB */
#define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD 0x34 /* 32bit位址的4線快速寫入指令 */
#define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC /* 32bit位址的4線快速讀取指令 */
79.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位元組。
79.3.4 W25QXX扇區擦除
SPI Flash的擦除支援扇區擦除(4KB),塊擦除(32KB或者64KB)以及整個晶片擦除。對于扇區擦除和塊擦除,使用的時候要注意一點,一般情況下,隻需使用者給出扇區或者塊的首位址即可。
如果給的不是扇區或者塊的首位址也沒有關系的,隻要此位址是在扇區或者塊的範圍内,此扇區或者塊也可以被正确擦除。不過建議使用時給首位址,友善管理。
79.3.5 W25QXX規格參數
這裡我們主要了解擦寫耗時和支援的時脈速度,下面是擦寫時間參數:
- 頁程式設計時間:典型值0.4ms,最大值3ms。
- 扇區擦除時間(4KB):典型值50ms,最大值400ms。
- 塊擦除時間(32KB):典型值120ms,最大值1600ms。
- 塊擦除時間(64KB):典型值150ms,最大值2000ms。
- 整個晶片擦除時間:典型值80s,最大值400s。
支援的速度參數如下:
可以看到最高支援的讀時鐘(使用指令03H)速度是50MHz,其它指令速度可以做到133MHz。
W25QXX的程式驅動架構設計如下:
有了這個框圖,程式設計就比較好了解了。
79.4.1 第1步:QSPI總線配置
QSPI總線配置如下:
/*
*********************************************************************************************************
* 函 數 名: bsp_InitQSPI_W25Q256
* 功能說明: QSPI Flash硬體初始化,配置基本參數
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitQSPI_W25Q256(void)
{
/* 複位QSPI */
QSPIHandle.Instance = QUADSPI;
if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 設定時脈速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */
QSPIHandle.Init.ClockPrescaler = 1;
/* 設定FIFO閥值,範圍1 - 32 */
QSPIHandle.Init.FifoThreshold = 32;
/*
QUADSPI在FLASH驅動信号後過半個CLK周期才對FLASH驅動的資料采樣。
在外部信号延遲時,這有利于推遲資料采樣。
*/
QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
/*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */
//QSPI_FLASH_SIZE - 1; 需要擴大一倍,否則記憶體映射方位最後1個位址時,會異常。
QSPIHandle.Init.FlashSize = QSPI_FLASH_SIZE;
/* 指令之間的CS片選至少保持2個時鐘周期的高電平 */
QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
/*
MODE0: 表示片選信号空閑期間,CLK時鐘信号是低電平
MODE3: 表示片選信号空閑期間,CLK時鐘信号是高電平
*/
QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;
/* QSPI有兩個BANK,這裡使用的BANK1 */
QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1;
/* V7開發闆僅使用了BANK1,這裡是禁止雙BANK */
QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
/* 初始化配置QSPI */
if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
}
QSPI這部配置設定置,要特别注意Flash大小的設定,這裡做了特别處理,本來是應該填入QSPI_FLASH_SIZE-1,而我們實際上填入的是QSPI_FLASH_SIZE,主要是因為記憶體映射模式下,最後一個位元組通路有問題。
79.4.2 第2步:QSPI總線的查詢和MDMA方式設定
本章提供了QSPI Flash的查詢和MDMA兩種方式的例子,驅動的差別是調用的API不同,查詢方式調用的API是HAL_QSPI_Transmit和HAL_QSPI_Receive,而DMA方式使用的API是HAL_QSPI_Transmit_DMA和HAL_QSPI_Receive_DMA。
79.4.3 第3步:W25QXX的讀取實作
注:這裡以查詢方式的API進行說明,DMA方式是一樣的。
W25QXX的讀取功能是發送的4線快速讀取指令0xEC,設定任意位址都可以讀取資料,隻要不超過晶片容量即可(如果采用的DMA方式,限制每次最大讀取65536位元組)。
/*
*********************************************************************************************************
* 函 數 名: QSPI_ReadBuffer
* 功能說明: 連續讀取若幹位元組,位元組個數不能超出晶片容量。
* 形 參: _pBuf : 資料源緩沖區。
* _uiReadAddr :起始位址。
* _usSize :資料個數, 可以大于PAGE_SIZE, 但是不能超出晶片總容量。
* 返 回 值: 無
*********************************************************************************************************
*/
void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
{
QSPI_CommandTypeDef sCommand = {0};
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1線方式發送指令 */
sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位位址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 無交替位元組 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支援DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,資料輸出延遲 */
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次傳輸要發指令 */
/* 讀取資料 */
sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit位址的4線快速讀取指令 */
sCommand.DummyCycles = 6; /* 空周期 */
sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4線位址 */
sCommand.DataMode = QSPI_DATA_4_LINES; /* 4線資料 */
sCommand.NbData = _uiSize; /* 讀取的資料大小 */
sCommand.Address = _uiReadAddr; /* 讀取資料的起始位址 */
if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 讀取 */
if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
}
此時函數使用的指令0xEC對應的W25Q256JV手冊說明,注意紅色方框位置:
左上角的1-4-4就是指令階段使用1個IO,位址階段使用4個IO,資料階段也是使用4個IO,并且采用的4位元組位址方式,反映到程式裡面就是:
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_4_LINES;
79.4.4 第4步:W25QXX的程式設計實作
下面實作了一個頁程式設計函數:
/*
*********************************************************************************************************
* 函 數 名: QSPI_WriteBuffer
* 功能說明: 頁程式設計,頁大小256位元組,任意頁都可以寫入
* 形 參: _pBuf : 資料源緩沖區;
* _uiWriteAddr :目标區域首位址,即頁首位址,比如0, 256, 512等。
* _usWriteSize :資料個數,不能超過頁面大小,範圍1 - 256。
* 返 回 值: 1:成功, 0:失敗
*********************************************************************************************************
*/
uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)
{
QSPI_CommandTypeDef sCommand={0};
/* 寫使能 */
QSPI_WriteEnable(&QSPIHandle);
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1線方式發送指令 */
sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位位址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 無交替位元組 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支援DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,資料輸出延遲 */
sCommand.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD; /* 僅發送一次指令 */
/* 寫序列配置 */
sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit位址的4線快速寫入指令 */
sCommand.DummyCycles = 0; /* 不需要空周期 */
sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 4線位址方式 */
sCommand.DataMode = QSPI_DATA_4_LINES; /* 4線資料方式 */
sCommand.NbData = _usWriteSize; /* 寫資料大小 */
sCommand.Address = _uiWriteAddr; /* 寫入位址 */
if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
{
//return 0;
Error_Handler(__FILE__, __LINE__);
}
/* 啟動傳輸 */
if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)
{
//return 0;
Error_Handler(__FILE__, __LINE__);
}
QSPI_AutoPollingMemReady(&QSPIHandle);
return 1;
}
此時函數使用的指令0x34對應的W25Q256JV手冊說明,注意紅色方框位置:
79.4.5 第5步:W25QXX的扇區擦除實作
通過發送“扇區擦除指令+扇區位址”即可完成相應扇區的擦除,擦除的扇區大小是4KB。
/*
*********************************************************************************************************
* 函 數 名: QSPI_EraseSector
* 功能說明: 擦除指定的扇區,扇區大小4KB
* 形 參: _uiSectorAddr : 扇區位址,以4KB為機關的位址,比如0,4096, 8192等,
* 返 回 值: 無
*********************************************************************************************************
*/
void QSPI_EraseSector(uint32_t _uiSectorAddr)
{
QSPI_CommandTypeDef sCommand={0};
/* 寫使能 */
QSPI_WriteEnable(&QSPIHandle);
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1線方式發送指令 */
sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位位址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 無交替位元組 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支援DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,資料輸出延遲 */
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次傳輸都發指令 */
/* 擦除配置 */
sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD; /* 32bit位址方式的扇區擦除指令,扇區大小4KB*/
sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 位址發送是1線方式 */
sCommand.Address = _uiSectorAddr; /* 扇區首位址,保證是4KB整數倍 */
sCommand.DataMode = QSPI_DATA_NONE; /* 無需發送資料 */
sCommand.DummyCycles = 0; /* 無需空周期 */
if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
QSPI_AutoPollingMemReady(&QSPIHandle);
}
此時函數使用的指令0x21對應的W25Q256JV手冊說明,注意紅色方框位置:
左上角的1-1-1就是指令階段使用1個IO,位址階段使用1個IO,資料階段也是使用1個IO,并且采用的4位元組位址方式,反映到程式裡面就是:
sCommand.AddressMode = QSPI_ADDRESS_1_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_1_LINES;
79.4.6 第6步:W25QXX的整個晶片擦除實作
整個晶片的擦除可以通過擦除各個扇區來實作,也可以調用專門的整個晶片擦除指令實作。下面實作方法是發送整個晶片擦除指令實作:
/*
*********************************************************************************************************
* 函 數 名: QSPI_EraseChip
* 功能說明: 整個晶片擦除
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void QSPI_EraseChip(void)
{
QSPI_CommandTypeDef sCommand={0};
/* 寫使能 */
QSPI_WriteEnable(&QSPIHandle);
/* 基本配置 */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1線方式發送指令 */
sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位位址 */
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 無交替位元組 */
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支援DDR */
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,資料輸出延遲 */
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次傳輸都發指令 */
/* 擦除配置 */
sCommand.Instruction = BULK_ERASE_CMD; /* 整個晶片擦除指令*/
sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 位址發送是1線方式 */
sCommand.Address = 0; /* 位址 */
sCommand.DataMode = QSPI_DATA_NONE; /* 無需發送資料 */
sCommand.DummyCycles = 0; /* 無需空周期 */
if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
QSPI_AutoPollingMemReady(&QSPIHandle);
}
此時函數使用的指令0xC7對應的W25Q256JV手冊說明,注意紅色方框位置:
左上角的1-1-1就是指令階段使用1個IO,位址階段使用1個IO,資料階段也是使用1個IO,并且采用的4位元組位址方式,反應到程式裡面就是:
sCommand.AddressMode = QSPI_ADDRESS_1_LINES;
擦除用不到資料階段,sCommand.DataMode = QSPI_DATA_NONE即可。
79.4.7 第7步:W25QXX記憶體映射實作
通過記憶體映射模式,就可以像使用内部Flash一樣使用W25QXX,代碼實作如下:
/*
*********************************************************************************************************
* 函 數 名: QSPI_MemoryMapped
* 功能說明: QSPI記憶體映射,位址 0x90000000
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void QSPI_MemoryMapped(void)
{
QSPI_CommandTypeDef s_command = {0};
QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};
/* 基本配置 */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1線方式發送指令 */
s_command.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位位址 */
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 無交替位元組 */
s_command.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支援DDR */
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,資料輸出延遲 */
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次傳輸都發指令 */
/* 全部采用4線 */
s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速讀取指令 */
s_command.AddressMode = QSPI_ADDRESS_4_LINES; /* 4個位址線 */
s_command.DataMode = QSPI_DATA_4_LINES; /* 4個資料線 */
s_command.DummyCycles = 6; /* 空周期 */
/* 關閉溢出計數 */
s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
s_mem_mapped_cfg.TimeOutPeriod = 0;
if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
}
左上角的1-4-4就是指令階段使用1個IO,位址階段使用4個IO,資料階段也是使用4個IO,采用的4位元組位址方式,反應到程式裡面就是:
79.4.8 第8步:使用MDMA方式要注意Cache問題
如果使用MDMA方式的話,可以使用TCM RAM,此時不用考慮Cache問題。如果使用的是其它RAM空間,要考慮Cache問題。因為MDMA和CPU同時通路DMA緩沖造成的資料一緻性問題,将這塊空間關閉讀Cache和寫Cache,比如使用的AXI SRAM,這樣可以友善大家做測試,測試通過後,再根據需要開啟Cache。
/* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO 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_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_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);
79.5 W25QXX闆級支援包(bsp_qspi_w25q256.c)
W25QXX驅動檔案bsp_qspi_w25q256.c主要實作了如下幾個API供使用者調用:
- QSPI_ReadBuffer
- QSPI_WriteBuffer
- QSPI_EraseSector
- QSPI_EraseChip
- QSPI_MemoryMapped
79.5.1 函數QSPI_ReadBuffer
函數原型:
void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);
函數描述:
此函數主要用于從QSPI Flash讀取資料,支援任意大小,任意位址,不超過晶片容量即可(如果使用DMA方式,每次最大65536位元組)。
函數參數:
- 第1個參數用于存儲從QSPI Flash讀取的資料。
- 第2個參數是讀取位址,不可以超過晶片容量。
- 第3個參數是讀取的資料大小,讀取範圍不可以超過晶片容量。
79.5.2 函數QSPI_WriteBuffer
uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);
頁程式設計,頁大小256位元組,任意頁都可以寫入。注意使用前,務必保證相應頁已經做了擦除操作。
- 第1個參數是源資料緩沖區。
- 第2個參數是目标區域首位址,即頁首位址,比如0, 256, 512等。
- 第3個參數是資料個數,不能超過頁面大小,範圍1 – 256,機關位元組個數。
- 傳回值,傳回1表示成功,傳回0表示失敗。
79.5.3 函數QSPI_EraseSector
void QSPI_EraseSector(uint32_t _uiSectorAddr)
此函數主要用于扇區擦除,一個扇區大小是4KB。
- 第1個參數是扇區位址,比如擦除扇區0,此處填0x0000,擦除扇區1,此處填0x1000,擦除扇區2,此處填0x2000,以此類推。
79.5.4 函數QSPI_EraseChip
void QSPI_EraseChip(void)
此函數主要用于整個晶片擦除。
79.5.5 函數QSPI_MemoryMapped
void QSPI_MemoryMapped(void)
調用了此函數就可以像使用内部Flash一樣使用外部Flash。
W25QXX移植步驟如下:
- 第1步:複制bsp_qspi_w25q256.c, bsp_qspi_w25q256.h到自己的工程目錄,并添加到工程裡面。
- 第2步:根據使用的QSPI引腳,時鐘等,修改bsp_qspi_w25q256.c檔案開頭的宏定義。
/*
STM32-V7開發闆接線
PG6/QUADSPI_BK1_NCS AF10
PF10/QUADSPI_CLK AF9
PF8/QUADSPI_BK1_IO0 AF10
PF9/QUADSPI_BK1_IO1 AF10
PF7/QUADSPI_BK1_IO2 AF9
PF6/QUADSPI_BK1_IO3 AF9
W25Q256JV有512塊,每塊有16個扇區,每個扇區Sector有16頁,每頁有256位元組,共計32MB
*/
/* QSPI引腳和時鐘相關配置宏定義 */
#define QSPI_CLK_ENABLE() __HAL_RCC_QSPI_CLK_ENABLE()
#define QSPI_CLK_DISABLE() __HAL_RCC_QSPI_CLK_DISABLE()
#define QSPI_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE()
#define QSPI_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D0_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D3_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_MDMA_CLK_ENABLE() __HAL_RCC_MDMA_CLK_ENABLE()
#define QSPI_FORCE_RESET() __HAL_RCC_QSPI_FORCE_RESET()
#define QSPI_RELEASE_RESET() __HAL_RCC_QSPI_RELEASE_RESET()
#define QSPI_CS_PIN GPIO_PIN_6
#define QSPI_CS_GPIO_PORT GPIOG
#define QSPI_CLK_PIN GPIO_PIN_10
#define QSPI_CLK_GPIO_PORT GPIOF
#define QSPI_BK1_D0_PIN GPIO_PIN_8
#define QSPI_BK1_D0_GPIO_PORT GPIOF
#define QSPI_BK1_D1_PIN GPIO_PIN_9
#define QSPI_BK1_D1_GPIO_PORT GPIOF
#define QSPI_BK1_D2_PIN GPIO_PIN_7
#define QSPI_BK1_D2_GPIO_PORT GPIOF
#define QSPI_BK1_D3_PIN GPIO_PIN_6
#define QSPI_BK1_D3_GPIO_PORT GPIOF
- 根據使用的QSPI指令不同,容量不同等,修改bsp_qspi_w25q256.h頭檔案
/* W25Q256JV基本資訊 */
#define QSPI_FLASH_SIZE 25 /* Flash大小,2^25 = 32MB*/
#define QSPI_SECTOR_SIZE (4 * 1024) /* 扇區大小,4KB */
#define QSPI_PAGE_SIZE 256 /* 頁大小,256位元組 */
#define QSPI_END_ADDR (1 << QSPI_FLASH_SIZE) /* 末尾位址 */
#define QSPI_FLASH_SIZES 32*1024*1024 /* Flash大小,2^25 = 32MB*/
/* W25Q256JV相關指令 */
#define WRITE_ENABLE_CMD 0x06 /* 寫使能指令 */
#define READ_ID_CMD2 0x9F /* 讀取ID指令 */
#define READ_STATUS_REG_CMD 0x05 /* 讀取狀态指令 */
#define BULK_ERASE_CMD 0xC7 /* 整個晶片擦除指令 */
#define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD 0x21 /* 32bit位址扇區擦除指令, 4KB */
#define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD 0x34 /* 32bit位址的4線快速寫入指令 */
#define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC /* 32bit位址的4線快速讀取指令 */
- 第4步:如果使用MDMA方式的話,可以使用TCM RAM,此時不用考慮Cache問題。如果使用的是其它RAM空間,要考慮Cache問題。因為MDMA和CPU同時通路DMA緩沖造成的資料一緻性問題,将這塊空間關閉讀Cache和寫Cache,比如使用的AXI SRAM:
/* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO 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_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_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);
- 第5步:初始化QSPI。
/* 針對不同的應用程式,添加需要的底層驅動子產品初始化函數 */
bsp_InitQSPI_W25Q256(); /* 配置SPI總線 */
- 第6步:QSPI Flash驅動主要用到HAL庫的SPI驅動檔案,簡單省事些可以添加所有HAL庫C源檔案進來。
- 第7步:應用方法看本章節配套例子即可。
79.7 實驗例程設計架構
通過程式設計架構,讓大家先對配套例程有一個全面的認識,然後再了解細節,本次實驗例程的設計架構如下:
第1階段,上電啟動階段:
- 這部分在第14章進行了詳細說明。
第2階段,進入main函數:
- 第1部分,硬體初始化,主要是MPU,Cache,HAL庫,系統時鐘,滴答定時器和LED。
- 第2部分,應用程式設計部分,實作QSPI Flash的查詢和MDMA方式操作。
配套例子:
V7-029_QSPI讀寫例程,四線DMA方式,讀每秒48MB(V1.1)
V7-059_QSPI讀寫例程,查詢方式
實驗目的:
- 學習QSPI Flash的讀寫測試例程
實驗操作:
- 支援以下7個功能,使用者通過電腦端序列槽軟體發送數字1-6給開發闆即可
- printf("請選擇操作指令:\r\n");
- printf("【1 - 讀QSPI Flash, 位址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
- printf("【2 - 寫QSPI Flash, 位址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
- printf("【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
- printf("【4 - 讀整個串行Flash, 測試讀速度】\r\n");
- printf("【Z - 讀取前1K,位址自動減少】\r\n");
- printf("【X - 讀取後1K,位址自動增加】\r\n");
- printf("【Y - 擦除整個串行Flash,整片32MB擦除大概300秒左右】\r\n");
- printf("其他任意鍵 - 顯示指令提示\r\n");
上電後序列槽列印的資訊:
波特率 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_InitQSPI_W25Q256(); /* 配置SPI總線 */}
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();
#if 0
/* 配置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);
#else
/* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO 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_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_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);
#endif
/* 配置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);
/*使能 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個功能,使用者通過電腦端序列槽軟體發送指令給開發闆即可
- printf("【1 - 讀QSPI Flash, 位址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
- printf("【2 - 寫QSPI Flash, 位址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
- printf("【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
- printf("【4 - 讀整個串行Flash, 測試讀速度】\r\n");
- printf("【Z - 讀取前1K,位址自動減少】\r\n");
- printf("【X - 讀取後1K,位址自動增加】\r\n");
- printf("【Y - 擦除整個串行Flash,整片32MB擦除大概300秒左右】\r\n");
- printf("其他任意鍵 - 顯示指令提示\r\n");
/*
*********************************************************************************************************
* 函 數 名: DemoSpiFlash
* 功能說明: QSPI讀寫例程
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiFlash(void)
{
uint8_t cmd;
uint32_t uiReadPageNo = 0, id;
/* 檢測串行Flash OK */
id = QSPI_ReadID();
printf("檢測到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);
printf(" 容量 : 32M位元組, 扇區大小 : 4096位元組, 頁大小:256位元組\r\n");
sfDispMenu(); /* 列印指令提示 */
bsp_StartAutoTimer(0, 200); /* 啟動1個200ms的自動重裝的定時器,軟體定時器0 */
while(1)
{
bsp_Idle(); /* 這個函數在bsp.c檔案。使用者可以修改這個函數實作CPU休眠和喂狗 */
/* 判斷軟體定時器0是否逾時 */
if(bsp_CheckTimer(0))
{
/* 每隔200ms 進來一次 */
bsp_LedToggle(2);
}
if (comGetChar(COM1, &cmd)) /* 從序列槽讀入一個字元(非阻塞方式) */
{
switch (cmd)
{
case '1':
printf("\r\n【1 - 讀QSPI Flash, 位址:0x%X ,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
sfReadTest(); /* 讀串行Flash資料,并列印出來資料内容 */
break;
case '2':
printf("\r\n【2 - 寫QSPFlash, 位址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
sfWriteTest(); /* 寫串行Flash資料,并列印寫入速度 */
break;
case '3':
printf("\r\n【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
sfWriteAll(0x55);/* 擦除串行Flash資料,實際上就是寫入全0xFF */
break;
case '4':
printf("\r\n【4 - 讀整個QSPI Flash, %dM位元組】\r\n", QSPI_FLASH_SIZES/(1024*1024));
sfTestReadSpeed(); /* 讀整個串行Flash資料,測試速度 */
break;
case 'y':
case 'Y':
printf("\r\n【Y - 擦除整個QSPI Flash】\r\n");
printf("整個Flash擦除完畢大概需要300秒左右,請耐心等待");
sfErase(); /* 擦除串行Flash資料,實際上就是寫入全0xFF */
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 < QSPI_FLASH_SIZES / 1024 - 1)
{
uiReadPageNo++;
}
else
{
printf("已經是最後\r\n");
}
sfViewData(uiReadPageNo * 1024);
break;
default:
sfDispMenu(); /* 無效指令,重新列印指令提示 */
break;
}
}
}
}
/*
*********************************************************************************************************
* 函 數 名: 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_InitQSPI_W25Q256(); /* 配置SPI總線 */}
/*
*********************************************************************************************************
* 函 數 名: MPU_Config
* 功能說明: 配置MPU
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable();
#if 0
/* 配置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);
#else
/* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO 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_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_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);
#endif
/* 配置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);
/*使能 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();
}
/*
*********************************************************************************************************
* 函 數 名: bsp_RunPer10ms
* 功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求
* 不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。
* 形 參: 無
* 返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
bsp_KeyScan10ms();
}
/*
*********************************************************************************************************
* 函 數 名: DemoSpiFlash
* 功能說明: QSPI讀寫例程
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiFlash(void)
{
uint8_t cmd;
uint32_t uiReadPageNo = 0, id;
/* 檢測串行Flash OK */
id = QSPI_ReadID();
printf("檢測到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);
printf(" 容量 : 32M位元組, 扇區大小 : 4096位元組, 頁大小:256位元組\r\n");
sfDispMenu(); /* 列印指令提示 */
bsp_StartAutoTimer(0, 200); /* 啟動1個200ms的自動重裝的定時器,軟體定時器0 */
while(1)
{
bsp_Idle(); /* 這個函數在bsp.c檔案。使用者可以修改這個函數實作CPU休眠和喂狗 */
/* 判斷軟體定時器0是否逾時 */
if(bsp_CheckTimer(0))
{
/* 每隔200ms 進來一次 */
bsp_LedToggle(2);
}
if (comGetChar(COM1, &cmd)) /* 從序列槽讀入一個字元(非阻塞方式) */
{
switch (cmd)
{
case '1':
printf("\r\n【1 - 讀QSPI Flash, 位址:0x%X ,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
sfReadTest(); /* 讀串行Flash資料,并列印出來資料内容 */
break;
case '2':
printf("\r\n【2 - 寫QSPFlash, 位址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
sfWriteTest(); /* 寫串行Flash資料,并列印寫入速度 */
break;
case '3':
printf("\r\n【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
sfWriteAll(0x55);/* 擦除串行Flash資料,實際上就是寫入全0xFF */
break;
case '4':
printf("\r\n【4 - 讀整個QSPI Flash, %dM位元組】\r\n", QSPI_FLASH_SIZES/(1024*1024));
sfTestReadSpeed(); /* 讀整個串行Flash資料,測試速度 */
break;
case 'y':
case 'Y':
printf("\r\n【Y - 擦除整個QSPI Flash】\r\n");
printf("整個Flash擦除完畢大概需要300秒左右,請耐心等待");
sfErase(); /* 擦除串行Flash資料,實際上就是寫入全0xFF */
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 < QSPI_FLASH_SIZES / 1024 - 1)
{
uiReadPageNo++;
}
else
{
printf("已經是最後\r\n");
}
sfViewData(uiReadPageNo * 1024);
break;
default:
sfDispMenu(); /* 無效指令,重新列印指令提示 */
break;
}
}
}
}
79.10 總結
本章節就為大家講解這麼多,實際應用中根據需要選擇DMA和查詢方式。
微信公衆号:armfly_com
安富萊論壇:www.armbbs.cn
安富萊淘寶:https://armfly.taobao.com