树莓派3B驱动ST7735(内核)(代码篇)

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 sources

  From Linux 3.15
    cd drivers/video/fbdev/fbtft
    git clone https://github.com/notro/fbtft.git

    Add 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.git

    Add 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源码速读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值