找回密码
 立即注册
查看: 638|回复: 4

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

[复制链接]
  • 打卡等级:以坛为家II
  • 打卡总天数:494
  • 最近打卡:2025-05-02 09:16:59

30

主题

347

回帖

3480

积分

荣誉版主

积分
3480
发表于 2024-9-27 11:55:42 | 显示全部楼层 |阅读模式
基于 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。
截图202409271148514470.jpg
K4键:上加  K5键:下减  K6键:右移  K7键:OK
输入地址时,16进制数据, 输入长度时,10进制数据。
菜单第一项:
PC<->W25Q40 进入与PC机通讯界面(类似官方DEMO实验),按K7键返回主菜单.
截图202409271149359682.jpg
上位机使用STCAI-ISP软件串口功能, 连接实验箱UART2(RS232)接口通讯…
截图202409271149575115.jpg
菜单第二项:
FlashW25Qx 进入与PC机发送数据文件的界面……每次传输一幅图,需要输入存储地址。
注意:第一幅图地址输入0x00000,第二幅图0x25800,第三幅图0x4B000,等待上位机数据发送。按K7键返回主菜单,或上位机发送完毕自动返回主菜单。
此时,利用STCAI-ISP软件,串口发送文件功能。注意:选择数据包每次间隔50ms,让实验箱有时间擦除写入。
截图202409271150245861.jpg
菜单第三项:
Read W25Qx 可以指定地址查阅已存储在W25Q40中的数据……
可用K4、K5键前后地址翻阅,按K7键返回主菜单.
截图202409271150433260.jpg
菜单第四项:
Del_Setctor可以指定地址删除一个扇区数据(4096字节)
为了防止误删,有确认提示,默认是No! 不删除。K6移位键选择,K7确认返回。
截图202409271151074091.jpg
菜单第五项:
Del_Block可以指定地址删除一个块数据(64K字节),可加快大量数据扇区删除。
为了防止误删,也有确认提示,默认是No! 不删除。K6移位键选择,K7确认返回。
菜单第六项:
Disp PICx 执行全屏彩色图片显示,按任意键继续,三幅图显示完毕,返回主菜单界面。

截图202409271151381304.jpg
截图202409271151587180.jpg
   截图202409271152228092.jpg
完整工程文件包, 供有兴趣坛友参考或指正:
12-TestTFT9341 W25Q40 显全屏图片.rar (525.24 KB, 下载次数: 75)

2 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:494
  • 最近打卡:2025-05-02 09:16:59

30

主题

347

回帖

3480

积分

荣誉版主

积分
3480
发表于 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[80];              //显示缓存
unsigned long ADDR = 0;  //0x25800/0x4B000
unsigned long Length=153600;
u16 W25Q40_ID;        //设备ID
u32 T_Addr;              //目标地址
u8  R_Buf[256];        //读取数据缓存
extern u8 xdata F_Buf[4096]; //扇区缓存

//菜单项目定义...
char * MN[6]={ "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[i],14,4);
    k=0;k=GETCH();
    switch(k)
    { case K40 : I[i]=I[i]<0x39? I[i]+1:0x30; break;
      case K50 : I[i]=I[i]>0x30? I[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[i],14,4);
    k=0;k=GETCH();
    switch(k)
    { case K40 : F[i]=F[i]<0x46? F[i]+1:0x30; if(F[i]==0x3A)F[i]=0x41; break;
      case K50 : F[i]=F[i]>0x30? F[i]-1:0x46; if(F[i]==0x40)F[i]=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[i]<0x3A?F[i]-48:F[i]-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[i],14,4);
         else   LCD_Str12(10,30+20*i,(char *)MN[i],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[m],15,0);
                  m=m>0?m-1:5;
                  LCD_Str12(10,30+20*m,(char *)MN[m],14,4); break;
        case K50: LCD_Str12(10,30+20*m,(char *)MN[m],15,0);
                  m=m<5?m+1:0;
                  LCD_Str12(10,30+20*m,(char *)MN[m],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[i] >= 'a') && (RX2_Buf[i] <= 'z'))RX2_Buf[i] -= 0x20; } //小写转大写
      if(((RX2_Buf[0]=='E')||(RX2_Buf[0]=='W')||(RX2_Buf[0]=='R')||(RX2_Buf[0]=='Q'))&&(RX2_Buf[1]==' '))
      { //解析地址...
        ch=RX2_Buf[4]-0x30; if(ch>9)ch=ch-0x07; T_Addr=(u32)ch;
        ch=RX2_Buf[5]-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
        ch=RX2_Buf[6]-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
        ch=RX2_Buf[7]-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
        ch=RX2_Buf[8]-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
        ch=RX2_Buf[9]-0x30; if(ch>9)ch=ch-0x07; T_Addr=16*T_Addr+(u32)ch;
        //解析指令
        switch(RX2_Buf[0])
        { 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[11+i];
                      if((ch>='0')&&(ch<='9'))len=10*len+RX2_Buf[11+i]-'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[i]);
                    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]=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[0]=Length/100000+'0';
  S[1]=(Length%100000)/10000+'0';
  S[2]=(Length%10000)/1000+'0';
  S[3]=(Length%1000)/100+'0';
  S[4]=(Length%100)/10+'0';
  S[5]=Length%10+'0';
  S[6]=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[i]);
      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[2*j];l=RX2_Buf[2*j+1];
      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 =================================================================================

回复 支持 2 反对 0

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:239
  • 最近打卡:2025-05-02 03:42:37

22

主题

283

回帖

905

积分

高级会员

积分
905
发表于 2024-9-27 13:33:34 来自手机 | 显示全部楼层
REMOVEUNUSED
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:494
  • 最近打卡:2025-05-02 09:16:59

30

主题

347

回帖

3480

积分

荣誉版主

积分
3480
发表于 2024-9-28 17:41:06 | 显示全部楼层
感谢 autopccopy 版主的鲜花鼓励.
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:494
  • 最近打卡:2025-05-02 09:16:59

30

主题

347

回帖

3480

积分

荣誉版主

积分
3480
发表于 2024-9-29 20:18:06 | 显示全部楼层
感谢 芯征程 版主的鲜花鼓励.
回复 支持 反对

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-2 20:21 , Processed in 0.115681 second(s), 76 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表