**內核版本5.4** 在使用spi匯流排接上了一個小網卡,實現了我們開發板對網路的訪問之後,我還想接一個小的[spi屏幕 1.44寸款](https://item.taobao.com/item.htm?spm=a1z09.2.0.0.731e2e8dAkrB01&id=571409957622&_ ...
內核版本5.4
在使用spi匯流排接上了一個小網卡,實現了我們開發板對網路的訪問之後,我還想接一個小的spi屏幕 1.44寸款,來畫一隻小企鵝,順便顯示一些系統的調試信息。但是由於我這個開發板向外暴露出來的spi介面就兩個,而且有一個已經因為串口的設置而不能使用。所以我們只能讓這個小屏幕和enc28j60共用一個spi外設。
內核配置
直接make menuconfig
,進入Device Drivers
,打開SPI,打開ST7735R的驅動。保存,再make -j16
.
接線與修改設備樹
我打算讓enc28j60使用spi自己的cs作為片選線,然後另外找一個GPIO作為spi屏幕的片選。
那這樣的話又得改設備樹。我們這個spi屏幕的驅動器晶元是"st7735s"
。但是linux有st7735r
,這倆
是相容的,可以直接用。
/ {
model = "Lichee Pi Nano";
compatible = "licheepi,licheepi-nano", "allwinner,suniv-f1c100s";
aliases {
serial1 = &uart1;
};
chosen {
stdout-path = "serial1:115200n8";
/delete-node/ framebuffer@0;
};
reg_vcc3v3: vcc3v3 {
compatible = "regulator-fixed";
regulator-name = "vcc3v3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};
backlight: backlight {
compatible = "gpio-backlight";
gpios = <&pio 4 4 GPIO_ACTIVE_HIGH>;
default-on;
};
};
&spi1{
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi1_pins>;
cs-gpios = <0>,<&pio 4 5 GPIO_ACTIVE_LOW>;
enc28j60: ethernet@0 {
compatible = "microchip,enc28j60";
pinctrl-names = "default";
pinctrl-0 = <&enc28j60_pins>;
reg = <0x0>;
interrupt-parent = <&pio>;
interrupts = <4 11 IRQ_TYPE_EDGE_FALLING>;
spi-max-frequency = <12000000>;
};
display@1{
compatible = "okaya,rh128128t", "sitronix,st7735r";
reg = <0x1>;
status = "okay";
spi-max-frequency = <48000000>;
spi-cpol;
spi-cpha;
bgr;
rotate = <90>;
fps = <30>;
buswidth = <8>;
dc-gpios = <&pio 4 2 GPIO_ACTIVE_HIGH>;
reset-gpios = <&pio 4 3 GPIO_ACTIVE_LOW>;
backlight = <&backlight>;
};
};
我們在spi節點下增加了一個display設備,進入linux主線的設備的DTC綁定信息都是在內核源碼中可以找得到的。例如我們要找"sitronix,st7735r"
的設備樹綁定信息,我們直接打開內核根目錄,一搜,就可以在Documentation/devicetree/bindings/display/sitronix,st7735r.yaml
找到我們要的信息。
這個yaml給的是最新的drm驅動匹配方式,不過我們目前還是使用比較簡單,資料比較多的framebuffer TFT
驅動比較好。下一次可以嘗試用這個drm驅動。
我們這個spi屏幕由於和enc28j60共用一個spi,所以我們需要指定其片選。我們可以在spi節點下直接設置cs-gpios = <0>,<&pio 4 5 GPIO_ACTIVE_LOW>
。這是什麼意思呢?第一個<0>
代表了對於第一個spi設備,我們使用spi預設的片選引腳。第二個<&pio 4 5 GPIO_ACTIVE_LOW>
,代表的是我們指定第二個spi設備的片選引腳為PE5,並且低電平有效。GPIO_ACTIVE_LOW
的意思是低電平有效,這樣做可以在內核中配置不同設備的正負邏輯。
引腳正負邏輯與代碼分析
fbtft設備在申請GPIO時,其實用的是gpio子系統的api。這些api用來用去最後是調用了
struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id,
unsigned int idx, unsigned long *flags)
它調用了
desc = of_get_named_gpiod_flags(np, prop_name, idx, &of_flags);
*flags = of_convert_gpio_flags(of_flags);
來獲取設備樹下gpio屬性的flag。在設置gpio的輸出值時,即調用gpiod_set_value
時,會調用
static void gpiod_set_value_nocheck(struct gpio_desc *desc, int value)
{
if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
value = !value;
if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
gpio_set_open_drain_value_commit(desc, value);
else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
gpio_set_open_source_value_commit(desc, value);
else
gpiod_set_raw_value_commit(desc, value);
}
所以我們在設備樹中配置引腳的正負邏輯,會在這裡被處理。如果是正邏輯,直接輸出,如果是負邏輯,則需要取反。
配置引腳正負邏輯
為了知道我們到底應該在哪些引腳配置我們的正負邏輯,我們應該直接看源代碼,因為壓根就沒有文檔說這些。
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.cs, 1); /* Activate chip */
}
RESET引腳在低電平的時候將會導致屏幕複位,Chip-Select在低電平時將會使能設備。所以這兩個引腳都是負邏輯。我們應當在他們設備樹的GPIO後邊加一個GPIO_ACTIVE_LOW
。
上電測試
我上電後,屏幕一直白屏,調試了很久,代碼改來改去,TF卡不知道插拔了多少次,也找不到原因。
最後插上邏輯分析儀,邏輯分析儀真的是電子工程師的眼睛,以前屏幕從來就沒調通過,這次插了邏輯分析儀,能夠分析邏輯後,調試就變得很容易了。一直看波形,發現RESET引腳一直被拉低。才發現原來linux的gpio子系統有這麼一套正負邏輯的區別。
配置好之後,屏幕時好時壞。有多時好時壞呢,一插上邏輯分析儀,屏幕就成功初始化。一拔掉邏輯分析儀,這個屏幕又白屏了。最後一聯想,就想到邏輯分析儀不是自帶上拉嗎?是不是因為我的RESET,CS,MOSI,CLK這些引腳都沒有上拉?找了兩個上拉電阻,一試,屏幕跑起來了!小企鵝出現了!
背光碟機動的修改
其實我一開始把PE4的一個引腳設置為背光引腳(這樣做似乎不太對,應該用mosfet或者三極體來驅動這個tft,因為電流大了會把f1c100s的引腳給燒了)。但是這個屏幕不知道為什麼,就是不亮,亮都不亮一下。於是我就把這根線拔了,直接把TFT的LED引腳連了3V3。但是我覺得這樣不夠優雅,一定要讓linux自己能夠設置這個背光。
backlight: backlight {
compatible = "gpio-backlight";
gpios = <&pio 4 4 GPIO_ACTIVE_HIGH>;
default-on;
};
這個backlight會註冊一個class,位置在/sys/class/backlight/backlight
,這個目錄下有一個文件叫brightness
,即亮度,可以通過echo 1 > brightness
,打開我們的背光。
這裡我們把gpio配置為正邏輯,這樣1就對應著打開背光,符合人類的操作邏輯。
但是為什麼,這個背光不工作呢?設置成1或者0,在調試後,發現
static int gpio_backlight_update_status(struct backlight_device *bl)
{
struct gpio_backlight *gbl = bl_get_data(bl);
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK ||
bl->props.fb_blank != FB_BLANK_UNBLANK ||
bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
brightness = 0;
gpiod_set_value_cansleep(gbl->gpiod, brightness);
return 0;
}
是它判斷這些power fb_blakn state
的時候,直接把brightness設置成0了。我們直接去掉這幾行代碼。但是它調用gpiod_set_value_cansleep(gbl->gpiod, brightness);
,我們的gpio也沒有輸出。
最後看了幾遍代碼,發現原來是它沒有配置好gpio為輸出。
static int gpio_backlight_probe_dt(struct platform_device *pdev,
struct gpio_backlight *gbl)
{
struct device *dev = &pdev->dev;
int ret;
gbl->def_value = device_property_read_bool(dev, "default-on");
gbl->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
if (IS_ERR(gbl->gpiod)) {
ret = PTR_ERR(gbl->gpiod);
if (ret != -EPROBE_DEFER) {
dev_err(dev,
"Error: The gpios parameter is missing or invalid.\n");
}
return ret;
}
return 0;
}
問題就在gbl->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
的這個GPIOD_ASIS
。
/**
* enum gpiod_flags - Optional flags that can be passed to one of gpiod_* to
* configure direction and output value. These values
* cannot be OR'd.
*
* @GPIOD_ASIS: Don't change anything
* @GPIOD_IN: Set lines to input mode
* @GPIOD_OUT_LOW: Set lines to output and drive them low
* @GPIOD_OUT_HIGH: Set lines to output and drive them high
* @GPIOD_OUT_LOW_OPEN_DRAIN: Set lines to open-drain output and drive them low
* @GPIOD_OUT_HIGH_OPEN_DRAIN: Set lines to open-drain output and drive them high
*/
enum gpiod_flags {
GPIOD_ASIS = 0,
GPIOD_IN = GPIOD_FLAGS_BIT_DIR_SET,
GPIOD_OUT_LOW = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT,
GPIOD_OUT_HIGH = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT |
GPIOD_FLAGS_BIT_DIR_VAL,
GPIOD_OUT_LOW_OPEN_DRAIN = GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_OPEN_DRAIN,
GPIOD_OUT_HIGH_OPEN_DRAIN = GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_OPEN_DRAIN,
};
我們改成GPIOD_OUT_LOW
,即成功解決問題。當然,最新的驅動已經修複了這些問題。