视频讲解:使用SPI_DMA读外部Flash+TFT_DMA双缓冲对TFT刷屏,不占CPU时间
STC32G数据手册的 29.12.3章节
//测试工作频率为35MHz
/************* 功能说明 ************** 使用SPI的DMA方式对外挂的串行FLASH进行读取数据,并将数据存储在XDATA的缓冲区中,然后使用LCM的DMA方式将该缓冲区的数据写入到TFT彩屏。 整个过程采样双缓冲Ping-Pang模式: 1、SPI_DMA从FLASH读取数据到缓冲区1 2、上一步的SPI_DMA完成后,启动LCM_DMA将缓冲区1的数据送彩屏,同时SPI_DMA从FLASH读取数据到缓冲区2 3、上一步的SPI_DMA完成后,启动LCM_DMA将缓冲区2的数据送彩屏,同时SPI_DMA从FLASH读取数据到缓冲区 4、重复步骤2和步骤3 本测试代码在实验箱9.4B上测试通过。使用DMA中断加双缓冲可极大提高CPU效率,实测数据如下: ******************************************/ //#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
|