痞子衡嵌入式:降低刷新率是定位LCD花屏显示问题的第一大法(i.MXRT1170, 1280x480 LVDS)
大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是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花屏显示问题的分析解决经验痞子衡便介绍完毕了,掌声在哪里~~~
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。
最后欢迎关注痞子衡个人微信公众号【痞子衡嵌入式】,一个专注嵌入式技术的公众号,跟着痞子衡一起玩转嵌入式。
衡杰(痞子衡),目前就职于某全球顶级半导体原厂MCU系统部门,担任高级嵌入式系统应用工程师。
专栏内所有文章的转载请注明出处:http://www.cnblogs.com/henjay724/
与痞子衡进一步交流或咨询业务合作请发邮件至 hengjie1989@foxmail.com
可以关注痞子衡的Github主页 https://github.com/JayHeng,有很多好玩的嵌入式项目。
关于专栏文章有任何疑问请直接在博客下面留言,痞子衡会及时回复免费(划重点)答疑。
痞子衡邮箱已被私信挤爆,技术问题不推荐私信,坚持私信请先扫码付款(5元起步)再发。