歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

嵌入式Linux字符設備驅動LED驅動編寫


嵌入式Linux字符設備驅動LED驅動編寫

標簽: linux內核
2015-04-30 14:41 105人閱讀 評論(0) 收藏 舉報

分類:
Linux開發


嵌入式Linux字符設備驅動LED驅動編寫嵌入式Linux字符設備驅動開發總結--LED驅動

作者:英貝得教育02就業班 楊廣東
設備驅動程序是集成在內核中,處理硬件或操作硬件控制器的軟件。字符設備還是塊設備都為內核提供相同的調用接口,所以內核能以相同的方式處理不同的設備。字符設備提供給應用程序流控制接口有:open/read/write/ioctl/……,添加一個字符設備驅動程序,實際上是給上述操作添加對應的代碼
模塊的概念:linux內核模塊是一種可以被內核動態的加載和卸載的可執行的二進制代碼。通過內核模塊可以擴展內核的功能,通常內核模塊被用於設備驅動,文件系統等。如果沒有內核模塊,需要向內核添加功能就需要修改代碼,重新編譯內核,安裝新內核等,不僅繁瑣而且容易出錯,不易於調試。Linux內核是一個整體結構,各種功能結合在一起,linux內核的開發者設計了內核模塊機制,從代碼的角度看,內核模塊是一組可以完成某種功能的函數集合。從執行的角度看,內核模塊是一個已經編譯但沒有連接的程序。內核模塊類似應用程序,但是與普通應用程序有所不同,區別在與:
運行環境不同
功能定位不同
函數調用方式不同
Linux設備驅動程序與外界的接口可以分為如下3個部分:
1.驅動程序與內核操作系統內核的接口:通過數據結構:file_operation來完成的
2.驅動程序與系統引導內核的接口:利用驅動程序對設備進行初始化
3.驅動程序與設備的接口:描述了驅動程序如何與設備進行交互
一.字符驅動的具體流程:
所需要的頭文件和宏定義:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#define LED_MAJOR 100 //主設備號
#define LED_SECOND 5 //次設備號
#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1
struct cdev led_cdev0; //定義cdev結構體,內核是通過這個結構體來訪問驅 //動程序的
dev_t led_t = 0; //無整型32位
1.定義設備結構體變量:
因為內核是通過cdev結構體來訪問驅動的,所以要定義結構體:
struct cdev my_cdve0;
定義好描述字符IO設備的結構體後,就用該結構體來定義一個變量,在內核中就用該變量來代表我們的字符IO設備,在這裡就代表的是LED燈。
2.定義設備的操作接口和編寫接口操作函數:
每個設備都對應一些操作,應用程序及是通過這些接口操作函數來使用驅動程序完成對設備的控制,設備的操作接口定義如下:
Struct file_operation led_ops = {
.ownr = THIS_MODULE; //一個宏
.open = myled_open,
ioctl = myled_ioctl,
.release = myled_close,
};
相應函數的實現:
static int leds_open(struct inode *inode, struct file *file)
{
int i;
i = (1 << 10) + (1 << 12) + (1 << 16) + (1 << 20);
writel(i,S3C2410_GPBCON); //S3C2410_GPBCON在頭文件<mach/regs-gpio.h>
//中已定義
i = 0x0; //燈全亮
writel(i,S3C2410_GPBUP); //在內核中讀寫函數用writel()和readl();
i = 0xfffffffe; //燈滅
writel(i,S3C2410_GPBDAT);
return 0;
}
tatic int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{ //內核中的參數 //應用程序中傳過來的參數
switch(cmd)
{
case IOCTL_LED_ON :
writel(0xffffffde,S3C2410_GPBDAT);
break;
case IOCTL_LED_ONa :
writel(0xffffffbe,S3C2410_GPBDAT);
break;
case IOCTL_LED_ONb:
writel(0xfffffefe,S3C2410_GPBDAT);
break;
case IOCTL_LED_ONc:
writel((~(0x01<<10)&0xfffffffe),S3C2410_GPBDAT);
break;
case IOCTL_LED_ONd:
writel(0x0,S3C2410_GPBDAT);
break;
case IOCTL_LED_OFF:
writel(0xfffffffe,S3C2410_GPBDAT);
break;
default:
break;
}
return 0;
}
一個LED燈對應一個字符設備驅動;
tatic int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{ //內核中的參數 //應用程序中傳過來的參數
Dev_t cur_dev_no = inode->i_rdev; //主設備號
Dev_t minordev = MINOR(inode->i_rdev); //次設備號
If(minordev == 0) //表示是第一個燈的驅動,後面以此類推
{
int i = 0;
switch(cmd)
{
case IOCTL_LED_ON:
writel((~(0x01)<<5)&&0xfffffffe,S3C2410_GPBDAT);
Break;
case IOCTL_LED_OFF:
writel(0xfffffffe,S3C2410_GPBDAT);
break;
default:
break;
}
}
If(minordev == 1) //表示是第二個燈的驅動,後面以此類推
{
……
}
return 0;
}
3.字符設備驅動模塊的初始化和退出函數:
字符驅動模塊是一個linux模塊,所以要遵循linux模塊的框架。首先查看自己定義的主設備號在內核中是否已被占用,初始化字符設備結構體變量cdev,向內核注冊該字符IO設備驅動,分別通過調用cdev_init(),cdev_add()函數來實現,對於字符驅動模塊初始化函數定義如下:
Static int__init myled_init(void)
{
led_t = MKDEV(LED_MAJOR,LED_SECOND);
re = register_chrdev_region(LED_MAJOR,1,"my"); //查看此設備號是否已被占用返回值等於0分配成功
if (re < 0)
{
printk(" can't register major number\n");
return -1;
}
cdev_init(&led_cdev,&leds_fops); //初始化cdev結構體
r = cdev_add(&led_cdev,led_t,4); //注冊到內核中 4 為要共用一個設備驅動的次設備號
if(r < 0)
{
printk("add wrong\n");
return -1;
}
return 0;
}
字符模塊退出函數組要就是刪除內核中的字符設備:
Static void__exit myled_exit(void)
{
Cdev_del(&led_cdev);
Printk("myled exit now\n");
}
完成了模塊的初始化和退出函數後,最後向內核申明myled_init和lyled_exit函數以及申明模塊license:
module_init(leds_init); 初始化字符驅動設備
module_exit(leds_exit);
MODULE_LICENSE("GPL"); 基於GPL庫開發的軟件在用到GPL庫的這些代碼必須開源
二.測試程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1
int main(int argc, char **argv)
{
int fd = 0;
fd = open(argv[2],0);
if(strcmp(argv[1],"on")==0)
{
ioctl(fd,IOCTL_LED_ON);
}
else if(strcmp(argv[1],"off")==0)
{
ioctl(fd,IOCTL_LED_OFF);
}
return 0;
}
如果是一個LED燈一個驅動,測試程序中的fd = open("/dev/ledS0",0)應改為fd = open(argv[2],0);用命令行的模式:
./LES_TEST on /dev/ledS0 這是第二個LED驅動
./LES_TEST on /dev/ledS1這是第二個LED驅動
三.Makefile:
obj-m:=dri_led.o
KDIR = /home/kernel/linux-2.6.30.9 //將要使用的內核
all:
make -C $(KDIR) SUBDIRS=$(shell pwd) modules
arm-linux-gcc -o led_test led_test.c //對led_test.c進行交叉編譯,否則在開發板上不能運行
clean:
@rm -rf dri_led *.o
四.字符IO設備驅動程序測試:
Leds0驅動,測試程序,makefile寫好之後,把linux-2.6.30.9內核重新編譯,通過超級終端把生成的zImage下載到開發板kernel中區(開發板起來之後,6-4-Y-1-4),把在linux下編譯好的測試程序的二進制文件和生成的KO文件下載到開發板中去(通過超級終端的菜單欄中的“傳送”-“發送文件”),這時開發板中已經有所需要用到的文件。
1.往內核添加驅動模塊:
在終端shell下通過執行 Insmod myled.ko
2.改變環境變量,如下:
在終端shell下通過執行chmod來改變環境變量
chmod +x ledtest(把測試程序改成可執行的二進制文件)
3.創建設備文件節點(一個LED燈一個驅動):
在終端shell下通過執行mknod命令創建文件節點
增加節點:mknod /dev/ledS0 c 100 0
增加節點:mknod /dev/ledS1 c 100 1
增加節點:mknod /dev/ledS2 c 100 2
增加節點:mknod /dev/ledS3 c 100 3
/dev/ledS0:字符設備名,包括路徑
C :設備類型,c表示字符設備
100 :主設備號
0 :次設備號
4.在shell下執行led_test測試應用程序:
./LES_TEST on /dev/ledS0
五:總結
應用程序通過調用API:open(),ioctl()函數來訪問內核,ioctl函數傳過來的設備號,到內核中指針數組找相對應主設備號的,通過這個設備號找到該設備的結構體然後調用相應的函數以實現相應的功能:
應用層:
fd = open("/dev/ledS0",0);
ioctl(fd,IOCTL_LED_OFF);
內核層:
標准輸入
標准輸出
出錯
&cdev
0
1
2
100 //主設備號與cdev結構體的首地址
Cdev 結構體
Struct file_operation*ops
Struct moduleowner
Dev_t dev
Struct list_head list
*open
*write
*ioctl
*read
Struct file_opertion*ops:(對應著相應的實現函數)
六.配置menucofig
1.把要加到內核中的驅動文件拷貝到drivers/char目錄下
2.修改kconfig文件添加如下代碼:
config HT_LED
Bool "LED"
default y
---help---
This is my first drive -LED!
3.修改makefile添加如下代碼:
obj-$(CONFIG_HT_LED) +=dri_led.o
配置成功,重新編譯生成zImage
Copyright © Linux教程網 All Rights Reserved