接続#
始める前に、まずあなたのスクリーンのピン定義を確認してください。以下は私が購入したスクリーンです:
店舗から提供されたスクリーンの資料を参照して、ピン定義は以下の通りです:
ピン | 定義 |
---|---|
BLK | バックライト |
CS | SPI チップセレクト |
DC | スクリーンデータ |
RES | スクリーンリセット |
SDA | SPI データ |
SCL | SPI クロック |
VCC | 電源 |
GND | GND |
Milk-V Duo のドキュメントから、Milk-V Duo にはハードウェア SPI があり、さらに DC
と RES
に接続するために 2 つのピンが必要であることがわかります。ここでは 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); // ピン 9
PINMUX_CONFIG(SD1_CMD, SPI2_SDO); // ピン 10
PINMUX_CONFIG(SD1_D0, SPI2_SDI); // ピン 11
PINMUX_CONFIG(SD1_D3, SPI2_CS_X); // ピン 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;
}
// アクティブローは初期状態をローに変換します
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
のスクリーン設定を変更し、幅と高さをスクリーンのサイズに変更します:
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);
/* スリープモードをオフにする */
write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
mdelay(120);
/* ピクセルフォーマットを 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 と VRH レジスタの値はコマンド書き込みから来ます
* (NVM ではなく)
*/
write_reg(par, VDVVRHEN, 0x01);
/*
* VRH = 4.3V + (VCOM + VCOM オフセット + 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 カーネルのデフォルトと異なる場合は、スクリーンオフセットを設定するためにメーカーの資料を参照する必要があります:
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 に全てゼロを書き込むと、スクリーンは黒い状態に戻るはずです。