Linux驅動之LED驅動編寫


從上到下,一個軟件系統可以分為:應用程序、操作系統(內核)、驅動程序。結構圖如下:我們需要做的就是寫出open、read、write等驅動層的函數。一個LED驅動的步驟如下:

1、查看原理圖,確定需要控制的IO端口

2、查看芯片手冊,確定IO端口的寄存器地址

3、編寫驅動代碼

4、確定應用程序功能,編寫測試代碼。

5、編寫Makefile,編譯驅動代碼與測試代碼,在開發板上運行

 

1、查看原理圖,確定需要控制的IO端口

打開原理圖,確定需要控制的IO端口為GPF4、GPF5、GPF6。

 

 

2、查看芯片手冊,確定IO端口的寄存器地址,可以看到它的基地址為0x56000050

 

 

3、編寫驅動代碼,編寫驅動代碼的步驟如下:

 1)、編寫出口、入口函數。

  a、首先利用register_chrdev函數如果第一個參數為0的話那么會自動分配一個主設備號為Firstmajor ;第二個參數firstled_drv會是這個字符設備的名稱可以利用命令cat /proc/devices看到;第三個參數是它的first_drv_fops結構體,這個結構體是字符設備中最主要的,后面再說明。

  b、接着利用class_create函數創建一個firt_drv_class類。它的第一個參數指向這個模塊,第二個參數為類的名稱。再利用class_device_create創建四個設備節點,第一個參數為類、第三個參數為設備號,第五個參數為設備節點的名稱,第六個參數為次設備號。這樣的話會在加載驅動之后自動在/dev目錄下創建四個設備文件。

  c、ioremap函數重映射函數,將物理地址轉換成虛擬地址

  d、a-c為驅動入口函數,在驅動出口函數會將a-c創建的東西全部刪除。

  e、module_init與module_exit表示在insmod與rmmod的時候內核會調用first_ledsdrv_init與first_ledsdrv_exit

/*
 * 執行insmod命令時就會調用這個函數 
 */
static int __init first_ledsdrv_init(void)
{
    int minor;//次設備號
    Firstmajor = register_chrdev(0, "firstled_drv", &first_drv_fops);//注冊first_drv_fops結構體到字符設備驅動表,0表示自動分配主設備號
    if(Firstmajor<0)
    {
              printk(" first_drv can't register major number\n");
              return Firstmajor;
        }

    firt_drv_class = class_create(THIS_MODULE, "leds");//創建類 
    
    firt_drv_class_dev[0] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, 0), NULL, "leds");//創建設備節點
    if (unlikely(IS_ERR(firt_drv_class_dev[0])))
            return PTR_ERR(firt_drv_class_dev[0]);

    for(minor=1;minor<4;minor++)
    {
        firt_drv_class_dev[minor] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, minor), NULL, "led%d",minor);//創建設備節點
        if (unlikely(IS_ERR(firt_drv_class_dev[minor])))
            return PTR_ERR(firt_drv_class_dev[minor]);
    }

    gpfcon = ioremap(0x56000050 , 16);//重映射,將物理地址變換為虛擬地址
    gpfdat = gpfcon + 1;
    
    printk("firstdrv module insmoded\n");
    return 0;
}

/*
 * 執行rmmod命令時就會調用這個函數 
 */
static void __exit first_ledsdrv_exit(void)
{
    int i;
    for(i=0;i<4;i++)
        class_device_unregister(firt_drv_class_dev[i]);//刪除設備節點
        
    class_destroy(firt_drv_class);//刪除類

    iounmap(gpfcon);//刪除重映射分配的地址
    
    unregister_chrdev(Firstmajor, "firstled_drv");//將rst_drv_fops結構體從字符設備驅動表中刪除
    printk("firstdrv module rmmod\n");
}


/* 這兩行指定驅動程序的初始化函數和卸載函數 */
module_init(first_ledsdrv_init);
module_exit(first_ledsdrv_exit);

 2)、添加file_operations 結構體,這個是字符設備驅動的核心結構,所有的應用層調用的函數最終都會調用這個結構下面定義的函數。

static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open   =   first_ledsdrv_open,     
    .write    =    first_ledsdrv_write,       
};

其中THIS_MODULE在linux/module.h中定義,它執向__this_module的地址

84    extern struct module __this_module;
85    #define THIS_MODULE (&__this_module)

而__this_module這個變量是在編譯的時候由modpost程序生成的,它的結構如下:

struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
 .name = KBUILD_MODNAME,
 .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
 .exit = cleanup_module,
#endif
};

3)、分別編寫file_operations 結構體下的open、wrtie函數。當應用程序調用系統調用led設備的open與write時最終內核會定位到驅動層的open與write函數。

其中open函數的功能是根據打開的設備文件初始化相應的io口為輸出口

static int first_ledsdrv_open(struct inode *inode, struct file *file)
{
    int minor = MINOR(inode->i_rdev);//取得次設備號,根據次設備號來配置IO端口

    switch(minor)
        {
            case 0:
                *gpfcon &= ~((3 << 8)  | (3 << 10) | (3 << 12));//先清0 :8,9,10,11,12,13
                *gpfcon |= ((1 << 8)  | (1 << 10) | (1 << 12));//再置1:8,10,12break;
                printk("initialize leds\n");
                break;
            case 1:
                *gpfcon &= ~((3 << 8) );//先清0 :8,9,10,11,12,13
                *gpfcon |= ((1 << 8));//再置1:8,10,12break;
                printk("initialize led1\n");
                break;
            case 2:
                *gpfcon &= ~( (3 << 10));//先清0 :8,9,10,11,12,13
                *gpfcon |= ( (1 << 10) );//再置1:8,10,12break;
                printk("initialize led2\n");
                break;
            case 3:
                *gpfcon &= ~((3 << 12));//先清0 :8,9,10,11,12,13
                *gpfcon |= ((1 << 12));//再置1:8,10,12break;
                printk("initialize led3\n");
                break;
            default:break;
        }
    
    
//    printk("hello this is open\n");
    return 0;
}

write函數的功能是根據設備文件以及向設備寫入的值來操作相應的IO口做相應的動作

static ssize_t first_ledsdrv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    char val;
    int ret;
    int minor = MINOR(file->f_dentry->d_inode->i_rdev);//根據文件取出次設備號
    
    ret = copy_from_user(&val, buf, count);//ret返回0表示拷貝成功

    if(!ret)
    {
        switch(minor)
        {
            case 0:
                if(val==1)
                {
                    *gpfdat &= ~((1 << 4) | (1<<5) | (1<<6));//點燈
                     printk("leds on\n");
                }
                else if(val == 0)
                {
                    *gpfdat |= ((1 << 4) | (1<<5) | (1<<6));//滅燈
                    printk("leds off\n");
                }
                break;
            case 1:
                if(val==1)
                {
                    *gpfdat &= ~((1 << 4));//點燈
                     printk("led1 on\n");
                }
                else if(val == 0)
                {
                    *gpfdat |= ((1 << 4));//滅燈
                    printk("led1 off\n");
                }
                break;
            case 2:
                if(val==1)
                {
                    *gpfdat &= ~((1<<5));//點燈
                     printk("led2 on\n");
                }
                else if(val == 0)
                {
                    *gpfdat |= ((1<<5));//滅燈
                    printk("led2 off\n");
                }
                break;
            case 3:
                if(val==1)
                {
                    *gpfdat &= ~((1<<6));//點燈
                     printk("led3 on\n");
                }
                else if(val == 0)
                {
                    *gpfdat |= ((1<<6));//滅燈
                    printk("led3 off\n");
                }
                break;
            default:break;
        }
    }
    else
        printk("copy from user wrong!!!!%d  %d\n",ret,count);
//    printk("hello this is write\n");
    return 0;
}

4)、下面是整個LED驅動的整體代碼

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/io.h>        //含有iomap函數iounmap函數
#include <asm/uaccess.h>//含有copy_from_user函數
#include <linux/device.h>//含有類相關的處理函數



static struct class *firt_drv_class;//
static struct class_device *firt_drv_class_dev[4];//類下面的設備
static int Firstmajor;

static unsigned long *gpfcon = NULL;
static unsigned long *gpfdat = NULL;

static int first_ledsdrv_open(struct inode *inode, struct file *file)
{
    int minor = MINOR(inode->i_rdev);//取得次設備號,根據次設備號來配置IO端口

    switch(minor)
        {
            case 0:
                *gpfcon &= ~((3 << 8)  | (3 << 10) | (3 << 12));//先清0 :8,9,10,11,12,13
                *gpfcon |= ((1 << 8)  | (1 << 10) | (1 << 12));//再置1:8,10,12break;
                printk("initialize leds\n");
                break;
            case 1:
                *gpfcon &= ~((3 << 8) );//先清0 :8,9,10,11,12,13
                *gpfcon |= ((1 << 8));//再置1:8,10,12break;
                printk("initialize led1\n");
                break;
            case 2:
                *gpfcon &= ~( (3 << 10));//先清0 :8,9,10,11,12,13
                *gpfcon |= ( (1 << 10) );//再置1:8,10,12break;
                printk("initialize led2\n");
                break;
            case 3:
                *gpfcon &= ~((3 << 12));//先清0 :8,9,10,11,12,13
                *gpfcon |= ((1 << 12));//再置1:8,10,12break;
                printk("initialize led3\n");
                break;
            default:break;
        }
    
    
//    printk("hello this is open\n");
    return 0;
}


static ssize_t first_ledsdrv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    char val;
    int ret;
    int minor = MINOR(file->f_dentry->d_inode->i_rdev);//根據文件取出次設備號
    
    ret = copy_from_user(&val, buf, count);//ret返回0表示拷貝成功

    if(!ret)
    {
        switch(minor)
        {
            case 0:
                if(val==1)
                {
                    *gpfdat &= ~((1 << 4) | (1<<5) | (1<<6));//點燈
                     printk("leds on\n");
                }
                else if(val == 0)
                {
                    *gpfdat |= ((1 << 4) | (1<<5) | (1<<6));//滅燈
                    printk("leds off\n");
                }
                break;
            case 1:
                if(val==1)
                {
                    *gpfdat &= ~((1 << 4));//點燈
                     printk("led1 on\n");
                }
                else if(val == 0)
                {
                    *gpfdat |= ((1 << 4));//滅燈
                    printk("led1 off\n");
                }
                break;
            case 2:
                if(val==1)
                {
                    *gpfdat &= ~((1<<5));//點燈
                     printk("led2 on\n");
                }
                else if(val == 0)
                {
                    *gpfdat |= ((1<<5));//滅燈
                    printk("led2 off\n");
                }
                break;
            case 3:
                if(val==1)
                {
                    *gpfdat &= ~((1<<6));//點燈
                     printk("led3 on\n");
                }
                else if(val == 0)
                {
                    *gpfdat |= ((1<<6));//滅燈
                    printk("led3 off\n");
                }
                break;
            default:break;
        }
    }
    else
        printk("copy from user wrong!!!!%d  %d\n",ret,count);
//    printk("hello this is write\n");
    return 0;
}


static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open   =   first_ledsdrv_open,     
    .write    =    first_ledsdrv_write,       
};


/*
 * 執行insmod命令時就會調用這個函數 
 */
static int __init first_ledsdrv_init(void)
{
    int minor;//次設備號
    Firstmajor = register_chrdev(0, "firstled_drv", &first_drv_fops);//注冊first_drv_fops結構體到字符設備驅動表,0表示自動分配主設備號
    if(Firstmajor<0)
    {
              printk(" first_drv can't register major number\n");
              return Firstmajor;
        }

    firt_drv_class = class_create(THIS_MODULE, "leds");//創建類 
    
    firt_drv_class_dev[0] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, 0), NULL, "leds");//創建設備節點
    if (unlikely(IS_ERR(firt_drv_class_dev[0])))
            return PTR_ERR(firt_drv_class_dev[0]);

    for(minor=1;minor<4;minor++)
    {
        firt_drv_class_dev[minor] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, minor), NULL, "led%d",minor);//創建設備節點
        if (unlikely(IS_ERR(firt_drv_class_dev[minor])))
            return PTR_ERR(firt_drv_class_dev[minor]);
    }

    gpfcon = ioremap(0x56000050 , 16);//重映射,將物理地址變換為虛擬地址
    gpfdat = gpfcon + 1;
    
    printk("firstdrv module insmoded\n");
    return 0;
}

/*
 * 執行rmmod命令時就會調用這個函數 
 */
static void __exit first_ledsdrv_exit(void)
{
    int i;
    for(i=0;i<4;i++)
        class_device_unregister(firt_drv_class_dev[i]);//刪除設備節點
        
    class_destroy(firt_drv_class);//刪除類

    iounmap(gpfcon);//刪除重映射分配的地址
    
    unregister_chrdev(Firstmajor, "firstled_drv");//將rst_drv_fops結構體從字符設備驅動表中刪除
    printk("firstdrv module rmmod\n");
}


/* 這兩行指定驅動程序的初始化函數和卸載函數 */
module_init(first_ledsdrv_init);
module_exit(first_ledsdrv_exit);


MODULE_LICENSE("GPL");//不加的話加載會有錯誤提醒
MODULE_AUTHOR("andylu");//作者
MODULE_VERSION("0.0.0");//版本
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");//簡單的描述

 

 4、確定應用程序功能,編寫測試代碼。應用程序功能為打開不同設備文件操作不同的IO口。代碼如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>



/*
  *  ledtest <dev> <on|off>
  */
void print_usage(char *file)
{
    printf("Usage:\n");
    printf("%s <dev> <on|off>\n",file);
    printf("eg. \n");
    printf("%s /dev/leds on\n", file);
    printf("%s /dev/leds off\n", file);
    printf("%s /dev/led1 on\n", file);
    printf("%s /dev/led1 off\n", file);
}


int main(int argc, char **argv)
{
    int fd;
    char* filename=NULL;
    char val;
    
    filename = argv[1];
    
    fd = open(filename, O_RDWR);//打開dev/firstdrv設備文件
    if (fd < 0)//小於0說明沒有成功
    {
        printf("error, can't open %s\n", filename);
        return 0;
    }
    
    if(argc !=3)
    {
        print_usage( argv[1]);//打印用法
    }

    if(!strcmp(argv[2], "on"))
        val = 1;
   else
       val = 0;
   
    write(fd, &val, 1);//操作LED
    
   return 0;
}

 

5、編寫Makefile,編譯驅動代碼與測試代碼,在開發板上運行

Makefile源碼如下:

KERN_DIR = /work/system/linux-2.6.22.6

all:
        make -C $(KERN_DIR) M=`pwd` modules //M='pwd'表示當前目錄。這句話的意思是利用內核目錄下的Makefile規則來編譯當前目錄下的模塊

clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order

obj-m   +=first_drv.o//調用內核目錄下Makefile編譯時需要用到這個參數

1)、然后在當前目錄下make后編譯出first_drv.ko文件

2)、arm-linux-gcc -o first_test first_test.c編譯出first_test測試程序

3)、cp first_drv.ko first_test /work/nfs_root將編譯出來的文件拷貝到開發板掛接的網絡文件系統上

4)、執行insmod first_drv.ko加載驅動。

5)、./first_test /dev/leds on測試程序,燈全部被點亮,成功運行。

 


免責聲明!

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



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