嵌入式Linux使用TFT屏幕:使用树莓派4B的MIPI

您所在的位置:网站首页 ips屏幕和hdmi 嵌入式Linux使用TFT屏幕:使用树莓派4B的MIPI

嵌入式Linux使用TFT屏幕:使用树莓派4B的MIPI

2024-02-21 13:11| 来源: 网络整理| 查看: 265

前言

距上一次写文章有点时间了,今天调通了一块MIPI DSI屏幕, 特写一篇笔记置于此,希望能帮到也想研究这个MIPI DSI的朋友。

正题

博主使用的开发板为Raspbery Pi 4B,系统为Raspberry Pi OS 64Bit (full版本)。

关于系统版本

博主测试了一下各个版本的Raspberry Pi OS对于DRM驱动的兼容性,发现驱动只能在Raspberry Pi OS 11(bullseye)版本以上运行,之前的旧版本系统博主测试过了均不能使用。推测原因是因为旧版本系统使用的legacy GL driver不兼容drm,即使在raspi-config中启用了fakekms驱动也不能运行,kernel报出一长串错误。博主使用的系统版本为2022年1月28日发布的Raspberry Pi OS 64Bit正式版,经测试该系统能正常驱动屏幕。 *32位版也可以正常使用该驱动。

屏幕信息

博主使用的屏幕是一块2.8寸的IPS屏幕,MIPI接口,驱动IC是ST7701S,分辨率是480*640。 为避免广告嫌疑,这里就不放链接了。

提取屏幕信息

博主拿到屏幕后,也拿到了商家提供的初始化代码和屏幕信息,我们主要关注这两个部分:

屏幕接口定义驱动IC datasheet(如果没有IC的datasheet,屏幕的也行)

博主这里放几张图:引脚定义

在这里插入图片描述 我们要从这两张图中提取到关键的信息。 首先第一张图中我们可以得出来引脚定义,后面需要根据它来lay板子。 还能从第一张图中得知,我们的屏幕是1 Lane的。 第二张图中则包含了关键的初始化序列,我们需要用它来初始化我们的屏幕。 好了,基本信息都齐了,开干。

开发环境准备

打开终端,安装一下raspberrypi-kernel-headers:

sudo apt install raspberrypi-kernel-headers

P.S.如果安装的内核头文件不是您现在使用的内核的版本,那您需要自行下载符合您目前内核版本的内核头文件,或者从(内核)源码编译。如果您无法找到需要的内核头文件(换句话说,必须得从[内核]源码编译了),请您参考博主的上一篇文章 嵌入式Linux使用TFT屏幕:使用TinyDRM点亮ST7789V屏幕 中的从内核源码编译章节。 然后我们建个文件夹,就取名叫w280bf036i:

mkdir w280bf036i

然后在那个文件夹中编写我们的驱动源码(panel-wlk-w280bf036i.c):

/* ** Copyright (C) 2022 CNflysky. All rights reserved. ** Kernel DRM driver for W280BF036I LCD Panel in DSI interface. ** Driver IC: ST7701 */ #include #include #include #include #include #include #include #include #include #include struct w280bf036i_panel_desc { const struct drm_display_mode *mode; unsigned int lanes; unsigned long flags; enum mipi_dsi_pixel_format format; }; struct w280bf036i { struct drm_panel panel; struct mipi_dsi_device *dsi; const struct w280bf036i_panel_desc *desc; struct gpio_desc *reset; }; static inline struct w280bf036i *panel_to_w280bf036i(struct drm_panel *panel) { return container_of(panel, struct w280bf036i, panel); } static inline int w280bf036i_dsi_write(struct w280bf036i *w280bf036i, const void *seq, size_t len) { return mipi_dsi_dcs_write_buffer(w280bf036i->dsi, seq, len); } #define w280bf036i_command(w280bf036i, seq...) \ { \ const uint8_t d[] = {seq}; \ w280bf036i_dsi_write(w280bf036i, d, ARRAY_SIZE(d)); \ } static void w280bf036i_init_sequence(struct w280bf036i *w280bf036i) { // Command2 BK3 Selection: Enable the BK function of Command2 w280bf036i_command(w280bf036i, 0xFF, 0x77, 0x01, 0x00, 0x00, 0x13); // Unknown w280bf036i_command(w280bf036i, 0xEF, 0x08); // Command2 BK0 Selection: Disable the BK function of Command2 w280bf036i_command(w280bf036i, 0xFF, 0x77, 0x01, 0x00, 0x00, 0x10); // Display Line Setting w280bf036i_command(w280bf036i, 0xC0, 0x4f, 0x00); // Porch Control w280bf036i_command(w280bf036i, 0xC1, 0x10, 0x0c); // Inversion selection & Frame Rate Control w280bf036i_command(w280bf036i, 0xC2, 0x07, 0x14); // Unknown w280bf036i_command(w280bf036i, 0xCC, 0x10); // Positive Voltage Gamma Control w280bf036i_command(w280bf036i, 0xB0, 0x0a, 0x18, 0x1e, 0x12, 0x16, 0x0c, 0x0e, 0x0d, 0x0c, 0x29, 0x06, 0x14, 0x13, 0x29, 0x33, 0x1c); // Negative Voltage Gamma Control w280bf036i_command(w280bf036i, 0xB1, 0x0a, 0x19, 0x21, 0x0a, 0x0c, 0x00, 0x0c, 0x03, 0x03, 0x23, 0x01, 0x0e, 0x0c, 0x27, 0x2b, 0x1c); // Command2 BK1 Selection: Enable the BK function of Command2 w280bf036i_command(w280bf036i, 0xFF, 0x77, 0x01, 0x00, 0x00, 0x11); // Vop Amplitude setting w280bf036i_command(w280bf036i, 0xB0, 0x5d); // VCOM amplitude setting w280bf036i_command(w280bf036i, 0xB1, 0x61); // VGH Voltage setting w280bf036i_command(w280bf036i, 0xB2, 0x84); // TEST Command Setting w280bf036i_command(w280bf036i, 0xB3, 0x80); // VGL Voltage setting w280bf036i_command(w280bf036i, 0xB5, 0x4d); // Power Control 1 w280bf036i_command(w280bf036i, 0xB7, 0x85); // Power Control 2 w280bf036i_command(w280bf036i, 0xB8, 0x20); // Source pre_drive timing set1 w280bf036i_command(w280bf036i, 0xC1, 0x78); // Source EQ2 Setting w280bf036i_command(w280bf036i, 0xC2, 0x78); // MIPI Setting 1 w280bf036i_command(w280bf036i, 0xD0, 0x88); // Sunlight Readable Enhancement w280bf036i_command(w280bf036i, 0xE0, 0x00, 0x00, 0x02); // Noise Reduce Control w280bf036i_command(w280bf036i, 0xE1, 0x06, 0xa0, 0x08, 0xa0, 0x05, 0xa0, 0x07, 0xa0, 0x00, 0x44, 0x44); // Sharpness Control w280bf036i_command(w280bf036i, 0xE2, 0x20, 0x20, 0x44, 0x44, 0x96, 0xa0, 0x00, 0x00, 0x96, 0xa0, 0x00, 0x00); // Color Calibration Control w280bf036i_command(w280bf036i, 0xE3, 0x00, 0x00, 0x22, 0x22); // Skin Tone Preservation Control w280bf036i_command(w280bf036i, 0xE4, 0x44, 0x44); w280bf036i_command(w280bf036i, 0xE5, 0x0d, 0x91, 0xa0, 0xa0, 0x0f, 0x93, 0xa0, 0xa0, 0x09, 0x8d, 0xa0, 0xa0, 0x0b, 0x8f, 0xa0, 0xa0); w280bf036i_command(w280bf036i, 0xE6, 0x00, 0x00, 0x22, 0x22); w280bf036i_command(w280bf036i, 0xE7, 0x44, 0x44); w280bf036i_command(w280bf036i, 0xE8, 0x0c, 0x90, 0xa0, 0xa0, 0x0e, 0x92, 0xa0, 0xa0, 0x08, 0x8c, 0xa0, 0xa0, 0x0a, 0x8e, 0xa0, 0xa0); w280bf036i_command(w280bf036i, 0xE9, 0x36, 0x00); w280bf036i_command(w280bf036i, 0xEB, 0x00, 0x01, 0xe4, 0xe4, 0x44, 0x88, 0x40); w280bf036i_command(w280bf036i, 0xED, 0xff, 0x45, 0x67, 0xfa, 0x01, 0x2b, 0xcf, 0xff, 0xff, 0xfc, 0xb2, 0x10, 0xaf, 0x76, 0x54, 0xff); w280bf036i_command(w280bf036i, 0xEF, 0x10, 0x0d, 0x04, 0x08, 0x3f, 0x1f); /* disable Command2 */ // w280bf036i_command(w280bf036i, 0xFF, 0x77, 0x01, 0x00, 0x00, 0x00); } static int w280bf036i_prepare(struct drm_panel *panel) { struct w280bf036i *w280bf036i = panel_to_w280bf036i(panel); gpiod_set_value(w280bf036i->reset, 0); msleep(50); gpiod_set_value(w280bf036i->reset, 1); msleep(150); mipi_dsi_dcs_soft_reset(w280bf036i->dsi); msleep(5); w280bf036i_init_sequence(w280bf036i); mipi_dsi_dcs_set_tear_on(w280bf036i->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); mipi_dsi_dcs_exit_sleep_mode(w280bf036i->dsi); return 0; } static int w280bf036i_enable(struct drm_panel *panel) { return mipi_dsi_dcs_set_display_on(panel_to_w280bf036i(panel)->dsi); } static int w280bf036i_disable(struct drm_panel *panel) { return mipi_dsi_dcs_set_display_off(panel_to_w280bf036i(panel)->dsi); } static int w280bf036i_unprepare(struct drm_panel *panel) { struct w280bf036i *w280bf036i = panel_to_w280bf036i(panel); mipi_dsi_dcs_enter_sleep_mode(w280bf036i->dsi); gpiod_set_value(w280bf036i->reset, 0); return 0; } static int w280bf036i_get_modes(struct drm_panel *panel, struct drm_connector *connector) { struct w280bf036i *w280bf036i = panel_to_w280bf036i(panel); const struct drm_display_mode *desc_mode = w280bf036i->desc->mode; struct drm_display_mode *mode; mode = drm_mode_duplicate(connector->dev, desc_mode); if (!mode) { dev_err(&w280bf036i->dsi->dev, "failed to add mode %ux%u@%u\n", desc_mode->hdisplay, desc_mode->vdisplay, drm_mode_vrefresh(desc_mode)); return -ENOMEM; } drm_mode_set_name(mode); drm_mode_probed_add(connector, mode); connector->display_info.width_mm = desc_mode->width_mm; connector->display_info.height_mm = desc_mode->height_mm; return 1; } static const struct drm_panel_funcs w280bf036i_funcs = { .disable = w280bf036i_disable, .unprepare = w280bf036i_unprepare, .prepare = w280bf036i_prepare, .enable = w280bf036i_enable, .get_modes = w280bf036i_get_modes, }; static const struct drm_display_mode w280bf036i_mode = { .clock = 25000, .hdisplay = 480, .hsync_start = 480 + /* HFP */ 10, .hsync_end = 480 + 10 + /* HSync */ 4, .htotal = 480 + 10 + 4 + /* HBP */ 20, .vdisplay = 640, .vsync_start = 640 + /* VFP */ 8, .vsync_end = 640 + 8 + /* VSync */ 4, .vtotal = 640 + 8 + 4 + /* VBP */ 14, .width_mm = 43, .height_mm = 57, .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, }; static const struct w280bf036i_panel_desc w280bf036i_desc = { .mode = &w280bf036i_mode, .lanes = 1, .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST, .format = MIPI_DSI_FMT_RGB888, }; static int w280bf036i_dsi_probe(struct mipi_dsi_device *dsi) { struct w280bf036i *w280bf036i = devm_kzalloc(&dsi->dev, sizeof(*w280bf036i), GFP_KERNEL); if (!w280bf036i) return -ENOMEM; const struct w280bf036i_panel_desc *desc = of_device_get_match_data(&dsi->dev); dsi->mode_flags = desc->flags; dsi->format = desc->format; dsi->lanes = desc->lanes; w280bf036i->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(w280bf036i->reset)) { dev_err(&dsi->dev, "Couldn't get our reset GPIO\n"); return PTR_ERR(w280bf036i->reset); } drm_panel_init(&w280bf036i->panel, &dsi->dev, &w280bf036i_funcs, DRM_MODE_CONNECTOR_DSI); int ret = drm_panel_of_backlight(&w280bf036i->panel); if (ret) return ret; drm_panel_add(&w280bf036i->panel); mipi_dsi_set_drvdata(dsi, w280bf036i); w280bf036i->dsi = dsi; w280bf036i->desc = desc; return mipi_dsi_attach(dsi); } static int w280bf036i_dsi_remove(struct mipi_dsi_device *dsi) { struct w280bf036i *w280bf036i = mipi_dsi_get_drvdata(dsi); mipi_dsi_detach(dsi); drm_panel_remove(&w280bf036i->panel); return 0; } static const struct of_device_id w280bf036i_of_match[] = { {.compatible = "wlk,w280bf036i", .data = &w280bf036i_desc}, {}}; MODULE_DEVICE_TABLE(of, w280bf036i_of_match); static struct mipi_dsi_driver w280bf036i_dsi_driver = { .probe = w280bf036i_dsi_probe, .remove = w280bf036i_dsi_remove, .driver = { .name = "w280bf036i", .of_match_table = w280bf036i_of_match, }, }; module_mipi_dsi_driver(w280bf036i_dsi_driver); MODULE_AUTHOR("[email protected]"); MODULE_DESCRIPTION("WLK w280bf036i LCD Panel Driver"); MODULE_LICENSE("GPL");

好了,驱动代码编写完成,我们来解析一下这个代码: 首先在static void w280bf036i_init_sequence函数里面,我们填入了厂家所给的初始化代码:

static void w280bf036i_init_sequence(struct w280bf036i *w280bf036i) { w280bf036i_command(w280bf036i, MIPI_DCS_SOFT_RESET, 0x00); /* We need to wait 5ms before sending new commands */ msleep(5); w280bf036i_command(w280bf036i, MIPI_DCS_EXIT_SLEEP_MODE, 0x00); // Command2 BK3 Selection: Enable the BK function of Command2 w280bf036i_command(w280bf036i, 0xFF, 0x77, 0x01, 0x00, 0x00, 0x13); // Your initial code here... }

然后在w280bf036i_mode这个结构体中,我们填上屏幕的Porch参数:

static const struct drm_display_mode w280bf036i_mode = { .clock = 25000, .hdisplay = 480, .hsync_start = 480 + /* HFP */ 10, .hsync_end = 480 + 10 + /* HSync */ 4, .htotal = 480 + 10 + 4 + /* HBP */ 20, .vdisplay = 640, .vsync_start = 640 + /* VFP */ 8, .vsync_end = 640 + 8 + /* VSync */ 4, .vtotal = 640 + 8 + 4 + /* VBP */ 14, .width_mm = 43, .height_mm = 57, .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, };

最后,在w280bf036i_desc这个结构体中,我们填上屏幕的lane数量:

static const struct w280bf036i_panel_desc w280bf036i_desc = { .mode = &w280bf036i_mode, .lanes = 1, .flags = MIPI_DSI_MODE_VIDEO, .format = MIPI_DSI_FMT_RGB888, };

然后在当前目录下,编写Makefile:

obj-m += panel-wlk-w280bf036i.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

最后,我们执行make命令,编译模块:

make

编译完成后,目录下会出现多个文件,确保有panel-wlk-w280bf036i.ko即可。

编辑设备树

驱动编写完毕,我们还需要设备树用来探测(Probe)设备,编写设备树代码(vc4-kms-dsi-w280bf036i.dts)如下:

// compile: dtc -@ -I dts -O dtb -o vc4-kms-dsi-w280bf036i.dtbo vc4-kms-dsi-w280bf036i.dts /dts-v1/; /plugin/; / { compatible = "brcm,bcm2835"; fragment@0 { target = ; __overlay__{ status = "okay"; #address-cells = ; #size-cells = ; port { dsi_out_port:endpoint { remote-endpoint = ; }; }; w280bf036i:w280bf036i@0 { compatible = "wlk,w280bf036i"; status = "okay"; reg = ; reset-gpios = ; // Dummy GPIO , Unused port { panel_dsi_port: endpoint { remote-endpoint = ; }; }; }; }; }; fragment@1 { target = ; __overlay__ { w280bf036i_pins: w280bf036i_pins { brcm,pins = ; brcm,function = ; // out brcm,pull = ; // off }; }; }; fragment@2 { target = ; __overlay__ { #address-cells = ; #size-cells = ; status = "okay"; }; }; };

我的转接板设计为DSI的I2C SCL脚用于连接Reset,故使用45号引脚作为Reset PIN. P.S. 该设计有问题,不应该将I2C引脚作为GPIO使用,应加一颗I2C转GPIO芯片(如PCA9536)。 P.P.S. 因为转接板虽然打好了,但是TMD元件还没到,所以先把I2C接口启用了让44和45都高电平阻止屏幕复位,等元件到了会更新的…

应用 dtc -@ -I dts -O dtb -o vc4-kms-dsi-w280bf036i.dtbo vc4-kms-dsi-w280bf036i.dts sudo cp vc4-kms-dsi-w280bf036i.dtbo /boot/overlays/ sudo cp panel-wlk-w280bf036i.ko /lib/modules/`uname -r`/kernel/drivers/gpu/drm/panel sudo depmod echo "ignore_lcd=1" >> /boot/config.txt echo "dtoverlay=vc4-kms-dsi-w280bf036i" >> /boot/config.txt sudo reboot

执行完上面的命令,板子重启之后,屏幕上就能显示树莓派桌面了。

展示

由于初始化代码或者是博主的layout有些问题,屏幕的显示效果不太正常,待再次调整。 桌面,颜色有点诡异 不过,这个屏幕跑UFOTest居然能跑到80帧,还是挺令人意外的 在这里插入图片描述

链接

本文所用的所有代码都能在这里找到: GitHub链接



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3