簡介
LCD device是一個rt_device,內(nèi)部是一個簡單的LCD框架用于注冊不同屏幕驅(qū)動。本章節(jié)主要介紹LCD device的使用及框架,以及如何注冊一個新屏幕到該框架。
內(nèi)部結(jié)構(gòu)
LCD驅(qū)動有3層:
- rt_device_graphic層 - 對上層提供統(tǒng)一調(diào)用接口
- 內(nèi)部支持多個驅(qū)動查找功能,方便兼容多個屏(根據(jù)比較注冊的ID和ReadID函數(shù)返回的值決定)
- 內(nèi)部包含3個framebuffer機制,讓渲染和送屏可以同步進行,支持壓縮。
- 具體驅(qū)動的邏輯層
- 具體各個屏驅(qū)動的接口、頻率、TE等配置,以及初始化代碼、送屏命令,睡眠,開關(guān)指令
- 屏物理接口的抽象層
- 對大部分接口提供統(tǒng)一的操作函數(shù) 詳見LCDC
Figure 1: lcd device SW arch
- Note
- SDK內(nèi)實現(xiàn)了2個rt_device_graphic實例,rt_device name:
- lcd(drv_lcd.c)
- ram_lcd(drv_ram_lcd.c)
- 將輸出寫到SRAM(打開后將從系統(tǒng)堆內(nèi)存分配一個LCD buffer)
- ram_lcd的使用跟正常屏幕一樣(屏幕尺寸固定)
上層使用LCD device的示例
在屏幕中央繪制一個100*100的RGB565格式的紅色區(qū)域,每刷新一次紅色加深一點,32次后從頭循環(huán)。
#define RGB565_FB_WIDTH 100
#define RGB565_FB_HEIGHT 100
static uint16_t rgb565_frambuffer[RGB565_FB_WIDTH * RGB565_FB_HEIGHT];
static struct rt_semaphore lcd_sema;
static rt_err_t lcd_flush_done(rt_device_t dev, void *buffer)
{
rt_sem_release(&lcd_sema);
return RT_EOK;
}
void lcd_flush_task(void *parameter)
{
rt_err_t ret;
uint8_t color = 0;
struct rt_device_graphic_info info;
uint16_t framebuffer_color_format = RTGRAPHIC_PIXEL_FORMAT_RGB565;
rt_device_t lcd_device = rt_device_find("lcd");
RT_ASSERT(lcd_device != RT_NULL);
rt_device_set_tx_complete(lcd_device, lcd_flush_done);
if (rt_device_open(lcd_device, RT_DEVICE_OFLAG_RDWR) == RT_EOK)
{
if (rt_device_control(lcd_device, RTGRAPHIC_CTRL_GET_INFO, &info) == RT_EOK)
{
rt_kprintf("Lcd info w:%d, h%d, bits_per_pixel %d\r\n", info.width, info.height, info.bits_per_pixel);
}
}
rt_device_control(lcd_device, RTGRAPHIC_CTRL_SET_BUF_FORMAT, &framebuffer_color_format);
rt_sem_init(&lcd_sema, "lv_lcd", 1, RT_IPC_FLAG_FIFO);
while (1)
{
rt_err_t err;
int32_t dx, dy;
dx = (info.width - RGB565_FB_WIDTH) / 2;
dy = (info.height - RGB565_FB_HEIGHT) / 2;
color = (color + 1) % 0x1F;
for(uint32_t i = 0; i < RGB565_FB_HEIGHT; i++)
for(uint32_t j = 0; j < RGB565_FB_WIDTH; j++)
{
rgb565_frambuffer[(i * RGB565_FB_WIDTH) + j] = ((uint16_t )color) << 11;
}
err = rt_sem_take(&lcd_sema, rt_tick_from_millisecond(1000));
RT_ASSERT(RT_EOK == err);
rt_graphix_ops(lcd_device)->set_window(dx, dy, dx + RGB565_FB_WIDTH - 1, dy + RGB565_FB_HEIGHT - 1);
rt_graphix_ops(lcd_device)->draw_rect_async((const char *)&rgb565_frambuffer, dx, dy, dx + RGB565_FB_WIDTH - 1, dy + RGB565_FB_HEIGHT - 1);
}
}
增加一個新屏幕的流程
1. 選擇example\rt_driver下對應(yīng)板子的工程
- 這個工程里面有一個簡單的繪制矩形區(qū)域的示例(見前面 “rt_device_graphic層接口的使用示例”)
- 如果選擇用watch_demo工程,需要 把tp線程關(guān)閉再調(diào)屏幕 ,防止TP不通阻塞UI送屏線程(關(guān)閉辦法:drv_touch.c touch_open函數(shù),去掉rt_thread_startup(touch_thread); )
2. 將新驅(qū)動添加到編譯工程里面
3. 檢查新增LCD用到的pin,以及reset pin 的pinmux是否正確
- 如前所述,在模組的配置時會選擇一個接口的宏,SDK內(nèi)部有對不同的LCDC接口宏做pin mux處理
- reset pin由于是獨立的GPIO控制,需要確認BPS_LCD_Reset函數(shù)控制的pin是否正確
4. 修改新屏幕的接口、頻率、輸出顏色格式
- 見下面示例,修改LCD_DRIVER_EXPORT宏內(nèi)init_cfg結(jié)構(gòu)體(輸出頻率跟配置的可能會有偏差,請以實際輸出的為準,因為HAL層實現(xiàn)輸出頻率=HCLK/divider, HCLK在console輸入"sysinfo"可以查看,divider為2~255的整數(shù))
- TE在調(diào)試初期建議關(guān)閉,防止LCDCD因為等不到TE信號而不送數(shù)(我們的TE信號是LCDC自動處理,不需要軟件參與)
- Note
- 我們的TE信號是LCDC自動處理,不需要軟件參與,所以沒有TE軟件中斷上來。只要TE pin mux和極性正確配置,啟動LCDC送數(shù)后(HAL_LCDC_SendLayerData2Reg_IT),收到了TE脈沖LCDC就會啟動送數(shù)
5 使用任意GPIO作為TE信號(可選)
大部分情況下,只要定義相應(yīng)的管腳為TE功能,LCDC就可以自動處理TE信號,但在某些特殊情況下,TE信號無法從期望的通路來時,需要改成普通GPIO來實現(xiàn),此時可以通過軟件GPIO中斷辦法實現(xiàn):
- 按照正常的配置將TE打開、設(shè)置好TE的延遲
- 定義宏“LCD_USE_GPIO_TE”為一個普通GPIO, 它將會在中斷上升沿主動去人為制造一個TE信號(翻轉(zhuǎn)TE極性),從而觸發(fā)LCDC送數(shù)據(jù)
- Note
- 因為正常TE通路不正常,所以第一步的設(shè)置無法工作,只能靠第二步的人為制造信號去觸發(fā)送數(shù),從而實現(xiàn)正常TE處理
6. 修改新屏幕驅(qū)動的初始化代碼
- 一般是先初始化LCDC,配置接口、頻率等. 調(diào)用API - HAL_LCDC_Init.
- 然后是reset LCD , 通過drv_io.c內(nèi)實現(xiàn)的BPS_LCD_Reset函數(shù)去控制GPIO 復(fù)位屏幕。
- 然后就是屏廠給的初始化代碼
7. 修改read id函數(shù)
- drv_lcd.c 將利用該函數(shù)返回值和LCD_DRIVER_EXPORT注冊的ID比較, 相同則認為該驅(qū)動可用,才會去調(diào)用。
8. QAD-SPI LCD擴展命令修改
- QAD-SPI LCD 一般會把標準8bit命令擴展成32bit,需要修改擴展方法。 可以參考rm69330.c, 一般有這幾個函數(shù)需要修改: RM69330_WriteMultiplePixels, RM69330_WriteReg,RM69330_ReadData。
客戶新增屏幕驅(qū)動代碼示例(部分)
以下示例代碼展示了RM69330如何注冊到drv_lcd.c(rt_device_graphic層)以及接口配置、函數(shù)回調(diào)等. 具體每個函數(shù)的實現(xiàn)請參考SDK代碼,此處不贅述。
#define RM69330_ID 0x8000
#define RM69330_LCD_PIXEL_WIDTH (454)
#define RM69330_LCD_PIXEL_HEIGHT (454)
{
.freq = 48000000,
.color_mode = LCDC_PIXEL_FORMAT_RGB565,
.cfg = {
.spi = {
.dummy_clock = 0,
.vsyn_polarity = 0,
.vsyn_delay_us = 1000,
.hsyn_num = 0,
},
},
};
static const LCD_DrvOpsDef RM69330_drv =
{
RM69330_Init,
RM69330_ReadID,
RM69330_DisplayOn,
RM69330_DisplayOff,
RM69330_SetRegion,
RM69330_WritePixel,
RM69330_WriteMultiplePixels,
RM69330_ReadPixel,
RM69330_SetColorMode,
RM69330_SetBrightness
};
LCD_DRIVER_EXPORT(rm69330, RM69330_ID, &lcdc_int_cfg_qspi,
&RM69330_drv,
RM69330_LCD_PIXEL_WIDTH,
RM69330_LCD_PIXEL_HEIGHT,2);
- Note
- 如前面所述,drv_lcd.c 初始化時將比較 LCD_DRIVER_EXPORT注冊的RM69330_ID 和 RM69330_ReadID返回的ID,如果相同才會調(diào)用。
同時兼容多個屏幕模組
假設(shè)要兼容2各模組:
- 模組一為LB55SPI17801(屏幕IC是RM69090,觸控IC是FT3168),
- 模組二為LB55BILI8688E(屏幕IC是ILI8688E,觸控IC是CST918),
跟前面添加屏幕一樣,往工程對應(yīng)的Kconfig文件添加一項同時選擇2款模組的屏驅(qū)和觸控驅(qū)動即可(注意:屏驅(qū)的ReadID函數(shù)要能分別2款I(lǐng)C,同理觸控的probe函數(shù)也要能區(qū)分不同的IC)。
Kconfig文件示例如下:
config LCD_USING_ED_LB55SPI17801_LB55BILI8688E_QADSPI_LB551
bool "1.78 rect QAD-SPI LCD(ED-LB55SPI17801 and ED-LB55BILI8688E)"
select TSC_USING_FT3168 if BSP_USING_TOUCHD
select TSC_USING_CST918 if BSP_USING_TOUCHD
select LCD_USING_RM69090
select LCD_USING_ILI8688E
select BSP_LCDC_USING_QADSPI
if LCD_USING_ED_LB55SPI17801_LB55BILI8688E_QADSPI_LB551
config LCD_RM69090_VSYNC_ENABLE
bool
def_bool n
config LCD_ILI8688E_VSYNC_ENABLE
bool
def_bool y
endif
- Note
- 其他的屏幕分辨率配置,DPI配置就共用LCD_USING_ED_LB55SPI17801_LB55BILI8688E_QADSPI_LB551宏去設(shè)置即可,既然兼容這些參數(shù)應(yīng)該都是一樣的
DSI屏幕調(diào)試
建議先調(diào)低速模式 因為低速可以繞過因為硬件導(dǎo)致的問題,并且好用示波器分析。 低速模式調(diào)通后讀ID,讀ID 能檢查屏幕上電是否正常。
低速模式讀ID,刷屏都正常后,再改用高速模式刷屏
DSI低速模式操作流程
- 降低LCDC送數(shù)的速度
- 需要將系統(tǒng)時鐘降到48M,在drv_io.c內(nèi),將HAL_RCC_HCPU_ClockSelect(RCC_CLK_MOD_SYS, XXX); XXX就是系統(tǒng)時鐘頻率,改成RCC_SYSCLK_HXT48(晶體時鐘48MHz)
- 將所有命令都改成LP模式(低速模式)發(fā)送,并且打開LCD acknowledge,方便用邏分儀或示波器抓取波形分析(在前面的LCDC_InitTypeDef那個結(jié)構(gòu)體內(nèi)配置,如下:)
.LPCmd = {
.LPGenShortWriteNoP = DSI_LP_GSW0P_ENABLE,
.LPGenShortWriteOneP = DSI_LP_GSW1P_ENABLE,
.LPGenShortWriteTwoP = DSI_LP_GSW2P_ENABLE,
.LPGenShortReadNoP = DSI_LP_GSR0P_ENABLE,
.LPGenShortReadOneP = DSI_LP_GSR1P_ENABLE,
.LPGenShortReadTwoP = DSI_LP_GSR2P_ENABLE,
.LPGenLongWrite = DSI_LP_GLW_ENABLE,
.LPDcsShortWriteNoP = DSI_LP_DSW0P_ENABLE,
.LPDcsShortWriteOneP = DSI_LP_DSW1P_ENABLE,
.LPDcsShortReadNoP = DSI_LP_DSR0P_ENABLE,
.LPDcsLongWrite = DSI_LP_DLW_ENABLE,
.LPMaxReadPacket = DSI_LP_MRDP_ENABLE,
.AcknowledgeRequest = DSI_ACKNOWLEDGE_ENABLE,
},
- Note
- init->cfg.dsi.LPCmd.LPDcsLongWrite調(diào)成DSI_LP_DLW_ENABLE后, HAL_LCDC_Init內(nèi)部會主動將DBI的送數(shù)頻率降到最低(48 * 16 / 126 = 6Mbps 左右,48為系統(tǒng)時鐘,16為DBI帶寬,126為最大分頻系數(shù))
- 調(diào)整DSI LP 模式的頻率到屏幕能支持的范圍(一般在6~20Mbps), 如下配置時LP頻率 = 480 / 8 / 4 = 15Mbps(其中480為freq, 8 為固定值, 4為TXEscapeCkdiv)
{
.freq = DSI_FREQ_480MHZ,
.color_mode = LCDC_PIXEL_FORMAT_RGB888,
.cfg = {
.dsi = {
.Init = {
.AutomaticClockLaneControl = DSI_AUTO_CLK_LANE_CTRL_ENABLE,
.NumberOfLanes = RM69090_DSI_DATALANES,
.TXEscapeCkdiv = 0x4,
},
...
}
...
}
...
}
- 將drv_lcd 層的刷屏超時時間加長,否則默認的500ms在低速下容易超時,調(diào)整宏MAX_LCD_DRAW_TIME的值即可
- 使能bf0_hal_dsi.c里面的log(需要覆蓋HAL_DBG_printf), 可以通過log檢查通信過程發(fā)生的錯誤
#define DSI_LOG_D(...) HAL_DBG_printf(__VA_ARGS__)
#define DSI_LOG_E(...) HAL_DBG_printf(__VA_ARGS__)
- 如果有條件可以用邏分儀或示波器抓取DATALANE0上P,N兩個引腳的波形,解析各個命令、顏色格式等是否是期望的數(shù)據(jù)
- Note
- 讀ID時,屏幕在bus turnaround之后有沒有ack,若沒有可能上電或者reset異常
DSI 高速模式配置
- 低速模式可以讀ID和刷期望的顏色后,可以調(diào)成高速模式,一般來說就通了。
- 高速模式要把AcknowledgeRequest改成disable,否則容易引起發(fā)數(shù)fifo溢出,
- 另外注意有的屏幕不同的顏色格式對應(yīng)不一樣的最高頻率。
在前面的LCDC_InitTypeDef那個結(jié)構(gòu)體內(nèi)配置,如下:
.LPCmd = {
...
.LPDcsLongWrite = DSI_LP_DLW_DISABLE,
...
.AcknowledgeRequest = DSI_ACKNOWLEDGE_DISABLE,
},
DSI 顏色,TE功能配置
- DSI顏色格式 參考DSI
- DSI TE功能 DSI TE有2條通路可選:通過DSI鏈路或者通過LCDC_TE的功能管腳 如果走LCDC_TE管腳,需要同時指定一個物理管腳的pinmux 為 LCDC_TE功能(可以是LCDCx_SPI_TE/LCDCx_8080_TE)
{
.freq = DSI_FREQ_480MHZ,
.color_mode = LCDC_PIXEL_FORMAT_RGB888,
.cfg = {
.dsi = {
...
.CmdCfg = {
...
.TearingEffectSource = DSI_TE_EXTERNAL, <<<---- DSI_TE_EXTERNAL 代表走LCDC_TE管腳, DSI_TE_DSILINK代表走DSI鏈路
- Note
- 在55x系列芯片上,不支持DSI使用LCDC_TE的功能管腳,請參考上面“使用任意GPIO作為TE信號”章節(jié)的辦法。