日落果

日落果的随手记

twitter
github

[Embedded Linux Development Introduction] Using Milk-V Duo to Drive st7789v Small Screen

Wiring#

Before starting, first determine the pin definitions of your screen. Below is the screen I purchased:

image

By consulting the screen documentation provided by the seller, the pin definitions are as follows:

PinDefinition
BLKBacklight
CSSPI Chip Select
DCScreen Data
RESScreen Reset
SDASPI Data
SCLSPI Clock
VCCPower
GNDGND

image

From the Milk-V Duo documentation, it can be seen that the Milk-V Duo has a hardware SPI, and in addition, two pins are needed to connect DC and RES. Here, I chose GPIOA24 and GPIOA23. Since the screen does not need to output data, only the data pin needs to be connected to the development board's SPI2_TX.

Here, I directly connected 3V3 to the BLK pin, without needing PWM dimming.

Thus, my wiring is as follows:

Screen     Development Board
BLK --- 3V3
CS  --- SPI2_CSn
SDA --- SPI2_TX
SCL --- SPI2_CLK
DC  --- GPIOA24
RES --- GPIOA23

Modify Pin Definitions#

Since the SPI2 of the Milk-V Duo development board is a multiplexed pin and its default function is not SPI2, the pin definitions need to be modified.

The pin definitions for the Milk-V Duo are declared in the build/boards/cv180x/cv1800b_milkv_duo_sd/u-boot/cvi_board_init.c file within cvi_board_init. Here we directly modify SD1_CLK, SD1_CMD, SD1_D0, SD1_D3 to the corresponding pins of SPI2 (refer to the Milk-V Duo pin definition diagram above).

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

Kernel Configuration#

First, configure the 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>;
	};
};

Here, set dc and reset to the GPIO port numbers you connected.

Then enable ST7789V support and FB support in the kernel. If you are using the official buildroot like me, you can directly modify the build/boards/cv180x/cv1800b_milkv_duo_sd/linux/cvitek_cv1800b_milkv_duo_sd_defconfig file.

Here we do not change other configurations, only add support for FB_TFT and the ST7789V driver:

CONFIG_FB_TFT=y
CONFIG_FB_TFT_ST7789V=y

Next, modify the cv180x dtsi, which is usually located in build/boards/default/dts/cv180x/cv180x_base.dtsi.

Set the SPI pins to pull-up by default:

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; // Pull-up
	};

Modify Driver#

First, modify the fbtft_request_one_gpio function in linux_5.10/drivers/staging/fbtft/fbtft-core.c. The modifications are based on https://github.com/notro/fbtft/blob/e9fc10a080a6c52e46e004c4c2bc9fd3cf3d7445/fbtft-core.c#L166-L204. If not modified, an error will occur when initializing the screen:

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;
}

Also, include the header files at the top of the file:

#include "fbtft.h"
#include "internal.h"

Next, modify the st7789v driver, which is linux_5.10/drivers/staging/fbtft/fb_st7789v.c. First, modify the screen configuration in fbtft_display, changing the width and height to match your screen size:

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
		},
};

Then, modify the initialization function according to the manufacturer's documentation:

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);

	/* The reference code from the screen seller, I don't know where it came from */
	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;
}

If your screen is the same size as mine but different from the default in the Linux kernel, you will also need to set the screen offset according to the manufacturer's documentation:

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);
}

Check if the Screen Works#

If your operations are correct, after powering on and SSHing in, executing dmesg | grep st7789v should show that fb0 has been correctly initialized:

[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

Executing cat /dev/random > /dev/fb0 will write random data to fb0, and the screen should look like this:

image

Then executing cat /dev/zero > /dev/fb0 will write zeros to fb0, and the screen should return to a black state.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.