这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 国产MCU » 【AT-START-F457】硬件SPI驱动存储芯片

共3条 1/1 1 跳转至

【AT-START-F457】硬件SPI驱动存储芯片

高工
2026-07-20 20:30:52   被打赏 45 分(兑奖)     打赏

一:W25Q64芯片介绍:

 W25Q64 是一款常见的串行 Flash 存储器芯片,由华邦(Winbond)公司生产。它的存储容量为 64Mbit(8MB),采用 SPI(串行外设接口)进行通信,常用于嵌入式系统中存储代码或数据。

1.2 参数特性:

    存储容量:64Mbit(相当于8MByte)可以存储大量数据

    接口:采用 SPI接口

    工作电压范围:2.7V~3.6V,典型 3.3V供电设备

    擦除功能:支持扇区、块和整个芯片擦除功能,4KB、32KB、64KB 和整个芯片的擦除操作工作温度范围:-40°C~+85°C的温度范围

W25Q64 存储容量:8MB 分为128个块

    1页=256字节(Byte)

    1扇区=16页=16256字节(Byte)=4096Byte/1024=4KB

    1块=16扇区=1616256字节(Byte)=65536Byte/1024=64KB

    则128块=12816*16*256字节(Byte)=8388608 Byte/

二:W25Q64操作注意事项:

W25Q64 的基本操作遵循 SPI 协议,一般流程是:片选拉低 → 发送命令字 → 发送地址(如需)→ 交换数据 → 片选拉高。

    2.1:写使能(Write Enable, 06h):在执行任何编程或擦除操作前,必须先发送写使能指令。该指令会将状态寄存器中的写使能锁存(WEL)

    2.2:页编程(Page Program, 02h):向指定地址写入数据。一次最多写入256字节(一页),且不能跨页写入。如果写入数据地址会跨页,超出页首地址部分会从当前页的开头覆写8。写入前必须确保目标区域已被擦除。

    2.3:扇区擦除(Sector Erase, 20h):擦除指定4KB扇区。擦除操作会将所有位变为1。擦除前需写使能。

    2.4:块擦除(Block Erase):提供32KB (52h) 和64KB (D8h) 块擦除选项。

    2.5:芯片擦除(Chip Erase, C7h / 60h):擦除整个芯片。谨慎使用。

    2.6:读数据(Read Data, 03h):从指定地址开始读取数据。读取操作没有页的限制。

    2.7:读状态寄存器(Read Status Register, 05h):主要用于检查 BUSY 位(S0)和 WEL 位(S1)。在编程、擦除或写状态寄存器期间,BUSY位会置1,此时芯片仅响应读状态寄存器和擦除暂停等少数指令。

    读设备ID:如 Read JEDEC ID (9Fh) 可用于识别器件。

三:图形化配置如下所示:

3.1 AT32图形化配置下载链接:

https://arterytek.com/cn/support/tools.jsp?index=0

软件介绍:

    AT32 Workbench通过对MCU的图形化配置,生成初始化C代码和对应IDE项目,同时为您提供入门配置例程,涵盖从环境搭建、工程创建到编译调试的全流程操作说明,帮助您快速上手AT32 MCU开发,以减少开发人员的工作量、时间和成本。

    支持外设初始化配置

    支持PIN MUX配置,并可自定义PIN脚标签

    支持系统时钟自动化配置

    支持在线代码预览,所见即所得

    支持add user code功能(客户已有代码不会被新生成工程覆盖)

    支持Keil、IAR、AT32 IDE等常见IDE工程自动生成

    支持最近编辑的设计记录

    支持配置PDF报告的生成

    支持中英文切换

    支持Windows、Linux等多平台

    支持FREERTOS、USB_DEVICE、USB_HOST等中间件

    支持软件的在线升级

界面展示如下所示:

111.png四:代码如下

4.1 SPI的初始化:

void wk_spi2_init(void)
{
  /* add user code begin spi2_init 0 */

  /* add user code end spi2_init 0 */

  gpio_init_type gpio_init_struct;
  spi_init_type spi_init_struct;

  gpio_default_para_init(&gpio_init_struct);
  spi_default_para_init(&spi_init_struct);

  /* add user code begin spi2_init 1 */

  /* add user code end spi2_init 1 */

  /* configure the SCK pin */
  gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE13, GPIO_MUX_5);
  gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
  gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
  gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
  gpio_init_struct.gpio_pins = GPIO_PINS_13;
  gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
  gpio_init(GPIOB, &gpio_init_struct);

  /* configure the MISO pin */
  gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE14, GPIO_MUX_5);
  gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
  gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
  gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
  gpio_init_struct.gpio_pins = GPIO_PINS_14;
  gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
  gpio_init(GPIOB, &gpio_init_struct);

  /* configure the MOSI pin */
  gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE15, GPIO_MUX_5);
  gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
  gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
  gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
  gpio_init_struct.gpio_pins = GPIO_PINS_15;
  gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
  gpio_init(GPIOB, &gpio_init_struct);

  /* configure param */
  spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX;
  spi_init_struct.master_slave_mode = SPI_MODE_MASTER;
  spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;
  spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;
  spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_8;
  spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_HIGH;
  spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE;
  spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
  spi_init(SPI2, &spi_init_struct);

  /* add user code begin spi2_init 2 */
  FLASH_CS_HIGH();
  crm_periph_clock_enable(CRM_SPI2_PERIPH_CLOCK, TRUE);
  spi_default_para_init(&spi_init_struct);
  spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX;
  spi_init_struct.master_slave_mode = SPI_MODE_MASTER;
  spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_8;
  spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;
  spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;
  spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_HIGH;
  spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE;
  spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
  spi_init(SPI2, &spi_init_struct);
  spi_enable(SPI2, TRUE);
  /* add user code end spi2_init 2 */

  spi_enable(SPI2, TRUE);

  /* add user code begin spi2_init 3 */

  /* add user code end spi2_init 3 */
}

    这里需要注意下:SPI通信中的时钟极性(CPOL)和时钟相位(CPHA)是两个关键参数。它们定义了时钟信号在空闲状态下的电平和数据采样的时机。当时是利用硬件的SPI读取两个芯片,一个是存储芯片(W25Q64),一个是AD芯片(CS5530),由于这个两个芯片的时序不一致,一个是在上升沿读取数据,另外一个是在下降沿读取数据,当时也是调试好久才发现,时序不一致,当初也是刚毕业不久,也没有太多的工作经验,还以为是芯片损坏,现在想起来还是真的小白了。后来工作时间久了才发现,影响SPI通讯不正常的原因有很多,需要注意硬件连接、通信协议与时序、数据传输等方面。

4.2 SPI发送一个字节

uint8_t spi_byte_write(uint8_t data)
{
  uint8_t brxbuff;
  spi_i2s_dma_transmitter_enable(SPI2, FALSE);
  spi_i2s_dma_receiver_enable(SPI2, FALSE);
  spi_i2s_data_transmit(SPI2, data);
  while(spi_i2s_flag_get(SPI2, SPI_I2S_RDBF_FLAG) == RESET);
  brxbuff = spi_i2s_data_receive(SPI2);
  
  /* wait spi idle when communication end */
  while(spi_i2s_flag_get(SPI2, SPI_I2S_BF_FLAG) != RESET);
  
  return brxbuff;
}

4.3 写入一页的函数如下所示:

void spiflash_write(uint8_t *pbuffer, uint32_t write_addr, uint32_t length)
{
  uint32_t sector_pos;
  uint16_t sector_offset;
  uint16_t sector_remain;
  uint16_t index;
  uint8_t *spiflash_buf;
  spiflash_buf = spiflash_sector_buf;

  /* sector address */
  sector_pos = write_addr / SPIF_SECTOR_SIZE;

  /* address offset in a sector */
  sector_offset = write_addr % SPIF_SECTOR_SIZE;

  /* the remain in a sector */
  sector_remain = SPIF_SECTOR_SIZE - sector_offset;
  if(length <= sector_remain)
  {
    /* smaller than a sector size */
    sector_remain = length;
  }
  while(1)
  {
    /* read a sector */
    spiflash_read(spiflash_buf, sector_pos * SPIF_SECTOR_SIZE, SPIF_SECTOR_SIZE);

    /* validate the read erea */
    for(index = 0; index < sector_remain; index++)
    {
      if(spiflash_buf[sector_offset + index] != 0xFF)
      {
        /* there are some data not equal 0xff, so this secotr needs erased */
        break;
      }
    }
    if(index < sector_remain)
    {
      /* erase the sector */
      spiflash_sector_erase(sector_pos);

      /* copy the write data */
      for(index = 0; index < sector_remain; index++)
      {
        spiflash_buf[index + sector_offset] = pbuffer[index];
      }
      spiflash_write_nocheck(spiflash_buf, sector_pos * SPIF_SECTOR_SIZE, SPIF_SECTOR_SIZE); /* program the sector */
    }
    else
    {
      /* write directly in the erased area */
      spiflash_write_nocheck(pbuffer, write_addr, sector_remain);
    }
    if(length == sector_remain)
    {
      /* write end */
      break;
    }
    else
    {
      /* go on writing */
      sector_pos++;
      sector_offset = 0;

      pbuffer += sector_remain;
      write_addr += sector_remain;
      length -= sector_remain;
      if(length > SPIF_SECTOR_SIZE)
      {
        /* could not write the remain data in the next sector */
        sector_remain = SPIF_SECTOR_SIZE;
      }
      else
      {
        /* could write the remain data in the next sector */
        sector_remain = length;
      }
    }
  }
}

写入时候的注意事项:

    每次写入操作前都必须进行写使能指令

    由于 FLASH 存储器的特性决定了我们再写入数据时候,首先需要擦除数据,否则会导致数据存储错乱,芯片的特性决定只能由1写成0,而不能由0写成1,所以在写入新数据时候,务必擦除

    擦除必须以最小擦除单元进行,也就是最少要进行一个扇区大小 4KB 的擦除

    写入数据都是先放到 256 字节的页缓存区,然后再转移到 FLASH 内,由于页缓冲区大小限制,所以每次最多写入一页的数据,超过页尾位置的数据会回到页首覆盖写入

    在执行写入和擦除操作后,芯片会进入忙状态,这时不能进行读写操作,可以通过查看状态寄存器的 BUSY 位来判断芯片是否退出忙状态

4.3 读取一页的函数如下所示:

void spiflash_read(uint8_t *pbuffer, uint32_t read_addr, uint32_t length)
{
  FLASH_CS_LOW();
  spi_byte_write(SPIF_READDATA); /* send instruction */
  spi_byte_write((uint8_t)((read_addr) >> 16)); /* send 24-bit address */
  spi_byte_write((uint8_t)((read_addr) >> 8));
  spi_byte_write((uint8_t)read_addr);
  spi_bytes_read(pbuffer, length);
  FLASH_CS_HIGH();
}

4.4 测试读取代码如下所示:

         flash_id_index = spiflash_read_id();
  if((flash_id_index != W25Q128)&&(flash_id_index != W25Q80)&&(flash_id_index != W25Q16)&&(flash_id_index != W25Q32)&&(flash_id_index != W25Q64))
  {
    printf("flash id check error!\r\n");

    return 1;
  }
  else
  {
    printf("flash id check success! id: %x\r\n", flash_id_index);
  }

  /* erase sector */
  spiflash_sector_erase(FLASH_TEST_ADDR / SPIF_SECTOR_SIZE);

  /* write data */
  spiflash_write(buffer_tx, FLASH_TEST_ADDR, BUF_SIZE);

  /* read data */
  spiflash_read(buffer_rx, FLASH_TEST_ADDR, BUF_SIZE);

  /* printf read data */
  printf("Read Data: ");
  for(index = 0; index < BUF_SIZE; index++)
  {
    printf("%x ", buffer_rx[index]);
  }

五:实物测试如下:

222.png

六:调试心得

核心要点是:W25Q64容量为8MB(64Mbit),其地址宽度为32位,必须切换至4字节地址模式,否则无法访问全部存储空间。

地址模式确认:上电后W25Q256默认处于3字节地址模式,如不切换则最高只能寻址到0xFFFFFF。在初始化时务必通过读状态寄存器3(0x15)检查并设置4字节地址模式。

写操作复杂性:Flash写入前必须先擦除(将位由1写为0),且最小擦除单位为4KB的扇区。若进行写操作,需遵循“读扇区-修改-擦除扇区-写回”的流程,并关注状态寄存器的忙标志。




关键词: AT-START-F457     硬件     存储    

院士
2026-07-21 22:40:41     打赏
2楼

楼主,您这个只有读flash的实现呀


助工
2026-07-28 23:17:30     打赏
3楼

这个应该是必做实验之一。


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]