7回答

0收藏

[教程] 【转东隅】基于Raspberry: 字符设备内核驱动程序框架编写

Raspberry Pi Raspberry Pi 3086 人阅读 | 7 人回复 | 2014-01-15

基于树莓派Raspberry: 字符设备内核驱动程序框架编写------LED入门
转自网友@东隅

之前写了一篇移植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. /***************Rasp led 驱动程序************************************/  
  3. /***************作者: Embbnux Ji*************************************/  
  4. /***************博客: http://blog.csdn.net/embbnux/ *****************/  
  5. /********************************************************************/  
  6.   
  7. #include <linux/kernel.h>   
  8. #include <linux/module.h>  
  9. #include <linux/device.h>   
  10. #include <mach/platform.h>         
  11. #include <linux/platform_device.h>  
  12. #include <linux/types.h>   
  13. #include <linux/fs.h>     
  14. #include <linux/ioctl.h>   
  15. #include <linux/cdev.h>   
  16. #include <linux/delay.h>   
  17. #include <linux/uaccess.h>  
  18. #include <linux/init.h>   
  19. #include <linux/gpio.h>  
  20.   
  21. #define DEVICE_NAME "Pi_Led"  
  22. #define DRIVER_NAME "pi_led"  
  23.   
  24. //class声明内核模块驱动信息,是UDEV能够自动生成/dev下相应文件  
  25. static dev_t pi_led_devno; //设备号  
  26. static struct class *pi_led_class;  
  27. static struct cdev pi_led_class_dev;  
  28.   
  29. struct gpio_chip *gpiochip;  
  30.   
  31. #define led_pin 4  //gpio 4  
  32.   
  33. //这部分函数为内核调用后open的设备IO操作,和裸板程序一样  
  34. int open_flag=0;  
  35. static int pi_led_open(struct inode *inode, struct file *filp)   
  36. {     
  37.     printk("Open led ing!\n");   
  38.     if(open_flag ==0){  
  39.        open_flag =1;  
  40.        printk("Open led success!\n");  
  41.        return 0;  
  42.     }  
  43.     else{  
  44.        printk("Led has opened!\n");      
  45.     }  
  46.     return 0;   
  47. }   
  48. //这部分函数为内核调用后ioctl的设备IO操作,和裸板程序一样  
  49. static long pi_led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)   
  50. {   
  51.     switch(cmd){  
  52.       case 0:   
  53.           gpiochip->set(gpiochip, led_pin, 0);  
  54.             printk("Led  up!\n");  
  55.            break;  
  56.       case 1:  
  57.             gpiochip->set(gpiochip, led_pin, 1);  
  58.             printk("Led  down!\n");  
  59.            break;  
  60.     }  
  61.      
  62.      
  63.     return 0;  
  64. }   
  65.   
  66. static int pi_led_release(struct inode *inode,struct file *file){  
  67.      printk("Led has release!\n");  
  68.      return 0;  
  69. }  
  70.   
  71. //file_operations使系统的open,ioctl等函数指针指向我们所写的led_open等函数,  
  72. //这样系统才能够调用  
  73. static struct file_operations pi_led_dev_fops = {   
  74.    .owner          =THIS_MODULE,   
  75.    .open       =pi_led_open,   
  76.    .unlocked_ioctl = pi_led_ioctl,   
  77.    .release       = pi_led_release,  
  78. };   
  79.   
  80. static int is_right_chip(struct gpio_chip *chip, void *data)  
  81. {  
  82.   
  83.     if (strcmp(data, chip->label) == 0)  
  84.         return 1;  
  85.     return 0;  
  86. }  
  87.   
  88. //内核加载后的初始化函数.  
  89. static int __init pi_led_init(void)  
  90. {  
  91.    struct device *dev;  
  92.    int major; //自动分配主设备号  
  93.    major = alloc_chrdev_region(&pi_led_devno,0,1,DRIVER_NAME);  
  94.    //register_chrdev 注册字符设备使系统知道有LED这个模块在.  
  95.      
  96.    cdev_init(&pi_led_class_dev, &pi_led_dev_fops);  
  97.    major = cdev_add(&pi_led_class_dev,pi_led_devno,1);  
  98.    //注册class  
  99.    pi_led_class = class_create(THIS_MODULE,DRIVER_NAME);  
  100.      
  101.    dev = device_create(pi_led_class ,NULL,pi_led_devno,NULL,DRIVER_NAME);  
  102.      
  103.    gpiochip = gpiochip_find("bcm2708_gpio", is_right_chip);  
  104.    gpiochip->direction_output(gpiochip, led_pin, 1);  
  105.    gpiochip->set(gpiochip, led_pin, 0);  
  106.    printk("pi led init ok!\n");  
  107.    return 0;  
  108. }  
  109. //内核卸载后的销毁函数.  
  110. void pi_led_exit(void)  
  111. {  
  112.    gpio_free(led_pin);  
  113.    device_destroy(pi_led_class,pi_led_devno);  
  114.    class_destroy(pi_led_class);  
  115.    cdev_del(&pi_led_class_dev);  
  116.    unregister_chrdev_region(pi_led_devno, 1);  
  117.    printk("pi led exit ok!\n");  
  118.      
  119. }  
  120.   
  121. module_init(pi_led_init);  
  122. module_exit(pi_led_exit);  
  123.   
  124. MODULE_DESCRIPTION("Rasp Led Driver");  
  125. MODULE_AUTHOR("Embbnux Ji < http://blog.csdn.net/embbnux >");  
  126. MODULE_LICENSE("GPL");  
复制代码
二  源码框架分析

    我们首先从内核模块的入口,module_init(pi_led_init)这个函数看起,可以看出初始化后调用pi_led_init这个函数.
  1. //内核加载后的初始化函数.  
  2. static int __init pi_led_init(void)  
  3. {  
  4.    struct device *dev;  
  5.    int major; //自动分配主设备号  
  6.    major = alloc_chrdev_region(&pi_led_devno,0,1,DRIVER_NAME);  
  7.    //register_chrdev 注册字符设备使系统知道有LED这个模块在.  
  8.      
  9.    cdev_init(&pi_led_class_dev, &pi_led_dev_fops);  
  10.    major = cdev_add(&pi_led_class_dev,pi_led_devno,1);  
  11.    //注册class  
  12.    pi_led_class = class_create(THIS_MODULE,DRIVER_NAME);  
  13.      
  14.    dev = device_create(pi_led_class ,NULL,pi_led_devno,NULL,DRIVER_NAME);  
  15.      
  16.    gpiochip = gpiochip_find("bcm2708_gpio", is_right_chip);  
  17.    gpiochip->direction_output(gpiochip, led_pin, 1);  
  18.    gpiochip->set(gpiochip, led_pin, 0);  
  19.    printk("pi led init ok!\n");  
  20.    return 0;  
  21. }  
复制代码
初始化时首先分配给这个函数设备号,注册该设备,通过class注册使能够在/dev/目录下自动生成相应的设备文件,用户通过操作这个文件,来告诉内核怎么做.
   由于是字符设备,所以对该文件的操作通过open,write,ioctl等函数,所以要把这个函数和底层的操作函数对应起来,这就要用到file_operation这个结构体,来声明:
  1. //file_operations使系统的open,ioctl等函数指针指向我们所写的led_open等函数,  
  2. //这样系统才能够调用  
  3. static struct file_operations pi_led_dev_fops = {   
  4.    .owner          =THIS_MODULE,   
  5.    .open       =pi_led_open,   
  6.    .unlocked_ioctl = pi_led_ioctl,   
  7.    .release       = pi_led_release,  
  8. };
复制代码
这里就让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. #define GPIOFSEL(x)  (0x00+(x)*4)  
  2. #define GPIOSET(x)   (0x1c+(x)*4)  
  3. #define GPIOCLR(x)   (0x28+(x)*4)  
  4. #define GPIOLEV(x)   (0x34+(x)*4)  
  5. #define GPIOEDS(x)   (0x40+(x)*4)  
  6. #define GPIOREN(x)   (0x4c+(x)*4)  
  7. #define GPIOFEN(x)   (0x58+(x)*4)  
  8. #define GPIOHEN(x)   (0x64+(x)*4)  
  9. #define GPIOLEN(x)   (0x70+(x)*4)  
  10. #define GPIOAREN(x)  (0x7c+(x)*4)  
  11. #define GPIOAFEN(x)  (0x88+(x)*4)  
  12. #define GPIOUD(x)    (0x94+(x)*4)  
  13. #define GPIOUDCLK(x) (0x98+(x)*4)
复制代码
这里定义的就是相应的GPIO寄存器的地址.
四  测试程序
    ssh进入树莓派,在主目录下新建led.c
  1. #include<stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <sys/ioctl.h>  
  5. #include <sys/time.h>  
  6. int main(int argc, char **argv)  
  7. {  
  8.     int on;  
  9.     int led_no;  
  10.     int fd;  
  11.     int i;  
  12.   
  13.     fd = open("/dev/pi_led", 0);  
  14.     if (fd < 0) {  
  15.        fd = open("/dev/pi_led", 0);  
  16.     }  
  17.     if (fd < 0) {  
  18.       perror("open device led");  
  19.       exit(1);  
  20.     }  
  21.     for(i=0;i<=20;i++){  
  22.       on = i%2;  
  23.       ioctl(fd, on, led_no);  
  24.       sleep(1);  
  25.     }  
  26.     close(fd);  
  27.     return 0;  
  28. }  
复制代码


分享到:
回复

使用道具 举报

回答|共 7 个

倒序浏览

沙发

东隅

发表于 2014-1-16 16:41:23 | 只看该作者

竟然被你给转过来了啊,有人看的感觉真是开心啊
板凳

东隅

发表于 2014-1-16 16:47:17 | 只看该作者

东隅 发表于 2014-1-16 16:41
竟然被你给转过来了啊,有人看的感觉真是开心啊

希望能把原文链接改为超链接
地板

anti-t

发表于 2014-1-16 19:28:21 | 只看该作者

不错哦-------------
5#

木林森X

发表于 2014-1-17 14:27:07 | 只看该作者

这么长,看着眼晕
6#

jauhua

发表于 2014-1-19 09:58:34 | 只看该作者

路过                          
7#

haothree

发表于 2014-1-20 14:49:23 | 只看该作者

东隅 发表于 2014-1-16 16:47
希望能把原文链接改为超链接

论坛好像不解析外部超链{:soso_e143:}
8#

woaidabaobao

发表于 2014-1-21 17:34:04 | 只看该作者

文章不错,顶个!!!
您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条