RISC-V技术论坛
直播中

cszzlsw

9年用户 202经验值
擅长:嵌入式技术
私信 关注
[经验]

【昉·星光 2 高性能RISC-V单板计算机体验】用自制的转接板学习lcd驱动点亮屏幕

一.前言

在上次与国产系统HarmonyOS互动搭建产品级蓝牙BLE的wifi配网之后,做了一块转接板,板子上将塞昉开发板自带的40Pin引脚做了扩展,同时做了几个按键和led灯,测试了一下,板子是好的:
c2b6c18c67b470ecc9b35e806685e4a.jpg

做板子其实并不难,掌握一点点硬件知识就行了,今次给大家带来LCD屏幕的驱动效果,把屏幕接上之后,在系统启动之后,可以在屏幕上显示日志,效果如如下:
cd3eb3937e357115613300ed7324f23.jpg
接下来将步骤记录下来

二.打开tftlcd驱动使用

使用make CROSS_COMPILE=riscv64-linux-gnu- ARCH=riscv INSTALL_PATH=./repos/compiled menuconfig指令打开Kconfig窗口界面,在下面找到TFT LCD的驱动开关,并把ILI9488驱动添加进来,
-> Device Drivers │
│ -> Staging drivers (STAGING [=y]) │
│ -> Support for small TFT LCD display modules (FB_TFT [=y])

结果没有找到ILI9488,却有ILI9486,把ILI9486先改个名字复制到同一级别目录下:
image.png
,在Kconfig里面加如ili9488的描述:

image.png
然后在Makefile添加ili9488.o文件:
image.png

然后就可以在Kconfig图形界面找到ili9488了:
image.png

打上星号就OK了,接下来编写设备树:

三.编写设备树

由于我们是外接屏,使用的是spi接口,所以在spi0下挂上ili9488设备,设备树如下:
image.png

本来想把SPI的速率调大些,结果系统提示不能超过8M:
image.png

注意这款屏的色彩是24bit的,所以bpp是24,然后设置reset,dc,led背光的引脚,找到转接板上实际的引脚:
image.png
设备树编写完毕之后就可以保存了.

四,编写设备驱动

在tft-core.c中修改reset,dc.lcd等引脚的request方式以适应新版linux内核:

#include "linux/gpio.h"
#include "linux/of_gpio.h"

static int fbtft_request_one_gpio(struct fbtft_par *par,
                  const char *name, int index,
                  struct gpio_desc **gpiop)
{
    struct device *dev = par->info->device;
    struct device_node *node = dev->of_node;
    int gpio, flags, ret = 0;
    enum of_gpio_flags of_flags;
    if (of_find_property(node, name, NULL)) {
		
        gpio = of_get_named_gpio_flags(node, name, index, &of_flags);
		
        if (gpio == -ENOENT)
            return 0;
        if (gpio == -EPROBE_DEFER)
            return gpio;
        if (gpio < 0) {
            dev_err(dev,
                "failed to get '%s' from DTn", name);
            return gpio;
        }
         //active low translates to initially low
        flags = (of_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_LOW :
                            GPIOF_OUT_INIT_HIGH;
        ret = devm_gpio_request_one(dev, gpio, flags,
                        dev->driver->name);
		printk(">>%s:%d _ %s() ret=%d\n",__FILE__,__LINE__, __func__,ret);
        if (ret) {
            dev_err(dev,
                "gpio_request_one('%s'=%d) failed with %dn",
                name, gpio, ret);
            return ret;
        }

        *gpiop = gpio_to_desc(gpio);
        fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' = GPIO%d %p",
                            __func__, name, gpio,*gpiop);

    }

    return ret;
}

static int fbtft_request_gpios(struct fbtft_par *par)
{
    int i;
    int ret;

    ret = fbtft_request_one_gpio(par, "reset-gpios", 0, &par->gpio.reset);
	printk(">>%s:%d _ %s() reset-gpios %d\n",__FILE__,__LINE__, __func__,par->gpio.reset);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "dc-gpios", 0, &par->gpio.dc);
	printk(">>%s:%d _ %s() dc-gpios %d\n",__FILE__,__LINE__, __func__,par->gpio.dc);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "rd-gpios", 0, &par->gpio.rd);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "wr-gpios", 0, &par->gpio.wr);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "cs-gpios", 0, &par->gpio.cs);
    if (ret)
        return ret;
    ret = fbtft_request_one_gpio(par, "latch-gpios", 0, &par->gpio.latch);
	ret = fbtft_request_one_gpio(par, "led-gpios", 0,
                         &par->gpio.led[0]);
	gpiod_set_value(par->gpio.led[0],1);					 
	printk(">>%s:%d _ %s() gpio-gpios %d\n",__FILE__,__LINE__, __func__,par->gpio.led[0]);
	fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, ": '%s' >>> ret =%d",
                            __func__,  ret);					 
    if (ret)
        return ret;

	
    return 0;
}

然后添加fbtft_write_vmem24_bus8函数:

/* 18/24 bit pixel over 8-bit databus */
int fbtft_write_vmem24_bus8(struct fbtft_par *par, size_t offset, size_t len)
{
        u8 *vmem8;
        u8 *txbuf8 = par->txbuf.buf;
        size_t remain;
        size_t to_copy;
        size_t tx_array_size;
        int i;
        int ret = 0;
        size_t startbyte_size = 0;
        fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s(offset=%zu, len=%zu)\n",
                __func__, offset, len);
		gpiod_set_value(par->gpio.led[0], 1);
        remain = len / 3;
        vmem8 = (u8 *)(par->info->screen_buffer + offset);

        if (par->gpio.dc != -1)
                gpiod_set_value(par->gpio.dc, 1);

        /* non buffered write */
        if (!par->txbuf.buf)
                return par->fbtftops.write(par, vmem8, len);

        /* buffered write, /4*4 to faster */
        tx_array_size = par->txbuf.len / 3 / 4 *4;

        if (par->startbyte) {
                txbuf8 = par->txbuf.buf + 1;
                tx_array_size -= 1;
                *(u8 *)(par->txbuf.buf) = par->startbyte | 0x2;
                startbyte_size = 1;
        }

        while (remain) {
                to_copy = min(tx_array_size, remain);
                dev_dbg(par->info->device, "    to_copy=%zu, remain=%zu\n",
                                                to_copy, remain - to_copy);

                for (i = 0; i < to_copy/4; i++)
                {       //faster copy
                        *(u32*)(txbuf8+i*12) = *(u32*)(vmem8+i*12);
                        *(u32*)(txbuf8+4+i*12) = *(u32*)(vmem8+4+i*12);
                        *(u32*)(txbuf8+8+i*12) = *(u32*)(vmem8+8+i*12);
                }
                for(i = to_copy/4*4; i < to_copy; i++)
                {
                        txbuf8[i*3] = vmem8[i*3];
                        txbuf8[i*3+1] = vmem8[i*3+1];
                        txbuf8[i*3+2] = vmem8[i*3+2];
                }
                vmem8 = vmem8 + to_copy*3;
                ret = par->fbtftops.write(par, par->txbuf.buf,
                                                startbyte_size + to_copy * 3);
                if (ret < 0)
                        return ret;
                remain -= to_copy;
        }

        return ret;
}
EXPORT_SYMBOL(fbtft_write_vmem24_bus8);

注意,在
fbtft_write_vmem24_bus8中,打开led背光,同时dc的控制方式需要用gpiod的方式:
然后在ili9488里代码如下:

// SPDX-License-Identifier: GPL-2.0+
/*
 * FB driver for the ILI9486 LCD Controller
 *
 * Copyright (C) 2014 Noralf Tronnes
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <video/mipi_display.h>

#include "fbtft.h"

#define DRVNAME		"ili9488"
#define WIDTH		320
#define HEIGHT		480
#define BPP  		24

/* this init sequence matches PiScreen */
static const s16 default_init_sequence[] = {
	/* Interface Mode Control */
	-1, 0xb0, 0x0,
	-1, MIPI_DCS_EXIT_SLEEP_MODE,
	-2, 250,
	/* Interface Pixel Format */
	-1, MIPI_DCS_SET_PIXEL_FORMAT, 0x66,
	// -1, 0x36 ,0x08,
	/* Power Control 3 */
	-1, 0xC2, 0x44,
	/* VCOM Control 1 */
	-1, 0xC5, 0x00, 0x00, 0x00, 0x00,
	/* PGAMCTRL(Positive Gamma Control) */
	-1, 0xE0, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98,
		  0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00,
	/* NGAMCTRL(Negative Gamma Control) */
	-1, 0xE1, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75,
		  0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00,
	/* Digital Gamma Control 1 */
	-1, 0xE2, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75,
		  0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00,
	-1, MIPI_DCS_EXIT_SLEEP_MODE,
	-1, MIPI_DCS_SET_DISPLAY_ON,
	/* 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 int set_var(struct fbtft_par *par)
{
	printk(">>%s:%d _ %s() %d %d\n",__FILE__,__LINE__, __func__,
		par->info->var.rotate ,par->bgr );
	switch (par->info->var.rotate) {
	case 0:
		write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
			  0x80 | (par->bgr << 3));
		break;
	case 90:
		write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
			  0x20 | (par->bgr << 3));
		break;
	case 180:
		write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
			  0x40 | (par->bgr << 3));
		break;
	case 270:
		write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
			  0xE0 | (par->bgr << 3));
		break;
	default:
		break;
	}

	//sw add
	par->info->var.red.offset = 16;
	par->info->var.red.length = 8;
	par->info->var.green.offset = 8;
	par->info->var.green.length = 8;
	par->info->var.blue.offset = 8;
	par->info->var.blue.length = 8;


	return 0;
}

static struct fbtft_display display = {
	.regwidth = 8,
	.width = WIDTH,
	.height = HEIGHT,
	.bpp = BPP,
	.init_sequence = default_init_sequence,
	.fbtftops = {
		.set_addr_win = set_addr_win,
		.set_var = set_var,
		.write_vmem = fbtft_write_vmem24_bus8,
	},
};

FBTFT_REGISTER_DRIVER(DRVNAME, "ilitek,ili9488", &display);

MODULE_ALIAS("spi:" DRVNAME);
MODULE_ALIAS("platform:" DRVNAME);
MODULE_ALIAS("spi:ili9488");
MODULE_ALIAS("platform:ili9488");

MODULE_DESCRIPTION("FB driver for the ILI9488 LCD Controller");
MODULE_AUTHOR("Noralf Tronnes");
MODULE_LICENSE("GPL");

然后还要注意因为我们使用的是24bit色彩模式,需要修改fbtft_framebuffer_alloc:
image.png

image.png

做完这些之后驱动就做好了.

五.添加linux的logo和虚拟串口显示在屏幕上

仍然执行
make CROSS_COMPILE=riscv64-linux-gnu- ARCH=riscv INSTALL_PATH=./repos/compiled menuconfig打开Kconfig窗口,输入/搜索logo:
Location: │
│ -> Device Drivers │
│ -> Graphics support │
│ -> Bootup logo (LOGO [=y])

然后就可以打开LOGO显示:
image.png

这是打开logo的,还有屏幕Console,位置:
image.png

同时,把系统的hdmi和mipi屏幕注销掉,因为步骤忘了截图,等想起来再补上:
到这里就完成了屏幕驱动点亮和显示终端日志了.
底部放上视频效果.

六.后续

既然有了屏幕,那触摸必须也是要支持的,所以下一篇就是驱动触摸屏的驱动了,一起期待吧.

3b0437d5ac52c4003a66d870a888d9f8

回帖(1)

jf_44860206

2023-10-11 10:02:06
感谢大佬分享,厉害,期待下一篇!
举报

更多回帖

发帖
×
20
完善资料,
赚取积分