一、W25QXX系列和AT24CXX系列的简介

AT24C系列简介:
AT24C系列是由Microchip等公司生产的经典 EEPROM(带电可擦除可编程只读存储器)系列芯片。

  • 接口类型:使用 I2C 总线通信(仅需两根线:SDA和SCL)。
  • 容量范围:通常较小,常见型号如 AT24C02(2 Kb / 256 KB)至 AT24C512(512 Kb / 64 KB)。
  • 主要特点:支持字节级的独立擦写;读写寿命极高(通常可达100万次),数据保存期可长达100年。
  • 典型应用:主要用于保存少量的配置参数、校准数据、密码或设备序列号(例如:智能家居设备配网信息、温湿度传感器的校准值)。

W25Q系列简介:
W25Q系列是由华邦(Winbond)等公司生产的 串行 NOR Flash(闪存)系列芯片。

  • 接口类型:支持标准 SPI、双重 SPI(160MHz 等效频率)、四重 SPI(320MHz 等效频率),传输速率快。
  • 容量范围:容量较大,以兆比特(Mb)计算。常见型号如 W25Q16(16 Mb / 2 MB)、W25Q64(64 Mb / 8 MB)、W25Q128(128 Mb / 16 MB)。
  • 主要特点:写入前必须先进行 “擦除” 操作,且通常只能以块或扇区为单位进行擦除;存储密度高,成本低。
  • 典型应用:用于存储大容量的代码、文件或多媒体数据(例如:单片机/路由器固件、汉字字库、音频文件、图片资源或系统运行日志)。

二、W25QXX系列和AT24CXX系列的区别

AT24C 系列 EEPROM W25Q 系列 SPI Flash
存储器类型 EEPROM(电可擦除可编程只读存储器) NOR Flash(串行闪存)
常见型号 AT24C01 / 02 / 04 / 08 / 16 / 64 / 256 W25Q16 / 32 / 64 / 128 / 256
通信接口 I²C SPI / Dual SPI / Quad SPI
通信速度 100kHz~1MHz 几MHz~上百MHz
读数据方式 任意地址读取 任意地址读取
写数据方式 可直接覆盖写入 必须先擦除后写入
是否需要擦除 不需要 需要(最小擦除单位:4KB)
最小写入单位 1 Byte 1 Byte
页(Page)大小 8B、16B、32B、64B(不同型号不同) 256 Byte
扇区 / 块 4KB / 64KB
容量范围 128B ~ 512KB左右 2MB ~ 数百MB
数据修改效率 较低(涉及擦除)
数据读取速度 较慢 很快
数据写入速度 较慢 较快
擦写寿命 约100万次 约10万次
数据保存时间 约100年 约20年
地址空间 较小 非常大
功耗 较低 较低

常见容量对比

AT24C 系列 容量(bit) 容量(Byte)
AT24C01 1Kb 128B
AT24C02 2Kb 256B
AT24C04 4Kb 512B
AT24C08 8Kb 1KB
AT24C16 16Kb 2KB
AT24C32 32Kb 4KB
AT24C64 64Kb 8KB
AT24C128 128Kb 16KB
AT24C256 256Kb 32KB
AT24C512 512Kb 64KB
W25Q 系列 容量(Mbit) 容量(MB)
W25Q16 16Mb 2MB
W25Q32 32Mb 4MB
W25Q64 64Mb 8MB
W25Q128 128Mb 16MB
W25Q256 256Mb 32MB
W25Q512 512Mb 64MB

三、框图和主要指令码说明

  这里主要以W25QXX系列进行说明,AT24CXX系列可参考以下这篇文章:STM32驱动AT24C02存储模块

框图


框图重点:
  ① 存储器划分:整个存储空间,首先划分若干块,对于每个快,再划分为若干个扇区。对于整个空间,会划分为很多页,每页256字节。
    存储阵列组织:总容量 16MB(128Mb),采用分层结构:
    块(Block):256个块,每块 64KB。整片擦除(Chip Erase) 会擦除所有块。
    扇区(Sector):每块分为 16 个扇区,每个扇区 4KB。这是 最小的擦除单元。
    页(Page):每扇区分为 16 页,每页 256 字节。这是 最大的连续写入单元。

  ② SPI控制逻辑:芯片的管理员,执行指令,读写数据

  ③ 状态寄存器:与忙状态,写使能,写保护等功能有关

  ④ 256字节页缓存:对一次性写入的数据量产生限制

框图的读写执行流程:
  1. 写入流程:写入数据会先放在页缓存区存放着,写入时序结束后就会把缓存区的数据转移到Flash存储器里,在转移的过程中,芯片会进入一段忙状态,状态寄存器的BUSY位就会置1。在忙的时候,芯片不会响应新的读写时序。

为什么不直接往Flash存储器写入数据,而要经过缓存区再写入?
  因为SPI写入频率很快,Flash写入需要掉电不丢失,写入时比较慢,而缓存区是RAM,速度快,可以跟得上SPI总线速度。

  注意:缓存区只有256字节,写入的时序有限制,即写入一个时序,连续写入的数据量,不能超过256字节。

  2. 读取流程:通过缓存区读取,但读取只需要查看电路状态即可,基本不花时间,因此,读取的限制很少,速度也很快。

Flash操作注意事项
写入操作时:

  • 写入操作前,必须先进行写使能
  • 每个数据位只能由1改写为0,不能由0改写为1
  • 写入数据前必须先擦除,擦除后,所有数据位变为1
  • 擦除必须按最小擦除单元(一个扇区4KB)进行
  • 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

  • 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

主要指令码

  1. Write Enable(0x06):写使能,在执行任何修改操作前必须发送。

  2. Write Disable(0x04):写失能,保护性操作,防止误写。

  3. JEDEC ID(0x9F):读取厂家ID和设备ID。ID号如下:

  1. Read Data(0x03):读取数据,最常用的读取指令,需24位地址。

  2. Page Program(0x02):页编程,写入数据,需24位地址,最大256字节。

  3. Sector Erase(0x20):扇区擦除 (4KB),最常用的擦除指令,需24位地址。

  4. Block Erase (32KB)(0x52):块擦除 (32KB),擦除较大区块。

  5. Block Erase (64KB)(0xD8):块擦除 (64KB),擦除更大区块。

  6. Chip Erase(0xC7 / 0x60):整片擦除,擦除整个芯片,谨慎使用。

  7. Read Status Register-1(0x05):读状态寄存器1,必用:检查BUSY和WEL位。

  8. Read Status Register-2 (0x35):读状态寄存器2,检查其他状态位(如QE位)。

四、STM32F103驱动W25QXX系列 Flash存储模块

准备工作

STM32F103C8T6最小系统板,W25Q128 Flash存储模块,1.69寸彩色TFT显示屏,导线若干等。

接线说明

STM32F103C8T6 W25Q128
3V3 VCC
GND GND
PB12 CS
PB13 CLK
PB14 DO
PB15 DI
TFT显示屏
3V3 VCC
GND GND
PA0 SCL
PA1 SDA
PA2 RES
PA3 DC
PA4 CS
PA5 BLK

代码示例

W25QXX.c

#include "w25qxx.h"
#include "myspi.h"
#include "w25qxx_ins.h"

void W25QXX_Init(void)
{
	MySPI_Init();
}

void W25QXX_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25QXX_JEDEC_ID);
	*MID = MySPI_SwapByte(W25QXX_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25QXX_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25QXX_DUMMY_BYTE);
	MySPI_Stop();
}

void W25QXX_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25QXX_WRITE_ENABLE);
	MySPI_Stop();
}	

void W25QXX_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25QXX_READ_STATUS_REGISTER_1);
	
	Timeout = 5000;
	
	while((MySPI_SwapByte(W25QXX_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout --;
		if(Timeout == 0)
		{
			break;
		}
	}
	
	MySPI_Stop();
}

void W25QXX_SectorErase(uint32_t Address)
{
	W25QXX_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25QXX_BLOCK_ERASE_64KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25QXX_WaitBusy();
}

void W25QXX_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25QXX_WriteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25QXX_PAGE_PROGRAM);
	
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	
	for(i = 0; i < Count; i++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	
	MySPI_Stop();
	
	W25QXX_WaitBusy();
}

void W25QXX_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	
	MySPI_Start();
	MySPI_SwapByte(W25QXX_READ_DATA);
	
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	
	for(i = 0; i < Count; i++)
	{
		DataArray[i] = MySPI_SwapByte(W25QXX_DUMMY_BYTE);
	}
	
	MySPI_Stop();
}

void W25QXX_WriteData(uint32_t Address,uint8_t *DataArray,uint32_t Count)
{
    uint32_t PageRemain;

    while(Count > 0)
    {
        PageRemain = 256 - (Address % 256);

        if(Count < PageRemain)
        {
            PageRemain = Count;
        }

        W25QXX_PageProgram(Address, DataArray, PageRemain);

        Address += PageRemain;
        DataArray += PageRemain;
        Count -= PageRemain;
    }
}

void W25QXX_WritePicture(uint32_t FlashAddress,const uint8_t *pic,uint32_t Size)
{
    W25QXX_SectorErase(FlashAddress);

    W25QXX_WriteData(FlashAddress,(uint8_t *)pic,Size);
}

lcd.c

#include "lcd.h"
#include "lcd_init.h"
#include "lcdfont.h"
#include "delay.h"
#include "w25qxx.h"

#define PIC_BUF_SIZE 512

/******************************************************************************
      函数说明:在指定区域填充颜色
      入口数据:xsta,ysta   起始坐标
                xend,yend   终止坐标
								color       要填充的颜色
      返回值:  无
******************************************************************************/
void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color)
{          
	u16 i,j; 
	LCD_Address_Set(xsta,ysta,xend-1,yend-1);//设置显示范围
	for(i=ysta;i<yend;i++)
	{													   	 	
		for(j=xsta;j<xend;j++)
		{
			LCD_WR_DATA(color);
		}
	} 					  	    
}

/******************************************************************************
      函数说明:在指定位置画点
      入口数据:x,y 画点坐标
                color 点的颜色
      返回值:  无
******************************************************************************/
void LCD_DrawPoint(u16 x,u16 y,u16 color)
{
	LCD_Address_Set(x,y,x,y);//设置光标位置 
	LCD_WR_DATA(color);
} 


/******************************************************************************
      函数说明:画线
      入口数据:x1,y1   起始坐标
                x2,y2   终止坐标
                color   线的颜色
      返回值:  无
******************************************************************************/
void LCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color)
{
	u16 t; 
	int xerr=0,yerr=0,delta_x,delta_y,distance;
	int incx,incy,uRow,uCol;
	delta_x=x2-x1; //计算坐标增量 
	delta_y=y2-y1;
	uRow=x1;//画线起点坐标
	uCol=y1;
	if(delta_x>0)incx=1; //设置单步方向 
	else if (delta_x==0)incx=0;//垂直线 
	else {incx=-1;delta_x=-delta_x;}
	if(delta_y>0)incy=1;
	else if (delta_y==0)incy=0;//水平线 
	else {incy=-1;delta_y=-delta_y;}
	if(delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
	else distance=delta_y;
	for(t=0;t<distance+1;t++)
	{
		LCD_DrawPoint(uRow,uCol,color);//画点
		xerr+=delta_x;
		yerr+=delta_y;
		if(xerr>distance)
		{
			xerr-=distance;
			uRow+=incx;
		}
		if(yerr>distance)
		{
			yerr-=distance;
			uCol+=incy;
		}
	}
}


/******************************************************************************
      函数说明:画矩形
      入口数据:x1,y1   起始坐标
                x2,y2   终止坐标
                color   矩形的颜色
      返回值:  无
******************************************************************************/
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2,u16 color)
{
	LCD_DrawLine(x1,y1,x2,y1,color);
	LCD_DrawLine(x1,y1,x1,y2,color);
	LCD_DrawLine(x1,y2,x2,y2,color);
	LCD_DrawLine(x2,y1,x2,y2,color);
}


/******************************************************************************
      函数说明:画圆
      入口数据:x0,y0   圆心坐标
                r       半径
                color   圆的颜色
      返回值:  无
******************************************************************************/
void Draw_Circle(u16 x0,u16 y0,u8 r,u16 color)
{
	int a,b;
	a=0;b=r;	  
	while(a<=b)
	{
		LCD_DrawPoint(x0-b,y0-a,color);             //3           
		LCD_DrawPoint(x0+b,y0-a,color);             //0           
		LCD_DrawPoint(x0-a,y0+b,color);             //1                
		LCD_DrawPoint(x0-a,y0-b,color);             //2             
		LCD_DrawPoint(x0+b,y0+a,color);             //4               
		LCD_DrawPoint(x0+a,y0-b,color);             //5
		LCD_DrawPoint(x0+a,y0+b,color);             //6 
		LCD_DrawPoint(x0-b,y0+a,color);             //7
		a++;
		if((a*a+b*b)>(r*r))//判断要画的点是否过远
		{
			b--;
		}
	}
}

/******************************************************************************
      函数说明:显示汉字串
      入口数据:x,y显示坐标
                *s 要显示的汉字串
                fc 字的颜色
                bc 字的背景色
                sizey 字号 可选 16 24 32
                mode:  0非叠加模式  1叠加模式
      返回值:  无
******************************************************************************/
void LCD_ShowChinese(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	while(*s!=0)
	{
		if(sizey==12) LCD_ShowChinese12x12(x,y,s,fc,bc,sizey,mode);
		else if(sizey==16) LCD_ShowChinese16x16(x,y,s,fc,bc,sizey,mode);
		else if(sizey==24) LCD_ShowChinese24x24(x,y,s,fc,bc,sizey,mode);
		else if(sizey==32) LCD_ShowChinese32x32(x,y,s,fc,bc,sizey,mode);
		else return;
		s+=2;
		x+=sizey;
	}
}

/******************************************************************************
      函数说明:显示单个12x12汉字
      入口数据:x,y显示坐标
                *s 要显示的汉字
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值:  无
******************************************************************************/
void LCD_ShowChinese12x12(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 i,j,m=0;
	u16 k;
	u16 HZnum;//汉字数目
	u16 TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
	TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
	                         
	HZnum=sizeof(tfont12)/sizeof(typFNT_GB12);	//统计汉字数目
	for(k=0;k<HZnum;k++) 
	{
		if((tfont12[k].Index[0]==*(s))&&(tfont12[k].Index[1]==*(s+1)))
		{ 	
			LCD_Address_Set(x,y,x+sizey-1,y+sizey-1);
			for(i=0;i<TypefaceNum;i++)
			{
				for(j=0;j<8;j++)
				{	
					if(!mode)//非叠加方式
					{
						if(tfont12[k].Msk[i]&(0x01<<j))LCD_WR_DATA(fc);
						else LCD_WR_DATA(bc);
						m++;
						if(m%sizey==0)
						{
							m=0;
							break;
						}
					}
					else//叠加方式
					{
						if(tfont12[k].Msk[i]&(0x01<<j))	LCD_DrawPoint(x,y,fc);//画一个点
						x++;
						if((x-x0)==sizey)
						{
							x=x0;
							y++;
							break;
						}
					}
				}
			}
		}				  	
		continue;  //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
	}
} 

/******************************************************************************
      函数说明:显示单个16x16汉字
      入口数据:x,y显示坐标
                *s 要显示的汉字
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值:  无
******************************************************************************/
void LCD_ShowChinese16x16(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 i,j,m=0;
	u16 k;
	u16 HZnum;//汉字数目
	u16 TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
  TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
	HZnum=sizeof(tfont16)/sizeof(typFNT_GB16);	//统计汉字数目
	for(k=0;k<HZnum;k++) 
	{
		if ((tfont16[k].Index[0]==*(s))&&(tfont16[k].Index[1]==*(s+1)))
		{ 	
			LCD_Address_Set(x,y,x+sizey-1,y+sizey-1);
			for(i=0;i<TypefaceNum;i++)
			{
				for(j=0;j<8;j++)
				{	
					if(!mode)//非叠加方式
					{
						if(tfont16[k].Msk[i]&(0x01<<j))LCD_WR_DATA(fc);
						else LCD_WR_DATA(bc);
						m++;
						if(m%sizey==0)
						{
							m=0;
							break;
						}
					}
					else//叠加方式
					{
						if(tfont16[k].Msk[i]&(0x01<<j))	LCD_DrawPoint(x,y,fc);//画一个点
						x++;
						if((x-x0)==sizey)
						{
							x=x0;
							y++;
							break;
						}
					}
				}
			}
		}				  	
		continue;  //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
	}
} 


/******************************************************************************
      函数说明:显示单个24x24汉字
      入口数据:x,y显示坐标
                *s 要显示的汉字
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值:  无
******************************************************************************/
void LCD_ShowChinese24x24(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 i,j,m=0;
	u16 k;
	u16 HZnum;//汉字数目
	u16 TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
	TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
	HZnum=sizeof(tfont24)/sizeof(typFNT_GB24);	//统计汉字数目
	for(k=0;k<HZnum;k++) 
	{
		if ((tfont24[k].Index[0]==*(s))&&(tfont24[k].Index[1]==*(s+1)))
		{ 	
			LCD_Address_Set(x,y,x+sizey-1,y+sizey-1);
			for(i=0;i<TypefaceNum;i++)
			{
				for(j=0;j<8;j++)
				{	
					if(!mode)//非叠加方式
					{
						if(tfont24[k].Msk[i]&(0x01<<j))LCD_WR_DATA(fc);
						else LCD_WR_DATA(bc);
						m++;
						if(m%sizey==0)
						{
							m=0;
							break;
						}
					}
					else//叠加方式
					{
						if(tfont24[k].Msk[i]&(0x01<<j))	LCD_DrawPoint(x,y,fc);//画一个点
						x++;
						if((x-x0)==sizey)
						{
							x=x0;
							y++;
							break;
						}
					}
				}
			}
		}				  	
		continue;  //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
	}
} 

/******************************************************************************
      函数说明:显示单个32x32汉字
      入口数据:x,y显示坐标
                *s 要显示的汉字
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值:  无
******************************************************************************/
void LCD_ShowChinese32x32(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 i,j,m=0;
	u16 k;
	u16 HZnum;//汉字数目
	u16 TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
	TypefaceNum=(sizey/8+((sizey%8)?1:0))*sizey;
	HZnum=sizeof(tfont32)/sizeof(typFNT_GB32);	//统计汉字数目
	for(k=0;k<HZnum;k++) 
	{
		if ((tfont32[k].Index[0]==*(s))&&(tfont32[k].Index[1]==*(s+1)))
		{ 	
			LCD_Address_Set(x,y,x+sizey-1,y+sizey-1);
			for(i=0;i<TypefaceNum;i++)
			{
				for(j=0;j<8;j++)
				{	
					if(!mode)//非叠加方式
					{
						if(tfont32[k].Msk[i]&(0x01<<j))LCD_WR_DATA(fc);
						else LCD_WR_DATA(bc);
						m++;
						if(m%sizey==0)
						{
							m=0;
							break;
						}
					}
					else//叠加方式
					{
						if(tfont32[k].Msk[i]&(0x01<<j))	LCD_DrawPoint(x,y,fc);//画一个点
						x++;
						if((x-x0)==sizey)
						{
							x=x0;
							y++;
							break;
						}
					}
				}
			}
		}				  	
		continue;  //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
	}
}


/******************************************************************************
      函数说明:显示单个字符
      入口数据:x,y显示坐标
                num 要显示的字符
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值:  无
******************************************************************************/
void LCD_ShowChar(u16 x,u16 y,u8 num,u16 fc,u16 bc,u8 sizey,u8 mode)
{
	u8 temp,sizex,t,m=0;
	u16 i,TypefaceNum;//一个字符所占字节大小
	u16 x0=x;
	sizex=sizey/2;
	TypefaceNum=(sizex/8+((sizex%8)?1:0))*sizey;
	num=num-' ';    //得到偏移后的值
	LCD_Address_Set(x,y,x+sizex-1,y+sizey-1);  //设置光标位置 
	for(i=0;i<TypefaceNum;i++)
	{ 
		if(sizey==12)temp=ascii_1206[num][i];		       //调用6x12字体
		else if(sizey==16)temp=ascii_1608[num][i];		 //调用8x16字体
		else if(sizey==24)temp=ascii_2412[num][i];		 //调用12x24字体
		else if(sizey==32)temp=ascii_3216[num][i];		 //调用16x32字体
		else return;
		for(t=0;t<8;t++)
		{
			if(!mode)//非叠加模式
			{
				if(temp&(0x01<<t))LCD_WR_DATA(fc);
				else LCD_WR_DATA(bc);
				m++;
				if(m%sizex==0)
				{
					m=0;
					break;
				}
			}
			else//叠加模式
			{
				if(temp&(0x01<<t))LCD_DrawPoint(x,y,fc);//画一个点
				x++;
				if((x-x0)==sizex)
				{
					x=x0;
					y++;
					break;
				}
			}
		}
	}   	 	  
}


/******************************************************************************
      函数说明:显示字符串
      入口数据:x,y显示坐标
                *p 要显示的字符串
                fc 字的颜色
                bc 字的背景色
                sizey 字号
                mode:  0非叠加模式  1叠加模式
      返回值:  无
******************************************************************************/
void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 fc,u16 bc,u8 sizey,u8 mode)
{         
	while(*p!='\0')
	{       
		LCD_ShowChar(x,y,*p,fc,bc,sizey,mode);
		x+=sizey/2;
		p++;
	}  
}


/******************************************************************************
      函数说明:显示数字
      入口数据:m底数,n指数
      返回值:  无
******************************************************************************/
u32 mypow(u8 m,u8 n)
{
	u32 result=1;	 
	while(n--)result*=m;
	return result;
}


/******************************************************************************
      函数说明:显示整数变量
      入口数据:x,y显示坐标
                num 要显示整数变量
                len 要显示的位数
                fc 字的颜色
                bc 字的背景色
                sizey 字号
      返回值:  无
******************************************************************************/
void LCD_ShowIntNum(u16 x,u16 y,u16 num,u8 len,u16 fc,u16 bc,u8 sizey)
{         	
	u8 t,temp;
	u8 enshow=0;
	u8 sizex=sizey/2;
	for(t=0;t<len;t++)
	{
		temp=(num/mypow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				LCD_ShowChar(x+t*sizex,y,' ',fc,bc,sizey,0);
				continue;
			}else enshow=1; 
		 	 
		}
	 	LCD_ShowChar(x+t*sizex,y,temp+48,fc,bc,sizey,0);
	}
} 


/******************************************************************************
      函数说明:显示两位小数变量
      入口数据:x,y显示坐标
                num 要显示小数变量
                len 要显示的位数
                fc 字的颜色
                bc 字的背景色
                sizey 字号
      返回值:  无
******************************************************************************/
void LCD_ShowFloatNum1(u16 x,u16 y,float num,u8 len,u16 fc,u16 bc,u8 sizey)
{         	
	u8 t,temp,sizex;
	u16 num1;
	sizex=sizey/2;
	num1=num*100;
	for(t=0;t<len;t++)
	{
		temp=(num1/mypow(10,len-t-1))%10;
		if(t==(len-2))
		{
			LCD_ShowChar(x+(len-2)*sizex,y,'.',fc,bc,sizey,0);
			t++;
			len+=1;
		}
	 	LCD_ShowChar(x+t*sizex,y,temp+48,fc,bc,sizey,0);
	}
}

void LCD_ShowHexNum(u16 x, u16 y, u32 num, u8 len, u16 fc, u16 bc, u8 sizey)
{
    u8 i;
    u8 temp;
    u8 sizex;

    sizex = sizey / 2;

    for(i = 0; i < len; i++)
    {
        temp = (num >> ((len - i - 1) * 4)) & 0x0F;

        if(temp < 10)
        {
            LCD_ShowChar(x + i * sizex, y, temp + '0', fc, bc, sizey, 0);
        }
        else
        {
            LCD_ShowChar(x + i * sizex, y, temp - 10 + 'A', fc, bc, sizey, 0);
        }
    }
}


/******************************************************************************
      函数说明:显示图片
      入口数据:x,y起点坐标
                length 图片长度
                width  图片宽度
                pic[]  图片数组    
      返回值:  无
******************************************************************************/
void LCD_ShowPicture(u16 x,u16 y,u16 length,u16 width,const u8 pic[])
{
	u16 i,j;
	u32 k=0;
	LCD_Address_Set(x,y,x+length-1,y+width-1);
	for(i=0;i<length;i++)
	{
		for(j=0;j<width;j++)
		{
			LCD_WR_DATA8(pic[k*2]);
			LCD_WR_DATA8(pic[k*2+1]);
			k++;
		}
	}			
}

void LCD_ShowPictureFromW25Q_Fast(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint32_t FlashAddress)
{
    uint32_t TotalBytes;
    uint32_t ReadBytes = 0;
    uint16_t i;

    uint8_t Buffer[PIC_BUF_SIZE];

    TotalBytes = width * height * 2;

    LCD_Address_Set(x,y,x+width-1,y+height-1);

    while(ReadBytes < TotalBytes)
    {
        uint32_t CurrentRead;

        CurrentRead = TotalBytes - ReadBytes;

        if(CurrentRead > PIC_BUF_SIZE)
        {
            CurrentRead = PIC_BUF_SIZE;
        }

        W25QXX_ReadData(FlashAddress + ReadBytes,
                        Buffer,
                        CurrentRead);

        for(i = 0; i < CurrentRead; i++)
        {
            LCD_WR_DATA8(Buffer[i]);
        }

        ReadBytes += CurrentRead;
    }
}

main.c

#include "stm32f10x.h"                  // Device header
#include "w25qxx.h" 
#include "lcd_init.h"
#include "lcd.h"
#include "pic.h"
#include "delay.h"

uint8_t MID;
uint16_t DID;

int main(void)
{
	LCD_Init();//LCD初始化
	LCD_Fill(0,0,LCD_W,LCD_H,WHITE);
	
	W25QXX_Init();
	
	LCD_ShowString(72,20,"W25QXX",RED,WHITE,32,0);
	LCD_ShowString(25,54,"MID:",RED,WHITE,24,0);
	LCD_ShowString(120,54,"DID:",RED,WHITE,24,0);

	W25QXX_ReadID(&MID, &DID);
	
	LCD_ShowHexNum(80,54,MID,2,RED,WHITE,24);
	LCD_ShowHexNum(175,54,DID,4,RED,WHITE,24);

	LCD_ShowChinese(25,80,"优信电子",RED,WHITE,24,0);
	
	/*写完注释掉或者删除掉这段代码,文件大可分多次写入*/
//	W25QXX_WritePicture(0x000000, gImage_1, sizeof(gImage_1));	//写入图片
//	W25QXX_WritePicture(0x010000, gImage_2, sizeof(gImage_2));

	while(1)
	{
		LCD_Fill(20,120,260,260,WHITE);		//前图片大,后图片小,无法覆盖前显示的大图片,每次显示前先清屏对应区域
		LCD_ShowPictureFromW25Q_Fast(20,120,200,143,0x000000);
		Delay_ms(1000);
		LCD_Fill(20,120,260,260,WHITE);
		LCD_ShowPictureFromW25Q_Fast(20,120,200,135,0x010000);
		Delay_ms(1000);
	}
}

效果展示

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐