一.前言
在上次与国产系统HarmonyOS互动搭建产品级蓝牙BLE的wifi配网之后,做了一块转接板,板子上将塞昉开发板自带的40Pin引脚做了扩展,同时做了几个按键和led灯,测试了一下,板子是好的:
做板子其实并不难,掌握一点点硬件知识就行了,今次给大家带来LCD屏幕的驱动效果,把屏幕接上之后,在系统启动之后,可以在屏幕上显示日志,效果如如下:
接下来将步骤记录下来
二.打开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先改个名字复制到同一级别目录下:
,在Kconfig里面加如ili9488的描述:
然后在Makefile添加ili9488.o文件:
然后就可以在Kconfig图形界面找到ili9488了:
打上星号就OK了,接下来编写设备树:
三.编写设备树
由于我们是外接屏,使用的是spi接口,所以在spi0下挂上ili9488设备,设备树如下:
本来想把SPI的速率调大些,结果系统提示不能超过8M:
注意这款屏的色彩是24bit的,所以bpp是24,然后设置reset,dc,led背光的引脚,找到转接板上实际的引脚:
设备树编写完毕之后就可以保存了.
四,编写设备驱动
在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;
}
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;
}
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
函数:
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);
if (!par->txbuf.buf)
return par->fbtftops.write(par, vmem8, len);
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++)
{
*(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里代码如下:
static const s16 default_init_sequence[] = {
-1, 0xb0, 0x0,
-1, MIPI_DCS_EXIT_SLEEP_MODE,
-2, 250,
-1, MIPI_DCS_SET_PIXEL_FORMAT, 0x66,
-1, 0xC2, 0x44,
-1, 0xC5, 0x00, 0x00, 0x00, 0x00,
-1, 0xE0, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98,
0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00,
-1, 0xE1, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75,
0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00,
-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,
-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;
}
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
:
做完这些之后驱动就做好了.
五.添加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显示:
这是打开logo的,还有屏幕Console,位置:
同时,把系统的hdmi和mipi屏幕注销掉,因为步骤忘了截图,等想起来再补上:
到这里就完成了屏幕驱动点亮和显示终端日志了.
底部放上视频效果.
六.后续
既然有了屏幕,那触摸必须也是要支持的,所以下一篇就是驱动触摸屏的驱动了,一起期待吧.