嵌入式Linux使用TFT屏幕:使用TinyDRM点亮ST7789V屏幕
前言
最近某宝上买了几块小屏幕,1.3寸和2.0寸的,驱动都是ST7789V,网上看了下,基本都是使用fbtft
驱动,而且内核都是5.0以下的才能用,5.0以上的教程很少,又去fbtft
作者GitHub上翻阅得知作者又开发了新的驱动模块TinyDRM
(地址),据他描述,TinyDRM
对比fbtft
有以下优点:
DRM
在用户空间要求刷新屏幕的时候才刷新,而fbtft
刷新屏幕是按照固定的帧率进行刷新(fbtft
的fps
参数)。fbtft
只能一次刷新整个屏幕,而DRM
没有这样的限制。fbtft
在探测(probe
)到屏幕后打开它,DRM
在使用屏幕的时候才会打开它。fbtft
的rotate
值变成了rotation
DRM
支持双缓冲和page-flips
(应该是一种可以避免图像撕裂现象(tearing effect)的算法)。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-sunxi
和make
:
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 <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_mipi_dbi.h>
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/dma-buf.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/spi/spi.h>
#include <video/mipi_display.h>
#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 <cnflysky@qq.com>");
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
,重启。
启动之后应该就能看见屏幕上面显示内容了。
展示图
更多推荐
所有评论(0)