嵌入式Linux使用TFT屏幕:使用TinyDRM点亮ST7789V屏幕

前言

最近某宝上买了几块小屏幕,1.3寸和2.0寸的,驱动都是ST7789V,网上看了下,基本都是使用fbtft驱动,而且内核都是5.0以下的才能用,5.0以上的教程很少,又去fbtft作者GitHub上翻阅得知作者又开发了新的驱动模块TinyDRM(地址),据他描述,TinyDRM对比fbtft有以下优点:

  1. DRM在用户空间要求刷新屏幕的时候才刷新,而fbtft刷新屏幕是按照固定的帧率进行刷新(fbtftfps参数)。
  2. fbtft只能一次刷新整个屏幕,而DRM没有这样的限制。
  3. fbtft在探测(probe)到屏幕后打开它,DRM在使用屏幕的时候才会打开它。
  4. fbtftrotate值变成了rotation
  5. DRM支持双缓冲和page-flips(应该是一种可以避免图像撕裂现象(tearing effect)的算法)。
  6. DRM支持在GPU中渲染,然后在屏幕上显示。

当然TinyDRM的缺点也是有的:只能使用兼容标准MIPI-DCS命令集的显示IC,非标准命令集的无法使用。这里提供一个简单的办法确定屏幕是否兼容MIPI-DCS指令集:查看屏幕的0x2A,0x2B,0x2C命令对应的操作,如果它们的操作分别是行地址设置(Column address set),列地址设置(Row address set)显存写入(Memory write),则表示该屏幕应该兼容MIPI-DCS指令集。

正题

博主使用的开发板是Orange Pi Zero,系统是armbian.

编译树外(out of tree)模块

首先打开终端,安装一下linux-headers-current-sunximake:

sudo apt install linux-headers-current-sunxi make

P.S.如果安装的内核头文件不是您现在使用的内核的版本,那您需要自行下载符合您目前内核版本的内核头文件,或者从(内核)源码编译。如果您无法找到需要的内核头文件(换句话说,必须得从[内核]源码编译了),请您查看下面从内核源码编译的步骤。
然后,新建一个文件夹以存放我们的源码文件st7789v.c:

mkdir st7789v
cd st7789v
touch st7789v.c

然后将下面的代码,填入st7789v.c中:

// SPDX-License-Identifier: GPL-2.0+
/*
 * DRM driver for display panels connected to a Sitronix ST7789V
 * display controller in SPI mode.
 * Copyright 2021 CNflysky
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ST7789V_MY BIT(7)
#define ST7789V_MX BIT(6)
#define ST7789V_MV BIT(5)
#define ST7789V_RGB BIT(3)

static void st7789v_pipe_enable(struct drm_simple_display_pipe *pipe,
                                struct drm_crtc_state *crtc_state,
                                struct drm_plane_state *plane_state) {
  struct mipi_dbi_dev *dbidev = drm_to_mipi_dbi_dev(pipe->crtc.dev);
  struct mipi_dbi *dbi = &dbidev->dbi;
  int idx;
  uint8_t addr_mode;
  if (!drm_dev_enter(pipe->crtc.dev, &idx)) return;
  DRM_DEBUG_KMS("\n");
  int ret = mipi_dbi_poweron_reset(dbidev);
  if (ret) drm_dev_exit(idx);
  // init seq begin
  mipi_dbi_command(dbi, MIPI_DCS_SET_PIXEL_FORMAT, 0x05);
  // Porch setting
  mipi_dbi_command(dbi, 0xB2, 0x0C, 0x0C, 0x00, 0x33, 0x33);
  // Gate control
  mipi_dbi_command(dbi, 0xB7, 0x35);
  // VCOM settings
  mipi_dbi_command(dbi, 0xBB, 0x19);
  // LCM Control
  mipi_dbi_command(dbi, 0xC0, 0x2C);
  // VDV and VRH Command Enable
  mipi_dbi_command(dbi, 0xC2, 0x01);
  // VRH Set
  mipi_dbi_command(dbi, 0xC3, 0x12);
  // VDV Set
  mipi_dbi_command(dbi, 0xC4, 0x20);
  // Frame Rate Control in Normal Mode
  mipi_dbi_command(dbi, 0xC6, 0x0F);
  // Power control 1
  mipi_dbi_command(dbi, 0xD0, 0xA4, 0xA1);
  // Positive Voltage Gamma Control
  mipi_dbi_command(dbi, 0xE0, 0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54,
                   0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23);
  // Negative Voltage Gamma Control
  mipi_dbi_command(dbi, 0xE1, 0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44,
                   0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23);
  mipi_dbi_command(dbi, MIPI_DCS_ENTER_INVERT_MODE);
  switch (dbidev->rotation) {
    default:
      addr_mode = 0;
      break;
    case 90:
      addr_mode = ST7789V_MX | ST7789V_MV;
      break;
    case 180:
      addr_mode = ST7789V_MX | ST7789V_MY;
      break;
    case 270:
      addr_mode = ST7789V_MY | ST7789V_MV;
      break;
  }
  //if colors were inverted(blue as red),then uncomment the following line:
  //addr_mode |= ST7789V_RGB;
  mipi_dbi_command(dbi, MIPI_DCS_SET_ADDRESS_MODE, addr_mode);
  mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);
  mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);
  msleep(20);
  // init seq end
  mipi_dbi_enable_flush(dbidev, crtc_state, plane_state);
}

static const struct drm_simple_display_pipe_funcs st7789v_pipe_funcs = {
    .enable = st7789v_pipe_enable,
    .disable = mipi_dbi_pipe_disable,
    .update = mipi_dbi_pipe_update,
    .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
};

static const struct drm_display_mode st7789v_mode = {
    DRM_SIMPLE_MODE(240, 240, 26, 26),
};

DEFINE_DRM_GEM_CMA_FOPS(st7789v_fops);

static struct drm_driver st7789v_driver = {
    .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
    .fops = &st7789v_fops,
    DRM_GEM_CMA_DRIVER_OPS_VMAP,
    .debugfs_init = mipi_dbi_debugfs_init,
    .name = "st7789v",
    .desc = "Sitronix ST7789V",
    .date = "20211022",
    .major = 1,
    .minor = 0,
};

static const struct of_device_id st7789v_of_match[] = {
    {.compatible = "sitronix,st7789v_240x240"},
    {},
};

MODULE_DEVICE_TABLE(of, st7789v_of_match);

static const struct spi_device_id st7789v_id[] = {
    {"st7789v_240x240", 0},
    {},
};

MODULE_DEVICE_TABLE(spi, st7789v_id);

static int st7789v_probe(struct spi_device *spi) {
  struct device *dev = &spi->dev;
  struct drm_device *drm;
  uint32_t rotation = 0;
  struct mipi_dbi_dev *dbidev =
      devm_drm_dev_alloc(dev, &st7789v_driver, struct mipi_dbi_dev, drm);
  if (IS_ERR(dbidev)) return PTR_ERR(dbidev);
  struct mipi_dbi *dbi = &dbidev->dbi;
  drm = &dbidev->drm;
  dbi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
  if (IS_ERR(dbi->reset)) {
    DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n");
    return PTR_ERR(dbi->reset);
  }
  struct gpio_desc *dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
  if (IS_ERR(dc)) {
    DRM_DEV_ERROR(dev, "Failed to get gpio 'dc'\n");
    return PTR_ERR(dc);
  }
  dbidev->backlight = devm_of_find_backlight(dev);
  if (IS_ERR(dbidev->backlight)) return PTR_ERR(dbidev->backlight);
  device_property_read_u32(dev, "rotation", &rotation);
  int ret = mipi_dbi_spi_init(spi, dbi, dc);
  if (ret) return ret;
  ret = mipi_dbi_dev_init(dbidev, &st7789v_pipe_funcs, &st7789v_mode, rotation);
  if (ret) return ret;
  drm_mode_config_reset(drm);
  ret = drm_dev_register(drm, 0);
  if (ret) return ret;
  spi_set_drvdata(spi, drm);
  drm_fbdev_generic_setup(drm, 0);
  return 0;
}

static int st7789v_remove(struct spi_device *spi) {
  struct drm_device *drm = spi_get_drvdata(spi);
  drm_dev_unplug(drm);
  drm_atomic_helper_shutdown(drm);
  return 0;
}

static void st7789v_shutdown(struct spi_device *spi) {
  drm_atomic_helper_shutdown(spi_get_drvdata(spi));
}

static struct spi_driver st7789v_spi_driver = {
    .driver =
        {
            .name = "st7789v",
            .of_match_table = st7789v_of_match,
        },
    .id_table = st7789v_id,
    .probe = st7789v_probe,
    .remove = st7789v_remove,
    .shutdown = st7789v_shutdown,
};

module_spi_driver(st7789v_spi_driver);
MODULE_DESCRIPTION("Sitronix ST7789V DRM driver");
MODULE_AUTHOR("CNflysky ");
MODULE_LICENSE("GPL");

代码解析

要修改源码来兼容其他显示屏的话,主要修改源码的这几个部分:
1.st7789v_pipe_enable函数,把里面的初始化代码换成厂家提供的初始化代码;
2.compatible这里面的device_id改成自己的device_id;
3.drm_display_mode里面,DRM_SIMPLE_MODE宏的4个参数分别是横向分辨率,纵向分辨率,屏幕宽度,屏幕长度(单位:mm)

编译

Makefile:

obj-m += st7789v.o
make -C /lib/modules/`uname -r`/build M=`pwd` modules

编译完成后,在本文件夹下的st7789v.ko文件,即为我们需要的模块文件。
将它复制到/lib/modules/$(uname -r)/kernel/drivers/gpu/drm/tiny下,然后运行depmod.

从内核源码编译

P.S.如不到万不得已,请勿使用此方法。编译整个内核的驱动模块需要非常久的时间,会大大拖累开发进度。
进入内核源码目录drivers/gpu/drm/tiny,复制上面的驱动代码,保存为st7789v.c.

修改Makefile和Kconfig

打开Makefile,在最后添加:

obj-$(CONFIG_TINYDRM_ST7789V)		+= st7789v.o

然后打开Kconfig,在最后添加:

config TINYDRM_ST7789V
	tristate "DRM support for Sitronix ST7789V display panels"
	depends on DRM && SPI
	select DRM_KMS_HELPER
	select DRM_KMS_CMA_HELPER
	select DRM_MIPI_DBI
	select BACKLIGHT_CLASS_DEVICE
	help
	  DRM driver for Sitronix ST7789V panels.
	  If M is selected the module will be called st7789v.

然后打开menuconfig,Device Drivers -> Graphics Support -> DRM support for Sitronix ST7789V display panels空格键改成M,编译。
编译完之后,将出来的内核模块st7789v.ko放入/lib/modules/$(uname -r)/kernel/drivers/gpu/drm/tiny中,然后运行depmod

配置设备树

在当前文件夹下编辑st7789v.dts设备树配置文件:

/dts-v1/;
/plugin/;

/ {
        compatible = "allwinner,sun8i-h3";
        fragment@0 {
                target = <&spi1>;
                __overlay__ {
                        status = "okay";
                        spidev@0{
                                status = "disabled";
                        };
                        spidev@1{
                                status = "disabled";
                        };
                };
        };

        fragment@1 {
                target = <&spi1>;
                __overlay__ {
                        /* needed to avoid dtc warning */
                        #address-cells = <1>;
                        #size-cells = <0>;

                        display@0{
                                compatible = "sitronix,st7789v_240x240";
                                reg = <0>;
                                spi-max-frequency = <40000000>;
                                dc-gpios = <&pio 0 199 0>;
                                reset-gpios = <&pio 0 198 0>;
                                rotation = <0>;
                        };
                };
        };
};

连线

ST7789 引脚 Orange Pi Zero
VCC 3.3V
GND 0V/GND
SCL PA14
SDA PA15
RES PG06
DC PG07
CS PA13/GND

然后sudo armbian-add-overlay st7789v.dts,重启。
启动之后应该就能看见屏幕上面显示内容了。

展示图

嵌入式Linux使用TFT屏幕:使用TinyDRM点亮ST7789V屏幕_第1张图片
嵌入式Linux使用TFT屏幕:使用TinyDRM点亮ST7789V屏幕_第2张图片
嵌入式Linux使用TFT屏幕:使用TinyDRM点亮ST7789V屏幕_第3张图片

你可能感兴趣的:(嵌入式,linux,kernel,嵌入式,驱动开发)