嵌入式linux與物聯網進階之路五:嵌入式驅動方式點亮LED

簡化的驅動框架

話說前面章節講到了如何利用嵌入式驅動開發的方式進行驅動開發。由於其學習路線相比於裸機開發來說,上手難度稍微大一些,而且代碼量也相對來說較多,所以對剛上手的人來說是頗有難度的。本章節,我們將以一個類似於Hello World點燈的例子,來講解在linux下如何進行內核驅動的開發。

工欲善其事,必先利其器,開始之前,我們需要先將驅動開發用到的主體框架搭建一下,這裏由於上節講過,我這裏直接貼上來:

#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/string.h>

#define LED_MAJOR 200
#define LED_NAME  "LED"

static int led_open(struct inode *inode, struct file *filep){
    printk("GPIO init \n");
    return 0;
}

static int led_write(struct file *filep, const char __user *buf, size_t count, loff_t *ppos){
    return count;
}

static int led_release(struct inode *inode, struct file *filep){
    printk("Release !! \n");
    return 0;
}


static const struct file_operations led_fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .write   = led_write,
    .release = led_release,
};

static int __init led_init(void){

    printk("led control device init success! \r\n");

    return 0;
}

static void __exit led_exit(void){
    printk(" led_exit \r\n");
}

module_init(led_init);
module_exit(led_exit);

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

可以看到,整個驅動開發框架無非是如下幾個東西,首先是module_init和module_exit函數,之後就是file_operations結構體,結構體中有對文件的讀,寫,打開,關閉等等,我們只需要把我們用到的操作去實現就行了。由於這部分整體比較固化,所以這裏我不再贅述了。

由於在荔枝派中,我選擇了A1口作爲我的控制口,所以我先嚐試利用GPIO手動控制了一下,整體控制流程如下:

1、 進入sys/class/gpio目錄下
2、 執行echo 1 > export命令,可以看出來創建了gpio1的文件夾
3、 進入gpio1文件夾,cat direction查看其值爲in,通過vi direction,將其值改爲out後, wq保存。
4、 運行 echo 1 > value 命令,則可以設置A1口高電平,設置echo 0 > value 命令,則可以設置A1口低電平。

通過如上步驟,我們發現,我們可以控制板子上的LED燈的亮滅了。之所以能這麼控制,是因爲我們編譯生成的根文件中,包含了對GPIO的支持,所以使得我們很容易的進行控制。這裏的內容,之後我們會用到,先繼續。

LED驅動編碼

接下來,我們就開始針對剛纔的驅動框架模板,來慢慢的填寫我們的內容吧。

#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/string.h>

/* 主設備號 */
#define LED_MAJOR 200
/* 設備名稱 */
#define LED_NAME  "LED"
/* 操作的引腳 */
#define LED_PIN 1
/* 內核態用戶態交互緩衝 */
static char recv_msg[20];

/* 自動創建設備樹:類 */
static struct class *led_control_class = NULL;
/* 自動創建設備樹:設備 */
static struct device *led_control_device = NULL;

/* led初始化 */
static int led_open(struct inode *inode, struct file *filep){
    printk("GPIO init \n");
    /* 校驗 */
    if(!gpio_is_valid(LED_PIN)){
        printk("Error wrong gpio number !!\n");
        return;
    }
    gpio_request(LED_PIN, "led_ctr");
    /* 設置direction爲輸出 */
    gpio_direction_output(LED_PIN,1);
    /* 初始置爲高電平 */
    gpio_set_value(LED_PIN,1);
    return 0;
}

/* 引腳電平寫入 */
static int led_write(struct file *filep, const char __user *buf, size_t count, loff_t *ppos){
    int cnt = _copy_from_user(recv_msg, buf, count);
    if(0 == cnt){
        /* 如果用戶輸入了on標記,代表打開 */    
        if(0 == memcmp(recv_msg, "on", 2)){
            printk("LED on! \n");
            gpio_set_value(LED_PIN, 1);
        }
        /* 如果用戶輸入了其他標記,代表關閉 */
        else{
            printk("LED off! \n");
            gpio_set_value(LED_PIN, 0);
        }
    }else{
        printk("ERROR occur when writing!!\n");
        return -EIO;
    }
    return count;
}

/* led註銷 */
static int led_release(struct inode *inode, struct file *filep){
    printk("Release !! \n");
    gpio_free(LED_PIN);
    return 0;
}

/* 設備文件操作對象 */
static const struct file_operations led_fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .write   = led_write,
    .release = led_release,
};

/* 設備操作的初始化  */
static int __init led_init(void){

    int ret = 0;

    //1. 註冊字符設備
    ret = register_chrdev(LED_MAJOR, LED_NAME,&led_fops);
    if(ret <0){
        printk("register fail!\r\n");
        return -EIO;
    }
    printk("register success, major number is %d \r\n",ret);
   
    /* 2. 自動註冊設備樹:首先創建類 */
    led_control_class = class_create(THIS_MODULE, LED_NAME);
    if(IS_ERR(led_control_class)){
        unregister_chrdev(LED_MAJOR, LED_NAME);
        return -EIO;
    }

    /* 3. 自動註冊設備樹:基於類創建設備 */
    led_control_device = device_create(led_control_class, NULL, MKDEV(LED_MAJOR,0), NULL,LED_NAME);
    if(IS_ERR(led_control_device)){
        class_destroy(led_control_class);
        unregister_chrdev(LED_MAJOR, LED_NAME);
        return -EIO;
    }

    printk("led control device init success! \r\n");

    return 0;
}

/* 設備註銷 */
static void __exit led_exit(void){
    printk(" led_exit \r\n");
    device_destroy(led_control_class, MKDEV(LED_MAJOR,0));
    class_unregister(led_control_class);
    class_destroy(led_control_class);
    unregister_chrdev(LED_MAJOR,LED_NAME);
}

module_init(led_init);
module_exit(led_exit);

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

  

由於代碼部分,我做了詳盡的註釋,所以這裏不再過多的解釋了。這裏唯一需要注意的幾點就是:

1. 用戶態和內核態的數據交換,是需要通過copy_from_user或者copy_to_user來進行。

2. 設備初始化,需要先利用register_chrdev來註冊字符設備,之後才能進行設備的創建操作。

3. 自動註冊設備樹,需要先進行class_create,之後才能進行device_create. 

4. insmod和rmmod命令的執行,分別對應led_init函數和led_exit函數。

Makefile製作

既然代碼寫好了,我們這裏就需要來編譯生成ko文件纔行。我們來手寫一個Makefile文件。

KERNELDIR := /home/scy/linux-mi/linux-f1c100s-480272lcd-test/

CURRENT_PATH := $(shell pwd)

obj-m := led1.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

可以看到,整個文件內容比較少,其中KERNELDIR代表linux內核源碼的目錄,CURRENT_PATH則代表當前路徑,kernel_modules則代表我們mak命令要執行的內容,需要注意的是,如果你的板子是ARM架構的,這裏要加上ARCH=arm,如果你在編譯內核的時候,有用到自己的交叉編譯鏈,那麼這裏也需要指定一下CROSS_COMPILE,否則可能會因爲內核編譯方式不一樣,導致生成的ko文件,不能被板子上的系統所加載。

執行make命令,可以看到如下熟悉的輸出。

 之後,我們就可以看到led1.ko文件被生成了。

 開發板上跑起來

由於ko文件已經被生成,所以這裏我們拿下tf卡,然後通過如下的命令,將我們的led1.ko,拷貝到media目錄中去:

sudo mkdir /mnt/sdb2             //創建一個臨時目錄
sudo mount /dev/sdb2 /mnt/sdb2   //將sdb2掛載到此臨時目錄
sudo cp led1.ko /mnt/sdb2/media //拷貝到sdb2/media目錄下
sudo sync
sudo umount /dev/sdb2

拷貝完畢後,利用sudo minicom啓動串口監聽,之後去media目錄,安裝我們的驅動看看:

可以看到,驅動被成功的安裝了,可以用lsmod命令看一下:

可以看到驅動成功的被加載了。那麼這時候,我們通過cat /proc/devices命令看看字符設備是否成功註冊:

這裏我們也可以清晰的看到模塊被註冊完畢了。

之後我們利用rmmod led1.ko命令卸載下看看:

可以看到模塊被成功卸載,同時再利用cat /proc/devices命令執行,發現字符設備已被註銷掉了。

我們這裏重新將驅動加載上,然後執行如下命令:

#關閉led
echo off > /dev/LED
#打開led
echo on > /dev/LED

通過交換執行命令,我們發現板子上的led不時的亮滅,整體驅動成功!

 

參考資料:

純代碼點燈方式    :https://www.cnblogs.com/y4247464/p/12379992.html
這篇參考文章也不錯(此人寫的一系列文章都可以的) :https://www.cnblogs.com/y4247464/p/12370190.html
vscode搭建內核開發環境  :https://blog.mxslly.com/archives/170.html
linux設備驅動程序--gpio控制   :https://www.cnblogs.com/downey-blog/p/10501709.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章