之前写了一篇移植2.4寸TFT驱动到树莓派的文章,那篇博文中的驱动代码是国外大牛写的,看了一下,还是有很多地方没理解,是得好好再学习一下内核驱动的编写,这里就从字符设备驱动开始,采用最简单的LED驱动来建立内核驱动移植的驱动框架.
个人原创,转载请注明原文出处:
http://blog.csdn.net/embbnux/article/details/17712547
参考文章:
http://blog.csdn.net/hcx25909/article/details/16860725
内核驱动与普通单片机模块驱动的差别就是在于,写内核驱动的时候,要提供内核调用的接口,使内核能找到相应的驱动入口,用户通过告诉内核要干嘛,内核再去调用那个驱动,驱动的最底层和单片机模块是一样的,同样是对GPIO的操作,配置输入输出,以及某些特殊的寄存器. LED的点亮就是对GPIO的操作 .
对于ARM的GPIO调用需要通过IO映射的方法,要操作内存上对应的地址.
对于bcm2708上的io对应关系,可以查看bcm2835的手册,和stm32基本上是一样的,因为同为ARM体系:
我参考的那博客讲这个比较清楚,可以参考下,由于树莓派的内核以及很好的提供了GPIO调用的接口,即把内存操作封装了很好,这里就不像那篇博客那样再自己写函数通过内存操作来进行GPIO操作,觉得有点麻烦,但是对于学好底层很有用.
一 首先上个驱动程序
这里直接把该程序添加到内核的源码目录里面,也可在自己的目录下,但是要写Makefile.
在/drivers/char/新建rasp_led.c,内核中的kconfig文件和makefile文件,参照前一篇文章
led.c:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
/********************************************************************/ /***************Rasp led 驱动程序************************************/ /***************作者: Embbnux Ji*************************************/ /***************博客: http://blog.csdn.net/embbnux/ *****************/ /********************************************************************/ #include <linux/kernel.h> #include <linux/module.h> #include <linux/device.h> #include <mach/platform.h> #include <linux/platform_device.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/delay.h> #include <linux/uaccess.h> #include <linux/init.h> #include <linux/gpio.h> #def ine DEVICE_NAME "Pi_Led" #def ine DRIVER_NAME "pi_led" //class声明内核模块驱动信息,是UDEV能够自动生成/dev下相应文件 static dev_t pi_led_devno; //设备号 static struct class *pi_led_class; static struct cdev pi_led_class_dev; struct gpio_chip *gpiochip; #def ine led_pin 4 //gpio 4 //这部分函数为内核调用后open的设备IO操作,和裸板程序一样 int open_flag= 0 ; static int pi_led_open(struct inode *inode, struct file *filp) { printk( "Open led ing!\n" ); if(open_flag == 0 ){ open_flag = 1 ; printk( "Open led success!\n" ); return 0 ; } else{ printk( "Led has opened!\n" ); } return 0 ; } //这部分函数为内核调用后ioctl的设备IO操作,和裸板程序一样 static long pi_led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch(cmd){ case 0: gpiochip->set(gpiochip, led_pin, 0 ); printk( "Led up!\n" ); break; case 1: gpiochip->set(gpiochip, led_pin, 1 ); printk( "Led down!\n" ); break; } return 0 ; } static int pi_led_release(struct inode *inode,struct file *file){ printk( "Led has release!\n" ); return 0 ; } //file_operations使系统的open,ioctl等函数指针指向我们所写的led_open等函数, //这样系统才能够调用 static struct file_operations pi_led_dev_fops = { .owner =THIS_MODULE, .open =pi_led_open, .unlocked_ioctl = pi_led_ioctl, .release = pi_led_release, }; static int is_right_chip(struct gpio_chip *chip, void *data) { if (strcmp(data, chip->label) == 0 ) return 1 ; return 0 ; } //内核加载后的初始化函数. static int __init pi_led_init(void) { struct device *dev; int major; //自动分配主设备号 major = alloc_chrdev_region(&pi_led_devno, 0 , 1 ,DRIVER_NAME); //register_chrdev 注册字符设备使系统知道有LED这个模块在. cdev_init(&pi_led_class_dev, &pi_led_dev_fops); major = cdev_add(&pi_led_class_dev,pi_led_devno, 1 ); //注册class pi_led_class = class_create(THIS_MODULE,DRIVER_NAME); dev = device_create(pi_led_class ,NULL,pi_led_devno,NULL,DRIVER_NAME); gpiochip = gpiochip_find( "bcm2708_gpio" , is_right_chip); gpiochip->direction_output(gpiochip, led_pin, 1 ); gpiochip->set(gpiochip, led_pin, 0 ); printk( "pi led init ok!\n" ); return 0 ; } //内核卸载后的销毁函数. void pi_led_exit(void) { gpio_free(led_pin); device_destroy(pi_led_class,pi_led_devno); class_destroy(pi_led_class); cdev_del(&pi_led_class_dev); unregister_chrdev_region(pi_led_devno, 1 ); printk( "pi led exit ok!\n" ); } module_init(pi_led_init); module_exit(pi_led_exit); MODULE_DESCRIPTION( "Rasp Led Driver" ); MODULE_AUTHOR( "Embbnux Ji < http://blog.csdn.net/embbnux >" ); MODULE_LICENSE( "GPL" ); |
二 源码框架分析
我们首先从内核模块的入口,module_init(pi_led_init)这个函数看起,可以看出初始化后调用pi_led_init这个函数.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//内核加载后的初始化函数. static int __init pi_led_init(void) { struct device *dev; int major; //自动分配主设备号 major = alloc_chrdev_region(&pi_led_devno, 0 , 1 ,DRIVER_NAME); //register_chrdev 注册字符设备使系统知道有LED这个模块在. cdev_init(&pi_led_class_dev, &pi_led_dev_fops); major = cdev_add(&pi_led_class_dev,pi_led_devno, 1 ); //注册class pi_led_class = class_create(THIS_MODULE,DRIVER_NAME); dev = device_create(pi_led_class ,NULL,pi_led_devno,NULL,DRIVER_NAME); gpiochip = gpiochip_find( "bcm2708_gpio" , is_right_chip); gpiochip->direction_output(gpiochip, led_pin, 1 ); gpiochip->set(gpiochip, led_pin, 0 ); printk( "pi led init ok!\n" ); return 0 ; } |
初始化时首先分配给这个函数设备号,注册该设备,通过class注册使能够在/dev/目录下自动生成相应的设备文件,用户通过操作这个文件,来告诉内核怎么做.
由于是字符设备,所以对该文件的操作通过open,write,ioctl等函数,所以要把这个函数和底层的操作函数对应起来,这就要用到file_operation这个结构体,来声明:
1
2
3
4
5
6
7
8
|
//file_operations使系统的open,ioctl等函数指针指向我们所写的led_open等函数, //这样系统才能够调用 static struct file_operations pi_led_dev_fops = { .owner =THIS_MODULE, .open =pi_led_open, .unlocked_ioctl = pi_led_ioctl, .release = pi_led_release, }; |
这里就让open函数对应到pi_led_open函数,ioctl函数对应到pi_led_ioctl函数;
然后我们就只需要编写相应的pi_led_open以及pi_led_ioctl;这些函数里面的操作就是最底层的GPIO操作,和单片机是一样的.
三 GPIO操作
内核里面的GPIO操作函数,被定义在#include <linux/gpio.h>,这个头文件里面,树莓派官方做好了树莓派的GPIO在内核里面的注册,所以调用gpio.h里面的函数即可进行树莓派的GPIO操作.
1
|
gpiochip = gpiochip_find( "bcm2708_gpio" , is_right_chip); |
通过上面这个函数把内核的GPIO操作和BCM2708的GPIO操作关联起来;
bcm2708的操作可以查看/arch/arm/mach-bcm2708/bcm2708_gpio.c文件,具体也是对内存地址的操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#def ine GPIOFSEL(x) ( 0 x 00 +(x)* 4 ) #def ine GPIOSET(x) ( 0 x 1 c+(x)* 4 ) #def ine GPIOCLR(x) ( 0 x 28 +(x)* 4 ) #def ine GPIOLEV(x) ( 0 x 34 +(x)* 4 ) #def ine GPIOEDS(x) ( 0 x 40 +(x)* 4 ) #def ine GPIOREN(x) ( 0 x 4 c+(x)* 4 ) #def ine GPIOFEN(x) ( 0 x 58 +(x)* 4 ) #def ine GPIOHEN(x) ( 0 x 64 +(x)* 4 ) #def ine GPIOLEN(x) ( 0 x 70 +(x)* 4 ) #def ine GPIOAREN(x) ( 0 x 7 c+(x)* 4 ) #def ine GPIOAFEN(x) ( 0 x 88 +(x)* 4 ) #def ine GPIOUD(x) ( 0 x 94 +(x)* 4 ) #def ine GPIOUDCLK(x) ( 0 x 98 +(x)* 4 ) |
这里定义的就是相应的GPIO寄存器的地址.
四 测试程序
ssh进入树莓派,在主目录下新建led.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include<stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/time.h> int main(int argc, char **argv) { int on; int led_no; int fd; int i; fd = open( "/dev/pi_led" , 0 ); if (fd < 0 ) { fd = open( "/dev/pi_led" , 0 ); } if (fd < 0 ) { perror( "open device led" ); exit( 1 ); } for(i= 0 ;i<= 20 ;i++){ on = i% 2 ; ioctl(fd, on, led_no); sleep( 1 ); } close(fd); return 0 ; } |