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

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

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

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

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

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

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

按照如下圖所示對 QSPI 模塊屬性進(jìn)行配置:

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

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

按照如下圖所示對 SPI 模塊屬性進(jìn)行配置:

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

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

只要向FLASH芯片發(fā)送了讀狀態(tài)寄存器的指令,F(xiàn)LASH芯片就會持續(xù)向主機返回最新的狀態(tài)寄存器內(nèi)容, 直到收到SPI通訊的停止信號。據(jù)此我們編寫了具有等待FLASH芯片寫入結(jié)束功能的函數(shù),見下。
代碼清單 通過讀狀態(tài)寄存器等待FLASH芯片空閑
/** * @brief 等待WIP(BUSY)標(biāo)志被置0,即等待FLASH內(nèi)部數(shù)據(jù)寫入完畢 * @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; }
這段代碼發(fā)送R_QSPI_StatusGet函數(shù)獲取當(dāng)前的芯片是否在寫入狀態(tài),并在while循環(huán)里持續(xù)獲取寄存器的內(nèi)容并檢驗它的標(biāo)志位,一直等待到該標(biāo)志表示寫入結(jié)束時才退出本函數(shù),以便繼續(xù)后面與FLASH芯片的數(shù)據(jù)通訊。
23.3.2.7.FLASH扇區(qū)擦除
由于FLASH存儲器的特性決定了它只能把原來為“1”的數(shù)據(jù)位改寫成“0”,而原來為“0”的數(shù)據(jù)位不能直接改寫為“1”。所以這里涉及到數(shù)據(jù)“擦除”的概念,在寫入前,必須要對目標(biāo)存儲矩陣進(jìn)行擦除操作,把矩陣中的數(shù)據(jù)位擦除為“1”,在數(shù)據(jù)寫入的時候,如果要存儲數(shù)據(jù)“1”,那就不修改存儲矩陣 ,在要存儲數(shù)據(jù)“0”時,才更改該位。
通常,對存儲矩陣擦除的基本操作單位都是多個字節(jié)進(jìn)行,如本例子中的FLASH芯片支持“扇區(qū)擦除”、“塊擦除”以及“整片擦除”,見下表。
本實驗FLASH芯片的擦除單位
| 擦除單位 | 大小 | 指令 |
| 扇區(qū)擦除Sector Erase | 4KB | 20h |
| 塊擦除Block Erase | 64KB | D8h |
| 整片擦除Chip Erase | 整個芯片完全擦除 | 60h |
FLASH芯片的最小擦除單位為扇區(qū)(Sector),而一個塊(Block)包含16個扇區(qū),其內(nèi)部存儲矩陣分布見下圖。

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

扇區(qū)擦除指令的第一個字節(jié)為指令編碼,緊接著發(fā)送的3個字節(jié)用于表示要擦除的存儲矩陣地址。 要注意的是在扇區(qū)擦除指令前,還需要先發(fā)送“寫使能”指令,發(fā)送扇區(qū)擦除指令后, 通過讀取寄存器狀態(tài)等待扇區(qū)擦除操作完畢,代碼實現(xiàn)見下。
代碼清單 擦除扇區(qū)
/**
* @brief 擦除FLASH扇區(qū)
* @param SectorAddr:要擦除的扇區(qū)地址
* @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調(diào)用R_QSPI_DirectWrite()發(fā)送四個字節(jié)的數(shù)據(jù), 先發(fā)送寫使能0x06,之后通過R_QSPI_DirectWrite()發(fā)送扇區(qū)的刪除命令0x02,以及三個字節(jié)的地址。調(diào)用扇區(qū)擦除指令時注意輸入的地址要對齊到4KB。
23.3.2.8.FLASH的頁寫入
目標(biāo)扇區(qū)被擦除完畢后,就可以向它寫入數(shù)據(jù)了。與EEPROM類似,F(xiàn)LASH芯片也有頁寫入命令, 使用頁寫入命令最多可以一次向FLASH傳輸256個字節(jié)的數(shù)據(jù),我們把這個單位為頁大小。 FLASH頁寫入的時序見下圖。

從時序圖可知,第1個字節(jié)為“頁寫入指令”編碼,2-4字節(jié)為要寫入的“地址Address”,接著的是要寫入的內(nèi)容,最多可以發(fā)送256字節(jié)數(shù)據(jù),這些數(shù)據(jù)將會從“地址Address”開始,按順序?qū)懭氲紽LASH的存儲矩陣。若發(fā)送的數(shù)據(jù)超出256個,則會覆蓋前面發(fā)送的數(shù)據(jù)。
與擦除指令不一樣,頁寫入指令的地址并不要求按256字節(jié)對齊,只要確認(rèn)目標(biāo)存儲單元是擦除狀態(tài)即可(即被擦除后沒有被寫入過)。所以,若對“地址x”執(zhí)行頁寫入指令后,發(fā)送了200個字節(jié)數(shù)據(jù)后終止通訊,下一次再執(zhí)行頁寫入指令,從“地址(x+200)”開始寫入200個字節(jié)也是沒有問題的(小于256均可)。 只是在實際應(yīng)用中由于基本擦除單元是4KB,一般都以扇區(qū)為單位進(jìn)行讀寫,想深入了解,可學(xué)習(xí)我們的“FLASH文件系統(tǒng)”相關(guān)的例子。
把頁寫入時序封裝成函數(shù),其實現(xiàn)見下。
代碼清單 FLASH的頁寫入
/**
* @brief 對FLASH按頁寫入數(shù)據(jù),調(diào)用本函數(shù)寫入數(shù)據(jù)前需要先擦除扇區(qū)
* @param pBuffer,要寫入數(shù)據(jù)的指針
* @param WriteAddr,寫入地址
* @param NumByteToWrite,寫入數(shù)據(jù)長度,必須小于等于頁大小
* @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數(shù)據(jù)
* @param p_ctrl
* @param p_src 需要傳回的數(shù)據(jù)
* @param p_dest 數(shù)據(jù)地址
* @param byte_count 數(shù)據(jù)長度
*/
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調(diào)用R_QSPI_Write()函數(shù)進(jìn)行進(jìn)行頁寫入, 先發(fā)送“寫使能”命令,接著才開始頁寫入時序,然后發(fā)送指令編碼、地址, 再把要寫入的數(shù)據(jù)一個接一個地發(fā)送出去,發(fā)送完后結(jié)束通訊,通過get_flash_status()函數(shù)來 檢查FLASH狀態(tài)寄存器, 等待FLASH內(nèi)部寫入結(jié)束。
23.3.2.9.不定量數(shù)據(jù)寫入
應(yīng)用的時候我們常常要寫入不定量的數(shù)據(jù),直接調(diào)用“頁寫入”函數(shù)并不是特別方便,所以我們在它的基礎(chǔ)上編寫了“不定量數(shù)據(jù)寫入”的函數(shù), 基實現(xiàn)見下。
代碼清單 不定量數(shù)據(jù)寫入
/**
* @brief 對FLASH寫入數(shù)據(jù),調(diào)用本函數(shù)寫入數(shù)據(jù)前需要先擦除扇區(qū)
* @param pBuffer,要寫入數(shù)據(jù)的指針
* @param WriteAddr,寫入地址
* @param NumByteToWrite,寫入數(shù)據(jù)長度
* @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整數(shù)倍,運算結(jié)果Addr值為0*/
Addr = WriteAddr % SPI_FLASH_PageSize;
/*差count個數(shù)據(jù)值,剛好可以對齊到頁地址*/
count = SPI_FLASH_PageSize - Addr;
/*計算出要寫多少整數(shù)頁*/
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
/*mod運算求余,計算出剩余不滿一頁的字節(jié)數(shù)*/
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 */
{
/*先把整數(shù)頁都寫了*/
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;
}
/*若有多余的不滿一頁的數(shù)據(jù),把它寫完*/
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, NumOfSingle);
QSPI_Flash_WaitForWriteEnd();
}
}
/* 若地址與 SPI_FLASH_PageSize 不對齊 */
else
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
/*當(dāng)前頁剩余的count個位置比NumOfSingle小,一頁寫不完*/
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
/*先寫滿當(dāng)前頁*/
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, count);
QSPI_Flash_WaitForWriteEnd();
WriteAddr += count;
pBuffer += count;
/*再寫剩余的數(shù)據(jù)*/
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, temp);
QSPI_Flash_WaitForWriteEnd();
}
else /*當(dāng)前頁剩余的count個位置能寫完NumOfSingle個數(shù)據(jù)*/
{
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個數(shù)據(jù),為的是讓下一次要寫的地址對齊 */
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, count);
QSPI_Flash_WaitForWriteEnd();
/* 接下來就重復(fù)地址對齊的情況 */
WriteAddr += count;
pBuffer += count;
/*把整數(shù)頁都寫了*/
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;
}
/*若有多余的不滿一頁的數(shù)據(jù),把它寫完*/
if (NumOfSingle != 0)
{
R_QSPI_Write(&g_qspi0_flash_ctrl, pBuffer, WriteAddr, NumOfSingle);
QSPI_Flash_WaitForWriteEnd();
}
}
}
}
這段代碼與EEPROM章節(jié)中的“快速寫入多字節(jié)”函數(shù)原理是一樣的,運算過程在此不再贅述。區(qū)別是頁的大小以及實際數(shù)據(jù)寫入的時候,使用的是針對FLASH芯片的頁寫入函數(shù),且在實際調(diào)用這個“不定量數(shù)據(jù)寫入”函數(shù)時,還要注意確保目標(biāo)扇區(qū)處于擦除狀態(tài)。
23.3.2.10.從FLASH讀取數(shù)據(jù)
相對于寫入,F(xiàn)LASH芯片的數(shù)據(jù)讀取要簡單得多,使用讀取指令“Read Data”即可,其指令時序見下圖。

發(fā)送了指令編碼及要讀的起始地址后,F(xiàn)LASH芯片就會按地址遞增的方式返回存儲矩陣的內(nèi)容,讀取的數(shù)據(jù)量沒有限制, 只要沒有停止通訊,F(xiàn)LASH芯片就會一直返回數(shù)據(jù)。代碼實現(xiàn)見下。
代碼清單 從FLASH讀取數(shù)據(jù)
/**
* @brief 讀取FLASH數(shù)據(jù),減少ctrl這個標(biāo)志
* @param pBuffer,存儲讀出數(shù)據(jù)的指針
* @param ReadAddr,讀取地址
* @param NumByteToRead,讀取數(shù)據(jù)長度
* @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數(shù)據(jù)
* @param p_ctrl
* @param p_src 需要傳回的數(shù)據(jù)
* @param p_dest 數(shù)據(jù)地址
* @param byte_count 數(shù)據(jù)長度
*/
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;
}
由于讀取的數(shù)據(jù)量沒有限制,所以發(fā)送讀命令后一直接收NumByteToRead個數(shù)據(jù)到結(jié)束即可。
23.3.2.11.hal_entry入口函數(shù)
最后我們來編寫 hal_entry 入口函數(shù),進(jìn)行FLASH芯片讀寫校驗,代碼見下。
代碼清單 hal_entry 入口函數(shù)
/* 用戶頭文件包含 */
#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
/* 發(fā)送緩沖區(qū)初始化 */
uint8_t Tx_Buffer[] = "感謝您選用野火啟明瑞薩RA開發(fā)板";
uint8_t Rx_Buffer[sizeof(Tx_Buffer)];
/*
* 函數(shù)名:Buffercmp
* 描述 :比較兩個緩沖區(qū)中的數(shù)據(jù)是否相等
* 輸入 :pBuffer1 src緩沖區(qū)指針
* pBuffer2 dst緩沖區(qū)指針
* BufferLength 緩沖區(qū)長度
* 輸出 :無
* 返回 :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 調(diào)試串口初始化
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 扇區(qū),F(xiàn)LASH寫入前要先擦除 */
// 這里擦除4K,即一個扇區(qū),擦除的最小單位是扇區(qū)
QSPI_Flash_SectorErase(FLASH_SectorToErase);
/* 將發(fā)送緩沖區(qū)的數(shù)據(jù)寫到flash中 */
// 這里寫一頁,一頁的大小為256個字節(jié)
QSPI_Flash_BufferWrite(Tx_Buffer, FLASH_WriteAddress, sizeof(Tx_Buffer));
printf("寫入的數(shù)據(jù)為:%s \r\n", Tx_Buffer);
/* 將剛剛寫入的數(shù)據(jù)讀出來放到接收緩沖區(qū)中 */
QSPI_Flash_BufferRead(Rx_Buffer, FLASH_ReadAddress, sizeof(Tx_Buffer));
printf("讀出的數(shù)據(jù)為:%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測試存儲浮點數(shù)和整數(shù)示例\r\n");
/* 存儲小數(shù)和整數(shù)的數(shù)組,各7個 */
long double double_buffer[7] = {0};
int int_buffer[7] = {0};
/*生成要寫入的數(shù)據(jù)*/
for (uint8_t k = 0; k < 7; k++)
{
double_buffer[k] = k + 0.1;
int_buffer[k] = k * 500 + 1 ;
}
printf("向芯片寫入數(shù)據(jù):");
/*打印到串口*/
printf("\r\n小數(shù) tx = ");
for (uint8_t k = 0; k < 7; k++)
{
printf("%LF, ", double_buffer[k]);
}
printf("\r\n整數(shù) tx = ");
for (uint8_t k = 0; k < 7; k++)
{
printf("%d, ", int_buffer[k]);
}
/* 前面已擦除整個扇區(qū)和寫入第0頁,現(xiàn)繼續(xù)寫入第1頁和第2頁 */
/*寫入小數(shù)數(shù)據(jù)到第一頁*/
QSPI_Flash_BufferWrite((void *)double_buffer, SPI_FLASH_PageSize * 1, sizeof(double_buffer));
/*寫入整數(shù)數(shù)據(jù)到第二頁*/
QSPI_Flash_BufferWrite((void *)int_buffer, SPI_FLASH_PageSize * 2, sizeof(int_buffer));
/*讀取小數(shù)數(shù)據(jù)*/
QSPI_Flash_BufferRead((void *)double_buffer, SPI_FLASH_PageSize * 1, sizeof(double_buffer));
/*讀取整數(shù)數(shù)據(jù)*/
QSPI_Flash_BufferRead((void *)int_buffer, SPI_FLASH_PageSize * 2, sizeof(int_buffer));
printf("\r\n\r\n從芯片讀到數(shù)據(jù):");
printf("\r\n小數(shù) rx = ");
for (uint8_t k = 0; k < 7; k++)
{
printf("%LF, ", double_buffer[k]);
}
printf("\r\n整數(shù) 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線連接開發(fā)板“USB TO UART”接口跟電腦,在電腦端打開串口調(diào)試助手,把編譯好的程序下載到開發(fā)板。在串口調(diào)試助手可看到FLASH測試的調(diào)試信息。

-
FlaSh
+關(guān)注
關(guān)注
10文章
1758瀏覽量
155854 -
開發(fā)板
+關(guān)注
關(guān)注
26文章
6398瀏覽量
120442 -
開發(fā)環(huán)境
+關(guān)注
關(guān)注
1文章
275瀏覽量
17670 -
QSPI
+關(guān)注
關(guān)注
0文章
55瀏覽量
13397
原文標(biāo)題:控制FLASH的指令——瑞薩RA系列FSP庫開發(fā)實戰(zhàn)指南(79)
文章出處:【微信號:瑞薩嵌入式小百科,微信公眾號:瑞薩嵌入式小百科】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
瑞薩RA系列FSP庫開發(fā)實戰(zhàn)指南之QSPI通訊協(xié)議簡介
瑞薩RA系列FSP庫開發(fā)實戰(zhàn)指南之QSPI控制FLASH的指令
瑞薩e2studio(1)----瑞薩芯片之搭建FSP環(huán)境
【瑞薩RA6E2地奇星開發(fā)板試用】開發(fā)板介紹及環(huán)境搭建
【RA-Eco-RA4M2開發(fā)板評測】+VS Code 下瑞薩 RA4M2 開發(fā)環(huán)境搭建與 GPIO 點燈實驗教程
【瑞薩RA4系列開發(fā)板體驗】開發(fā)環(huán)境搭建和新手點燈指南
【瑞薩RA4系列開發(fā)板體驗】體驗過程
【野火啟明6M5開發(fā)板體驗】開箱+認(rèn)識開發(fā)板+資料
【有獎直播預(yù)報名】瑞薩電子RA系列產(chǎn)品開發(fā)工具之FSP4.0.0新特性介紹
【視頻教程】瑞薩RA單片機FSP開發(fā)(3)FSP架構(gòu)-解釋Blinky架構(gòu)[上]
瑞薩RA6系列芯片外擴SRAM方法
瑞薩電子RA系列微控制器的可擴展性強的配置軟件包 (FSP)安裝下載與使用指南
RA MCU眾測寶典 | 在瑞薩CPKCOR-RA8D1B核心板上實現(xiàn)QSPI讀取外部Flash
瑞薩RA系列FSP庫開發(fā)實戰(zhàn)指南之QSPI讀寫外部Flash芯片實驗
評論