手机扫码
链接直达
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 中断源等信息。
SYS_FREQ
:系统时钟频率,值为 80000000,即 80MHz。UART_BAUD
:UART 通信的波特率,值为 115200。GPIO_INTSRC
:GPIO 中断源,值为 0x04。UART1_INTSRC
:UART1 中断源,值为 0x01。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 的读写测试。让我们逐行解释其功能:
unsigned int i;
:声明一个无符号整数变量i
,用作循环计数器。const unsigned int buf_size = 512;
:定义一个常量buf_size
,表示缓冲区的大小,这里设为512个字节。unsigned short tx_buf[buf_size];
:声明一个大小为buf_size
的无符号短整型数组tx_buf
,用于存储要写入 Flash 的数据。for(i=0;i<(sizeof(tx_buf)/256);i++){
:使用for
循环遍历tx_buf
数组的索引,从0开始,直到数组长度除以256。这个循环的目的是将数据写入 Flash。winbond_flash_write_enable();
:调用函数使能 Flash 的写入功能。flash_sector_program(address+(i<<8),(unsigned char *)tx_buf+(i<<8),256);
:调用函数将数据写入 Flash 的扇区中。参数包括要写入的地址(每个扇区256字节)、数据缓冲区中的数据。winbond_flash_wait_busy();
:等待 Flash 写入操作完成。if(flash_verify(address+(i<<8),(unsigned char *)tx_buf+(i<<8),256)){
:检查写入的数据是否与 Flash 中的数据相匹配,如果不匹配,说明写入失败。anl_printf("FATAL:Sector%d verify failed @0x%x!\r\n",i,address+(i<<8));
:如果写入失败,则打印错误信息,并调用函数flash_dump_page
打印出错的扇区内容,然后进入死循环。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");
:如果验证失败,则打印错误信息。for(i=0;i<(sizeof(tx_buf)/256);i++)
:使用for
循环遍历tx_buf
数组的索引,从0开始,直到数组长度除以256。这个循环的目的是打印整页的内容,以便验证。anl_printf("Verify OK!\r\n");
:打印消息,表示整页验证通过。
flash_dump_page
这段代码定义了一个名为 flash_dump_page
的函数,用于从 Flash 中读取一页数据并打印。让我逐行解释其功能:
void flash_dump_page(unsigned int address,unsigned int length)
:函数接受两个参数,address
表示要读取的 Flash 地址,length
表示要读取的字节数。int cnt, temp;
:声明两个整数变量cnt
和temp
,用作循环计数器和临时存储。flash_start_read(address);
:调用函数开始从 Flash 中读取数据,传入要读取的地址。Spi_sendbyte(FLASH_SPI, 0xff);
:通过 SPI 总线发送一个字节的数据(值为 0xFF),这可能是一个读取 Flash 的命令。for(cnt = 0; cnt < length; cnt++)
:使用for
循环迭代读取 Flash 中的数据,直到达到指定的length
。Spi_sendbyte(FLASH_SPI, 0xff);
:再次通过 SPI 总线发送一个字节的数据(值为 0xFF),可能是为了触发 Flash 发送数据。anl_printf("%x\t", Spi_recvbyte(FLASH_SPI));
:通过 SPI 总线接收 Flash 发送的一个字节的数据,并打印它。if (cnt % 16 == 15)
:如果已经打印了16个字节(即达到一行的末尾),则换行。flash_end_read();
:读取完成后,调用函数结束 Flash 的读取操作。
winbond_dump_scrs
这段代码定义了一个名为 winbond_dump_scrs
的函数,用于打印 Flash 的状态寄存器。让我逐行解释其功能:
void winbond_dump_scrs()
:函数声明,不接受任何参数。unsigned char i;
:声明一个无符号字符型变量i
,用作循环计数器。for(i=1; i<=3; i++)
:使用for
循环遍历 Flash 的状态寄存器,从编号为1到3的寄存器。anl_printf("SCR%d=0x%x\t", i, winbond_flash_get_scrs(i));
:在循环中,调用winbond_flash_get_scrs
函数获取第i
号寄存器的值,并使用anl_printf
函数打印寄存器的编号和值。anl_printf("\r\n");
:在每次循环结束后,打印换行符,以便下一次打印从新的一行开始。
实验结果
我从们手册中可以查到JEDEC 指令9Fh的应该读出的数据是 EF 40 15。
从下图可以看到正功读取了板载FLASH的ID号,我们接着往下看还可以看到数据写入正常,校验通过,并且打印了写入的数据,与我们代码中设计的一致。
没有回复内容