前言

MIPI-DSI接口因为需要更少的IO数量,能够提供连接器体积和布线难度上的一些优势,而被广泛应用于手机和手表屏幕上。显然作为一个点屏爱好者我也不可能忽略这种常见的屏幕接口,只是之前一直没有机会亲自研究一番,这次借着VerilogBoy的机会也算是大致了解了一下。考虑到MIPI为私有接口,对于其具体的描述在网上并不多,这里就具体介绍一下我对MIPI DSI的了解。需要注意的是,文中所有内容均基于互联网上可以找到的文档编写,并不可能完整描述MIPI DSI标准,甚至可能存在偏差或错误。本文仅供需要调试MIPI屏幕的工程师或业余爱好者入门参考。

基本概念

需要知道的是,MIPI DSI仍然只是一种用于连接屏幕的接口,而不是连接显示器的接口。前者常见的例子如DBI(包括8/16位并口和SPI)、DPI(也称为TTL,RGB和PixelBus等)和LVDS(也称为FlatLink),而后者常见的例子如VGA、DVI、HDMI和DP。前者的特点就是,通常驱动是和屏幕具体参数相关的,不同的屏幕需要不同的驱动程序。而后者的特点是,通常驱动是通用的,显示器可以通过DDC/CI或其它方式将相关的参数(如EDID)传输给驱动,驱动根据得到的信息产生需要的信号。通常来说,前者多用于嵌入式场景。不过现在后者中也出现了eDP专用于嵌入式,也许在以后能代替掉一部分现有的DSI使用场景。本文还是只讨论DSI。既然这么分类了,也就很明确,DSI也是一种不同屏幕不同驱动的接口,“DSI”其实大部分只是对于物理层和传输层的定义,具体的屏幕操作其实更往上还是以前DBI和DPI那一套。不如说,DSI其实就是把DBI和DPI打了一个包,让他们能共享几对差分线一起传输。以下所有讨论的内容仅考虑从主机到屏幕的单向传输。

低功耗模式与高速模式

DSI的物理层传输有两种模式,一种是1.2V CMOS电平的低功耗单端(LP)模式,另外一种是高速差分(HS)模式。在单端模式下,时钟线并不被使用,D0+和D0-用于传输数据和时钟,最大速度为10Mbps,其它的数据线不被使用。在差分模式下,时钟线用于传输差分时钟,最多可以有四对数据线用于传输数据,最大速度为每对数据线1Gbps。

屏幕上电后默认情况下数据线和时钟线都处于LP模式下(CLK+/CLK-均为高电平)。在LP模式下时钟线可以使用如下的系列进入HS模式:

1556590073857-dsi_1.png

即首先拉低CLK+,随后拉低CLK+,等待一段时间后开始HS传输。数据线也可以使用类似的序列进入HS模式:

1556590077269-dsi_2.png

相比时钟线,数据线多了一个同步的过程,任何数据开始传输之前需要发送00011101的序列。一旦同步完成数据线上必须连续传输数据。如果需要中断传输则需要回到LP模式。

在LP模式下,同样也可以直接进行数据传输,通过Escape Mode的LPDT(LP Data Transmission)完成。注意到只画出了数据线,因为LPDT下不需要时钟线,通常而言时钟线此时保持LP-11(两条线均为约1.2V)的状态。而数据时钟可以通过 D+ | D- 得到。

1556590081396-dsi_3.png

数据包

在DSI中,最小可能的传输单位是数据包。数据包可以在HS模式下传输,也可以在LP模式下传输。无论是何种模式下,数据包的内容都是一致的。在高速模式下,多个数据包可以拼接在一起一并发送。

数据包可以分为短包和长包两种。短包永远为4个字节,第一字节为数据标识符(DI),第二三字节为数据内容,第四字节为ECC。而长包为数据长度+6字节,第一字节为包类型,第二三字节为数据长度,第四字节为前ECC,随后是数据payload,最后是两字节的CRC。所有传输低位在前。以下是一个长包的例子。

1556590085707-dsi_4.png

无论是长包还是短包,第一个字节均为包类型,其中高2位为虚拟通道ID(VC),在同一条DSI总线被多个设备共享时使用。这里不讨论这种情况,所以应该始终为0;低6位为数据类型(DT),是一些预先定义的值,用于区分不同用途的包,后面会讲到。

ECC只考虑前三字节,计算方法如下:

P7 = 0
P6 = 0
P5 = D10^D11^D12^D13^D14^D15^D16^D17^D18^D19^D21^D22^D23
P4 = D4^D5^D6^D7^D8^D9^D16^D17^D18^D19^D20^D22^D23
P3 = D1^D2^D3^D7^D8^D9^D13^D14^D15^D19^D20^D21^D23
P2 = D0^D2^D3^D5^D6^D9^D11^D12^D15^D18^D20^D21^D22
P1 = D0^D1^D3^D4^D6^D8^D10^D12^D14^D17^D20^D21^D22^D23
P0 = D0^D1^D2^D4^D5^D7^D10^D11^D13^D16^D20^D21^D22^D23

以下是参考实现:

; name: dsi_ecc
; description:
;   calculate DSI packet header ECC checksum
; parameter:
;   B: first byte
;   C: second byte
;   D: third byte
; return:
;   A: forth byte (ECC byte)
; caller saved:
;   A, E, H, L
dsi_ecc__lut:
    ; ECC LUT
    db $ef, $fc, $00
    db $df, $03, $f0
    db $b8, $e3, $8e
    db $74, $9a, $6d
    db $f2, $55, $5b
    db $f1, $2c, $b7

dsi_ecc:
    ld hl, dsi_ecc__lut
    ld e, 6
    ld a, 0
    ccf
dsi_ecc__loop:
    ; save the iterator and arguments
    push bc
    push de
    ; save the result of last round
    push af
    ; load the lut and calculate this round
    ld a, [hl+]
    and a, b
    call byte_parity
    ld e, a
    ld a, [hl+]
    and a, c
    call byte_parity
    xor a, e
    ld e, a
    ld a, [hl+]
    and a, d
    call byte_parity
    xor a, e
    ; now a have the result of this round
    ld e, a
    ; fetch last round result and shift left
    pop af
    sla a
    ; combine the result from this round
    or a, e
    ; restor the iterator and arguments
    pop de
    pop bc
    dec e
    jr nz, dsi_ecc__loop
    ; finished
    ret

这里不讨论长包的CRC问题,实际使用时可以直接将CRC字节设为0。

命令模式与视频模式

DSI的传输在概念上大致可以分为两种模式,一种是命令模式,另外一种是视频模式。命令模式的概念接近于传统的DBI屏幕,发送给屏幕的是单独的指令或者数据,这些指令或者数据可以是控制屏幕的寄存器,如电源,显示模式等等,也可以是像素。但是这些包并不和屏幕本身的扫描时序挂钩,收发都是和屏幕扫描异步的,所以传输速度可快可慢。视频模式的概念接近于传统的DPI屏幕,发送给屏幕的永远是连续的像素数据加上行场同步。同步信号直接控制了屏幕的扫描,传输速度必定等于扫描速度。DSI同时支持了这两种模式,通常而言屏幕也确实需要同时用到两者:命令模式用于配置屏幕电源,而视频模式用于传输图像。视频模式必须在差分模式下使用,而命令模式可以在单端模式和差分模式下使用。

这两种模式的区别其实只是发送的数据包的区别罢了。实际上并不像LP和HS模式一样存在“模式切换”一类的说法。

在命令模式下,常用的数据包类型(DT)有三种:

  • 0x05 DCS Short WRITE, no parameters.
  • 0x15 DCS Short WRITE, 1 parameter.
  • 0x39 DCS Long WRITE

其中前两种为短包,后者为长包。三者根据长度区别。如常见的开屏幕指令0x29,没有任何参数,就应该使用0x05包,随后第二字节为0x29,第三字节为0x00,最后是ECC。又比如设定显示功能指令,指令为0xb6,参数为0x8a,0x07,0x27,这就需要使用0x39包,第二、三字节设置成0x04、0x00,表示payload长度为4字节,随后的payload中就填入0xb6,0x8a,0x07,0x27。

在视频模式下,常用的数据包类型(DT)有9种,其中以下的用于发送同步信号,均为短包:

  • 0x01 V Sync Start
  • 0x11 V Sync End
  • 0x21 H Sync Start
  • 0x31 H Sync End

以下的用于发送像素信号,均为长包:

  • 0x19 Blanking Packet
  • 0x0e Packed Pixel Stream, 16-bit RGB
  • 0x1e Packed Pixel Stream, 18-bit RGB
  • 0x2e Loosely Packed Pixel Stream, 18-bit RGB
  • 0x3e Packed Pixel Stream, 24-bit RGB

这些包可以直接翻译成原本的DPI信号,Start信号就是把Sync信号设置为有效,而End信号就是把Sync信号置无效。Blanking Packet表示发送像素,但是DE为无效。Pixel Stream则是发送有效数据,DE为有效。值得注意的是,在DSI中,为了时序表示的准确性,Vsync Start和Vsync End信号同时表示了Hsync Start,为此第一行开始的时候并不需要发送Hsync Start。Start信号也可以被认为是Sync信号出现有效边沿,为此End信号也是可选的。

视频计时

DSI的视频计时和VGA、DPI等等其它的计时都是类似的,可以参考如下的示意图:

1556590094330-dsi_5.png

如果用伪代码写出发送一帧的流程,应该如下:

// DSI
#define V_SYNC_START         0x01
#define V_SYNC_END           0x11
#define H_SYNC_START         0x21
#define H_SYNC_END           0x31
#define BLANKING             0x19
#define PIXEL_STREAM_16BPP   0x0e
#define PIXEL_STREAM_24BPP   0x3e

// Standard VGA
#define V_TOTAL              525
#define VS                   2
#define VBP                  33
#define VACT                 480
#define VFP                  10
#define H_TOTAL              800
#define HS                   96
#define HBP                  48
#define HACT                 640
#define HFP                  16

// 16BPP
#define BPP                  2
#define PIXEL_STREAM         PIXEL_STREAM_16BPP
uint16_t pixel[VACT * HACT];

for (int y = 0; y < V_TOTAL; y++) {
    if (y == 0)
        dsi_short_packet(V_SYNC_START, 0x00, 0x00);
    else if (y == VS)
        dsi_short_packet(V_SYNC_END, 0x00, 0x00);
    else
        dsi_short_packet(H_SYNC_START, 0x00, 0x00);
    dsi_long_packet(BLANKING, HS * BPP, empty_buffer);
    dsi_short_packet(H_SYNC_END, 0x00, 0x00);
    dsi_long_packet(BLANKING, HBP * BPP, empty_buffer);
    if ((y >= (VS + VBP)) && (y < (VS + VBP + VACT)))
        dsi_long_packet(PIXEL_STREAM, HACT * BPP, &(pixel[(y - VBP - VS) * HACT * BPP]));
    else
        dsi_long_packet(BLANKING, HACT * BPP, empty_buffer);
    dsi_long_packet(BLANKING, HFP * BPP);
}

整体屏幕初始化流程

整体而言,屏幕的初始化流程应该如下:

  • 通过RST引脚复位屏幕控制器
  • 按照正常的屏幕控制器初始化序列,利用LPDT或者HSDT发送DCS写入数据包
  • 时钟线开始发送高速时钟
  • 开始视频计时,输出视频信号

结论

没了,我说完了