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

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

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

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

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

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

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

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

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

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

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

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

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

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

使用扇區(qū)擦除指令“Sector Erase”可控制FLASH芯片開(kāi)始擦寫(xiě),其指令時(shí)序見(jiàn)下圖。

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

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

發(fā)送了指令編碼及要讀的起始地址后,F(xiàn)LASH芯片就會(huì)按地址遞增的方式返回存儲(chǔ)矩陣的內(nèi)容,讀取的數(shù)據(jù)量沒(méi)有限制, 只要沒(méi)有停止通訊,F(xiàn)LASH芯片就會(huì)一直返回?cái)?shù)據(jù)。代碼實(shí)現(xiàn)見(jiàn)下。
代碼清單 從FLASH讀取數(shù)據(jù)
/**
* @brief 讀取FLASH數(shù)據(jù),減少ctrl這個(gè)標(biāo)志
* @param pBuffer,存儲(chǔ)讀出數(shù)據(jù)的指針
* @param ReadAddr,讀取地址
* @param NumByteToRead,讀取數(shù)據(jù)長(zhǎng)度
* @retval 無(wú)
*/
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ù)長(zhǎng)度
*/
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ù)量沒(méi)有限制,所以發(fā)送讀命令后一直接收NumByteToRead個(gè)數(shù)據(jù)到結(jié)束即可。
23.3.2.11.hal_entry入口函數(shù)
最后我們來(lái)編寫(xiě) hal_entry 入口函數(shù),進(jìn)行FLASH芯片讀寫(xiě)校驗(yàn),代碼見(jiàn)下。
代碼清單 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開(kāi)發(fā)板";
uint8_t Rx_Buffer[sizeof(Tx_Buffer)];
/*
* 函數(shù)名:Buffercmp
* 描述 :比較兩個(gè)緩沖區(qū)中的數(shù)據(jù)是否相等
* 輸入 :pBuffer1 src緩沖區(qū)指針
* pBuffer2 dst緩沖區(qū)指針
* BufferLength 緩沖區(qū)長(zhǎng)度
* 輸出 :無(wú)
* 返回 :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("這是一個(gè)串行FLASH的讀寫(xiě)例程\r\n");
printf("打開(kāi)串口助手查看打印的信息\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("檢測(cè)到串行FLASH:W25Q32 !\r\n");
}
else
{
printf("檢測(cè)到串行FLASH:AT25SF32 !\r\n");
}
printf("FlashID is 0x%X, Manufacturer Device ID is 0x%X.\r\n", FlashID, FlashDeviceID);
/* 擦除將要寫(xiě)入的 SPI FLASH 扇區(qū),F(xiàn)LASH寫(xiě)入前要先擦除 */
// 這里擦除4K,即一個(gè)扇區(qū),擦除的最小單位是扇區(qū)
QSPI_Flash_SectorErase(FLASH_SectorToErase);
/* 將發(fā)送緩沖區(qū)的數(shù)據(jù)寫(xiě)到flash中 */
// 這里寫(xiě)一頁(yè),一頁(yè)的大小為256個(gè)字節(jié)
QSPI_Flash_BufferWrite(Tx_Buffer, FLASH_WriteAddress, sizeof(Tx_Buffer));
printf("寫(xiě)入的數(shù)據(jù)為:%s \r\n", Tx_Buffer);
/* 將剛剛寫(xiě)入的數(shù)據(jù)讀出來(lái)放到接收緩沖區(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測(cè)試成功!\r\n");
LED3_ON;
}
else
{
printf("\r\n32Mbit串行Flash測(cè)試失敗!\r\n");
LED1_ON;
}
printf("\r\n測(cè)試存儲(chǔ)浮點(diǎn)數(shù)和整數(shù)示例\r\n");
/* 存儲(chǔ)小數(shù)和整數(shù)的數(shù)組,各7個(gè) */
long double double_buffer[7] = {0};
int int_buffer[7] = {0};
/*生成要寫(xiě)入的數(shù)據(jù)*/
for (uint8_t k = 0; k < 7; k++)
{
double_buffer[k] = k + 0.1;
int_buffer[k] = k * 500 + 1 ;
}
printf("向芯片寫(xiě)入數(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]);
}
/* 前面已擦除整個(gè)扇區(qū)和寫(xiě)入第0頁(yè),現(xiàn)繼續(xù)寫(xiě)入第1頁(yè)和第2頁(yè) */
/*寫(xiě)入小數(shù)數(shù)據(jù)到第一頁(yè)*/
QSPI_Flash_BufferWrite((void *)double_buffer, SPI_FLASH_PageSize * 1, sizeof(double_buffer));
/*寫(xiě)入整數(shù)數(shù)據(jù)到第二頁(yè)*/
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 錯(cuò)誤:0x%X", FlashID);
LED1_ON;
}
while(1);
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
23.3.3.下載驗(yàn)證
用USB線連接開(kāi)發(fā)板“USB TO UART”接口跟電腦,在電腦端打開(kāi)串口調(diào)試助手,把編譯好的程序下載到開(kāi)發(fā)板。在串口調(diào)試助手可看到FLASH測(cè)試的調(diào)試信息。

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