上节使用fbtft来驱动SPI接口TFT屏幕,但是是编译进内核的方式。 本节再来测试下编译成模块的用法。
引脚
240x240分辨率,1.3寸,主控为ST7789VW。 与开发板的引脚连接确定如下:
功能 | IO |
---|
GND | Pin6 | 5V | Pin2 | LCD_RESET | Pin7-PG11 | LCD_DC | Pin22-PA1 | SPICLK | Pin23-PC2 | SPIMOSI | Pin19-PC0 |
从开发板的引脚图发现,开发板使用的SPI0。
修改设备树
打开/home/ql/linux/H3/linux/arch/arm/boot/dts/sun8i-h3-nanopi.dtsi,找到spi0节点,把上节添加的节点注释掉,避免干扰本节的测试。修改为如下:
&spi0 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
cs-gpios = <&pio 2 3 GPIO_ACTIVE_HIGH>, <&pio 0 6 GPIO_ACTIVE_HIGH>;
pitft: pitft@0{
compatible = "sitronix,st7789vw";
reg = <0>;
status = "okay";
spi-max-frequency = <96000000>;
rotate = <0>;
fps = <33>;
buswidth = <8>;
dc-gpios = <&pio 0 1 GPIO_ACTIVE_HIGH>;
reset-gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>;
led-gpios = <&pio 0 0 GPIO_ACTIVE_LOW>;
debug = <0x0>;
};
};
&pio {
leds_npi: led_pins {
pins = "PA10";
function = "gpio_out";
};
pinctrl_testTFTRes: testTFTRes_pins {
pins = "PG11";
function = "gpio_out";
};
pinctrl_testTFTDc: testTFTDc_pins {
pins = "PA1";
function = "gpio_out";
};
pinctrl_testTFTBk: testTFTBk_pins {
pins = "PA0";
function = "gpio_out";
};
spi0_cs_pins: spi0_cs_pins {
pins = "PC3", "PA6";
function = "gpio_out";
};
};
关闭HDMI,否则设备默认HDMI输出,tft黑屏
&hdmi {
status = "disable";
};
&hdmi_out {
hdmi_out_con: endpoint {
remote-endpoint = <&hdmi_con_in>;
};
};
&sound_hdmi {
status = "disable";
};
编辑Device
根据Linux统一设备模型思想:Device提供硬件信息描述,Driver提供使用硬件信息的方法。 我们打开 /kernel/driver/staging/fbtft/fbtft_device.c文件,看到
static struct fbtft_device_display displays[] = {
{
.name = "adafruit18",
.spi = &(struct spi_board_info) {
.modalias = "fb_st7735r",
.max_speed_hz = 32000000,
.mode = SPI_MODE_0,
.platform_data = &(struct fbtft_platform_data) {
.display = {
.buswidth = 8,
.backlight = 1,
},
.gamma = ADAFRUIT18_GAMMA,
}
}
}, {
.name = "adafruit18_green",
.spi = &(struct spi_board_info) {
.modalias = "fb_st7735r",
.max_speed_hz = 4000000,
.mode = SPI_MODE_0,
.platform_data = &(struct fbtft_platform_data) {
.display = {
.buswidth = 8,
.backlight = 1,
.fbtftops.set_addr_win =
adafruit18_green_tab_set_addr_win,
},
.bgr = true,
.gamma = ADAFRUIT18_GAMMA,
}
}
}, {
......................省略
上面的结构体描述了大量的显示屏信息,我们在最后面仿造前面的,加上ST7789VW屏幕的描述
}, {
.name = "qlexceltft",
.spi = &(struct spi_board_info) {
.modalias = "fb_st7789vW",
.max_speed_hz = 96000000,
.mode = SPI_MODE_0,
.bus_num=0,
.chip_select = 0,
.platform_data = &(struct fbtft_platform_data) {
.display = {
.buswidth = 8,
.backlight = 1,
},
.gpios = (const struct fbtft_gpio []) {
{ "reset", 203 },
{ "dc", 1 },
{ "led", 0 },
},
}
}
}
添加Driver
接着我们修改位于统一设备模型中的driver 打开/home/ql/linux/H3/linux/drivers/staging/fbtft/路径,看下有没有fb_st7789vw.c文件,如果有下面的步骤就可以忽略了。 如果没有,那么新建一个fb_st7789vw.c文件,把下面内容复制进去,保存。
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <video/mipi_display.h>
#include <linux/gpio.h>
#include "fbtft.h"
#define DRVNAME "fb_st7789vw"
#define DEFAULT_GAMMA \
"D0 04 0D 11 13 2B 3F 54 4C 18 0D 0B 1F 23\n" \
"D0 04 0C 11 13 2C 3F 44 51 2F 1F 1F 20 23"
enum st7789v_command {
PORCTRL = 0xB2,
GCTRL = 0xB7,
VCOMS = 0xBB,
LCMCTRL = 0xC0,
VDVVRHEN = 0xC2,
VRHS = 0xC3,
VDVS = 0xC4,
VCMOFSET = 0xC5,
FRCTRL2 = 0xC6,
PWCTRL1 = 0xD0,
PVGAMCTRL = 0xE0,
NVGAMCTRL = 0xE1,
};
#define MADCTL_BGR BIT(3)
#define MADCTL_MV BIT(5)
#define MADCTL_MX BIT(6)
#define MADCTL_MY BIT(7)
static int init_display(struct fbtft_par *par)
{
write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
mdelay(120);
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, 0x0);
write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT);
write_reg(par, PORCTRL, 0x0C, 0x0C, 0x00, 0x33, 0x33);
write_reg(par, GCTRL, 0x35);
write_reg(par, VCOMS, 0x19);
write_reg(par, LCMCTRL, 0x2C);
write_reg(par, VDVVRHEN, 0x01);
write_reg(par, VRHS, 0x12);
write_reg(par, VDVS, 0x20);
write_reg(par, FRCTRL2, 0x0F);
write_reg(par, PWCTRL1, 0xA4, 0xA1);
write_reg(par, MIPI_DCS_ENTER_INVERT_MODE);
write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
return 0;
}
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 int set_var(struct fbtft_par *par)
{
u8 madctl_par = 0;
if (par->bgr)
madctl_par |= MADCTL_BGR;
switch (par->info->var.rotate) {
case 0:
break;
case 90:
madctl_par |= (MADCTL_MV | MADCTL_MY);
break;
case 180:
madctl_par |= (MADCTL_MX | MADCTL_MY);
break;
case 270:
madctl_par |= (MADCTL_MV | MADCTL_MX);
break;
default:
return -EINVAL;
}
write_reg(par, MIPI_DCS_SET_ADDRESS_MODE, madctl_par);
return 0;
}
static int set_gamma(struct fbtft_par *par, u32 *curves)
{
int i;
int j;
int c;
static const u8 gamma_par_mask[] = {
0xFF,
0x3F,
0x3F,
0x1F,
0x1F,
0x3F,
0x7F,
0x77,
0x7F,
0x3F,
0x1F,
0x1F,
0x3F,
0x3F,
};
for (i = 0; i < par->gamma.num_curves; i++) {
c = i * par->gamma.num_values;
for (j = 0; j < par->gamma.num_values; j++)
curves[c + j] &= gamma_par_mask[j];
write_reg(
par, PVGAMCTRL + i,
curves[c + 0], curves[c + 1], curves[c + 2],
curves[c + 3], curves[c + 4], curves[c + 5],
curves[c + 6], curves[c + 7], curves[c + 8],
curves[c + 9], curves[c + 10], curves[c + 11],
curves[c + 12], curves[c + 13]);
}
return 0;
}
static int blank(struct fbtft_par *par, bool on)
{
if (on)
write_reg(par, MIPI_DCS_SET_DISPLAY_OFF);
else
write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
return 0;
}
static struct fbtft_display display = {
.regwidth = 8,
.width = 240,
.height = 240,
.gamma_num = 2,
.gamma_len = 14,
.gamma = DEFAULT_GAMMA,
.fbtftops = {
.init_display = init_display,
.set_addr_win = set_addr_win,
.set_var = set_var,
.set_gamma = set_gamma,
.blank = blank,
},
};
FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7789vw", &display);
MODULE_ALIAS("spi:" DRVNAME);
MODULE_ALIAS("platform:" DRVNAME);
MODULE_ALIAS("spi:st7789vw");
MODULE_ALIAS("platform:st7789vw");
MODULE_DESCRIPTION("FB driver for the ST7789VW LCD Controller");
MODULE_AUTHOR("FriendlyElec");
MODULE_LICENSE("GPL");
在Makefile中添加obj-$(CONFIG_FB_TFT_ST7789VW) += fb_st7789vw.o 在Kconfig中添加
config FB_TFT_ST7789VW
tristate "FB driver for the ST7789VW LCD Controller"
depends on FB_TFT
help
This enables generic framebuffer support for the Sitronix ST7789VW
display controller. The controller is intended for small color
displays with a resolution of up to 240x240 pixels.
Say Y if you have such a display that utilizes this controller.
编译
到menuconfig中使能此驱动 make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-
Device Drivers --->
[*] Staging drivers --->
<*> Support for small TFT LCD display modules --->
将FB driver for the ST7789VW LCD Controller 和Module to for adding FBTFT devices 选择成模块
# 编译内核、设备树、模块
make zImage dtbs modules ARCH=arm CROSS_COMPILE=arm-linux-
# 网络更新内核
scp arch/arm/boot/zImage root@192.168.0.101:/boot
# 网络更新设备树
scp arch/arm/boot/dts/sun8i-h3-nanopi-neo-air.dtb root@192.168.0.101:/boot
# 网络上传模块
scp drivers/staging/fbtft/fbtft_device.ko root@192.168.0.101:/lib/modules/4.14.111/
scp drivers/staging/fbtft/fb_st7789vw.ko root@192.168.0.101:/lib/modules/4.14.111/
上电测试
上电后,切换root,加载模块
insmod fbtft_device.ko name=qlexceltft busnum=0 cs=0 rotate=0 debug=7
insmod fb_st7789vw.ko
insmod fbtft_device.ko后面的参数解释如下:
- name:fbtft_device里你自己设置的
- busnum:用于指定使用第几个spi控制器
- CS:SPI的第几个CS片选线
- rotate:屏幕翻转角度
- debug:数字越大,调试的时候打印的信息越多,debug的时候可以设置为7.
执行成功的话屏幕应该点亮了,通过lsmod 命令就能看到模块已被加载,在/dev目录下,会产生fb0设备,如果执行失败,可以通过dmesg | grep "fb" 来查看内核日志,分析具体原因,一般都是由于参数错了导致;
设置开机自动加载模块
1、在st7735s.ko fbtft_device.ko 驱动模块目录下(/lib/modules/4.x.x/kernel/driver/staging/fbtft)执行sudo depmod -A 生成map文件。 2、编辑文件 /etc/modules,添加如下:
fb_st7735s
fbtft_device
3、新建配置文件 /etc/modprobe.d/fbtft.conf,内容如下:
options fbtft_device name=qlexceltft busnum=0 gpios=reset:3,dc:17 rotate=90 custom=1 height=128 width=128
4、上面 3 步完成后重启,应该就可以看到屏幕被点亮并显示了 console,说明驱动被自动加载了
参考: 嵌入式Linux fbtft(SPI TFT屏)驱动移植及调试
|