大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家分享的是 i.MXRT1170 上 LCD 花屏顯示問題的分析解決經(jīng)驗(yàn)。
痞子衡最近這段時(shí)間在參與一個(gè)基于 i.MXRT1170 的大項(xiàng)目(先保個(gè)密),需要做一個(gè)開機(jī)動(dòng)畫功能,板子連接的 LCD 屏分辨率是 1280x480,因?yàn)殚_機(jī)動(dòng)畫要求達(dá)到 30fps,并且要畫質(zhì)清晰,如果是從 SD 卡里讀 mp4 或者 jpeg 去解碼,這么高分辨率的圖像(暫不考慮低分辨率的圖片再用 PXP 模塊去拉伸的方案)解碼耗時(shí)比較長,恐怕難以達(dá)成 30fps,所以痞子衡打算直接把圖片的裸 rgb 數(shù)據(jù)事先存在 Flash 里,然后 LCD 模塊直接去刷 Flash 里的數(shù)據(jù)去顯示。
板子上的 SPI NOR Flash 有兩種,默認(rèn)是八線 DDR 高性能 Flash,還有一個(gè)可選的四線 SDR 普通 Flash,痞子衡做好的代碼在默認(rèn)高性能 Flash 上跑得沒問題,換到另一塊 rework 為普通四線 Flash 上就出問題了,顯示完全是花屏,沒有一點(diǎn)圖片的影子,到底是怎么回事?跟著痞子衡一起去發(fā)現(xiàn)答案吧。
一、項(xiàng)目板卡簡圖
先來看一下這個(gè)項(xiàng)目板卡簡圖,簡圖里只示意了痞子衡今天要分享的 LCD 問題相關(guān)的器件,顯示屏是 TM103XDKP13 控制器驅(qū)動(dòng)的 LVDS 接口屏,跟 i.MXRT 連接的話需要有一個(gè) RGB2LVDS 轉(zhuǎn)接。Flash 都是選的旺宏的,一個(gè)是 MX25UW51345(200MHz,8bit,DDR),還有一個(gè)是 MX25U25645(133MHz,4bit,SDR)。此外還有兩個(gè) 16bit 的 W9825G6KH 組成的 32bit SDRAM 做顯存,總?cè)萘渴?64MB。
?
二、在 Flash 中準(zhǔn)備好圖片裸數(shù)據(jù)
首先我們需要在 Flash 中存入圖片數(shù)據(jù),1280x480-24bpp (rgb888)圖片一張的裸數(shù)據(jù)大小是 1800KB,32MB 的 Flash 最大可以存 18 張圖片,為了給程序存儲(chǔ)留點(diǎn)空間,我們就存 17 張,從 Flash 偏移 0x100000 處開始存圖片。
2.1 截取一段 mp4 視頻
痞子衡本地有一個(gè) NXP 十周年宣傳視頻(MP4 格式),原始分辨率是 1920x1080,可以先用 ffmpeg 或者格式工廠將其轉(zhuǎn)換成 1280x480,然后可以直接用 Windows 自帶的圖片軟件里的 Trim 功能截取其中一段,30fps 幀率的視頻截取 1 秒就夠了。
?
2.2 使用 ScreenToGif 軟件分離出圖片
這時(shí)候可以用非常好用的 GIF 制作軟件 ScreenToGif 打開這個(gè) 1 秒的 MP4,可以看到一共有 31 張圖片,可以刪掉其中一些留下 17 張,然后將其保存為圖片(當(dāng)前版本僅能保存為 png 格式),可以再用格式工廠軟件將圖片格式轉(zhuǎn)為 jpg,存在 D:/nxp_logo 文件夾下。
?
?
2.3 Python 腳本轉(zhuǎn)成 rgb888 裸數(shù)據(jù)
有了 17 張 jpg 圖片,這時(shí)候?qū)懸粋€(gè) Python 腳本(jpg2rgb.py),借助 Image 庫將 17 張 jpg 圖片中的 rgb 數(shù)據(jù)全部抽取出來保存在一個(gè) 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 裸數(shù)據(jù)寫入 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 將圖片裸數(shù)據(jù) bin 文件下載進(jìn) Flash
現(xiàn)在可以借助 MCUBootUtility 的通用編程器功能將 startup_video_white_rgb888_17f.bin 文件燒錄進(jìn) Flash 里 0x100000 處偏移的地方。至此,準(zhǔn)備工作已經(jīng)就緒。
?
三、引出 LCD 花屏顯示問題
現(xiàn)在讓我們開始設(shè)計(jì)開機(jī)動(dòng)畫程序,可以基于 SDK_2.x.x_MIMXRT1170-EVKboardsevkmimxrt1170jpeg_examplessd_jpeg 例程,將其中的 LCD 配置,Pinmux 配置稍微改一下,適配這個(gè)項(xiàng)目的板子,然后主函數(shù)可以精簡如下(sd 卡讀,libjpeg 解碼函數(shù)全部去掉):
#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;
}
這時(shí)候把代碼下載進(jìn)高性能 DDR Flash 的那塊板子,我們的代碼可以鏈接到 TCM 里執(zhí)行,這樣不占用運(yùn)行時(shí) Flash 訪問帶寬,不與 LCD 搶帶寬。斷電重啟可以看到在 60Hz 的 LCD 刷新率下,開機(jī)動(dòng)畫效果顯示杠杠的。
?
現(xiàn)在把代碼下載進(jìn)普通 SDR Flash 的板子試試,可以看到 LCD 顯示花屏了,完全沒有圖像的影子,這時(shí)候該怎么定位問題?
?
四、嘗試降低 LCD 刷新率
在嘗試降低 LCD 刷新率之前,痞子衡額外做了一些 debug 工作來確認(rèn)是不是 Flash 焊接的問題,首先是調(diào)試器掛上去查看 PC 指針停在哪里,經(jīng)調(diào)試發(fā)現(xiàn),PC 指針是在 TCM 里,根據(jù)工程 map 文件可以查到其地址對應(yīng)的是程序的結(jié)尾,說明代碼正常跑完了,這至少證明芯片能夠正常從 Flash 啟動(dòng)。
然后痞子衡又對程序作了一些改動(dòng),將 Flash 中的圖片數(shù)據(jù)拷貝到 SDRAM 中,讓 LCD 模塊去刷 SDRAM,這時(shí)候圖像顯示是正常的,這幾乎就可以定位問題了,是普通 SDR Flash 帶寬不夠,F(xiàn)lash 訪問速度撐不起 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 刷新率來驗(yàn)證是不是 Flash 帶寬的問題,在 BOARD_InitLcdifClock()函數(shù)中修改 lcdifv2ClockConfig.div 的值,慢慢增大該值,經(jīng)痞子衡測試,當(dāng) div 設(shè)為 22 時(shí)(即對應(yīng) LCD 刷新率為 33.9Hz),終于能夠正常顯示開機(jī)動(dòng)畫了。
五、關(guān)于帶寬的分析
現(xiàn)在給出痞子衡的觀點(diǎn),對于一個(gè)新項(xiàng)目,如果首次測試 LCD 顯示,建議先從低刷新率開始,只有低刷新率調(diào)試通過,再逐漸增大刷新率,否則會(huì)因?yàn)閹拞栴}浪費(fèi)不少時(shí)間。
最后再讓我們通過理論公式來推算這款普通 SDR Flash 能支持最大的刷新率,計(jì)算公式其實(shí)很簡單:
LCD 最大刷新率 = (Flash 時(shí)鐘頻率 * Flash 數(shù)據(jù)位) / 圖片大小 = 133MHz * 4bit / (1280 * 480 * 24bit) = 36.08Hz
理論計(jì)算值 36.08Hz 跟我們實(shí)測值 33.9Hz 很接近,那點(diǎn)差值應(yīng)該是 FLEXSPI 和 eLCDIF 模塊的開銷。
至此,i.MXRT1170 上 LCD 花屏顯示問題的分析解決經(jīng)驗(yàn)痞子衡便介紹完畢了,掌聲在哪里~~~