浦江一水 发表于 2024-9-27 11:55:42

基于 32G12K128 实验箱V9.62_实验之TFT彩屏显示全屏图片

基于 32G12K128 实验箱V9.62_实验之TFT彩屏显示全屏图片

本实验目的很简单,显示三幅全屏的图片。但是对于新手(高手请绕行)来说,实现过程并不简单。要面临一系列问题需要解决:首先,一幅320*240的彩色图片,占153600字节,存放在哪里?显然,仅有64K的MCU片内是无法存放的,只有存放在片外的Flash存储器中。实验箱配有W25Q40芯片,具有512K空间,可以存放三幅全屏图片的数据(三图先用STCAI-ISP工具,转为Bin文件)。既然启用了外部存储器,那么必然要先实现对存储器的读写功能吧?这就对W25Q40要有一个基本认识:它具有512K容量,分16个块,每块64K字节;共有128个扇区,每扇区4096字节,擦除是按扇区为单位进行的;一次读写最大字节数是按页进行的,每页256字节。对存储器的读写,总得有个操作界面、同时要考虑上位机如何将图片数据传输到Flash存储器中去、并且能观察读写是否正确吧?于是要有与PC机通讯的功能,这就要启用实验箱的UART2(RS232)功能了。当用上位机传输数据时总要有个指定地址和数据长度的对话操作吧?那么就涉及到实验箱键盘的对话使用了。当上位机传入数据后,要验证一下写入数据是否正确,总要有个读出来被查看的功能吧?假如数据不正确、或地址不对,总要有个擦除重新传输的功能吧?最后实现将图片数据传输到屏中依次显示出来吧?总之,其实显示三幅全屏的图片,实现并不简单,涉及到了多个方面。
本实验先设计一个简单的主菜单界面。启用实验箱矩阵键盘中的下面四个键,K4…K7。 K4键:上加K5键:下减K6键:右移K7键:OK输入地址时,16进制数据, 输入长度时,10进制数据。菜单第一项: PC<->W25Q40 进入与PC机通讯界面(类似官方DEMO实验),按K7键返回主菜单. 上位机使用STCAI-ISP软件串口功能, 连接实验箱UART2(RS232)接口通讯… 菜单第二项: FlashW25Qx 进入与PC机发送数据文件的界面……每次传输一幅图,需要输入存储地址。注意:第一幅图地址输入0x00000,第二幅图0x25800,第三幅图0x4B000,等待上位机数据发送。按K7键返回主菜单,或上位机发送完毕自动返回主菜单。此时,利用STCAI-ISP软件,串口发送文件功能。注意:选择数据包每次间隔50ms,让实验箱有时间擦除写入。 菜单第三项: Read W25Qx 可以指定地址查阅已存储在W25Q40中的数据……可用K4、K5键前后地址翻阅,按K7键返回主菜单. 菜单第四项: Del_Setctor可以指定地址删除一个扇区数据(4096字节)为了防止误删,有确认提示,默认是No! 不删除。K6移位键选择,K7确认返回。 菜单第五项: Del_Block可以指定地址删除一个块数据(64K字节),可加快大量数据扇区删除。为了防止误删,也有确认提示,默认是No! 不删除。K6移位键选择,K7确认返回。菜单第六项: Disp PICx 执行全屏彩色图片显示,按任意键继续,三幅图显示完毕,返回主菜单界面。
完整工程文件包, 供有兴趣坛友参考或指正:

浦江一水 发表于 2024-9-27 12:03:17

贴一下主文件

//=================================================================================================
// 文件名称: Main.C
// 功能说明: 测试液晶屏模块驱动函数 2.4寸彩色显示屏 240*320 8位并行口模式+ 测试通信以及W25Q40读写
// 基于电路: STC32G12K128 实验箱V9.62
// 整理改编: 浦晓明(浦江一水) 2024-09-14
// 实验内容: 实验全屏彩色图片显示

//**************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "STC32G.H"
#include "STC32G_SYS.H"
#include "STC32G_UART2.H"
#include "LCD9341.H"
#include "KEY8.H"
#include "W25Q40.H"//存储512K

//全局变量说明
unsigned char k;       //键值
unsigned char m,n;   //菜单项
char S;            //显示缓存
unsigned long ADDR = 0;//0x25800/0x4B000
unsigned long Length=153600;
u16 W25Q40_ID;      //设备ID
u32 T_Addr;            //目标地址
u8R_Buf;      //读取数据缓存
extern u8 xdata F_Buf; //扇区缓存

//菜单项目定义...
char * MN={ "0.PC<->W25Qx", "1.FlashW25Qx","2.Read W25Qx","3.Del_Sector","4.Del_Block ","5.Disp PICx ", };

//函数说明(凡是排列在main()之后的函数须先说明一下)
void PCRW_W25Q(void);//PC机通信实验W25Q读写
void FlashW25Q(void);   //PC机通信刷新扇区数据
void Read_W25Q(void);   //读出扇区数据-页256字节
void DelSector(void);       //删除指定扇区4096字节
void DelBlock(void);      //删除块数据(64K)
void Disp_PIC(void);       //显示图片

//=============================================================================
// 输入整型数 (10进制数显示)
//=============================================================================
unsigned long InputI(unsigned int X,unsigned int Y,char * I)
{ unsigned char set=1;
unsigned int i=0,Val=0,l;
l=strlen(I)-1;
while(set)
{ LCD_Str12(X,Y,I,7,0);
    LCD_A12(X+i*8,Y,I,14,4);
    k=0;k=GETCH();
    switch(k)
    { case K40 : I=I<0x39? I+1:0x30; break;
      case K50 : I=I>0x30? I-1:0x39; break;
      case K60 : i=i<l?i+1:0; break;
      case K61 : i=i>0?i-1:l; break;
      case K70 : LCD_Str12(X,Y,I,15,0); set=0; break;
    } if(i>l)i=0;
}
return(atol(I));
}
unsigned long Adr=0;
//=============================================================================
// 输入存储器入口地址 (16进制数显示)
//=============================================================================
unsigned long InputAdr(unsigned int X,unsigned int Y,char * F)
{ unsigned char set=1;
unsigned int i=0,val,l;
l=strlen(F)-1;
while(set)
{ LCD_Str12(X,Y,F,7,0);
    LCD_A12(X+i*8,Y,F,14,4);
    k=0;k=GETCH();
    switch(k)
    { case K40 : F=F<0x46? F+1:0x30; if(F==0x3A)F=0x41; break;
      case K50 : F=F>0x30? F-1:0x46; if(F==0x40)F=0x39; break;
      case K60 : i=i<l?i+1:0; break;//短按
      case K61 : i=i>0?i-1:l; break;//长按
      case K70 : LCD_Str12(X,Y,F,15,0); set=0; break;
    } if(i>l)i=0;
} Adr=0;
for(i=0;i<=l;i++)
{ val=F<0x3A?F-48:F-55;
    Adr=16*Adr+val;
}
return(Adr);
}

//=============================================================================
// 主函数
//=============================================================================
void main(void)
{ u16 i;
u8 disp=1;
SYS_Init();   //系统初始化(35MHz)
KEY8_Init();    //键盘初始化
LCD_Init();           //液晶屏初始化
//LCD_SetDIR(0);//原显示方向(纵向屏)
LCD_SetDIR(3);//调整显示方向(横屏)
LCD_CLS(0);   //清屏
W25Q40_Init();
W25Q40_ID=W25Q40_ReadID(); //读取ID(验证设备是否存在)
UART2_Init(1,1,115200);    //IO切换1,模式1,波特率115200
UART2_SendStr("STC32G12K128_UART2_OK!\r\n");
printf("Printf()_UART2_OK!\r\n");
m=0;
while(1)
{
    if(disp)
    { disp=0;
      LCD_CLS(0);            //清全屏
      LCD_CLSA(0,0,319,22,1);//局部清屏
      LCD_Str12(8,2,(char *)"Test TFT9341 W25Q40...",15,1);
      LCD_CLSA(0,220,319,20,7);
      for(i=0;i<6;i++)       //显示主菜单项
      { if(m==i)LCD_Str12(10,30+20*i,(char *)MN,14,4);
         else   LCD_Str12(10,30+20*i,(char *)MN,15,0);
      } LCD_Str12(10,222,"KEY=00",0,7);//显示按键值
    }
    k=0;k=GETCH();    //RDKEY();
    if(k!=0)
    { sprintf(S,"KEY=%02X",k);
      LCD_Str12(10,222,S,0,7);
      switch(k)
      { case K40: LCD_Str12(10,30+20*m,(char *)MN,15,0);
                  m=m>0?m-1:5;
                  LCD_Str12(10,30+20*m,(char *)MN,14,4); break;
      case K50: LCD_Str12(10,30+20*m,(char *)MN,15,0);
                  m=m<5?m+1:0;
                  LCD_Str12(10,30+20*m,(char *)MN,14,4); break;
      case K60: break;
      case K70: switch(m)
               { case 0: PCRW_W25Q(); disp=1; break;//实验与PC机通讯读写W25Q
                   case 1: FlashW25Q(); disp=1; break;//与PC机通讯刷新扇区数据(图片文件)
                   case 2: Read_W25Q(); disp=1; break;//读取W25Q扇区数据显示
                   case 3: DelSector(); disp=1; break;//删除W25Q指定扇区数据4096字节
                   case 4: DelBlock();disp=1; break;//删除W25Q指定块区数据64K字节
                   case 5: Disp_PIC();disp=1; break;//实验显示图片
               }
          break;
      }
    }
    delay_ms(20); //延时
}   
}
//PC机读写W25Q40(512K存储器)
void PCRW_W25Q(void)
{ unsigned char run=1,ts=1,ch,n;
u16 i,len;
LCD_CLSA(0,25,160,150,0);
LCD_Str12(10, 30,(char *)"PC<==>UART2+W25Q40 Read & Write...",10,0);
LCD_Str12(10,222,(char *)"Press K7 return...",0,7);
while(run)
{ k=0;k=RDKEY();
    if(k==K70)run=0;
    if(ts)
    { //实验操作提示...
      printf("\r\n** STC32G单片机对外部Flash_W25Q40读写实验(主频35MHz) **\r\n");
      printf("- 实验箱_USART2(RS232)串口连接PC机串口.波特率115200,8,1,N\r\n");
      printf("- PC机使用串口助手,串口命令例\xfd举如下:(无需回车换行)\r\n");   
      printf("E 0x000020            --> 删除\xfd字节地址0x000020所在扇区.\r\n");
      printf("W 0x000020 ABCDE12345 --> 写入地址0x000020字符串ABCDE12345\r\n");
      printf("R 0x000020 10         --> 读出地址0x000020指定长度字节.\r\n");   
      printf("Q 0x000000            --> 返回主菜单...\r\n");   
      printf("读写地址范围: 0x000000 -- 0x07FFFF (512K)\r\n\r\n");
      ts=0;
    }
    delay_ms(10);
    if(RX2_Cnt>=10)          //串口2收到指令信息...
    { for(i=0;i<8;i++)
      { if((RX2_Buf >= 'a') && (RX2_Buf <= 'z'))RX2_Buf -= 0x20; } //小写转大写
      if(((RX2_Buf=='E')||(RX2_Buf=='W')||(RX2_Buf=='R')||(RX2_Buf=='Q'))&&(RX2_Buf==' '))
      { //解析地址...
      ch=RX2_Buf-0x30; if(ch>9)ch=ch-0x07; T_Addr=(u32)ch;
      ch=RX2_Buf-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
      ch=RX2_Buf-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
      ch=RX2_Buf-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
      ch=RX2_Buf-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
      ch=RX2_Buf-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
      //解析指令
      switch(RX2_Buf)
      { case 'E': W25Q40_SectorErase(T_Addr);
                  printf("OK! Sector Erase.\r\n");
                  break;
          case 'W': n=RX2_Cnt-11;         //取指令数据长度
                  W25Q40_Write(T_Addr,RX2_Buf+11,n);
                  printf("OK! Write %2d Byte.\r\n",n);
                  break;
          case 'R': n=RX2_Cnt-11; len=0;//取指令数据长度
                  for(i=0;i<n;i++)      //取需要读取的数据长度
                  { ch=RX2_Buf;
                      if((ch>='0')&&(ch<='9'))len=10*len+RX2_Buf-'0';
                      else break;
                  }
                  if(len>256)len=256;
                  W25Q40_Read(T_Addr,R_Buf,len);
                  printf("OK! Read out %3d Bytes:\r\n",len);
                  for(i=0;i<len;i++)UART2_SendChar(R_Buf);
                  UART2_SendChar(0x0D); UART2_SendChar(0x0A);
                  break;
          case 'Q': n=RX2_Cnt-11; len=0; run=0; break;
      }
      } else { printf("指令错误!!\r\n");ts=1; }
      RX2_Cnt=0;RX2_Buf=0;   //计数清零
    }
}
}
// 刷新存储器...
void FlashW25Q(void)
{ unsigned int Dh,Dl,Cnt=0;
unsigned char wait=1;
LCD_CLSA(0,25,160,150,0);
LCD_Str12(10,30,(char *)"Address=0x00000 (<=0x7FF00)",10,0);
Dh=ADDR / 65536; Dl=ADDR % 65536;
sprintf(S,"%1X%04X",Dh,Dl);   //C251无法实现长整型数格式化字符串
ADDR=InputAdr(90,30,S);       //输入要写入存储器的首地址
LCD_Str12(10,55,(char *)"Length=153600",10,0);
Length=153600;                  //一幅320*240图片数据长度
S=Length/100000+'0';
S=(Length%100000)/10000+'0';
S=(Length%10000)/1000+'0';
S=(Length%1000)/100+'0';
S=(Length%100)/10+'0';
S=Length%10+'0';
S=0;
//sprintf(S,"%02d%06d",Dh,Dl);
Length=InputI(66,55,S);       //输入要写入存储器的首地址
LCD_Str12(10,80,(char *)"FlashSector Start...",11,0);
sprintf(S,"FlashSector=%03d",Cnt);
LCD_Str12(10,105,S,11,0);
while(wait)
{ k=0;k=RDKEY();
    if(k==K70)wait=0;
    if(RX2_TimeOut>0)                                 //如果已经有接收到字节
    { if(--RX2_TimeOut == 0)                        //超时计数减1等于0了:接收到一包数据了
      { if(RX2_Cnt>0)W25Q40_Write(ADDR,RX2_Buf,RX2_Cnt);//将数据写入制定地址的扇区中
      Cnt++;sprintf(S,"%03d",Cnt);            //接收计数++
      LCD_Str12(106,105,S,15,0);                   //显示已经刷入的扇区(256字节)数
      ADDR=ADDR+RX2_Cnt;                        //目标地址递增
      RX2_Cnt=0; //计数清零            
      }
    }
    if(Cnt>=(Length/256))wait=0;                   //如果接收包数到达预定数,结束返回
}
LCD_Str12(10,130,"Flash End.",15,0);
GETCH();      //按任意键继续...
delay_ms(500);//延时
}
//读一页数据
void Read_W25Q(void)
{ unsigned int i,Dh,Dl;
unsigned char loop=1;
LCD_CLSA(0,25,160,150,0);
LCD_Str12(10,30,(char *)"Address=0x00000(<=0x7FF00)",10,0);
Dh=ADDR / 65536; Dl=ADDR % 65536;
sprintf(S,"%1X%04X",Dh,Dl);
ADDR=InputAdr(90,30,S);   //输入要读出存储器的首地址
ADDR &= 0x7FF00;          //转页(256字节)地址
LCD_CLS(0);
while(loop)
{ Dh=ADDR / 65536; Dl=ADDR % 65536;
    sprintf(S,"ADDR=0x%01X%04X",Dh,Dl);
    LCD_Str12(0,0,S,10,0);
    W25Q40_Read(ADDR,RX2_Buf,256);
    for(i=0;i<256;i++)
    { sprintf(S,"%02X ",(unsigned int)RX2_Buf);
      LCD_Str12((i%16)*20,16+(i/16)*14,S,15,0);
    }
    k=0;k=GETCH();   
    switch(k)   //长按或短按都可以...
    { case K40: case K41: ADDR=ADDR>0xFF?ADDR-256:0x7FF00; break;
      case K50: case K51: ADDR=ADDR<0x7FF00?ADDR+256:0;   break;
      case K70: case K71: loop=0; break;         
    }
    delay_ms(10); //延时
}
}
//删除扇区4096字节数据
void DelSector(void)
{ unsigned int Dh,Dl;
unsigned char set=1,OK=0;
LCD_CLSA(0,25,160,150,0);
LCD_Str12(10,30,(char *)"Address=0x00000(<=0x7F000)",10,0);
Dh=ADDR / 65536; Dl=ADDR % 65536;
sprintf(S,"%1X%04X",Dh,Dl);
ADDR=InputAdr(90,30,S);   //输入要读出存储器的首地址
ADDR &= 0x7F000;          //转扇区(4096字节)地址
LCD_Str12(10,60,(char *)"Del Sector ??!",12,0);
while(set)
{ if(OK){ LCD_Str12(10,90,(char *)"NO!",15,0);LCD_Str12(50,90,(char *)"YES",14,4); }
    else{ LCD_Str12(10,90,(char *)"NO!",14,4);LCD_Str12(50,90,(char *)"YES",15,0); }
    k=0;k=GETCH();
    switch(k)
    { case K60: OK=OK<1?1:0; break;
      case K70: set=0;       break;
    }
}
if(OK)
{ W25Q40_SectorErase(ADDR);
    LCD_Str12(10,120,(char *)"Del Sector OK!",14,0);
    delay_ms(1000); //延时
}
}
//删除块区64K字节数据
void DelBlock(void)
{ unsigned int Dh,Dl;
unsigned char set=1,OK=0;
LCD_CLSA(0,25,160,150,0);
LCD_Str12(10,30,(char *)"Address=0x00000(<=0x70000)",10,0);
Dh=ADDR / 65536; Dl=ADDR % 65536;
sprintf(S,"%1X%04X",Dh,Dl);
ADDR=InputAdr(90,30,S);   //输入要读出存储器的首地址
ADDR &= 0x70000;          //转扇区(256字节)地址
LCD_Str12(10,60,(char *)"Del Block ??!",12,0);
while(set)
{ if(OK){ LCD_Str12(10,90,(char *)"NO!",15,0);LCD_Str12(50,90,(char *)"YES",14,4); }
    else{ LCD_Str12(10,90,(char *)"NO!",14,4);LCD_Str12(50,90,(char *)"YES",15,0); }
    k=0;k=GETCH();
    switch(k)
    { case K60: OK=OK<1?1:0; break;
      case K70: set=0;       break;
    }
}
if(OK)
{ W25Q40_BlockErase(ADDR);
    LCD_Str12(10,120,(char *)"Del Sector OK!",14,0);
    delay_ms(1000); //延时
}
}
//显示全屏彩色图片
void DispBMP(unsigned long Addr)
{ unsigned int i,j,Col;
unsigned char h,l;
LCD_SetWindow(0,0,319,239);//设置显示窗口
for(i=0;i<600;i++)
{ W25Q40_Read(Addr,RX2_Buf,256);
    for(j=0;j<128;j++)
    { h=RX2_Buf;l=RX2_Buf;
      Col=256*h+l;             //图像数据:高位在前.
      LCD_WR_DAT_16Bit(Col);
    }
    Addr=Addr+256;
}
}
//显示BMP图片...
void Disp_PIC(void)
{
DispBMP(0x00000); //指定图片0数据的首地址
GETCH();
DispBMP(0x25800); //指定图片1数据的首地址
GETCH();
DispBMP(0x4B000); //指定图片2数据的首地址
GETCH();
delay_ms(500);    //延时(防止菜单项连续被触发)
}

//=== Main.C END =================================================================================

邮箱 发表于 2024-9-27 13:33:34

{:4_174:}

浦江一水 发表于 2024-9-28 17:41:06

感谢 autopccopy 版主的鲜花鼓励.

浦江一水 发表于 2024-9-29 20:18:06

感谢 芯征程 版主的鲜花鼓励.
页: [1]
查看完整版本: 基于 32G12K128 实验箱V9.62_实验之TFT彩屏显示全屏图片