正点原子嵌入式linux驱动开发——Linux LCD驱动

LCD是很常用的一个外设,通过LCD可以显示绚丽的图片、界面等,提交人机交互的效率。STM32MP1提供了一个LTDC接口用于连接RGB接口的液晶屏。本章就来学校一下如何在Linux下驱动LCD屏。

LCD和LTDC简介

LCD简介

这里在当时学习stm32裸机开发的时候就学过了,但是当时我的是一个2.8寸的电阻触摸屏,而且接口是MCU的,8080时序,和现在买的这个RGB不一样,所以还是要学习一下。

现在要在STM32MP1开发板上使用LCD,所以不需要去研究LCD的具体实现原理,只需要从使用的角度去关注LCD的几个重要点:

分辨率

LCD显示器都是由一个个的像素点组成,像素点就类似一个灯(在OLED显示器中,像素点就是一个小灯),这个小灯是RGB灯,也就是由R(红色)、G(绿色)和B(蓝色)这三种颜色组成的,而RGB就是光的三原色。1080P的意思就是LCD屏幕上的像素数量是1920*1080个,也就是这个屏幕一列1080个像素点,一共1920列,如下所示:

上图就是1080P显示器的像素示意图,X轴就是LCD显示器的横轴,Y轴就是显示器的竖轴。图中的小方块就是像素点,一共有1920*1080=2073600个像素点。左上角的A点是第一个像素点,右下角的C点就是最后一个像素点。2K就是2560*1440个像素点,4K是3840*2160个像素点。

LCD显示器的分辨率是一个很重要的参数,但是并不是分辨率越高的LCD就越好。衡量一款LCD的好坏,分辨率只是其中的一个参数,还有色彩还原程度、色彩偏离、亮度、可视角度、屏幕刷新率等其他参数。

像素格式

一个像素点就相当于一个RGB小灯,通过控制R、G、B这三种颜色的亮度就可以显示出各种各样的色彩。一般一个像素点的R、G、B这三部分分别使用8bit的数据,那么一个像素点就是8bit*3=24bit,也就是说一个像素点3个字节,这种像素格式称为RGB888 。如果再加入8bit的Alpha(透明)通道的话一个像素点就是32bit,也就是4个字节,这种像素格式称为ARGB8888。本次实验中使用ARGB8888这种像素格式,一个像素占用4个字节的内存,这四个字节每个位的分配如下图所示:

在上图中,一个像素点是4个字节,其中bit31-bit24 是Alpha通道,bit23-bit16是RED通道,bit15-bit8是GREEN通道,bit7~bit0是BLUE通道。所以红色对应的值就是0X00FF0000,蓝色对应的值就是0X000000FF,绿色对应的值为0X0000FF00。通过调节R、G、B的比例可以产生其它的颜色,比如0X00FFFF00就是黄色,0X00000000就是黑色,0X00FFFFFF就是白色。

LCD屏幕接口

LCD屏幕或者说显示器有很多种接口,比如在显示器上常见的VGA、HDMI、DP等等,但是STM32MP1开发板不支持这些接口。STM32MP1支持RGB接口的LCD,RGBLCD 接口的信号线如下图所示:

上图就是RGBLCD的信号线,R[7:0]、G[7:0]和B[7:0]这24根是数据线,DE、VSYNC、HSYNC和PCLK这四根是控制信号线 。RGB LCD一般有两种驱动模式:DE模式和HV模式,这两个模式的区别是DE模式需要用到DE信号线,而HV模式不需要用到DE信号线,在DE模式下是可以不需要HSYNC信号线的,即使不接HSYNC信号线LCD也可以正常工作。

ALIENTEK一共有四款RGB LCD屏幕,这个针对STM32MP157开发板的linux驱动开发教程是以ATK-7016这款屏幕为例讲解(鼓励你买贵的,当然我确实买了这个),ATK-7016的屏幕接口原理图如下图所示:

图中J1就是对外接口,是一个40PIN的FPC座(0.5mm间距),通过FPC线,可以连接到STM32MP1开发板上面 。该接口十分完善,采用RGB888格式,并支持DE&HV模式,还支持触摸屏和背光控制 。右侧的几个电阻,并不是都焊接的,可以根据自己需要而选择是否焊接(正点原子出厂屏幕不能做修改! )。默认情况,R1和R6焊接,设置LCD_LR和LCD_UD控制LCD的扫描方向,是从左到右,从上到下(横屏看) 。而LCD_R7/G7/B7则用来设置LCD的 ID,由于RGBLCD没有读写寄存器,也就没有所谓的ID,这里通过在模块上面,控制R7/G7/B7的上/下拉,来自定义 LCD模块的ID,帮助SOC判断当前LCD面板的分辨率和相关参数,以提高程序兼容性。这几个位的设置关系如下图所示:

ATK-7016模块,就设置M2:M0=010即可 。这样,在程序里面,读取LCD_R7/G7/B7

得到M0:M2的值,从而判断RGBLCD模块的型号,并执行不同的配置,即可实现不同 LCD模块的兼容。

LCD时间参数

如果将LCD显示一帧图像按照从左至右、从上到下的顺序扫描每个像素点,并且在像素画上对应的颜色,当画到最后一个像素点的时候一幅图像就绘制好了。假如一个LCD的分辨率为1024*600,那么其扫描如下图所示:

结合上图,来看一下LCD是怎么扫描显示一帧图像的。一帧图像也是由一行一行组成的。HSYNC是水平同步信号,也叫做行同步信号 ,当产生此信号的话就表示开始显示新
的一行了
,所以此信号都是在上图的最左边VSYNC信号是垂直同步信号,也叫做帧同步信号 ,当产生此信号的话就表示开始显示新的一帧图像了 ,所以此信号在上图的左上角

当显示完一行以后会发出HSYNC信号 ,此时电子枪就会关闭,然后迅速的移动到屏幕的左边,当HSYNC信号结束以后就可以显示新的一行数据了,电子枪就会重新打开。在HSYNC信号结束到电子枪重新打开之间会插入一段延时,这段延时就是上图中的HBP 。当显示完一行以后就会关闭电子枪等待HSYNC信号产生,关闭电子枪到HSYNC信号产生之间会插入一段延时,这段延时就是上图中的HFP信号 。同理,当显示完一帧图像以后电子枪也会关闭,然后等到VSYNC信号产生,期间也会加入一段延时,这段延时就是上图的VFP 。VSYNC信号产生,电子枪移动到左上角,当VSYNC信号结束以后电子枪重新打开,中间也会加入一段延时,这段延时就是上图中的VBP

HBP、 HFP、 VBP和 VFP就是导致上图中黑边的原因,RGB LCD屏幕内部是有一个IC的,发送一行或者一帧数据给IC,IC是需要反应时间的 。通过这段反应时间可以让IC识别到一行数据扫描完了,要换行了,或者一帧图像扫描完了,要开始下一帧图像显示了。因此,在LCD屏幕中继续存在HBP、HFP、VPB和VFP这四个参数的主要目的是为了锁定有效的像素数据 。这四个时间是LCD重要的时间参数,后面编写LCD驱动的时候要用到的,至于这四个时间参数具体值是多少,那要需要去查看所使用的LCD数据手册了

RGB LCD屏幕时序

先看一下行显示对应的时序图,如下图所示:

上图就是RGB LCD的行显示时序,来分析一下其中重要几个的参数:

  • HSYNC:行同步信号,当此信号有效的话就表示开始显示新的一行数据,查阅所使用的LCD数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。
  • HSPW:有些地方也叫做thp,是HSYNC信号宽度,也就是HSYNC信号持续时间。HSYNC信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为CLK。
  • HBP:有些地方叫做thb,术语叫做行同步信号后肩,单位是CLK。
  • HOZVAL:有些地方叫做thd,显示一行数据所需的时间,假如屏幕分辨率为1024*600,那么HOZVAL就是1024,单位为CLK。
  • HFP:有些地方叫做thf,术语叫做行同步信号前肩,单位是CLK。

当HSYNC信号发出以后,需要等待HSPW+HBP个CLK时间才会接收到真正有效的像素数据。当显示完一行数据以后需要等待HFP个CLK时间才能发出下一个HSYNC信号,所以显示一行所需要的时间就是:HSPW + HBP + HOZVAL + HFP

一帧图像就是由很多个行组成的,RGB LCD的帧显示时序如下图所示:

上图就是RGB LCD的帧显示时序,来分析一下其中重要的几个参数:

  • VSYNC:帧同步信号,当此信号有效的话就表示开始显示新的一帧数据,查阅所使用的LCD数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。
  • VSPW:有些地方也叫做tvp,是VSYNC信号宽度,也就是VSYNC信号持续时间,单位为1行的时间。
  • VBP:有些地方叫做tvb,术语叫做帧同步信号后肩,单位为1行的时间。
  • LINE:有些地方叫做tvd,显示一帧有效数据所需的时间,假如屏幕分辨率为1024*600,那么LINE就是600行的时间。
  • VFP:有些地方叫做tvf,术语叫做帧同步信号前肩,单位为1行的时间。

显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP个行时间,最终的计算公式:

T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)

因此在配置一款RGB LCD的时候需要知道这几个参数:HOZVAL(屏幕有效宽度)、LINE(屏幕有效高度)、HBP、HSPW、HFP、VSPW、VBP和VFP。 ALIENTEK四款RGB LCD屏幕的参数如下图所示:

像素时钟

像素时钟就是RGB LCD的时钟信号 ,以ATK7016这款屏幕为例,显示一帧图像所需要的时钟数就是:

= (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)

= (3 + 20 + 600 + 12) * (20 + 140 + 1024 + 160)

= 635 * 1344

= 853440。

显示一帧图像需要853440个时钟数,那么显示60帧就是:853440 * 60 = 51206400 =51.2M,所以像素时钟就是51.2MHz

显存

如果采用ARGB8888格式的话一个像素需要4个字节的内存来存放像素数据,那么1024600分辨率就需要1024 600*4=2457600B=2.4MB内存。但

RGB LCD内部是没有内存的,所以就需要在开发板上的DDR3中分出一段内存作为RGB LCD屏幕的显存,如果要在屏幕上显示什么图像的话直接操作这部分显存即可

LTDC接口

LTDC是STM32MP1自带的液晶屏幕接口,用于连接RGB LCD接口的屏幕,LTDC接口特性如下:

  1. 24位RGB并行像素输出;每像素8位(RGB888)。
  2. 2个带有专用FIFO的显示层(FIFO深度 64x64位)。
  3. 查色表(CLUT),每个图层最高256 种颜色 (256x24)位。
  4. 可针对不同显示面板编程时序。
  5. 每层有多达8个输入颜色格式可供选择,分别为:ARGB8888、RGB888、RGB565、ARGB1555、ARGB4444、L8、AL44、AL88。

下图为LTDC功能框架图:

从图可以看出LTDC的信号可以分为两类:4个控制信号(LCD_CLK像素时钟、LCD_HSYNC水平同步、LCD_VSYNC垂直同步、LCD_DE数据有效)和3个RGB数据信号(8bit x 3)。

DRM驱动框架

DRM简介

在Linux系统中,主流的显示框架有两种:DRM(Direct Rendering Module)框架和FB(FrameBuffer)框架。 FB框架不能处理基于 3D加速GPU显卡,DRM是可以统一管理GPU显示,所以DRM相对于FB更能适应新的显示硬件。比如DRM支持多层合成、支持VSYNC、支持DMA-BUF、支持fence机制等等。

下图就是一个DRM驱动框架包括两部分:DRM core和DRM driver。DRM core提供了一个基本的DRM框架,DRM driver就可以注册进DRM框架,同时为用户空间提供一组ioctl。

1ibdrm对底层接口(DRM driver提供的ioctl)进行封装,向上层提供统一的API接口。DRM driver包含了GEM模块和KMS模块,这两模块也分为好几个小模块。

先介绍上图中的部分名词意思和作用:

  • 图形执行管理器(GEM):全称Graphics Execution Manager,这是一个内存管理器,主要负责内存的分配和释放,可以调用GPU。
  • DUMB:这是一个dumb缓冲区,主要负责一些简单的buffer显示,可以通过CPU直接渲染dumb,GPU不会使用dumb。
  • 内核显示模式设置(KMS):全称Kernel Mode Setting,主要负责显示的控制,包括屏幕分辨率、屏幕刷新率和颜色深度等等
  • CRTC:就是指显示控制器 ,在DRM里有多个显存,就可以通过操作CRTC来控制要显示那个显存
  • Encoder:负责从CRTC里输出的timing时转换成外部设备所需要的信号的模块 ,同时也负责控制LCD的显示
  • Connector:连接物理显示设备的连接器,比如DSI、HDMI等等。
  • Plane:负责获取显存 ,在输出到CRTC里,说明CRTC必须要有一个Plane
  • 帧缓冲(FB):能够显示图层的buffer

接着看下DRM driver里的两大模块GEM和KMS是如何连接显示器的,如下图所示:

上图里,蓝色框表示KMS里的模块。plane是连接crtc和framebuffer的纽带,而encoder是连接crtc和connector的纽带。GEM是负责和 物理的buffer打交道不是framebuffer。plane把获取到显存输出到crtc里,crtc通过connector接口输出到显示器。

ST官方的DRM驱动框架介绍

在Linux系统中,DRM驱动的核心主要就一个drm_driver结构体 ,驱动程序要初始化drm_driver结构体,然后调用drm_dev_init函数,将其注册到DRM core

简单分析ST官方编写的在Linux下的DRM驱动 ,打开stm32mp151.dtsi,然后找到ltdc节点内容,如下所示:

示例代码40.2.2.1中的ltdc节点信息是所有使用STM32MP1芯片的板子所共有的 ,并不是

完整的ltdc节点信息。可以看出ltdc节点的compatible属性值为"st,stm32-ltdc",因此在Linux源码中搜索这个字符串就可以找到STM32MP1的DRM驱动文件,这个文件为"drivers/gpu/drm/stm/drv.c"文件,打开此文件找的如下内容所示:

从示例代码40.2.2.2可以看出,这是一个标准的platform驱动,当驱动和设备匹配以后stm_drm_platform_probe函数就会执行。

和其他设备驱动一样,DRM也分为DRM设备和DRM驱动,drm_device结构体为DRM设备,drm_driver为DRM驱动,依次来看一下这两个结构体。

drm_device结构体

drm_device结构体定义在include/drm/drm_device.h文件里,内容如下 (有省略):

在编写DRM驱动的时候需要自行申请drm_device内存并且使用初始化 ,这个可以直接通过drm_dev_alloc函数来完成,函数原型如下:

c 复制代码
struct drm_device *drm_dev_alloc(struct drm_driver *driver, struct device *parent)

此函数会先调用kzalloc为drm_device分配内存,然后调用drm_dev_init初始化drm_device。函数参数和返回值含义如下:

  • driver:drm_driver结构体指针,也就是DRM设备对应的DRM 驱动。
  • parent:父设备。
  • 返回值:返回分配成功的新DRM设备,如果返回ERR_PTR的话就表示drm_device申请失败。

drm_device分配成功以后还需要使用drm_dev_register函数向内核注册,函数原型如下:

c 复制代码
int drm_dev_register(struct drm_device *dev, unsigned long flags)

函数参数和返回值含义如下:

  • dev:需要注册到内核的drm_device。
  • flags:传递给驱动.load函数的标志。
  • 返回值:0,成功;负数,失败。

drm_driver结构体

Linux内核为DRM驱动提供一个叫做drm_driver的结构体,drm_driver结构体包含了DRM驱动的完整属性和操作集合,因此每一个DRM驱动都必须有一个drm_driver。 drm_driver结构体定义在include/drm/drm_drv.h文件里,内容如下(有省略):

drm_driver结构体的成员变量很多,重点关注driver_features、fops和dumb_create

第57行,dumb_create是一个回调函数,用于创建gem对象,并分配物理buffer。

第74行,driver_features用来描述驱动特性,枚举类型drm_driver_feature定义了可以选择

的驱动特性:

  • DRIVER_GEM:驱动使用GEM内存管理,此特性必须选中!
  • DRIVER_MODESET:驱动支持模式设置接口(KMS)。
  • DRIVER_RENDER:驱动支持专用渲染节点。
  • DRIVER_ATOMIC:驱动提供完整的原子操作,以供用户空间API函数操作。
  • DRIVER_SYNCOBJ:驱动支持SYNCOBJ,用于命令提交的显式同步。
  • DRIVER_SYNCOBJ_TIMELINE:驱动支持SYNCOBJ时间线。
  • DRIVER_USE_AGP:驱动程序使用AGP接口,DRM核心将管理AGP资源。
  • DRIVER_LEGACY:表明这是一个使用影子附着的旧驱动程序,不使用。
  • DRIVER_PCI_DMA:驱动支持PCI DMA。
  • DRIVER_SG:驱动可以提供scatter/gather DMA功能。
  • DRIVER_HAVE_DMA:驱动支持DMA。
  • DRIVER_HAVE_IRQ:驱动支持IRQ,旧驱动使用。
  • DRIVER_KMS_LEGACY_CONTEXT:仅供nouveau使用!

第77行,fops就是一个简单的字符设备接口结构体。

STM32MP1的LTDC是个platform驱动,当设备和驱动匹配成功以后stm_drm_platform_probe函数就会执行, 函数内容如下:

第201行, 调用drm_dev_alloc函数,此函数主要完成两个功能:

  1. 给drm_device分配内存。
  2. 通过drm_dev_init函数初始化drm_device。

drm_dev_alloc会通过调用drm_dev_init函数将drm_driver和drm_device联系起来,drm_device结构体里面有个drvier指针成员变量,此成员变量指向DRM设备对应的DRM驱动的。因此,drm_dev_init函数会通过将drm_device下的driver成员变量指向drm_driver来实现两者相连。

第205 行,drv_load这个函数就是初始化KMS。

第209 行,注册drm_device对象进DRM core。

接下来看下,drv_load函数,内容如下:

c 复制代码
示例代码40.2.2.6 drv_load函数 
29  #define STM_MAX_FB_WIDTH 2048 
30  #define STM_MAX_FB_HEIGHT 2048 
31 
32  static const struct drm_mode_config_funcs drv_mode_config_funcs = { 
33      .fb_create = drm_gem_fb_create, 
34      .atomic_check = drm_atomic_helper_check, 
35      .atomic_commit = drm_atomic_helper_commit, 
36  }; 
...... 
79  static int drv_load(struct drm_device *ddev) 
80  { 
81      struct platform_device *pdev = to_platform_device(ddev->dev); 
82      struct ltdc_device *ldev; 
83      int ret; 
84 
85      DRM_DEBUG("%s\n", __func__); 
86 
87      ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL); 
88      if (!ldev) 
89          return -ENOMEM; 
90 
91      ddev->dev_private = (void *)ldev; 
92 
93      drm_mode_config_init(ddev); 
94 
95      /* 
96       * set max width and height as default value. 
97       * this value would be used to check framebuffer size limitation 
98       * at drm_mode_addfb(). 
99       */ 
100     ddev->mode_config.min_width = 0; 
101     ddev->mode_config.min_height = 0; 
102     ddev->mode_config.max_width = STM_MAX_FB_WIDTH; 
103     ddev->mode_config.max_height = STM_MAX_FB_HEIGHT; 
104     ddev->mode_config.funcs = &drv_mode_config_funcs; 
105 
106     ret = ltdc_load(ddev);
107     if (ret) 
108         goto err; 
109 
110     drm_mode_config_reset(ddev); 
111     drm_kms_helper_poll_init(ddev); 
112 
113     platform_set_drvdata(pdev, ddev); 
114 
115     return 0; 
116 err: 
117     drm_mode_config_cleanup(ddev); 
118     return ret; 
119 }

第102行,设置DRM驱动X轴(宽度)最大支持2048个像素。

第103行,设置DRM驱动Y轴(宽度)最大支持2048个像素,结合上一行,可以看出驱动里面设置的最大分辨率支持2048*2048。但是根据STM32MP157手册所描述,最大支持1366*768分辨率的屏幕。

第104行,设置framebuffer的回调函数结构体。

第106行,这里要引入drm_panel结构体,此结构体作用是提供一堆控制回调函数 。比如屏幕参数回调函数,背光控制函数等等。ltdc_load函数是负责初始化ltdc接口(同时connector和encoder一起初始化) 。在connector初始化的时候,就会调用drm_panel结构体里的获取屏幕参数函数(所以只需要提供一个屏的驱动就能正常显示了)。通常encoder和connector是放在同一个驱动初始化的,目的是为了方便驱动程序设计。

重点来了! 要完成整个DRM驱动的正常初始化,前面的GEM和KMS这些模块ST官方

已经提供了,只需提供一个drm_panel对象就行了 。打开"include/drm/drm_panel.h"文

件,找到如下内容所示:

这个结构体主要是第5行,funcs对象成员。在这里DRM驱动分析基本完成了。

RGB LCD驱动分析(屏幕驱动)

drm_panel就是DRM驱动的核心结构体,需要实现此结构体 。先来看一下panel_simple结构体。这里用到了面向对象的思维,drm_panel结构体是基类,panel_simple在drm_panel基础上增加了一些成员变量,相当于继承类。

使用的是RGB LCD屏,所以LCD驱动文件为drivers/gpu/drm/panel/panel-simple.c,打文件找到如下内容所示:

panel_simple结构体用来管理RGB LCD设备。

第100行,base成员变量,为drm_panel结构体类型。可以看出panel_simple就是在drm_panel的基础上发展而来的,在DRM驱动注册的时候就会回调base->funcs。

第105行,desc属性就是RGB屏参数结构体。

第107行,屏的背光结构体。

接着看下如何跟设备树匹配的, 找到如下所示内容:

示例代码40.3.1中,这是一个标准的platform驱动框架,第3494行就是匹配表。platform_of_match内容如下所示(有省略):

可以看出,platform_of_match里面有大量的匹配项,分别针对不同的屏幕,比如:

第3135行,这就是一个匹配项,compatible内容为"ampire,am-480272h3tmqw-t01h"。

第3136行,在platform框架里有个data成员变量,这个是一个void类型的指针,这里指向ampire_am_480272h3tmqw_t01h,内容如 下所示:

第516-525行,drm_display_mode结构体就是用来设置屏幕参数。

第529行,定义一个panel_desc结构体对象。

第530行,modes变量设置为ampire_am_480272h3tmqw_t01h_mode。

第531行,设置modes的数量。

第532行,设置屏幕为8bit。

第533-536行,设置屏幕实际显示区域的物理宽度,单位为毫米,此屏幕尺寸为105mm×67mm。

第537行,bus_format属性设置总线模式,include/uapi/linux/media-bus-format.h里面定义了所有可选的总线类型:

可以看出,Linux内核支持很多种不同的总线格式,比如RGB、YUV、Bayer等。以MEDIA_BUS_FMT_RGB888_1X24为例,看看这个总线格式的含义:

  1. 从"RGB888"可以看出,这是一个RGB888格式的。
  2. 后面的"1X24"表示一个像素点使用24bit,如果是2X8就表示一个像素点使用2个8bit表示。
  3. 有的右面也会有"BE"或"LE",BE表示最高位先传输,LE表示最低位先传输。

假设设备树里有个设备节点的compatible属性为"ampire,am-480272h3tmqw-t01h",那么就

会和驱动匹配成功,然后运行panel_simple_platform_probe函数,此函数的内容如下所示:

第3474行,使用of_match_node函数查找匹配的设备ID。

第3478行,当得到匹配的设备ID(of_device_id)以后就可以通过提取data成员变量得到屏幕参数信息,比如此处id->data就是ampire_am_480272h3tmqw_t01h。最后调用panel_simple_probe函数将其注册到内核,panel_simple_probe函数也定义在drivers/gpu/drm/panel/panel-simple.c文件中,函数内容如下所示:

c 复制代码
示例代码40.3.7 panel_simple_probe函数 
414 static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) 
415 { 
416     struct device_node *backlight, *ddc; 
417     struct panel_simple *panel; 
418     struct display_timing dt; 
419     int err; 
420 
421     panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); 
422     if (!panel) 
423         return -ENOMEM; 
424 
425     panel->enabled = false; 
426     panel->prepared = false; 
427     panel->desc = desc; 
428 
...... 
444     backlight = of_parse_phandle(dev->of_node, "backlight", 0); 
445     if (backlight) { 
446         panel->backlight = of_find_backlight_by_node(backlight); 
447         of_node_put(backlight); 
448 
449         if (!panel->backlight) 
450             return -EPROBE_DEFER; 
451     } 
452 
...... 
467     drm_panel_init(&panel->base); 
468     panel->base.dev = dev;
469     panel->base.funcs = &panel_simple_funcs; 
470 
471     err = drm_panel_add(&panel->base); 
472     if (err < 0) 
473         goto free_ddc; 
474 
475     dev_set_drvdata(dev, panel); 
476 
477     return 0; 
478 
479 free_ddc: 
480     if (panel->ddc) 
481         put_device(&panel->ddc->dev); 
482 free_backlight: 
483     if (panel->backlight) 
484         put_device(&panel->backlight->dev); 
485 
486     return err; 
487 }

第427行,设置屏幕参数。

第444行,从设备树里获取背光节点,所以设备树要提供"backlight"属性。

第467行,用drm_panel_init函数初始化屏幕。

第469行,设置panel_simple的回调函数,为DRM驱动注册的时候提供屏的参数。

第471行,把屏幕注册到内核。

LCD屏的驱动分析结束,总结一下添加自己的屏要做那些操作:

  1. 在根节点下提供一个LCD设备树 ,包含背光的节点和引用ltdc节点
  2. 在panel-simple.c文件里的platform_of_match结构体里添加一组设备ID ,此设备ID对应所使用的屏幕,重点是屏幕参数panel_desc结构体

硬件原理图分析

在STM32MP1开发板里,有一个RGB LCD接口,其原理如下所示:

上图左边对应的是LCD接口引脚图,右边为STM32MP1开发板RGB LCD接口的原理图。

LCD驱动程序编写

修改设备树

首先就是修改设备树,重点要注意以下几点:

  1. LCD所使用的IO配置,由于STM32MP1的IO支持复用,所以实际所使用的LCD引脚可能不一样。因此首先要根据实际硬件设计,修改LCD所有使用的IO配置。
  2. LDTC接口节点修改,修改相应的属性值,告诉内核要指定那个输出接口,比如输出到RGB LCD屏、MIPI屏等。本实验用到是正点原子ATK7016屏,也就是RGB LCD屏。
  3. 输出接口节点的编写,比如本实验用到的RGB LCD屏需要在根节点下添加RGB LCD节点。
  4. LCD背光节点信息修改,要根据实际所使用的背光IO来修改相应的设备节点信息。

接下来依次来看一下上面这三个节点如何去修改。

LCD屏幕使用IO配置

首先要检查一下设备树中LCD所使用的IO配置,这个其实ST都已经写好了,需要修改,不过还是要看一下。打开arch/arm/boot/dts/stm32mp15-pinctrl.dtsi文件,在pinctrl节点中找到如下内容:

c 复制代码
示例代码40.5.1.1 ltdc的pinmux节点 
1  ltdc_pins_b: ltdc-b-0 { 
2      pins { 
3          pinmux = <STM32_PINMUX('I', 14, AF14)>, /* LCD_CLK */ 
4              <STM32_PINMUX('I', 12, AF14)>, /* LCD_HSYNC */ 
5              <STM32_PINMUX('I', 13, AF14)>, /* LCD_VSYNC */ 
6              <STM32_PINMUX('K', 7, AF14)>, /* LCD_DE */ 
7              <STM32_PINMUX('I', 15, AF14)>, /* LCD_R0 */ 
8              <STM32_PINMUX('J', 0, AF14)>, /* LCD_R1 */
9              <STM32_PINMUX('J', 1, AF14)>, /* LCD_R2 */
10             <STM32_PINMUX('J', 2, AF14)>, /* LCD_R3 */ 
11             <STM32_PINMUX('J', 3, AF14)>, /* LCD_R4 */ 
12             <STM32_PINMUX('J', 4, AF14)>, /* LCD_R5 */ 
13             <STM32_PINMUX('J', 5, AF14)>, /* LCD_R6 */ 
14             <STM32_PINMUX('J', 6, AF14)>, /* LCD_R7 */ 
15             <STM32_PINMUX('J', 7, AF14)>, /* LCD_G0 */ 
16             <STM32_PINMUX('J', 8, AF14)>, /* LCD_G1 */ 
17             <STM32_PINMUX('J', 9, AF14)>, /* LCD_G2 */ 
18             <STM32_PINMUX('J', 10, AF14)>, /* LCD_G3 */ 
19             <STM32_PINMUX('J', 11, AF14)>, /* LCD_G4 */ 
20             <STM32_PINMUX('K', 0, AF14)>, /* LCD_G5 */ 
21             <STM32_PINMUX('K', 1, AF14)>, /* LCD_G6 */ 
22             <STM32_PINMUX('K', 2, AF14)>, /* LCD_G7 */ 
23             <STM32_PINMUX('J', 12, AF14)>, /* LCD_B0 */ 
24             <STM32_PINMUX('J', 13, AF14)>, /* LCD_B1 */ 
25             <STM32_PINMUX('J', 14, AF14)>, /* LCD_B2 */ 
26             <STM32_PINMUX('J', 15, AF14)>, /* LCD_B3 */ 
27             <STM32_PINMUX('K', 3, AF14)>, /* LCD_B4 */ 
28             <STM32_PINMUX('K', 4, AF14)>, /* LCD_B5 */ 
29             <STM32_PINMUX('K', 5, AF14)>, /* LCD_B6 */ 
30             <STM32_PINMUX('K', 6, AF14)>; /* LCD_B7 */ 
31         bias-disable; 
32         drive-push-pull; 
33         slew-rate = <1>; 
34     }; 
35 }; 
36 
37 ltdc_pins_sleep_b: ltdc-b-1 { 
38     pins { 
39         pinmux = <STM32_PINMUX('I', 14, ANALOG)>, /* LCD_CLK */ 
40             <STM32_PINMUX('I', 12, ANALOG)>, /* LCD_HSYNC */ 
41             <STM32_PINMUX('I', 13, ANALOG)>, /* LCD_VSYNC */ 
42             <STM32_PINMUX('K', 7, ANALOG)>, /* LCD_DE */ 
43             <STM32_PINMUX('I', 15, ANALOG)>, /* LCD_R0 */ 
44             <STM32_PINMUX('J', 0, ANALOG)>, /* LCD_R1 */ 
45             <STM32_PINMUX('J', 1, ANALOG)>, /* LCD_R2 */ 
46             <STM32_PINMUX('J', 2, ANALOG)>, /* LCD_R3 */ 
47             <STM32_PINMUX('J', 3, ANALOG)>, /* LCD_R4 */ 
48             <STM32_PINMUX('J', 4, ANALOG)>, /* LCD_R5 */ 
49             <STM32_PINMUX('J', 5, ANALOG)>, /* LCD_R6 */ 
50             <STM32_PINMUX('J', 6, ANALOG)>, /* LCD_R7 */ 
51             <STM32_PINMUX('J', 7, ANALOG)>, /* LCD_G0 */
52             <STM32_PINMUX('J', 8, ANALOG)>, /* LCD_G1 */ 
53             <STM32_PINMUX('J', 9, ANALOG)>, /* LCD_G2 */ 
54             <STM32_PINMUX('J', 10, ANALOG)>, /* LCD_G3 */ 
55             <STM32_PINMUX('J', 11, ANALOG)>, /* LCD_G4 */ 
56             <STM32_PINMUX('K', 0, ANALOG)>, /* LCD_G5 */ 
57             <STM32_PINMUX('K', 1, ANALOG)>, /* LCD_G6 */ 
58             <STM32_PINMUX('K', 2, ANALOG)>, /* LCD_G7 */ 
59             <STM32_PINMUX('J', 12, ANALOG)>, /* LCD_B0 */ 
60             <STM32_PINMUX('J', 13, ANALOG)>, /* LCD_B1 */ 
61             <STM32_PINMUX('J', 14, ANALOG)>, /* LCD_B2 */ 
62             <STM32_PINMUX('J', 15, ANALOG)>, /* LCD_B3 */ 
63             <STM32_PINMUX('K', 3, ANALOG)>, /* LCD_B4 */ 
64             <STM32_PINMUX('K', 4, ANALOG)>, /* LCD_B5 */ 
65             <STM32_PINMUX('K', 5, ANALOG)>, /* LCD_B6 */ 
66             <STM32_PINMUX('K', 6, ANALOG)>; /* LCD_B7 */ 
67     }; 
68 };

第1-35行,子节点ltdc_pins_b,为RGB LCD的24根数据线配置项和4根控制线配置项

第37-67行,子节点ltdc_pins_sleep_b,同样也是RGB LCD的24根数据线配置项和4根
控制线配置项

可以看出,这里有两个pinmux分别为:ltdc_pins_b和ltdc_pins_sleep_b,其中ltdc_pins_b是在默认模式下的RGB LCD pinmux配置,ltdc_pins_sleep_b是在sleep模式下RGB LCD的pinmux配置 。如果想要LCD屏进入睡眠模式就切换为sleep模式。正点原子STM32MP1开发板RGB LCD屏幕所使用的引脚和ST官方开发板一致 ,因此示

例代码40.5.1不需要做任何修改。如果所使用的开发板其LCD引脚和正点原子STM32MP1开发板不一致,一定要根据自己的实际硬件修改示例代码40.5.1.1。

LDTC接口节点修改

LTDC节点在stm32mp151.dtsi里已经写好一部分了,只需要告诉LTDC节点输出到RGB LCD 屏里就行。在stm32mp157d-atk.dts文件,添加如下内容所示:

c 复制代码
示例代码40.5.5.2 ltdc节点 
1  &ltdc { 
2      pinctrl-names = "default", "sleep"; 
3      pinctrl-0 = <&ltdc_pins_b>; 
4      pinctrl-1 = <&ltdc_pins_sleep_b>; 
5      status = "okay"; 
6 
7      port { 
8          #address-cells = <1>; 
9          #size-cells = <0>; 
10 
11         ltdc_ep0_out: endpoint@0 { 
12             reg = <0>; 
13             remote-endpoint = <&rgb_panel_in>;
14         };
15     };
16 };

第2-4行,给LTDC设置了两个pinmux模式,pinctrl-0为default模式,pinctrl-1为sleep模式,系统默认使用default模式。

第8-9行,设置port下子节点的reg属性的地址信息描述。

第11-14行,在port下添加了一个子节点为ltdc_ep0_out。在第12行里,reg属性值为0。

在第13行里,remote-endpoint属性是用来告诉ltdc节点输出到那里,是用RGB LCD屏做

实验,所以输出到rgb_panel_in接口。

输出接口编写

还需要添加一个LCD设备树节点,在stm32mp157d-atk.dts文件的根节点"/"下添加如下所示内容:

c 复制代码
示例代码40.5.5.3 LCD屏的设备节点 
1  panel_rgb: panel-rgb { 
2      compatible = "alientek,lcd-rgb"; 
3      backlight = <&backlight>; 
4      status = "okay"; 
5 
6      port { 
7          rgb_panel_in: endpoint { 
8              remote-endpoint = <&ltdc_ep0_out>; 
9          }; 
10     }; 
11 };

第2行,设置compatible属性值为"alientek,lcd-rgb",所以稍后要在panel-simple.c文件里的platform_of_match数组增加一个of_device_id结构体,此结构体的compatible成员属性

值为"alientek,lcd-rgb"。

第3行,设置backlight属性值为"&backlight",此属性值为引用背光节点。

第6-9行,告诉LCD驱动,要从LTDC节点里获取显示数据。第8行就是引用ltdc节点。

在panel-simple.c中添加屏幕参数

接着就要在panel-simple.c文件里面添加屏幕参数,打开此文件,找到platform_of_match数组,添加如下内容:

c 复制代码
示例代码40.5.2.1 屏的匹配属性 
1 .compatible = "alientek,lcd-rgb", 
2 .data = &alientek_desc,

添加完成以后如下图所示:

在上图里的alientek_desc保存参数,还需要继续在panel-simple.c里面实现alientek_desc,添加如下所示代码:

c 复制代码
示例代码40.5.2.2 alientek_desc结构体 
1  static const struct drm_display_mode ATK7016_mode = { 
2      .clock = 51200, /* LCD像素时钟,单位KHz */ 
3      .hdisplay = 1024, /* LCD X轴像素个数 */ 
4      .hsync_start = 1024 + 140, /* LCD X轴+hbp的像素个数 */ 
5      .hsync_end = 1024 + 140 + 20, /* LCD X轴+hbp+hspw的像素个数 */ 
6      .htotal = 1024 + 140 + 20 + 160,/* LCD X轴+hbp+hspw+hfp */ 
7      .vdisplay = 600, /* LCD Y轴像素个数 */ 
8      .vsync_start = 600 + 20, /* LCD Y轴+vbp的像素个数 */ 
9      .vsync_end = 600 + 20 + 3, /* LCD Y轴+vbp+vspw的像素个数 */ 
10     .vtotal = 600 + 20 + 3 + 12,/* LCD Y轴+vbp+vspw+vfp */ 
11     .vrefresh = 60, /* LCD的刷新频率为60HZ */ 
12 }; 
13 
14 static const struct panel_desc alientek_desc = { 
15     .modes = &ATK7016_mode, 
16     .num_modes = 1, 
17     .bus_format = MEDIA_BUS_FMT_RGB888_1X24, 
18 };

在示例代码40.5.2.2就是ATK7016屏的参数,并且设置为RGB888模式。如果使用的其他

屏幕,请按照屏幕手册对应的时序参数设置。

第2-11行,ATK7016屏幕时序参数,根据自己所使用的屏幕修改即可。

LCD屏幕背光节点信息

背光PWM节点设置

LCD背光使用PWM来控制,通过PWM波形来调节屏幕亮度。

正点原子的LCD接口背光控制IO连接到了STM32MP1的PD13引脚上,需要将PD13复用为TIM4_CH2,然后配置TIM4的CH2输出PWM信号,然后通过此PWM信号来控制LCD屏幕背光的亮度,接着来看一下如何在设备树中添加背光节点信息。

首先是PD13这个pinmux的配置,在stm32mp15-pinctrl.dtsi中找到如下内容:

c 复制代码
示例代码40.5.3.1 PD13的pinmux配置
1  pwm4_pins_b: pwm4-1 { 
2      pins { 
3          pinmux = <STM32_PINMUX('D', 13, AF2)>; /* TIM4_CH2 */ 
4          bias-pull-down; 
5          drive-push-pull; 
6          slew-rate = <0>; 
7      }; 
8  }; 
9 
10 pwm4_sleep_pins_b: pwm4-sleep-1 { 
11     pins { 
12         pinmux = <STM32_PINMUX('D', 13, ANALOG)>; /* TIM4_CH2 */ 
13     }; 
14 };

示例代码40.5.3.1默认设置了PD13引脚的两种 pinmux配置,从第3行可以看出,设置PD13复用为TIM4_CH2,并且设置电气属性为内部下拉和推挽输出。这是因为ST官方开发板就使用了PD13作为LCD的背光控制引脚,正点原子STM32MP1开发板也使用PD13作为LCD背光控制引脚,所以不需要修改。如果使用其他引脚作为LCD的背光控制引脚,那么就需要进行修改。

继续在stm32mp157d-atk.dts文件中向timers4追加内容,如下所示:

c 复制代码
示例代码40.5.3.2 向tim4节点追加内容 
1  &timers4 { 
2      status = "okay"; 
3      /* spare dmas for other usage */ 
4      /delete-property/dmas; 
5      /delete-property/dma-names; 
6      pwm4: pwm { 
7          pinctrl-0 = <&pwm4_pins_b>; 
8          pinctrl-1 = <&pwm4_sleep_pins_b>; 
9          pinctrl-names = "default", "sleep"; 
10         #pwm-cells = <2>; 
11         status = "okay"; 
12     }; 
13 };

第2行,把status设置为okay。

第4-5行,设置此节点不用dma。

第6行,pwm4是为pwm设置的一个别名。

第7-9行,设置PWM所使用的IO配置。

第10行,此参数是用来规定pwms属性的参数。比如:#pwm-cells =<2>,表示pwms属性有2个参数,如下所示:

|----------------------------|
| pwms= <&pwm4 1 5000000> |

其中pwm4表示使用PWM4,后面两个是参数,其中1表示使用PWM4的通道2(通道从1开始),5000000表示为200Hz。

如果背光用的其他pwm通道,比如pwm2,那么就需要仿照示例代码40.5.3.2,向timers2节点追加相应的内容。比如:如果要设置TIM2_CH4,那么timers2里的pwm节点下的pinmux配置就是TIM2_CH4。

backlight节点设置

到这里,PWM和相关的IO已经准备好了,但是Linux系统怎么知道TIM4_CH2就是控制LCD背光的呢?因此还需要一个节点来将LCD背光和TIM4_CH2连接起来。这个节点就是backlight,backlight节点描述可以参考Documentation/devicetree/bindings/leds/backlight/pwm-backlight.txt这个文档,此文档详细讲解了backlight节点该如何去创建,大概总结一下:

  1. 节点名称要为"backlight"。
  2. 节点的compatible属性值要为"pwm-backlight",因此可以通过在Linux内核中搜索"pwm-backlight"来查找PWM背光控制驱动程序,这个驱动程序文件为drivers/video/backlight/pwm_bl.c。
  3. pwms属性用于描述背光所使用的PWM的通道以及PWM频率,比如本章要使用的pwm4的第二个通道,pwm频率设置为200Hz。
  4. brightness-levels属性描述亮度级别,范围为0~255,0表示PWM占空比为0%,也就是亮度最低,255表示100%占空比,也就是亮度最高。至于设置几级亮度,可以自行填写此属性。
  5. default-brightness-level属性为默认亮度级别。

根据上述5点设置 backlight节点,在根节点创建一个backlight节点,在 tm32mp157d-atk.dts文件中新建内容如下:

c 复制代码
示例代码40.5.3.3 backlight节点 
1 backlight: backlight { 
2     compatible = "pwm-backlight"; 
3     pwms = <&pwm4 1 5000000>; 
4     brightness-levels = <0 4 8 16 32 64 128 255>; 
5     power-supply = <&v3v3>; 
6     default-brightness-level = <7>; 
7     status = "okay"; 
8 };

第3行,设置背光使用pwm4的第二个通道,PWM 频率为200Hz。

第4行,设置8级背光(0-7),分别为0、4、8、16、32、64、128、255,对应占空比为0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%,如果嫌少的话可以自行添加一

些其他的背光等级值。

第5行,设置默认背光等级为7,也就是100%的亮度。

注意:背光的驱动代码有点bug,如果设置0级屏不会灭屏,打开pwm_bl.c文件,找到123行,如下图所示:

将上图中123行的">"改为''>='即可。

关于背光的设备树节点信息就讲到这里,整个的LCD设备树节点内容就讲完了,按照这些节点内容配置自己的开发板即可。

运行测试

LCD屏幕的DRM基本测试

编译新的内核和设备树

输入如下命令重新编译Linux内核和设备树:

|-------------------------------------------|
| make uImage dtbs LOADADDR=0xC2000040 -j16 |

编译完成后用新的设备树和Linux内核启动。

配置内核

ST官方的默认配置已经使能了DRM驱动,还是要告诉各位如何配置内核,打开Linux内核图形化配置界面,按下路径找到对应的配置项:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| -> Device Drivers -> Graphics support [*] Direct Rendering Manager (XFree86 4.1.0 and higher DRI support) //选中 [*] DRM Support for STMicroelectronics SoC Series //选中 -> Display Panels [*] support for simple panels //选中 -> Backlight & LCD device support [*] Generic PWM based Backlight Driver //选中 |

用新的uImage镜像和stm32mp157d-atk.dtb设备树用来启动内核,如果设置正确那么在文件系统 /sys/class/drm/路径下,有如下图所示内容:

如果没有出现上图的内容,就要检查一下设备树和panel-simple.c文件是否设置正确。因为Linux一切皆文件,所以DRM驱动肯定会提供一个接口给用户使用,接口为"/dev/dri/card0"。可以通过此接口来设置LCD的显示。

文件系统使能libdrm库

没有libdrm库是不能调用drm驱动的,所以要在文件系统使能libdrm库,跳转到buildroot-2020.02.6的目录下,打开buildroot的图形化配置界面, 根据如下配置去使能libdrm库。

|-------------------------------------------------------------------------------------------------------------------|
| Location: -> Target packages -> Libraries -> Graphics ->[*]libdrm //选中 ->[*]Install test programs //选中 |

如下图所示:

上图中的Install test programs配置会生成modetest命令,此命令是用来测试DRM驱
。保存并重新编译buildroot的文件系统,然后直接将新得到的根文件系统解压到开发板正在使用的根文件系统中,命令如下:

|---------------------------|
| sudo tar -axvf rootfs.tar |

测试

重新启动开发板,使用modetest命令进行测试,输入如下命令可以查看modetest命令的使用方法:

|-----------------|
| modetest --help |

帮助信息如下图所示:

先输入如下命令查看设备信息

|-----------------|
| modetest -M stm |

-M:指定模块,这里查看"stm"这个设备。

输入命令以后就会打印出stm这个设备的详细信息,比较长,下图是一些重要信息:

从上图可以看出,Connectors的id为32,CRTC的id为35,这两个 ID很重要,一会测试要用到,要根据自己的实际情况填写。

输入如下命令测试DRM驱动

|-----------------------------------|
| modetest -M stm -s 32@35:1024x600 |

命令参数介绍如下:

  • -M:指定stm模块。
  • -s:32表示connectors的ID,35表示CRTC的ID,1024x600表示显示的模式。

显示结果如下图所示:

LCD屏幕的FB基本测试

之前的学习中有提到,KMS包含了FB框架。DRM驱动默认为CRTC用来控制,CRTC是可以模仿FB框架,实现使用FB接口。示例代码40.2.2.5中的第213行就是负责初始化基于CRTC的FB接口。只需在Linux内核图形化配置界面里配置以下选项。

使能DRM驱动的FB

配置路径如下:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| -> Device Drivers -> Graphics support -> Direct Rendering Manager (XFree86 4.1.0 and higher DRI support) -> [*]Enable legacy fbdev support for your modesetting driver //选中 |

如下图所示:

使能PL110

|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| -> Device Drivers -> Graphics support -> Frame buffer Devices -> Support for frame buffer devices -> <*>ARM PrimeCell PL110 support //选中,支持 /dev/fb0 |

配置如下图所示:

使能Linux logo显示

Linux内核启动的时候可以选择显示小企鹅logo。打开Linux内核图形化配置界面,按如下路径找到对应的配置项:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| -> Device Drivers -> Graphics support -> [*] Bootup logo //选中 -> [*] Standard black and white Linux logo (NEW) //选中 -> [*] Standard 16-color Linux logo (NEW) //选中 -> [*] Standard 224-color Linux logo (NEW) //选中 |

如下图所示:

上图中这三个选项分别对应黑白、16位、24位色彩格式的logo,把这三个都选中,都编译进Linux内核里面。设置好以后保存退出,重新编译Linux内核,编译完成以后使用新编译出来uImage镜像启动系统,在LCD屏幕左上角出现两个彩色的小企鹅logo(企鹅的个数和CPU的个数相同 ),屏幕背景色为黑色,如下图所示:

设置LCD作为终端控制台

LCD作为终端控制台前提条件要实现FB接口 。之前的学习过程中一直使用MobaXterm作为Linux开发板终端,开发板通过串口和MobaXterm进行通信 。现在已经驱动起来LCD并且提供了FB接口,所以可以设置LCD作为终端,也就是开发板使用自己的显示设备作为自己的终端,接上键盘就可以直接在开发板上敲命令了,将LCD设置为终端控制台的方法如下:

设置uboot中的bootargs

重启开发板,进入Linux命令行,重新设置bootargs参数的console内容,命令如下所示:

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| setenv bootargs 'console=tty1 console=ttySTM0,115200 root=/dev/nfs nfsroot=192.168.1.21:/home/liangwencong/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.246:192.168.1.21:192.168.1.1:255.255.255.0::eth0:off' |

注意红色字体部分设置console,这里设置了两遍console,第一次设置console=tty1,也就是设置LCD屏幕为控制台,第二遍又设置console=ttySTM0,115200,也就是设置串口也作为控制台。相当于打开了两个console,一个是LCD,一个是串口,重启开发板就会发现LCD和串口都会显示Linux启动log信息。但是此时还不能使用LCD作为终端进行交互。

修改/etc/inittab文件

打开开发板根文件系统中的/etc/inittab文件,在里面加入下面这一行:

|-------------------------|
| tty1::askfirst:-/bin/sh |

添加完成以后的/etc/inittab文件内容如下图所示:

修改完成以后保存/etc/inittab 并退出,然后重启开发板,重启以后开发板LCD屏幕最后一行会显示下面一行语句:Please press Enter to activate this console至此,就拥有了两套终端,一个是基于串口的MobaXterm,一个就是开发板的LCD屏幕,但是为了方便调试,以后还是以MobaXterm 为主。可以通过下面这一行命令向LCD 屏幕输出"hello linux!":

|--------------------------------|
| echo hello linux! > /dev/tty1 |

屏幕显示如下图所示:

LCD背光调节

背光调节不用像上一章PWM实验那样,直接操作pwmchipX(X=0~N)目录里面的文件,但是还是需要看一下。因为本章开启了TIM4_CH2这路PWM,上一章开启了TIM1_CH3这路PWM,相当于开启了两个不同的定时器对应的PWM通道。因此会在/sys/class/pwm目录下存在两个pwmchipX目录,如下图所示:

从上图可以看出此时有pwmchip0和pwmchip4。进入pwmchip0目录,查看一下路径中的寄存器首地址,如下图所示:

从上图看出此时pwmchip0对应的定时器寄存器首地址为0X40002000,这个正是TIM4的寄存器收地址!所以在本章pwmchip0对应的是TIM4。同样的方法查看一下pwmchip4的地址,如下图所示:

从上图可以看出,pwmchip4对应的定时器寄存器首地址为0X4000000,这个是TIM1定时器寄存器地址。所以在本章pwmchip0对应的是TIM4,pwmchip4对应的是TIM1

在开启多路PWM以后,一定要使用这个的方法来确定TIM对应的pwmchip文件!!

背光设备树节点设置了8个等级的背光调节,可以设置为0-7,可以通过设置背光等级来实现LCD背光亮度的调节,进入如下目录:

|-----------------------------------------------------|
| /sys/devices/platform/backlight/backlight/backlight |

此目录下的文件如下图所示:

上图中的brightness表示当前亮度等级,max_brigntness表示最大亮度等级。当前这两个文件内容如下图所示:

从上图可以看出,当前屏幕亮度等级为7,根据前面的分析可以,这个是100%亮度。屏幕最大亮度等级为7。如果要修改屏幕亮度,只需要向brightness写入需要设置的屏幕亮度等级即可。比如设置屏幕亮度等级为6,那么可以使用如下命令:

|----------------------|
| echo 6 > brightness |

输入上述命令以后就会发现屏幕亮度变暗了,如果设置brightness为0的话就会关闭LCD背光,屏幕就会熄灭。

总结

这里因为我之前学习stm32逻辑的时候,买的是精英板加一个2.8寸的MCU屏,那个是用8080时序去读写的,接口是FSMC接口 ;现在Linux驱动这边是一个RGB屏,接口是LTDC这个STM32MP1自带的屏幕接口 ,还是跟之前有所区别的。而且这里的屏幕驱动主要是修改设备树文件,不用自行编写驱动程序,stm32裸机开发还是要自己写程序然后根据时序来完成驱动的

采用DRM框架来驱动屏幕,不过需要完成一些配置来完成使用。屏幕的驱动和DRM的驱动都不用自己编写,都是在Linux里面配置就OK

需要修改设备树,在stm32mp15-pinctrl.dtsi里面,正点原子的屏幕就是采用的ltdc_pins_b和ltdc_pins_sleep_b这两个节点,这里我是正点原子的开发板就不需要修改,如果是其他开发板就可能需要改。

在stm32mp157d-atk.dts设备树中,需要添加ltdc节点,把关联的pinctrl给写进去,同时要在port里面设置reg属性信息,并添加ltdc_ep0_out子节点,并通过其remote-endpoint来告诉ltdc输出到LCD屏幕,也就是rgb_panel_in接口。

继续在设备树中,在根节点"/"下添加panel_rgb节点,需要设置compatile和backlight属性,status要写okay来开启,然后在port里面添加rgb_panel_in: endpoint设置remote-endpoint,连接到刚才LTDC的节点ltdc_ep0_out。

打开drivers/gpu/drm/panel/panel-simple.c,添加自己的屏幕参数,需要在platform_of_match数组中添加.compatible和.data;然后添加drm_display_mode结构体的屏幕参数,具体配置可以看上文;最后添加panel_desc结构体的刚才.data设置的节点alientek_desc,里面设置了.modes关联屏幕参数。

背光设置,需要通过PWM来控制,正点原子的开发板配置里面,是通过PD13的TIM4_CH2来控制的,pinctrl里面是配置好了的,只要在stm32mp157d-atk.dts设备树里面,追加timer4节点,在里面的pwm4:pwm里面,添加关联的pinctrl,通过#pwm-cells控制pwms属性的参数(一般频率和占空比就2个),然后status设置okay就好了。

pwm设置完,还需要再根节点"/"添加backlight节点,设置compatible属性,并通过pwms关联刚才设置的pwm4,通过brightness-levels设置背光级别,power-supply设置供电,然后default-brightness-level设置默认背光级别,最后status okay掉就好了。(设置背光为0可能不会熄灭,需要在drivers/video/backlight/pwm_bl.c里面修改123行,把if里面的条件改为>=0)

LCD的测试,Linux内核是默认打开的,但是buildroot需要使能libdrm库,重新编译一下。值域使用FB驱动,需要配置Linux内核,具体配置看上文,有好几个我就不赘述了,配置完FB还要设置uboot的bootargs加一个console,这样才能把LCD作为终端控制台。

相关推荐
学Linux的语莫4 分钟前
搭建服务器VPN,Linux客户端连接WireGuard,Windows客户端连接WireGuard
linux·运维·服务器
LateBloomer7775 分钟前
FreeRTOS——信号量
笔记·stm32·学习·freertos
legend_jz9 分钟前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
Komorebi.py10 分钟前
【Linux】-学习笔记04
linux·笔记·学习
黑牛先生11 分钟前
【Linux】进程-PCB
linux·运维·服务器
友友马30 分钟前
『 Linux 』网络层 - IP协议(一)
linux·网络·tcp/ip
wenchm40 分钟前
细说STM32单片机DMA中断收发RTC实时时间并改善其鲁棒性的另一种方法
stm32·单片机·嵌入式硬件
weiabc1 小时前
学习electron
javascript·学习·electron
猿java1 小时前
Linux Shell和Shell脚本详解!
java·linux·shell
fengbizhe1 小时前
笔试-笔记2
c++·笔记