教程12_nios ii软核SPI接口驱动串行flash芯片W25Q128

一、前言

本节将介绍如何在NIOS II软核中通过SPI接口驱动W25Q128 Flash芯片。W25Q128是华邦公司推出的128Mb(也就是16MB)串行Flash存储器,支持SPI总线通信,常用于存储固件、配置参数等非易失数据。通过本节内容,读者将学会在QSYS中添加SPI控制器、编写底层驱动代码,并实现Flash的读写操作。

距离上一篇NIOS的教程教程11_nios ii软核uart的用法之三fp文件操作fopen与fwrite,时间有点久,赶紧更新下。

二、硬件与开发环境

硬件平台:小梅哥AC601 FPGA开发板(集成EP4CE10F17I7芯片)

外设模块:W25Q128(SPI接口,板载已连接至FPGA)

软件环境:Quartus II 13.0

Nios II Software Build Tools for Eclipse

SPI Flash数据手册(W25Q128 Datasheet)

三、QSYS中添加SPI控制器

3.1 打开QSYS系统:

在Quartus II中打开之前创建的软核工程(如core_led.qsys),点击QSys进入系统配置界面。

3.2 添加SPI核:

在组件库中找到SPI (3-wire, 4-wire)并双击。

配置参数如下,我这里基本没改,保持默认即可:

  • Number of slaves: 1
  • Data Width: 8 bits
  • Clock Phase: 0 (CPHA=0)
  • Clock Polarity: 0 (CPOL=0)
  • Frequency: 128KHz(根据实际需求调整)

点击Finish完成添加,并将模块重命名为spi_flash。

3.3 连接时钟、复位、数据信号:

将clk_0的时钟信号连接到SPI核的clk接口。

复位信号reset_n连接到全局复位总线。

spi_control_port那里,连接数据线。

3.4 引出SPI接口引脚:

在Export列中,将spi_flash的信号引出,并命名为spi_flash。

3.5 基地址、中断号重新分配

因为在软核中增加了新的外设,因此需要给外设分配基地址和中断向量序列号。方法参照之前的,左上方system菜单下的那两个选项,依次点击即可。

分配之前:

分配之后(红框位置,鼠标左键先点一下再分配中断向量号):

3.6 保存,更新软核

配置完成,CTRL+S保存当前配置,然后切换到Generation页面,点击下面的Generate按钮,开始更新软核:

更新完成,点击Close,关闭提示框:

四、 更新SPI接口

SPI软核模块添加完成,现在要把模块中的接口更新到组件中,实现信号的连接。

4.1组件接口更新

如图,点击HDL_Example,切换选项卡,然后复制对应的红框的内容,粘贴到niso_led.vhd文件中,进行组件接口的更新。

更新到nios_led.vhd文件中component core_led里,如下:

4.2 模块接口更新

同理,实例化区域中的模块接口也要更新:

4.3 实体接口更新

最后,更新实体entity nios_led中的接口,把SPI接口更新到里面,毕竟它是要和外部芯片通信的。

保存。

五、编译、更新芯片配置

第四步操作完成后,保存、编译,正常情况下是没有报错的。

点击菜单栏中的Pin Planner,按钮:

弹出管脚分配页面,在这里把实际原理图中连接W25Q128的那几个引脚配置好。我这里用的是:

所以配置好以后如图(记得修改管脚电平):

然后关闭Pin Planner页面,保存工程,再次编译。

编译成功!

六、修改软核程序

打开NIOS II,用新生成的软核创建新的工程,之前说过,不解释。如果当前打开工程较多,创建完成后,可以互相关联一下相关的BSP和工程。然后编译一下,确认创建的工程正常。

6.1 添加spi flash的驱动包

这个驱动程序是我之前在github上下载的,很好用。直接粘贴到软件工程所在的路径下:

NIOS II软件中,右键工程,点击刷新,可以看到该文件夹:

但是这样还不行,系统不知道这个文件夹所在路径,需要手动添加一下。右键工程,选中Properties,如图添加驱动程序所在路径:

弹出的对话框选YES:

然后点OK,关闭Properties页面。

6.2 修改驱动程序

主要是修改w25qxx.c这个文件,修改里面的SPI收发函数,适配整体。增加了片选的宏定义:

#define W25Q_CS_L IOWR_ALTERA_AVALON_SPI_CONTROL(SPI_FLASH_BASE, ALTERA_AVALON_SPI_CONTROL_SSO_MSK)

#define W25Q_CS_H IOWR_ALTERA_AVALON_SPI_CONTROL(SPI_FLASH_BASE, 0)

调整里NIOS II里面自带的SPI驱动函数,适配flash的操作:

int user_avalon_spi_command(alt_u32 base, alt_u32 slave,
                           alt_u32 write_length, const alt_u8 * write_data,
                           alt_u32 read_length, alt_u8 * read_data,
                           alt_u32 flags)
{
  const alt_u8 * write_end = write_data + write_length;
  alt_u8 * read_end = read_data + read_length;

  alt_u32 write_zeros = read_length;
  alt_u32 read_ignore = write_length;
  alt_u32 status;

  /* We must not send more than two bytes to the target before it has
   * returned any as otherwise it will overflow. */
  /* Unfortunately the hardware does not seem to work with credits > 1,
   * leave it at 1 for now. */
  alt_32 credits = 1;

  /* Warning: this function is not currently safe if called in a multi-threaded
   * environment, something above must perform locking to make it safe if more
   * than one thread intends to use it.
   */

  IOWR_ALTERA_AVALON_SPI_SLAVE_SEL(base, 1 << slave);

  /* Set the SSO bit (force chipselect) only if the toggle flag is not set */
  /*
   * Discard any stale data present in the RXDATA register, in case
   * previous communication was interrupted and stale data was left
   * behind.
   */
  IORD_ALTERA_AVALON_SPI_RXDATA(base);

  /* Keep clocking until all the data has been processed. */
  for ( ; ; )
  {

    do
    {
      status = IORD_ALTERA_AVALON_SPI_STATUS(base);
    }
    while (((status & ALTERA_AVALON_SPI_STATUS_TRDY_MSK) == 0 || credits == 0) &&
            (status & ALTERA_AVALON_SPI_STATUS_RRDY_MSK) == 0);

    if ((status & ALTERA_AVALON_SPI_STATUS_TRDY_MSK) != 0 && credits > 0)
    {
      credits--;

      if (write_data < write_end)
        IOWR_ALTERA_AVALON_SPI_TXDATA(base, *write_data++);
      else if (write_zeros > 0)
      {
        write_zeros--;
        IOWR_ALTERA_AVALON_SPI_TXDATA(base, 0);
      }
      else
        credits = -1024;
    };

    if ((status & ALTERA_AVALON_SPI_STATUS_RRDY_MSK) != 0)
    {
      alt_u32 rxdata = IORD_ALTERA_AVALON_SPI_RXDATA(base);

      if (read_ignore > 0)
        read_ignore--;
      else
        *read_data++ = (alt_u8)rxdata;
      credits++;

      if (read_ignore == 0 && read_data == read_end)
        break;
    }

  }

  /* Wait until the interface has finished transmitting */
  do
  {
    status = IORD_ALTERA_AVALON_SPI_STATUS(base);
  }
  while ((status & ALTERA_AVALON_SPI_STATUS_TMT_MSK) == 0);

  /* Clear SSO (release chipselect) unless the caller is going to
   * keep using this chip
   */

  return read_length;
}

对应的数据发送和接收:

uint8_t	W25qxx_Spi(uint8_t	Data)
{
	uint8_t	ret;
	user_avalon_spi_command(SPI_FLASH_BASE, 0,
	                           1, &Data,
	                           0, &ret,
	                           0);
	return ret;
}
uint8_t	W25qxx_Spi_r(uint8_t	Data)
{
	uint8_t	ret;
	user_avalon_spi_command(SPI_FLASH_BASE, 0,
	                           0, &Data,
	                           1, &ret,
	                           0);
	return ret;
}

主要就这些,还有些细节注意下就行了。

七、编写测试程序

测试程序就比较简单了,在主程序中定义两个数组,一个用来存放写入的数据,一个用来存放读出的数据,然后打印出来,看对不对。

注意,写之前要先擦除。

#include <stdio.h>
#include "system.h"
#include "unistd.h"
#include "w25q/w25qxx.h"
alt_u8 w25_buffer[256];
alt_u8 buf_cnt = 0;
alt_u8 re_buffer[256];
int main()
{
	W25qxx_Init();
	W25qxx_EraseSector(0);
	for(buf_cnt = 0 ; buf_cnt < 255 ; buf_cnt++)
	{
		w25_buffer[buf_cnt] = buf_cnt;
		re_buffer[buf_cnt] = 0;
	}
	W25qxx_WritePage(w25_buffer,0 ,0 , 256);
	usleep(500);
	W25qxx_ReadPage(re_buffer,0,0,256);
	printf("Hello from Nios II!\n");
	//
	printf("re_buffer value is:\n");
	for(buf_cnt = 0 ; buf_cnt < 255 ; buf_cnt++)
	{
		//
		printf("%d ",re_buffer[buf_cnt]);
	}
  	return 0;
}

依次下载sof文件和编译后的C文件,接上串口,效果如下:

效果OK,打完收工!我是单片机爱好者-MCU起航!

发表评论

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理