本帖最后由 jinglixixi 于 2023-2-26 16:37 编辑
#申请原创#
在HC32F4A0开发板上,配有8M字节Flash存储器W25Q64, 该存储器的ID标识为“EF16”,其页长度256字节,扇区长度为4096字节,块长度为64K字节。 使用它可以有效地扩展数据的存储容量,并可用它来构建字库或是图库,其电路如图1所示,相应的引脚连接关系见图2所示。
图1 W25Q64电路
图2 连接关系
为了便于学习和掌握它的使用方法,可通过厂家所配备的2个例程来进行,其名称为spi_write_read_flash和QSPI_Base,其中的spi_write_read_flash主要是用于解决以磁道为单位的擦除及读写问题,而QSPI_Base主要是解决多次读写的问题。 以spi_write_read_flash为例,此测试结果是由LED灯来指示,若顺利完成则以闪亮蓝色指示灯来表示,辅助则是以点亮红色指示灯来表示。
在测试过程一个比较奇特的现象是,若以J14和J19来供电,则只会见到红色指示灯被点亮;而若以J21和J25来供电,则会见到蓝色指示灯闪亮的效果,奇怪不?可千万不要因为它的神奇而勿怪了W25Q64的读写性能呦! 图3 点亮绿色指示灯
图4 点亮红色指示灯
此外,通过对主程序的观察可以发现,若是能通过校验的审核会因的设计而形成的擦写与读写操作被反复地执行,并以此过程的时间消耗来作为指示灯闪烁的延时。这样的处理其实是很不足取的,因为存储器的擦写是有一定寿命限定的,这样来消耗它是实在是一种性能的消耗与浪费。
其主程序的内容如下: int32_t main(void)
{
LL_PERIPH_WE(EXAMPLE_PERIPH_WE);
BSP_CLK_Init();
BSP_IO_Init();
BSP_LED_Init();
BSP_W25QXX_Init();
LL_PERIPH_WP(EXAMPLE_PERIPH_WP);
LoadData();
for (;;) {
(void)BSP_W25QXX_EraseSector(SPI_FLASH_ADDR);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
VerifyData();
ClearData();
BSP_LED_Toggle(LED_BLUE);
}
}
在同样效果的情况下,可改为如下的内容来实现,其内容为: int32_t main(void)
{
LL_PERIPH_WE(EXAMPLE_PERIPH_WE);
BSP_CLK_Init();
BSP_IO_Init();
BSP_LED_Init();
BSP_W25QXX_Init();
LL_PERIPH_WP(EXAMPLE_PERIPH_WP);
LoadData();
(void)BSP_W25QXX_EraseSector(SPI_FLASH_ADDR);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
VerifyData();
ClearData();
for (;;) {
BSP_LED_Toggle(LED_BLUE);
DDL_DelayMS(500);
}
}
为了便于后续的读写测试及图像显示,可以为它添加上一个彩色的LCD显示屏,其显示分辨率为160*80像素点,其连接形式如图5所示。 该显示屏与开发板的连接关系为: SCL---PE11 SDA---PE12 RST---PE13 DC ---PE14 CS ---PE15 BL ---VDD
图5 添加LCD显示
为了解其实现方法和过程,下面分成3个部分来详细地展开介绍。
在进行图像存储和再现时,是通过数组来把图像数据转存到FLASH存储器的。由于图像的数据量都比较大,因此要分批次来转存。
1)单扇区写入与图像显示 将数组中的数据转存到写入数组的函数为: void LoadDatap(uint32_t ti)
{
uint32_t i;
for (i = 0UL; i < SPI_FLASH_DATA_SIZE; i++) {
m_au8WriteData[i] = gImage_RW[ti+i];
}
}
相较于以前的图像显示函数LCD_ShowPicture(),需将其更改为函数LCD_ShowPicturep()。 原图像显示函数为: void LCD_ShowPicture(u16 x1,u16 y1,u16 x2,u16 y2)
{
int i;
LCD_Address_Set(x1,y1,x2,y2);
for(i=0;i<12800;i++)
{
LCD_WR_DATA8(gImage_RW[i*2]);
LCD_WR_DATA8(gImage_RW[i*2+1]);
}
}
更改后的图像显示函数为: void LCD_ShowPicturep(void)
{
int i;
for(i=0;i<1024;i++)
{
LCD_WR_DATA8(m_au8ReadData[i*2]);
LCD_WR_DATA8(m_au8ReadData[i*2+1]);
}
}
以一个扇区的数据实现图像再现的主程序为: #define SPI_FLASH_DATA_SIZE (1024UL * 2UL)
#define W25Q64_SECTOR_SIZE (1024UL * 4UL)
int32_t main(void)
{
uint32_t ti=0;
LL_PERIPH_WE(EXAMPLE_PERIPH_WE);
BSP_CLK_Init();
BSP_IO_Init();
BSP_LED_Init();
LED_Init();
BSP_W25QXX_Init();
LL_PERIPH_WP(EXAMPLE_PERIPH_WP);
Lcd_Init();
LCD_ShowPicture(0,0,159,79);
DDL_DelayMS(1000);
LCD_Clear(RED);
DDL_DelayMS(1000);
LCD_ShowString(20,10,"HC32F4A0 ",RED);
LCD_Address_Set (0,0,159,79);
SPI_FLASH_ADDR=0;
LoadDatap(ti);
(void)BSP_W25QXX_EraseSector(SPI_FLASH_ADDR);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
LCD_ShowPicturep();
SPI_FLASH_ADDR= SPI_FLASH_ADDR+ SPI_FLASH_DATA_SIZE;
ti= ti+ SPI_FLASH_DATA_SIZE;
LoadDatap(ti);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
LCD_ShowPicturep();
for (;;) {
BSP_LED_Toggle(LED_BLUE);
DDL_DelayMS(500);
}
}
2)单幅图像的显示处理 对于一幅160*80像素点的图像来说,要占用25600个字节空间,单是一个扇区是无法存储这样一幅图像的数据,完成要使用多个扇区来完成。 那一幅160*80像素点的16位色图像要需要几个扇区呢? 用需7个扇区,即160*80*2=25600/4096=6.25。 因此在进行数据读写的过程中,既有整扇区的处理,还需有剩余零散扇区的处理。
以多个扇区来完成单幅图像显示的程序为: LCD_Clear(RED);
LCD_Address_Set (0,0,159,79);
SPI_FLASH_ADDR=0;
for (j=0;j<6;j++) {
LoadDatap(ti);
(void)BSP_W25QXX_EraseSector(SPI_FLASH_ADDR);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
LCD_ShowPicturep();
SPI_FLASH_ADDR= SPI_FLASH_ADDR+ SPI_FLASH_DATA_SIZE;
ti= ti+ SPI_FLASH_DATA_SIZE;
LoadDatap(ti);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
LCD_ShowPicturep();
SPI_FLASH_ADDR= SPI_FLASH_ADDR+ SPI_FLASH_DATA_SIZE;
ti= ti+ SPI_FLASH_DATA_SIZE;
}
LoadDatap(ti);
(void)BSP_W25QXX_EraseSector(SPI_FLASH_ADDR);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
for(k=0;k<512;k++)
{
LCD_WR_DATA8(m_au8ReadData[k*2]);
LCD_WR_DATA8(m_au8ReadData[k*2+1]);
}
for (;;) {
BSP_LED_Toggle(LED_BLUE);
DDL_DelayMS(500);
}
图6 扇区读写的显示效果
图7 整幅图像再现
3)多幅图像的显示处理 以单幅图像显示为基础,要实现多幅图像的显示(数码相框功能),需要将图像数据的存储处理和读取再现功能相分离,这主要是受数组空间的影响,需分批次将一幅幅图像数据存储到FLASH存储器以构成图像库。 如何在通过读取处理将存储的多幅图像再现出来。
其主程序为: int32_t main(void)
{
uint32_t ti=0,j,k,a;
uint8_t F,N=3,M;
LL_PERIPH_WE(EXAMPLE_PERIPH_WE);
BSP_CLK_Init();
BSP_IO_Init();
BSP_LED_Init();
LED_Init();
BSP_W25QXX_Init();
LL_PERIPH_WP(EXAMPLE_PERIPH_WP);
Lcd_Init();
LCD_Clear(BLACK); //RED
LCD_ShowString(20,10,"HC32F4A0 ",RED);
LCD_ShowString(20,40,"Mini PHOTO ",YELLOW);
DDL_DelayMS(2000);
LCD_ShowPicture(0,0,159,79);
DDL_DelayMS(2000);
F=0; // F=0 PLAY ; F=1 RECORDE.
if(F) {
M=N*8;
SPI_FLASH_ADDR=M * W25Q64_SECTOR_SIZE; // 0 8 16 24
goto aaa; //RECORDE
}
// PLAY
a=0;
while(1)
{
SPI_FLASH_ADDR=a*8 * W25Q64_SECTOR_SIZE;
LCD_Address_Set (0,0,159,79);
for (j=0;j<6;j++) {
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
LCD_ShowPicturep();
SPI_FLASH_ADDR= SPI_FLASH_ADDR+ SPI_FLASH_DATA_SIZE;
DDL_DelayMS(1);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
LCD_ShowPicturep();
SPI_FLASH_ADDR= SPI_FLASH_ADDR+ SPI_FLASH_DATA_SIZE;
DDL_DelayMS(1);
}
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE/2);
DDL_DelayMS(1);
for(k=0;k<512;k++)
{
LCD_WR_DATA8(m_au8ReadData[k*2]);
LCD_WR_DATA8(m_au8ReadData[k*2+1]);
}
DDL_DelayMS(1);
DDL_DelayMS(2000);
//SPI_FLASH_ADDR=a*8 * W25Q64_SECTOR_SIZE;
// a=0 RW
// a=1 FJ
// a=2 HY
// a=3 YX
a=a+1;
if(a>N) // N=3
{
a=0; //SPI_FLASH_ADDR=0;
}
DDL_DelayMS(500);
}
for (;;) {
BSP_LED_Toggle(LED_BLUE);
DDL_DelayMS(500);
}
aaa: // RECORDE
LCD_Address_Set (0,0,159,79);
for (j=0;j<6;j++) {
LoadDatap(ti);
(void)BSP_W25QXX_EraseSector(SPI_FLASH_ADDR);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
LCD_ShowPicturep();
DDL_DelayMS(1);
SPI_FLASH_ADDR= SPI_FLASH_ADDR+ SPI_FLASH_DATA_SIZE;
ti= ti+ SPI_FLASH_DATA_SIZE;
LoadDatap(ti);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE);
LCD_ShowPicturep();
DDL_DelayMS(1);
SPI_FLASH_ADDR= SPI_FLASH_ADDR+ SPI_FLASH_DATA_SIZE;
ti= ti+ SPI_FLASH_DATA_SIZE;
}
LoadDatap(ti);
(void)BSP_W25QXX_EraseSector(SPI_FLASH_ADDR);
(void)BSP_W25QXX_Write(SPI_FLASH_ADDR, m_au8WriteData, SPI_FLASH_DATA_SIZE/2);
(void)BSP_W25QXX_Read(SPI_FLASH_ADDR, m_au8ReadData, SPI_FLASH_DATA_SIZE/2);
for(k=0;k<512;k++)
{
LCD_WR_DATA8(m_au8ReadData[k*2]);
LCD_WR_DATA8(m_au8ReadData[k*2+1]);
}
for (;;) {
BSP_LED_Toggle(LED_BLUE);
DDL_DelayMS(500);
}
}
在使用时,若进行浏览,则令F=1,并使用N来指定可供浏览的图片个数;若进行通讯存储,则令F=0,并以N来指定存储的位置,并将存的通讯数据存放到数组中以供读取。
相信通过前面的介绍,即使你所用的不再是W25Q64,也能以所介绍的思路和方法来解决它。
演示视频:
|
通过QSPI访问Flash储存器,快速存取数据,并在OLED显示屏上显示图片,整个流程介绍较为详细,实现效果较好。