TA的每日心情 | 开心 2020-11-27 08:26 |
---|
签到天数: 9 天 连续签到: 1 天 [LV.3]偶尔看看II
|
早就听说stm32从F4系列开始就带有LCD控制器,但这还是第一次使用,总的测试下来感觉效果还不错。看看手册上对LTDC的介绍吧
由于LTDC需要内存作为现存,还需要驱动板载的16MB SDRAM芯片MT48LC4M32B2B5-6A,直接开始吧。依旧是使用cubemx创建工程先配置SDRAM,F7和F4系的FMC一样,一共6个bank区,开发板上的sdram位于SDRAM Bank1,因此在cubemx中我们选择SDCKE0+SDNE0,查看MT48LC4M32B2B5数据手册可知其容量大小为4 Meg x 32 (1 Meg x 32 x 4 banks),即128Mb(16MB),但是由于板上数据线只用到16位,可用实际大小只有8MB,将sdram配置如图所示(根据数据手册确定相关参数)。
接着是配置LTDC,选择24位色RGB888,使能DMA2D(对LTDC的2D显示加速引擎,从寄存器到内存的拷贝,内存到内存的拷贝等等,速度更快)。注意LTDC的管脚根据板子实际IO口配置(因为有些LTDC的功能引脚对应多个管脚),管脚的时钟速度不要设为最快。基本设置完成,生成工程。下面是修改代码了,参考官方的sdram驱动,修改初始化程序如下:
void MX_FMC_Init(void)
{
FMC_SDRAM_TimingTypeDef SdramTiming;
/** Perform the SDRAM1 memory initialization sequence
*/
hsdram1.Instance = FMC_SDRAM_DEVICE;
/* hsdram1.Init */
hsdram1.Init.SDBank = FMC_SDRAM_BANK1;
hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8;
hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12;
hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;
hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_2;
hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0;
/* SdramTiming */
SdramTiming.LoadToActiveDelay = 2;
SdramTiming.ExitSelfRefreshDelay = 7;
SdramTiming.SelfRefreshTime = 4;
SdramTiming.RowCycleDelay = 7;
SdramTiming.WriteRecoveryTime = 2;
SdramTiming.RPDelay = 2;
SdramTiming.RCDDelay = 2;
if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK)
{
//Error_Handler();
}
BSP_SDRAM_Initialization_sequence(REFRESH_COUNT);
}
经测试SDRAM工作正常,接下来可以测试LTDC了,在sdram中分配一段内存作为lcd缓存
uint16_t ltdc_lcd_framebuf1[480][272] __attribute__((at(0XC0000000)));//图层1255K
初始化LTDC,根据数据手册修改时序(其实是参考的官方例程),图层只激活1,根据需要设置图像的长宽和窗口的长宽。
void MX_LTDC_Init(void)
{
LTDC_LayerCfgTypeDef pLayerCfg;
//LTDC_LayerCfgTypeDef pLayerCfg1;
hltdc.Instance = LTDC;
hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL;
hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL;
hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL;
hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
hltdc.Init.HorizontalSync = (RK043FN48H_HSYNC - 1);
hltdc.Init.VerticalSync = (RK043FN48H_VSYNC - 1);
hltdc.Init.AccumulatedHBP = (RK043FN48H_HSYNC + RK043FN48H_HBP - 1);
hltdc.Init.AccumulatedVBP = (RK043FN48H_VSYNC + RK043FN48H_VBP - 1);
hltdc.Init.AccumulatedActiveH = (RK043FN48H_HEIGHT + RK043FN48H_VSYNC + RK043FN48H_VBP - 1);
hltdc.Init.AccumulatedActiveW = (RK043FN48H_WIDTH + RK043FN48H_HSYNC + RK043FN48H_HBP - 1);
hltdc.Init.TotalHeigh = (RK043FN48H_HEIGHT + RK043FN48H_VSYNC + RK043FN48H_VBP + RK043FN48H_VFP - 1);
hltdc.Init.TotalWidth = (RK043FN48H_WIDTH + RK043FN48H_HSYNC + RK043FN48H_HBP + RK043FN48H_HFP - 1);
hltdc.Init.Backcolor.Blue = 0;
hltdc.Init.Backcolor.Green = 0;
hltdc.Init.Backcolor.Red = 0;
if (HAL_LTDC_Init(&hltdc) != HAL_OK)
{
//Error_Handler();
}
ltdc_framebuf[0] = (uint32_t*)&ltdc_lcd_framebuf1;
ltdc_framebuf[1] = (uint32_t*)&ltdc_lcd_framebuf2;
memset(ltdc_framebuf[0], 0x11, 480*272*2);
pLayerCfg.WindowX0 = 0;
pLayerCfg.WindowX1 = 480;
pLayerCfg.WindowY0 = 0;
pLayerCfg.WindowY1 = 272;
pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
pLayerCfg.Alpha = 255;
pLayerCfg.Alpha0 = 0;
pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA;
pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA;
pLayerCfg.FBStartAdress = (uint32_t)ltdc_framebuf[0];//&RGB565_480x272;
pLayerCfg.ImageWidth = 480;
pLayerCfg.ImageHeight = 272;
pLayerCfg.Backcolor.Blue = 0;
pLayerCfg.Backcolor.Green = 0;
pLayerCfg.Backcolor.Red = 0;
if (HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg, 1) != HAL_OK)
{
//Error_Handler();
}
}
添加一个快速填充矩形函数,用DMA2D实现,这里的函数参考原子哥的从寄存器到内存的方式,为了提高速度,用寄存器的方式实现
void LTDC_Fill(uint16_t sx,uint16_t sy,uint16_t ex,uint16_t ey,uint32_t color)
{
uint32_t psx,psy,pex,pey;//以LCD面板为基准的坐标系
uint32_t timeout=0;
uint16_t offline;
uint32_t addr;
psx=sx;psy=sy;
pex=ex;pey=ey;
offline=RK043FN48H_WIDTH-(pex-psx+1);
addr=((uint32_t)ltdc_framebuf[0]+2*(RK043FN48H_WIDTH*psy+psx));
__HAL_RCC_DMA2D_CLK_ENABLE();//使能DMA2D时钟
DMA2D->CR&=~(DMA2D_CR_START);//先停止DMA2D
DMA2D->CR=DMA2D_R2M;//寄存器到存储器模式
DMA2D->OPFCCR=LTDC_PIXEL_FORMAT_RGB565;//设置颜色格式
DMA2D->OOR=offline;//设置行偏移
DMA2D->OMAR=addr;//输出寄存器地址
DMA2D->NLR=(pey-psy+1)|((pex-psx+1)<<16);//设定行数寄存器
DMA2D->OCOLR=color;//设定输出颜色寄存器
DMA2D->CR|=DMA2D_CR_START;//启动DMA2D
while((DMA2D->ISR&(DMA2D_FLAG_TC))==0)//等待传输完成
{
timeout++;
if(timeout>0X1FFFFF)break;//超时退出
}
DMA2D->IFCR|=DMA2D_FLAG_TC;//清除传输完成标志
}
再添加一个填充颜色数组的函数,这个函数在后面的额敏移植用到,可以做一个快速画图。该函数传入的是一个颜色数组。内存到内存方式。
void LTDC_Color_Fill(uint16_t sx,uint16_t sy,uint16_t ex,uint16_t ey,uint16_t *color)
{
uint32_t psx,psy,pex,pey;//以LCD面板为基准的坐标系,不随横竖屏变化而变化
uint32_t timeout=0;
uint16_t offline;
uint32_t addr;
psx=sx;psy=sy;
pex=ex;pey=ey;
offline=RK043FN48H_WIDTH-(pex-psx+1);
addr=((uint32_t)ltdc_framebuf[0]+2*(RK043FN48H_WIDTH*psy+psx));
__HAL_RCC_DMA2D_CLK_ENABLE();//使能DM2D时钟
DMA2D->CR&=~(DMA2D_CR_START);//先停止DMA2D
DMA2D->CR=DMA2D_M2M;//存储器到存储器模式
DMA2D->FGPFCCR=LTDC_PIXEL_FORMAT_RGB565;//设置颜色格式
DMA2D->FGOR=0;//前景层行偏移为0
DMA2D->OOR=offline;//设置行偏移
DMA2D->FGMAR=(uint32_t)color;//源地址
DMA2D->OMAR=addr;//输出存储器地址
DMA2D->NLR=(pey-psy+1)|((pex-psx+1)<<16);//设定行数寄存器
DMA2D->CR|=DMA2D_CR_START;//启动DMA2D
while((DMA2D->ISR&(DMA2D_FLAG_TC))==0)//等待传输完成
{
timeout++;
if(timeout>0X1FFFFF)break;//超时退出
}
DMA2D->IFCR|=DMA2D_FLAG_TC;//清除传输完成标志
}
读点画点
void LTDC_Draw_Point(uint16_t x, uint16_t y, uint32_t color)
{
*(uint16_t*)((uint32_t)ltdc_framebuf[0]+2*(RK043FN48H_WIDTH*y+x))=color;
}
uint32_t LTDC_Read_Point(uint16_t x, uint16_t y)
{
return (*(uint16_t*)((uint32_t)ltdc_framebuf[0]+2*(RK043FN48H_WIDTH*y+x)));
}
驱动到这里就可以了,可以测试一下是否好用,在main.c初始化完成调用一次LTDC_Fill函数,是不是有颜色出来了。
接下来移植EMWIN,我这里移植的是不带os的,这个根据自己的选择了。添加完库和文件后开始修改接口函数了。对了,想到一个隐藏的很好的坑,在调用GUI_Init之前记得使能__HAL_RCC_CRC_CLK_ENABLE();不加是不会运行emwin的。说回正题,在GUIDRV_Template.c中修改和添加接口函数。首先是画点,
static void _SetPixelIndex(GUI_DEVICE * pDevice, int x, int y, int PixelIndex) {
//
// Convert logical into physical coordinates (Dep. on LCDConf.h)
//
#if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
int xPhys, yPhys;
xPhys = LOG2PHYS_X(x, y);
yPhys = LOG2PHYS_Y(x, y);
#else
#define xPhys x
#define yPhys y
#endif
GUI_USE_PARA(pDevice);
GUI_USE_PARA(x);
GUI_USE_PARA(y);
GUI_USE_PARA(PixelIndex);
{
//
// Write into hardware ... Adapt to your system
//
LTDC_Draw_Point(x, y, PixelIndex);
// TBD by customer...
//
}
#if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
#undef xPhys
#undef yPhys
#endif
}
读点函数
static unsigned int _GetPixelIndex(GUI_DEVICE * pDevice, int x, int y) {
unsigned int PixelIndex;
//
// Convert logical into physical coordinates (Dep. on LCDConf.h)
//
#if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
int xPhys, yPhys;
xPhys = LOG2PHYS_X(x, y);
yPhys = LOG2PHYS_Y(x, y);
#else
#define xPhys x
#define yPhys y
#endif
GUI_USE_PARA(pDevice);
GUI_USE_PARA(x);
GUI_USE_PARA(y);
{
//
// Write into hardware ... Adapt to your system
//
PixelIndex = LTDC_Read_Point(x, y);
// TBD by customer...
//
//PixelIndex = 0;
}
#if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
#undef xPhys
#undef yPhys
#endif
return PixelIndex;
}
填充矩形,这个就用我们在驱动中做的DMA2D寄存器到内存的快速填充方式了。
static void _FillRect(GUI_DEVICE * pDevice, int x0, int y0, int x1, int y1) {
LTDC_Fill(x0,y0,x1,y1,LCD_COLORINDEX);
}
创建一个填充颜色数组的函数,在后面修改位图显示函数会用到
static void _SetPixelIndex_multi(GUI_DEVICE *pDevice, int x1, int y, int x2,unsigned short * PixelIndex)
{
short xsize;
#if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
int xPhys, yPhys,xPhys2;
xPhys = LOG2PHYS_X(x1, y);
yPhys = LOG2PHYS_Y(x1, y);
xPhys2 = LOG2PHYS_X(x2, y);
#else
#define xPhys x1
#define yPhys y
#define xPhys2 x2
#endif
// GUI_USE_PARA(pDevice);
// GUI_USE_PARA(x1);
// GUI_USE_PARA(y);
// GUI_USE_PARA(PixelIndex);
{
//
// Write into hardware ... Adapt to your system
//
// TBD by customer...
//
//if(PixelIndex == 0)
//{
//T2();
//}
if(xPhys>xPhys2)
{
xsize = xPhys-xPhys2+1;
Draw_multi(xPhys2, yPhys, xsize, PixelIndex);
//my_frame.func.PutPixel_HLine(xPhys2, yPhys,xsize, PixelIndex);
//putPixel_multi(xPhys2, yPhys,xsize, PixelIndex);
}
else
{
xsize = xPhys2-xPhys+1;
//my_frame.func.PutPixel_HLine(xPhys, yPhys,xsize, PixelIndex);
Draw_multi(xPhys, yPhys, xsize, PixelIndex);
//putPixel_multi(xPhys, yPhys,xsize, PixelIndex);
}
//putPixel_multi(xPhys, yPhys,xsize, PixelIndex);
}
#if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
#undef xPhys
#undef yPhys
#undef xPhys2
#endif
}
接着就是修改图片显示函数了,只需修改_DrawBitLine16BPP,
这里把画点改成获取颜色数组,一行获取结束直接填充一条线,经测试,在画尺寸较大的图片时速度提升效果很明显。
static void _DrawBitLine16BPP(GUI_DEVICE * pDevice, int x, int y, U16 const GUI_UNI_PTR * p, int xsize) {
Draw_multi(x, y, xsize, (uint16_t*)p);
}
当然还有更快的方法,就是直接在_DrawBitmap中的16位色处理中,直接全部获取颜色,然后填充块。
全部结束,可以运行程序了。
这个快速填充是不是比画点快很多了。 |
|