找回密码
 立即注册
查看: 2551|回复: 5

视频讲解:使用SPI_DMA读外部Flash+TFT_DMA双缓冲对TFT刷屏,不占CPU时间

[复制链接]

该用户从未签到

550

主题

9234

回帖

1万

积分

管理员

积分
13942
发表于 2023-5-19 21:33:15 | 显示全部楼层 |阅读模式
视频讲解:使用SPI_DMA读外部Flash+TFT_DMA双缓冲TFT刷屏,不占CPU时间
STC32G数据手册的 29.12.3章节
//测试工作频率为35MHz

/*************  功能说明    **************
使用SPIDMA方式对外挂的串行FLASH进行读取数据,并将数据存储在XDATA的缓冲区中,然后使用LCMDMA方式将该缓冲区的数据写入到TFT彩屏。
整个过程采样双缓冲Ping-Pang模式:
1SPI_DMAFLASH读取数据到缓冲区1
2、上一步的SPI_DMA完成后,启动LCM_DMA将缓冲区1的数据送彩屏,同时SPI_DMAFLASH读取数据到缓冲区2
3、上一步的SPI_DMA完成后,启动LCM_DMA将缓冲区2的数据送彩屏,同时SPI_DMAFLASH读取数据到缓冲区
4、重复步骤2和步骤3
本测试代码在实验箱9.4B上测试通过。使用DMA中断加双缓冲可极大提高CPU效率,实测数据如下:
  
DMA缓冲区大小
  
中断周期
中断代码执行时间
DMA中断处理程序的CPU占有率
3K
7.5ms
1.6us
0.021%
2K
5.0ms
1.6us
0.032%
1K
2.49ms
1.6us
0.064%
512
1.25ms
1.6us
0.128%
256
610us
1.6us
0.262%
128
310us
1.6us
0.516%
******************************************/
//#include "stc8h.h"
#include "stc32g.h"//头文件见下载软件
#include "stdio.h"
#define       FOSC                           35000000UL                                   //系统工作频率
#define       BAUD                          (65536- (FOSC/115200+2)/4)
                                                                                                                //2操作是为了让Keil编译器
                                                                                                                //自动实现四舍五入运算
#define       T2S                               (65536- FOSC*2/12/128)
#define       SCREENCX                320                                                  //TFT彩屏的宽度(横向像素)
#define       SCREENCY                240                                                  //TFT彩屏的高度(纵向像素)
#define       IMG1_ADDR              0x00000000                                    //1幅图片在FLASH中的起始地址
#define       IMG2_ADDR              0x00025800                                    //2幅图片在FLASH中的起始地址
#define       IMG3_ADDR              0x0004b000                                    //3幅图片在FLASH中的起始地址
#define       HIBYTE(w)                 (BYTE)(((WORD)(w))>> 8)
#define       LOBYTE(w)                 (BYTE)(w)
#define       RGB565(r, g, b)           ((((r) & 0x1f) << 11) |(((g)& 0x3f) << 5) | ((b) & 0x1f))
#define       WHITE                        RGB565(31,63, 31)                       //白色
#define       BLACK                        RGB565(0,0, 0)                             //黑色
#define       GRAY                           RGB565(16,32, 16)                       //灰色
#define       RED                             RGB565(31,0, 0)                           //红色
#define       GREEN                       RGB565(0,63, 0)                           //绿色
#define       BLUE                           RGB565(0,0, 31)                           //蓝色
#define       CYAN                           RGB565(0,63, 31)                         //青色
#define       MAGENTA                 RGB565(31,0, 31)                         //紫色
#define       YELLOW                     RGB565(31,63, 0)                         //黄色
#define       DMA_BUFSIZE         (3*1024)                                         //DMA缓冲区大小
//320*240的彩屏一屏的图片数据为150KB
//DMA缓冲区设置为3K比较好
typedef       bit                                 BOOL;
typedef       unsigned char              BYTE;
typedef       unsigned int                 WORD;
typedef       unsigned long              DWORD;
sbit SPI_SS                  =       P2^2;
sbit SPI_MOSI            =       P2^3;
sbit SPI_MISO            =       P2^4;
sbit SPI_SCLK            =       P2^5;
sbit LCM_CS               =       P3^4;
sbit LCM_RST             =       P4^3;
sbit LCM_RS               =       P4^5;
sbit LCM_RD              =       P4^4;
sbit LCM_WR              =       P4^2;
#define LCM_DB                 P6
#define       LCM_WRDB(d)          LCM_WR= 0;\
                                                        LCM_DB= (d);\
                                                        _nop_();\
                                                        LCM_WR= 1
void delay_ms(WORD n);
void delay_us(WORD n);
void sys_init();
void lcm_init();
void lcm_write_cmd(BYTE   cmd);
void lcm_write_dat(BYTE     dat);
void lcm_write_dat2(WORD dat);
void lcm_set_window(int x0, int x1,int  y0, int y1);
void lcm_clear_screen();
void lcm_show_next_image();
BYTE xdata buffer1[DMA_BUFSIZE];                                              //定义缓冲区
BYTE xdata buffer2[DMA_BUFSIZE];                                              //注意:如果需要使用DMA发送数据,
//则缓冲区必须定义在xdata区域内
BYTE image;
BYTE stage;
BOOL f2s;                                                                                              //2秒标志位
void main()
{
         sys_init();                                                                                      //系统初始化
         lcm_init();
         f2s    = 1;
         while(1)
         {
                   if(f2s)
                   {
                            f2s    = 0;
                            lcm_show_next_image();                                           //2秒自动显示下一幅图片
                   }
         }
}
void tm0_isr() interrupt 1
{
         f2s    = 1;                                                                                       //设置2秒标志
}
void common_isr(void) interrupt  13
{
         if(DMA_LCM_STA & 0x01)
         {
                   DMA_LCM_STA= 0;
                   if(stage >= 2L*SCREENCY*SCREENCX/DMA_BUFSIZE)
                   {
                            LCM_CS= 1;
                   }
         }
         if(DMA_SPI_STA & 0x01)
         {
                   DMA_SPI_STA= 0;
                   if(!(stage & 1))
                   {
                            DMA_LCM_TXAL= (BYTE)&buffer1;                   //设置DMA缓冲区起始地址
                            DMA_LCM_TXAH= (WORD)&buffer1 >> 8;
                            DMA_LCM_CR= 0xa1;                                           //启动DMA开始发送数据
                   }
                   else
                   {
                            DMA_LCM_TXAL= (BYTE)&buffer2;                   //设置DMA缓冲区起始地址
                            DMA_LCM_TXAH= (WORD)&buffer2 >> 8;
                            DMA_LCM_CR= 0xa1;                                           //启动DMA开始发送数据
                   }
                   if(stage < 2L*SCREENCY*SCREENCX/DMA_BUFSIZE)
                   {
                            if(!(stage & 1))
                            {
                                     DMA_SPI_RXAL= (BYTE)&buffer2;           //设置DMA缓冲区起始地址
                                     DMA_SPI_RXAH= (WORD)&buffer2 >> 8;
                                     DMA_SPI_CR= 0xc1;                                     //启动DMA开始接收数据
                            }
                            else
                            {
                                     DMA_SPI_RXAL= (BYTE)&buffer1;           //设置DMA缓冲区起始地址
                                     DMA_SPI_RXAH= (WORD)&buffer1 >> 8;
                                     DMA_SPI_CR= 0xc1;                                     //启动DMA开始接收数据
                            }
                            stage++;
                   }
                   else
                   {
                            SPI_SS= 1;
                   }
         }
}
void delay_ms(WORD n)
{
         while(n--)
                   delay_us(1000);
}
void delay_us(WORD n)
{
         while(n--)                                                                                     //每个循环24个时钟
         {
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
                   _nop_();
         }
}
void sys_init()
{
         WTST= 0x00;
         CKCON= 0x00;
         EAXFR= 1;
         P0M0= 0x00; P0M1 = 0x00;
         P1M0= 0x00; P1M1 = 0x00;
         P2M0= 0x00; P2M1 = 0x00;
         P3M0= 0x10; P3M1 = 0x00;
         P4M0= 0x3c; P4M1 = 0x00;
         P5M0= 0x00; P5M1 = 0x00;
         P6M0= 0xff; P6M1 = 0x00;
         P7M0= 0x00; P7M1 = 0x00;
         TM0PS= 127;                                                                              //设置定时器0时钟预分频系统为128(127+1)
         TMOD= 0x00;
         T0x12= 0;
         TL0= T2S;                                                                                   //设置定时器0的定时周期为2
         TH0= T2S >> 8;
         TR0= 1;
         ET0= 1;
         EA= 1;
         SPI_S0= 1;                                                                                   //P2.2(SS_2)/P2.3(MOSI_2)/P2.4(MISO_2)/P2.5(SCLK_2)
         SPI_S1= 0;
         SPI_SS= 1;
         SPCTL= 0xd0;                                                                             //初始化SPI模块(主模式,CPHA=CPOL=0)
         SPIF= 1;
         f3s= 0;
         image= 0;
}
void lcm_init()
{
         static BYTE code INIT[]      =                                                      //TFT彩屏初始化命令
         {
                   4,      0xcf,        0x00,0xc1, 0x30,
                   5,      0xed,       0x64,0x03, 0x12, 0x81,
                   4,      0xe8,       0x85,0x10, 0x7a,
                   6,      0xcb,       0x39,0x2c, 0x00, 0x34, 0x02,
                   2,      0xf7,        0x20,
                   3,      0xea,       0x00,0x00,
                   2,      0xc0,       0x1b,
                   2,      0xc1,       0x01,
                   3,      0xc5,       0x30,0x30,
                   2,      0xc7,       0xb7,
                   2,      0x36,       0x28,
                   2,      0x3a,       0x55,
                   3,      0xb1,       0x00,0x1a,
                   3,      0xb6,       0x0a,0xa2,
                   2,      0xf2,        0x00,
                   2,      0x26,       0x01,
                   16,    0xe0,       0x0f,0x2a, 0x28, 0x08, 0x0e, 0x08, 0x54,
                            0xa9,       0x43, 0x0a, 0x0f, 0x00, 0x00, 0x00, 0x00,
                   16,    0xe1,       0x00,0x15, 0x17, 0x07, 0x11, 0x06, 0x2b,
                            0x56,       0x3c, 0x05, 0x10, 0x0f, 0x3f, 0x3f, 0x0f,
                   5,      0x2b,       0x00,0x00, HIBYTE(SCREENCY-1), LOBYTE(SCREENCY-1),
                   5,      0x2a,       0x00,0x00, HIBYTE(SCREENCX-1), LOBYTE(SCREENCX-1),
                   1,      0x11,
                   0
         };
         BYTE i, j;
         LCM_DB= 0xff;
         LCM_RS= 1;
         LCM_CS= 1;
         LCM_WR= 1;
         LCM_RD= 1;
         LCM_RST= 0;                                                                             //复位TFT彩屏
         delay_ms(50);
         LCM_RST= 1;
         delay_ms(50);
         i= 0;
         while(INIT)
         {
                   j= INIT[i++] - 1;
                   lcm_write_cmd(INIT[i++]);
                   while(j--)
                   {
                            lcm_write_dat(INIT[i++]);
                   }
         }
         lcm_clear_screen();
         lcm_write_cmd(0x29);                                                                  //打开显示
}
void lcm_write_cmd(BYTE cmd)                                                          //写命令到彩屏
{
         LCM_RS= 0;
         LCM_CS= 0;
         LCM_WRDB(cmd);
         LCM_CS= 1;
}
void lcm_write_dat(BYTE dat)                                                              //8位数据到彩屏
{
         LCM_RS= 1;
         LCM_CS= 0;
         LCM_WRDB(dat);
         LCM_CS= 1;
}
void lcm_write_dat2(WORD dat)                                                          //16位数据到彩屏
{
         LCM_RS= 1;
         LCM_CS= 0;
         LCM_WRDB(dat>> 8);
         LCM_WRDB(dat);
         LCM_CS= 1;
}
BYTE spi_shift(BYTE dat)                                                                    //使用SPI读写FLASH数据
{
         SPIF= 1;
         SPDAT= dat;
         while(!SPIF);
         returnSPDAT;
}
void lcm_set_window(int x0, int x1,int y0, int y1)                               //TFT彩屏中定义窗口大小
{
         lcm_write_cmd(0x2a);
         lcm_write_dat2(x0);
         lcm_write_dat2(x1);
         lcm_write_cmd(0x2b);
         lcm_write_dat2(y0);
         lcm_write_dat2(y1);
}
void lcm_clear_screen()                                                                         //清屏(使用白色填充全屏)
{
         int i, j;
         lcm_set_window(0,SCREENCX - 1, 0, SCREENCY - 1);
         lcm_write_cmd(0x2c);                                                                   //设置写显示数据命令
         LCM_RS= 1;
         LCM_CS= 0;
         for(i=SCREENCY; i; i--)
         {
                   for(j=SCREENCX; j; j--)
                   {
                            LCM_WRDB(WHITE>> 8);
                            LCM_WRDB(WHITE);
                   }
         }
         LCM_CS= 1;
}
void lcm_show_next_image()
{
         DWORD addr;
         switch(image++)                                                                          //获取图片在Flash中的起始地址
         {
         default:      image = 1;
         case0:       addr = IMG1_ADDR; break;
         case1:       addr = IMG2_ADDR; break;
         case2:       addr = IMG3_ADDR; break;
         }
         SPI_SS= 0;
         spi_shift(0x03);                                                                             //发送读取FLASH数据命令
         spi_shift((BYTE)(addr>> 16));                                                   //设置目标地址
         spi_shift((BYTE)(addr>> 8));
         spi_shift((BYTE)(addr));
         lcm_set_window(0,SCREENCX - 1, 0, SCREENCY - 1);
         lcm_write_cmd(0x2c);                                                                   //设置写显示数据命令
         LCM_RS= 1;
         LCM_CS= 0;
         LCMIFCFG= 0x04;                                                                    //设置LCM接口为8位数据位,I8080接口,数据口为P6
         LCMIFCFG2= 0x09;                                                                  //配置LCM时序
         LCMIFSTA= 0x00;                                                                     //清除LCM状态
         LCMIFCR= 0x80;                                                                       //使能LCM接口
         DMA_LCM_CFG= 0x80;                                                           //使能LCM发送DMA功能,使能DMA中断
         DMA_LCM_STA= 0x00;                                                             //清除DMA状态
         DMA_LCM_AMT= (DMA_BUFSIZE - 1);                              //设置DMA传输数据长度
         DMA_LCM_AMTH= (DMA_BUFSIZE - 1) >> 8;
         DMA_SPI_CFG= 0xa0;                                                             //使能SPI接收DMA功能,使能DMA中断
         DMA_SPI_STA= 0x00;                                                               //清除DMA状态
         DMA_SPI_AMT= (DMA_BUFSIZE - 1);                                 //设置DMA传输数据长度
         DMA_SPI_AMTH= (DMA_BUFSIZE - 1) >> 8;
         DMA_SPI_CFG2= 0x00;                                                           //DMA传输过程中不控制SS
         DMA_SPI_RXAL= (BYTE)&buffer1;                                       //设置DMA缓冲区起始地址
         DMA_SPI_RXAH= (WORD)&buffer1 >> 8;
         DMA_SPI_CR= 0xc1;                                                                 //启动DMA开始接收数据
         stage= 0;
}
//文件:ISR.ASM
//中断号大于31的中断,需要进行中断入口地址重映射处理
                            CSEG     AT       018BH                                            ;SPIDMA_VECTOR
                            JMP                    SPIDMA_ISR
                            CSEG     AT       01D3H                                            ;LCMDMA_VECTOR
                            JMP                    LCMDMA_ISR
SPIDMA_ISR:
LCMDMA_ISR:
                            JMP                    006BH
                            END



回复 送花

使用道具 举报

该用户从未签到

16

主题

155

回帖

1357

积分

版主

积分
1357
发表于 2023-7-22 10:22:27 | 显示全部楼层
视频讲解:使用SPI_DMA读外部Flash+TFT_DMA双缓冲TFT刷屏,不占CPU时间
截图202310111013151199.jpg
https://www.stcaimcu.com/forum.p ... 3184&extra=page%3D1

STC32G12K128实验箱中的演示程序,第79个参考程序
截图202310111017314615.jpg

截图202310111018571405.jpg
深圳国芯人工智能有限公司-实验箱 (stcai.com)


QQ:3398500488
微信号:18106296592(小刘)
回复 支持 反对 送花

使用道具 举报

该用户从未签到

550

主题

9234

回帖

1万

积分

管理员

积分
13942
 楼主| 发表于 2023-10-11 10:06:41 | 显示全部楼层
USB / CAN 专题免费教学会议通知:
USB 原理实战16课时10月/9号, 10月/11号; 10/16, 10/18;
CAN 原理实战,  8课时12月/18号, 12月/20号;
线上视频授课:周一
下午/周三下午14:00 ~ 17:00;  
腾讯会议号885-5858-2739; (安装腾讯会议软件后,输入会议号即可)

参会学习立即【免费+包邮USB核心功能实验板
参会学习立即【免费+包邮CAN核心功能实验板,模拟的CAN收发器您自己补上
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=4526&extra=&page=1
请帮忙转发给可能需要:从0开始了解USBCAN同学/同事/老师/研发人员
回复 支持 反对 送花

使用道具 举报

  • TA的每日心情

    2023-12-21 08:58
  • 签到天数: 2 天

    [LV.1]初来乍到

    1

    主题

    1

    回帖

    39

    积分

    新手上路

    积分
    39
    发表于 2023-12-20 22:19:41 | 显示全部楼层
    SPI+DMA到底怎么用,我现在需要读取和写入分开,在CS拉低时先写入3个字节,再读取2个字节,再写入90个字节,再读取2个字节,然后把拉高,硬件SPI都可以实现,但是SPI+DMA就不行了,DMA不能单独写,或者单独读取吗?
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    550

    主题

    9234

    回帖

    1万

    积分

    管理员

    积分
    13942
     楼主| 发表于 2023-12-24 17:27:28 | 显示全部楼层
    回复 支持 1 反对 0 送花

    使用道具 举报

  • TA的每日心情
    难过
    16 小时前
  • 签到天数: 147 天

    [LV.7]常住居民III

    134

    主题

    187

    回帖

    1596

    积分

    金牌会员

    积分
    1596
    发表于 2024-1-20 18:35:15 | 显示全部楼层
    @辉-无名 发表于 2023-12-20 22:19
    SPI+DMA到底怎么用,我现在需要读取和写入分开,在CS拉低时先写入3个字节,再读取2个字节,再写入90个字节 ...

    我也是发送地址+读取数据不能直接DMA实现,只能分开,发完地址CS拉高后在拉低才能接收数据,这不在加代码量吗
    回复 支持 反对 送花

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2024-4-28 23:03 , Processed in 0.073448 second(s), 52 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

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