OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 44|回复: 0

《ESP32-P4开发指南— V1.0》第二十二章 MIPILCD实验

[复制链接]

1224

主题

1238

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5268
金钱
5268
注册时间
2019-5-8
在线时间
1340 小时
发表于 昨天 10:00 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2025-12-26 10:00 编辑

第二十二章 MIPILCD实验

1)实验平台:正点原子DNESP32P4开发板

2)章节摘自【正点原子】ESP32-P4开发指南— V1.0

3)购买链接:https://detail.tmall.com/item.htm?id=873309579825

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子DNESP32S3开发板技术交流群:132780729


2.jpg

3.png

本章将继续学习ESP_IDF的LCD外设驱动,主要学习MIPI接口屏幕的驱动方法。本章用到的是ESP32-P4的MIPI_DSI接口驱动MIPILCD屏幕,实现和MIPILCD屏之间的通信,实现ASCII字符、图形和彩色的显示。
本章分为如下几个小节:
22.1 MIPI介绍和ESP32-P4的MIPI_DSI介绍
22.2 硬件设计
22.3 程序设计
22.4 下载验证


22.1 MIPI介绍和ESP32-P4的MIPI_DSI介绍

22.1.1 MIPI介绍
MIPI即移动产业处理器接口,是Mobile Industry Processor Interface缩写,2003年由ARM、Nokia、ST和TI等公司成立的一个联盟,所以大家在看MIPI相关文档的时候常看到MIPI Aliance。MIPI官方网址为https://www.mipi.org/。当前MIPI联盟已经有很多成员, 比如大家熟知的ARM、ST、高通、TI、苹果、海思等,基本上囊括了主流的移动处理器生产厂家,如图22.1.1.1所示:

第二十二章 MIPILCD实验518.png
图22.1.1.1 MIPI联盟成员(部分)

MIPI联盟主要是为移动处理器定制标准接口和规范,开发的接口广泛应用于处理器、相机、显示屏、基带调制解调器等设备。比如:
·MIPI DSI(显示屏接口)
·MIPI CSI(摄像头接口)
·MIPI I3C
·MIPI RFFE(射频前端控制接口)
·MIPI SPMI(系统电源管理接口)
·等等。
MIPI通过定义一套协议和标准,满足各个子系统之间互联,确保不同公司开发的产品可以兼容连接,减少协议标准。图22.1.1.2就是MIPI相关协议在移动处理器中的使用:


第二十二章 MIPILCD实验779.png
图22.1.1.2 移动处理器中MIPI的使用场合

从图22.1.1.2可以看出,MIPI下的各种协议、接口在移动处理器中使用非常广泛,典型的就是MIPI DSI和MIPI CSI。
MIPI主要包括四个方面,如图22.1.1.3所示:


第二十二章 MIPILCD实验901.png
图22.1.1.3 MIPI框架

从图22.1.1.3可以看出,MIPI主要有四个方向的协议:
①、Multimedia,多媒体。
②、Control&Data,控制和数据。
③、Chip-to-Chip Inter Process Communications,
④、Debug&Trace,调试和追踪
我们依次来简单看一下这四部分都有哪些内容。
1、Multimedia
Multimedia部分框图如图22.1.1.4所示:


第二十二章 MIPILCD实验1125.png
图22.1.1.4 Multimedia部分框图

Multimedia就是多媒体部分,分为如下几部分:
·摄像头,应用层有CCS,协议层主要有CSI-2、CSI-3,物理层有A-PHY、C-PHY、D-PHY和M-PHY。
·屏幕,应用层有DCS,协议层主要有DSI,物理层有A-PHY、C-PHY、D-PHY。
·触摸,应用层有TCS,协议层是I3C。
·存储,UFS协议,这个是目前手机以及平板上最常用的存储协议,物理层为M-PHY。
·音频,协议层有SLIMIbus和SoundWire。
多媒体部分我们用的最多就是DSI和CSI,DSI应用于屏幕,CSI用于摄像头。对应的物理层协议有A-PHY、C-PHY、D-PHY和M-PHY。
D-PHY:目前用的最多的接口,不管是摄像头还是屏幕,D-PHY接口为1/2/4lane(lane可以理解为通道,也就是1/2/3/4通道,每个通道2条差分线),外加一对时钟线,数据线和时钟线都是差分线,为电流驱动型,不同版本的D-PHY速度不同,比如ESP32-P4用的V1.1版本的D-PHY双lane最高可到3Gbps。D-PHY最多10根线,有专门的时钟线来进行同步。
C-PHY:随着屏幕和摄像头的分辨率以及帧率越来越高,D-PHY的带宽越来越不够用。C-PHY应运而生,C-PHY接口是1/2/3 Trio,每个Trio有3根线,最高9根线,没有专用的时钟线了。C-PHY目前在高端旗舰手机芯片中可能会用到,本教程不讲解C-PHY。
A-PHY:主要为汽车自动驾驶而生,目前汽车自动驾驶发展非常迅猛, ADAS(高级驾驶员辅助系统)摄像头于车载娱乐屏幕越来越多,分辨率也越来越高,而且车载摄像头和娱乐屏幕分布比较分散,到主控的距离一般比较长。但是C-PHY和D-PHY的距离太短,最多不超过15CM,显然不适合用在当今高度智能化的车载领域。A-PHY于2020年9月发布,用于长距离、超高速的汽车应用中,比如ADAS、自动驾驶系统 (ADS)、车载信息娱乐系统 (IVI) 和其他环绕传感器。
M-PHY:目前主要用在USF存储中。
2、Control&Data
Control&Data部分框图如图22.1.1.5所示:


第二十二章 MIPILCD实验2067.png
图22.1.1.5 Control&Data

图22.1.1.5中主要是RF、电源管理以及I3C通信接口相关的协议。
3、Chip-to-Chip Inter Process Communications
Chip-to-Chip Inter Process Communications框图如图22.1.1.6所示:


第二十二章 MIPILCD实验2232.png
图22.1.1.6 Chip-to-Chip Inter Process Communications框图

4、Debug&Trace
Debug&Trace框图如图22.1.1.7所示:


第二十二章 MIPILCD实验2330.png
图22.1.1.7 Debug&Trace框图

关于MIPI联盟就介绍到这里,感兴趣的可以去MIPI联盟官网上去看一下。

22.1.2 MIPI DSI概述

22.1.2.1 MIPI DSI协议综述
本章我们是来学习如何驱动MIPI接口屏幕,所以需要学习的就是MIPI DSI。DSI全称是Display Serial Interface,是主控和显示模组之间的串行连接接口,图22.1.2.1.1展示了主控和屏幕之间的连接方式:

第二十二章 MIPILCD实验2575.png
图22.1.2.1.1 DSI主控与屏幕之间接口示意图

MIPI DSI接口分为数据线和时钟线,均为差分信号。数据线可选择1/2/3/4 lanes,时钟线有一对,最多10根线。MIPI DSI以串行的方式发送指令和数据给屏幕,也可以读取屏幕中的信息。如果屏幕的分辨率和帧率越高,需要的带宽就越大,就需要更多的数据线来传输图像数据,但是ESP32-P4只支持使用2lanes来驱动MIPI屏幕。对于MIPI DSI接口而言,最常用的就是2 lanes和4 lanes。

22.1.2.2 MIPI DSI分层
和网络协议栈一样,MIPI DSI也是分层的,如图22.1.2.2.1所示:


第二十二章 MIPILCD实验2873.png
图22.1.2.2.1 MIPI DSI协议分层

从图22.1.2.2.1可以看出,MIPI DSI一共有四层,从上往下依次为:
·应用层
·协议层
·通道管理层
·物理层
1、应用层
应用层处理更高层次的编码,将要显示的数据打包进数据流中,下层会处理并发送应用层的数据流。发送端将命令以及数据编码成MIPI DSI规格的格式,接收端则将接收到的数据还原为原始的数据。
2、协议层
协议层主要是打包数据,在原始的数据上添加ECC和校验和等东西。应用层传递下来的数据会打包成两种格式的数据:长数据包和短数据包,关于长短数据包后面会有详细讲解。发送端将原始数据打包好,添加包头和包尾,然后将打包好的数据发送给下层。接收端介绍到下层传来的数据包以后执行相反的操作,去除包头和包尾,然后使用ECC进行校验接收到的数据,如果没问题就将解包后的原始数据交给应用层。
3、链路层
链路层负责如何将数据分配到具体的通道上,若MIPI DSI可以支持1/2/3/4 Lane,采用几通道取决于你的实际应用,如果带宽需求低,那么2 Lane就够了,带宽高的话就要4 Lane。协议层下来的数据包都是串行的,如果只有1 Lane的话,那就直接使用这1 Lane将数据串行的发送出去,如果是2/4 Lane的话数据该如何发送呢?如图22.1.2.2.2所示:


第二十二章 MIPILCD实验3453.png
图22.1.2.2.2 发送端通道管理层处理示意图

图22.1.3.2左侧是1 Lane的时候数据如何在数据线上传输,由于只有1 Lane,所以上层传递下来的数据就只能按照Byte0~ByteN这样的顺序传输。
图22.1.3.2右侧是4 lane的时候数据传输方式,由于采用了4通道,那么上层传递下来的数据就要平均分配给4个通道。分配方法也很简单,每个通道一个字节,比如Byte0是Lane0,Byte1是Lane1,Byte2是Lane2,Byte3是Lane3,如此反复循环。
如果要发送的数据和通道数不是整数倍数,那么先发送完的数据通道就进入EoT(End of Transmission)模式。我们来看一个2 Lane整数倍传输和非整数倍传输的图,如图22.1.2.2.3所示:


第二十二章 MIPILCD实验3801.png
图22.1.2.2.3 整数倍和非整数倍数据传输方式

图22.1.2.2.3中上部分就是整数倍传输,2条通道一起结束,进入EoT模式。下图是非整数倍传输,其中Lane 1先传输完,所以Lane 1先进入EoT模式。同理,3/4 Lane也一样。
在接收端执行相反的操作,将Lane上的数据整理打包成串行数据上报给上层,如图22.1.2.2.4所示:


第二十二章 MIPILCD实验3980.png
图22.1.2.2.4 接收端通道管理层处理示意图

4、物理层
物理层就是最底层了,完成MIPI DSI数据在具体电路上的发送与接收,与物理层紧密相关的就是D-PHY。物理层规定了MIPI DSI的整个电气属性,信号传输的时候电压等,关于物理层后面会详细讲解。

22.1.3 MIPI DSI物理层和D-PHY
MIPI DSI的物理层也叫PHY层,前面说了MIPI有C-PHY、D-PHY等,只是在MIPI DSI领域D-PHY用的最多,所以这里可以简单的认为MIPI DSI物理层说的就是D-PHY,本文后面统一用D-PHY来表示MIPI DSI的物理层。
D-PHY是一个源同步、高速、低功耗、低开销的PHY,特别适合移动领域。D-PHY主要用于主处理器的摄像头和显示器外设,比如MIPI摄像头和屏幕。D-PHY提供了一个同步通信接口来连接主机和外设,在实际使用中提供一对时钟线以及一对或多对信号线。时钟线是单向的,由主控产生,发送给设备。数据线根据实际配置,可以有1~4 Lane,只有Data0这一组数据线可以是单向也可以是双向的,其他组的数据线都是单向的。
数据链路分为High-Speed模式和Low-Power模式,也就是图22.2.1.1中的HS和LP。HS模式用来传输高速数据,比如屏幕像素数据。LP模式用来传输低速的异步信号,一般是配置指令,屏幕的配置参数就是用LP模式传输的。HS模式下每个数据通道速率为80~1500Mbps, LP模式下最高10Mbps。

22.1.3.1 什么是Lane
我们前面已经提了很多次“Lane”这个词,英文直译过来就是:航道、跑道。在这里就是在主控与外设直接传输数据的通道,MIPI DSI包括一个时钟Lane和多个数据Lane,每条Lane使用2根差分线来连接主控和外设。收发端都有对应的Lane模块来处理相关的数据,一个完整的Lane模块如图22.1.3.1.1所示:

第二十二章 MIPILCD实验4804.png
图22.1.3.1.1 通用的Lane模块

从图22.1.3.1.1可以看出,一个通用的Lane模块,包括一个高速收发器和一个低速收发器,其中高速收发器有HS-TX、HS-RX,低速收发器有LP-RX和LP-TX,以及一个低速竞争检测器LP-CD。实际的Lane模块是在图22.1.3.1.1中简化而来的,比如对于高速单向数据通道,可能只有HS-TX或者HS-RX。

22.1.3.2 D-PHY信号电平
Lane分为HS和LP两种模式,其中HS采用低压差分信号,传输速度高,但是功耗大,信号电压幅度100mv~300mV,中心电平200mV。LP模式下采用单端驱动,功耗小,速率低(<10Mbps),信号电压幅度0~1.2V。在LP模式下只使用Lane0(也就是数据通道0),不需要时钟信号,通信过程的时钟信号通过Lane0两个差分线异或得到,而且是双向通信。
HS和LP模式下的信号电平如图22.1.3.2.1所示:


第二十二章 MIPILCD实验5219.png
图22.1.3.2.1 HS和LP模式信号电平

图22.1.3.2.1中蓝色实线是LP模式下的信号波形示例,电压为0~1.2V。绿色虚线是LP模式下信号的高低电平门限。红色实线是HS模式下的信号波形示例,中心电平200mV。

22.1.3.3 通道状态
上面说了HS模式下是单向差分信号,主控发送(HS_TX),外设接收(HS_RX)。而LP是双向单端信号,接收和发送端都有LP_TX和LP_RX,注意只有Lane0能做LP。
由于HS采用差分信号,所以只有两种状态:
HS-0:高速模式下Dp信号低电平,Dn信号高电平的时候。
HS-1:高速模式下Dp信号高电平,Dn信号低电平的时候。
LP模式下有两根独立的信号线驱动,所以有4个状态:
LP-00:后面的“00”对应两根信号线的电平状态,第1个0表示Dp为低电平,第2个0表示Dn为低电平。如果是高电平,那么就为1。因此LP模式剩下的三个状态就是LP-01、LP-10和LP-11。
这6种状态对应的功能如图22.1.3.3.1所示:


第二十二章 MIPILCD实验5669.png
图22.1.3.3.1 6种状态

通过图22.1.3.3.1种这6个状态的转换,D-PHY就能工作在不同的工作模式。

22.1.3.4 数据Lane三种工作模式D-PHY协议规定了,通过Lane的不同状态转换有三种工作模式:控制模式、高速模式(Burst Mode)和Escape模式。控制模式和Escape模式都属于LP,高速模式属于HS。正常情况下,数据Lane工作在控制模式或者高速模式下。
1、高速模式(HS Burst Mode)
高速模式用于传输实际的屏幕像素数据,采用突发(Burst)传输方式。为了帮助接收端同步,需要在数据头尾添加一些序列,接收端在接收到数据以后要把头尾去掉。高速数据传输起始于STOP状态(LP-11),并且也终止于STOP状态(LP-11)。在高速模式下传输数据的时候, Lane始终工作于HS模式,提供DDR时钟,也就是双边沿时钟,在时钟频率不变的情况下,传输速率提高一倍,这样可以有效利用带宽。
当数据传输请求发出以后,数据Lane退出STOP模式进入到高速模式,顺序是:LP-11→LP-01→LP-00。然后发出一个SoT序列(Start-of-Transmission),SoT后面跟着的就是实际的负载数据。当负载数据传输结束以后会紧跟一个EoT序列(End-of-Transmission)序列,数据线直接进入到STOP模式。图22.1.3.4.1是一个基础的高速传输结构示意图:


第二十二章 MIPILCD实验6292.png
图22.1.3.4.1 基础的高速数据传说结构体

图22.1.3.4.1只是展示了一条Lane,负载数据前面是一个SoT,传输完成以后紧跟一个EoT,中间就是实际的负载数据。
一个完整的高速模式数据传输时序如图22.1.3.4.2所示:


第二十二章 MIPILCD实验6414.png
图22.1.3.4.2高速数据传输时序

图22.1.3.4.2中左侧蓝色部分是进入HS模式,要从LP-11→LP01→LP-00, 数据线进入到HS模式,也就是中间红色部分,传输实际的数据。传输完成以后重新进入到LP-11(STOP)模式,也就是右边的蓝色部分。
2、Escape模式
Escape是运行在LP状态下的一个特殊模式,在此模式下可以实现一些特殊的功能,我们给屏幕发送配置信息就需要运行在Escape模式下。数据线进入Escape模式的方式为:LP-11→LP-10→LP-00→LP-01→LP-00。退出Escape模式的方式为:LP-00→LP-10→LP-11,也就是最后会进入到STOP模式,进入和退出Escape模式的时序如图22.1.3.4.3所示:


第二十二章 MIPILCD实验6784.png
图22.1.3.4.3 Escape模式进入和退出

对于数据Lanes,进入Escape模式以后,应该紧接着发送一个8bit的命令来表示接下来要做的操作,命令如图22.1.3.4.4所示:

第二十二章 MIPILCD实验6882.png
图22.1.3.4.4 Escape模式命令

从图22.1.3.4.4可以看出,有三个可选的命令:LPDT(Low-Power Data Transmissio)、ULPS(Ultra-Low Power State)和Remote-Trigger(这里没有写错,因为这个命令大部分做复位操作,所以有些资料将这个命令也叫做Remote-Trigger)。
1)、LPDT命令
图22.1.3.4.4中第一个就是LPDT命令,命令序列为11100001,注意低bit先发送,所以对应的十六进制就是0X87(0X10000111)!LPDT直译过来就是低功耗数据传输,我们在初始化MIPI屏幕的时候发送的初始化序列就需要用LPDT命令,后面会给大家看逻辑分析仪采集到的实际数据。LPDT命令序列后面紧跟着就是要发送的数据,分为长包和短包两种,长短包结构后面会详细讲解。
2)、ULPS命令
ULPS命令是让Lane进入超低功耗模式。
3)、Remote-Trigger命令
注意,这里大家可能会疑惑,有的资料叫做Remote-Trigger,有的叫做Reset-Trigger,其实都是一个东西。因为本质是Remote Application,但是做的是Reset的工作,所以就产生了两种叫法,目前此命令就是用于远程复位。
Escape模式下发送这三个命令的时序图如图22.1.3.4.5所示:


第二十二章 MIPILCD实验7514.png
图22.1.3.4.5 Escape模式下各命令时序图

22.1.4 video和command模式
在MIPI DSI的链路层有两种模式:video(视频)和command(命令)模式,这个属于HOST端,也就是主控端,比如ESP32-P4的DSI HOST接口。video和command通常离不开HS和LP模式,但是video和command属于Host范畴,HS和LP属于D-PHY范畴。

22.1.4.1 command模式
command模式一般是针对那些含有buffer的MCU屏幕,比如STM32单片机驱动MCU屏的时候就是command模式。当画面有变化的时候,DSI Host端将数据发给屏幕,主控只有在画面需要更改的时候发送像素数据,画面不变化的时候屏幕驱动芯片从自己内部buffer里面提取数据显示,command模式下需要双向数据接口。一般此种模式的屏幕尺寸和分辨率不大,一般用在单片机等低端领域。command模式如图22.1.4.1.1所示:


第二十二章 MIPILCD实验7981.png
图22.1.4.1.1 command模式示意图

22.1.4.2 video模式
video模式没有framebuffer,需要主控一直发送数据给屏幕,和我们使用过的RGB接口屏幕类似。但是MIPI DSI没有专用的信号线发送同步信息,比如VSYNC、HSYNC等,所以这些控制信号和RGB图像数据以报文的形式在MIPI数据线上传输。基本上我们说的“MIPI屏”就是工作在video模式下,包括我们使用的RK3568,其工作模式就是video。video模式如图22.1.4.2.1所示:


第二十二章 MIPILCD实验8257.png
图22.1.4.2.1 video模式示意图

22.1.5 DPI格式
DPI接口全称Display Pixel Interface,就是我们常说的RGB接口,在21.1.1章节有做介绍,大家可以自行回顾一下RGB接口涉及到的时序参数。
关于DPI这两种格式的详细协议内容,请参考《MIPI Alliance Standard for Display Pixel Interface(DPI-2).pdf》这份文档。
MIPI DSI接口的屏幕里面传输的是DPI格式的数据。DPI格式的数据时序,主要就是要关注一些时序参数,比如thpw、thb、thfp、tvpw、tvb和tvfp。这些参数都是为了锁定有效的像素数据,都可以从MIPI LCD屏幕数据手册中找到。

22.1.6 video模式下三种传输方式
对于video模式下的数据传输有三种时序模式:
●Non-Burst Mode with Sync Pulses:外设可以准确的重建原始的视频时序,包括同步脉冲宽度。
●Non-Burst Mode with Sync Events:和上面的模式类似,但是不需要精准的重建同步脉冲宽度,取而代之的是发送一个“Sync event”包。
●Burst Mode:此模式下发送RGB数据包的时间被压缩,这样可以在发送一行数据以后尽快进入到LP模式,以节省功耗。

22.1.6.1 Non-Burst Mode with Sync Pluses
此模式下外设可以准确的重建原始视频的时序、同步信号。通过在DSI接口上发送DPI时序,可以精确的匹配DPI像素传输速率以及时序宽度等,比如同步脉冲。所以此模式下每一个Sync Start(HSA)都要有一个对应的Sync End(HSE),此模式下的时序图如图22.1.6.1.1所示:

第二十二章 MIPILCD实验9035.png
图22.1.6.1.1 Non-Burst Mode with Sync Pluses模式时序图

Non-Burst Mode with Sync Pluses模式需要精准控制同步时序宽度,所以有Sync Start和Sync End,比如图22.1.6.1.1中的VSS和VSE、HSS和HSE信号,也就是水平同步开始和结束信号。

22.1.6.2 Non-Burst Mode with Sync Events
此模式和上一小节讲解的Non-Burst Mode with Sync Pluses模式类似,只是此模式下不需要精准的控制同步时序的宽度,所以此模式只有Sync Start,时序如图22.1.6.2.1所示:


第二十二章 MIPILCD实验9352.png
图22.1.6.2.1 Non-Burst Mode with Sync Events模式时序图

从图22.1.6.2.1可以看出此模式下只有Sync Start,没有Sync End,比如只有VSS和HSS,而没有VSE和HSE。

22.1.6.3 Burst Mode
Burst Mode是MIPI DSI最常用的模式,相比于Non-Burst Mode with Sync Events模式,Burst Mode可以快速的完成一帧或一行图像像素的传输,这样可以是数据线有更多的时间处于LP模式以节省功耗。此模式下时序图如图22.1.6.3.1所示:


第二十二章 MIPILCD实验9634.png
图22.1.6.3.1 Burst Mode模式时序图

图22.1.6.3.1中VACT Lines中,RGB图像像素数据会尽快传递完成,然后进入到BLLP以节省功耗。

22.1.7 长短数据包

22.1.7.1 数据包概述
在MIPI DSI的数据传输中,不管是并行数据、信号事件还是命令,都需要按照协议打包成数据包。按照规定的协议添加头尾等信息,然后通过数据Lane将打包好的数据发送出去。
如一次传输只发送一个数据包,那么在传输多个数据包就会花费大量的开销在LPS和HS切换上,这样会严重的浪费带宽。为此,MIPI DSI协议允许在一次传输中可以串行的发送多个数据包,这样就可以大幅的提高带宽利用率,这对于像外设初始化这种操作非常有益,比如我们在初始化屏幕的时候会发送大量的初始化命令。
数据包第一个字节是DI(Data Identifier),用来指定当前数据包的含义,数据包        一共有两种数据包:
● 短数据包:固定 4个字节,包括ECC。短包一般用于Command模式下发送命令和参数,但是在实际使用中,Video模式下也用短包发送命令参数信息。其他的一些短包发送一些事件,比如H Sync和V Sync边沿。
● 长数据包:通过2个字节的 WC(Word Count)域来指定负载长度,负载长度范围:0~216-1个字节,也就是0~65535个字节。因此一个长数据包最多有65541个字节:
1个字节的DI+2个字节的WC+1个字节的ECC+65535个字节的负载+2个字节的校验和=1+2+1+65535+2=65541。
我们在22.1.3.4小节讲解Escape模式的时候说过,进入Escape模式以后紧跟着一个8bit的命令来表示接下来要做的操作,我们一般使用LPDT命令+具体的配置参数来完成向MIPI屏幕发送初始化参数的操作。但是并不是直接在LPDT命令后面跟着发送想要的时序参数就行了,而是要按照上面说的MIPI DSI格式将时序参数打包成长短数据包发送出去。

22.1.7.2 数据包字节排序策略
数据包都是以字节形式通过接口传输,同一个字节LSB先传输,MSB后传输,也就是低bit先发,高bit后发。如果某个域有多个字节,那么低字节(LS Byte)先发,高字节(MS Byte)后发,比如WC字段有2个字节,那么低字节的就先发,高字节的后发。但是,对于负载段就不用按照这个规定来,而是按照字节顺序发送。
图22.1.7.2.1就是一个长包数据的发送方式:

第二十二章 MIPILCD实验10688.png
图22.1.7.2.1 长包数据发送格式

22.1.7.3 长数据包
长数据包结构如图22.1.7.3.1所示:


第二十二章 MIPILCD实验10774.png
图22.1.7.3.1长数据包结构

图22.1.7.3.1中绿色的部分就是长包结构,长包有3部分:32-bit的PH(包头)、用于自定义的负载数据、16-bit的包尾(PF)。PH有3部分:8-bit的DI,16-bit的WC以及8-bit的ECC。PF只有1部分:16-bit的校验和,因此长包数据长度范围是6~65541个字节。

22.1.7.4 短数据包
短包结构如图22.1.7.4.1所示:


第二十二章 MIPILCD实验11006.png
图22.1.7.4.1 短数据包结构

图22.1.7.4.1就是短数据包结构,只有一个PH(包头),PH分为3部分:和长数据包一样,第1个就是8-bit的DI域;接下来是2个字节的数据负载域,也就是用户要实际发送的内容;最后是一个8-bit的ECC域,可以实现1bit纠错,2比特错误检测。
我们重点来看一下DI域,因为长短数据包第第一部分就是DI域,而且是含义是相同的。DI域由两部分组成:虚拟通道和负载数据类型,结构如图22.1.7.4.2所示:


第二十二章 MIPILCD实验11263.png
图22.1.7.4.2 DI域接口

其中bit7:6这两位指定虚拟通道,在本章节我们不研究这个虚拟通道。bit5:0这6位就是指定后面要发送的负载数据类型,也就是那些负载数据是做啥的。注意,这个数据类型才是我们要学习的重点,后面小节会详细讲解MIPI DSI协议所支持的数据类型。

22.1.8 指令类型
在上一小节讲解长短数据包的时候说过,DI域包含了后面负载数据的数据类型,MIPI DSI协议已经定义好了这些类型,这里只介绍最常用的。这里叫说数据类型,有些资料也叫做指令,有两种指令集:Generic指令和DSC指令,这两个的区别在于Generic指令是不区分index和parameter的,而DSC会默认把data0作为index,然后计算parameter数目。关于DCS相关的详细请参考《Specification for Display Command Set (DCS).pdf》。
指令分为主机发向外设,以及外设发向主机两种,我们要初始化屏幕肯定用的是主机发向外设的,其指令集合如图22.1.8.1所示:

第二十二章 MIPILCD实验11732.png
图22.1.8.1 主控向外设发送的命令

图22.1.8.1中有DCS指令,也有Generic指令。每个指令是长数据包还是短数据包,在最后一列都注明了。图中这些指令的详细含义请参考《MIPI DSI Specification_v1-3.pdf》的“8.8 Processor-to-Peripheral Transactions – Detailed Format Description”小节。
我们稍后在具体初始化MIPI屏幕的时候就要用到上面这些指令。

22.1.9 MIPI DSI时钟计算
屏幕传输一帧图形数据的时候要用到的理论像素时钟不是1280*720,因为还涉及到thpw、thb、thfp、tvpw、tvb和tvfp等参数,因此屏幕真实的水平和垂直像素数时钟如下:
        水平:H_total=HPW+HB+HACTIVE+HFP
        垂直:V_total=VPW+VB+VACTIVE+VFP
其中,HPW和VPW是水平和垂直同步信号宽度,HB和VB是水平和垂直后廊,HACTIVE是屏幕的水平有效像素,VACTIVE是屏幕的宽度有效像素,HFP和VFP是水平和垂直前廊。屏幕的这些参数都能在屏幕手册里面找到,比如正点原子两款5.5寸MIPI屏幕参数如表22.1.9.1所示:

1.png
表22.1.9.1 正点原子MIPI屏幕时序参数

1秒钟的像素数量就是:
Pixel_total=H_total×V_total×fps
其中fps就是屏幕帧率,一般是60帧,也就是1秒钟刷新60张图像,Pixel_total就是1秒钟要传输的总的像素点。
如果屏幕采用RGB888格式,那么1个像素就是24bit,如果是RGB565那就是16bit,这个叫色深,我们一般都使用RGB565。那么总的bit时钟就是:
Bit_total=Pixel_total×色深(比如24或16)
得到的Bit_total是总的bit时钟数,但是MIPI DSI可以配置多条数据lane,一般是2或4lane,也就是有2个或者4个通道,所以每个通道的bit时钟就是:


12.png

计算出来的Bit_clk就是MIPI DSI的时钟,但是由于MIPI DSI是双边沿采集,所以最终的DSI CLK时钟还要除以2:

13.png

总结一下DSI CLK时钟的计算公式如下:

15.png

我们以正点原子720*1280这款5.5寸MIPI屏幕为例,采用RGB565格式,帧率为60fps,使用2lane传输数据,那么此屏幕的DSI时钟为:
Dsi_clk= (8+52+720+48)×(6+15+1280+16)×60×16/2/2
              = 828×1317×60×16/2/2
              = 261,714,240 Hz
           ≈261M Hz
261M就是我们用示波器测出来的频率,注意这个261M只是理论值,实际值要高!因为要考虑到开销,后面会讲。
我们一般说的MIPI DSI时钟要在这个时间测量到的频率上乘以2,因为双边沿采集嘛,所以MIPI DSI速率就是261714240*2=523,428,480 Hz≈523M。
注意重点:
后面在程序中设置MIPI DSI速率时,不能直接设置前面计算出来的理论MIPI DSI速率,在前面学习长短包的时候就知道,实际的MIPI DSI通信中还有其他的开销。假如直接将MIPI DSI的速率设置成523M,那么实际的屏幕帧率肯定到不了60fps。但是我们也没必要研究具体用了多少开销,实际精准的速率是多少,这样太耗费时间了,难度也很大。一般都是在理论速率上加上一些余量,参考ESP32-P4驱动其他MIPI屏幕,那么实际设置的MIPI DSI速率大于750即可。

22.1.10 ESP32-P4的MIPI DSI介绍
ESP32-P4带有一个MIPI DSI接口,用于连接MIPI接口的显示屏,具有如下特性:
● 符合MIPI DSI协议。
● 使用DPHY v1.1版本。
● 2-lane x 1.5 Gbps。
● 输入格式支持RGB888、RGB666、RGB565、YUV422。
● 输出格式支持RGB888、RGB666、RGB565。
● 使用video mode输出视频流
● 支持输出固定图像pattern
MIPI DSI接口使用专用数字管脚,管脚序号为34~40。
关于ESP32-P4的MIPI DSI外设就介绍到这里,具体内容可以自行查阅ESP32-P4参考手册。

22.2 硬件设计

22.2.1 例程功能
使用ESP32-P4底板的MIPI屏幕FPC座实现MIPILCD模块的显示。通过把正点原子的MIPI屏幕模块插入底板上的MIPI屏幕FPC座,按下复位之后,就可以看到MIPILCD模块不停地显示一些信息并不断切换底色。LED0闪烁用于提示程序正在运行。

22.2.2 硬件资源
1)LED灯
        LED        0        - IO51
2)MIPILCD
        DSI_D0_P(固定引脚)                DSI_D0_N(固定引脚)
        DSI_D1_P(固定引脚)                DSI_D1_N(固定引脚)
        DSI_CK_P(固定引脚)                DSI_CK_N(固定引脚)
        LCD_RST         - IO52
        CT_RST                 - IO45                        CT_INT        - IO21
        IIC_SCL                - IO32                        IIC_SDA        - IO33

22.2.3 原理图
MIPILCD原理图,如下图所示。

第二十二章 MIPILCD实验14241.png
图22.2.3.1 MIPILCD原理图

从上图可知,正点原子ESP32-P4开发板的MIPI屏幕接口采用2Lane,其中LCD_RST是屏幕的复位引脚,通过一个电阻分压电路得到MIPI_RST_1V8,因为MIPI屏幕要求复位引脚电平为1.8V。

22.3 程序设计

22.3.1 LCD的IDF驱动
LCD外设驱动位于ESP-IDF下的components/esp_lcd目录下。要使用esp_lcd功能,需要导入一下头文件:
  1. <font size="3">#include "esp_lcd_panel_interface.h"        /* LCD面板结构体类型 */</font>
  2. <font size="3">#include "esp_lcd_panel_io.h"                        /* 驱动芯片接收/发送命令,发送颜色数据等函数 */</font>
  3. <font size="3">#include "esp_lcd_panel_vendor.h"                /* 包含LCD外设驱动支持的几款驱动芯片 */</font>
  4. <font size="3">#include "esp_lcd_panel_ops.h"                        /* LCD设备接口函数(reset/init/del等) */</font>
  5. <font size="3">#include "esp_lcd_panel_commands.h"        /* LCD驱动芯片的命令 */</font>
  6. <font size="3">#include "esp_lcd_mipi_dsi.h"                        /* MIPILCD的函数 */</font>
复制代码
MIPILCD 驱动流程可大致分为三个部分:初始化接口设备、移植驱动组件和初始化 LCD 设备。
接下来,作者就按照这三个部分分别介绍用到的函数。
初始化接口设备
初始化接口设备需要先初始化总线,再创建接口设备。
1,初始化MIPI_DSI总线函数esp_lcd_new_dis_bus
该函数用于创建DSI总线,并对D-PHY进行初始化设置,其函数原型如下:
  1. esp_err_t esp_lcd_new_dsi_bus(const esp_lcd_dsi_bus_config_t *bus_config, esp_lcd_dsi_bus_handle_t *ret_bus)
复制代码
函数形参:

2.png
表22.3.1.1 esp_lcd_new_dsi_bus函数形参描述

函数返回值:
ESP_OK表示MIPI_DSI总线初始化成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_NO_MEM表示内存不足。
ESP_ERR_NOT_FOUND表示没有空闲的DSI总线。
ESP_FAIL表示创建MIPI_DSI发生其他错误。
bus_config为指向DSI总线配置结构体的指针,esp_lcd_dsi_bus_config_t结构体中包含很多成员,如下代码所示。
  1. typedef struct {
  2.     int bus_id;                                          /* 指定要使用的DSI主机 */
  3.     uint8_t num_data_lanes;                          /* 要使用的数据通道数 */
  4.     mipi_dsi_phy_clock_source_t phy_clk_src;         /* DPHY时钟源 */
  5.     uint32_t lane_bit_rate_mbps;                     /* 数据通道的比特率(Mbps) */
  6. } esp_lcd_dsi_bus_config_t;
复制代码
esp_lcd_dsi_bus_config_t结构体的bus_id注意要从0开始编号;num_data_lanes要根据芯片支持的数量进行设置;DPHY时钟源是可选XTAL、F160M和F240M,都有对应的宏选择MIPI_DSI_DPI_CLK_SRC_XTAL、MIPI_DSI_DPI_CLK_SRC_PLL_F160M和MIPI_DSI_DPI_CLK_SRC_PLL_F240M,直接设置默认MIPI_DSI_DPI_CLK_SRC_DEFAULT即可,即选择F240M;而lane_bit_rate_mbps要根据dsi时钟计算进行设置。
ret_bus为指向esp_lcd_dsi_bus_handle_t结构体的指针,esp_lcd_dis_bus_handle_t结构体可以不需要了解。
创建接口设备esp_lcd_new_panel_io_dpi
该函数用于创建DBI接口LCD IO设备。DBI接口用于控制IO层,使用该接口可读写LCD设备内部的配置寄存器,其函数原型如下:
  1. esp_err_t esp_lcd_new_panel_io_dbi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dbi_io_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
复制代码
函数形参:

3.png
表22.3.1.2 esp_lcd_new_panel_io_dpi函数形参描述

函数返回值:
ESP_OK表示创建接口设备成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_NO_MEM表示内存不足。
ESP_FAIL表示其他错误。
bus为DSI总线句柄结构体,调用 esp_lcd_new_dsi_bus函数会创建DSI总线句柄结构体。
io_config为指向DBI接口的LCD IO设备配置结构体的指针,esp_lcd_dbi_io_config_t结构体中包含很多成员,如下代码所示。
  1. typedef struct {
  2.     uint8_t virtual_channel;  /* 设置虚拟通道号  */
  3.     int lcd_cmd_bits;         /* 设置LCD控制芯片可识别的命令位宽  */
  4.     int lcd_param_bits;       /* 设置LCD控制芯片可识别的参数位宽  */
  5. } esp_lcd_dbi_io_config_t;
复制代码
这里需要注意:① virtual_channel虚拟通道是一种逻辑通道,用于从不同来源多路复用数据。如果只连接了一个LCD,则将此值设置为0。② 根据LCD驱动芯片的实际情况,配置命令和参数位宽,对于正点原子的MIPI屏这两个参数位宽都是8位。
ret_io为指向LCD接口句柄结构体指针,esp_lcd_panel_io_handle_t实际是esp_lcd_panel_io_t,该结构体中包含一些函数接口,用于给LCD驱动芯片发送命令和图像数据,如下代码所示。
  1. struct esp_lcd_panel_io_t {
  2.     esp_err_t (*rx_param)(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size);                                                                /* 发送单个LCD命令并接收响应参数 */
  3. esp_err_t (*tx_param)(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);                                                /* 发送单个LCD命令及配套参数 */
  4. esp_err_t (*tx_color)(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);                                                /* 发送单次LCD刷屏命令和图像数据 */
  5. esp_err_t (*del)(esp_lcd_panel_io_t *io);        /* 卸载LCD IO设备句柄 */
  6. esp_err_t (*register_event_callbacks)(esp_lcd_panel_io_t *io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);        /* 注册LCD IO设备回调 */
  7. };
复制代码
在esp_lcd_new_panel_io_dbi函数的内部就会将DBI底层接口与ret_io绑定起来,后续直接访问ret_io即可调用到DBI的底层接口,比如panel_io_dbi_rx_param、panel_io_ dbi _tx_param和panel_io_dbi_del。
移植驱动组件
移植MIPI LCD驱动组件的基本原理包含以下三点:
1、基于数据类型为esp_lcd_panel_io_handle_t的接口设备句柄发送指定格式的命令及参数
2、实现并创建一个LCD设备,然后通过注册回调函数的方式实现结构体esp_lcd_panel_t中的各项功能
3、实现一个函数用于提供数据类型为esp_lcd_panel_handle_t的LCD设备句柄,使得应用程序能够利用LCD通用API来操作LCD设备
第一点已经在前面创建接口设备时完成了,可以调用DBI底层接口。而第二和第三点需要用到esp_lcd_new_panel_xxx函数去实现。xxx对应的是LCD驱动芯片,由于正点原子的MIPI屏幕有三款,虽然说乐鑫已经对两款做了支持,但是我们为了兼容所有MIPI屏幕,还是自己编写MIPI LCD的驱动组件。想要知道乐鑫已经对哪些MIPI屏做了兼容可以通过组件仓库进行搜索,如下图所示。


第二十二章 MIPILCD实验18220.png
图22.3.1.1 查看乐鑫支持MIPI LCD驱动芯片

在前面SPILCD章节中,直接使用乐鑫提供的st7789文件很方便就能驱动起来。RGBLCD章节更简单,由于不需要SPI接口配置,直接跳过该步骤。而在本章节,就得介绍一下怎么对屏幕驱动,知道要编写哪些函数,函数该怎么编写,以及怎么样与上层接口对应起来,了解这种方法之后,后续大家就可以兼容自己的屏幕。
接下来,我们了解一下LCD驱动组件需要要实现的函数接口以及我们要实现的函数,如下两表所示。


第二十二章 MIPILCD实验18448.png
表22.3.1.3 LCD通用API函数

4.png
表22.3.1.4 MIPILCD函数列表

表22.3.1.1罗列的是通用API接口,而表22.3.1.2罗列的是我们在mipi_lcd.c文件中实现的函数接口。通过两表,大家大概也猜到一些函数里面的实现了,就是发送不同命令实现不同功能。draw_bitmap函数没有实现,后续在其他文件中实现。
首先介绍一下mipi_lcd_new_panel函数。
2,为MIPI LCD驱动芯片创建LCD面板mipi_lcd_new_panel。
该函数用于为MIPI LCD驱动芯片创建LCD面板并对LCD设备配置,其函数原型如下:
  1. esp_err_t mipi_lcd_new_panel(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
复制代码
函数形参:

5.png
表22.3.1.5 mipi_lcd_new_panel函数形参描述

函数实现:
  1. esp_err_t mipi_lcd_new_panel(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
  2. {
  3.     esp_err_t ret = ESP_OK;

  4.     ESP_RETURN_ON_FALSE(        io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, mipi_lcd_tag, "invalid argument");

  5.     mipi_panel_t *mipi_lcd = (mipi_panel_t *)calloc(1, sizeof(mipi_panel_t));
  6. ESP_RETURN_ON_FALSE( mipi_lcd, ESP_ERR_NO_MEM, mipi_lcd_tag,
  7. "no mem for mipi_lcd panel");

  8.     if (panel_dev_config->reset_gpio_num >= 0)         /* 配置LCD复位引脚 */
  9.     {
  10.         gpio_config_t io_conf = {
  11.             .mode         = GPIO_MODE_OUTPUT,
  12.             .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
  13.         };
  14.         ESP_GOTO_ON_ERROR( gpio_config(&io_conf), err, mipi_lcd_tag,
  15. "configure GPIO for RST line failed");
  16.     }

  17.     switch (panel_dev_config->rgb_ele_order)                 /* 颜色顺序RGB/BGR */
  18.     {
  19.         case LCD_RGB_ELEMENT_ORDER_RGB:
  20.             mipi_lcd->madctl_val = 0;
  21.             break;
  22.         case LCD_RGB_ELEMENT_ORDER_BGR:
  23.             mipi_lcd->madctl_val |= LCD_CMD_BGR_BIT;
  24.             break;
  25.         default:
  26.             ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, mipi_lcd_tag, "unsupported rgb element order");
  27.             break;
  28.     }

  29.     switch (panel_dev_config->bits_per_pixel)       /* 像素格式 */
  30.     {
  31.         case 16:    /* RGB565 */
  32.             mipi_lcd->colmod_val = 0x55;
  33.             break;
  34.         case 18:    /* RGB666 */
  35.             mipi_lcd->colmod_val = 0x66;
  36.             break;
  37.         case 24:    /* RGB888 */
  38.             mipi_lcd->colmod_val = 0x77;
  39.             break;
  40.         default:
  41.             ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, mipi_lcd_tag, "unsupported pixel width");
  42.             break;
  43.     }

  44.     mipi_lcd->io                = io;
  45.     mipi_lcd->reset_gpio_num    = panel_dev_config->reset_gpio_num;        
  46.     mipi_lcd->reset_level       = panel_dev_config->flags.reset_active_high;
  47.     mipi_lcd->base.reset        = mipi_lcd_panelreset;                        
  48.     mipi_lcd->base.init         = mipi_lcd_panelinit;                 
  49.     mipi_lcd->base.del          = mipi_lcd_paneldel;                 
  50.     mipi_lcd->base.mirror       = mipi_lcd_panelmirror;               
  51.     mipi_lcd->base.swap_xy      = mipi_lcd_panelswap_xy;              
  52.     mipi_lcd->base.set_gap      = mipi_lcd_panelset_gap;              
  53.     mipi_lcd->base.invert_color = mipi_lcd_panelinvert_color;         
  54.     mipi_lcd->base.disp_on_off  = mipi_lcd_paneldisp_on_off;         
  55.     mipi_lcd->base.disp_sleep   = mipi_lcd_panelsleep;               

  56.     *ret_panel = &mipi_lcd->base;

  57.     return ESP_OK;

  58. err:
  59.     if (mipi_lcd)
  60.     {
  61.         mipi_lcd_paneldel(&mipi_lcd->base);
  62.     }

  63.     return ret;
  64. }
复制代码
简单来说,就做了两件事情:
① 根据传参panel_dev_config对LCD做配置,比如说复位引脚配置和像素格式。
② 将LCD的配置信息和一些函数接口传递给esp_lcd_panel_handle_t结构体类型指针变量ret_panel。
函数返回值:
ESP_OK表示创建成功。
其他表示异常。
io为LCD接口句柄结构体esp_lcd_panel_io_t,该结构体在前面已经介绍了,这里不再展开。
panel_dev_config为指向LCD设备配置结构体指针,esp_lcd_panel_dev_config_t结构体中包含几个成员,如下代码所示。
  1. typedef struct {
  2.     int reset_gpio_num;                                                 /* 连接LCD复位信号的引脚 */
  3.     union {
  4.         esp_lcd_color_space_t color_space;           /* RGB色彩空间,rgb_ele_order代替 */
  5.         lcd_color_rgb_endian_t rgb_endian;           /* 设置数据端序,rgb_ele_order代替 */
  6.         lcd_rgb_element_order_t rgb_ele_order;        /* 像素色彩的元素顺序(RGB/BGR) */
  7.     };
  8.     lcd_rgb_data_endian_t data_endian;                /* 设置>1字节的颜色数据的数据端序 */
  9.     uint32_t bits_per_pixel;                            /* 色彩格式的位数 */
  10.     struct {
  11.         uint32_t reset_active_high: 1;                 /* 复位引脚有效电平 */
  12.     } flags;                           
  13.     void *vendor_config;                                                 /* 用于替换驱动组件的初始化序列 */
  14. } esp_lcd_panel_dev_config_t;
复制代码
esp_lcd_panel_dev_config_t结构体需要注意以下几点:
①当发生颜色显示不对,比如想显示红色,最终是蓝色,这是像素色彩的元素顺序问题,通过调整rgb_ele_order的赋值。
②颜色显示异常,并非①中描述的情况,除了白色、黑色显示出来,这是由于颜色数据的发送顺序错误,通过调整data_endian的赋值。
③若手上的屏幕驱动起来,色彩有点偏差,可通过vendor_config把厂家提供的初始化序列填充进去。
④若不是通过芯片的IO作为复位引脚,reset_gpio_num直接赋值GPIO_NUM_NC即可,硬件复位就得自己去实现。
ret_panel为指向LCD设备句柄结构体指针,esp_lcd_panel_handle_t其实是esp_lcd_panel_t,如下代码所示。
  1. struct esp_lcd_panel_t {
  2. esp_err_t (*reset)(esp_lcd_panel_t *panel);        /* LCD屏幕复位 */
  3. esp_err_t (*init)(esp_lcd_panel_t *panel);                /* LCD屏幕初始化 */
  4. esp_err_t (*del)(esp_lcd_panel_t *panel);                /* 卸载LCD屏幕 */
  5. esp_err_t (*draw_bitmap)(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);                /* LCD屏幕绘画函数 */
  6. /* LCD屏幕镜像X轴和Y轴 */
  7. esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
  8. /* LCD屏幕交换X轴和Y轴 */
  9. esp_err_t (*swap_xy)(esp_lcd_panel_t *panel, bool swap_axes);
  10. /* LCD屏幕设置画图的起始坐标 */
  11. esp_err_t (*set_gap)(esp_lcd_panel_t *panel, int x_gap, int y_gap);
  12. /* LCD屏幕像素颜色数据按位取反(0xF0F0->0x0F0F),即反显功能 */
  13. esp_err_t (*invert_color)(esp_lcd_panel_t *panel, bool invert_color_data);
  14. /* LCD屏幕显示开关 */
  15. esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
  16. /* LCD屏幕休眠开关 */
  17.     esp_err_t (*disp_sleep)(esp_lcd_panel_t *panel, bool sleep);
  18. void *user_data;    /* 用户数据,用于存储外部自定义数据 */
  19. };
复制代码
esp_lcd_panel_handle_t结构体对应的就是表22.3.1.1的内容了。
接下来,就要介绍一下我们对应实现的函数接口。
第一个介绍的是mipi_lcd_panelinit函数,如下代码所示。
  1. static esp_err_t mipi_lcd_panelinit(esp_lcd_panel_t *panel)
  2. {
  3.     mipi_panel_t *mipi_lcd = __containerof(panel, mipi_panel_t, base);
  4.     esp_lcd_panel_io_handle_t io = mipi_lcd->io;
  5.    
  6.     const mipi_lcd_init_cmd_t *init_cmds = {0};
  7.     uint16_t init_cmds_size = 0;
  8.     bool mirror_x = true;
  9.     bool mirror_y = false;

  10.     if (mipidev.id == 0x8399)       /* 5寸,720P */
  11.     {
  12.         init_cmds = vendor_specific_init_code_default_1080p;
  13.         init_cmds_size = sizeof(vendor_specific_init_code_default_1080p) /
  14. sizeof(mipi_lcd_init_cmd_t);
  15.     }
  16.     else if (mipidev.id == 0x8394)  /* 5寸,1080p */
  17.     {
  18.         init_cmds = vendor_specific_init_code_default_720p;
  19.         init_cmds_size = sizeof(vendor_specific_init_code_default_720p) /
  20. sizeof(mipi_lcd_init_cmd_t);
  21.     }
  22.     else if (mipidev.id == 0x9881)  /* 10.1寸,800p */
  23.     {
  24.         init_cmds = vendor_specific_init_code_default_800p;
  25.         init_cmds_size = sizeof(vendor_specific_init_code_default_800p) /
  26. sizeof(mipi_lcd_init_cmd_t);

  27.         /* 返回 命令页 1 */
  28.         ESP_RETURN_ON_ERROR(        esp_lcd_panel_io_tx_param(io, ILI9881C_CMD_CNDBKxSEL, (uint8_t[]) {
  29.                   ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE1
  30. }, 3), mipi_lcd_tag, "send command failed");
  31.         /* 设置2 lane */
  32.         ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ILI9881C_PAD_CONTROL,
  33. (uint8_t[]) {
  34.                                             ILI9881C_DSI_2_LANE,
  35.                                                 }, 1), mipi_lcd_tag, "send command failed");
  36.         /* 返回 命令页 0 */
  37.         ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io,
  38. ILI9881C_CMD_CNDBKxSEL, (uint8_t[]) {
  39.                        ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1,
  40.                            ILI9881C_CMD_BKxSEL_BYTE2_PAGE0
  41.                         }, 3), mipi_lcd_tag, "send command failed");
  42.         mirror_x = false;
  43.     }

  44.     /* 发送初始化序列 */
  45.     for (int i = 0; i < init_cmds_size; i++)
  46.     {
  47.         ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds.cmd,
  48. <span style="font-style: italic;"><span style="font-style: normal;">init_cmds.data, init_cmds</span><span style="font-style: normal;">.data_bytes),mipi_lcd_tag,"send command failed");
  49.     }

  50.     vTaskDelay(pdMS_TO_TICKS(120));

  51.     /* 退出睡眠 */
  52. ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0),
  53. mipi_lcd_tag, "io tx param failed");
  54.    
  55.     /* 根据MIPI屏放置位置调整显示方向(也可调用mipi_lcd_panelmirror函数设置) */
  56.     if (mirror_x)
  57.     {
  58.         mipi_lcd->madctl_val |= HX_F_SS_PANEL;              /* 扫描方向水平翻转 */
  59.     }
  60.     else
  61.     {
  62.         mipi_lcd->madctl_val &= ~HX_F_SS_PANEL;             /* 扫描方向水平不翻转 */
  63.     }

  64.     if (mirror_y)
  65.     {
  66.         mipi_lcd->madctl_val |= HX_F_GS_PANEL;              /* 扫描方向垂直翻转 */
  67.     }
  68.     else
  69.     {
  70.         mipi_lcd->madctl_val &= ~HX_F_GS_PANEL;             /* 扫描方向垂直不翻转 */
  71.     }
  72.     ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io,LCD_CMD_MADCTL, (uint8_t[])
  73.     {
  74.         mipi_lcd->madctl_val,
  75.     }, 1), mipi_lcd_tag, "send command failed");                  /* 配置MIPILCD的显示 */

  76.     ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io,LCD_CMD_COLMOD, (uint8_t[])
  77.     {
  78.         mipi_lcd->colmod_val,
  79.     }, 1), mipi_lcd_tag, "send command failed");                  /* 配置像素格式 */
  80.     vTaskDelay(pdMS_TO_TICKS(120));

  81. return ESP_OK;
  82. }</span></span>
复制代码
mipi_lcd_panelinit函数主要就是通过发送厂家提供的初始化序列来初始化LCD设备,其次设置LCD的扫描方向以及配置像素格式。
在这里说明一下container_of函数,该函数的功能就是根据结构体中的已知成员变量的地址,来寻求该结构体的首地址,如下图所示。


第二十二章 MIPILCD实验27478.png
图22.3.1.2 container_of函数功能

通过container_of函数能把mipi_lcd结构体(自定义的结构体类型)中的base成员地址获取到,即结构体的首地址。通过mipi_lcd结构体便可以通过其成员,对LCD进行配置。
mipi_lcd结构体类型,如下代码所示。
  1. /* 初始化屏幕结构体 */
  2. typedef struct {
  3.     esp_lcd_panel_t base;           /* LCD设备的基础接口函数 */
  4.     esp_lcd_panel_io_handle_t io;   /* LCD设备的IO接口函数配置 */
  5.     int reset_gpio_num;             /* 复位管脚 */
  6.     int x_gap;                      /* x偏移 */
  7.     int y_gap;                      /* y偏移 */
  8.     uint8_t madctl_val;             /* 保存LCD CMD MADCTL寄存器的当前值 */
  9.     uint8_t colmod_val;             /* 保存LCD_CMD_COLMOD寄存器的当前值 */
  10.     uint16_t init_cmds_size;        /* 初始化序列大小 */
  11.     bool reset_level;               /* 复位电平 */
  12. } mipi_panel_t;
复制代码
第二个介绍的是mipi_lcd_paneldisp_on_off函数,如下代码所示。
  1. static esp_err_t mipi_lcd_paneldisp_on_off(esp_lcd_panel_t *panel, bool on_off)
  2. {
  3.     mipi_panel_t *mipi_lcd = __containerof(panel, mipi_panel_t, base);
  4.     esp_lcd_panel_io_handle_t io = mipi_lcd->io;
  5.     int command = 0;

  6.     if (on_off)
  7.     {
  8.         command = LCD_CMD_DISPON;   /* 打开显示命令 */
  9.     }
  10.     else
  11.     {
  12.         command = LCD_CMD_DISPOFF;  /* 关闭显示命令 */
  13.     }
  14. ESP_RETURN_ON_ERROR( esp_lcd_panel_io_tx_param(io, command, NULL, 0),
  15. mipi_lcd_tag, "send command failed");
  16.    
  17.     return ESP_OK;
  18. }
复制代码
该函数的功能是LCD显示开关,通过传参进行判断,打开显示命令LCD_CMD_DISPON,关闭显示命令LCD_CMD_DISPOFF,最终,通过esp_lcd_panel_io_tx_param函数接口发送出去。
其他函数接口跟这里实现的代码逻辑是比较相似的,所以也不再一个个罗列说明,大家自行查看mipi_lcd.c文件即可。
初始化LCD设备
通过前面两个步骤的操作,算是完成了准备工作,接下来,就得调用LCD通用API接口对LCD进行初始化,如下代码所示。
  1. ESP_ERROR_CHECK(esp_lcd_panel_reset(mipi_lcd_ctrl_panel));                /* 复位LCD */
  2. ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_lcd_ctrl_panel));          /* 初始化LCD */
  3. /* 打开LCD */
  4.     ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(mipi_lcd_ctrl_panel, true));
复制代码
此时仍然无法通过esp_lcd_panel_draw_bitmap函数向LCD绘画,因为MIPI LCD具有高分辨率,而LCD控制器中没有GRAM。因此需要维护LCD帧buffer,并通过MIPI DSI DPI接口将其刷新到LCD屏幕上,所以在这里需要对DPI进行配置。
1,为MIPI DSI DPI接口创建LCD面板函数esp_lcd_new_panel_dpi
该函数用于创建DPI接口,并创建LCD面板,其函数原型如下:
  1. esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_panel_config_t *panel_config, esp_lcd_panel_handle_t *ret_panel)
复制代码
函数形参:

6.png
表22.3.1.6 esp_lcd_new_panel_dpi函数形参描述

函数返回值:
ESP_OK表示MIPI_DSI数据面板创建成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_NO_MEM表示内存不足。
ESP_ERR_NOT_SUPPORTED表示不支持。
ESP_FAIL表示发生其他错误。
bus为DSI总线句柄结构体。
panel_config为指向DSI数据面板配置结构体指针,esp_lcd_dpi_panel_config_t 结构体如下代码所示。
  1. typedef struct {
  2.     uint8_t virtual_channel;                   /* 设置虚拟通道号 */
  3.     mipi_dsi_dpi_clock_source_t dpi_clk_src;   /* 设置DPI接口的时钟源 */
  4.     uint32_t dpi_clock_freq_mhz;               /* 设置DPI时钟频率 */
  5.     lcd_color_rgb_pixel_format_t pixel_format; /* 设置像素数据的像素格式 */
  6.     uint8_t num_fbs;                           /* 整屏大小的帧缓存区数量 */
  7.     esp_lcd_video_timing_t video_timing;       /* LCD面板的特定时序参数 */

  8.     struct extra_flags {
  9.         uint32_t use_dma2d: 1;                                         /* 是否启用DMA2D */
  10.     } flags;                  
  11. } esp_lcd_dpi_panel_config_t;
复制代码
这里需要注意几点:
① 与DBI接口类似,DPI 接口也需要设置虚拟通道。如果只连接了一个LCD,则将此值设置为0。
② DPI时钟源dpi_clk_src有三个,分别是XTAL、F160M和F240M,都有对应的宏选择MIPI_DSI_DPI_CLK_SRC_XTAL、MIPI_DSI_DPI_CLK_SRC_PLL_F160M和MIPI_DSI_DPI_CLK_SRC_PLL_F240M,直接设置默认MIPI_DSI_DPI_CLK_SRC_DEFAULT即可,即选择F240M;
③ 在设置DPI时钟频率时需要注意:像素时钟频率越高,刷新率越高,但如果DMA带宽不足或LCD控制器芯片不支持高像素时钟频率,则可能会导致闪烁。
④ 设置像素数据格式pixel_format有三种选择,分别是LCD_COLOR_PIXEL_FORMAT_RGB565、LCD_COLOR_PIXEL_FORMAT_RGB666、LCD_COLOR_PIXEL_FORMAT_RGB888,为了兼容性,这里选择了RGB565。MIPI LCD通常使用RGB888来获得最佳色彩深度。
⑤ LCD面板的特定时序参数video_timing,主要是看屏幕的规格书,在前面22.1.9小节已经有说明了,配置时对号入座即可。
ret_panel为指向esp_lcd_panel_handle_t结构体的指针,esp_lcd_panel_handle_t结构体在前面介绍mipi_lcd_new_panel函数时,已经介绍过了,这里不再展开。
最后,再调用一下esp_lcd_panel_init函数初始化一下,这时,MIPILCD完全驱动好,可以使用esp_lcd_panel_draw_bitmap函数进行绘画了。

22.3.2 程序流程图

第二十二章 MIPILCD实验31199.png
图22.3.2.1 MIPILCD实验程序流程图

22.3.3 程序解析
在12_mipilcd例程中,作者在12_mipilcd\components\BSP路径下新建LCD文件夹,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. LCD驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。LCD驱动源码包括五个文件:mipi_lcd.c、mipi_lcd.h、lcd.c、lcd.h和lcdfont.h。
mipi_lcd.c文件存放的是MIPILCD的驱动函数,而mipi_lcd.h存放的是LCD驱动芯片的命令宏、DSI总线配置宏以及自定义用于管理LCD的结构体类型,以及函数声明。lcd.c文件主要lcd的一些绘图函数,而lcd.h则存放的是引脚接口宏定义以及函数声明。lcdfont.h存放的是4种字体大小不一样的ASCII字符集(12*12、16*16、24*24和32*32)。lcd.c和lcd.h两个文件在11_rgblcd例程和12_mipilcd例程中存在差别,都是单独为某种类型屏幕进行驱动,而在13_lcd例程中对这两种类型屏幕做了兼容。
在前面22.3.1小节中,已经提及了不少mipi_lcd.c文件里面的函数,这里主要给大家介绍一下MIPILCD的初始化函数mipi_lcd_init,如下代码所示:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief           mipi_lcd初始化</font>
  3. <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url]           无</font>
  4. <font size="3"> * @retval            LCD控制句柄</font>
  5. <font size="3"> */</font>
  6. <font size="3">esp_lcd_panel_handle_t mipi_lcd_init(void)</font>
  7. <font size="3">{</font>
  8. <font size="3">    mipi_dev_bsp_enable_dsi_phy_power();                  /* 配置MIPI接口电压1.8V */</font>

  9. <font size="3">    /* 创建DSI总线 */</font>
  10. <font size="3">    esp_lcd_dsi_bus_handle_t mipi_dsi_bus;</font>
  11. <font size="3">    esp_lcd_dsi_bus_config_t bus_config = {</font>
  12. <font size="3">        .bus_id = 0,                                                  /* 总线ID */</font>
  13. <font size="3">        .num_data_lanes = MIPI_DSI_LANE_NUM,                     /* 2路数据信号 */</font>
  14. <font size="3">        .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,              /* DPHY时钟源为20M */</font>
  15. <font size="3">        .lane_bit_rate_mbps = MIPI_DSI_LANE_BITRATE_MBPS,        /* 数据通道比特率 */</font>
  16. <font size="3">};</font>
  17. <font size="3">/* 新建DSI总线 */</font>
  18. <font size="3">    ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));   </font>

  19. <font size="3">    /* 配置DSI总线的DBI接口 */</font>
  20. <font size="3">    esp_lcd_panel_io_handle_t mipi_dbi_io;</font>
  21. <font size="3">    esp_lcd_dbi_io_config_t dbi_config = {</font>
  22. <font size="3">        .virtual_channel = 0,           /* 虚拟通道(只有一个LCD连接,设置0即可) */</font>
  23. <font size="3">        .lcd_cmd_bits    = 8,             /* 根据MIPI LCD驱动IC规格设置 命令位宽度 */</font>
  24. <font size="3">        .lcd_param_bits  = 8,            /* 根据MIPI LCD驱动IC规格设置 参数位宽度 */</font>
  25. <font size="3">    };</font>
  26. <font size="3">ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, </font>
  27. <font size="3">&mipi_dbi_io));</font>

  28. <font size="3">    /* 创建LCD控制器驱动 */</font>
  29. <font size="3">    esp_lcd_panel_handle_t mipi_lcd_ctrl_panel;                  /* MIPI控制句柄 */</font>
  30. <font size="3">    esp_lcd_panel_dev_config_t lcd_dev_config = {</font>
  31. <font size="3">        .bits_per_pixel = 16,                                                          /* MIPILCD的像素位宽度 */</font>
  32. <font size="3">        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,          /* 像素数据的RGB元素顺序 */</font>
  33. <font size="3">        .reset_gpio_num = lcddev.ctrl.lcd_rst,              /* MIPILCD屏的复位引脚 */</font>
  34. <font size="3">    };</font>
  35. <font size="3">ESP_ERROR_CHECK(mipi_lcd_new_panel(mipi_dbi_io, &lcd_dev_config, </font>
  36. <font size="3">&mipi_lcd_ctrl_panel));</font>

  37. <font size="3">    ESP_ERROR_CHECK(esp_lcd_panel_reset(mipi_lcd_ctrl_panel));         /* 复位LCD屏 */</font>

  38. <font size="3">    /* 读取屏幕ID */</font>
  39. <font size="3">    esp_lcd_panel_io_rx_param(mipi_dbi_io, 0xDA, &mipi_id[0], 1);</font>
  40. <font size="3">    vTaskDelay(pdMS_TO_TICKS(20));</font>
  41. <font size="3">    esp_lcd_panel_io_rx_param(mipi_dbi_io, 0xDB, &mipi_id[1], 1);</font>
  42. <font size="3">    vTaskDelay(pdMS_TO_TICKS(20));</font>
  43. <font size="3">    /* 不是HX8399和HX8394 */</font>
  44. <font size="3">    if (mipi_id[0] == 0x00 || mipi_id[1] == 0x00)</font>
  45. <font size="3">    {</font>
  46. <font size="3">        /* 读取ILI9881 ID */</font>
  47. <font size="3">        esp_lcd_panel_io_tx_param(mipi_dbi_io, ILI9881C_CMD_CNDBKxSEL,</font>
  48. <font size="3">        (uint8_t[]) {</font>
  49. <font size="3">                                   ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1,</font>
  50. <font size="3">                                 ILI9881C_CMD_BKxSEL_BYTE2_PAGE1</font>
  51. <font size="3">        }, 3);</font>
  52. <font size="3">        esp_lcd_panel_io_rx_param(mipi_dbi_io, 0x00, &mipi_id[0], 1);</font>
  53. <font size="3">        esp_lcd_panel_io_rx_param(mipi_dbi_io, 0x01, &mipi_id[1], 1);</font>
  54. <font size="3">    }</font>

  55. <font size="3">    mipidev.id = (uint16_t)(mipi_id[0] << 8) | mipi_id[1];</font>
  56. <font size="3">    ESP_LOGI(mipi_lcd_tag, "mipilcd_id:%#x ", mipidev.id);  /* 打印LCD的ID */</font>

  57. <font size="3">ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_lcd_ctrl_panel));        /* 初始化LCD屏 */</font>
  58. <font size="3">    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(mipi_lcd_ctrl_panel, true));  </font>

  59. <font size="3">    if (mipidev.id == 0x8394)                          /* 5.5寸720P屏幕 */</font>
  60. <font size="3">    {</font>
  61. <font size="3">        mipidev.pwidth = 720;                         /* 面板宽度,单位:像素 */</font>
  62. <font size="3">        mipidev.pheight = 1280;                   /* 面板高度,单位:像素 */</font>
  63. <font size="3">        mipidev.hbp = 52;                        /* 水平后廊 */</font>
  64. <font size="3">        mipidev.hfp = 48;                              /* 水平前廊 */</font>
  65. <font size="3">        mipidev.hsw = 8;                          /* 水平同步宽度 */</font>
  66. <font size="3">        mipidev.vbp = 15;                         /* 垂直后廊 */</font>
  67. <font size="3">        mipidev.vfp = 16;                        /* 垂直前廊 */</font>
  68. <font size="3">        mipidev.vsw = 5;                               /* 垂直同步宽度 */</font>
  69. <font size="3">        mipidev.pclk_mhz = 60;                    /* 设置像素时钟 60Mhz */</font>
  70. <font size="3">        mipidev.dir = 0;                              /* 只能竖屏 */</font>
  71. <font size="3">    }</font>
  72. <font size="3">    else if (mipidev.id == 0x8399)         /* 5.5寸1080P屏幕 */</font>
  73. <font size="3">    {</font>
  74. <font size="3">        mipidev.pwidth = 1080;                /* 面板宽度,单位:像素 */</font>
  75. <font size="3">        mipidev.pheight = 1920;              /* 面板高度,单位:像素 */</font>
  76. <font size="3">        mipidev.hbp = 22;                   /* 水平后廊 */</font>
  77. <font size="3">        mipidev.hfp = 22;                     /* 水平前廊 */</font>
  78. <font size="3">        mipidev.hsw = 20;                     /* 水平同步宽度 */</font>
  79. <font size="3">        mipidev.vbp = 9;                      /* 垂直后廊 */</font>
  80. <font size="3">        mipidev.vfp = 7;                      /* 垂直前廊 */</font>
  81. <font size="3">        mipidev.vsw = 7;                      /* 垂直同步宽度 */</font>
  82. <font size="3">        mipidev.pclk_mhz = 60;              /* 设置像素时钟 60Mhz */</font>
  83. <font size="3">        mipidev.dir = 0;                     /* 只能竖屏 */</font>
  84. <font size="3">    }</font>
  85. <font size="3">    else if (mipidev.id == 0x9881)                /* 10.1寸800P屏幕 */</font>
  86. <font size="3">    {</font>
  87. <font size="3">        mipidev.pwidth = 800;              /* 面板宽度,单位:像素 */</font>
  88. <font size="3">        mipidev.pheight = 1280;               /* 面板高度,单位:像素 */</font>
  89. <font size="3">        mipidev.hbp = 24;                     /* 水平后廊 */</font>
  90. <font size="3">        mipidev.hfp = 15;                     /* 水平前廊 */</font>
  91. <font size="3">        mipidev.hsw = 24;                     /* 水平同步宽度 */</font>
  92. <font size="3">        mipidev.vbp = 9;                      /* 垂直后廊 */</font>
  93. <font size="3">        mipidev.vfp = 7;                      /* 垂直前廊 */</font>
  94. <font size="3">        mipidev.vsw = 2;                     /* 垂直同步宽度 */</font>
  95. <font size="3">        mipidev.pclk_mhz = 60;                /* 设置像素时钟 60Mhz */</font>
  96. <font size="3">        mipidev.dir = 0;                     /* 支持横/竖屏,默认为竖屏 */</font>
  97. <font size="3">    }</font>

  98. <font size="3">    lcddev.id = mipidev.id;                                 /* LCD_ID */</font>
  99. <font size="3">    lcddev.width = mipidev.pwidth;                          /* 宽度 */</font>
  100. <font size="3">    lcddev.height = mipidev.pheight;                        /* 高度 */</font>

  101. <font size="3">    esp_lcd_panel_handle_t mipi_dpi_panel;                  /* MIPILCD控制句柄 */</font>
  102. <font size="3">    esp_lcd_dpi_panel_config_t dpi_config = {               /* DSI数据配置 */</font>
  103. <font size="3">        .virtual_channel    = 0,                            /* 虚拟通道 */</font>
  104. <font size="3">        .dpi_clk_src        = MIPI_DSI_DPI_CLK_SRC_DEFAULT, /* 时钟源 */</font>
  105. <font size="3">        .dpi_clock_freq_mhz = mipidev.pclk_mhz,             /* 像素时钟频率 */</font>
  106. <font size="3">        .pixel_format       = LCD_COLOR_PIXEL_FORMAT_RGB565,/* 颜色格式 */</font>
  107. <font size="3">        .num_fbs            = 2,                            /* 帧缓冲区数量 */</font>
  108. <font size="3">        .video_timing       = {                             /* 面板特定时序参数 */</font>
  109. <font size="3">            .h_size            = mipidev.pwidth,            /* 水平分辨率 */</font>
  110. <font size="3">            .v_size            = mipidev.pheight,           /* 垂直分辨率 */</font>
  111. <font size="3">            .hsync_back_porch  = mipidev.hbp,               /* 水平后廊 */</font>
  112. <font size="3">            .hsync_pulse_width = mipidev.hsw,               /* 水平同步宽度 */</font>
  113. <font size="3">            .hsync_front_porch = mipidev.hfp,               /* 水平前廊 */</font>
  114. <font size="3">            .vsync_back_porch  = mipidev.vbp,               /* 垂直后廊 */</font>
  115. <font size="3">            .vsync_pulse_width = mipidev.vsw,               /* 垂直同步宽度 */</font>
  116. <font size="3">            .vsync_front_porch = mipidev.vfp,               /* 垂直前廊 */</font>
  117. <font size="3">        },</font>
  118. <font size="3">    };</font>
  119. <font size="3">ESP_ERROR_CHECK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, </font>
  120. <font size="3">&mipi_dpi_panel));        /* 为MIPI DSI DPI接口创建LCD控制句柄 */</font>
  121. <font size="3">    /* 初始化MIPILCD */</font>
  122. <font size="3">ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_dpi_panel));                                    </font>
  123. <font size="3">    return mipi_dpi_panel;</font>
  124. <font size="3">}</font>
复制代码
该函数就是对MIPILCD进行初始化,整个过程就围绕着22.3.1小节中描述的驱动流程实现。通过esp_lcd_new_dsi_bus函数和esp_lcd_new_panel_io_dbi函数初始化DBI接口设备;通过mipi_lcd_new_panel函数创建MIPI设备;后续通过LCD通用API接口对LCD进行配置,比如esp_lcd_panel_reset复位LCD屏,esp_lcd_panel_init初始化LCD屏,esp_lcd_panel_disp_on_off打开显示;最终还得通过esp_lcd_new_panel_dpi函数为DPI接口创建LCD面板,申请到对应的缓冲区即GRAM,后面还需要重新调用esp_lcd_panel_init再次初始化LCD屏。函数的返回值是esp_lcd_panel_handle_t类型的句柄,便于后续对LCD屏幕进行操作。函数中还涉及到配置DSI总线的电压,通过调用mipi_dev_bsp_enable_dsi_phy_power函数设置LDO_VO3输出1.8V给到MIPI_DSI总线。
下面介绍在mipi_lcd.h文件定义的两个重要结构体:
  1. <font size="3">/* MIPI LCD重要参数集 */</font>
  2. <font size="3">typedef struct  </font>
  3. <font size="3">{</font>
  4. <font size="3">    uint16_t id;                    /* 720p/800p/1080p */</font>
  5. <font size="3">    uint32_t pwidth;                /* MIPI面板的宽度,固定参数,不随显示方向改变 */</font>
  6. <font size="3">    uint32_t pheight;               /* MIPI面板的高度,固定参数,不随显示方向改变 */</font>
  7. <font size="3">    uint16_t hsw;                   /* 水平同步宽度 */</font>
  8. <font size="3">    uint16_t vsw;                   /* 垂直同步宽度 */</font>
  9. <font size="3">    uint16_t hbp;                   /* 水平后廊 */</font>
  10. <font size="3">    uint16_t vbp;                   /* 垂直后廊 */</font>
  11. <font size="3">    uint16_t hfp;                   /* 水平前廊 */</font>
  12. <font size="3">    uint16_t vfp;                   /* 垂直前廊  */</font>
  13. <font size="3">    uint8_t dir;                    /* 0,竖屏;1,横屏; */</font>
  14. <font size="3">    uint32_t pclk_mhz;              /* 设置像素时钟 */</font>
  15. <font size="3">} _mipilcd_dev; </font>

  16. <font size="3">/* 初始化屏幕结构体 */</font>
  17. <font size="3">typedef struct {</font>
  18. <font size="3">    esp_lcd_panel_t base;           /* LCD设备的基础接口函数 */</font>
  19. <font size="3">    esp_lcd_panel_io_handle_t io;   /* LCD设备的IO接口函数配置 */</font>
  20. <font size="3">    int reset_gpio_num;             /* 复位管脚 */</font>
  21. <font size="3">    int x_gap;                      /* x偏移 */</font>
  22. <font size="3">    int y_gap;                      /* y偏移 */</font>
  23. <font size="3">    uint8_t madctl_val;             /* 保存LCD CMD MADCTL寄存器的当前值 */</font>
  24. <font size="3">    uint8_t colmod_val;             /* 保存LCD_CMD_COLMOD寄存器的当前值 */</font>
  25. <font size="3">    uint16_t init_cmds_size;        /* 初始化序列大小 */</font>
  26. <font size="3">    bool reset_level;               /* 复位电平 */</font>
  27. <font size="3">} mipi_panel_t;</font>
复制代码
_mipilcd_dev结构体用于保存一些MIPILCD重要参数信息,比如MIPILCD的ID、MIPILCD的长宽、MIPILCD的时序参数等。最后声明_mipilcd_dev结构体类型变量mipilcd_dev,mipilcd_dev在mipi_lcd.c中定义。
_mipi_panel_t结构体用来保存MIPILCD相关的一些重要信息,比如LCD设备的基础接口结构体、LCD设备的IO接口结构体、复位引脚、初始化序列等。
在lcd.h文件中也定义了一个重要参数结构体_lcd_dev,在RGBLCD实验章节中介绍过,这里就不再展开。
下面我们再解析lcd.c的程序,看一下初始化函数lcd_init,代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief             初始化LCD</font>
  3. <font size="3"> * @param             无</font>
  4. <font size="3"> * @retval             无</font>
  5. <font size="3"> */</font>
  6. <font size="3">void lcd_init(void)</font>
  7. <font size="3">{</font>
  8. <font size="3">    lcddev.ctrl.lcd_rst = LCD_RST_PIN;                          /* 复位管脚 */</font>
  9. <font size="3">    lcddev.ctrl.lcd_bl = LCD_BL_PIN;                            /* 背光管脚 */</font>

  10. <font size="3">    gpio_config_t gpio_init_struct = {0};</font>
  11. <font size="3">    gpio_init_struct.intr_type    = GPIO_INTR_DISABLE;          /* 失能引脚中断 */</font>
  12. <font size="3">    gpio_init_struct.mode         = GPIO_MODE_OUTPUT;           /* 输出模式 */</font>
  13. <font size="3">    gpio_init_struct.pull_up_en   = GPIO_PULLUP_DISABLE;        /* 失能上拉 */</font>
  14. <font size="3">    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;      /* 失能下拉 */</font>
  15. <font size="3">    gpio_init_struct.pin_bit_mask = 1ull << lcddev.ctrl.lcd_bl; /* 设置的引脚 */</font>
  16. <font size="3">    ESP_ERROR_CHECK(gpio_config(&gpio_init_struct));            /* 配置GPIO */</font>

  17. <font size="3">    LCD_BL(0);      /* 背光关闭 */</font>

  18. <font size="3">    lcddev.lcd_panel_handle = mipi_lcd_init();                          /* 初始化MIPI LCD */</font>
  19. <font size="3">ESP_ERROR_CHECK(esp_lcd_dpi_panel_get_frame_buffer(lcddev.lcd_panel_handle,</font>
  20. <font size="3">2, &lcd_buffer[0], &lcd_buffer[1]));         /* 获取帧缓冲区 */</font>
  21. <font size="3">    </font>
  22. <font size="3">const esp_lcd_dpi_panel_event_callbacks_t mipi_cbs = {</font>
  23. <font size="3">        /* 内部缓冲区刷新完成回调函数 */</font>
  24. <font size="3">        .on_refresh_done = lcd_panel_refresh_done_callback,     </font>
  25. <font size="3">    };</font>
  26. <font size="3">esp_lcd_dpi_panel_register_event_callbacks(lcddev.lcd_panel_handle, </font>
  27. <font size="3">&mipi_cbs, NULL);</font>
  28. <font size="3">    lcd_clear(WHITE);</font>
  29. <font size="3">    LCD_BL(1);      /* 打开背光 */</font>
  30. <font size="3">}</font>
复制代码
在lcd_init函数中,首先是对LCD背光控制引脚配置,然后调用mipi_lcd_init函数初始化MIPILCD,后面直接通过esp_lcd_dpi_panel_get_frame_buffer函数接口把数据获取到。为了防止屏幕撕裂,这里还注册了刷新完成回调函数,在进行清屏函数中,需要等待一帧刷新完成,再进行下一帧刷新。最后调用lcd_clear函数清屏,拉高背光控制引脚,打开背光。
接下来,再看看这个回调函数里面的实现,如下代码所示。
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief            内部缓存刷新完成回调函数</font>
  3. <font size="3"> * @param           panel_io: LCD IO的句柄</font>
  4. <font size="3"> * @param             edata: 事件数据类型</font>
  5. <font size="3"> * @param            user_ctx: 传入参数</font>
  6. <font size="3"> * @retval           无</font>
  7. <font size="3"> */</font>
  8. <font size="3">IRAM_ATTR static bool lcd_panel_refresh_done_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)</font>
  9. <font size="3">{</font>
  10. <font size="3">    refresh_done_flag = 1;</font>
  11. <font size="3">    return false;</font>
  12. <font size="3">}</font>
复制代码
当发送一帧完成之后,就回进入到回调函数中,把refresh_done_flag标志置1。
lcd.c的其他函数与RGBLCD例程中的lcd.c是一致的,请大家自行查看源码,都有详细的注释。
13_lcd例程则是有对RGBLCD和MIPILCD的兼容,大家也可以自行学习查看。
2. CMakeLists.txt文件
本例程的功能实现主要依靠LCD驱动。要在main函数中,成功调用LCD文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
  1. <font size="3">set(src_dirs</font>
  2. <font size="3">                   LED</font>
  3. <font size="3">LCD)</font>

  4. <font size="3">set(include_dirs</font>
  5. <font size="3">                   LED</font>
  6. <font size="3">LCD)</font>

  7. <font size="3">set(requires</font>
  8. <font size="3">                  driver</font>
  9. <font size="3">                        esp_lcd</font>
  10. <font size="3">                        esp_common)</font>

  11. <font size="3">idf_component_register(        SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})</font>

  12. <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码
3. main.c驱动代码
在main.c里面编写如下代码。
  1. <font size="3">void app_main(void)</font>
  2. <font size="3">{</font>
  3. <font size="3">    esp_err_t ret;</font>
  4. <font size="3">    uint8_t x = 0;</font>
  5. <font size="3">    </font>
  6. <font size="3">    ret = nvs_flash_init();     /* 初始化NVS */</font>
  7. <font size="3">    if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
  8. <font size="3">    {</font>
  9. <font size="3">        ESP_ERROR_CHECK(nvs_flash_erase());</font>
  10. <font size="3">        ESP_ERROR_CHECK(nvs_flash_init());</font>
  11. <font size="3">    }</font>

  12. <font size="3">    led_init();                             /* LED初始化 */</font>
  13. <font size="3">    lcd_init();                             /* LCD屏初始化 */</font>

  14. <font size="3">    while (1)</font>
  15. <font size="3">    {</font>
  16. <font size="3">        switch (x)</font>
  17. <font size="3">        {</font>
  18. <font size="3">            case 0:</font>
  19. <font size="3">            {</font>
  20. <font size="3">                lcd_clear(WHITE);</font>
  21. <font size="3">                break;</font>
  22. <font size="3">            }</font>
  23. <font size="3">            case 1:</font>
  24. <font size="3">            {</font>
  25. <font size="3">                lcd_clear(BLACK);</font>
  26. <font size="3">                break;</font>
  27. <font size="3">            }</font>
  28. <font size="3">            case 2:</font>
  29. <font size="3">            {</font>
  30. <font size="3">                lcd_clear(BLUE);</font>
  31. <font size="3">                break;</font>
  32. <font size="3">            }</font>
  33. <font size="3">            case 3:</font>
  34. <font size="3">            {</font>
  35. <font size="3">                lcd_clear(RED);</font>
  36. <font size="3">                break;</font>
  37. <font size="3">            }</font>
  38. <font size="3">            case 4:</font>
  39. <font size="3">            {</font>
  40. <font size="3">                lcd_clear(MAGENTA);</font>
  41. <font size="3">                break;</font>
  42. <font size="3">            }</font>
  43. <font size="3">            case 5:</font>
  44. <font size="3">            {</font>
  45. <font size="3">                lcd_clear(GREEN);</font>
  46. <font size="3">                break;</font>
  47. <font size="3">            }</font>
  48. <font size="3">            case 6:</font>
  49. <font size="3">            {</font>
  50. <font size="3">                lcd_clear(CYAN);</font>
  51. <font size="3">                break;</font>
  52. <font size="3">            }</font>
  53. <font size="3">            case 7:</font>
  54. <font size="3">            {</font>
  55. <font size="3">                lcd_clear(YELLOW);</font>
  56. <font size="3">                break;</font>
  57. <font size="3">            }</font>
  58. <font size="3">            case 8:</font>
  59. <font size="3">            {</font>
  60. <font size="3">                lcd_clear(BRRED);</font>
  61. <font size="3">                break;</font>
  62. <font size="3">            }</font>
  63. <font size="3">            case 9:</font>
  64. <font size="3">            {</font>
  65. <font size="3">                lcd_clear(GRAY);</font>
  66. <font size="3">                break;</font>
  67. <font size="3">            }</font>
  68. <font size="3">            case 10:</font>
  69. <font size="3">            {</font>
  70. <font size="3">                lcd_clear(LGRAY);</font>
  71. <font size="3">                break;</font>
  72. <font size="3">            }</font>
  73. <font size="3">            case 11:</font>
  74. <font size="3">            {</font>
  75. <font size="3">                lcd_clear(BROWN);</font>
  76. <font size="3">                break;</font>
  77. <font size="3">            }</font>
  78. <font size="3">        }</font>

  79. <font size="3">        lcd_show_string(10, 40,  240, 32, 32, "ESP32-P4", RED);</font>
  80. <font size="3">        lcd_show_string(10, 80,  240, 24, 24, "MIPILCD TEST", RED);</font>
  81. <font size="3">        lcd_show_string(10, 110, 240, 16, 16, "ATOM@ALIENTEK", RED);</font>

  82. <font size="3">        x++;</font>

  83. <font size="3">        if (x == 12)</font>
  84. <font size="3">        {</font>
  85. <font size="3">            x = 0;</font>
  86. <font size="3">        }</font>

  87. <font size="3">        LED0_TOGGLE();</font>

  88. <font size="3">        vTaskDelay(pdMS_TO_TICKS(500));</font>
  89. <font size="3">    }</font>
  90. <font size="3">}</font>
复制代码
app_main函数功能主要是显示一些固定的字符,字体大小包括32、24和16三种,然后不停的切换背景颜色,每500毫秒切换一次。而LED0也会不停地闪烁,指示程序已经在运行了。

22.4 下载验证
下载代码后,LED0不停地闪烁,提示程序已经在运行了。同时可以看到MIPILCD屏幕模块显示背景色不停切换,如下图所示。

第二十二章 MIPILCD实验44265.png
图22.4.1 MIPILCD显示效果图
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-12-27 06:44

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表