一、前言
本节将介绍如何在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起航!