Linux驱动标准SPI TFT屏幕ST7789V电容触摸屏FT6336U
1. 实验物料
一个驱动芯片使用ST7789V的TFT显示屏+FT6336U为主控的电容触摸


2. Framebuffer
在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。Frame 是帧的意思,buffer 是缓冲的意思,这意味着 Framebuffer 就是一块内存,里面保存着一帧图像。Framebuffer 中保存着一帧图像的每一个像素颜色值,假设 LCD 的 分辨率是 1024x768,每一个像素的颜色用 32 位来表示,那么 Framebuffer 的 大小就是:1024x768x32/8=3145728 字节。
帧缓冲(framebuffer)是Linux系统中的一种显示驱动接口,它将显示设备(如LCD)进行抽象、屏蔽了不同显示设备硬件的实现,对应用层抽象为一块显示内存(显存),它允许上层应用程序直接对显示缓冲区进行读写操作,而用户不必关心物理显存的位置等具体细节,这些都由Framebuffer设备驱动来完成。
FrameBuffer设备对应的设备文件为/dev/fbX(X为数字,0、1、2、3等),Linux下可支持多个FrameBuffer设备,最多可达32个,分别为/dev/fb0到/dev/fb31,开发板出厂系统中,/dev/fb0设备节点便是LCD屏。
2.1 简要分析fbtft框架
例子为ST7789V
对应的驱动C文件路径是内核下路径为: drivers/staging/fbtft/fb\_st7789v.c
以下是复制源码片段
static struct fbtft_display display = {
.regwidth = 8,
.width = 128,
.height = 160,
.init_sequence = default_init_sequence,
.gamma_num = 2,
.gamma_len = 16,
.gamma = DEFAULT_GAMMA,
.fbtftops = {
//.init_display = init_display,
.set_addr_win = set_addr_win,
.set_var = set_var,
.set_gamma = set_gamma,
},
};
struct fbtft_display 用于描述一款 TFT-LCD,包括硬件参数+硬件访问操作函数,需要根据 LCD 驱动 IC 的手册进行填写:
- regwidth: LCD 驱动 IC 寄存器的位宽;
- width / height: LCD 的分辨率;
- init_sequence: 初始化序列;
- fbtftops: LCD 操作函数集,init_display 和 init_sequence 一般只需要设置其中一项就行了;
代码中
FBTFT\_REGISTER\_DRIVER(DRVNAME, "sitronix,st7789v", &display);
这句话是宏定义中实现了一个驱动程序,其中根据设备树匹配规则,可以匹配SPI设备和平台总线设备。
设备树里的带有 "sitronix,st7735r" 属性的节点匹配上,触发 fbtft-core.c / fbtft\_probe\_common()。
static int __init fbtft_driver_module_init(void) \
{ \
int ret; \
\
ret = spi_register_driver(&fbtft_driver_spi_driver); \
if (ret < 0) \
return ret; \
ret = platform_driver_register(&fbtft_driver_platform_driver); \
if (ret < 0) \
spi_unregister_driver(&fbtft_driver_spi_driver); \
return ret; \
}
根据分析probe函数可以得出其中调用了 drivers/staging/fbtft/fbtft-core.c
文件
核心代码为:
/**
* fbtft_probe_common() - Generic device probe() helper function
* @display: Display properties
* @sdev: SPI device
* @pdev: Platform device
*
* Allocates, initializes and registers a framebuffer
*
* Either @sdev or @pdev should be NULL
*
* Return: 0 if successful, negative if error
*/
int fbtft_probe_common(struct fbtft_display *display,
struct spi_device *sdev,
struct platform_device *pdev)
其中参数 struct spi_device *sdev 也就是说支持SPI外设驱动。
2.2 fbtft_probe_common 函数分析
关键代码 :
1241行: pdata = fbtft\_properties\_read(dev);
在这句代码中将会从device设备信息中这里应该是从设备树中读取配置信息,进入函数内可查看支持的配置项
也就是说我们可以在设备树中写如下的配置,将会使用fbtft_property_value进行读取并填充到 struct fbtft_platform_data 结构体中保存
static struct fbtft_platform_data *fbtft_properties_read(struct device *dev)
{
struct fbtft_platform_data *pdata;
if (!dev_fwnode(dev)) {
dev_err(dev, "Missing platform data or properties\n");
return ERR_PTR(-EINVAL);
}
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->display.width = fbtft_property_value(dev, "width");
pdata->display.height = fbtft_property_value(dev, "height");
pdata->display.regwidth = fbtft_property_value(dev, "regwidth");
pdata->display.buswidth = fbtft_property_value(dev, "buswidth");
pdata->display.backlight = fbtft_property_value(dev, "backlight");
pdata->display.bpp = fbtft_property_value(dev, "bpp");
pdata->display.debug = fbtft_property_value(dev, "debug");
pdata->rotate = fbtft_property_value(dev, "rotate");
pdata->bgr = device_property_read_bool(dev, "bgr");
pdata->fps = fbtft_property_value(dev, "fps");
pdata->txbuflen = fbtft_property_value(dev, "txbuflen");
pdata->startbyte = fbtft_property_value(dev, "startbyte");
device_property_read_string(dev, "gamma", (const char **)&pdata->gamma);
if (device_property_present(dev, "led-gpios"))
pdata->display.backlight = 1;
if (device_property_present(dev, "init"))
pdata->display.fbtftops.init_display =
fbtft_init_display_from_property;
pdata->display.fbtftops.request_gpios = fbtft_request_gpios;
return pdata;
}
关于字段的描述信息有如下说明
* struct fbtft_display - Describes the display properties
* @width: Width of display in pixels
* @height: Height of display in pixels
* @regwidth: LCD Controller Register width in bits
* @buswidth: Display interface bus width in bits
* @backlight: Backlight type.
* @fbtftops: FBTFT operations provided by driver or device (platform_data)
* @bpp: Bits per pixel
* @fps: Frames per second
* @txbuflen: Size of transmit buffer
* @init_sequence: Pointer to LCD initialization array
* @gamma: String representation of Gamma curve(s)
* @gamma_num: Number of Gamma curves
* @gamma_len: Number of values per Gamma curve
* @debug: Initial debug value
*struct fbtft_display- 描述显示属性
*@width:以像素为单位的显示宽度
*@height:以像素为单位的显示高度
*@regwidth:LCD控制器寄存器宽度(以位为单位)
*@buswidth:以位为单位显示接口总线宽度
*@backlight:背光类型。
*@fbtftops:FBTFT操作由驱动程序或设备提供(platform_data)
*@bpp:每像素位
*@fps:每秒帧数
*@txbuflen:传输缓冲区大小
*@init_sequence:指向LCD初始化数组的指针
*@gamma:gamma曲线的字符串表示
*@gamma_num:伽玛曲线数
*@gamma_len:每条gamma曲线的值数
*@debug:初始调试值
* struct fbtft_platform_data - Passes display specific data to the driver
* @display: Display properties
* @gpios: Pointer to an array of pinname to gpio mappings
* @rotate: Display rotation angle
* @bgr: LCD Controller BGR bit
* @fps: Frames per second (this will go away, use @fps in @fbtft_display)
* @txbuflen: Size of transmit buffer
* @startbyte: When set, enables use of Startbyte in transfers
* @gamma: String representation of Gamma curve(s)
* @extra: A way to pass extra info
*struct fbtft_platform_data-将显示特定的数据传递给驱动程序
*@display:显示属性
*@gpios:pinname到gpio映射数组的指针
*@rotate:显示旋转角度
*@bgr:LCD控制器bgr位
*@fps:每秒帧数(这将消失,在@fbtft_display中使用@fps)
*@txbuflen:传输缓冲区大小
*@startbyte:设置后,启用在传输中使用startbyte
*@gamma:gamma曲线的字符串表示
*@extra:一种传递额外信息的方式
我们继续往下分析代码
在执行到这里的部分将会初始化 framebuffer
info = fbtft_framebuffer_alloc(display, dev, pdata);
if (!info)
return -ENOMEM;
这里会优先选择display结构体内的参数为默认参数,如果设备数有配置将会覆盖display结构体原先相同的配置
3. 编写设备树
需要注意的是,fbtft的驱动只有在内核framebuffer相关驱动开启后才会出现,所以要先开启fb驱动支持
Device Drivers -> Graphics support -> Frame buffer Devices --->
然后寻找fbtft驱动,路径如下:
Device Drivers -> Staging drivers -> Support for small TFT LCD display modules --->,编译进内核
3.1 编写TFT设备树
修改内容
- 首先确定屏幕的引脚使用的有哪些,排查是否有其他功能被占用,将被占用的功能注释掉。
- 添加SPI屏幕需要的引脚并设置好复用例如: rest、dc、bl
在 &pinctrl
节点内添加如下内容设置GPIO的复用关系
设置SPI复用
spi3_tft{
spi3_tft:spi3-tft{
rockchip,pins =
/* spi3_clkm1 */
<4 RK_PC2 2 &pcfg_pull_up_drv_level_1>,
/* spi3_cs */
<4 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>,
/* spi3_mosim1 */
<4 RK_PC3 2 &pcfg_pull_up_drv_level_1>;
};
};
配置TFT
/* 配置TFT */
tft_rst {
gpio3_pb6:gpio3-pb6 {
rockchip,pins = <3 RK_PB6 RK_FUNC_GPIO &pcfg_output_high>;
};
};
tft_dc {
gpio3_pb0:gpio3-pb0 {
rockchip,pins = <3 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
tft_bl {
gpio3_pc2:gpio3-pc2 {
rockchip,pins = <3 RK_PC2 RK_FUNC_GPIO &pcfg_output_low>;
};
};
因为我使用的是SPI3的复用管脚所以开启SPI3
&spi3{
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi3_tft &gpio3_pb6 &gpio3_pb0 &gpio3_pc2>;
#address-cells = <1>;
#size-cells = <0>;
tft: lcd@0{
compatible = "sitronix,st7789v"; //固定的写法使用sitronix,st7789v驱动
spi-max-frequency = <100000000>; //设置SPI的速率
reg = <0>;//SPI序号
spi-cpol;
spi-cpha;
rotate = <90>;// 旋转角度,lcd驱动里会读取并设置对应寄存器
fps = <100>;
width = <240>;
height = <320>;
invert;
// bgr = <0>;
buswidth = <8>;//SPI 8位数据长度
led-gpios = <&gpio3 RK_PC2 GPIO_ACTIVE_LOW>;//BL
dc-gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_LOW>;//DC
reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_HIGH>;//RES
cs-gpios = <&gpio4 RK_PC5 GPIO_ACTIVE_LOW>;//CS
// debug = <0x7>;
};
};
3.1.1 测试刷屏
//搜索内容日志的打印
# dmesg | grep fb_
// 测试花屏
# cat /dev/urandom > /dev/fb0
// 测试清屏
# cat /dev/zero > /dev/fb0
使用屏幕颜色与动画测试工具
https://github.com/ccy-studio/fb-test-app
此工具需要自行编译
3.2 触摸驱动 FT6636U适配
首先要在内核中开启配置 CONFIG_TOUCHSCREEN_FTS
可以内核中在menuconfig搜索然后打上星
Device Drivers -> Input Device Support ->Touchscreens -> Focaltech Touchscreen
在 &pinctrl
节点内添加如下内容设置GPIO的复用关系、
/* 配置触摸 */
touch_int {
gpio3_pa1:gpio3-pa1 {
rockchip,pins = <3 RK_PA1 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
touch_rst {
gpio1_pa4:gpio1-pa4 {
rockchip,pins = <1 RK_PA4 RK_FUNC_GPIO &pcfg_output_high>;
};
};
由于我使用的是I2C2的复用管脚所以使用i2c2
&i2c2{
status = "okay";
focaltech@38{
status = "okay";
compatible = "focaltech,fts";
pinctrl-names = "default";
pinctrl-0 = <&gpio3_pa1 &gpio1_pa4>;
reg = <0x38>;//I2C地址
interrupt-parent = <&gpio3>;
interrupts = <RK_PA1 IRQ_TYPE_LEVEL_LOW>;
focaltech,reset-gpio = <&gpio1 RK_PA4 GPIO_ACTIVE_LOW>;
focaltech,irq-gpio = <&gpio3 RK_PA1 GPIO_ACTIVE_HIGH>;
focaltech,max-touch-number = <1>;//最大触摸数量-多点触摸配置
focaltech,display-coords = <0 0 240 320>;//分辨率
};
};
3.2.1 测试触摸
查看内核打印日志是否有报错
dmesg | grep i2c
如果有错误会打印error信息
查看I2C挂载是否成功
输入命令: ls /sys/bus/i2c/devices
# ls /sys/bus/i2c/devices
0-0038 4-0030 4-0030-1 4-0030-2 i2c-0 i2c-4
可以看出来有x-0038这个地址的节点。 x代表使用到的 I2C 编号
查看是否生成了输入子系统节点
使用命令: cd /dev/input/
# ls
by-path event0 event1
这里我们依次测试event,过滤到那个是属于我们的触摸屏节点
使用命令 hexdump event0
先测试0节点的
# hexdump event0
0000000 04ed 0000 a2d7 000a 0003 0039 0033 0000
0000010 04ed 0000 a2d7 000a 0003 0035 0066 0000
0000020 04ed 0000 a2d7 000a 0003 0036 0082 0000
0000030 04ed 0000 a2d7 000a 0001 014a 0001 0000
0000040 04ed 0000 a2d7 000a 0000 0000 0000 0000
0000050 04ed 0000 cae6 000a 0003 0039 ffff ffff
0000060 04ed 0000 cae6 000a 0001 014a 0000 0000
0000070 04ed 0000 cae6 000a 0000 0000 0000 0000
0000080 04ef 0000 c3d7 0004 0003 0039 0034 0000
0000090 04ef 0000 c3d7 0004 0003 0035 004d 0000
00000a0 04ef 0000 c3d7 0004 0003 0036 006c 0000
00000b0 04ef 0000 c3d7 0004 0001 014a 0001 0000
00000c0 04ef 0000 c3d7 0004 0000 0000 0000 0000
00000d0 04ef 0000 ed72 0005 0003 0039 ffff ffff
00000e0 04ef 0000 ed72 0005 0001 014a 0000 0000
00000f0 04ef 0000 ed72 0005 0000 0000 0000 0000
0000100 04f0 0000 6d20 0008 0003 0039 0035 0000
0000110 04f0 0000 6d20 0008 0003 0035 003f 0000
0000120 04f0 0000 6d20 0008 0003 0036 00a4 0000
0000130 04f0 0000 6d20 0008 0001 014a 0001 0000
0000140 04f0 0000 6d20 0008 0000 0000 0000 0000
0000150 04f0 0000 dad8 0008 0003 0039 ffff ffff
0000160 04f0 0000 dad8 0008 0001 014a 0000 0000
0000170 04f0 0000 dad8 0008 0000 0000 0000 0000
点了一下屏幕发现有日志打印说明event0就是我们的触摸屏节点,后面的就不测了因为我们已经找到了。
评论区