23.3
實驗:讀寫外部Flash芯片
23.3.1
硬件設計
野火啟明6M5開發板的QSPI FLASH電路圖如圖所示:

野火啟明4M2開發板的QSPI FLASH電路圖如圖所示:

野火啟明2L1開發板的SPI FLASH電路圖如圖所示:

FLASH芯片連接到MCU的引腳如下表所示。

23.3.2
軟件設計
23.3.2.1
新建工程
因為本章節的QSPI Flash相關實驗例程需要用到板子上的串口功能,因此我們可以直接以前面的“19_UART_Receive_Send”工程為基礎進行修改。
對于e2studio開發環境:拷貝一份我們之前的e2s工程模板“19_UART_Receive_Send”,然后將工程文件夾重命名為“QSPI_Flash”,最后再將它導入到我們的e2studio工作空間中。
對于Keil開發環境:拷貝一份我們之前的Keil工程模板“19_UART_Receive_Send”,然后將工程文件夾重命名為“QSPI_Flash”,并進入該文件夾里面雙擊Keil工程文件,打開該工程。
注
對于野火啟明2L1開發板,連接外部Flash使用的是普通SPI接口,工程文件夾可重命名為“SPI_Flash”。
工程新建好之后,在工程根目錄的“src”文件夾下面新建“qspi_flash”或“spi_flash”文件夾,再進入改文件夾里面新建源文件和頭文件:“bsp_qspi_flash.c/.h”(啟明6M5/啟明4M2開發板)或“bsp_spi_flash.c/.h”(啟明2L1開發板)。
工程文件結構如下。
列表2:文件結構
左右滑動查看完整內容
QSPI_Flash(啟明6M5/啟明4M2開發板)或SPI_Flash(啟明2L1開發板) ├─ ...... └─src ├─ led │ ├─ bsp_led.c │ └─ bsp_led.h ├─ debug_uart │ ├─ bsp_debug_uart.c │ └─ bsp_debug_uart.h ├─ qspi_flash 或spi_flash(啟明2L1開發板) │ ├─ bsp_qspi_flash.c或bsp_spi_flash.c(啟明2L1開發板) │ └─ bsp_qspi_flash.h或bsp_spi_flash.h(啟明2L1開發板) └─ hal_entry.c
23.3.2.2.FSP配置
打開工程項目的 FSP 配置界面進行配置。 啟明6M5/啟明4M2開發板對比啟明2L1開發板,由于前者使用QSPI外設連接Flash芯片,后者使用SPI外設連接Flash芯片, QSPI與SPI是兩個不同的外設,因此它們的FSP配置方法有比較大的不同。
對于啟明6M5/啟明4M2開發板的 “QSPI_Flash” 工程:
打開工程項目的 FSP 配置界面之后,首先切到“Pins”頁面,配置QSPI引腳。
啟明6M5按照如下圖所示配置:

啟明4M2按照如下圖所示配置:

接著在 FSP 配置界面里面依次點擊“Stacks”->“New Stack”->“Storage”->“QSPI”來添加QSPI模塊。 如下圖所示。

按照如下圖所示對 QSPI 模塊屬性進行配置:

QSPI 模塊的屬性介紹如下:
QSPI 屬性介紹| QSPI屬性 | 描述 |
|---|---|
| SPI Protocol | SPI協議。 |
| Address Byte | 地址的長度(字節)。 |
| Read Mode | 讀取的模式。 |
| Page Size Bytes | 頁寫入長度(字節)。 |
| Command Definitions | 指令的定義。 |
| QSPKCLK Divisor | CLK時鐘設置 |
| Minimum QSSL Deselect Cycles | QSSL保持周期 |
| Pins | QSPI引腳配置 |
注解
當我們需要QSPI四根線進行四線快數讀取數據的時候,我們只需要在Read Mode里選擇 Fast Read Quad I/O 即可。
對于啟明2L1開發板的 “SPI_Flash” 工程:
打開工程項目的 FSP 配置界面之后,首先切到“Pins”頁面,配置SPI引腳。
啟明2L1按照如下圖所示配置:

接著在 FSP 配置界面里面依次點擊Stacks->New Stack->Connectivity->SPI (r_spi)來添加SPI模塊。 如下圖所示。

按照如下圖所示對 SPI 模塊屬性進行配置:

SPI 模塊的屬性介紹如下:
SPI 屬性介紹| SPI屬性 | 描述 |
|---|---|
| Name | 模塊實例名。設置為g_spi0_flash |
| Channel | 通道。這里選擇spi0 |
| Receive Interrupt Priority | 接收中斷優先級 |
| Transmit Buffer Empty Interrupt Priority | 發送緩存區空中斷優先級 |
| Transfer Complete Interrupt Priority | 發送完成中斷優先級 |
| Error Interrupt Priority | 錯誤中斷優先級 |
| Operating Mode | 操作模式。可選SPI主機或從機 |
| Clock Phase | SPI時鐘相位 |
| Clock Polarity | SPI時鐘極性 |
| Mode Fault Error | 模式錯誤檢測。檢測主從模式沖突 |
| Bit Order | 位時序。MSB或LSB |
| Callback | 中斷回調函數。設置為spi_flash_callback |
| SPI Mode | SPI 模式。設置為SPI Operation |
| Full or Transmit Only Mode | 全雙工或僅發送模式選擇 |
| Slave Select Polarity | 從機選擇引腳極性。一般是低電平有效 |
| Select SSL(Slave Select) | 從機選擇信號 |
| MOSI Idle State | 總線空閑時 MOSI 電平 |
| Parity Mode | 極性模式 |
| Byte Swapping | 字節交換模式 |
| Bitrate | 比特率 |
| Clock Delay | 時鐘延遲 |
| SSL Negation Delay | SSL失效延遲 |
| Next Access Delay | 下一次訪問延遲 |
配置完成之后可以按下快捷鍵“Ctrl + S”保存, 最后點右上角的“Generate Project Content”按鈕,讓軟件自動生成配置代碼即可。
接下來就可以為外部串行Flash編寫操作代碼了。 使用 QSPI 和使用 SPI 操作串行Flash其實是類似的,只是兩者的通信接口不同而已。 下面以啟明6M5開發板為例,對串行Flash芯片進行操作。啟明4M2和啟明2L1的代碼讀者可直接參考相應配套例程。
23.3.2.3.QSPI直接讀寫FLASH函數
當使用QSPI接口時,通過 R_QSPI_DirectWrite 和 R_QSPI_DirectRead 這兩個函數,可以直接讀寫QSPI FLASH, 通過這種方式,用戶需要寫入FLASH芯片的控制指令進行相應操作。
R_QSPI_DirectWrite 的函數原型如下:
fsp_err_t R_QSPI_DirectWrite (spi_flash_ctrl_t * p_ctrl,uint8_t const * const p_src,uint32_t const bytes,bool const read_after_write)
發送一個數組的數據,p_src需要發送的數組,bytes字節的長度,read_after_write是否發送數據的截止信號(意思是將QSSL拉高代表數據的截止),一般我們需要和R_QSPI_DirectRead進行組合發送數據。 在這個函數之后我們需要增加一定時間的延時,或者是通過中斷來進行判斷寫入數據是否成功。
R_QSPI_DirectRead 的函數原型如下:
fsp_err_t R_QSPI_DirectRead (spi_flash_ctrl_t * p_ctrl, uint8_t * const p_dest, uint32_t const bytes)
接收一個數組的數據,p_dest需要接收到的數組,bytes需要接收數組的長度(字節)。在執行讀取的函數命令之后我們需要增加一定時間的延時,或者是通過中斷來進行判斷讀取數據是否成功。
在此之后,我們將使用R_QSPI_DirectWrite和R_QSPI_DirectRead的組合,來實現我們想要的一些功能。
23.3.2.4.讀取FLASH芯片ID
根據“JEDEC”指令的時序,我們把讀取FLASH ID的過程編寫成一個函數,見下
代碼清單 讀取FLASH芯片ID
/** * @brief 讀取FLASH ID * @param 無 * @retval FLASH ID */ uint32_t QSPI_Flash_ReadID(void) { unsigned char data[6] = {}; uint32_t back; data[0] = JedecDeviceID; R_QSPI_DirectWrite(&g_qspi0_flash_ctrl, &data[0], 1, true); //false: close the spi true: go go go R_QSPI_DirectRead(&g_qspi0_flash_ctrl, &data[0], 3); /*把數據組合起來,作為函數的返回值*/ back = (data[0] << 16) | (data[1] << 8) | (data[2]); return back; }
這段代碼利用FSP里的R_QSPI_DirectWrite函數發送JedecDeviceID指令,然后通過R_QSPI_DirectRead函數讀取三個字節的函數,最后把讀取到的這3個數據合并到一個變量(back)中,然后作為函數返回值,把該返回值與我們預先定義的ID進行對比,即可知道FLASH芯片是否正常。
23.3.2.5.FLASH寫使能
在向FLASH芯片存儲矩陣寫入數據前,首先要使能寫操作,通過“Write Enable”命令即可寫使能,見下。
代碼清單 寫使能命令
/**
* @brief 向FLASH發送 寫使能 命令
* @param none
* @retval none
*/
void QSPI_Flash_WriteEnable(void)
{
unsigned char data[6] = {};
data[0] = WriteEnable;
R_QSPI_DirectWrite(&g_qspi0_flash_ctrl, &data[0], 1, false);
}
23.3.2.6.讀取當前FLASH狀態
與EEPROM一樣,由于FLASH芯片向內部存儲矩陣寫入數據需要消耗一定的時間,并不是在總線通訊結束的一瞬間完成的, 所以在寫操作后需要確認FLASH芯片“空閑”時才能進行再次寫入。為了表示自己的工作狀態, FLASH芯片定義了一個狀態寄存器,如下圖所示。

我們只關注這個狀態寄存器的第0位“BUSY”,當這個位為“1”時,表明FLASH芯片處于忙碌狀態,它可能正在對內部的存儲矩陣進行“擦除”或“數據寫入”的操作。
利用指令表中的“Read Status Register”指令可以獲取FLASH芯片狀態寄存器的內容,其時序見下圖。

只要向FLASH芯片發送了讀狀態寄存器的指令,FLASH芯片就會持續向主機返回最新的狀態寄存器內容, 直到收到SPI通訊的停止信號。據此我們編寫了具有等待FLASH芯片寫入結束功能的函數,見下。
代碼清單 通過讀狀態寄存器等待FLASH芯片空閑
/** * @brief 等待WIP(BUSY)標志被置0,即等待FLASH內部數據寫入完畢 * @param 無 */ fsp_err_t QSPI_Flash_WaitForWriteEnd(void) { spi_flash_status_t status = {.write_in_progress = true}; int32_t time_out = (INT32_MAX); fsp_err_t err = FSP_SUCCESS; do { /* Get status from QSPI flash device */ err = R_QSPI_StatusGet(&g_qspi0_flash_ctrl, &status); if (FSP_SUCCESS != err) { printf("R_QSPI_StatusGet Failed\r\n"); return err; } /* Decrement time out to avoid infinite loop in case of consistent failure */ --time_out; if (RESET_VALUE >= time_out) { printf("\r\n ** Timeout : No result from QSPI flash status register ** \r\n"); return FSP_ERR_TIMEOUT; } } while (false != status.write_in_progress); return err; }
這段代碼發送R_QSPI_StatusGet函數獲取當前的芯片是否在寫入狀態,并在while循環里持續獲取寄存器的內容并檢驗它的標志位,一直等待到該標志表示寫入結束時才退出本函數,以便繼續后面與FLASH芯片的數據通訊。
23.3.2.7.FLASH扇區擦除
由于FLASH存儲器的特性決定了它只能把原來為“1”的數據位改寫成“0”,而原來為“0”的數據位不能直接改寫為“1”。所以這里涉及到數據“擦除”的概念,在寫入前,必須要對目標存儲矩陣進行擦除操作,把矩陣中的數據位擦除為“1”,在數據寫入的時候,如果要存儲數據“1”,那就不修改存儲矩陣 ,在要存儲數據“0”時,才更改該位。
通常,對存儲矩陣擦除的基本操作單位都是多個字節進行,如本例子中的FLASH芯片支持“扇區擦除”、“塊擦除”以及“整片擦除”,見下表。
本實驗FLASH芯片的擦除單位
| 擦除單位 | 大小 | 指令 |
| 扇區擦除Sector Erase | 4KB | 20h |
| 塊擦除Block Erase | 64KB | D8h |
| 整片擦除Chip Erase | 整個芯片完全擦除 | 60h |
FLASH芯片的最小擦除單位為扇區(Sector),而一個塊(Block)包含16個扇區,其內部存儲矩陣分布見下圖。

使用扇區擦除指令“Sector Erase”可控制FLASH芯片開始擦寫,其指令時序見下圖。

扇區擦除指令的第一個字節為指令編碼,緊接著發送的3個字節用于表示要擦除的存儲矩陣地址。 要注意的是在扇區擦除指令前,還需要先發送“寫使能”指令,發送扇區擦除指令后, 通過讀取寄存器狀態等待扇區擦除操作完畢,代碼實現見下。
代碼清單 擦除扇區
/**
* @brief 擦除FLASH扇區
* @param SectorAddr:要擦除的扇區地址
* @retval 無
*/
void QSPI_Flash_SectorErase(uint32_t adress)
{
unsigned char data[6] = {};
data[0] = 0x06; //write_enable_command
data[1] = 0x20; //erase_command
data[2] = (uint8_t)(adress >> 16);
data[3] = (uint8_t)(adress >> 8);
data[4] = (uint8_t)(adress);
R_QSPI->SFMCMD = 1U;
R_QSPI->SFMCOM = data[0];
R_QSPI_DirectWrite(&g_qspi0_flash_ctrl, &data[1], 4, false);
QSPI_Flash_WaitForWriteEnd();
}
這段代碼使用瑞薩FSP調用R_QSPI_DirectWrite()發送四個字節的數據, 先發送寫使能0x06,之后通過R_QSPI_DirectWrite()發送扇區的刪除命令0x02,以及三個字節的地址。調用扇區擦除指令時注意輸入的地址要對齊到4KB。
23.3.2.8.FLASH的頁寫入
目標扇區被擦除完畢后,就可以向它寫入數據了。與EEPROM類似,FLASH芯片也有頁寫入命令, 使用頁寫入命令最多可以一次向FLASH傳輸256個字節的數據,我們把這個單位為頁大小。 FLASH頁寫入的時序見下圖。

從時序圖可知,第1個字節為“頁寫入指令”編碼,2-4字節為要寫入的“地址Address”,接著的是要寫入的內容,最多可以發送256字節數據,這些數據將會從“地址Address”開始,按順序寫入到FLASH的存儲矩陣。若發送的數據超出256個,則會覆蓋前面發送的數據。
與擦除指令不一樣,頁寫入指令的地址并不要求按256字節對齊,只要確認目標存儲單元是擦除狀態即可(即被擦除后沒有被寫入過)。所以,若對“地址x”執行頁寫入指令后,發送了200個字節數據后終止通訊,下一次再執行頁寫入指令,從“地址(x+200)”開始寫入200個字節也是沒有問題的(小于256均可)。 只是在實際應用中由于基本擦除單元是4KB,一般都以扇區為單位進行讀寫,想深入了解,可學習我們的“FLASH文件系統”相關的例子。
把頁寫入時序封裝成函數,其實現見下。
代碼清單 FLASH的頁寫入
/**
* @brief 對FLASH按頁寫入數據,調用本函數寫入數據前需要先擦除扇區
* @param pBuffer,要寫入數據的指針
* @param WriteAddr,寫入地址
* @param NumByteToWrite,寫入數據長度,必須小于等于頁大小
* @retval 無
*/
void QSPI_Flash_PageWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();
}
static void qspi_d0_byte_write_standard(uint8_t byte)
{
R_QSPI->SFMCOM = byte;
}
/**
* @brief 讀取flash數據
* @param p_ctrl
* @param p_src 需要傳回的數據
* @param p_dest 數據地址
* @param byte_count 數據長度
*/
fsp_err_t R_QSPI_Read(spi_flash_ctrl_t *p_ctrl,
uint8_t *p_src,
uint8_t *const p_dest,
uint32_t byte_count)
{
qspi_instance_ctrl_t *p_instance_ctrl = (qspi_instance_ctrl_t *) p_ctrl;
uint32_t chip_address = (uint32_t) p_dest - (uint32_t) QSPI_DEVICE_START_ADDRESS + R_QSPI->SFMCNT1;
bool restore_spi_mode = false;
void (* write_command)(uint8_t byte) = qspi_d0_byte_write_standard;
void (* write_address)(uint8_t byte) = qspi_d0_byte_write_standard;
#if QSPI_CFG_SUPPORT_EXTENDED_SPI_MULTI_LINE_PROGRAM
/* If the peripheral is in extended SPI mode, and the configuration provided in the BSP allows for programming on
* multiple data lines, and a unique command is provided for the required mode, update the SPI protocol to send
* data on multiple lines. */
if ((SPI_FLASH_DATA_LINES_1 != p_instance_ctrl->data_lines) &&
(SPI_FLASH_PROTOCOL_EXTENDED_SPI == R_QSPI->SFMSPC_b.SFMSPI))
{
R_QSPI->SFMSPC_b.SFMSPI = p_instance_ctrl->data_lines;
restore_spi_mode = true;
/* Write command in extended SPI mode on one line. */
write_command = gp_qspi_prv_byte_write[p_instance_ctrl->data_lines];
if (SPI_FLASH_DATA_LINES_1 == p_instance_ctrl->p_cfg->page_program_address_lines)
{
/* Write address in extended SPI mode on one line. */
write_address = gp_qspi_prv_byte_write[p_instance_ctrl->data_lines];
}
}
#endif
/* Enter Direct Communication mode */
R_QSPI->SFMCMD = 1;
/* Send command to enable writing */
write_command(0x03);
/* Write the address. */
if ((p_instance_ctrl->p_cfg->address_bytes & R_QSPI_SFMSAC_SFMAS_Msk) == SPI_FLASH_ADDRESS_BYTES_4)
{
/* Send the most significant byte of the address */
write_address((uint8_t)(chip_address >> 24));
}
/* Send the remaining bytes of the address */
write_address((uint8_t)(chip_address >> 16));
write_address((uint8_t)(chip_address >> 8));
write_address((uint8_t)(chip_address));
/* Write the data. */
uint32_t index = 0;
while (index < byte_count)
{
/* Read the device memory into the passed in buffer */
*(p_src + index) = (uint8_t) R_QSPI->SFMCOM;
index++;
}
/* Close the SPI bus cycle. Reference section 39.10.3 "Generating the SPI Bus Cycle during Direct Communication"
* in the RA6M3 manual R01UH0886EJ0100. */
R_QSPI->SFMCMD = 1;
/* Return to ROM access mode */
R_QSPI->SFMCMD = 0;
return FSP_SUCCESS;
}
這段代碼使用瑞薩FSP調用R_QSPI_Write()函數進行進行頁寫入, 先發送“寫使能”命令,接著才開始頁寫入時序,然后發送指令編碼、地址, 再把要寫入的數據一個接一個地發送出去,發送完后結束通訊,通過get_flash_status()函數來 檢查FLASH狀態寄存器, 等待FLASH內部寫入結束。
23.3.2.9.不定量數據寫入
應用的時候我們常常要寫入不定量的數據,直接調用“頁寫入”函數并不是特別方便,所以我們在它的基礎上編寫了“不定量數據寫入”的函數, 基實現見下。
代碼清單 不定量數據寫入
/**
* @brief 對FLASH寫入數據,調用本函數寫入數據前需要先擦除扇區
* @param pBuffer,要寫入數據的指針
* @param WriteAddr,寫入地址
* @param NumByteToWrite,寫入數據長度
* @retval 無
*/
void QSPI_Flash_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
/*mod運算求余,若writeAddr是SPI_FLASH_PageSize整數倍,運算結果Addr值為0*/
Addr = WriteAddr % SPI_FLASH_PageSize;
/*差count個數據值,剛好可以對齊到頁地址*/
count = SPI_FLASH_PageSize - Addr;
/*計算出要寫多少整數頁*/
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
/*mod運算求余,計算出剩余不滿一頁的字節數*/
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* Addr=0,則WriteAddr 剛好按頁對齊 aligned */
if (Addr == 0)
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
/*先把整數頁都寫了*/
while (NumOfPage--)
{
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, SPI_FLASH_PageSize);
QSPI_Flash_WaitForWriteEnd();
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不滿一頁的數據,把它寫完*/
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, NumOfSingle);
QSPI_Flash_WaitForWriteEnd();
}
}
/* 若地址與 SPI_FLASH_PageSize 不對齊 */
else
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
/*當前頁剩余的count個位置比NumOfSingle小,一頁寫不完*/
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
/*先寫滿當前頁*/
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, count);
QSPI_Flash_WaitForWriteEnd();
WriteAddr += count;
pBuffer += count;
/*再寫剩余的數據*/
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, temp);
QSPI_Flash_WaitForWriteEnd();
}
else /*當前頁剩余的count個位置能寫完NumOfSingle個數據*/
{
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();
}
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
/*地址不對齊多出的count分開處理,不加入這個運算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* 先寫完count個數據,為的是讓下一次要寫的地址對齊 */
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, count);
QSPI_Flash_WaitForWriteEnd();
/* 接下來就重復地址對齊的情況 */
WriteAddr += count;
pBuffer += count;
/*把整數頁都寫了*/
while (NumOfPage--)
{
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, SPI_FLASH_PageSize);
QSPI_Flash_WaitForWriteEnd();
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不滿一頁的數據,把它寫完*/
if (NumOfSingle != 0)
{
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, NumOfSingle);
QSPI_Flash_WaitForWriteEnd();
}
}
}
}
這段代碼與EEPROM章節中的“快速寫入多字節”函數原理是一樣的,運算過程在此不再贅述。區別是頁的大小以及實際數據寫入的時候,使用的是針對FLASH芯片的頁寫入函數,且在實際調用這個“不定量數據寫入”函數時,還要注意確保目標扇區處于擦除狀態。
23.3.2.10.從FLASH讀取數據
相對于寫入,FLASH芯片的數據讀取要簡單得多,使用讀取指令“Read Data”即可,其指令時序見下圖。

發送了指令編碼及要讀的起始地址后,FLASH芯片就會按地址遞增的方式返回存儲矩陣的內容,讀取的數據量沒有限制, 只要沒有停止通訊,FLASH芯片就會一直返回數據。代碼實現見下。
代碼清單 從FLASH讀取數據
/**
* @brief 讀取FLASH數據,減少ctrl這個標志
* @param pBuffer,存儲讀出數據的指針
* @param ReadAddr,讀取地址
* @param NumByteToRead,讀取數據長度
* @retval 無
*/
void QSPI_Flash_BufferRead(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
R_QSPI_Read(&g_qspi0_flash_ctrl, pBuffer, ReadAddr, NumByteToRead);
}
/**
* @brief 讀取flash數據
* @param p_ctrl
* @param p_src 需要傳回的數據
* @param p_dest 數據地址
* @param byte_count 數據長度
*/
fsp_err_t R_QSPI_Read(spi_flash_ctrl_t *p_ctrl,
uint8_t *p_src,
uint8_t *const p_dest,
uint32_t byte_count)
{
qspi_instance_ctrl_t *p_instance_ctrl = (qspi_instance_ctrl_t *) p_ctrl;
uint32_t chip_address = (uint32_t) p_dest - (uint32_t) QSPI_DEVICE_START_ADDRESS + R_QSPI->SFMCNT1;
bool restore_spi_mode = false;
void (* write_command)(uint8_t byte) = qspi_d0_byte_write_standard;
void (* write_address)(uint8_t byte) = qspi_d0_byte_write_standard;
#if QSPI_CFG_SUPPORT_EXTENDED_SPI_MULTI_LINE_PROGRAM
/* If the peripheral is in extended SPI mode, and the configuration provided in the BSP allows for programming on
* multiple data lines, and a unique command is provided for the required mode, update the SPI protocol to send
* data on multiple lines. */
if ((SPI_FLASH_DATA_LINES_1 != p_instance_ctrl->data_lines) &&
(SPI_FLASH_PROTOCOL_EXTENDED_SPI == R_QSPI->SFMSPC_b.SFMSPI))
{
R_QSPI->SFMSPC_b.SFMSPI = p_instance_ctrl->data_lines;
restore_spi_mode = true;
/* Write command in extended SPI mode on one line. */
write_command = gp_qspi_prv_byte_write[p_instance_ctrl->data_lines];
if (SPI_FLASH_DATA_LINES_1 == p_instance_ctrl->p_cfg->page_program_address_lines)
{
/* Write address in extended SPI mode on one line. */
write_address = gp_qspi_prv_byte_write[p_instance_ctrl->data_lines];
}
}
#endif
/* Enter Direct Communication mode */
R_QSPI->SFMCMD = 1;
/* Send command to enable writing */
write_command(0x03);
/* Write the address. */
if ((p_instance_ctrl->p_cfg->address_bytes & R_QSPI_SFMSAC_SFMAS_Msk) == SPI_FLASH_ADDRESS_BYTES_4)
{
/* Send the most significant byte of the address */
write_address((uint8_t)(chip_address >> 24));
}
/* Send the remaining bytes of the address */
write_address((uint8_t)(chip_address >> 16));
write_address((uint8_t)(chip_address >> 8));
write_address((uint8_t)(chip_address));
/* Write the data. */
uint32_t index = 0;
while (index < byte_count)
{
/* Read the device memory into the passed in buffer */
*(p_src + index) = (uint8_t) R_QSPI->SFMCOM;
index++;
}
/* Close the SPI bus cycle. Reference section 39.10.3 "Generating the SPI Bus Cycle during Direct Communication"
* in the RA6M3 manual R01UH0886EJ0100. */
R_QSPI->SFMCMD = 1;
/* Return to ROM access mode */
R_QSPI->SFMCMD = 0;
return FSP_SUCCESS;
}
由于讀取的數據量沒有限制,所以發送讀命令后一直接收NumByteToRead個數據到結束即可。
23.3.2.11.hal_entry入口函數
最后我們來編寫 hal_entry 入口函數,進行FLASH芯片讀寫校驗,代碼見下。
代碼清單 hal_entry 入口函數
/* 用戶頭文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "qspi_flash/bsp_qspi_flash.h"
#define FLASH_WriteAddress 0x00000
#define FLASH_ReadAddress FLASH_WriteAddress
#define FLASH_SectorToErase FLASH_WriteAddress
/* 發送緩沖區初始化 */
uint8_t Tx_Buffer[] = "感謝您選用野火啟明瑞薩RA開發板";
uint8_t Rx_Buffer[sizeof(Tx_Buffer)];
/*
* 函數名:Buffercmp
* 描述 :比較兩個緩沖區中的數據是否相等
* 輸入 :pBuffer1 src緩沖區指針
* pBuffer2 dst緩沖區指針
* BufferLength 緩沖區長度
* 輸出 :無
* 返回 :0 pBuffer1 等于 pBuffer2
* 1 pBuffer1 不等于 pBuffer2
*/
int Buffercmp(uint8_t *pBuffer1, uint8_t *pBuffer2, uint16_t BufferLength)
{
while (BufferLength--)
{
if (*pBuffer1 != *pBuffer2)
{
return 1;
}
pBuffer1++;
pBuffer2++;
}
return 0;
}
void hal_entry(void)
{
/* TODO: add your own code here */
uint32_t FlashID = 0;
uint32_t FlashDeviceID = 0;
LED_Init(); // LED 初始化
Debug_UART4_Init(); // SCI4 UART 調試串口初始化
QSPI_Flash_Init(); // 串行FLASH初始化
printf("這是一個串行FLASH的讀寫例程\r\n");
printf("打開串口助手查看打印的信息\r\n\r\n");
/* 獲取 SPI g_qspi0_flash ID */
FlashID = QSPI_Flash_ReadID();
FlashDeviceID = QSPI_Flash_ReadDeviceID();
if ((FlashID == FLASH_ID_W25Q32JV) || (FlashID == FLASH_ID_AT25SF321B))
{
if(FlashID == FLASH_ID_W25Q32JV)
{
printf("檢測到串行FLASH:W25Q32 !\r\n");
}
else
{
printf("檢測到串行FLASH:AT25SF32 !\r\n");
}
printf("FlashID is 0x%X, Manufacturer Device ID is 0x%X.\r\n", FlashID, FlashDeviceID);
/* 擦除將要寫入的 SPI FLASH 扇區,FLASH寫入前要先擦除 */
// 這里擦除4K,即一個扇區,擦除的最小單位是扇區
QSPI_Flash_SectorErase(FLASH_SectorToErase);
/* 將發送緩沖區的數據寫到flash中 */
// 這里寫一頁,一頁的大小為256個字節
QSPI_Flash_BufferWrite(Tx_Buffer, FLASH_WriteAddress, sizeof(Tx_Buffer));
printf("寫入的數據為:%s \r\n", Tx_Buffer);
/* 將剛剛寫入的數據讀出來放到接收緩沖區中 */
QSPI_Flash_BufferRead(Rx_Buffer, FLASH_ReadAddress, sizeof(Tx_Buffer));
printf("讀出的數據為:%s \r\n", Rx_Buffer);
if (Buffercmp(Tx_Buffer, Rx_Buffer, sizeof(Tx_Buffer)) == 0)
{
printf("\r\n32Mbit串行Flash測試成功!\r\n");
LED3_ON;
}
else
{
printf("\r\n32Mbit串行Flash測試失敗!\r\n");
LED1_ON;
}
printf("\r\n測試存儲浮點數和整數示例\r\n");
/* 存儲小數和整數的數組,各7個 */
long double double_buffer[7] = {0};
int int_buffer[7] = {0};
/*生成要寫入的數據*/
for (uint8_t k = 0; k < 7; k++)
{
double_buffer[k] = k + 0.1;
int_buffer[k] = k * 500 + 1 ;
}
printf("向芯片寫入數據:");
/*打印到串口*/
printf("\r\n小數 tx = ");
for (uint8_t k = 0; k < 7; k++)
{
printf("%LF, ", double_buffer[k]);
}
printf("\r\n整數 tx = ");
for (uint8_t k = 0; k < 7; k++)
{
printf("%d, ", int_buffer[k]);
}
/* 前面已擦除整個扇區和寫入第0頁,現繼續寫入第1頁和第2頁 */
/*寫入小數數據到第一頁*/
QSPI_Flash_BufferWrite((void *)double_buffer, SPI_FLASH_PageSize * 1, sizeof(double_buffer));
/*寫入整數數據到第二頁*/
QSPI_Flash_BufferWrite((void *)int_buffer, SPI_FLASH_PageSize * 2, sizeof(int_buffer));
/*讀取小數數據*/
QSPI_Flash_BufferRead((void *)double_buffer, SPI_FLASH_PageSize * 1, sizeof(double_buffer));
/*讀取整數數據*/
QSPI_Flash_BufferRead((void *)int_buffer, SPI_FLASH_PageSize * 2, sizeof(int_buffer));
printf("\r\n\r\n從芯片讀到數據:");
printf("\r\n小數 rx = ");
for (uint8_t k = 0; k < 7; k++)
{
printf("%LF, ", double_buffer[k]);
}
printf("\r\n整數 rx = ");
for (uint8_t k = 0; k < 7; k++)
{
printf("%d, ", int_buffer[k]);
}
}
else
{
printf("\tFLASH_ID 錯誤:0x%X", FlashID);
LED1_ON;
}
while(1);
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
23.3.3.下載驗證
用USB線連接開發板“USB TO UART”接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。在串口調試助手可看到FLASH測試的調試信息。

-
FlaSh
+關注
關注
10文章
1747瀏覽量
155503 -
開發板
+關注
關注
26文章
6289瀏覽量
118023 -
開發環境
+關注
關注
1文章
270瀏覽量
17637 -
QSPI
+關注
關注
0文章
55瀏覽量
13355
原文標題:控制FLASH的指令——瑞薩RA系列FSP庫開發實戰指南(79)
文章出處:【微信號:瑞薩嵌入式小百科,微信公眾號:瑞薩嵌入式小百科】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
瑞薩RA系列MCU FSP庫開發實戰指南(09)存儲器映射
瑞薩RA系列FSP庫開發實戰指南之QSPI通訊協議簡介
瑞薩RA系列FSP庫開發實戰指南之QSPI控制FLASH的指令
瑞薩e2studio(1)----瑞薩芯片之搭建FSP環境
【瑞薩RA6E2地奇星開發板試用】開發板介紹及環境搭建
【瑞薩RA4系列開發板體驗】體驗過程
【有獎直播預報名】瑞薩電子RA系列產品開發工具之FSP4.0.0新特性介紹
【視頻教程】瑞薩RA單片機FSP開發(3)FSP架構-解釋Blinky架構[上]
瑞薩電子RA系列微控制器的可擴展性強的配置軟件包 (FSP)安裝下載與使用指南
RA MCU眾測寶典 | 在瑞薩CPKCOR-RA8D1B核心板上實現QSPI讀取外部Flash
瑞薩RA系列FSP庫開發實戰指南之QSPI讀寫外部Flash芯片實驗
評論