查看: 4388|回复: 0

stm32f746 discovery试用之LTDC+SDRAM+EMWIN移植

[复制链接]
  • TA的每日心情
    开心
    2020-11-27 08:26
  • 签到天数: 9 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2016-11-24 09:17:45 | 显示全部楼层 |阅读模式
    分享到:
    早就听说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位色处理中,直接全部获取颜色,然后填充块。
    全部结束,可以运行程序了。

    这个快速填充是不是比画点快很多了。
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-27 08:56 , Processed in 0.116267 second(s), 17 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.