linux設備驅動程序--gpio控制

gpio驅動程序

上一章節linux設備驅動程序--建立設備節點章節主要介紹了linux字符設備驅動程序的框架,從這一章節開始咱們講解各類外設的控制,包括gpio,i2c,dma等等,既然是外設,那就涉及到具體的目標板,博主在這裏使用的開發板是開源平臺beagle bone green,內核版本爲4.14.html

今天咱們來說解gpio的設備驅動程序。node

gpio相關的庫函數

爲了linux的可移植性和統一,linux提供一套函數庫供用戶使用,內容涵蓋了GPIO/I2C/SPI等外設的控制,關於函數庫能夠參考官方網站linux

這一章咱們須要用到gpio相關的庫函數:api

//檢查gpio number是否合法
int gpio_to_irq(unsigned gpio)
//根據gpio number申請gpio資源,label爲gpio名稱  
int gpio_request(unsigned gpio, const char *label)
//釋放gpio 資源
void gpio_free(unsigned gpio)
//設置gpio 爲輸入
int gpio_direction_input(unsigned gpio)
//設置gpio 爲輸出
int gpio_direction_output(unsigned gpio, int value)
//設置gpio的值
gpio_set_value(unsigned gpio, int value)
//設置gpio的消抖時間,主要用於按鍵消抖
int gpio_set_debounce(unsigned gpio, unsigned debounce)
//獲取gpio對應的中斷線路
int gpio_to_irq(unsigned gpio)
//gpio中斷,當產生中斷時調用handle函數
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)

linux gpio設備驅動程序

在前面的章節咱們知道了怎麼寫一個字符設備驅動程序的框架,如今咱們就只須要往框架裏面添加相應的處理代碼就能夠了。框架

如今嘗試實現這樣的需求:函數

  • 在beagle bone green開發板上的gpio上鍊接一個指示燈
  • 當用戶打開/dev目錄下的設備文件時,完成對gpio的初始化
  • 往文件中寫入OPEN實現打開燈,往文件中寫入CLOSE關閉燈
  • 關閉設備文件時,釋放gpio資源

下面就是咱們實現的代碼,gpio_led_control.c:測試

#include <linux/init.h>  
#include <linux/module.h>
#include <linux/device.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/string.h>


MODULE_AUTHOR("Downey");
MODULE_LICENSE("GPL");

static int majorNumber = 0;
/*Class 名稱,對應/sys/class/下的目錄名稱*/
static const char *CLASS_NAME = "led_control_class";
/*Device 名稱,對應/dev下的目錄名稱*/
static const char *DEVICE_NAME = "led_control_demo";

static int led_control_open(struct inode *node, struct file *file);
static ssize_t led_control_read(struct file *file,char *buf, size_t len,loff_t *offset);
static ssize_t led_control_write(struct file *file,const char *buf,size_t len,loff_t* offset);
static int led_control_release(struct inode *node,struct file *file);

#define LED_PIN   26 
static int gpio_status;


static char recv_msg[20];

static struct class *led_control_class = NULL;
static struct device *led_control_device = NULL;

/*File opertion 結構體,咱們經過這個結構體創建應用程序到內核之間操做的映射*/
static struct file_operations file_oprts = 
{
    .open = led_control_open,
    .read = led_control_read,
    .write = led_control_write,
    .release = led_control_release,
};


static void gpio_config(void)
{
    if(!gpio_is_valid(LED_PIN)){
        printk(KERN_ALERT "Error wrong gpio number\n");
        return ;
    }
    gpio_request(LED_PIN,"led_ctr");
    gpio_direction_output(LED_PIN,1);
    gpio_set_value(LED_PIN,1);
    gpio_status = 1;
}


static void gpio_deconfig(void)
{
    gpio_free(LED_PIN);
}

static int __init led_control_init(void)
{
    printk(KERN_ALERT "Driver init\r\n");
    /*註冊一個新的字符設備,返回主設備號*/
    majorNumber = register_chrdev(0,DEVICE_NAME,&file_oprts);
    if(majorNumber < 0 ){
        printk(KERN_ALERT "Register failed!!\r\n");
        return majorNumber;
    }
    printk(KERN_ALERT "Registe success,major number is %d\r\n",majorNumber);

    /*以CLASS_NAME建立一個class結構,這個動做將會在/sys/class目錄建立一個名爲CLASS_NAME的目錄*/
    led_control_class = class_create(THIS_MODULE,CLASS_NAME);
    if(IS_ERR(led_control_class))
    {
        unregister_chrdev(majorNumber,DEVICE_NAME);
        return PTR_ERR(led_control_class);
    }

    /*以DEVICE_NAME爲名,參考/sys/class/CLASS_NAME在/dev目錄下建立一個設備:/dev/DEVICE_NAME*/
    led_control_device = device_create(led_control_class,NULL,MKDEV(majorNumber,0),NULL,DEVICE_NAME);
    if(IS_ERR(led_control_device))
    {
        class_destroy(led_control_class);
        unregister_chrdev(majorNumber,DEVICE_NAME);
        return PTR_ERR(led_control_device);
    }
    printk(KERN_ALERT "led_control device init success!!\r\n");

    return 0;
}

/*當用戶打開這個設備文件時,調用這個函數*/
static int led_control_open(struct inode *node, struct file *file)
{
    printk(KERN_ALERT "GPIO init \n");
    gpio_config();
    return 0;
}

/*當用戶試圖從設備空間讀取數據時,調用這個函數*/
static ssize_t led_control_read(struct file *file,char *buf, size_t len,loff_t *offset)
{
    int cnt = 0;
    /*將內核空間的數據copy到用戶空間*/
    cnt = copy_to_user(buf,&gpio_status,1);
    if(0 == cnt){
        return 0;
    }
    else{
        printk(KERN_ALERT "ERROR occur when reading!!\n");
        return -EFAULT;
    }
    return 1;
}

/*當用戶往設備文件寫數據時,調用這個函數*/
static ssize_t led_control_write(struct file *file,const char *buf,size_t len,loff_t *offset)
{
    /*將用戶空間的數據copy到內核空間*/
    int cnt = copy_from_user(recv_msg,buf,len);
    if(0 == cnt){
        if(0 == memcmp(recv_msg,"on",2))
        {
            printk(KERN_INFO "LED ON!\n");
            gpio_set_value(LED_PIN,1);
            gpio_status = 1;
        }
        else
        {
            printk(KERN_INFO "LED OFF!\n");
            gpio_set_value(LED_PIN,0);
            gpio_status = 0;
        }
    }
    else{
        printk(KERN_ALERT "ERROR occur when writing!!\n");
        return -EFAULT;
    }
    return len;
}

/*當用戶打開設備文件時,調用這個函數*/
static int led_control_release(struct inode *node,struct file *file)
{
    printk(KERN_INFO "Release!!\n");
    gpio_deconfig();
    return 0;
}

/*銷燬註冊的全部資源,卸載模塊,這是保持linux內核穩定的重要一步*/
static void __exit led_control_exit(void)
{
    device_destroy(led_control_class,MKDEV(majorNumber,0));
    class_unregister(led_control_class);
    class_destroy(led_control_class);
    unregister_chrdev(majorNumber,DEVICE_NAME);
}

module_init(led_control_init);
module_exit(led_control_exit);

在init函數中對gpio進行相應的初始化,當用戶在文件進行寫操做時,根據傳入的參數來判斷打開或者關閉燈,在用戶關閉文件時釋放資源。網站

爲此,須要添加一個用戶程序來對設備文件進行讀寫,gpio_led_contro_user.c:調試

#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
static char buf[256] = {1};
int main(int argc,char *argv[])
{
    int fd = open("/dev/led_control_demo",O_RDWR);
    if(fd < 0)
    {
        perror("Open file failed!!!\r\n");
        return -1;
    }
    while(1){
        printf("Please input <on> or <off>:\n");
        scanf("%s",buf);
        if(strlen(buf) > 3){
            printf("Ivalid input!\n");
        }
        else
        {
            int ret = write(fd,buf,strlen(buf));
            if(ret < 0){
                perror("Failed to write!!");
        }
    }
    }
    close(fd);
    return 0;
}

當用戶程序執行時,用戶程序一直獲取用戶的輸入,根據用戶輸入"on"或者"off",而後將其寫入設備文件,觸發系統調用,設備文件根據設備號找到內核中相應的file_operation結構體,相應write函數被調用,執行相應的點燈操做。code

編譯加載運行

鏈接led

在例程中咱們使用了gpio26做爲led引腳,因此咱們須要鏈接一個led(視狀況加一個電阻)到gpio26引腳上,具體引腳位置須要自行查看開發板手冊。

查看log

首先咱們能夠打開兩個終端窗口,一個爲查看log信息,一個用來進行相關指令操做。

編譯加載模塊

在查看log信息終端,咱們須要循環查看/var/log/kern.log文件:

tail -f /var/log/kern.log

這樣內核printk()輸出的信息就能夠在這裏看到了,方便進行調試。

而後須要編譯內核驅動文件gpio_led_control.c,先修改Makefile(這裏就再也不展開,能夠參考前面章節)。

而後編譯:

make

編譯成功,在本目錄下生成相應的.ko文件,加載內核文件:

sudo insmod gpio_led_control.ko

查看log終端會顯示相應的信息。

編譯運行用戶程序

再編譯用戶文件:

gcc gpio_led_contro_user.c -o user

執行用戶文件:

sudo ./user

窗口輸出:

Please input <on> or <off>:

這是咱們輸入on或者off就能夠控制led的亮滅了(其實就是控制gpio的高低電平)。

將gpio映射到/sys目錄下

對於gpio而言,linux驅動庫實現了將gpio引腳信息映射到/sys目錄下,用戶能夠很方便地直接經過相關文件的操做來讀寫gpio的值,達到控制gpio的目的,這個接口API原型爲:

//第一個參數表示導出的引腳,第二個參數表示是否可改變IO的輸出方向。
int gpio_export(unsigned gpio, bool direction_may_change)

固然,相對應的釋放函數爲
void gpio_unexport(unsigned gpio)

本身動手試試

在gpio初始化的函數中添加這個接口,在加載完成以後查看/sys/class/gpio/目錄下是否有相應的gpio$num(這裏是gpio26)文件(須要注意的是,在上例中,當用戶程序打開設備時才進行gpio的初始化,關閉文件時釋放gpio的資源,因此須要打開文件再操做)。

若是有相應的文件,試試下面的指令:

echo 0 > /sys/class/gpio/gpio26/value
echo 1 > /sys/class/gpio/gpio26/value

若是你有興趣也能夠研究研究裏面其餘的文件,這裏不過多描述,留做家庭做業。

gpio中斷的實現

上文實現了經過操做設備文件來控制開發板的gpio,接下來咱們看看gpio中斷的實現,一個按鍵點燈程序,當加載模塊後,按鍵反轉燈的狀態:

  • 添加gpio按鍵中斷代碼。
  • 再也不須要建立設備文件節點,直接經過按鍵來操做led。

接下來就是按鍵中斷的示例代碼 gpio_key_led_control.c:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>

#define BUTTON     27
#define LED        26

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Downey");
MODULE_DESCRIPTION("Gpio irq test!!\n");

int button_irq_num = 0;
bool led_status = 1;


static  irqreturn_t button_irq_handle(int irq, void *dev_id)
{
    printk(KERN_INFO "Enter irq!!\n");
    if(0 == led_status)
    {
        gpio_set_value(LED,1);
        led_status = 1;
    }
    else
    {
        led_status = 0;
        gpio_set_value(LED,0);
    }
    return (irqreturn_t)IRQ_HANDLED;
}


static int gpio_config(void)
{
    int ret = 0;
    if(!gpio_is_valid(BUTTON) || !gpio_is_valid(LED))
    {
        printk(KERN_ALERT "Gpio is invalid!\n");
        return -ENODEV;
    }
    gpio_request(BUTTON,"button");
    gpio_direction_input(BUTTON);
    gpio_set_debounce(BUTTON,20);

    button_irq_num = gpio_to_irq(BUTTON);
    printk(KERN_INFO "NUM = %d",button_irq_num);
    ret = request_irq(button_irq_num,
                            (irq_handler_t)button_irq_handle,
                            IRQF_TRIGGER_RISING,
                            "BUTTON1",
                            NULL);
    printk(KERN_INFO "GPIO_TEST: The interrupt request result is: %d\n", ret);
    
    gpio_request(LED,"LED");
    gpio_direction_output(LED,1);
    gpio_set_value(LED,1);
    return 0;
}


static void gpio_deconfig(void)
{
    gpio_direction_output(LED,0);
    free_irq(button_irq_num,NULL);
    gpio_free(BUTTON);
    gpio_free(LED);
}

int __init gpio_irq_init(void)
{
    gpio_config();
    printk(KERN_INFO "gpio_irq_init!\n");
    return 0;
}


void __exit gpio_irq_exit(void)
{
    gpio_deconfig();
    printk(KERN_INFO "gpio_irq_exit!\n");
}

module_init(gpio_irq_init);
module_exit(gpio_irq_exit);

編譯加載執行

鏈接led和按鍵

首先爲了試驗,咱們須要將按鍵鏈接在gpio27上,將led鏈接在gpio26上。(視狀況添加電阻)

編譯加載

修改Makefile,而後編譯:

make

加載:
sudo insmod gpio_key_led_control.ko

測試

這部分例程並無註冊文件接口,而是直接在內核中經過硬件中斷檢測是否有按鍵時間產生,來執行點亮和熄滅指示燈的操做。

如今就能夠測試按鍵是否有效了,若是出現什麼問題,可能須要調試代碼,別忘了根據printk()輸出的log信息來判斷錯誤。

好了,關於linux驅動程序-gpio控制就到此爲止啦,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言

原創博客,轉載請註明出處!

祝各位早日實現項目叢中過,bug不沾身.

相關文章
相關標籤/搜索