瑞萨单片机开发教程[三十三] QSPI 外部flash读写实验
2026/8/5 11:54:42 网站建设 项目流程

开发板官网(https://www.edevkit.com/)

本系列教程目录:
- 瑞萨单片机开发教程[一] e2-studio + DAP LINK开发环境搭建
- 瑞萨单片机开发教程[二] Keil/RASC开发环境搭建
- 瑞萨单片机开发教程[三] 新建RASC工程
- 瑞萨单片机开发教程[四] GPIO输出——led实验
- 瑞萨单片机开发教程[五] GPIO输入——按键key实验
- 瑞萨单片机开发教程[六] ICU外部中断实验
- 瑞萨单片机开发教程[七] SCI UART串口通信实验
- 瑞萨单片机开发教程[八] DMAC 串口数据传输实验
- 瑞萨单片机开发教程[九] ADC 电压采集实验


QSPI 外部 Flash 读写实验

本章目标:

  • 了解 RA6M5 处理器的 QSPI 模块
  • 学会使用 RASC 配置 QSPI 模块,使用 API 读写数据。

硬件设计(略,参见配套硬件原理图)

软件设计

新建工程

复制一份 "07 SCI_UART_Recv_send" 修改文件夹名 "33 QSPI_flash",双击打开EBF_RA6M5.uvprojx工程,通过 Keil 的新建文件新建qspi.cqspi.h保存到目录下的\\src文件夹下。

FSP 配置

Keil 界面选择 “Tools”→“RA Smart Configurator” 打开 RASC 界面。

在 “Pins” 添加 QSPI 设备。

添加 QSPI Stack 模块。

配置 QSPI Stack 参数。

配置完成之后可以按下快捷键"Ctrl + S"保存,最后点击 "Generate Project Content" 按钮,让软件自动生成配置代码即可。

程序设计

实现 QSPI 的读写函数,根据数据长度自动换页,写入一组字符串,一组float数据,一组int数据,分别读取出来,打印到串口查看数据是否正确。

  1. 打开 QSPI 设备
fsp_err_t R_QSPI_Open(spi_flash_ctrl_t * p_ctrl, spi_flash_cfg_t const * const p_cfg);
  1. 擦除扇区
fsp_err_t R_QSPI_Erase(spi_flash_ctrl_t * p_ctrl, uint8_t * const p_device_address, uint32_t byte_count);
  1. 直接写指令数据
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);
  1. 直接读指令数据
fsp_err_t R_QSPI_DirectRead(spi_flash_ctrl_t * p_ctrl, uint8_t * const p_dest, uint32_t const bytes);
  1. 写入页数据
fsp_err_t R_QSPI_Write(spi_flash_ctrl_t * p_ctrl, uint8_t const * const p_src, uint8_t * const p_device_address, uint32_t byte_count);
  1. 读取页数据
fsp_err_t R_QSPI_Read(spi_flash_ctrl_t * p_ctrl, uint8_t * const p_device_address, uint8_t * const p_dest, uint32_t byte_count);
  1. 状态读取
fsp_err_t R_QSPI_StatusGet(spi_flash_ctrl_t * p_ctrl, spi_flash_status_t * const p_status);

程序编写

qspi.h头文件定义相关函数和宏定义。

#ifndef __BSP_QSPI_FLASH_H #define __BSP_QSPI_FLASH_H #include "hal_data.h" #define W25X16 0xEF3015 #define W25Q16 0xEF4015 #define W25Q128 0XEF4018 #define W25Q64 0XEF4017 #define W25Q32 0xEF4016 #define AT25SF321B 0x1F8701 #define sFLASH_ID W25Q32 #define FLASH_ID_W25Q32JV 0xEF4016 // W25Q32JV #define FLASH_ID_AT25SF321B 0x1F8701 // AT25SF321B #define SPI_FLASH_PageSize 256 #define SPI_FLASH_PerWritePageSize 256 #define RESET_VALUE (0x00) /*命令定义-开头*******************************/ #define WriteEnable 0x06 #define WriteDisable 0x04 #define ReadStatusReg 0x05 #define WriteStatusReg 0x01 #define ReadData 0x03 #define FastReadData 0x0B #define FastReadDual 0x3B #define PageProgram 0x02 #define BlockErase 0xD8 #define SectorErase 0x20 #define ChipErase 0xC7 #define PowerDown 0xB9 #define ReleasePowerDown 0xAB #define DeviceID 0xAB #define ManufactDeviceID 0x90 #define JedecDeviceID 0x9F /* WIP(busy)标志,FLASH内部正在写入 */ #define WIP_Flag 0x01 #define Dummy_Byte 0xFF /*命令定义-结尾*******************************/ void QSPI_Flash_Init(void); uint32_t QSPI_Flash_ReadID(void); uint32_t QSPI_Flash_ReadDeviceID(void); void QSPI_Flash_PowerDown(void); void QSPI_Flash_WakeUp(void); void QSPI_Flash_WriteEnable(void); fsp_err_t QSPI_Flash_WaitForWriteEnd(void); void QSPI_Flash_SectorErase(uint32_t adress); void QSPI_Flash_PageWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite); //页写入 void QSPI_Flash_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite); void QSPI_Flash_BufferRead(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead); //static void qspi_d0_byte_write_standard (uint8_t byte); 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); #endif

qspi.c中实现 ID 读取,设备 ID 读取,用于判断 Flash 是否存在。

#include "qspi.h" #include "stdio.h" void QSPI_Flash_Init(void) { R_QSPI_Open(&g_qspi_flash_ctrl, &g_qspi_flash_cfg); R_BSP_SoftwareDelay(10u, BSP_DELAY_UNITS_MILLISECONDS); } /** * @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_qspi_flash_ctrl, &data[0], 1, true); R_QSPI_DirectRead(&g_qspi_flash_ctrl, &data[0], 3); back = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2]); return back; } /** * @brief 读取FLASH 设备ID * @param 无 * @retval FLASH ID */ uint32_t QSPI_Flash_ReadDeviceID(void) { unsigned char data[6] = {}; uint32_t back; data[0] = DeviceID; data[1] = 0xff; data[2] = 0xff; data[3] = 0xff; R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 4, true); R_QSPI_DirectRead(&g_qspi_flash_ctrl, &data[0], 3); back = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2]); return back; } /** * @brief 进入掉电模式 * @param 无 */ void QSPI_Flash_PowerDown(void) { unsigned char data[6] = {}; data[0] = PowerDown; R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false); } /** * @brief 唤醒 * @param 无 */ void QSPI_Flash_WakeUp(void) { unsigned char data[6] = {}; data[0] = ReleasePowerDown; R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false); } /** * @brief 向FLASH发送 写使能 命令 * @param none * @retval none */ void QSPI_Flash_WriteEnable(void) { unsigned char data[6] = {}; data[0] = WriteEnable; R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false); }

等待写入数据完成。

/** * @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_qspi_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; }

擦除扇区函数,根据写入数据长度自动换页函数,读取函数。

/** * @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_qspi_flash_ctrl, &data[1], 4, false); QSPI_Flash_WaitForWriteEnd(); } /** * @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_qspi_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite); QSPI_Flash_WaitForWriteEnd(); } /** * @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区 * @param pBuffer,要写入数据的指针 * @param WriteAddr,写入地址 * @param NumByteToWrite,写入数据长度 * @retval 无 */ void QSPI_Flash_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite) { uint32_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_qspi_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite); QSPI_Flash_WaitForWriteEnd(); } else //NumByteToWrite > SPI_FLASH_PageSize { //先把整数页都写了 while (NumOfPage--) { R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, SPI_FLASH_PageSize); QSPI_Flash_WaitForWriteEnd(); WriteAddr += SPI_FLASH_PageSize; pBuffer += SPI_FLASH_PageSize; } //若有多余的不满一页的数据,把它写完 R_QSPI_Write(&g_qspi_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_qspi_flash_ctrl, pBuffer, WriteAddr, count); QSPI_Flash_WaitForWriteEnd(); WriteAddr += count; pBuffer += count; //再写剩余的数据 R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, temp); QSPI_Flash_WaitForWriteEnd(); } else //当前页剩余的count个位置能写完NumOfSingle个数据 { R_QSPI_Write(&g_qspi_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_qspi_flash_ctrl, pBuffer, WriteAddr, count); QSPI_Flash_WaitForWriteEnd(); // 接下来就重复地址对齐的情况 WriteAddr += count; pBuffer += count; //把整数页都写 while (NumOfPage--) { R_QSPI_Write(&g_qspi_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_qspi_flash_ctrl, pBuffer, WriteAddr, NumOfSingle); QSPI_Flash_WaitForWriteEnd(); } } } } /** * @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_qspi_flash_ctrl, pBuffer, ReadAddr, NumByteToRead); }

hal_entry.c文件中添加qspi.h头文件,添加测试代码,写入一组字符串,一组float数据,一组int数据,分别读出来打印到串口查看读写数据。

#define Flash_Opt_Addr 0x00000000 uint8_t txString[] = "either write something worth reading or do something worth writing."; uint8_t rxString[sizeof(txString)]; double txFloat[10] = {1.2, 2.54, 3.47, 4.12, 5.22, 6.31, 7.15, 8.65, 9.71, 10.94}; double rxFloat[10] = {0}; int txInt[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 100}; int rxInt[10] = {0}; void hal_entry(void) { /* TODO: add your own code here */ led_Init(); uart0_Init(); QSPI_Flash_Init(); uint32_t Id = QSPI_Flash_ReadID(); uint32_t devId = QSPI_Flash_ReadDeviceID(); if(Id != 0 && devId != 0) { printf("FlashID: 0x%X, DeviceID: 0x%X \r\n", Id, devId); //擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 //擦除4K,即一个扇区,擦除的最小单位是扇区 QSPI_Flash_SectorErase(Flash_Opt_Addr); //写一页,一页的大小为256个字节 QSPI_Flash_BufferWrite(txString, Flash_Opt_Addr, sizeof(txString)); printf("write string: %s \r\n", txString); //将写入的数据读出来 QSPI_Flash_BufferRead(rxString, Flash_Opt_Addr, sizeof(txString)); printf("read string: %s \r\n", rxString); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒 //前面已擦除整个扇区和写入第0页,现继续写入第1页和第2页 //写入float数据到第一页 QSPI_Flash_BufferWrite((void *)txFloat, SPI_FLASH_PageSize * 1, sizeof(txFloat)); printf("write float: "); for(int i=0; i<10; i++) printf("%f, ", txFloat[i]); printf("\r\n"); //读取float数据 QSPI_Flash_BufferRead((void *)rxFloat, SPI_FLASH_PageSize * 1, sizeof(txFloat)); printf("read float: "); for(int i=0; i<10; i++) printf("%f, ", rxFloat[i]); printf("\r\n"); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒 //写入第二页 QSPI_Flash_BufferWrite((void *)txInt, SPI_FLASH_PageSize * 2, sizeof(txInt)); printf("write int: "); for(int i=0; i<10; i++) printf("%d, ", txInt[i]); printf("\r\n"); //读取整数数据 QSPI_Flash_BufferRead((void *)rxInt, SPI_FLASH_PageSize * 2, sizeof(txInt)); printf("read int: "); for(int i=0; i<10; i++) printf("%d, ", rxInt[i]); printf("\r\n"); } while(1) { LED1_TOGGLE(); R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒 } #if BSP_TZ_SECURE_BUILD /* Enter non-secure code */ R_BSP_NonSecureEnter(); #endif }

下载验证

连接下载器,将程序编译并下载到开发板之后,按下复位按键来复位开发板,程序启动初始化完。

可以看到打印读写的数据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询