1. STM32MP1 LCD显示系统开发实战
作为一名嵌入式Linux驱动开发者,我最近在STM32MP157平台上完成了MIPI DSI接口LCD屏幕的完整驱动开发。这个项目涉及从硬件连接到软件驱动的全流程实现,过程中踩了不少坑,也积累了不少实战经验。本文将系统性地分享整个开发过程,希望能帮助正在开发类似项目的同行少走弯路。
1.1 项目背景与核心需求
STM32MP1系列是ST推出的首款Cortex-A7架构MPU,内置3D GPU和LCD-TFT显示控制器。我们需要在一块搭载STM32MP157C-DK2的开发板上驱动一块5.5寸MIPI DSI接口的LCD屏幕,实现从uboot启动logo到Linux桌面环境的完整显示功能。
核心需求包括:
- 正确配置硬件连接(MIPI差分对、背光控制、复位信号等)
- 完成设备树节点配置(LTDC、DSI、Panel等)
- 实现Linux DRM/KMS驱动框架支持
- 制作并显示启动logo
- 开发用户态测试工具验证显示功能
2. LCD显示系统基础
2.1 LCD屏幕类型与工作原理
现代嵌入式系统常用的显示技术主要有两种:
2.1.1 TFT-LCD显示原理
TFT-LCD(薄膜晶体管液晶显示器)是目前最主流的显示技术。其核心结构是在两片平行的玻璃基板之间填充液晶材料,下基板设置TFT阵列,上基板设置彩色滤光片。通过控制每个TFT的电压来改变液晶分子的排列方向,从而调节背光的透过率,实现像素的明暗变化。
关键参数:
- 分辨率:如800×480表示水平800像素,垂直480像素
- 像素格式:如RGB565表示每个像素用16位表示(5位红+6位绿+5位蓝)
- 刷新率:通常60Hz,表示每秒刷新60次
2.1.2 OLED显示原理
OLED(有机发光二极管)采用有机发光材料,当有电流通过时会自发光的显示技术。与LCD相比,OLED具有以下特点:
- 不需要背光模组,更薄更轻
- 对比度更高,黑色更纯净
- 响应速度更快
- 可视角度更大
- 功耗更低(显示黑色时)
2.2 常见显示接口技术
2.2.1 RGB接口
RGB接口是并行接口,包含以下信号线:
- 数据线(D0-D23):传输像素数据
- 同步信号(HSYNC/VSYNC):行同步和帧同步
- 数据使能(DE):有效数据区域标识
- 像素时钟(PCLK):数据采样时钟
优点:时序简单,易于实现
缺点:信号线多(通常需要20+引脚),抗干扰能力弱
2.2.2 LVDS接口
LVDS(低压差分信号)采用差分传输技术,主要特点:
- 每组数据线采用差分对传输(D+/D-)
- 传输速率高(可达数Gbps)
- 抗干扰能力强
- 需要专用的LVDS转换芯片
典型配置:
- 单通道:1对时钟+4对数据(支持最高1920×1200@60Hz)
- 双通道:1对时钟+8对数据(支持更高分辨率)
2.2.3 MIPI DSI接口
MIPI DSI(Display Serial Interface)是移动行业处理器接口联盟制定的显示接口标准,特点包括:
- 串行差分传输(1对时钟+1~4对数据)
- 传输速率高(单通道可达1.5Gbps)
- 支持命令模式和视频模式
- 引脚数量少(最少只需4线:CLK+/CLK-, D+/D-)
DSI协议栈:
code复制+-------------------+
| 应用层 |
+-------------------+
| DSI协议层 |
+-------------------+
| D-PHY物理层 |
+-------------------+
2.2.4 HDMI接口
HDMI(高清多媒体接口)特点:
- 同时传输视频和音频
- 支持高清分辨率(最高8K)
- 采用TMDS编码技术
- 需要授权费用
在嵌入式系统中使用较少,主要用于消费电子产品。
2.3 LCD时序参数解析
LCD显示需要严格的时序控制,主要参数包括:
c复制struct display_timing {
u32 pixelclock; // 像素时钟频率(Hz)
u32 hactive; // 水平有效像素
u32 hfront_porch;// 水平前廊
u32 hback_porch; // 水平后廊
u32 hsync_len; // 水平同步脉宽
u32 vactive; // 垂直有效行数
u32 vfront_porch;// 垂直前廊
u32 vback_porch; // 垂直后廊
u32 vsync_len; // 垂直同步脉宽
u32 flags; // 同步极性等标志
};
计算示例(以800×480@60Hz屏幕为例):
- 总行数 = vactive + vfront_porch + vsync_len + vback_porch
- 总像素数 = hactive + hfront_porch + hsync_len + hback_porch
- 帧率 = pixelclock / (总行数 × 总像素数)
3. STM32MP1显示子系统架构
3.1 硬件框图
STM32MP1显示子系统主要包含以下组件:
code复制+-------------------+ +-------------------+ +-------------------+
| 应用处理器 | | LTDC控制器 | | DSI主机 |
| (Cortex-A7) |<----->| (LCD-TFT Display |<----->| (MIPI DSI Host) |
| | AXI | Controller) | | |
+-------------------+ +-------------------+ +-------------------+
|
v
+-------------------+
| MIPI DSI屏 |
| (ST7701驱动IC) |
+-------------------+
3.2 LTDC控制器
LTDC(LCD-TFT Display Controller)是STM32MP1内置的显示控制器,主要特性:
- 支持RGB、I8080等接口
- 最大支持1366×768分辨率
- 支持多层混合(最多8层)
- 支持多种像素格式(ARGB8888、RGB888、RGB565等)
- 内置DMA引擎,自动从内存读取帧数据
3.3 DSI主机控制器
DSI主机控制器负责将LTDC输出的并行视频数据转换为MIPI DSI串行数据,主要特性:
- 兼容MIPI DSI 1.02和D-PHY 1.1规范
- 支持1/2/4条数据通道
- 最大带宽1Gbps/通道
- 支持视频模式和命令模式
4. 硬件连接与设备树配置
4.1 硬件连接设计
典型MIPI DSI LCD连接方式:
code复制+-------------------+ +-------------------+ +-------------------+
| STM32MP157 | | 电平转换芯片 | | LCD面板 |
| | | (如SN65DSI86) | | |
| DSI_CLKP ----->|------>| DSI_CLK+ |------>| DSI_CLK+ |
| DSI_CLKN ----->|------>| DSI_CLK- |------>| DSI_CLK- |
| DSI_D0P ----->|------>| DSI_D0+ |------>| DSI_D0+ |
| DSI_D0N ----->|------>| DSI_D0- |------>| DSI_D0- |
| GPIOx ----->|------>| RESET |------>| RESET |
| PWMx ----->|------>| BL_EN |------>| 背光使能 |
+-------------------+ +-------------------+ +-------------------+
4.2 设备树关键配置
4.2.1 PWM背光配置
dts复制&pwm2 {
pinctrl-names = "default";
pinctrl-0 = <&pwm2_pins_a>;
status = "okay";
};
panel_backlight: panel-backlight {
compatible = "pwm-backlight";
pwms = <&pwm2 0 5000000>; // PWM2,通道0,周期5ms(200Hz)
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
};
4.2.2 DSI主机配置
dts复制&dsi {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
dsi_in: endpoint {
remote-endpoint = <<dc_ep1_out>;
};
};
port@1 {
reg = <1>;
dsi_out: endpoint {
remote-endpoint = <&panel_in>;
};
};
};
panel@0 {
compatible = "sitronix,st7701";
reg = <0>;
reset-gpios = <&gpiog 9 GPIO_ACTIVE_HIGH>;
backlight = <&panel_backlight>;
power-supply = <&v3v3>;
port {
panel_in: endpoint {
remote-endpoint = <&dsi_out>;
};
};
};
};
4.2.3 LTDC控制器配置
dts复制<dc {
pinctrl-names = "default", "sleep";
pinctrl-0 = <<dc_pins_a>;
pinctrl-1 = <<dc_pins_sleep_a>;
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
ltdc_ep1_out: endpoint@1 {
reg = <1>;
remote-endpoint = <&dsi_in>;
};
};
};
5. Linux显示驱动框架
5.1 DRM/KMS框架概述
DRM(Direct Rendering Manager)是Linux内核的显示子系统,KMS(Kernel Mode Setting)是其核心组件,主要功能:
- 管理显示设备(CRTC、Encoder、Connector等)
- 处理显示模式设置
- 管理帧缓冲(framebuffer)
- 提供用户态接口(通过libdrm)
关键对象:
- CRTC:对应显示控制器(如LTDC)
- Encoder:将数字信号转换为特定接口信号(如DSI)
- Connector:物理连接器(如LCD面板)
- Plane:图像层,支持多层合成
5.2 STM32 DRM驱动实现
STM32MP1的DRM驱动主要由以下部分组成:
- stm32-ltdc:LTDC控制器驱动
- stm32-dsi:DSI主机驱动
- panel-simple:通用面板驱动
- dw-mipi-dsi:Synopsys DSI主机控制器驱动
驱动初始化流程:
c复制static int stm32_drm_probe(struct platform_device *pdev)
{
// 1. 初始化DRM设备
drm_dev = drm_dev_alloc(&stm32_drm_driver, dev);
// 2. 初始化LTDC
ret = stm32_ltdc_probe(pdev, drm_dev);
// 3. 初始化DSI
ret = stm32_dsi_probe(pdev, drm_dev);
// 4. 绑定CRTC/Encoder/Connector
ret = drm_bridge_attach(encoder, dsi_bridge, NULL);
// 5. 注册DRM设备
drm_dev_register(drm_dev, 0);
}
5.3 用户态接口
通过libdrm提供的API可以控制显示设备:
c复制// 打开DRM设备
fd = open("/dev/dri/card0", O_RDWR);
// 获取资源ID
drmModeRes *res = drmModeGetResources(fd);
// 获取Connector信息
drmModeConnector *conn = drmModeGetConnector(fd, res->connectors[0]);
// 获取CRTC信息
drmModeCrtc *crtc = drmModeGetCrtc(fd, res->crtcs[0]);
// 创建framebuffer
struct drm_mode_create_dumb create = {0};
create.width = width;
create.height = height;
create.bpp = 32;
ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
// 设置显示模式
drmModeSetCrtc(fd, crtc->crtc_id, fb_id, 0, 0, &conn->connector_id, 1, &mode);
6. 实战开发步骤
6.1 内核配置
确保以下内核选项已启用:
code复制CONFIG_DRM=y
CONFIG_DRM_STM=y
CONFIG_DRM_STM_LTDC=y
CONFIG_DRM_STM_DSI=y
CONFIG_DRM_PANEL_SITRONIX_ST7701=y
CONFIG_DRM_PANEL_SIMPLE=y
CONFIG_DRM_DW_MIPI_DSI=y
6.2 设备树调试技巧
- 检查设备树编译:
bash复制make dtbs
- 查看当前设备树:
bash复制fdtdump /sys/firmware/fdt
- 检查节点状态:
bash复制ls /proc/device-tree/
6.3 启动logo配置
- 准备ppm格式的logo图片:
bash复制pngtopnm logo.png | ppmquant 224 | pnmtoplainpnm > logo_linux_clut224.ppm
- 内核配置:
code复制CONFIG_LOGO=y
CONFIG_LOGO_LINUX_CLUT224=y
- 设备树配置居中显示:
dts复制chosen {
stdout-path = "serial0:115200n8";
linux,stdout-path = &uart4;
framebuffer {
compatible = "simple-framebuffer";
memory-region = <&logo_reserved>;
width = <800>;
height = <480>;
stride = <1600>; // 800*2 (RGB565)
format = "r5g6b5";
};
};
6.4 常见问题排查
- 无显示输出:
- 检查背光是否使能
- 测量MIPI DSI时钟信号
- 检查reset时序是否正确
- 查看内核日志:
dmesg | grep -i dsi
- 显示花屏:
- 检查像素格式配置(RGB565/RGB888)
- 验证时序参数是否正确
- 检查内存带宽是否足够
- 性能问题:
- 启用CMA区域:
cma=128M在bootargs中 - 使用硬件加速:
CONFIG_DRM_STM_LTDC_HW_ACCEL=y - 优化刷新率:调整pixelclock和时序参数
7. 用户态测试工具开发
7.1 使用modetest测试
modetest是DRM提供的测试工具,可以验证基本显示功能:
bash复制modetest -M stm
7.2 自定义测试程序
c复制#include <xf86drm.h>
#include <xf86drmMode.h>
int main(int argc, char *argv[])
{
int fd = open("/dev/dri/card0", O_RDWR);
// 获取资源
drmModeRes *res = drmModeGetResources(fd);
drmModeConnector *conn = drmModeGetConnector(fd, res->connectors[0]);
// 创建dumb buffer
struct drm_mode_create_dumb create = {0};
create.width = conn->modes[0].hdisplay;
create.height = conn->modes[0].vdisplay;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
// 映射framebuffer
drmModeAddFB(fd, create.width, create.height, 24, 32, create.pitch, create.handle, &fb_id);
// 设置CRTC
drmModeSetCrtc(fd, res->crtcs[0], fb_id, 0, 0, &conn->connector_id, 1, &conn->modes[0]);
// 绘制操作...
return 0;
}
7.3 显示图片示例
c复制#include <libdrm/drm.h>
#include <libjpeg/jpeglib.h>
void display_jpg(const char *filename, int fd, uint32_t fb_id)
{
// 1. 加载JPEG图片
struct jpeg_decompress_struct cinfo;
FILE *infile = fopen(filename, "rb");
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
// 2. 准备framebuffer
uint32_t stride = cinfo.output_width * (cinfo.output_components);
uint8_t *buffer = malloc(stride * cinfo.output_height);
// 3. 解码图片
while (cinfo.output_scanline < cinfo.output_height) {
uint8_t *row = buffer + (cinfo.output_scanline * stride);
jpeg_read_scanlines(&cinfo, &row, 1);
}
// 4. 显示到屏幕
drmModeFB *fb = drmModeGetFB(fd, fb_id);
void *map = mmap(0, fb->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, fb->handle);
memcpy(map, buffer, fb->size);
// 5. 清理资源
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
free(buffer);
munmap(map, fb->size);
}
8. 性能优化技巧
8.1 内存带宽优化
- 使用CMA连续内存:
dts复制reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x8000000>; // 128MB
linux,cma-default;
};
};
- 启用IOMMU:
dts复制&iommu {
status = "okay";
};
8.2 硬件加速
- 启用LTDC硬件光标:
c复制static const struct drm_crtc_funcs stm32_ltdc_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = stm32_ltdc_enable_vblank,
.disable_vblank = stm32_ltdc_disable_vblank,
.set_cursor = stm32_ltdc_crtc_set_cursor,
.move_cursor = stm32_ltdc_crtc_move_cursor,
};
- 使用DMA加速:
c复制static int stm32_ltdc_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct drm_gem_cma_object *gem;
if (!state->fb)
return 0;
gem = drm_fb_cma_get_gem_obj(state->fb, 0);
dma_sync_single_for_device(plane->dev->dev, gem->paddr,
gem->base.size, DMA_TO_DEVICE);
return 0;
}
9. 项目总结与经验分享
经过这个项目的实践,我总结了以下几点重要经验:
- 硬件连接检查:
- MIPI DSI差分对走线要等长,阻抗控制在100Ω
- 背光电路要提供足够的驱动电流
- 复位时序要严格遵循面板规格书要求
- 设备树调试技巧:
- 使用
fdtdump工具验证设备树是否正确加载 - 逐步添加节点,先确保基础功能正常
- 注意时钟配置,特别是DSI的PHY时钟
- 性能优化方向:
- 使用CMA预留足够的内存给显示系统
- 启用硬件加速功能(如LTDC的层混合)
- 合理设置刷新率和分辨率平衡性能与功耗
- 常见问题快速定位:
- 无显示先查电源和背光
- 花屏检查像素格式和时序
- 闪屏可能是时钟不稳定导致
这个项目完整实现了STM32MP1平台的MIPI DSI LCD驱动,从硬件设计到软件驱动,再到用户态测试工具开发,涵盖了嵌入式显示系统开发的各个环节。希望这些经验能帮助其他开发者更快地完成类似项目。