受身無間者永遠不死,壽長乃無間地獄中之大劫。
----電影《無間道》經典臺詞
文章目錄
一、設備樹引入與作用
上一節在介紹總線設備驅動模型時,提到platform平臺修改引腳時,設備端的代碼需要重新編譯生成platform_device結構體,同時過多的設備對應的.c文件也會造成過多的冗餘代碼,致使以優雅冠稱的Linux臃腫不堪,Linus祖師爺自然要發火,於是就有了設備樹。
設備樹和總線驅動模型Platform是類似的,都是構造platform_device,不涉及驅動程序核心。而設備樹是如何做到優雅的呢?看下圖左側部分,使用配置文件讓系統生成Platform_device!
需要牢記的一點是:設備樹不可能用來寫驅動。
設備樹只是用來給內核裏的驅動程序, 指定硬件的信息。比如 LED 驅動,在內核的驅動程序裏去操作寄存器,但是操作哪一個引腳?這由設備樹指定。
一個單板啓動時, u-boot 先運行,它的作用是啓動內核。 U-boot 會把內核和設備樹文件都讀入內存,然後啓動內核。在啓動內核時會把設備樹在內存中的地址告訴內核。
二、設備樹的語法
2.1 怎麼描述這棵樹?
既然稱之爲設備樹,得名原因自然像一個樹的形狀咯,如下圖。
描述設備樹的文件叫做 DTS(Device Tree Source),它需要編譯爲 dtb(device tree blob)文件供內核使用。所以這裏我們要談的設備樹語法,當然就是DTS語法了!還需要知道的是.dtsi
文件,它是芯片的公共設備樹文件(i是include),我們寫的.dts
文件會追加到了.dtsi
文件中。
我們先來直觀感受一個設備樹實例。
- 樹狀表達
- 代碼表達
2.2 DTS文件的格式
- 文件佈局
/dts-v1/; // 表示版本
[memory reservations] // 格式爲: /memreserve/ <address> <length>;
/ {
[property definitions]
[child nodes]
};
- node 的格式
- 設備樹中的基本單元,被稱爲“node”。
- label 是標號,可以省略, label 的作用是爲了方便地引用 node。
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
舉例:
/dts-v1/;
/ {
uart0: uart@fe001000 {
compatible="ns16550";
reg=<0xfe001000 0x100>;
};
};
- 引用node的兩種方法
// ①、在根節點之外使用 label 引用 node:
&uart0 {
status = “disabled”;
};
// ②、在根節點之外使用全路徑:
&{/uart@fe001000} {
status = “disabled”;
};
2.3 dtsi文件和頭文件
dts 中可以包含.h 頭文件,也可以包含 dtsi 文件,在.h 頭文件中可以定義一些宏。dts 文件之所以支持“#include”語法,是因爲 arm-linux-gnueabihf-gcc 幫忙。如果只用 dtc(類比gcc) 工具,它是不支持”#include”語法的,只支持“/include”語法。
/dts-v1/;
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/ {
……
};
2.4 常用的屬性(properties)
- compatible
- 根節點下的
compatible
屬性- 用來選擇哪一個“machine desc”: 一個內核可以支持machine A,也支持 machine B,內核啓動後會根據根節點的 compatible 屬性找到對應的machine desc (機器描述)結構體,執行其中的初始化函數。
- compatible 的值,建議取這樣的形式: “manufacturer,model”,即“廠家名,模塊名”。
- 設備節點下的
compatible
屬性- 用來表示兼容的驅動。比如對於某個 LED,內核中可能有 A、 B、 C 三個驅動都支持它,那可以這樣寫。內核啓動時,就會爲這個 LED 按這樣的優先順序爲它找到驅動程序: A、 B、 C。
- 根節點下的
led {
compatible = “A”, “B”, “C”;
};
-
model
- model 用來準確地定義這個硬件是什麼
- 比如一個單板的設備文件的根節點下compatible屬性,可以兼容內核中的“smdk2440”,也兼容“mini2440”。但是它到底是什麼板?用 model 屬性來明確。
- 比如有 2 款板子配置基本一致, 它們的 compatible 是一樣的,那麼就通過 model 來分辨這 2 款板子
-
status
- 最後追加的值會覆蓋前面的值,即只有最後一次賦的值纔有效!
- dtsi 文件中定義了很多設備,但是在你的板子上某些設備是沒有的。這時你可以給這個設備節點添加一個 status 屬性,設置爲“disabled”。
- 通常使用的屬性值:“okay”和“disabled”
示例
//address-cells 爲 1,所以 reg 中用 1 個數來表示地址,即用 0x80000000 來表示地址
//size-cells 爲 1,所以 reg 中用 1 個數來表示大小,即用 0x20000000 表示大小
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
-
#address-cells、 #size-cells
- cell 指一個 32 位的數值
- address-cells: address 要用多少個 32 位數來表示;size-cells: size 要用多少個 32 位數來表示。
-
reg
- 本意是 register,用來表示寄存器地址。但在設備樹裏,它可以用來描述一段空間。
- reg 屬性的值,是一系列的“address size”,用多少個 32 位的數來表示 address 和 size,由其父節點的#address-cells、 #size-cells 決定。
示例:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
對於 ARM 系統,寄存器和內存是統一編址的,即訪問寄存器時用某塊地址,訪問內存時用某塊地址,在訪問方法上沒有區別。
-
name(已過時)
- 它的值是字符串,用來表示節點的名字。在跟 platform_driver 匹配時,優先級最低。compatible 屬性在匹配過程中,優先級最高。
-
device_type(已過時)
- 它的值是字符串,用來表示節點的類型。在跟 platform_driver 匹配時,優先級爲中。compatible 屬性在匹配過程中,優先級最高。
2.5 屬性格式
- 設備節點的屬性形式是
“name=value”
,其中value
有多種取值方式arrays of cells
,1個或多個cell【32 位】,用尖括號< >
表示。- 如:
interrupts = <17 0xc>;
、clock-frequency = <0x00000001 0x00000000>;
- 如:
string(字符串)
,用" "
表示。- 如:
compatible = "simple-bus";
- 如:
A bytestring(字節【8位】序列)
,用中括號[ ]
表示。- 如:
local-mac-address = [00 00 12 34 56 78]; // 每個 byte 使用 2 個 16 進制數來表示
- 如:
- 當然了也可以是以上三種方式的組合,用
,
分隔開每種表示方法。- 如:
example = <0xf00f0000 19>, "a strange property format";
- 如:
2.6 常用的節點(node)
-
根節點
/
- dts 文件中必須有一個根節點
- 根節點中必須有這4個屬性:#address-cells 、#size-cells、compatible、model
-
CPU節點
- 一般不需要我們設置,在 dtsi 文件中都定義好了。
-
memory 節點
- 芯片廠家不可能事先確定你的板子使用多大的內存,所以 memory 節點需要板廠設置.
-
chosen 節點
- 可以通過設備樹文件給內核傳入一些參數,這要在 chosen 節點中設置 bootargs 屬性。
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
三、內核對設備樹的處理
-
如何查看設備樹的節點信息
- 系統啓動後可以在根文件系統中看到設備樹的節點信息:
ls /proc/device-tree
- 系統啓動後可以在根文件系統中看到設備樹的節點信息:
-
從源代碼文件 dts 文件開始,設備樹的處理過程爲:
- ① dts 在 PC 機上被編譯爲 dtb 文件;
- ② u-boot 把 dtb 文件傳給內核;
- ③ 內核解析 dtb 文件,把每一個節點都轉換爲 device_node 結構體;
- ④ 對於某些 device_node 結構體,會被轉換爲 platform_device 結構體。
-
根節點被保存在全局變量 of_root 中, 從 of_root 開始可以訪問到任意節點。
3.1 誰可以轉換爲 platform_device?
- 哪些設備樹節點會被轉換爲 platform_device?
- 情形A - 父節點是根節點:該節點含有compatile 屬性即可轉化爲 platform_device
- 情形B - 父節點非根節點:需要滿足兩個條件 ①、該節點還有compatile 屬性 ②父節點 compatile 屬性爲下面4個之一:“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”,才能轉化爲platform_device。
- 情形C - 還需要記住一個:總線 I2C、SPI 節點下的子節點:不轉換爲 platform_device
- 某個總線下的子節點, 應該交給對應的總線驅動程序來處理, 它們不應該被轉換爲platform_device。
示例講解:
/ {
mytest {
compatile = "mytest", "simple-bus";
mytest@0 {
compatile = "mytest_0";
};
};
i2c {
compatile = "samsung,i2c";
at24c02 {
compatile = "at24c02";
};
};
spi {
compatile = "samsung,spi";
flash@0 {
compatible = "winbond,w25q32dw";
spi-max-frequency = <25000000>;
reg = <0>;
};
};
};
1、/mytest
會被轉換爲 platform_device(依據情形A)。它的子節點/mytest/mytest@0
也會被轉換爲 platform_device(依據情形B)。
2、/i2c
節點一般表示 i2c 控制器, 它會被轉換爲 platform_device, 在內核中有對應的platform_driver。/i2c/at24c02
節點不會被轉換爲 platform_device, 它被如何處理完全由父節點的platform_driver 決定, 一般是被創建爲一個 i2c_client。
3、/spi
節點一般也是用來表示 SPI 控制器, 它會被轉換爲 platform_device,在內核中有對應的 platform_driver。/spi/flash@0
節點不會被轉換爲 platform_device, 它被如何處理完全由父節點的platform_driver 決定, 一般是被創建爲一個 spi_device。
3.2 怎麼轉換爲 platform_device?
結論如下:
- A. platform_device 中含有 resource 數組, 它來自 device_node 的 reg, interrupts 屬性;
- B. platform_device.dev.of_node 指向 device_node, 可以通過它獲得其他屬性
3.3 platform_device 如何與 platform_driver 配對?
從設備樹轉換得來的 platform_device 會被註冊進內核裏,以後當我們每註冊一個platform_driver 時, 它們就會兩兩確定能否配對,如果能配對成功就調用 platform_driver 的probe 函數。套路是一樣的,我們需要將上一節談到的“匹配規則”再完善一下。源碼如下:
- ①. 最先比較: platform_device. driver_override 和 platform_driver.driver.name
- 可以設置 platform_device 的 driver_override,強制選擇某個 platform_driver。
- ②. 接下來比較:設備樹信息
- 比較: platform_device. dev.of_node 和 platform_driver.driver.of_match_table。
- 首先,如果 of_match_table 中含有 compatible 值,就跟 dev 的 compatile 屬性比較,若一致則成功,否則返回失敗;
- 其次,如果 of_match_table 中含有 type 值,就跟 dev 的 device_type 屬性比較,若一致則成功,否則返回失敗;
- 最後,如果 of_match_table 中含有 name 值,就跟 dev 的 name 屬性比較,若一致則成功,否則返回失敗。
- 而設備樹中建議不再使用 devcie_type 和 name 屬性,所以基本上只使用設備節點的compatible 屬性來尋找匹配的 platform_driver。
- 由設備樹節點轉換得來的 platform_device 中,含有一個結構體: of_node,類型如下:
- 如果一個 platform_driver 支持設備樹,它的 platform_driver.driver.of_match_table 是一個數組,類型如下:
- 比較: platform_device. dev.of_node 和 platform_driver.driver.of_match_table。
- ③. 然後比較: platform_device. name 和 platform_driver.id_table[i].name
- Platform_driver.id_table 是“platform_device_id”指針,表示該 drv 支持若干個 device,它裏面列出了各個 device 的{.name, .driver_data}, 其中的“name”表示該 drv 支持的設備的名字, driver_data 是些提供給該 device 的私有數據。
- ④. 最後比較: platform_device.name 和 platform_driver.driver.name
- platform_driver.id_table 可能爲空,這時可以根據 platform_driver.driver.name 來尋找同名的 platform_device。
一個圖概括所有的配對過程
3.4 沒有轉換爲 platform_device 的節點,如何使用?
任意驅動程序裏,都可以直接訪問設備樹。可以使用of相關函數找到節點,讀出裏面的值。具體內容參考4.3。
四、內核裏操作設備樹常用的of函數
4.1 內核中設備樹相關的頭文件介紹
內核源碼中 include/linux/目錄下有很多 of 開頭的頭文件, of 表示“open firmware”即開
放固件。設備樹的處理過程是: dtb -> device_node -> platform_device
4.1.1 處理 dtb
of_fdt.h // dtb 文件的相關操作函數, 我們一般用不到,
// 因爲 dtb 文件在內核中已經被轉換爲 device_node 樹(它更易於使用)
4.1.2 處理device_node
of.h // 提供設備樹的一般處理函數,
// 比如 of_property_read_u32(讀取某個屬性的 u32 值),
// of_get_child_count(獲取某個 device_node 的子節點數)
of_address.h // 地址相關的函數,
// 比如 of_get_address(獲得 reg 屬性中的 addr, size 值)
// of_match_device (從 matches 數組中取出與當前設備最匹配的一項)
of_dma.h // 設備樹中 DMA 相關屬性的函數
of_gpio.h // GPIO 相關的函數
of_graph.h // GPU 相關驅動中用到的函數, 從設備樹中獲得 GPU 信息
of_iommu.h // 很少用到
of_irq.h // 中斷相關的函數
of_mdio.h // MDIO (Ethernet PHY) API
of_net.h // OF helpers for network devices.
of_pci.h // PCI 相關函數
of_pdt.h // 很少用到
of_reserved_mem.h // reserved_mem 的相關函數
4.1.3 處理 platform_device
of_platform.h // 把 device_node 轉換爲 platform_device 時用到的函數,
// 比如 of_device_alloc(根據 device_node 分配設置 platform_device),
// of_find_device_by_node (根據 device_node 查找到 platform_device),
// of_platform_bus_probe (處理 device_node 及它的子節點)
of_device.h // 設備相關的函數, 比如 of_match_device
4.2 platform_device 相關的函數
of_platform.h 中聲明瞭很多函數,但是作爲驅動開發者,我們只使用其中的 1、 2 個。其他的都是給內核自己使用的,內核使用它們來處理設備樹,轉換得到 platform_device。
4.2.1 of_find_device_by_node
函數原型爲:
extern struct platform_device *of_find_device_by_node(struct device_node *np);
設備樹中的每一個節點,在內核裏都有一個 device_node;你可以使用 device_node 去找到對應的 platform_device
4.2.2 platform_get_resource
這個函數跟設備樹沒什麼關係, 但是設備樹中的節點被轉換爲 platform_device 後, 設備樹中的 reg 屬性、 interrupts 屬性也會被轉換爲“resource”(呼應前面的:怎麼轉換爲 platform_device?)。這時,你可以使用這個函數取出這些資源。
數原型爲:
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type // 取哪類資源? IORESOURCE_MEM、 IORESOURCE_REG
* // IORESOURCE_IRQ 等
* @num: resource index // 這類資源中的哪一個?
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
對於設備樹節點中的 reg 屬性,它屬性 IORESOURCE_MEM 類型的資源;
對於設備樹節點中的 interrupts 屬性,它屬性 IORESOURCE_IRQ 類型的資源。
4.3 有些節點不會生成 platform_device,怎麼訪問它們?
內核會把 dtb 文件解析出一系列的 device_node 結構體,我們可以直接訪問這些device_node。內核源碼 incldue/linux/of.h 中聲明瞭 device_node 和屬性 property 的操作函數,device_node 和 property 的結構體定義如下:
4.3.1 找到節點
-
of_find_node_by_path
- 根據路徑找到節點,比如“/”就對應根節點,“/memory”對應 memory 節點。
static inline struct device_node *of_find_node_by_path(const char *path);
-
of_find_node_by_name
- 根據名字找到節點, 節點如果定義了 name 屬性,那我們可以根據名字找到它。
extern struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
- 參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
但是在設備樹的官方規範中不建議使用“name”屬性,所以這函數也不建議使用。
- 參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
-
of_find_node_by_type
- 根據類型找到節點,節點如果定義了 device_type 屬性,那我們可以根據類型找到它。
extern struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
- 參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
但是在設備樹的官方規範中不建議使用“device_type”屬性,所以這函數也不建議使用。
- 參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
-
of_find_compatible_node
- 根據 compatible 找到節點,節點如果定義了 compatible 屬性,那我們可以根據
compatible 屬性找到它。 extern struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compat);
- 參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
- 參數 compat 是一個字符串,用來指定 compatible 屬性的值;
- 參數 type 是一個字符串,用來指定 device_type 屬性的值,可以傳入 NULL。
- 根據 compatible 找到節點,節點如果定義了 compatible 屬性,那我們可以根據
-
of_find_node_by_phandle
- 根據 phandle 找到節點。dts 文件被編譯爲 dtb 文件時,每一個節點都有一個數字 ID,這些數字 ID 彼此不同。可以使用數字 ID 來找到 device_node。這些數字 ID 就是 phandle。
extern struct device_node *of_find_node_by_phandle(phandle handle);
- 參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
-
of_get_parent
- 找到 device_node 的父節點。
extern struct device_node *of_get_parent(const struct device_node *node);
- 參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
-
of_get_next_parent
- 這個函數名比較奇怪,怎麼可能有“next parent”?它實際上也是找到 device_node 的父節點,跟 of_get_parent 的返回結果是一樣的。差別在於它多調用下列函數,把 node 節點的引用計數減少了 1。這意味着調用of_get_next_parent 之後,你不再需要調用 of_node_put 釋放 node 節點。
extern struct device_node *of_get_next_parent(struct device_node *node);
- 參數 from 表示從哪一個節點開始尋找,傳入 NULL 表示從根節點開始尋找。
-
of_get_next_child
- 取出下一個子節點。
extern struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);
- 參數 node 表示父節點;prev 表示上一個子節點,設爲 NULL 時表示想找到第 1 個子節點。
- 不斷調用 of_get_next_child 時,不斷更新 pre 參數, 就可以得到所有的子節點。
-
of_get_next_available_child
- 取出下一個“可用”的子節點,有些節點的 status 是“disabled”,那就會跳過這些節點。
struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *prev);
- 參數 node 表示父節點;prev 表示上一個子節點,設爲 NULL 時表示想找到第 1 個子節點。
-
of_get_child_by_name
-
根據名字取出子節點.
-
extern struct device_node *of_get_child_by_name(const struct device_node *node, const char *name);
- 參數 node 表示父節點;name 表示子節點的名字。
-
4.3.2 找到屬性
內核源碼 incldue/linux/of.h 中聲明瞭 device_node 的操作函數,當然也包括屬性的操作函數。
- of_find_property
- 找到節點中的屬性。
extern struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
- 參數 np 表示節點,我們要在這個節點中找到名爲 name 的屬性。lenp 用來保存這個屬性的長度, 即它的值的長度。
舉個例子,在設備樹中,節點大概是這樣:
xxx_node {
xxx_pp_name = “hello”;
};
上述節點中,“xxx_pp_name”就是屬性的名字,值的長度是 6。
4.3.3 獲取屬性的值
-
of_get_property
- 根據名字找到節點的屬性,並且返回它的值
const void *of_get_property(const struct device_node *np, const char *name, int *lenp)
- 參數 np 表示節點,我們要在這個節點中找到名爲 name 的屬性,然後返回它的值。lenp 用來保存這個屬性的長度, 即它的值的長度。
-
of_property_count_elems_of_size
- 根據名字找到節點的屬性,確定它的值有多少個元素(elem)。
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
- 參數 np 表示節點,我們要在這個節點中找到名爲 propname 的屬性,然後返回下列結果:
return prop->length / elem_size;
舉個例子,在設備樹中,節點大概是這樣:
xxx_node {
xxx_pp_name = <0x50000000 1024> <0x60000000 2048>;
};
調用 of_property_count_elems_of_size(np, “xxx_pp_name”, 8)時,返回值是 2 - ?(疑問)
調用 of_property_count_elems_of_size(np, “xxx_pp_name”, 4)時,返回值是 4 - ?(疑問)
- 讀整數 u32/u64
static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
extern int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value);
舉個例子,在設備樹中,節點大概是這樣
xxx_node {
name1 = <0x50000000>;
name2 = <0x50000000 0x60000000>;
};
調用 of_property_read_u32 (np, “name1”, &val)時, val 將得到值 0x50000000;
調用 of_property_read_u64 (np, “name2”, &val)時, val 將得到值0x0x6000000050000000。
- 讀某個整數 u32/u64
extern int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
舉個例子,在設備樹中,節點大概是這樣:
xxx_node {
name2 = <0x50000000 0x60000000>;
};
調用 of_property_read_u32 (np, “name2”, 1, &val)時, val 將得到值 0x0x60000000。?(疑問)
- 讀數組
int of_property_read_variable_u8_array( const struct device_node *np,
const char *propname, u8 *out_values,
size_t sz_min, size_t sz_max);
int of_property_read_variable_u16_array(const struct device_node *np,
const char *propname, u16 *out_values,
size_t sz_min, size_t sz_max);
int of_property_read_variable_u32_array(const struct device_node *np,
const char *propname, u32 *out_values,
size_t sz_min, size_t sz_max);
int of_property_read_variable_u64_array(const struct device_node *np,
const char *propname, u64 *out_values,
size_t sz_min, size_t sz_max);
舉個例子,在設備樹中,節點大概是這樣
xxx_node {
name2 = <0x50000012 0x60000034>;
};
上述例子中屬性 name2 的值,長度爲 8。
調用 of_property_read_variable_u8_array (np, “name2”, out_values, 1, 10)時, out_values
中將會保存這 8 個字節: 0x12,0x00,0x00,0x50,0x34,0x00,0x00,0x60。
調用 of_property_read_variable_u16_array (np, “name2”, out_values, 1, 10)時, out_values中將會保存這 4 個 16 位數值: 0x0012, 0x5000,0x0034,0x6000。
總之,這些函數要麼能取到全部的數值,要麼一個數值都取不到;如果值的長度在 sz_min 和 sz_max 之間,就返回全部的數值;否則一個數值都不返回。
- 讀字符串
int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
- 返回節點 np 的屬性(名爲 propname)的值, (*out_string)指向這個值,把它當作字符串。
4.4 如何修改設備樹?
一個寫得好的驅動程序, 它會盡量確定所用資源。只把不能確定的資源留給設備樹, 讓設備樹來指定。根據原理圖確定"驅動程序無法確定的硬件資源", 再在設備樹文件中填寫對應內容。那麼, 所填寫內容的格式是什麼?
4.4.1 使用芯片廠家提供的工具
有些芯片,廠家提供了對應的設備樹生成工具,可以選擇某個引腳用於某些功能,就可以自動生成設備樹節點。你再把這些節點複製到內核的設備樹文件裏即可。
4.4.2 看綁定文檔
內核文檔 Documentation/devicetree/bindings/
做得好的廠家也會提供設備樹的說明文檔
4.4.3 參考同類型單板的設備樹文件
4.4.4 網上搜索
4.4.5 實在沒辦法時, 只能去研究驅動源碼
本節,我們初步介紹了設備樹的引入,設備樹的作用,設備樹的基本語法以及設備樹常用的of函數。關於of函數當然有很多,我們不可能每個都要記住,還是那個原則,Learn by doing,做着學着,做着記着!下一節我們將通過設備樹實際操作LED,來吧,一起感受下設備樹的魅力吧!