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


gpio驅動程序

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

今天我們來講解gpio的設備驅動程序。

gpio相關的庫函數

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

這一章我們需要用到gpio相關的庫函數:

//檢查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函數被調用,執行相應的點燈操作。

編譯加載運行

連接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不沾身.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM