背景
调试一个FSMC接口的TFT-LCD(9341驱动芯片),最开始使用的是LCD_DrawPoint方式,设一次坐标描一个像素点,效率极低,而且占用CPU。所以想使用DMA方式来处理像素数据,上一篇内容介绍了DMA MemoryToMemor,内存到内存数据拷贝功能。与上一篇不同的仅仅是一些配置而已
代码如下:
LCD外设驱动,可参考参考正点原子,代码。需要留意LCD_BASE的地址设置,硬件上我的LCD的CS接了FSMC_NE1,RS接了FSMC_A16。所以基地址设置为
#define LCD_BASE ((u32)(0x60000000 | 0x0001FFFE))
//LCD地址结构体
typedef struct
{
volatile uint16_t LCD_REG;
volatile uint16_t LCD_RAM;
} LCD_TypeDef;
//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A6作为数据命令区分线((u32)(0x6C000000 | 0x0000007E))
//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A10作为数据命令区分线((u32)(0x6C000000 | 0x000007FE))
//使用NOR/SRAM的 Bank1.sector1,地址位HADDR[27,26]=00 A16作为数据命令区分线((u32)(0x60000000 | 0x0001FFFE))
//注意设置时STM32内部会右移一位对其!
#define LCD_BASE ((u32)(0x60000000 | 0x0001FFFE))
#define LCD ((LCD_TypeDef *) LCD_BASE)
DMA配置:
#define LCD_DMA_Stream DMA2_Stream3
#define LCD_DMA_Channel DMA_Channel_0
#define LCD_DMA_IT_TCIFx DMA_IT_TCIF3
/******************************************************************************
函数说明:设置起始和结束地址
入口数据:x1,x2 设置列的起始和结束地址
y1,y2 设置行的起始和结束地址
返回值: 无
******************************************************************************/
void LCD_Address_Set(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2)
{
LCD_WR_REG(0x2a);//列地址设置
LCD_WR_DATA(x1>>8);
LCD_WR_DATA(x1&0xff);
LCD_WR_DATA(x2>>8);
LCD_WR_DATA(x2&0xff);
LCD_WR_REG(0x2b);//行地址设置
LCD_WR_DATA(y1>>8);
LCD_WR_DATA(y1&0xff);
LCD_WR_DATA(y2>>8);
LCD_WR_DATA(y2&0xff);
LCD_WR_REG(0x2c);//储存器写(2C命令之后开始写颜色数据)
}
void LCD_DMA_Init_Core(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
if((uint32_t)LCD_DMA_Stream>(uint32_t)DMA2)//得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
}
DMA_DeInit(LCD_DMA_Stream);
while (DMA_GetCmdStatus(LCD_DMA_Stream) != DISABLE){}//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = LCD_DMA_Channel; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)0;//DMA外设地址(后续赋值)
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&LCD->LCD_RAM;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = 0;//数据传输量(后续赋值)
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//外设增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;//存储器非增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_HalfWord;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输
DMA_Init(LCD_DMA_Stream, &DMA_InitStructure);//初始化DMA Stream
DMA_ITConfig(LCD_DMA_Stream,DMA_IT_TC,ENABLE);
//DMA NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_Cmd(LCD_DMA_Stream, DISABLE);
}
void LCD_Start_DMA_Transfer(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2,uint16_t *color)
{
LCD_Address_Set(x1,y1,x2,y2);
DMA_Cmd(LCD_DMA_Stream, DISABLE); //关闭DMA传输
while (DMA_GetCmdStatus(LCD_DMA_Stream) != DISABLE){} //等待DMA可配置
LCD_DMA_Stream->NDTR = (uint16_t)((x2-x1+1)*(y2-y1+1)*2); //数据传输量
LCD_DMA_Stream->PAR = (uint32_t)color; //传输数组
DMA_Cmd(LCD_DMA_Stream, ENABLE); //开启DMA传输
}
void DMA2_Stream3_IRQHandler(void)
{
if(DMA_GetITStatus(LCD_DMA_Stream,LCD_DMA_IT_TCIFx)!=RESET)
{
DMA_ClearITPendingBit(LCD_DMA_Stream,LCD_DMA_IT_TCIFx);
}
}
调用
LCD_Start_DMA_Transfer
即可开始一次DMA数据传输。
同理LVGL可将描点的方式替换为LCD_Start_DMA_Transfer让DMA自动传输Color数据,
中断回调可执行LVGL的lv_disp_flush_ready函数