Linux驅動開發之LED驅動


首先講下字符設備控制技術 :

大部分驅動程序除了需要提供讀寫設備的能力外,還需要具備控制設備的能力。比如改變波特率。


在用戶空間,使用ioctl系統調用來控制設備,原型如下:
int ioctl(int fd,unsigned long cmd,...)
fd: 要控制的設備文件描述符
cmd: 發送給設備的控制命令
…: 3個參數是可選的參數,存在與否是依賴於控制命令(第 個參數 )

 

 

當應用程序使用ioctl系統調用時,驅動程序將由如下函數來響應:
2.6.36 之前的內核:long (*ioctl) (struct inode* node ,struct file* filp, unsigned int cmd,unsigned long arg)
2.6.36 之后的內核:long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
參數cmd: 通過應用函數ioctl傳遞下來的命令

 

命令從其實質而言就是一個整數, 但為了讓這個整數具備更好的可讀性,我們通常會把這個整數分為幾個段:類型(8位),序號,參數傳送方向,參數長度
Type(類型/幻數):表明這是屬於哪個設備的命令。
Number( ):序號 ,用來區分同一設備的不同命令
Direction:參數傳送的方向,可能的值是 _IOC_NONE(沒有數據傳輸), _IOC_READ, _IOC_WRITE(向設備寫入參數)
Size: 參數長度

 

Linux系統提供了下面的宏來幫助定義命令:
_IO(type,nr):不帶參數的命令
_IOR(type,nr,datatype):從設備中讀參數的命令
_IOW(type,nr,datatype):向設備寫入參數的命令
例:
#define MEM_MAGIC ‘m’ //定義幻數
#define MEM_SET _IOW(MEM_MAGIC, 0, int)

 

unlocked_ioctl函數的實現通常是根據命令執行的一個switch語句。但是,當命令號不能匹配任何一個設備所支持的命令時,返回-EINVAL.
編程模型:
Switch cmd
Case 命令A://執行A對應的操作
Case 命令B://執行B對應的操作
Default:return -EINVAL

 

 

 

LED驅動程序是以內核模塊的形式存在,首先搭好框架:

#include <linux/module.h>
#include <linux/init.h>

static int led_init(void)
{

    return 0;
}

static void led_exit(void)
{

}

MODULE_LICENSE("GPL");

module_init(led_init);
module_exit(led_exit);

 

接着定義設備結構體和設備號:

設備都會用內核中的一種結構來描述。我們的字符設備在內核中使用structcdev來描述。
struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops; //設備操作集
        struct list_head list;
        dev_t dev; //設備號
        unsigned int count; //設備數
};

struct cdev cdev;
dev_t devnum;

 

接着定義文件操作結構體:這里就實現文件打開和IO控制:

Linux系統中,每一個打開的文件,在內核中都會關聯一個struct file,它由內核在打開文件時創建在文件關閉后釋放。

static struct file_operations led_fops =
{
    .open = led_open,
    .ioctl = led_ioctl,
};

 

接下來初始化設備,分配主設備號,注冊設備:

這里說一下靜態申請和動態申請:

靜態申請:開發者自己選擇一個數字作為主設備號,然后通過函數register_chrdev_region向內核申請使用。缺點:如果申請使用的設備號已經被內核中的其他驅動使用了,則申請失敗。

動態分配:使用alloc_chrdev_region由內核分配一個可用的主設備號。優點:因為內核知道哪些號已經被使用了,所以不會導致分配到已經被使用的號。 

向led_init(void)里面添加這些代碼:

static int led_init(void)
{
    cdev_init(&cdev, &led_fops);                         //初始化設備     
    
    alloc_chrdev_region(&devnum, 0, 1, "myled");         //動態分配主設備號,這里的0是我們寫的,如果設備在注冊時候會檢查,如果我們寫的不行,會重新分配    
    cdev_add(&cdev, devnum, 1);                          //注冊設備           
    return 0;
}    

 

然后再led_exit(void)這個函數里添加設備注銷的代碼,不論使用何種方法分配設備號,都應該在驅動退出時,使用unregister_chrdev_region函數釋放這些設備號。

static void led_exit(void)
{
    cdev_del(&cdev);              //刪除這個設備
    unregister_chrdev_region(devnum, 1);  //注銷這個設備的設備號
}

 

接下來我們實現文件操做結構體里面的兩個接口函數,就是這兩個函數



 當我們在應用程序里訪問這兩個open和ioctl時,內核就會通過這個文件操做結構體訪問led_open和led_ioctl這兩個函數。如果不知道這個函數的結構,可以去Linux下找file_operations這個關鍵詞

 

這些都是指向寫的函數(比如led_open、led_ioctl這些函數)的指針,復制過來加上名字、填寫參數(比如這樣:int led_open(struct inode *node, struct file *filp)),就可以了。

每一個存在於文件系統里面的文件都會關聯一個inode 結構,該結構主要用來記錄文件物理上的信息。因此它和代表打開文件的file結構是不同的。一個文件沒有被打開時不會關聯file結構,但是卻會關聯一個inode 結構。
      

    首先我們要定義出硬件led的寄存器

#define GPBCON 0x56000010
#define GPBDAT 0x56000014
unsigned int *led_config;
unsigned int *led_data;

int led_open(struct inode *node, struct file *filp)
{
    led_config = ioremap(GPBCON,4);            //在linux系統下操作硬件,不能直接使用物理地址,必須轉化成虛擬地址,需要ioremap函數
    writel(0x400,led_config);                //對一個寄存器賦值,用writel這個函數,l表示寫進的是一個32位的值,這里是選擇mini2440上的LED1
    
    led_data = ioremap(GPBDAT,4);
    
    return 0;
}

int led_ioctl(struct inode *node, struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch(cmd)
    {
        case LED_ON:
            writel(0x00,led_data);      //這里是點亮LED1
            return 0;
            
        case LED_OFF:
            writel(0x7ff,led_data);     //這里是熄滅LED1
            return 0;
            
        default:
            return -EINVAL;    
    }
}

 

上面代碼里的LED_ON和LED_OFF是操作者的指令(cmd),需要再次定義一個led.h的文件

#define LED_MAGIC    'L'
#define LED_ON        _IO(LED_MAGIC,0)
#define LED_OFF       _IO(LED_MAGIC,1)

 

接着加入需要的頭文件:

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/io.h>
#include "led.h"

 

編寫Makefile文件

obj-m := led.o
KDIR := /root/myhome/linux-2.6.32.2                      //內核文件的目錄
all :
    make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm  
clean : rm
-f *.o *.ko *.order *.symvers

 

接着編寫led_app應用程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "led.h"

int main(int argc, char *argv[])    //帶參數的函數
{
    int fd;
    int cmd;
    
    if(argc < 2)
    {
        printf("please enter the second para!\n");
        return 0;
    }
    
    cmd = atoi(argv[1]);
    
    fd = open("/dev/myled",O_RDWR);
    if(cmd == 1)
    {
        ioctl(fd,LED_ON);
    }
    else
    {
        ioctl(fd,LED_OFF);
    }
    
    return 0;
}

編譯這個led_app的時候,一定要選取靜態編譯,不然下載開發板后運行會找不到動態鏈接庫

arm-linux-gcc -static led_app.c -o led_app

 

把編譯好的led.ko和led_app這兩個文件通過nfs傳到開發板后,顯示這樣

現狀安裝驅動insmod led.ko

然后再cat /proc/devices主設備號

主設備號是動態分配的253

這個時候再mknod /dev/myled c 253 0創建設備文件,這個文件就是led的設備文件

  mknod    創建設備文件指令

  /dev/myled  這是目錄

  c       代表字符設備文件

  253       主設備號

  0      次設備號

創建好了之后就./led_app 0,這個時候會發現燈滅了

再執行./led_app 1,燈又亮了

正如代碼里那樣,點亮第一個燈。

 

 

 





 

 


免責聲明!

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



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