在社区目前还没有找到对 ili9341 的移植文档, 于是我打算研究一下.由于我手中的开发板是 milkv duos sd 版本, 于是以 duos 为例.
ili9341侧移植
准备工作
-
官方sdk: GitHub - milkv-duo/duo-buildroot-sdk: Milk-V Duo Official buildroot SDK
-
编译工具链: GitHub - milkv-duo/host-tools(其实不用下载, 官方sdk中包含)
移植
-
修改设备树
在 build/boards/cv181x/cv1813h_milkv_duos_sd/dts_riscv/cv1813h_milkv_duos_sd.dts 下找到 spi3 的部分(这里引脚可以自定义).
&spi3 { status = "okay"; /delete-node/ spidev@0; ili9341: ili9341@0 { compatible = "ilitek,ili9341"; status = "okay"; reg = <0>; spi-cpol; spi-cpha; rotate = <90>; fps = <60>; rgb; buswidth = <8>; dc = <&porta 18 GPIO_ACTIVE_HIGH>; // DC引脚 reset = <&porta 28 GPIO_ACTIVE_LOW>; // RESET引脚 spi-max-frequency = <40000000>; debug = <0x0>; }; };
-
修改 ili9341 驱动代码
这里注释掉的部分都是源码
-
linux_5.10/drivers/staging/fbtft/fbtft-core.c
引入头文件
#include <linux/gpio.h> #include <linux/of_gpio.h>
fbtft_reset
static void fbtft_reset(struct fbtft_par *par) { /* if (!par->gpio.reset) return; fbtft_par_dbg(DEBUG_RESET, par, "%s()\n", __func__); gpiod_set_value_cansleep(par->gpio.reset, 1); usleep_range(20, 40); gpiod_set_value_cansleep(par->gpio.reset, 0); msleep(120); gpiod_set_value_cansleep(par->gpio.reset, 1); msleep(10); */ if (!par->gpio.reset) return; fbtft_par_dbg(DEBUG_RESET, par, "%s()\n", __func__); gpiod_set_value_cansleep(par->gpio.reset, 1); usleep_range(20, 40); gpiod_set_value_cansleep(par->gpio.reset, 0); msleep(120); gpiod_set_value_cansleep(par->gpio.reset, 1); msleep(10); }
fbtft_request_one_gpio
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 ret = 0; if (of_find_property(node, name, NULL)) { *gpiop = devm_gpiod_get_index(dev, dev->driver->name, index, GPIOD_OUT_HIGH); if (IS_ERR(*gpiop)) { ret = PTR_ERR(*gpiop); dev_err(dev, "Failed to request %s GPIO:%d\n", name, ret); return ret; } fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' GPIO\n", __func__, name); } return ret; */ struct device *dev = par->info->device; struct device_node *node = dev->of_node; int gpio, flags, ret = 0; enum of_gpio_flags of_flags; char gpio_names[32]; //sprintf(gpio_names, "%s-gpios", name); sprintf(gpio_names, "%s", name); printk("@ gpio_names = %s\n", gpio_names); if (of_find_property(node, gpio_names, NULL)) { gpio = of_get_named_gpio_flags(node, gpio_names, index, &of_flags); printk ("@ gpio = %d | ENOENT = %d | EPROBE_DEFER = %d \n", gpio, ENOENT, EPROBE_DEFER); if (gpio == -ENOENT) return 0; if (gpio == -EPROBE_DEFER) return gpio; if (gpio < 0) { dev_err(dev, "failed to get '%s' from DT\n", gpio_names); 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); if (ret) { dev_err(dev, "gpio_request_one('%s'=%d) failed with %d\n", gpio_names, gpio, ret); return ret; } *gpiop = gpio_to_desc(gpio); fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' = GPIO%d\n", __func__, gpio_names, gpio); } return ret; }
-
linux_5.10/drivers/staging/fbtft/fbtft-bus.c 中
/* 16 bit pixel over 8-bit databus */ int fbtft_write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len) { u16 *vmem16; __be16 *txbuf16 = 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); remain = len / 2; vmem16 = (u16 *)(par->info->screen_buffer + offset); if (par->gpio.dc) { //printk("dc拉高!\n"); gpiod_set_value(par->gpio.dc, 1); } /* non buffered write */ if (!par->txbuf.buf) return par->fbtftops.write(par, vmem16, len); /* buffered write */ tx_array_size = par->txbuf.len / 2; if (par->startbyte) { txbuf16 = par->txbuf.buf + 1; tx_array_size -= 2; *(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; i++) txbuf16[i] = cpu_to_be16(vmem16[i]); vmem16 = vmem16 + to_copy; ret = par->fbtftops.write(par, par->txbuf.buf, startbyte_size + to_copy * 2); if (ret < 0) return ret; remain -= to_copy; } return ret; }
-
-
修改 config 文件
在 build/boards/cv181x/cv1813h_milkv_duos_sd/linux/cvitek_cv1813h_milkv_duos_sd_defconfig 增加以下几行
// CONFIG_FB=y 在新版有, 不用加了, 没有可以加一下 CONFIG_FB_TFT=y CONFIG_FB_TFT_ILI9341=y CONFIG_SPI_MASTER=y CONFIG_SPI_DESIGNWARE=y
-
编译
回到 sdk 的根目录, 直接编译就可以了, 具体参考 简介 | Milk-V
-
测试
-
接线
接线直接参考官方, 简介 | Milk-V
进入 milkv duos 中, 你会看到 /dev/fb0, 尝试驱动它
-
花屏
cat /dev/urandom > /dev/fb0
-
清屏
cat /dev/zero > /dev/fb0
-
lvgl侧移植
-
lv_port_linux: https://github.com/lvgl/lv_port_linux:
-
lv_drivers: GitHub - lvgl/lv_drivers: TFT and touch pad drivers for LVGL embedded GUI library
移植
可以尝试自己移植一下, 也可以直接利用官方提供的框架
-
自己动手丰衣足食
这里移植 lvgl 的话可以参考 嵌入式 Linux 下的 LVGL 移植_lvgl 在linux 板上-CSDN博客
-
使用官方移植好的框架
需要注意
-
编译工具链的替换
编译工具链的根目录就在 sdk 的 host-tools 目录下, 在移植好 lvgl 之后, 在Makefile 下记得更换工具链.
CC := 绝对路径/duo-buildroot-sdk-offical/host-tools/gcc/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-gcc
-
在执行 demo 的时候可能会报错
ioctl(FBIOBLANK): Invalid argument
这个时候直接注释掉 lv_drivers/display/fbdev.c 下的代码
// Make sure that the display is on. // if (ioctl(fbfd, FBIOBLANK, FB_BLANK_UNBLANK) != 0) { // perror("ioctl(FBIOBLANK)"); // return; // }
最终效果
PS
我屏幕的颜色貌似不太正确, 偏黄, 想请教一下大佬颜色的问题怎么解决呢?