在Linux驅(qū)動(dòng)中使用gpio子系統(tǒng)

reference:

內(nèi)核相關(guān)文檔

Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt
Documentation\gpio\gpio.txt
Documentation\devicetree\bindings\gpio\gpio.txt

背景

隨著內(nèi)核的發(fā)展,linux驅(qū)動(dòng)框架在不斷的變化。在早期,GPIO子系統(tǒng)存在之前,我們驅(qū)動(dòng)需要在代碼中配置寄存器來(lái)使用GPIO引腳设捐。

此后,出現(xiàn)了gpio子系統(tǒng),后來(lái)又出現(xiàn)了pinctrl子系統(tǒng)猴凹。

有些平臺(tái)的實(shí)現(xiàn)沒(méi)有使用內(nèi)核提供的pinctrl子系統(tǒng),而是繼續(xù)采用在內(nèi)核提供pinctrl子系統(tǒng)前自己實(shí)現(xiàn)的那套機(jī)制來(lái)pinmux操作岭皂,如Ti的omap平臺(tái)精堕;

有些平臺(tái)則基于pinctrl子系統(tǒng)來(lái)實(shí)現(xiàn)pinmux、pinconf的控制蒲障。

介紹

GPIO子系統(tǒng)可以說(shuō)是Linux中最簡(jiǎn)單的子系統(tǒng)歹篓。

  • GPIO(General Purpose Input Output):負(fù)責(zé)管理整個(gè)系統(tǒng)各gpio輸入輸出管腳的使用情況,同時(shí)通過(guò)sys文件系統(tǒng)導(dǎo)出了調(diào)試信息和應(yīng)用層控制接口揉阎。
  • Pinctrl(Pin Control):負(fù)責(zé)管理SOC中各pin的狀態(tài)庄撮,比如輸出電流能力、是否有內(nèi)部上拉或者下拉毙籽,是否有功能復(fù)用等參數(shù)洞斯。

要想操作GPIO引腳,需要先把所用引腳配置成GPIO功能,這個(gè)通過(guò)pinctrl子系統(tǒng)來(lái)實(shí)現(xiàn)烙如。然后可以根據(jù)設(shè)置的引腳的方向來(lái)讀取引腳的值和設(shè)置輸出值么抗。

在BSP工程師實(shí)現(xiàn)好GPIO子系統(tǒng)后,我們就可以在設(shè)備樹(shù)中指定GPIO引腳亚铁,在驅(qū)動(dòng)中使用GPIO子系統(tǒng)的標(biāo)準(zhǔn)函數(shù)來(lái)獲取GPIO蝇刀、設(shè)置GPIO方向、讀取/設(shè)置GPIO的值徘溢。這樣的驅(qū)動(dòng)代碼是于單板無(wú)關(guān)的吞琐。

gpio子系統(tǒng)

gpio子系統(tǒng)內(nèi)部實(shí)現(xiàn)主要提供了兩類接口:

  • 一類給bsp工程師,用于注冊(cè)gpio chip(也就是所謂的gpio控制器驅(qū)動(dòng))

  • 另一部分給驅(qū)動(dòng)工程師使用然爆,為驅(qū)動(dòng)工程師屏蔽了不同gpio chip之間的區(qū)別站粟,驅(qū)動(dòng)工程師調(diào)用的api的最終操作流程會(huì)導(dǎo)向gpio對(duì)應(yīng)的gpio chip的控制代碼,也就是bsp的代碼曾雕。

核心實(shí)現(xiàn)

gpio子系統(tǒng)的實(shí)現(xiàn)源碼在drivers/gpio文件夾下奴烙,主要文件有:

在安卓系統(tǒng)中,實(shí)現(xiàn)源碼在kernel/drivers/gpio

文件 作用
devres.c 針對(duì)gpio api增加的devres機(jī)制的支持
gpiolib.c gpio子系統(tǒng)的核心實(shí)現(xiàn)
gpiolib-of.c 對(duì)設(shè)備樹(shù)的支持
gpiolib-acpi.c 和acpi相關(guān)剖张,不分析
gpio-xxx.c 根據(jù)平臺(tái)的不同切诀,所對(duì)應(yīng)的gpio控制

gpio子系統(tǒng)提供了兩層接口,一層給上層驅(qū)動(dòng)工程師調(diào)用修械,一層給下層bsp工程師調(diào)用趾牧。

上層使用前,當(dāng)然先得bsp工程師完成對(duì)應(yīng)的動(dòng)作肯污。

Linux內(nèi)核中GPIO子系統(tǒng)的軟件驅(qū)動(dòng)分層圖

GPIO子系統(tǒng)有兩套接口

  1. 一是基于描述符(descriptor-based)的翘单,相關(guān)api函數(shù)都是以"gpiod_"為前綴,它使用gpio_desc結(jié)構(gòu)來(lái)表示一個(gè)引腳蹦渣。

  2. 另一種是老(legency)的哄芜,相關(guān)api函數(shù)都是以"gpio_"為前綴,它使用一個(gè)整數(shù)來(lái)表示一個(gè)引腳柬唯,強(qiáng)烈建議不要使用legacy的接口函數(shù)认臊。

其實(shí),legacy gpio大部分api就是基于描述符api來(lái)實(shí)現(xiàn)的锄奢,我們可以看到很多legacy api內(nèi)部的實(shí)現(xiàn)調(diào)用了to_desc失晴。

// 1.獲取GPIO
gpiod_get;
gpiod_get_index;
gpiod_get_array;
devm_gpiod_get;
devm_gpiod_get_index;
devm_gpiod_get_array;

// 2.設(shè)置方向
gpiod_direction_input;
gpiod_direction_output;

// 3.讀值、寫值
gpiod_get_value;
gpiod_set_value;

// 4\. 設(shè)為中斷(如果必要)
request_irq(gpiod_to_irq(gpio_desc)...); //將gpio轉(zhuǎn)為對(duì)應(yīng)的irq拘央,然后注冊(cè)該irq的中斷handler

// 5.釋放GPIO
gpiod_put;
gpiod_put_array;
devm_gpiod_put;
devm_gpiod_put_array;

前綴為"devm_"的含義是設(shè)備資源管理涂屁,這是一種自動(dòng)釋放資源的機(jī)制。

思想:“資源是屬于設(shè)備的灰伟,設(shè)備不存在時(shí)資源就可以自動(dòng)釋放”拆又。

背景:在Linux驅(qū)動(dòng)開(kāi)發(fā)過(guò)程中,先申請(qǐng)了GPIO,再申請(qǐng)內(nèi)存帖族,如果內(nèi)存申請(qǐng)失敗栈源,那么在返回之前就需要先釋放GPIO資源。如果使用的是devm相關(guān)函數(shù)竖般,在內(nèi)存申請(qǐng)失敗時(shí)可以直接返回甚垦,設(shè)備的銷毀函數(shù)會(huì)自動(dòng)地釋放已經(jīng)申請(qǐng)了的GPIO資源。

因此捻激,建議使用devm相關(guān)函數(shù)操作GPIO制轰。

gpio控制api( descriptor)

使用基于描述符的接口時(shí)前计,GPIO被作為一個(gè)描述符來(lái)使用胞谭。

#include <linux/gpio/consumer.h>
// 更多相關(guān)的說(shuō)明可以參考 Documentation/gpio/consumer.txt

獲取一個(gè)或一組GPIO

struct gpio_desc * gpiod_get(struct device *dev, 
                             const char *con_id,
                             enum gpiod_flags flags);

/*
在允許GPIO不存在時(shí),可以使用gpiod_get_optional()和gpiod_get_index_optional()函數(shù)男杈。
這兩個(gè)函數(shù)在沒(méi)有成功分配到GPIO的時(shí)候返回NULL而不是-ENOENT丈屹。
*/
struct gpio_desc * gpiod_get_optional(struct device *dev,
                                      const char *con_id,
                                      enum gpiod_flags flags);

struct gpio_descs {
    unsigned int ndescs; // 數(shù)量
    struct gpio_desc *desc[]; // 每一個(gè) desc 的情況
}
// 返回gpio_descs 注意:不是 gpio_desc
struct gpio_descs * gpiod_get_array(struct device *dev,
                                    const char *con_id,
                                    enum gpiod_flags flags);

/*多個(gè)Pin時(shí)需要附帶index參數(shù)。*/
struct gpio_desc * gpiod_get_index(struct device *dev,
                                   const char *con_id, 
                                   unsigned int idx,
                                   enum gpiod_flags flags);

struct gpio_desc * gpiod_get_index_optional(struct device *dev,
                                            const char *con_id,
                                            unsigned int index,
                                            enum gpiod_flags flags);

struct gpio_desc * devm_gpiod_get(struct device *dev, const char *con_id,
                                  enum gpiod_flags flags);

struct gpio_desc * devm_gpiod_get_index(struct device *dev,
                                        const char *con_id,
                                        unsigned int idx,
                                        enum gpiod_flags flags);

描述:必須通過(guò)調(diào)用gpiod_get()函數(shù)族來(lái)獲取對(duì)應(yīng)的描述符伶棒。

參數(shù)解析:

  • con_id:字符串類型旺垒,即GPIO的名字;

一般需要查看設(shè)備樹(shù)中的定義肤无。除此之外先蒋,我們還可以在設(shè)備樹(shù)文件里添加參數(shù)(GPIO_ACTIVE_LOWGPIO_OPEN_DRAIN宛渐、GPIO_OPEN_SOURCE)來(lái)觸發(fā)該接口內(nèi)部設(shè)置gpio竞漾,具體的參數(shù)格式和具體的gpio chip driver有關(guān),一般可以在Documentation/devicetree/bindings/gpio里找到對(duì)應(yīng)平臺(tái)的方法窥翩。

有關(guān)DeviceTree情況中con_id參數(shù)的更詳細(xì)說(shuō)明請(qǐng)參閱Documentation/gpio/board.txt

例如:

在SD卡驅(qū)動(dòng)看到的去查找名字為cd-gpios的gpio:

// simple.c:
ctx->cd_gpio = devm_gpiod_get_optional(dev, "cd", 0);

在使用SD卡驅(qū)動(dòng)的主dts就有cd pin的定義:

// xxx.dts:
    cd-gpios = <&gpio2 12 GPIO_ACTIVE_LOW>;

  • index:邏輯下標(biāo)业岁。將一個(gè)GPIO設(shè)備(DESC)下的多個(gè)Pin看成一個(gè)數(shù)組,此時(shí)index是數(shù)組成員下標(biāo)寇蚊。

內(nèi)核文檔有個(gè)例子笔时,比如gpio如下定義:

led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
            <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
            <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */

如果index是0,那么對(duì)應(yīng)的就是gpio 15仗岸;如果index是1允耿,那么對(duì)應(yīng)就是gpio 16,以此類推扒怖。

  • flags:用于可選地指定GPIO的方向和初始值较锡,它的值可以是:
    • GPIOD_ASIS或0表示根本不初始化GPIO。需要隨后使用專門的函數(shù)設(shè)置方向
    • GPIOD_IN初始化GPIO作為輸入姚垃。
    • GPIOD_OUT_LOW將GPIO初始化為輸出念链,值為0。
    • GPIOD_OUT_HIGH將GPIO初始化為輸出,值為1掂墓。
    • GPIOD_OUT_LOW_OPEN_DRAIN:與GPIOD_OUT_LOW相同谦纱,但強(qiáng)制以開(kāi)漏的方式使用
    • GPIOD_OUT_HIGH_OPEN_DRAIN:與GPIOD_OUT_HIGH相同,但強(qiáng)制以開(kāi)漏的方式使用

最后兩個(gè)標(biāo)志用于必須開(kāi)漏方式的情況君编,比如GPIO被用作I2C時(shí)跨嘉,如果該GPIO尚未在映射(參見(jiàn)board.txt)中被配置為開(kāi)漏方式,將被強(qiáng)制配置為開(kāi)漏方式并給出WARNING吃嘿。

這兩個(gè)函數(shù)都返回有效的GPIO描述符或可被IS_ERR()檢查的錯(cuò)誤代碼(它們永遠(yuǎn)不會(huì)返回NULL指針)祠乃。

返回值:成功返回一個(gè)GPIO描述符;失敗返回錯(cuò)誤編碼兑燥,可以使用IS_ERR()進(jìn)行檢查錯(cuò)誤原因亮瓷。

  • 返回-ENOENT只會(huì)發(fā)生在當(dāng)且僅當(dāng)沒(méi)有為設(shè)備/功能/索引三元組成功分配GPIO的時(shí)候。
  • 其他錯(cuò)誤代碼用于已成功分配GPIO降瞳,但在試圖獲得它的時(shí)候發(fā)生了錯(cuò)誤的情況:這可以用于區(qū)分錯(cuò)誤原因是可選GPIO參數(shù)錯(cuò)誤還是GPIO缺失這兩種情況嘱支。

釋放

void gpiod_put(struct gpio_desc *desc);
void gpiod_put_array(struct gpio_descs *descs); // 對(duì)應(yīng) get*array

void devm_gpiod_put(struct device *dev, struct gpio_desc *desc);
void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs);

描述:釋放之前通過(guò)get獲取的GPIO描述符

注意:在釋放之后,嚴(yán)格禁止使用被釋放的描述符挣饥;也不允許在使用gpiod_get_array()獲取的數(shù)組中單獨(dú)使用gpiod_put()釋放描述符除师。

設(shè)置方向

注意:GPIO沒(méi)有默認(rèn)方向。因此扔枫,使用GPIO前必須首先設(shè)置其方向汛聚,否則將導(dǎo)致未定義的行為!

// 設(shè)置GPIO為輸入還是輸出
int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);

// 查詢GPIO的當(dāng)前方向:返回0表示輸出短荐,1表示輸入倚舀,或錯(cuò)誤代碼(如果出錯(cuò))
int gpiod_get_direction(const struct gpio_desc *desc);

描述:使用設(shè)備驅(qū)動(dòng)必須首先確定GPIO的方向。如果在調(diào)用gpiod_get* ()時(shí)搓侄,flag指定了nodirection瞄桨,就可以調(diào)用上面的某個(gè)gpiod_direction_*()函數(shù)來(lái)設(shè)置方向:

參數(shù)解析:

  • value:對(duì)于輸出GPIO,提供的值將成為初始輸出值讶踪;用于避免系統(tǒng)啟動(dòng)期間的信號(hào)故障芯侥。

返回值:成功返回值為零,否則返回值為負(fù)的錯(cuò)誤代碼乳讥。

該返回值應(yīng)該被檢查柱查,因?yàn)橹螳@取/設(shè)置GPIO引腳值get/set調(diào)用不會(huì)返回錯(cuò)誤,所以錯(cuò)誤的配置是有可能的云石。您通常應(yīng)該在任務(wù)上下文進(jìn)行這些調(diào)用唉工。但是,對(duì)于自旋鎖安全(Spinlock-Safe)的GPIO汹忠,可以作為板級(jí)設(shè)置初期的一部分淋硝,在啟用任務(wù)之前使用它們雹熬。

使用單個(gè)GPIO

/* 
Spinlock-Safe的GPIO訪問(wèn) 
    意義:如果操作GPIO可能導(dǎo)致sleep,那么同步機(jī)制不能采用spinlock谣膳,因?yàn)閟pinlock要求不能sleep
*/

// 讀取輸出引腳的值時(shí)竿报,返回的值應(yīng)該是引腳上的值。由于包括開(kāi)漏信號(hào)和輸出延遲在內(nèi)的問(wèn)題继谚,它并不總是匹配指定的輸出值烈菌。
int gpiod_get_value(const struct gpio_desc *desc);

void gpiod_set_value(struct gpio_desc *desc, int value);

描述:大多數(shù)GPIO控制器可通過(guò)存儲(chǔ)器讀/寫指令訪問(wèn)。在不能睡眠的環(huán)境下調(diào)用花履。

不能睡眠的環(huán)境:內(nèi)部hard(非線程的)IRQ handler芽世、類似的上下文中完成的操作(即原子操作中)。

參數(shù)解析:

  • value:布爾值诡壁,零為低济瓢,非零為高。

返回值:get/set調(diào)用不會(huì)返回錯(cuò)誤欢峰,因?yàn)椤盁o(wú)效的GPIO”應(yīng)該在這之前就從gpiod_direction_*()中得知葬荷。

但請(qǐng)注意涨共,并非所有平臺(tái)都可以讀取輸出引腳的值纽帖;對(duì)于那些不能讀取的平臺(tái),函數(shù)永遠(yuǎn)返回零举反。另外懊直,使用這些函數(shù)訪問(wèn)需要睡眠才能安全訪問(wèn)的GPIO(見(jiàn)下文)是錯(cuò)誤的操作。

/* 允許睡眠的GPIO訪問(wèn) */

// 判斷是否允許睡眠:返回非零 代表 可以睡眠:
int gpiod_cansleep(const struct gpio_desc *desc);

// 獲取火鼻、設(shè)置GPIO的值室囊。
int gpiod_get_value_cansleep(const struct gpio_desc * desc);
void gpiod_set_value_cansleep(struct gpio_desc * desc,int value);

描述:有些GPIO控制器必須使用基于消息的總線(如I2C或SPI)訪問(wèn)魁索。讀取或?qū)懭脒@些GPIO值的命令需要等待到達(dá)隊(duì)列的頭部以傳輸命令并獲得其響應(yīng)融撞。這樣就需要允許睡眠,導(dǎo)致這類GPIO的訪問(wèn)不能在內(nèi)部IRQ處理程序內(nèi)(原子上下文)完成粗蔚。

訪問(wèn)這樣的GPIO需要一個(gè)可以休眠的上下文尝偎,例如一個(gè)threaded IRQ處理程序,并且必須使用上述訪問(wèn)函數(shù)訪問(wèn)函數(shù)(而不是沒(méi)有帶cansleep()后綴的)鹏控。

除了可以睡眠致扯,無(wú)法在hardIRQ處理程序訪問(wèn)的特點(diǎn)以外,這些調(diào)用與Spinlock-Safe的調(diào)用相同当辐。

使用gpio的時(shí)候需要了解一下低有效和開(kāi)漏語(yǔ)義抖僵,見(jiàn)附錄。

使用一組GPIO

## 獲取值
int gpiod_get_array_value(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
int gpiod_get_raw_array_value(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
int gpiod_get_array_value_cansleep(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
int gpiod_get_raw_array_value_cansleep(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);

## 設(shè)置值
void gpiod_set_array_value(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
void gpiod_set_raw_array_value(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
void gpiod_set_array_value_cansleep(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);
void gpiod_set_raw_array_value_cansleep(unsigned int array_size,
        struct gpio_desc **desc_array,
        int *value_array);

描述:如果相應(yīng)的芯片驅(qū)動(dòng)器支持缘揪,這些函數(shù)將嘗試同時(shí)訪問(wèn)屬于同一存儲(chǔ)體或芯片的GPIO耍群。在這種情況下义桂,可以預(yù)期顯著改善的性能。如果無(wú)法同時(shí)訪問(wèn)蹈垢,GPIO將按順序訪問(wèn)澡刹。用來(lái)獲取、設(shè)置GPIO的值耘婚。

參數(shù)解析:

  • array_size - 數(shù)組元素的數(shù)量
  • desc_array - GPIO描述符數(shù)組罢浇,可以是任意一組GPIO

如何理解“任意”:

我們可以先使用gpiod_get()gpiod_get_array()的任意組合來(lái)獲得描述符后,放入一個(gè)我們自己構(gòu)建數(shù)組中沐祷,再將其傳遞給上述函數(shù))嚷闭。

同時(shí),如果為了獲得最佳性能赖临,屬于同一芯片的GPIO應(yīng)該在描述符數(shù)組中是連續(xù)的胞锰。

  • value_array - 存儲(chǔ)GPIO值(get)的數(shù)組或要分配給GPIO的值數(shù)組(set)

返回值:

  • gpiod_get_array_value()及其變體成功時(shí)返回0,錯(cuò)誤返回負(fù)數(shù)兢榨。
  • gpiod_get_value()在成功傳遞GPIO值時(shí)返回0或1嗅榕。使用數(shù)組函數(shù)時(shí),GPIO值存儲(chǔ)在value_array中吵聪,而不是作為返回值傳回凌那。

小例子:

struct gpio_descs *my_gpio_descs = gpiod_get_array(...);

if(!my_gpio_descs) 
    return ERROR...

gpiod_set_array_value(my_gpio_descs->ndescs, my_gpio_descs->desc,
        my_gpio_values);

配置為中斷(可選)

int gpiod_to_irq(const struct gpio_desc *desc);

描述:獲取與給定GPIO相對(duì)應(yīng)的IRQ編號(hào)。

返回值:返回IRQ編號(hào)或負(fù)的errno代碼(很可能是因?yàn)樵撎囟℅PIO不能用作IRQ)吟逝。

注意:

  • 使用未使用gpiod_direction_input()設(shè)置為輸入的GPIO帽蝶,或者使用最初不是來(lái)自gpiod_to_irq()的IRQ編號(hào),是錯(cuò)誤的操作块攒。
  • gpiod_to_irq()不允許休眠励稳。

gpiod_to_irq()返回的非錯(cuò)誤值可以傳遞給request_irq()free_irq()

它們通常通過(guò)特定于板的初始化代碼存儲(chǔ)到平臺(tái)設(shè)備的IRQ資源中囱井。

注意驹尼,IRQ觸發(fā)選項(xiàng)是IRQ接口的一部分,例如庞呕, IRQF_TRIGGER_FALLING新翎,

導(dǎo)出到應(yīng)用空間(可選)

drivers/gpio/gpiolib.c

/**
 * gpiod_export - export a GPIO through sysfs
 * @gpio: gpio to make available, already requested
 * @direction_may_change: true if userspace may change gpio direction
 * Context: arch_initcall or later
 *
 * When drivers want to make a GPIO accessible to userspace after they
 * have requested it -- perhaps while debugging, or as part of their
 * public interface -- they may use this routine.  If the GPIO can
 * change direction (some can't) and the caller allows it, userspace
 * will see "direction" sysfs attribute which may be used to change
 * the gpio's direction.  A "value" attribute will always be provided.
 *
 * Returns zero on success, else an error.
 */
int gpiod_export(struct gpio_desc *desc, bool direction_may_change)

gpiod_export提供了用戶層的訪問(wèn),主要用于驅(qū)動(dòng)工程師調(diào)試或者應(yīng)用程序控制千扶。

描述:將該gpio的信息通過(guò)sys文件系統(tǒng)導(dǎo)出料祠,這樣應(yīng)用層可以直接查看狀態(tài)、設(shè)置狀態(tài)等澎羞。

參數(shù)解析:

  • direction_may_change: 用來(lái)標(biāo)記這個(gè)gpio的輸入輸出方向是否可以改變髓绽。

如果該gpio已經(jīng)設(shè)置了輸入或者輸出,那么它的direction_may_change為false妆绞。

兼容

舊的GPIO系統(tǒng)使用基于標(biāo)號(hào)的結(jié)構(gòu)而不是基于描述符顺呕》闩剩可以使用如下兩個(gè)函數(shù)進(jìn)行相互轉(zhuǎn)換:

int desc_to_gpio(const struct gpio_desc *desc);
struct gpio_desc *gpio_to_desc(unsigned gpio);

注意:不能使用一套API的方法釋放另一套API獲取的設(shè)備。

附錄:GPIO子系統(tǒng)其他內(nèi)容

低有效和開(kāi)漏語(yǔ)義

介紹

一般情況下株茶,使用GPIO子系統(tǒng)的開(kāi)發(fā)者并不需要關(guān)心GPIO對(duì)外的實(shí)際電平来涨,因此,gpiod_set_value_xxx()gpiod_set_array_value_xxx() 這樣的函數(shù)都以邏輯值操作启盛。

這些函數(shù)會(huì)將低電平有效的性質(zhì)考慮在內(nèi)蹦掐。也就是說(shuō),低電平有效僵闯,物理值0對(duì)應(yīng)邏輯值的1卧抗。

如果我們事先告知內(nèi)核某一個(gè)GPIO是低電平有效(active_low)這些函數(shù)內(nèi)部會(huì)進(jìn)行處理,就不再需要我們關(guān)心“到底是不是電平1有效還是電平0有效”鳖粟,

例如社裆,如果設(shè)置了GPIO的低電平有效屬性,并且gpiod_set_(array)_value_xxx()傳遞了邏輯值1(“asserted”)向图,則物理線路電平將被驅(qū)動(dòng)為低電平泳秀。

同樣適用于開(kāi)漏或開(kāi)源輸出:它們并不輸出高電平(開(kāi)漏)或低電平(開(kāi)源),它們只是將輸出切換到高阻抗值榄攀。使用者應(yīng)該不需要關(guān)注嗜傅。

有關(guān)的詳細(xì)信息,請(qǐng)參閱driver.txt中關(guān)于開(kāi)漏的細(xì)節(jié)航攒。

總結(jié):

函數(shù)(示例) 線路屬性 物理線路
gpiod_set_raw_value(desc, 0); - 低電平
gpiod_set_raw_value(desc, 0); - 高電平
gpiod_set_value(desc, 0); 默認(rèn)(高電平有效) 低電平
gpiod_set_value(desc, 1); 默認(rèn)(高電平有效) 高電平
gpiod_set_value(desc, 0); 低電平有效 高電平
gpiod_set_value(desc, 1); 低電平有效 低電平
gpiod_set_value(desc, 0); 開(kāi)漏 低電平
gpiod_set_value(desc, 1); 開(kāi)漏 高阻態(tài)
gpiod_set_value(desc, 0); 開(kāi)漏 高阻態(tài)
gpiod_set_value(desc, 1); 開(kāi)漏 高電平

接口

當(dāng)然磺陡,如果你硬是要知道GPIO此時(shí)的電平值(的確需要管理GPIO線路物理狀態(tài)),可以使用下面的一組函數(shù)來(lái)達(dá)到你要的目的漠畜。

但應(yīng)盡可能避免去讀原始值,尤其是系統(tǒng)無(wú)關(guān)的驅(qū)動(dòng)程序坞靶,它們只需要關(guān)心邏輯值憔狞。

下面的一組調(diào)用忽略GPIO的低有效或開(kāi)漏屬性,設(shè)置什么值彰阴,物理值就是什么什么值:

raw-value的意思就是不在乎DTS里面的ACTIVE瘾敢,我set 高電平,就是高電平尿这。

int gpiod_get_raw_value(const struct gpio_desc *desc);
void gpiod_set_raw_value(struct gpio_desc *desc, int value);
int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc);
void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value);
int gpiod_direction_output_raw(struct gpio_desc *desc, int value);

還可以使用以下方法查詢GPIO的低有效屬性:

int gpiod_is_active_low(const struct gpio_desc *desc);

請(qǐng)注意簇抵,這些函數(shù)只能在使用者明白自己在做什么的情況下使用射众;驅(qū)動(dòng)程序一般不應(yīng)該關(guān)心線路物理狀態(tài)或開(kāi)漏語(yǔ)義。

在設(shè)備樹(shù)中設(shè)置低有效

假設(shè)我們?cè)贒TS里面這樣設(shè)置

reset-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_LOW>;

然后我們這樣調(diào)用:

gpiod_set_value_cansleep(gc5025->reset_gpio, 1);

因?yàn)镈TS里面的active 狀態(tài)是 GPIO_ACTIVE_LOW典蜕,所以這個(gè)代碼輸出的是 低電平。

系統(tǒng)喚醒功能

這個(gè)功能與ACPI有關(guān)愉舔。

有關(guān)詳細(xì)信息轩缤,請(qǐng)參閱Documentation/acpi/gpio-properties.txt

在ACPI系統(tǒng)上,GPIO由設(shè)備的_CRS配置對(duì)象列出的GpioIo()/ GpioInt()資源描述火的。這些資源不提供GPIO的連接ID(名稱)躺翻,因此有必要為此目的使用附加機(jī)制。

符合ACPI 5.1或更新版本的系統(tǒng)可能可以提供_DSD配置對(duì)象卫玖,它可以用于提供_CRS中的GpioIo()/ GpioInt()資源描述的特定GPIO的連接ID公你。如果是這種情況,它將由GPIO子系統(tǒng)自動(dòng)處理假瞬。但是陕靠,如果不存在_DSD,則GpioIo()/ GpioInt()資源與GPIOconnection ID之間的映射需要由設(shè)備驅(qū)動(dòng)程序提供脱茉。

附錄:legacy-api

#include <linux/gpio.h>

還有一組用于允許睡眠場(chǎng)景的api沒(méi)有給出剪芥,更多相關(guān)的說(shuō)明可以參考Documentation/gpio/gpio-legacy.txt

使用流程:

  • 申請(qǐng)、釋放:gpio_request琴许、gpio_free
  • 設(shè)置GPIO方向:gpio_direction_input优妙、gpio_direction_output
  • 獲取設(shè)置GPIO值:gpio_get_valuegpio_set_value
  • (可選)設(shè)置為中斷:gpio_to_irq
  • (可選)導(dǎo)出到sys文件系統(tǒng):gpio_export

判斷

/*
 * "valid" GPIO numbers are nonnegative and may be passed to
 * setup routines like gpio_request().  only some valid numbers
 * can successfully be requested and used.
 *
 * Invalid GPIO numbers are useful for indicating no-such-GPIO in
 * platform data and other tables.
 */
static inline bool gpio_is_valid(int number)
{
    return number >= 0 && number < ARCH_NR_GPIOS;
}

描述:來(lái)判斷獲取到的gpio號(hào)是否是有效的胚宦,只有有效的gpio號(hào)脊凰,才能向內(nèi)核中進(jìn)行申請(qǐng)使用,因此箭券,當(dāng)我們從設(shè)備樹(shù)的設(shè)備節(jié)點(diǎn)獲取到gpio號(hào)净捅,可以使用該函數(shù)進(jìn)行判斷是否有效。

參數(shù)解析:

  • numb:需要判斷的GPIO號(hào)辩块。

返回值:合法為1蛔六,否則為0。

申請(qǐng)废亭、釋放

/* Always use the library code for GPIO management calls,
 * or when sleeping may be involved.
 */
extern int gpio_request(unsigned gpio, const char *label);
extern void gpio_free(unsigned gpio);

/**
 * struct gpio - a structure describing a GPIO with configuration
 * @gpio:    the GPIO number
 * @flags:    GPIO configuration as specified by GPIOF_*
 * @label:    a literal description string of this GPIO
 */
struct gpio {
    unsigned    gpio;
    unsigned long    flags;
    const char    *label;
};

int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
int gpio_request_array(struct gpio *array, size_t num);
void gpio_free_array(const struct gpio *array, size_t num);

/* CONFIG_GPIOLIB: bindings for managed devices that want to request gpios */
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
int devm_gpio_request_one(struct device *dev, unsigned gpio,
              unsigned long flags, const char *label);
void devm_gpio_free(struct device *dev, unsigned int gpio);

gpio_request為例液兽,gpio_request_one桃漾、gpio_request_array是它的擴(kuò)展,devm_為前綴的是gpio devres機(jī)制的實(shí)現(xiàn)恋追。

描述:請(qǐng)求一個(gè)/一組gpio。

參數(shù)解析:

  • gpio:gpio號(hào)撕彤,可以通過(guò)sdk開(kāi)發(fā)包說(shuō)明文檔查看羹铅,或者查看設(shè)備樹(shù)文件,也可以在gpio chip驅(qū)動(dòng)的實(shí)現(xiàn)中找到焊切。
  • flags:可以指定GPIOF_OPEN_DRAINGPIOF_OPEN_SOURCE牵祟、GPIOF_DIR_IN咕晋、GPIOF_EXPORT等標(biāo)志
    • 如果指定了GPIOF_DIR_IN滓玖,那么后面就不需要自己再額外調(diào)用gpio_direction_input或者gpio_direction_output了翩肌,
    • 如果指定了GPIOF_EXPORT念祭,后面就不需要自己調(diào)用gpio_export了。
  • label:向系統(tǒng)中申請(qǐng)GPIO使用的標(biāo)簽站玄,類似于GPIO的名稱
  • array,num:是gpio_request_array對(duì)gpio_request_one的封裝晾剖,用于處理同時(shí)申請(qǐng)多個(gè)gpio的情形。
  • dev:帶有devm_前綴雕什,用于帶設(shè)備資源管理版本的函數(shù),因此在使用上面的函數(shù)時(shí)偿警,需要指定設(shè)備的struct device指針螟蒸,生命周期與設(shè)備相同。

返回值:成功返回0诵原。

意義gpio_request主要做了以下動(dòng)作:

  1. 檢查是否已經(jīng)被申請(qǐng)蔓纠,沒(méi)有的話腿倚,標(biāo)記為已申請(qǐng)
  2. 填充label到該pin數(shù)據(jù)結(jié)構(gòu),用于debug
  3. 如果chip driver提供了request回調(diào)懈叹,調(diào)用它
  4. 如果chip driver提供了get_direction回調(diào),調(diào)用它墨状,通過(guò)它更新pin數(shù)據(jù)結(jié)構(gòu),標(biāo)明gpio方向

用法舉例:

static struct gpio leds_gpios[] = {
    { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
    { 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
    { 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
    { 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
    { ... },
};

err = gpio_request_one(31, GPIOF_IN, "Reset Button");
if (err)
    ...;

err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
if (err)
    ...;

gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios)); 

設(shè)置方向

//設(shè)置gpio方向?yàn)檩斎?輸出
gpio_direction_input 或者gpio_direction_output        ---------<2>  ;

static inline int gpio_direction_input(unsigned gpio)
{
    return gpiod_direction_input(gpio_to_desc(gpio));
}

static inline int gpio_direction_output(unsigned gpio, int value)
{
    return gpiod_direction_output_raw(gpio_to_desc(gpio), value);
}

描述:當(dāng)我們使用gpio_request()函數(shù)族向系統(tǒng)中申請(qǐng)了GPIO資源后镐确,可以使用上面的函數(shù)進(jìn)行GPIO的方向設(shè)置:

  • 函數(shù)gpio_direction_input()用來(lái)設(shè)置GPIO的方向?yàn)檩斎?/li>
  • 函數(shù)gpio_direction_output()用來(lái)設(shè)置GPIO的方向?yàn)檩敵觯⑶彝ㄟ^(guò)value值可以設(shè)置輸出的電平息堂。

意義: gpio_direction_input或者gpio_direction_output主要是回調(diào)gpio chip driver提供的direction_input或者direction_output來(lái)設(shè)置該gpio寄存器為輸入、輸出持隧。

導(dǎo)出

// include/asm-generic/gpio.h

/*
 * A sysfs interface can be exported by individual drivers if they want,
 * but more typically is configured entirely from userspace.
 */

static inline int gpio_export(unsigned gpio, bool direction_may_change)
{
    return gpiod_export(gpio_to_desc(gpio), direction_may_change);
}

gpio_export提供了用戶層的訪問(wèn),主要用于驅(qū)動(dòng)工程師調(diào)試或者應(yīng)用程序控制呀狼。

描述:將該gpio的信息通過(guò)sys文件系統(tǒng)導(dǎo)出,這樣應(yīng)用層可以直接查看狀態(tài)、設(shè)置狀態(tài)等窟勃。

參數(shù)解析:

  • direction_may_change: 用來(lái)標(biāo)記這個(gè)gpio的輸入輸出方向是否可以改變眷昆。

如果該gpio已經(jīng)設(shè)置了輸入或者輸出,那么它的direction_may_change為false帅刊。

使用

static inline int gpio_get_value(unsigned int gpio)
{
    return __gpio_get_value(gpio);
}

static inline void gpio_set_value(unsigned int gpio, int value)
{
    __gpio_set_value(gpio, value);
}

描述:當(dāng)我們將GPIO的方向設(shè)置為輸入時(shí)赖瞒,可以使用上面的函數(shù)gpio_get_value()來(lái)獲取當(dāng)前的IO口電平值,當(dāng)GPIO的方向設(shè)置為輸出時(shí)抡爹,使用函數(shù)gpio_set_value()可以設(shè)置IO口的電平值。

申請(qǐng)中斷

static inline int gpio_to_irq(unsigned int gpio)
{
    return __gpio_to_irq(gpio);
}

描述:用于獲取該gpio對(duì)應(yīng)的中斷號(hào)泵殴,這個(gè)需要設(shè)備樹(shù)里的該gpio節(jié)點(diǎn)描述使用哪個(gè)中斷號(hào)

并不是所有的gpio都可以觸發(fā)中斷的。

意義:回調(diào)gpio chip driver提供的to_irq弦叶。

例子

reference:https://www.cnblogs.com/Cqlismy/p/11891789.html

設(shè)備樹(shù)(高通msm平臺(tái)):

dev_gpio {
    status = "okay";
    compatible = "dev-gpio";
    label = "test_gpio";
    gpios = <&msm_gpio 68 0>;
};

驅(qū)動(dòng)程序:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/sysfs.h>

struct gpio_platform_data {
    const char *label;
    unsigned int gpio_num;
    enum of_gpio_flags gpio_flag;
};

struct gpio_drvdata {
    struct gpio_platform_data *pdata;

    bool gpio_state;
};

static ssize_t ctrl_show(struct device *dev, 
                         struct device_attribute *attr, char *buf)
{
    struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    int ret;

    if (ddata->gpio_state)
        ret = snprintf(buf, PAGE_SIZE - 2, "%s", "enable");
    else
        ret = snprintf(buf, PAGE_SIZE - 2, "%s", "disable");

    buf[ret++] = '\n';
    buf[ret] = '\0';

    return ret;
}

static ssize_t ctrl_store(struct device *dev,
                          struct device_attribute *attr, const char *buf, size_t count)
{
    struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    bool state = ddata->gpio_state;

    if (!strncmp(buf, "enable", strlen("enable"))) {
        if (!state) {
            gpio_set_value(ddata->pdata->gpio_num, !state);
            ddata->gpio_state = !state;
            goto ret;
        }
    } else if (!strncmp(buf, "disable", strlen("disable"))) {
        if (state) {
            gpio_set_value(ddata->pdata->gpio_num, !state);
            ddata->gpio_state = !state;
            goto ret;
        }
    }

    return 0;

    ret:
    return strlen(buf);
}
static DEVICE_ATTR(ctrl, 0644, ctrl_show, ctrl_store);

static ssize_t gpio_show(struct device *dev,
                         struct device_attribute *attr, char *buf)
{
    struct gpio_drvdata *ddata = dev_get_drvdata(dev);
    int ret;

    ret = snprintf(buf, PAGE_SIZE - 2, "gpio-number: GPIO_%d",
                   ddata->pdata->gpio_num - 911);
    buf[ret++] = '\n';
    buf[ret] = '\0';

    return ret;
}
static DEVICE_ATTR(gpio, 0444, gpio_show, NULL);

static struct attribute *gpio_attrs[] = {
    &dev_attr_ctrl.attr,
    &dev_attr_gpio.attr,
    NULL
};

static struct attribute_group attr_grp = {
    .attrs = gpio_attrs,
};

static struct gpio_platform_data *
    gpio_parse_dt(struct device *dev)
{
    int ret;
    struct device_node *np = dev->of_node;
    struct gpio_platform_data *pdata;

    pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
    if (!pdata) {
        dev_err(dev, "failed to alloc memory of platform data\n");
        return NULL;
    }

    ret = of_property_read_string(np, "label", &pdata->label);
    if (ret) {
        dev_err(dev, "failed to read property of lable\n");
        goto fail;
    }

    pdata->gpio_num = of_get_named_gpio_flags(np, "gpios",
                                              0, &pdata->gpio_flag);
    if (pdata->gpio_num < 0) {
        dev_err(dev, "invalid gpio number %d\n", pdata->gpio_num);
        ret = pdata->gpio_num;
        goto fail;
    }

    return pdata;

    fail:
    kfree(pdata);
    return ERR_PTR(ret);
}

static int gpio_probe(struct platform_device *pdev)
{
    struct gpio_drvdata *ddata;
    struct gpio_platform_data *pdata;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    int ret;

    printk("[%s]==========gpio_probe start==========\n", __func__);

    if (!np) {
        dev_err(dev, "failed to find device node of gpio device\n");
        return -ENODEV;
    }

    ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
    if (!ddata) {
        dev_err(dev, "failed to alloc memory for driver data\n");
        return -ENOMEM;
    }

    pdata = gpio_parse_dt(dev);
    if (IS_ERR(pdata)) {
        dev_err(dev, "failed to parse device node\n");
        ret = PTR_ERR(pdata);
        goto fail1;
    }
    /* gpio初始化 */
    if (gpio_is_valid(pdata->gpio_num)) {
        /* 申請(qǐng)gpio資源 */
        ret = gpio_request(pdata->gpio_num, pdata->label);
        if (ret) {
            dev_err(dev, "failed to request gpio number %d\n",
                    pdata->gpio_num);
            goto fail2;
        }
        /* 設(shè)置gpio的方向(輸出) */
        ret = gpio_direction_output(pdata->gpio_num, 0);
        if (ret) {
            dev_err(dev, "failed to set gpio direction for output\n");
            goto fail3;
        }
        /* 在sysfs中導(dǎo)出gpio(方向不能改變) */
        ret = gpio_export(pdata->gpio_num, false);
        if (ret) {
            dev_err(dev, "failed to export gpio %d\n", pdata->gpio_num);
            goto fail3;
        }
        /* 設(shè)置gpio電平值(高電平) */
        gpio_set_value(pdata->gpio_num, 1);
    }

    ddata->gpio_state = false;
    ddata->pdata = pdata;
    platform_set_drvdata(pdev, ddata);

    ret = sysfs_create_group(&dev->kobj, &attr_grp);
    if (ret) {
        dev_err(dev, "failed to create sysfs files\n");
        goto fail3;
    }

    printk("[%s]==========gpio_probe over==========\n", __func__);
    return 0;

    fail3:
    gpio_free(pdata->gpio_num);
    fail2:
    kfree(pdata);
    fail1:
    kfree(ddata);
    return ret;
}

static int gpio_remove(struct platform_device *pdev)
{
    struct gpio_drvdata *ddata = platform_get_drvdata(pdev);
    struct gpio_platform_data *pdata = ddata->pdata;

    sysfs_remove_group(&pdev->dev.kobj, &attr_grp);
    /* 釋放已經(jīng)申請(qǐng)的gpio資源 */
    if (gpio_is_valid(pdata->gpio_num))
        gpio_free(pdata->gpio_num);

    kfree(pdata);
    pdata = NULL;

    kfree(ddata);
    ddata = NULL;

    return 0;
}

static struct of_device_id device_match_table[] = {
    { .compatible = "dev-gpio",},
    { },
};
MODULE_DEVICE_TABLE(of, device_match_table);

static struct platform_driver dev_gpio_driver = {
    .probe = gpio_probe,
    .remove = gpio_remove,
    .driver = {
        .name = "dev-gpio",
        .owner = THIS_MODULE,
        .of_match_table = device_match_table,
    },
};

module_platform_driver(dev_gpio_driver);

MODULE_AUTHOR("HLY");
MODULE_LICENSE("GPL v2");

當(dāng)驅(qū)動(dòng)模塊加載的時(shí)候蜓耻,由于嵌入了platform_driver這個(gè)驅(qū)動(dòng)框架中,所以看起來(lái)復(fù)雜一點(diǎn)芦鳍,實(shí)際上根據(jù)上面的注釋進(jìn)行參考即可:

  • 需要獲取要使用的GPIO號(hào),然后需要向系統(tǒng)申請(qǐng)使用GPIO資源
  • 資源申請(qǐng)成功后菲宴,我們需要設(shè)置GPIO的方向(輸入或者輸出),
  • 此外谣蠢,還能使用gpio_export()函數(shù)在sysfs中導(dǎo)出GPIO,導(dǎo)出的好處在于可以方便地debug代碼谈喳,
  • 當(dāng)驅(qū)動(dòng)模塊卸載時(shí)婿禽,需要將已經(jīng)申請(qǐng)的GPIO資源進(jìn)行釋放掉

另外,在設(shè)備節(jié)點(diǎn)中導(dǎo)出了ctrl和gpio屬性文件谈宛,便可以很方便地在應(yīng)用層進(jìn)行設(shè)備的GPIO控制了次哈。

本文地址:https://www.cnblogs.com/schips/p/linux_subsystem_using_gpio_ss.html

若在頁(yè)首無(wú)特別聲明,本篇文章由 Schips 經(jīng)過(guò)整理后發(fā)布吆录。
博客地址:https://www.cnblogs.com/schips/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市琼牧,隨后出現(xiàn)的幾起案子恢筝,更是在濱河造成了極大的恐慌,老刑警劉巖撬槽,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薪者,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)铺根,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門精置,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事招刨』丫螅” “怎么了孽查?”我有些...
    開(kāi)封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵禽绪,是天一觀的道長(zhǎng)樱拴。 經(jīng)常有香客問(wèn)我翻具,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任昧旨,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己篮幢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布晕鹊。 她就那樣靜靜地躺著同规,像睡著了一般匣吊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耸成,一...
    開(kāi)封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天约急,我揣著相機(jī)與錄音,去河邊找鬼苗分。 笑死厌蔽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的摔癣。 我是一名探鬼主播奴饮,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼择浊!你這毒婦竟也來(lái)了戴卜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤琢岩,失蹤者是張志新(化名)和其女友劉穎投剥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體担孔,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡江锨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了攒磨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泳桦。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖娩缰,靈堂內(nèi)的尸體忽然破棺而出灸撰,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布浮毯,位于F島的核電站完疫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏债蓝。R本人自食惡果不足惜壳鹤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饰迹。 院中可真熱鬧芳誓,春花似錦、人聲如沸啊鸭。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赠制。三九已至赂摆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钟些,已是汗流浹背烟号。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留政恍,地道東北人汪拥。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像篙耗,于是被迫代替她去往敵國(guó)和親喷楣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 一鹤树、前言 在嵌入式中,GPIO作為一個(gè)常用的信息輸入輸出手段經(jīng)常被用在各種場(chǎng)合逊朽,那么今天本文就來(lái)簡(jiǎn)單地說(shuō)一下嵌入式...
    wipping的技術(shù)小棧閱讀 4,277評(píng)論 0 0
  • 下面的內(nèi)容均在imx6平臺(tái)上舉例罕伯,這一次分析希望將整個(gè)GPIO子系統(tǒng)的所有細(xì)節(jié)整理清晰。 第一篇從gpiolib入...
    天熱了該裸奔了閱讀 4,230評(píng)論 0 5
  • 簡(jiǎn)介 GPIO, 全稱 General-Purpose Input/Output(通用輸入輸出),是一種軟件運(yùn)行期...
    shaniadolphin閱讀 19,630評(píng)論 0 3
  • 對(duì)于gpio的應(yīng)用其實(shí)會(huì)在很多地方岛蚤,最常用的就是led和key邑狸,我們也可以使用類似單片機(jī)的寫法,去直接讀寫寄存器來(lái)...
    Creator_Ly閱讀 521評(píng)論 0 0
  • 久違的晴天涤妒,家長(zhǎng)會(huì)单雾。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)硅堆。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,515評(píng)論 16 22