大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是 i.MXRT1170 上 LCD 花屏显示问题的分析解决经验。

 

痞子衡最近这段时间在参与一个基于 i.MXRT1170 的大项目(先保个密),需要做一个开机动画功能,板子连接的 LCD 屏分辨率是 1280x480,因为开机动画要求达到 30fps,并且要画质清晰,如果是从 SD 卡里读 mp4 或者 jpeg 去解码,这么高分辨率的图像(暂不考虑低分辨率的图片再用 PXP 模块去拉伸的方案)解码耗时比较长,恐怕难以达成 30fps,所以痞子衡打算直接把图片的裸 rgb 数据事先存在 Flash 里,然后 LCD 模块直接去刷 Flash 里的数据去显示。

 

板子上的 SPI NOR Flash 有两种,默认是八线 DDR 高性能 Flash,还有一个可选的四线 SDR 普通 Flash,痞子衡做好的代码在默认高性能 Flash 上跑得没问题,换到另一块 rework 为普通四线 Flash 上就出问题了,显示完全是花屏,没有一点图片的影子,到底是怎么回事?跟着痞子衡一起去发现答案吧。

 

一、项目板卡简图

先来看一下这个项目板卡简图,简图里只示意了痞子衡今天要分享的 LCD 问题相关的器件,显示屏是 TM103XDKP13 控制器驱动的 LVDS 接口屏,跟 i.MXRT 连接的话需要有一个 RGB2LVDS 转接。Flash 都是选的旺宏的,一个是 MX25UW51345(200MHz,8bit,DDR),还有一个是 MX25U25645(133MHz,4bit,SDR)。此外还有两个 16bit 的 W9825G6KH 组成的 32bit SDRAM 做显存,总容量是 64MB。

 

 

二、在 Flash 中准备好图片裸数据

首先我们需要在 Flash 中存入图片数据,1280x480-24bpp (rgb888)图片一张的裸数据大小是 1800KB,32MB 的 Flash 最大可以存 18 张图片,为了给程序存储留点空间,我们就存 17 张,从 Flash 偏移 0x100000 处开始存图片。

 

2.1 截取一段 mp4 视频

痞子衡本地有一个 NXP 十周年宣传视频(MP4 格式),原始分辨率是 1920x1080,可以先用 ffmpeg 或者格式工厂将其转换成 1280x480,然后可以直接用 Windows 自带的图片软件里的 Trim 功能截取其中一段,30fps 帧率的视频截取 1 秒就够了。

 

 

2.2 使用 ScreenToGif 软件分离出图片

这时候可以用非常好用的 GIF 制作软件 ScreenToGif 打开这个 1 秒的 MP4,可以看到一共有 31 张图片,可以删掉其中一些留下 17 张,然后将其保存为图片(当前版本仅能保存为 png 格式),可以再用格式工厂软件将图片格式转为 jpg,存在 D:/nxp_logo 文件夹下。

 

 

2.3 Python 脚本转成 rgb888 裸数据

有了 17 张 jpg 图片,这时候写一个 Python 脚本(jpg2rgb.py),借助 Image 库将 17 张 jpg 图片中的 rgb 数据全部抽取出来保存在一个 bin 文件中,下面脚本使用命令为 python jpg2rgb.py D:/nxp_logo/ -o startup_video_white_rgb888_17f.bin 。

 

import sys, os
import argparse
import Image

parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-o", "--output", required=True, metavar="PATH", type=argparse.FileType('wb'))
parser.add_argument("input", help="JPEG Image folder.")
args = parser.parse_args()

imgFiles = []

# 获取指定文件夹中所有 jpg 图片路径
imgFolder = os.path.abspath(args.input)
inputFiles = os.listdir(imgFolder)
for idx in range(len(inputFiles)):
    imgFiles.append(os.path.join(imgFolder, inputFiles[idx]))

for idx in range(len(imgFiles)):
    # 使用 Image 库打开 jpg 图片
    imgObj = Image.open(imgFiles[idx])
    pixelBuf = imgObj.getdata()
    # 抽取 rgb 裸数据写入 bin 文件
    for i in range(len(pixelBuf)):
        for j in range(len(pixelBuf[i])):
            args.output.write(chr(pixelBuf[i][len(pixelBuf[i]) - j - 1]))
args.output.close()

 

2.4 将图片裸数据 bin 文件下载进 Flash

现在可以借助 MCUBootUtility 的通用编程器功能将 startup_video_white_rgb888_17f.bin 文件烧录进 Flash 里 0x100000 处偏移的地方。至此,准备工作已经就绪。

 

 

三、引出 LCD 花屏显示问题

现在让我们开始设计开机动画程序,可以基于 \SDK_2.x.x_MIMXRT1170-EVK\boards\evkmimxrt1170\jpeg_examples\sd_jpeg 例程,将其中的 LCD 配置,Pinmux 配置稍微改一下,适配这个项目的板子,然后主函数可以精简如下(sd 卡读,libjpeg 解码函数全部去掉):

 

#define APP_FB_HEIGHT 480
#define APP_FB_WIDTH  1280
/* LCD frame buffer byte per pixel, RGB888 format, 24-bit. */
#define APP_FB_BPP 3

const uint32_t s_imagePics = 17;
const uint32_t s_imageStartAddr = 0x30100000;

int main(void)
{
    uint8_t *imageAddr = (uint8_t *)s_imageStartAddr;
    uint32_t imageBytes = APP_FB_HEIGHT * APP_FB_WIDTH * APP_FB_BPP;

    BOARD_ConfigMPU();
    BOARD_InitBootPins();
    BOARD_BootClockRUN();
    BOARD_ResetDisplayMix();
    APP_InitDisplay();

    while (1)
    {
        /* Wait for the previous set frame buffer active. */
        while (s_newFrameShown == false);

        /* Now new frame is ready, pass it to LCDIF. */
        s_newFrameShown = false;
        g_dc.ops->setFrameBuffer(&g_dc, 0, imageAddr);

        imageAddr += imageBytes;
        if ((uint32_t)imageAddr >= (s_imageStartAddr + imageBytes * s_imagePics))
        {
            break;
        }
    }
}

static void APP_BufferSwitchOffCallback(void *param, void *switchOffBuffer)
{
    s_newFrameShown = true;
}

 

这时候把代码下载进高性能 DDR Flash 的那块板子,我们的代码可以链接到 TCM 里执行,这样不占用运行时 Flash 访问带宽,不与 LCD 抢带宽。断电重启可以看到在 60Hz 的 LCD 刷新率下,开机动画效果显示杠杠的。

 

 

现在把代码下载进普通 SDR Flash 的板子试试,可以看到 LCD 显示花屏了,完全没有图像的影子,这时候该怎么定位问题?

 

 

四、尝试降低 LCD 刷新率

在尝试降低 LCD 刷新率之前,痞子衡额外做了一些 debug 工作来确认是不是 Flash 焊接的问题,首先是调试器挂上去查看 PC 指针停在哪里,经调试发现,PC 指针是在 TCM 里,根据工程 map 文件可以查到其地址对应的是程序的结尾,说明代码正常跑完了,这至少证明芯片能够正常从 Flash 启动。

 

然后痞子衡又对程序作了一些改动,将 Flash 中的图片数据拷贝到 SDRAM 中,让 LCD 模块去刷 SDRAM,这时候图像显示是正常的,这几乎就可以定位问题了,是普通 SDR Flash 带宽不够,Flash 访问速度撑不起 60Hz 刷新率。

#define DEMO_HSW        (1U)
#define DEMO_HBP        (48U)
#define DEMO_HFP        (16U)
#define DEMO_VSW        (1U)
#define DEMO_VBP        (3U)
#define DEMO_VFP        (5U)

static void BOARD_InitLcdifClock(void)
{
    /*
     * The pixel clock is (height + VSW + VFP + VBP) * (width + HSW + HFP + HBP) * frame rate.
     *
     * For 60Hz frame rate, the TM103XDKP13 pixel clock should be 40MHz.
     *
     */
    const clock_root_config_t lcdifv2ClockConfig = {
        .clockOff = false,
        .mfn      = 0,
        .mfd      = 0,
        .mux      = 4, /*!< PLL_528. */
        .div      = 12,
    };

    CLOCK_SetRootClock(kCLOCK_Root_Lcdifv2, &lcdifv2ClockConfig);
}

让我们尝试降低 LCD 刷新率来验证是不是 Flash 带宽的问题,在 BOARD_InitLcdifClock()函数中修改 lcdifv2ClockConfig.div 的值,慢慢增大该值,经痞子衡测试,当 div 设为 22 时(即对应 LCD 刷新率为 33.9Hz),终于能够正常显示开机动画了。

 

五、关于带宽的分析

现在给出痞子衡的观点,对于一个新项目,如果首次测试 LCD 显示,建议先从低刷新率开始,只有低刷新率调试通过,再逐渐增大刷新率,否则会因为带宽问题浪费不少时间。

 

最后再让我们通过理论公式来推算这款普通 SDR Flash 能支持最大的刷新率,计算公式其实很简单:

 

LCD 最大刷新率 = (Flash 时钟频率 * Flash 数据位) / 图片大小 = 133MHz * 4bit / (1280 * 480 * 24bit) = 36.08Hz

 

理论计算值 36.08Hz 跟我们实测值 33.9Hz 很接近,那点差值应该是 FLEXSPI 和 eLCDIF 模块的开销。

 

至此,i.MXRT1170 上 LCD 花屏显示问题的分析解决经验痞子衡便介绍完毕了,掌声在哪里~~~