日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

字符设备驱动程序——点亮、熄灭LED操作

發布時間:2025/3/17 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 字符设备驱动程序——点亮、熄灭LED操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先后數據。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制臺和LED設備等。

每一個字符設備都在/dev目錄下對應一個設備文件。linux用戶程序通過設備文件(或稱設備節點)來使用驅動程序操作字符設備。

Linux操作系統將所有的設備看成文件,以操作文件的方式訪問設備。應用程序不能直接操作硬件,而是使用統一的接口函數調用硬件的驅動程序。這組接口被稱為系統調用,在庫函數中定義。可以在glibc的fcntl.h、unistd.h、sys/ioctl.h等文件中看到如下的定義,這個些文件也可以在交叉編譯工具鏈的/usr/local/arm/3.4.1/include目錄下找到。

extern int open (__const char *__file, int __oflag, ...) __nonnull ((1));

extern ssize_t read (int __fd, void *__buf, size_t __nbytes);

extern ssize_t write (int __fd, __const void *__buf, size_t __n);

extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;

.......

對于上述每個系統調用,驅動程序中都有一個與之對應的函數。對于字符設備驅動程序,這些函數集合在一個file_operations類型的數據結構中。file_operations結構在Linux內核的include/linux/fs.h文件中定義。

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, struct dentry *, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*dir_notify)(struct file *filp, unsigned long arg);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

};

當應用程序使用open函數打開某個設備時,設備驅動程序的file_operations結構中的open成員就會被調用;當應用程序使用read、write、ioctl等函數讀寫、控制設備時,驅動程序的file_operations結構中的相應成員(read、write、ioctl等)就會被調用。從這個角度來說,編寫字符設備驅動程序就是為具體硬件的file_operations結構編寫各個函數(并不需要全部實現file_operations結構中的成員)。

那么,當應用程序通過open、read、write等系統調用訪問某個設備文件時,Linux系統怎么知道去調用那個驅動程序的file_operations結構中的open、read、write等成員呢?

(1)、設備文件主/次設備號

設備文件分為字符設備、塊設備,比如PC機上的串口屬于字符設備,硬盤屬于塊設備。在PC機上運行命令“ls /dev/ttyS0 /dev/hdal -l”可以看到:

brw-rw---- 1 root disk ??3, 1 ?5月 21 08:29 /dev/hdal

crw-rw---- 1 root uucp ?4, 64 ?5月 21 08:29 /dev/ttyS0

“brw-rw----”中的“b”表示/dev/hdal是個塊設備,他的主設備號是3,次設備號是1;“crw-rw----”中的“c”表示/dev/ttyS0是個塊設備,它的主設備號位4,次設備號為64。

(2)、模塊初始化時,將主設備號與file_operations結構一起向內核注冊

驅動程序有一個初始化函數,在安裝驅動程序時會調用它。在初始化函數中,會將驅動程序的file_operations結構連同主設備號一起向內核進行注冊。對于字符設備使用如下以下函數進行注冊:

extern int register_chrdev(unsigned int, const char *,const struct file_operations *);

這樣,應用程序操作設備文件時,Linux系統就將會根據設備文件的類型(是字符設備還是塊設備)、主設備號找到在內核中注冊的file_operations結構(對于塊設備位block_device_operations結構),次設備號供驅動程序自身用來分辨它是同類設備中的第幾個。

編寫字符驅動程序的過程大概如下。

(1)、編寫驅動程序初始化函數

進行必要的初始化,包括硬件初始化、向內核注冊驅動程序等。

(2)、構造file_operations結構中要用到的各個成員函數

實際的驅動程序當然比上述兩個步驟復雜,但這兩個步驟已經可以讓我們編寫比較簡單的驅動程序,比如LED控制。其他比較高級的技術,比如中斷、select機制、fasync異步通知機制,將在其他章節的例子中介紹。

LED驅動程序源碼分析:

本節以一個簡單的LED驅動程序作為例子,讓讀者初步了解驅動程序的開發。

本例子的開發板使用引腳GPF4~6外接3個LED,它們的操作方法以前已經做了詳細的說明。

(1)、引腳功能為輸出;

(2)、要點亮LED,令引腳輸出0;

(3)、要熄滅LED,令引腳輸出1。

硬件連接方式如下圖:

1、LED驅動程序代碼分析

下面按照函數調用的順序進行講解。

模塊的初始化函數和卸載函數如下:

/*

* 執行“insmod first_drv.ko”命令時就會調用這個函數

*/

static int __init first_drv_init(void)

{

/* 注冊字符設備驅動程序

?* 參數為主設備號、設備名字、file_operations結構;本例子中寫0,

?* 內核就會自動分配一個主設備號,使用major變量存儲。

?* 這樣,主設備號和具體的file_operations結構就聯系起來了

?* 操作主設備號為major的設備文件時,就會調用first_drv_fops中的相關成員函數

*/

99 : major = register_chrdev( 0, "first_drv", &first_drv_fops);

?

/* 創建一個struct class的指針

?* 第一個參數默認“THIS_MODULE”

?* 第二個參數和register_chrdev()函數中的驅動設備名字相同

*/

firstdrv_class = class_create(THIS_MODULE, "first_drv");

/* 創建/dev/xxx設備

?* 第一個參數為上面創建的struct class類型的變量

?* 第二個參數為NULL,第三個參數為MKDEV(major, 0)

?* 第四個參數為NULL,第五個參數為設備名字

*/

firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xxx");

?

/* 將物理端口進行映射

*/

GPFCON = (volatile unsigned long *)ioremap(0x56000050, 16);

GPFDATA = GPFCON+1;

return 0;

}

?

/* 執行rmmod first_drv命令時就會調用這個函數

?*

*/

static void __exit first_drv_exit(void)

{

/* 卸載驅動程序 */

unregister_chrdev(major, "first_drv");

/* 卸載/dev/目錄下的設備 */

class_device_unregister(firstdrv_class_dev);

/* 銷毀class_create()創建的類 */

class_destroy(firstdrv_class);

/* 取消端口映射 */

iounmap(GPFCON);

}

/* 這兩行指定驅動程序的初始化和卸載函數 */

119 : module_init(first_drv_init);

120 : module_exit(first_drv_exit);

/* 驅動模塊的許可證聲明 */

MODULE_LICENSE("GPL");

第119、120兩行用來指明裝載、卸載模塊時所調用的函數。也可以不使用這兩行,但是需要將這兩個函數的名字改為init_module、cleanup_module。

執行“insmod first_drv.ko”命令時就會調用first_drv_init函數,這個函數核心的代碼只有第99行。它調用register_chrdev函數向內核注冊驅動程序;內核會自動分配主設備號,并保存在major變量中,將主設備號位major與file_operations結構first_drv_fops聯系起來。以后應用程序操作主設備號位major的設備文件時,比如open、read、write、ioctl,first_drv_fops中的相應成員函數就會被調用。但是,first_drv_fops中并不需要全部實現這些函數,用到哪個就實現哪個。

執行“rmmod first_drv”命令時就會調用first_drv_exit函數,它進而調用unregister_chrdev函數卸載驅動程序,它的功能與register_chrdev函數相反。

first_drv_init、first_drv_exit函數前的“__init”、“__exit”只有在將驅動程序靜態鏈接進內核時才有意義。前者表示first_drv_init函數的代碼被放在“.init.text”段中,這個段在使用一次后被釋放(這可以節省內存);后者表示first_drv_exit函數的代碼段被放在“.exit.data”段中,在連接內核時這個段沒有使用,因為不能卸載靜態鏈接的驅動程序。

下面看看first_drv_fops結構的組成。

/* 這個結構是字符設備驅動程序的核心

?* 當應用程序操作設備文件時所調用的open、read、write等函數

?* 最終會調用這個結構中的對應函數

*/

static struct file_operations first_drv_fops = {

???81 .owner = THIS_MODULE,/* 這是一個宏,指向編譯模塊時自動創建的__this_module變量 */

????.open = ?first_drv_open, ????

????.write = first_drv_write, ???

};

第81行的宏THIS_MODULE在include/linux/module.h中定義如下,__this_module變量在編譯模塊時自動創建,無需關注這點。

# define THIS_MODULE (&__this_module)

file_operations類型的first_drv_fops結構是驅動中最重要的數據結構,編寫字符設備驅動程序的主要工作也是填充其中的各個成員。比如本驅動程序中用到open、write成員被設為first_drv_open、first_drv_write函數,前者用來初始化LED所用的GPIO引腳,后者用來根據用戶傳入的參數設置GPIO的輸出電平。

first_drv_open函數的代碼如下:

/* 應用程序對設備文件/dev/xxx執行open()時,

* 就會調用first_drv_open 函數

*/

static int first_drv_open(struct inode *inode, struct file *file)

{

/* 設置GPIO引腳的功能,本驅動中LED所涉及的GPIO引腳設為輸出功能 */

/*配置GPF4、5、6為輸出*/

*GPFCON ?&= ?~( (0X3<<(4*2)) | (0X3<<(5*2)) | (0X3<<(6*2))); //首先清零

*GPFCON ?|= ?( (0X1<<(4*2)) | (0X1<<(5*2)) | (0X1<<(6*2))); ?//后置1

return 0;

}

在應用程序執行“open(“/dev/xxx”)”系統調用時,first_drv_open函數將被調用。它用來將LED所涉及的GPIO引腳設為輸出功能。不在模塊的初始化函數中進行這些設置的原因是:雖然加載了模塊,但是這個模塊卻不一定會被用到,就是說這些引腳不一定用于這些用途,它們可能在其他模塊中另作他用。所以,在使用時才去設置它,我們把對引腳的初始化放在open操作中。

first_drv_write函數的代碼如下:

/* 應用程序對設備文件/dev/xxx執行write()函數時

* 就會調用first_drv_write函數

*/

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)

{

int val;

/* 拷貝從應用程序傳遞過來的值到val變量中 */

copy_from_user(&val, buf, count);

if(val == 1){

//點燈

*GPFDATA &= ~((0X1<<(4)) | (0X1<<(5)) | (0X1<<(6)));

}else{

//滅燈

*GPFDATA |= ((0X1<<(4)) | (0X1<<(5)) | (0X1<<(6)));

}

return 0;

}

應用程序執行系統調用write()時,first_drv_write函數將被調用,copy_from_user(&val, buf, count);作用是將用戶傳遞的輸出取出,根據用戶傳遞的值來設置LED燈的亮滅。

注意:應用程序執行的open、write等系統調用,它們的參數和驅動程序中相應函數的參數不是一一對應的,其中經過了內核文件系統層的轉換。

系統調用的原型如下:

int open(const char *pathname, int flags);

ssize_t write(int fd, const void *buf, size_t count);

int ioctl(int fd, int request, ...);

ssize_t read(int fd, void *buf, size_t count);

file_operations結構中的成員如下:

int (*open) (struct inode *, struct file *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

可以看到,這些參數有很大一部分非常相似。

(1)、系統調用open傳入的參數已經被內核文件系統層處理了,在驅動程序中看不出原來的參數了。

(2)、系統調用ioctl的參數個數可變,一般最多傳入3個:后面兩個參數與file_operations結構中ioctl成員的后兩個參數對應。

(3)、系統調用read傳入的buf、count參數,對應file_operations結構中read成員的buf、count參數。而參數offp表示用戶在文件中進行存取操作的位置,當執行完讀寫操作后由驅動程序調用。

(4)、系統調用write與file_operations結構中write成員的參數關系,與第(3)點相似。

驅動程序的最后,有如下描述信息,它們不是必須的。

/* 描述驅動程序的一些信息,不是必須的 */

MODULE_AUTHOR(“http:\\www.100ask.net”); //驅動程序作者

MODULE_DESCRIPTION(“S3C2410 LED Driver”); //一些描述信息

MODULE_LICENSE(“GPL”); //遵循的協議

驅動程序的編譯:

在次文件的同一目錄下,制作Makefile文件,文件內容如下:

KERN_DIR = /work/linux/linux-2.6.22.6 ?#對應內核的所在目錄

all:

make -C $(KERN_DIR) M=`pwd` modules

clean: ???#執行make clean時刪除生成的文件

make -C $(KERN_DIR) M=`pwd` modules clean

rm -rf modules.order

obj-m += first_drv.o ?#執行make命令生成的模塊名字為“first_drv.o”

驅動程序測試:

首先要編譯測試驅動程序firstdrv_test.c,它的代碼很簡單,關鍵部分如下:

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

/*

* firstdrvtest

*/

int main(int argc, char **argv)

{

int fd;

int val = 1;

fd = open("/dev/xxx", O_RDWR); //打開設備

if (fd < 0)

{

printf("can't open!\n");

}

if(argc != 2){

printf("Usage:\n");

printf("%s <on | off>\n",argv[0]);

return 0;

}

if(strcmp(argv[1], "on") == 0){ ?//判斷用戶運行程序時,

????????????????//添加的參數時on還是off

val = 1; ?//寫入1,既點亮LED

}else{

val = 0; ?//寫入0,既熄滅LED

}

write(fd, &val, 4); //寫入數據

return 0;

}

其中的open、write函數最終會調用驅動程序中的first_drv_open、first_drv_write函數。

現在就可以參照firstdrv_test.c的使用說明,(直接運行firstdrv_test命令即可看到)操作LED了,以下兩條命令點亮、熄滅LED。

./firstdrv_test on

./firstdrv_test off

?

轉載于:https://my.oschina.net/cht2000/blog/906223

新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!

總結

以上是生活随笔為你收集整理的字符设备驱动程序——点亮、熄灭LED操作的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。