led驱动

知识点精简拓展:
cdev:设备号就像我们的身份证号一样,号本身并没有什么特殊的意义,只有把这个号和人对应才有意义,通用设备号也需要和一个特殊的东西对于,这就是cdev, cdev是linux下抽象出来的一个用来描述一个字符设备的结构体。
file_operation:在学linux系统编程的时候,都会讲到linux 应用程序通过系统调用陷入到内核空间,从而执行内核代码,而驱动作为内核的一部分同样也是需要在内核空间执行的,ops也就是file_operations这个结构体就是我们的驱动为应用程序调用驱动而实现的一个操作的集合。

(如果还没搭建NFS或TFTP建议暂时别看)

实验步骤:
首先,最好新建一个目录放驱动实验的。我就建在NFS共享目录下/nfsroot/a。
1、建立一个目录叫/drivers,然后在该目录下新建目录/led,用来存放这次实验所有文件,然后在这目录下又建两个目录:/driver存放驱动程序,/test存放测试程序。
2、进入/driver目录,新建驱动程序文件led.c,内容如下:

//led.c
/*很多新手在都搞不请这些头文件都放什么,我也是,所以头文件不解释了*/
#include  
#include
#include  //分配设备号,注册设备和注销设备函数申明
#include   //内核中每一个设备都对应一个cdev变量
#include
#include
#include

#define GPBOUT ((1<<10)|(1<<12)|(1<<14)|(1<<16)); //设置GPB5~8为输出

int major = 0; //这是设置主设备号的,0表示由系统自动分配

struct cdev* leds_cdev; //指向cdev变量的一个指针

 /*做过裸机的都很熟悉吧,指向GPB的寄存器的指针*/
volatile unsigned long *GPBCON = NULL;  
volatile unsigned long *GPBDAT = NULL;
/*
==================================================================
int leds_open()
概述:IO初始化函数,设置GPB5~8为LED输出
参数:无
返回值:无
==================================================================
*/
static int leds_open(struct inode *inode,struct file *filp)
{
 *GPBCON = 0;  //端口清零
 *GPBCON = GPBOUT;
 return 0;
}
/*
==================================================================
leds_ioctl()
概述:LED控制函数,控制不同LED亮灭
参数: cmd 控制亮灭,1熄灭,0点亮
  arg 选择控制第几个LED灯
返回值:0

==================================================================
*/

static int leds_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
 if(arg>4)
 {
  printk("deyond the led no\n");

 }

 switch(cmd)
 {
  case 1:
   *GPBDAT |=(1<<(arg+4));
   break;
  case 0:
   *GPBDAT &=~(1<<(arg+4));
   break;
  default:
   printk("cmd err\n");
   break;
 }
 return 0;
}
/*
==================================================================
struct file_operations leds_fops 
概述:文件操作结构体,实现以下函数系统调用的连接,应用程序调用操作设备的时候就用下面的函数。注意此类结构体file_operations(这个在fs.h中定义)是必须的。

==================================================================
*/

struct file_operations leds_fops ={

 .owner = THIS_MODULE, //宏,指向编译模块时自动创建的
 .open = leds_open,
 .ioctl = leds_ioctl,
};
/*
==================================================================
leds_init()
概述:当你执行“insmod led.ko”加载驱动模块的命令时,就会调用这个初始化驱动函数
参数:无
返回值: 0
==================================================================
*/

static int __init leds_init(void)
{
 /*dev_t其实是unsigned int(32位),dev代表字符设备的设备号高12位为主设备号,低20位为次设备号*/
 dev_t dev; 
 leds_cdev = cdev_alloc(); //空间申请和简单初始化,分配一个cdev结构

 alloc_chrdev_region(&dev,0,1,"leds"); //动态申请和注册设备号

 major = MAJOR(dev); //取得主设备号,如果是MANOR就是次设备号
 printk("major = %d\n", major); //这句可以在加载模块时知道设备号


 leds_cdev->ops = &leds_fops; //这句我也模模糊糊,学过指针的可以自己结合上下看懂

/*初始化cdev,并建立cdev与file_operations之间的连接*/
 cdev_init(&leds_cdev,&leds_fops);

/*向系统添加一个字符设备,并且立刻置于活动状态,返回一个负值则表示添加失败。*/
 cdev_add(&leds_cdev,dev,1);

 GPBCON =(volatile unsigned int *)ioremap(0x56000010,16); //物理地址映射到虚拟地址
 GPBDAT =GPBCON + 1;

 return 0;
}
/*
==================================================================
leds_exit()
概述:模块卸载函数
参数:无
返回值:无

==================================================================
*/

static void __exit leds_exit(void)
{
 dev_t dev;
 dev = MKDEV(major,0); //注销设备号
 cdev_del(&leds_cdev); //删除设备
 unregister_chrdev_region(dev,1);
}

module_init(leds_init); //向内核注册函数
module_exit(leds_exit); //向内核注册函数

MODULE_LICENSE("GPL"); //设备许可

MODULE_AUTHOR("XTZ"); //制作人
MODULE_DESCRIPTION("leds"); //驱动名

终于分析完上面的代码了。一眨眼就过了两个小时,其实我也是刚接触驱动,从linux到驱动才半个月,而且偷懒了几天。上面我是边分析边查,这样你除了技术提高,你的知识量也能提高噢。
3、现在我们制作Mainfile。在同一目录下新建Mainfile,内容如下:

obj-m := led.o
CURRENT_PATH := $(shell pwd)
ARM_LINUX_KERNEL := /workspace/2.6.30.4/opt/EmbedSky/kernel
all:
 $(MAKE) -C $(ARM_LINUX_KERNEL) SUBDIRS=$(CURRENT_PATH) modules
clean:
 rm -rf *.cmd *.o *.ko  *.mod.c *.symvers *.order

注意:上面/workspace/2.6.30.4/opt/EmbedSky/kernel地址换成你编译内核那个目录,一般只适用那个目录编译出来的内核。
4、#make 编译,如果没问题应该会生成*.ko文件,这个就是模块文件。
在挂载的情况下,我们在ARM平台下输入:#insmod XXX/led.ko 代表你放的目录,还有,这儿用到NFS,如果你还没学可以用TFTP,如果还没学,那你还是回去学吧。。。要挂载了才能输入那命令。
5、我们做出来了,总要写个测试程序吧,进入一开始创建的test目录。新建led_test.c,内容如下:
#include
#include
#include
#include
#include
#define ON 0
#define OFF 1
int main(int argc,char* argv[])
{
 int led_no;
 if(argc != 3)
 {
  printf("Usage:%s \n",argv[0]);
  exit(0);
 }
 int fd;
 fd = open("/dev/led", O_RDWR); 
 if(fd<0)
 {
  printf("open error\n");
  exit(0);
 }
 led_no=strtoul(argv[1],0,0);
 if(!strcmp(argv[2],"ON"))
  ioctl(fd,ON,led_no);
 else if(!strcmp(argv[2],"OFF"))
  ioctl(fd,OFF,led_no);
 else
  exit(0);
 return 0;
}
从上面我们可以看到,主要用到了ioctl()函数,这个可是得包含在leds_fops文件操作结构体中哦。我们可以观察到led_no=strtoul(argv[1],0,0)第一个参数得到后经过if(!strcmp(argv[2],"ON")),以我们输入的第二个参数ON/OFF判断开关,然后再调用驱动程序,所以当我们运行这程序时,./led_test 1 ON就是点亮led1灯,递推。
6、为编译这程序,我们要在同文件夹下建立Makefile,内容如下:
all:
 arm-linux-gcc led_test.c -o led_test
clean:
 rm -rf *.o led_test
7、#make后生成可执行文件,我们可以在NFS下,在ARM平台下该共享目录下输入# ./led_test 1 ON观察现象。
,一开始去百度找了一篇,可是做到后来发现他使用的开发板是老式2410,布线和现在普遍的2440不一样,还有出现了大量错误,后来只能再找一个TQ2440适合的,半天时间就没了,花了2个小时做出了驱动,然后又花了2个小时做这个分析,网上那些分析大多只说函数作用,而不细致分析,为了让跟多人不像我一样茫然,写出细致分析分析发福利哈。

你可能感兴趣的:(字符设备)