PotatoPie 4.0 实验教程(43) —— FPGA实现RISC-V 扩展SPI功能-Anlogic-安路社区-FPGA CPLD-ChipDebug

PotatoPie 4.0 实验教程(43) —— FPGA实现RISC-V 扩展SPI功能

手机扫码

20240416075513933-1713225291635

链接直达

https://item.taobao.com/item.htm?ft=t&id=776516984361

工程介绍

该工程主要完成两个功能,一个是检查FLASH的ID以确认SPI读写操作正常;另一个对写入FLASH指定区域的数据进行读出并比较结果,以确认写入正常。

代码分析

头文件、自定义常量及全局变量

这些头文件包含了程序所需的各种函数和定义,包括 UART、GPIO、中断、SPI以及SPI FLASH等的相关操作库。

#include "core.h"
#include "uart.h"
#include "gpio.h"
#include "anl_printf.h"
#include "interrupt.h"
#ifdef USE_MTIME
    #include "mtime.h"
#else
    #include "systick.h"
#endif
#include "spi.h"
#include "spi_flash.h"

static char sys_banner[] = {"- Anlogic eMCU buildtime [" __TIME__" " __DATE__ "] " "rev 1.0 \r\n"};

这行代码定义了一个静态字符数组 sys_banner,用于存储系统的横幅信息。该信息包含了编译时间和日期,以及版本号。

  • __TIME__:编译时的时间,格式为 HH:MM:SS。
  • __DATE__:编译时的日期,格式为 MMM DD YYYY(月份、日期、年份)。

这两个宏是编译器提供的预定义宏,在编译时会被替换为当前的编译时间和日期。在这里,它们被用于构建一个包含编译时间和日期的字符串。

整个 sys_banner 的格式是一个包含编译信息的字符串,形如:- Anlogic eMCU buildtime [编译时间 编译日期] rev 1.0 \r\n

#define SYS_FREQ 80000000
#define UART_BAUD 115200
#define GPIO_INTSRC 0x04
#define UART1_INTSRC 0x01
#define TIM_TRIG_FREQ 1

这些常量定义了系统频率、UART 波特率、GPIO 中断源等信息。

  1. SYS_FREQ:系统时钟频率,值为 80000000,即 80MHz。
  2. UART_BAUD:UART 通信的波特率,值为 115200。
  3. GPIO_INTSRC:GPIO 中断源,值为 0x04。
  4. UART1_INTSRC:UART1 中断源,值为 0x01。
  5. TIM_TRIG_FREQ:定时器触发频率,值为 1,表示每秒触发一次定时器中断。

主函数

// 配置 UART 参数
    Uart_Config UART1_Cfg;
    UART1_Cfg.BaudDivider=(SYS_FREQ/UART_BAUD);
    UART1_Cfg.IntEnable=True;
    UART1_Cfg.Event_ParityCheckFail=False;
    UART1_Cfg.Event_RxFifoHalfFull=False;
    UART1_Cfg.Event_TxFifoHalfEmpty=False;
	UART1_Cfg.Event_RxBufFull=True;
	UART1_Cfg.Event_TxBufEmpty=False;
	UART1_Cfg.RxFifoEnable=False;
	UART1_Cfg.TxFifoEnable=True;
	UART1_Cfg.Parity=NONE;
	UART1_Cfg.Stop=ONE;
	uart_applyConfig(UART,&UART1_Cfg);

这段代码是配置 UART 参数的过程,具体的配置包括:

  • BaudDivider:波特率分频器,根据系统时钟频率 SYS_FREQ 和波特率 UART_BAUD 计算得出。
  • IntEnable:使能 UART 中断。
  • Event_ParityCheckFail:奇偶校验失败事件的处理,设置为 False 表示不处理。
  • Event_RxFifoHalfFull:接收 FIFO 缓冲区半满事件的处理,设置为 False 表示不处理。
  • Event_TxFifoHalfEmpty:发送 FIFO 缓冲区半空事件的处理,设置为 False 表示不处理。
  • Event_RxBufFull:接收缓冲区满事件的处理,设置为 True 表示处理。
  • Event_TxBufEmpty:发送缓冲区空事件的处理,设置为 False 表示不处理。
  • RxFifoEnable:使能接收 FIFO 缓冲区,设置为 False 表示不使用 FIFO 缓冲区。
  • TxFifoEnable:使能发送 FIFO 缓冲区,设置为 True 表示使用 FIFO 缓冲区。
  • Parity:奇偶校验类型,设置为 NONE 表示不使用奇偶校验。
  • Stop:停止位数,设置为 ONE 表示一个停止位。

最后,通过 uart_applyConfig 函数将以上配置应用到 UART 上。

Spi_init(SPI,0x000f); //polling-based SPI,1/16 of sysclk

这行代码是用于初始化 SPI 接口:

  • Spi_init(SPI,0x000f);
    • 这是一个函数调用,用于初始化 SPI 接口。
    • 第一个参数 SPI 表示要初始化的 SPI 接口。在这里,SPI 是SPI控制器的地址,代表了某个具体的 SPI 接口或 SPI 控制器的实例。
    • 第二个参数 0x000f 是一个配置参数,用于配置 SPI 接口的工作模式和参数。在这里,0x000f 是一个掩码值,用于设置 SPI 的工作模式和其他配置选项,主要配置了这两项,具体寄存器位的定义可以参见文档。这里主要定义了SPI 工作在polling-based 轮询(polling)模式,也就是说,它不使用中断或 DMA 进行数据传输,而是通过主动查询的方式进行数据传输;另外还定义了SPI 接口的时钟频率是系统时钟频率(SYSCLK)的 1/16。
anl_printf("---------------------------------------------------------------- \r\n");
    anl_printf("- SoftCore Demo     : RISCV(Freq 80MHz. 32kB RAM. Full Feature)  \r\n");
    anl_printf("- Hardware Platform : EG4S20NG88     PotatoPie V4.0              \r\n");
    anl_printf("- TD Ver.           : TD 5.6.4(97693)                            \r\n");
    anl_printf("- OS+Build+Debug    : Windows, OpenOCD + Riscv-none-embed.       \r\n");
    anl_printf("- Test Case         : UART & GPIO Interrupt, SPI Master Demo     \r\n");
    anl_printf(sys_banner);
    anl_printf("---------------------------------------------------------------- \r\n");

这段代码使用 anl_printf 函数打印了一段系统信息。让我们来逐行解释:

  • anl_printf("---------------------------------------------------------------- \r\n");

    打印了一条分隔线,用于美观和区分信息的开头。

  • anl_printf("- SoftCore Demo : RISCV(Freq 80MHz. 32kB RAM. Full Feature) \r\n");

    打印了系统的软核演示信息,包括软核类型(RISCV)、频率(80MHz)、RAM 大小(32kB)和功能(全功能)。

  • anl_printf("- Hardware Platform : EG4S20NG88 PotatoPie V4.0 \r\n");

    打印了硬件平台信息,包括硬件平台型号(EG4S20NG88)和版本(PotatoPie V4.0)。

  • anl_printf("- TD Ver. : TD 5.6.4(97693) \r\n");

    打印了测试开发工具的版本信息,包括版本号(TD 5.6.4)和编译号(97693)。

  • anl_printf("- OS+Build+Debug : Windows, OpenOCD + Riscv-none-embed. \r\n");

    打印了操作系统、构建环境和调试工具的信息,包括操作系统类型(Windows)和使用的调试工具(OpenOCD + Riscv-none-embed)。

  • anl_printf("- Test Case : UART & GPIO Interrupt, SPI Master Demo \r\n");

    打印了测试用例的信息,包括测试的内容(UART & GPIO 中断、SPI 主控演示)。

  • anl_printf(sys_banner);

    打印了之前定义的 sys_banner 字符串,其中包含了编译时间和日期的信息。

  • anl_printf("---------------------------------------------------------------- \r\n");

    再次打印了一条分隔线,用于美观和区分信息的结尾。

//Detect The existence of Flash using JEDEC 0x9F command
    //Note: Cypress/Ateml/Hynix does not support JEDEC ID
    jedec_id=flash_read_jedec_id();
    if(jedec_id==0 || jedec_id==0x00FFFFFF)
    	anl_printf("Error! Flash JEDEC ID did not detected! Proceed anyway\r\n");
    else
    	anl_printf("Flash JEDEC ID Found! JEDEC ID: 0x%x\r\n",jedec_id);
    anl_printf("Original: ");
winbond_dump_scrs();

这段代码的作用是检测并读取 Flash 的 JEDEC ID,并打印相关信息。我解释一下:

  • jedec_id=flash_read_jedec_id();:调用 flash_read_jedec_id() 函数读取 Flash 的 JEDEC ID,并将其存储在变量 jedec_id 中。
  • if(jedec_id==0 || jedec_id==0x00FFFFFF):检查 JEDEC ID 是否为零或者 0x00FFFFFF。这些值可能表示未检测到 Flash 设备或者设备出现异常。
    • 如果 JEDEC ID 等于零或者 0x00FFFFFF,则打印错误信息,表示未检测到 Flash 设备。
    • 否则,表示成功检测到 Flash 设备,执行 else 分支。
  • anl_printf("Error! Flash JEDEC ID did not detected! Proceed anyway\r\n");:打印错误信息,表示未检测到 Flash 设备,但仍然继续执行后续操作。
  • anl_printf("Flash JEDEC ID Found! JEDEC ID: 0x%x\r\n",jedec_id);:如果成功检测到 Flash 设备,则打印 Flash 的 JEDEC ID。
  • anl_printf("Original: ");winbond_dump_scrs();:打印 “Original: ” 字符串,并调用 winbond_dump_scrs() 函数打印 Flash 的状态寄存器信息。

注意 Cypress/Ateml/Hynix 不支持 JEDEC ID 指令,我们板上的winbond flash是支持这个指令的。

anl_printf("Test R/W sector <16MB Region at 24bit mode\r\n");
//    winbond_exit_32b_mode();
    winbond_dump_scrs();
    Flash_RwTest(0x0100000); //@1MiB

这段代码执行了一系列操作来测试在24位模式下读写Flash的一个扇区,让我逐行解释:

  • anl_printf("Test R/W sector <16MB Region at 24bit mode\r\n");:打印一条消息,说明接下来将在24位模式下测试读写Flash的一个扇区。
  • winbond_dump_scrs();:调用 winbond_dump_scrs() 函数打印Flash的状态寄存器信息,这可能是为了确保Flash处于正确的工作模式。
  • Flash_RwTest(0x0E00000);:调用 Flash_RwTest 函数来执行Flash的读写测试。传递的参数 0x0100000 表示要测试的Flash地址,这里即为是1MiB的地址,即1 * 1024 * 1024字节处的地址。

Flash_RwTest

void Flash_RwTest(unsigned int address){
    unsigned int i;
    const unsigned int buf_size = 512;
    unsigned short tx_buf[buf_size];
    for(i=0;i<buf_size;i++)
    	tx_buf[i]=(unsigned short)i;
#if 1
    if(flash_chk_empty(address,65536)){
    	anl_printf("Page not empty! We needs to erase page first!\r\n");
        winbond_flash_write_enable();
    	winbond_flash_64k_erase(address);
    	//anl_printf("SCR1=0x%x\t",winbond_flash_get_scrs(1));
    	winbond_flash_wait_busy();
    	//anl_printf("SCR1=0x%x\t",winbond_flash_get_scrs(1));
    }
    else{
    	anl_printf("Page already empty! proceed directly write!\r\n");
    }
#endif
    for(i=0;i<(sizeof(tx_buf)/256);i++){
        winbond_flash_write_enable();
    	flash_sector_program(address+(i<<8),(unsigned char *)tx_buf+(i<<8),256);
    	//anl_printf("SCR1=0x%x\t",winbond_flash_get_scrs(1));
    	winbond_flash_wait_busy();
    	//anl_printf("SCR1=0x%x\r\n",winbond_flash_get_scrs(1));
    	if(flash_verify(address+(i<<8),(unsigned char *)tx_buf+(i<<8),256)){
    		anl_printf("FATAL:Sector%d verify failed @0x%x!\r\n",i,address+(i<<8));
    		flash_dump_page(address+(i<<8),256);
    		while(1);
    	}
    	else{
    		//anl_printf("Sector%d verify OK!\r\n",i);
    	}
    }
    anl_printf("Program finished, start full page verify\r\n");
	if(flash_verify(address,(unsigned char *)tx_buf,buf_size)){
		anl_printf("FATAL:verify failed!\r\n");
	}
	else{
		for(i=0;i<(sizeof(tx_buf)/256);i++)
			flash_dump_page(address+(i<<8),256);
		anl_printf("Verify OK!\r\n");
	}
}

这段代码定义了一个名为 Flash_RwTest 的函数,用于执行 Flash 的读写测试。让我们逐行解释其功能:

  1. unsigned int i;:声明一个无符号整数变量 i,用作循环计数器。
  2. const unsigned int buf_size = 512;:定义一个常量 buf_size,表示缓冲区的大小,这里设为512个字节。
  3. unsigned short tx_buf[buf_size];:声明一个大小为 buf_size 的无符号短整型数组 tx_buf,用于存储要写入 Flash 的数据。
  4. for(i=0;i<(sizeof(tx_buf)/256);i++){:使用 for 循环遍历 tx_buf 数组的索引,从0开始,直到数组长度除以256。这个循环的目的是将数据写入 Flash。
  5. winbond_flash_write_enable();:调用函数使能 Flash 的写入功能。
  6. flash_sector_program(address+(i<<8),(unsigned char *)tx_buf+(i<<8),256);:调用函数将数据写入 Flash 的扇区中。参数包括要写入的地址(每个扇区256字节)、数据缓冲区中的数据。
  7. winbond_flash_wait_busy();:等待 Flash 写入操作完成。
  8. if(flash_verify(address+(i<<8),(unsigned char *)tx_buf+(i<<8),256)){:检查写入的数据是否与 Flash 中的数据相匹配,如果不匹配,说明写入失败。
  9. anl_printf("FATAL:Sector%d verify failed @0x%x!\r\n",i,address+(i<<8));:如果写入失败,则打印错误信息,并调用函数 flash_dump_page 打印出错的扇区内容,然后进入死循环。
  10. anl_printf("Program finished, start full page verify\r\n");:打印消息,表示写入操作完成,开始进行整页的验证。
  11. if(flash_verify(address,(unsigned char *)tx_buf,buf_size)){:检查整页的数据是否与写入的数据相匹配,如果不匹配,说明写入失败。
  12. anl_printf("FATAL:verify failed!\r\n");:如果验证失败,则打印错误信息。
  13. for(i=0;i<(sizeof(tx_buf)/256);i++):使用 for 循环遍历 tx_buf 数组的索引,从0开始,直到数组长度除以256。这个循环的目的是打印整页的内容,以便验证。
  14. anl_printf("Verify OK!\r\n");:打印消息,表示整页验证通过。

flash_dump_page

这段代码定义了一个名为 flash_dump_page 的函数,用于从 Flash 中读取一页数据并打印。让我逐行解释其功能:

  1. void flash_dump_page(unsigned int address,unsigned int length):函数接受两个参数,address 表示要读取的 Flash 地址,length 表示要读取的字节数。
  2. int cnt, temp;:声明两个整数变量 cnttemp,用作循环计数器和临时存储。
  3. flash_start_read(address);:调用函数开始从 Flash 中读取数据,传入要读取的地址。
  4. Spi_sendbyte(FLASH_SPI, 0xff);:通过 SPI 总线发送一个字节的数据(值为 0xFF),这可能是一个读取 Flash 的命令。
  5. for(cnt = 0; cnt < length; cnt++):使用 for 循环迭代读取 Flash 中的数据,直到达到指定的 length
  6. Spi_sendbyte(FLASH_SPI, 0xff);:再次通过 SPI 总线发送一个字节的数据(值为 0xFF),可能是为了触发 Flash 发送数据。
  7. anl_printf("%x\t", Spi_recvbyte(FLASH_SPI));:通过 SPI 总线接收 Flash 发送的一个字节的数据,并打印它。
  8. if (cnt % 16 == 15):如果已经打印了16个字节(即达到一行的末尾),则换行。
  9. flash_end_read();:读取完成后,调用函数结束 Flash 的读取操作。

winbond_dump_scrs

这段代码定义了一个名为 winbond_dump_scrs 的函数,用于打印 Flash 的状态寄存器。让我逐行解释其功能:

  1. void winbond_dump_scrs():函数声明,不接受任何参数。
  2. unsigned char i;:声明一个无符号字符型变量 i,用作循环计数器。
  3. for(i=1; i<=3; i++):使用 for 循环遍历 Flash 的状态寄存器,从编号为1到3的寄存器。
  4. anl_printf("SCR%d=0x%x\t", i, winbond_flash_get_scrs(i));:在循环中,调用 winbond_flash_get_scrs 函数获取第 i 号寄存器的值,并使用 anl_printf 函数打印寄存器的编号和值。
  5. anl_printf("\r\n");:在每次循环结束后,打印换行符,以便下一次打印从新的一行开始。

实验结果

我从们手册中可以查到JEDEC 指令9Fh的应该读出的数据是 EF 40 15。

20240426142406181-image

20240424153253666-image

从下图可以看到正功读取了板载FLASH的ID号,我们接着往下看还可以看到数据写入正常,校验通过,并且打印了写入的数据,与我们代码中设计的一致。

20240426133934137-image

请登录后发表评论

    没有回复内容