前言

距上一次写文章有点时间了,今天调通了一块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。
为避免广告嫌疑,这里就不放链接了。

提取屏幕信息

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

  1. 屏幕接口定义
  2. 驱动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 <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/regulator/consumer.h>
#include <video/mipi_display.h>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 Command2w280bf036i_command(w280bf036i, 0xFF, 0x77, 0x01, 0x00, 0x00, 0x13);// Unknownw280bf036i_command(w280bf036i, 0xEF, 0x08);// Command2 BK0 Selection: Disable the BK function of Command2w280bf036i_command(w280bf036i, 0xFF, 0x77, 0x01, 0x00, 0x00, 0x10);// Display Line Settingw280bf036i_command(w280bf036i, 0xC0, 0x4f, 0x00);// Porch Controlw280bf036i_command(w280bf036i, 0xC1, 0x10, 0x0c);// Inversion selection & Frame Rate Controlw280bf036i_command(w280bf036i, 0xC2, 0x07, 0x14);// Unknownw280bf036i_command(w280bf036i, 0xCC, 0x10);// Positive Voltage Gamma Controlw280bf036i_command(w280bf036i, 0xB0, 0x0a, 0x18, 0x1e, 0x12, 0x16, 0x0c, 0x0e,0x0d, 0x0c, 0x29, 0x06, 0x14, 0x13, 0x29, 0x33, 0x1c);// Negative Voltage Gamma Controlw280bf036i_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 Command2w280bf036i_command(w280bf036i, 0xFF, 0x77, 0x01, 0x00, 0x00, 0x11);// Vop Amplitude settingw280bf036i_command(w280bf036i, 0xB0, 0x5d);// VCOM amplitude settingw280bf036i_command(w280bf036i, 0xB1, 0x61);// VGH Voltage settingw280bf036i_command(w280bf036i, 0xB2, 0x84);// TEST Command Settingw280bf036i_command(w280bf036i, 0xB3, 0x80);// VGL Voltage settingw280bf036i_command(w280bf036i, 0xB5, 0x4d);// Power Control 1w280bf036i_command(w280bf036i, 0xB7, 0x85);// Power Control 2w280bf036i_command(w280bf036i, 0xB8, 0x20);// Source pre_drive timing set1w280bf036i_command(w280bf036i, 0xC1, 0x78);// Source EQ2 Settingw280bf036i_command(w280bf036i, 0xC2, 0x78);// MIPI Setting 1w280bf036i_command(w280bf036i, 0xD0, 0x88);// Sunlight Readable Enhancementw280bf036i_command(w280bf036i, 0xE0, 0x00, 0x00, 0x02);// Noise Reduce Controlw280bf036i_command(w280bf036i, 0xE1, 0x06, 0xa0, 0x08, 0xa0, 0x05, 0xa0, 0x07,0xa0, 0x00, 0x44, 0x44);// Sharpness Controlw280bf036i_command(w280bf036i, 0xE2, 0x20, 0x20, 0x44, 0x44, 0x96, 0xa0, 0x00,0x00, 0x96, 0xa0, 0x00, 0x00);// Color Calibration Controlw280bf036i_command(w280bf036i, 0xE3, 0x00, 0x00, 0x22, 0x22);// Skin Tone Preservation Controlw280bf036i_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("CNflysky@qq.com");
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 Command2w280bf036i_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 = <&dsi1>;__overlay__{status = "okay";#address-cells = <1>;#size-cells = <0>;port {dsi_out_port:endpoint {remote-endpoint = <&panel_dsi_port>;};};w280bf036i:w280bf036i@0 {compatible    = "wlk,w280bf036i";status        = "okay";reg           = <0>;reset-gpios   = <&gpio 47 1>;   // Dummy GPIO , Unusedport {panel_dsi_port: endpoint {remote-endpoint = <&dsi_out_port>;};};};};};fragment@1 {target = <&gpio>;__overlay__ {w280bf036i_pins: w280bf036i_pins {brcm,pins = <47>;brcm,function = <1>; // outbrcm,pull = <0>; // off};};};fragment@2 {target = <&i2c_csi_dsi>;__overlay__ {#address-cells = <1>;#size-cells = <0>;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链接

嵌入式Linux使用TFT屏幕:使用树莓派4B的MIPI-DSI接口点亮ST7701S屏幕相关推荐

  1. 嵌入式Linux使用TFT屏幕:使用Kernel 5.18自带的panel-mipi-dbi模块点亮ST7789V屏幕

    前言 最近无聊翻Linux内核源码,发现了个好玩的东西:tinydrm驱动里合并了panel-mipi-dbi模块.从名字上来看,这个模块是用来驱动MIPI-DBI屏幕用的.又翻阅了下notro的Gi ...

  2. 树莓派4B驱动1.8寸ST7735S TFT屏幕

    用到的第三方库的官方文档:Introduction - Luma.LCD: Display drivers for PCD8544, ST7735, ST7789, HT1621, UC1701X, ...

  3. 2022最新树莓派4B学习笔记:系统备份 OpenCV SPI TFT HTML

    树莓派4B学习笔记 一.首次使用 1.系统烧录 2.优化配置与备份 3.一些基本操作 二.摄像头 三.Pi Dashboard好看的仪表盘 四.OpenCV安装编译 五.SPI_TFT屏幕 六.最终代 ...

  4. 树莓派4B Raspbian系统常用Linux 命令及快捷键

    2021SC@SDUSC 命令在哪里输入? 通过 SSH 客户端登录你的树莓派,或者进入树莓派的桌面运行终端程序,接下来就可以输入命令了. 什么是 sudo 命令? 在一行命令之前加上 sudo,表示 ...

  5. 树莓派4B安装系统,配置远程连接和WiFi,更新源,更新中文支持,基本Linux命令,用Python输出hello和“你好,世界”

    树莓派4B 系列1 从入门到进阶 作者(当然就是我本人了,哈哈哈哈哈),写这篇文章的目的是给刚入门树莓派或者从3B/3B+版本过度到4B的小朋友们总结出一些成系统的规律和方法,以及4B全新版本的变化. ...

  6. 嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)

    你好!这里是风筝的博客, 欢迎和我一起交流. 最近入手了一块spi接口的tft彩屏,想着在我的h3板子上使用framebuffer驱动起来. 我们知道: Linux抽象出FrameBuffer这个设备 ...

  7. 树莓派 4B 下 Linux 系统高级命令行

    2021SC@SDUSC 现在我们已经将 ubuntu 系统安装到了树莓派上面,并且实现了两种连接树莓派的方式,同时对 Linux 系统中常见的命令有了一定的了解,接下来就是对一些 Linux 系统高 ...

  8. 树莓派4B 4G从烧录系统到无屏幕配置ssh和静态IP

    目录 一.烧录系统 二. 进入系统(难点) 1.串口连接树莓派和PC电脑利用putty连接 驱动安装 串口如何连接 2.PC机和树莓派连接同一个热点 3.网线直连PC机和树莓派访问 三.设置静态IP ...

  9. 工控机的io开发_Amazing!从树莓派4B主板到嵌入式无风扇工控机,只需三步!

    因应现今工业物联网的趋势,为满足广大树莓派开发者工业App应用落地的需求,研华推出了基于Raspberry Pi 4的工业级网关套件UNO-220. UNO-220工业树莓派网关套件,包含一套工业级的 ...

最新文章

  1. linux上安装redis
  2. 4月21日云栖精选夜读:【校园头条】第1期:找实习、找工作时,让你脱颖而出的秘籍...
  3. Facebook's New Real-time Messaging System: HBase to Store 135+ Billion Messages a Month
  4. 2020-08-20
  5. 判断存储,是栈?还是队列?
  6. iOS开发经验总结(一)
  7. 程序员接单网站哪个好(盘点十大程序员接私活平台)
  8. python list 交集并集差集补集
  9. 模式识别与机器学习(PATTERNnbsp;RECO…
  10. auto头文件 qt_C++ auto 关键字的使用
  11. 中小企业如何才能招聘到合适的程序员?
  12. 【英语:基础高阶_经典外刊阅读】L3.长句子扒皮—如何快速寻找主干
  13. TFN系列超声波探伤仪使用评价怎么样?好不好用?
  14. JVM的三种常见GC:Minor GC、Major GC与Full GC
  15. 制造linux安装系统老毛桃u盘
  16. mysql 初始化修改密码问题
  17. 拼多多新手商家开店必做的四步
  18. linux远程打印,SUSE 远程打印
  19. 领导说,不懂汇报,怎么给你升职?让我学习麦肯锡的:金字塔原理、MECE法则...
  20. java为什么用工厂模式_【Java】为什么建议没事不要随便用工厂模式创建对象?...

热门文章

  1. 如何快速无损地把flv格式文件转换为mp4格式(可在iPhone上播放)
  2. Java中有哪些锁?
  3. pq分解法潮流计算的matlab,第四节PQ分解法潮流计算
  4. 从有道云笔记迁移到另一个有道云笔记账号
  5. MoviePy - 中文文档2-快速上手-创建和导出video clips
  6. GBF ASIA亚太电池展
  7. 在STM32上实现NTFS之4:GPT分区表的C语言实现(1):主GPT表头的实现
  8. 用python制作一张简单的节日贺卡
  9. error LNK2001: 无法解析的外部符号 _DllMainCRTStartup@12
  10. Android 炫酷进度条