接線#
在開始之前,首先確定你的螢幕的引腳定義,下面是我買到的螢幕:
透過查閱店家給的螢幕資料,得出引腳定義如下:
引腳 | 定義 |
---|---|
BLK | 背光 |
CS | SPI 片選 |
DC | 螢幕數據 |
RES | 螢幕重置 |
SDA | SPI 數據 |
SCL | SPI 時鐘 |
VCC | 電源 |
GND | GND |
透過 Milk-V Duo 的文檔可以看出,Milk-V Duo 有一個硬體 SPI,除此之外需要兩個引腳來接 DC
跟 RES
,這裡我選擇 GPIOA24 跟 GPIOA23,由於螢幕不需要輸出數據,所以只需的數據引腳接到開發板的 SPI2_TX
即可。
這裡我直接將 3V3 接到 BLK 引腳,無需 PWM 調光。
於是我的接線如下:
螢幕 開發板
BLK --- 3V3
CS --- SPI2_CSn
SDA --- SPI2_TX
SCL --- SPI2_CLK
DC --- GPIOA24
RES --- GPIOA23
修改引腳定義#
由於 Milk-V Duo 開發板的 SPI2 是複用引腳且預設功能不是 SPI2,所以需要修改引腳定義。
Milk-V Duo 的引腳定義在 build/boards/cv180x/cv1800b_milkv_duo_sd/u-boot/cvi_board_init.c
文件中的 cvi_board_init
內聲明,這裡我們直接把 SD1_CLK
, SD1_CMD
, SD1_D0
, SD1_D3
直接修改為 SPI2 的對應引腳(參考上面 Milk-V Duo 引腳定義圖)。
PINMUX_CONFIG(SD1_CLK, SPI2_SCK); // Pin 9
PINMUX_CONFIG(SD1_CMD, SPI2_SDO); // Pin 10
PINMUX_CONFIG(SD1_D0, SPI2_SDI); // Pin 11
PINMUX_CONFIG(SD1_D3, SPI2_CS_X); // Pin 12
內核配置#
首先配置 device tree
:
&spi2 {
status = "okay";
/delete-node/ spidev@0;
st7789v: st7789v@0{
compatible = "sitronix,st7789v";
reg = <0>;
status = "okay";
spi-max-frequency = <48000000>;
spi-cpol;
spi-cpha;
rotate = <0>;
fps = <60>;
rgb;
buswidth = <8>;
dc = <&porta 24 GPIO_ACTIVE_HIGH>;
reset = <&porta 23 GPIO_ACTIVE_HIGH>;
debug = <0x0>;
};
};
這裡把 dc
reset
設置成你自己接的 GPIO 端口號。
然後啟用內核中的 ST7789V 支持跟 FB 支持,如果你跟我一樣使用官方的 buildroot 的話可以直接修改 build/boards/cv180x/cv1800b_milkv_duo_sd/linux/cvitek_cv1800b_milkv_duo_sd_defconfig
這個文件。
這裡我們不動別的配置,只增加 FB_TFT 的支持跟 ST7789V 的驅動:
CONFIG_FB_TFT=y
CONFIG_FB_TFT_ST7789V=y
接下來修改 cv180x 的 dtsi,文件預設在 build/boards/default/dts/cv180x/cv180x_base.dtsi
。
將 SPI 引腳預設上拉:
spi2:spi2@041A0000 {
compatible = "snps,dw-apb-ssi";
reg = <0x0 0x041A0000 0x0 0x10000>;
clocks = <&clk CV180X_CLK_SPI>;
#address-cells = <1>;
#size-cells = <0>;
bias-pull-up; // 上拉
};
修改驅動#
首先修改 linux_5.10/drivers/staging/fbtft/fbtft-core.c
中的 fbtft_request_one_gpio
函數,這裡的修改依據 https://github.com/notro/fbtft/blob/e9fc10a080a6c52e46e004c4c2bc9fd3cf3d7445/fbtft-core.c#L166-L204
來,如果不修改在初始化螢幕時候會報錯:
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 DT\n", 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);
if (ret)
{
dev_err(dev,
"gpio_request_one('%s'=%d) failed with %d\n",
name, gpio, ret);
return ret;
}
*gpiop = gpio_to_desc(gpio);
fbtft_par_dbg(DEBUG_REQUEST_GPIOS, par, "%s: '%s' = GPIO%d\n",
__func__, name, gpio);
}
return ret;
}
同時在文件最上面引入頭文件:
#include "fbtft.h"
#include "internal.h"
接下來修改 st7789v
的驅動,也就是 linux_5.10/drivers/staging/fbtft/fb_st7789v.c
,這裡先修改 fbtft_display
中的螢幕配置,將 width 跟 height 改為你螢幕的大小:
static struct fbtft_display display = {
.regwidth = 8,
.width = 240,
.height = 280,
.gamma_num = 2,
.gamma_len = 14,
.gamma = HSD20_IPS_GAMMA,
.fbtftops = {
.init_display = init_display,
.set_var = set_var,
.set_gamma = set_gamma,
.blank = blank,
.set_addr_win = set_addr_win
},
};
接著按照屏廠給的資料,修改初始化函數:
static int init_display(struct fbtft_par *par)
{
par->fbtftops.reset(par);
mdelay(100);
/* turn off sleep mode */
write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
mdelay(120);
/* set pixel format to RGB-565 */
write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT);
write_reg(par, VCMOFSET, 0x1A);
if (HSD20_IPS)
write_reg(par, PORCTRL, 0x05, 0x05, 0x00, 0x33, 0x33);
else
write_reg(par, PORCTRL, 0x08, 0x08, 0x00, 0x22, 0x22);
/*
* VGH = 12.2V
* VGL = -10.43V
*/
if (HSD20_IPS)
write_reg(par, GCTRL, 0x05);
else
write_reg(par, GCTRL, 0x35);
/* VCOM */
write_reg(par, VCOMS, 0x3F);
/*
* VDV and VRH register values come from command write
* (instead of NVM)
*/
write_reg(par, VDVVRHEN, 0x01);
/*
* VRH = 4.3V + (VCOM + VCOM offset + VDV)
*/
if (HSD20_IPS)
write_reg(par, VRHS, 0x0F);
else
write_reg(par, VRHS, 0x0B);
/* VDV = 0V */
write_reg(par, VDVS, 0x20);
/* 111 Hz */
write_reg(par, FRC, 0x01);
/*
* AVDD = 6.8V
* AVCL = -4.8V
* VDS = 2.3V
*/
write_reg(par, PWCTRL1, 0xA4, 0xA1);
/* 賣螢幕的人給你參考代碼裡的 我也不知道哪裡來的 */
write_reg(par, 0xE8, 0x03);
write_reg(par, ETC, 0x09, 0x09, 0x08);
write_reg(par, 0xE0, 0xD0, 0x05, 0x09, 0x09, 0x08, 0x14, 0x28, 0x33, 0x3F, 0x07, 0x13, 0x14, 0x28, 0x30);
write_reg(par, 0xE1, 0xD0, 0x05, 0x09, 0x09, 0x08, 0x03, 0x24, 0x32, 0x32, 0x3B, 0x14, 0x13, 0x28, 0x2F);
if (HSD20_IPS)
write_reg(par, MIPI_DCS_ENTER_INVERT_MODE);
write_reg(par, MIPI_DCS_SET_DISPLAY_ON);
return 0;
}
如果你的螢幕跟我一樣大小與 linux kernel 預設不一樣,那麼還需要參考屏廠的資料設置螢幕 offset:
static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe,
int ye)
{
unsigned int x = xs, y = xe;
write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
(x >> 8), x, (y >> 8), y);
x = ys + 20;
y = ye + 20;
write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
(x >> 8), x, (y >> 8), y);
write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
}
檢測螢幕是否工作#
如果你的操作沒錯的話,在上電 ssh 上去之後執行 dmesg | grep st7789v
應該可以看出 fb0 被正確的初始化了:
[root@milkv-duo]~# dmesg | grep st7789v
[ 0.813120] fb_st7789v spi0.0: fbtft_property_value: buswidth = 8
[ 0.819555] fb_st7789v spi0.0: fbtft_property_value: debug = 0
[ 0.825703] fb_st7789v spi0.0: fbtft_property_value: rotate = 0
[ 0.831919] fb_st7789v spi0.0: fbtft_property_value: fps = 60
[ 1.357745] graphics fb0: fb_st7789v frame buffer, 240x280, 131 KiB video memory, 4 KiB buffer memory, fps=62, spi0.0 at 48 MHz
執行 cat /dev/random > /dev/fb0
給 fb0 內寫隨機數據,這時候看螢幕應該是像這樣的:
再執行 cat /dev/zero > /dev/fb0
,向 fb0 內全寫 0,現在螢幕應該會回到黑屏狀態。