瑞萨RA系列FSP库开发实战指南之QSPI读写外部Flash芯片实验

环贸财神 2026-03-04 4798人围观 瑞萨

23.3

实验:读写外部Flash芯片

23.3.1

硬件设计

野火启明6M5开发板的QSPI FLASH电路图如图所示:

9b72158a-16b7-11f1-90a1-92fbcf53809c.png

野火启明4M2开发板的QSPI FLASH电路图如图所示:

野火启明2L1开发板的SPI FLASH电路图如图所示:

9c1a84b8-16b7-11f1-90a1-92fbcf53809c.png

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芯片定义了一个状态寄存器,如下图所示。

图 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个扇区,其内部存储矩阵分布见下图。

图 FLASH芯片的存储矩阵

使用扇区擦除指令“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页写入的时序见下图。

图 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”即可,其指令时序见下图。

图 SPI FLASH读取数据时序

发送了指令编码及要读的起始地址后,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测试的调试信息。

图

Powered By Z-BlogPHP