1 基本配置
书接上回:树莓派3B驱动ST7735(内核)(配置篇)_st7735s驱动固件下载-CSDN博客,这次主要是精读一下树莓派内核中的ST7735驱动源码
今天又看了下树莓派Linux内核,其实这个dts在系统中已经内置了。。。
位置是arch/arm/boot/dts/overlays/adafruit18-overlay.dts
/*
* Device Tree overlay for Adafruit 1.8" TFT LCD with ST7735R chip 160x128
*/
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <&spidev0>;
__overlay__ {
status = "disabled";
};
};
fragment@1 {
target = <&spi0>;
__overlay__ {
/* needed to avoid dtc warning */
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
af18: adafruit18@0 {
compatible = "fbtft,adafruit18";
reg = <0>;
pinctrl-names = "default";
spi-max-frequency = <40000000>;
rotate = <90>;
buswidth = <8>;
fps = <50>;
height = <160>;
width = <128>;
reset-gpios = <&gpio 25 1>;
dc-gpios = <&gpio 24 0>;
led-gpios = <&gpio 18 0>;
debug = <0>;
};
};
};
__overrides__ {
green = <&af18>, "compatible=fbtft,adafruit18_green";
speed = <&af18>,"spi-max-frequency:0";
rotate = <&af18>,"rotate:0";
fps = <&af18>,"fps:0";
bgr = <&af18>,"bgr?";
debug = <&af18>,"debug:0";
dc_pin = <&af18>,"dc-gpios:4";
reset_pin = <&af18>,"reset-gpios:4";
led_pin = <&af18>,"led-gpios:4";
};
};
理论上直接把adafruit18-overlay.dtbo加到config里面就可以跑起来了。
2 ST7735驱动
对应的驱动是在drivers/staging/fbtft/fb_st7735r.c
这个fbtft,貌似有点点小特殊,可以看一下它的readme
FBTFT
=========Linux Framebuffer drivers for small TFT LCD display modules.
The module 'fbtft' makes writing drivers for some of these displays very easy.Development is done on a Raspberry Pi running the Raspbian "wheezy" distribution.
INSTALLATION
Download kernel sourcesFrom Linux 3.15
cd drivers/video/fbdev/fbtft
git clone https://github.com/notro/fbtft.gitAdd to drivers/video/fbdev/Kconfig: source "drivers/video/fbdev/fbtft/Kconfig"
Add to drivers/video/fbdev/Makefile: obj-y += fbtft/Before Linux 3.15
cd drivers/video
git clone https://github.com/notro/fbtft.gitAdd to drivers/video/Kconfig: source "drivers/video/fbtft/Kconfig"
Add to drivers/video/Makefile: obj-y += fbtft/Enable driver(s) in menuconfig and build the kernel
See wiki for more information: https://github.com/notro/fbtft/wiki
Source: https://github.com/notro/fbtft/
是内核中专门搞了一个framebuffer小屏驱动库。。。 不知道是树莓派加的,还是本来内核就有。
这个部分讲解可以看看这个,我觉得写的还挺详细:https://www.shangyexinzhi.com/article/2986320.html
关于驱动代码的原理,我之前也写过,是基于python版本的:显示学习5(基于树莓派Pico) -- 彩色LCD的驱动_pico驱动屏幕-CSDN博客
这里它封装了一个框架
先看看封装的st7735代码吧
// SPDX-License-Identifier: GPL-2.0+
/*
* FB driver for the ST7735R LCD Controller
*
* Copyright (C) 2013 Noralf Tronnes
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <video/mipi_display.h>
#include "fbtft.h"
#define DRVNAME "fb_st7735r"
#define DEFAULT_GAMMA "0F 1A 0F 18 2F 28 20 22 1F 1B 23 37 00 07 02 10\n" \
"0F 1B 0F 17 33 2C 29 2E 30 30 39 3F 00 07 03 10"
#define ADAFRUIT18_GAMMA \
"02 1c 07 12 37 32 29 2d 29 25 2B 39 00 01 03 10\n" \
"03 1d 07 06 2E 2C 29 2D 2E 2E 37 3F 00 00 02 10"
static const s16 default_init_sequence[] = {
-1, MIPI_DCS_SOFT_RESET,
-2, 150, /* delay */
-1, MIPI_DCS_EXIT_SLEEP_MODE,
-2, 500, /* delay */
/* FRMCTR1 - frame rate control: normal mode
* frame rate = fosc / (1 x 2 + 40) * (LINE + 2C + 2D)
*/
-1, 0xB1, 0x01, 0x2C, 0x2D,
/* FRMCTR2 - frame rate control: idle mode
* frame rate = fosc / (1 x 2 + 40) * (LINE + 2C + 2D)
*/
-1, 0xB2, 0x01, 0x2C, 0x2D,
/* FRMCTR3 - frame rate control - partial mode
* dot inversion mode, line inversion mode
*/
-1, 0xB3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D,
/* INVCTR - display inversion control
* no inversion
*/
-1, 0xB4, 0x07,
/* PWCTR1 - Power Control
* -4.6V, AUTO mode
*/
-1, 0xC0, 0xA2, 0x02, 0x84,
/* PWCTR2 - Power Control
* VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
*/
-1, 0xC1, 0xC5,
/* PWCTR3 - Power Control
* Opamp current small, Boost frequency
*/
-1, 0xC2, 0x0A, 0x00,
/* PWCTR4 - Power Control
* BCLK/2, Opamp current small & Medium low
*/
-1, 0xC3, 0x8A, 0x2A,
/* PWCTR5 - Power Control */
-1, 0xC4, 0x8A, 0xEE,
/* VMCTR1 - Power Control */
-1, 0xC5, 0x0E,
-1, MIPI_DCS_EXIT_INVERT_MODE,
-1, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT,
-1, MIPI_DCS_SET_DISPLAY_ON,
-2, 100, /* delay */
-1, MIPI_DCS_ENTER_NORMAL_MODE,
-2, 10, /* delay */
/* end marker */
-3
};
static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
{
write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);
write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF);
write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
}
static void adafruit18_green_tab_set_addr_win(struct fbtft_par *par,
int xs, int ys, int xe, int ye)
{
write_reg(par, 0x2A, 0, xs + 2, 0, xe + 2);
write_reg(par, 0x2B, 0, ys + 1, 0, ye + 1);
write_reg(par, 0x2C);
}
#define MY BIT(7)
#define MX BIT(6)
#define MV BIT(5)
static int set_var(struct fbtft_par *par)
{
/* MADCTL - Memory data access control
* RGB/BGR:
* 1. Mode selection pin SRGB
* RGB H/W pin for color filter setting: 0=RGB, 1=BGR
* 2. MADCTL RGB bit
* RGB-BGR ORDER color filter panel: 0=RGB, 1=BGR
*/
switch (par->info->var.rotate) {
case 0:
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
MX | MY | (par->bgr << 3));
break;
case 270:
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
MY | MV | (par->bgr << 3));
break;
case 180:
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
par->bgr << 3);
break;
case 90:
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
MX | MV | (par->bgr << 3));
break;
}
return 0;
}
/*
* Gamma string format:
* VRF0P VOS0P PK0P PK1P PK2P PK3P PK4P PK5P PK6P PK7P PK8P PK9P SELV0P SELV1P SELV62P SELV63P
* VRF0N VOS0N PK0N PK1N PK2N PK3N PK4N PK5N PK6N PK7N PK8N PK9N SELV0N SELV1N SELV62N SELV63N
*/
#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
static int set_gamma(struct fbtft_par *par, u32 *curves)
{
int i, j;
/* apply mask */
for (i = 0; i < par->gamma.num_curves; i++)
for (j = 0; j < par->gamma.num_values; j++)
CURVE(i, j) &= 0x3f;
for (i = 0; i < par->gamma.num_curves; i++)
write_reg(par, 0xE0 + i,
CURVE(i, 0), CURVE(i, 1),
CURVE(i, 2), CURVE(i, 3),
CURVE(i, 4), CURVE(i, 5),
CURVE(i, 6), CURVE(i, 7),
CURVE(i, 8), CURVE(i, 9),
CURVE(i, 10), CURVE(i, 11),
CURVE(i, 12), CURVE(i, 13),
CURVE(i, 14), CURVE(i, 15));
return 0;
}
#undef CURVE
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 = {
.set_addr_win = set_addr_win,
.set_var = set_var,
.set_gamma = set_gamma,
},
};
int variant_adafruit18(struct fbtft_display *display)
{
display->gamma = ADAFRUIT18_GAMMA;
return 0;
}
int variant_adafruit18_green(struct fbtft_display *display)
{
display->gamma = ADAFRUIT18_GAMMA;
display->fbtftops.set_addr_win = adafruit18_green_tab_set_addr_win;
return 0;
}
FBTFT_REGISTER_DRIVER_START(&display)
FBTFT_COMPATIBLE("sitronix,st7735r")
FBTFT_COMPATIBLE("fbtft,sainsmart18")
FBTFT_VARIANT_COMPATIBLE("fbtft,adafruit18", variant_adafruit18)
FBTFT_VARIANT_COMPATIBLE("fbtft,adafruit18_green", variant_adafruit18_green)
FBTFT_REGISTER_DRIVER_END(DRVNAME, &display);
MODULE_ALIAS("spi:" DRVNAME);
MODULE_ALIAS("platform:" DRVNAME);
MODULE_ALIAS("spi:st7735r");
MODULE_ALIAS("platform:st7735r");
MODULE_ALIAS("spi:sainsmart18");
MODULE_ALIAS("platform:sainsmart");
MODULE_ALIAS("spi:adafruit18");
MODULE_ALIAS("platform:adafruit18");
MODULE_ALIAS("spi:adafruit18_green");
MODULE_ALIAS("platform:adafruit18_green");
MODULE_DESCRIPTION("FB driver for the ST7735R LCD Controller");
MODULE_AUTHOR("Noralf Tronnes");
MODULE_LICENSE("GPL");
这个地方提取了差异化的内容,可以看到,差异化的重点主要是以下几个部分。
default_init_sequence,初始化的序列,就是一系列命令组合。
set_addr_win,TFT 显示屏通常是基于帧缓冲操作的,在显示图像时,需要指定屏幕的某个区域来刷新或更新像素数据。set_addr_win 函数通过设定 TFT 控制器的显示窗口(地址窗口),定义从哪里开始刷新屏幕。这个窗口可以是整个屏幕,也可以是屏幕的一部分,通常用于加快图像渲染过程。
set_var,当帧缓冲设备的显示模式改变时,set_var 函数会被调用来更新显示屏的分辨率、颜色深度或其他参数。该函数通常与 fb_var_screeninfo 结构体关联,里面包含了关于显示模式的详细信息。
set_gamma,伽马曲线用于调整显示屏上颜色的亮度和对比度。在某些显示屏中,通过修改伽马曲线,可以优化显示效果。set_gamma 函数允许你为 TFT 屏幕设置自定义的伽马值,从而影响屏幕的整体亮度和色彩表现。
display这个结构体就是基本的display参数。前阵做的高通平台,这个整个都在设备树里面去了。
这里面初始化序列,设置刷新窗口,set_var。Display结构这几个和python驱动是一致的,set_gamma是内核大佬独门秘籍!这个也是大佬和爱好者的区别吧。。。空了可以多看看大佬的技术。。。
#define CURVE(num, idx) curves[(num) * par->gamma.num_values + (idx)]
static int set_gamma(struct fbtft_par *par, u32 *curves)
{
int i, j;
/* apply mask */
for (i = 0; i < par->gamma.num_curves; i++)
for (j = 0; j < par->gamma.num_values; j++)
CURVE(i, j) &= 0x3f;
for (i = 0; i < par->gamma.num_curves; i++)
write_reg(par, 0xE0 + i,
CURVE(i, 0), CURVE(i, 1),
CURVE(i, 2), CURVE(i, 3),
CURVE(i, 4), CURVE(i, 5),
CURVE(i, 6), CURVE(i, 7),
CURVE(i, 8), CURVE(i, 9),
CURVE(i, 10), CURVE(i, 11),
CURVE(i, 12), CURVE(i, 13),
CURVE(i, 14), CURVE(i, 15));
return 0;
}
#undef CURVE
看了这个gamma的调用位置,是在fbtft_register_framebuffer的时候。嗯,linux下的这个framebuffer,还会再写一篇。。。
看起来Gamma值得就是显示屏的调校,有点类似显示器的tunning。
显示器的gamma设置指的是屏幕亮度与输入信号之间的非线性关系。它影响图像的对比度和色彩深度,调整gamma值可以使图像看起来更亮或更暗,从而改善视觉效果。适当的gamma设置可以帮助确保图像在不同显示设备上的一致性。
这里可以比较一下如果相同,两个实现的差别
Python _setwindowloc
CASET = 0x2A
RASET = 0x2B
RAMWR = 0x2C
def _setwindowloc( self, aPos0, aPos1 ) :
'''Set a rectangular area for drawing a color to.'''
self._writecommand(TFT.CASET) #Column address set.
self.windowLocData[0] = self._offset[0]
self.windowLocData[1] = self._offset[0] + int(aPos0[0])
self.windowLocData[2] = self._offset[0]
self.windowLocData[3] = self._offset[0] + int(aPos1[0])
self._writedata(self.windowLocData)
self._writecommand(TFT.RASET) #Row address set.
self.windowLocData[0] = self._offset[1]
self.windowLocData[1] = self._offset[1] + int(aPos0[1])
self.windowLocData[2] = self._offset[1]
self.windowLocData[3] = self._offset[1] + int(aPos1[1])
self._writedata(self.windowLocData)
self._writecommand(TFT.RAMWR) #Write to RAM.
C的adafruit18_green_tab_set_addr_win
static void adafruit18_green_tab_set_addr_win(struct fbtft_par *par,
int xs, int ys, int xe, int ye)
{
write_reg(par, 0x2A, 0, xs + 2, 0, xe + 2);
write_reg(par, 0x2B, 0, ys + 1, 0, ye + 1);
write_reg(par, 0x2C);
}
两个有区别吗?看起来好像没有区别。。。write_reg是fbtft提供的公共函数,省了一些事。
set_var这个和python里面的rotation那函数差不多。
default_init_sequence是封装的比较多。这里就要看看core的实现了。
3 FBTFT框架
这个是在fbtft_init_display中调用的,就是一个while (i < FBTFT_MAX_INIT_SEQUENCE) {,遍历所有的初始化参数。
这里有一个有趣的。ST7735的分辨率是128*160。这个值是否要传到屏幕去初始化呢?答案是不。。。因为实际上分辨率的管理都是在驱动这边,屏幕那边是不会管的什么分辨率的。屏幕驱动的核心就是点的位置和点的颜色。位置超了这些事,屏幕根本不会去管。。。
这些核心参数,都是从之前配置的设备树读来的:
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);
整体看看吧。模块函数都是在drivers\staging\fbtft\fbtft.h文件里面(这样搞也是少见。。。),入口和出口是:
module_init(fbtft_driver_module_init);
module_exit(fbtft_driver_module_exit);
这里的init和exit主要就是处理的SPI。
static struct spi_driver fbtft_driver_spi_driver = { \
.driver = { \
.name = _name, \
.of_match_table = dt_ids, \
}, \
.id_table = spi_ids, \
.probe = fbtft_driver_probe_spi, \
.remove = fbtft_driver_remove_spi, \
};
对了,这个驱动里面包含了很多的硬件,用都是上面同一套框架。也就是一个module,内核里面这种搞法还很普遍。。。
fbtft_probe_common就是最重要初始化函数了。
首先还是配置树参数,然后就是fbtft_framebuffer_alloc,之后是写寄存器的函数,fbtft框架集成了8位,16位等多个函数。然后fbtft_merge_fbtftops,将要用的函数设置成具体的设备的函数。最后是fbtft_register_framebuffer,里面调用的register_framebuffer,这个的定义是在\include\linux\fb.h。看了一下很多地方都在用,感觉很重要的样子。。。
IO处理的函数,也是做了封装,单独放在drivers\staging\fbtft\fbtft-io.c。
fbtft-bus.c这里有上面写寄存器的函数,还有就是写显存的函数。
至于fbtft_init_display这些,好像在驱动里面都没有调用,看起来做为回调函数传到framebuffer用的。
最后里面还有一个sysfs接口,看起来像是sys下面用户空间接口,貌似可以处理gamma配置这些。后面有时间再看看吧。
4 参考
Linux驱动开发 / fbtft源码速读Linux驱动开发 / fbtft源码速读Linux驱动开发 / fbtft源码速读