视频讲解:使用SPI_DMA读外部Flash+TFT_DMA双缓冲对TFT刷屏,不占CPU时间
视频讲解:使用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读取数据到缓冲区12、上一步的SPI_DMA完成后,启动LCM_DMA将缓冲区1的数据送彩屏,同时SPI_DMA从FLASH读取数据到缓冲区23、上一步的SPI_DMA完成后,启动LCM_DMA将缓冲区2的数据送彩屏,同时SPI_DMA从FLASH读取数据到缓冲区4、重复步骤2和步骤3本测试代码在实验箱9.4B上测试通过。使用DMA中断加双缓冲可极大提高CPU效率,实测数据如下:
DMA缓冲区大小中断周期中断代码执行时间DMA中断处理程序的CPU占有率
3K7.5ms1.6us0.021%
2K5.0ms1.6us0.032%
1K2.49ms1.6us0.064%
5121.25ms1.6us0.128%
256610us1.6us0.262%
128310us1.6us0.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= 1void 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,inty0, int y1);void lcm_clear_screen();void lcm_show_next_image();BYTE xdata buffer1; //定义缓冲区BYTE xdata buffer2; //注意:如果需要使用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) interrupt13{ 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 - 1; lcm_write_cmd(INIT); while(j--) { lcm_write_dat(INIT); } } 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_ISRSPIDMA_ISR:LCMDMA_ISR: JMP 006BH END
视频讲解:使用SPI_DMA读外部Flash+TFT_DMA双缓冲对TFT刷屏,不占CPU时间
https://www.stcaimcu.com/forum.p ... 3184&extra=page%3D1
STC32G12K128实验箱中的演示程序,第79个参考程序
深圳国芯人工智能有限公司-实验箱 (stcai.com)
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开始了解USB和CAN的 同学/同事/老师/研发人员
SPI+DMA到底怎么用,我现在需要读取和写入分开,在CS拉低时先写入3个字节,再读取2个字节,再写入90个字节,再读取2个字节,然后把拉高,硬件SPI都可以实现,但是SPI+DMA就不行了,DMA不能单独写,或者单独读取吗?
DMA 是连续 【1个字节 ~ N个字节】
视频教学《STC32位8051单片机原理及应用-STC32G12K128》,何宾教授主讲 - 软件库函数/原理图库/PCB库/最小包装 - 国芯论坛-STC全球32位8051爱好者互助交流社区 - STC全球32位8051爱好者互助交流社区 (stcaimcu.com)
@辉-无名 发表于 2023-12-20 22:19
SPI+DMA到底怎么用,我现在需要读取和写入分开,在CS拉低时先写入3个字节,再读取2个字节,再写入90个字节 ...
我也是发送地址+读取数据不能直接DMA实现,只能分开,发完地址CS拉高后在拉低才能接收数据,这不在加代码量吗 mark一下,正好需要用到这个 mark STC8H8K64U也有这个功能么,不知道怎么搞? 也有这个功能,按这个搞,换个头文件,就行了
#include "stc8h.h"//#include "stc32g.h"//头文件见下载软件
页:
[1]