字符设备驱动(四)-led实例

  前面已经讲解了字符设备驱动的基本框架,接下来我们就写一个led的驱动程序,一步步的了解字符设备驱动。我们还是先从应用程序开始。

    #include <sys/types.h>  
    #include <sys/stat.h>  
    #include <fcntl.h>  
    #include <stdio.h>  
    /* firstdrvtest on 
      * firstdrvtest off 
      */  
    int main(int argc, charchar **argv)  
    {  
        int fd;  
        int val = 1;  
         //打开设备 
        fd = open("/dev/led", 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)  
        {  
            val  = 1;  
        }  
        else  
        {  
            val = 0;  
        }  
       //操作led设备
       //对应驱动程序的first_drv_write(file,buf,count)函数   
        write(fd, &val, 4);  
        return 0;  
    }  

  应用程序很简单,就是打开设备,等待控制台输入命令,然后点亮或者熄灭led。我们开始一步步的写led驱动程序,和应用程序相对应。

  • 1 添加头文件
    #include <linux/init.h>  
    #include <linux/module.h>  
    #include <linux/fs.h>  
    #include <linux/cdev.h>  
    #include <linux/device.h>  
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <asm/uaccess.h>
    #include <asm/irq.h>
    #include <asm/io.h>
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
  • 2 定义变量
    // 是应当连接到这个编号范围的设备的名字,出现在/proc/devices和sysfs中  
    #define CHAR_DEV_DEVICE_NAME   "led" 
    // 节点名,出现在/dev中  
    #define CHAR_DEV_NODE_NAME     "led"        
    //出现在/sys/devices/virtual/和/sys/class/中  
    #define CHAR_DEV_CLASS_NAME    "led_dev_class"  
    // class结构用于自动创建设备结点   
    struct class *led_dev_class; 
    //0表示动态分配主设备号,可以设置成未被系统分配的具体的数字。  
    static int major = 0; 
    // 定义一个cdev结构,  
    static struct cdev led_dev_cdev;

    volatile unsigned long *gpfcon = NULL;
    volatile unsigned long *gpfdat = NULL;
  • 3 定义file_operations并初始化成员变量
 // 进行初始化设置,打开设备,对应应用空间的open 系统调用   
    int led_dev_open(struct inode *inode, struct file *filp)  
    {  
        //  这里可以进行一些初始化  
        printk("led_dev device open.\n");  
        /* 配置GPF4,5,6为输出 */
       *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
       *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
        return 0;  
    }  
    // 实现写功能,写设备,对应应用空间的write 系统调用  
    ssize_t led_dev_write(struct file *file,const char __user *buff,size_t count,loff_t *offp)  
    {  
   int val = 0;
   int copy_test;
   printk("led_dev device write.\n");
   /*从用户空间得到操作变量val ,然后操作硬件*/
   copy_test = copy_from_user(&val, buff, count);

   if (copy_test < 0)
   {
      printk("copy_from_user error.\n");
   }
   printk("val: %d\n", val);
   if (val == 0)
   {
      // 点灯
      *gpfdat &= ~((1 << 4) | (1 << 5) | (1 << 6));
   }
   else
   {
      // 灭灯
      *gpfdat |= (1 << 4) | (1 << 5) | (1 << 6);
   }
   return 0;
    }  

   // 实现读功能,读设备,对应应用空间的read 系统调用  
    /*__user. 这种注解是一种文档形式, 注意, 一个指针是一个不能被直接解引用的 
    用户空间地址. 对于正常的编译, __user 没有效果, 但是它可被外部检查软件使 
    用来找出对用户空间地址的错误使用.*/  
    ssize_t led_dev_read(struct file *file,char __user *buff,size_t count,loff_t *offp)  
    {  
        printk("led_dev device read.\n");  
        return 0;  
    }  

        // 释放设备,关闭设备,对应应用空间的close 系统调用  
    static int led_dev_release (struct inode *node, struct file *file)  
    {  
        //  这里可以进行一些资源的释放  
        printk("led_dev device release.\n");  
         return 0; 
    }
    // 实现主要控制功能,控制设备,对应应用空间的ioctl系统调用  
    static int led_dev_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)  
    {    
        printk("led_dev device ioctl.\n");  
        return 0;  
    }    
    //  file_operations 结构体设置,该设备的所有对外接口在这里明确,此处只写出了几常用的  
    static struct file_operations led_dev_fops =   
    {  
        .owner = THIS_MODULE,  
        .open  = led_dev_open,      // 打开设备   
        .release = led_dev_release, // 关闭设备   
        .read  = led_dev_read,      // 实现设备读功能   
        .write = led_dev_write,     // 实现设备写功能   
        .ioctl = led_dev_ioctl,     // 实现设备控制功能   
    };  
  • 4 写完了函数,我们需要用起来,添加到内核。
    // 设备建立子函数,被char_dev_init函数调用    
    static void char_dev_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops)  
    {  
        int err, devno = MKDEV(major, minor);
        /* led_dev_fops 最后从这里添加到内核*/  
        cdev_init(dev, fops);//对cdev结构体进行初始化  
        dev->owner = THIS_MODULE;  
        dev->ops = fops;  
        err = cdev_add(dev, devno, 1);//参数1是应当关联到设备的设备号的数目. 常常是1  
        if(err)  
        {  
            printk(KERN_NOTICE "Error %d adding char_dev %d.\n", err, minor);  
        }  
        printk("char_dev device setup.\n");  
    } 


   // 设备初始化   
    static int led_dev_init(void)  
    {  
        int result;  
        dev_t dev = MKDEV(major, 0);//将主次编号转换为一个dev_t类型  
        if(major)  
        {  
            // 给定设备号注册  
            result = register_chrdev_region(dev, 1, CHAR_DEV_DEVICE_NAME);//1是你请求的连续设备编号的总数  
            printk("char_dev register_chrdev_region.\n");  
        }  
        else  
        {  
            // 动态分配设备号   
            result = alloc_chrdev_region(&dev, 0, 1, CHAR_DEV_DEVICE_NAME);//0是请求的第一个要用的次编号,它常常是 0  
            printk("char_dev alloc_chrdev_region.\n");  
            major = MAJOR(dev);  
        }  
        if(result < 0)//获取设备号失败返回  
        {  
            printk(KERN_WARNING "char_dev region fail.\n");  
            return result;  
        }  
        char_dev_setup_cdev(&led_dev_cdev, 0, &led_dev_fops);  
        printk("The major of the char_dev device is %d.\n", major);  
        //==== 有中断的可以在此注册中断:request_irq,并要实现中断服务程序 ===//  
        // 创建设备节点  
        led_dev_class = class_create(THIS_MODULE,CHAR_DEV_CLASS_NAME);  
        if (IS_ERR(led_dev_class))  
        {  
            printk("Err: failed in creating char_dev class.\n");  
            return 0;  
        }  
    class_device_create(led_dev_class, NULL, dev, NULL, CHAR_DEV_NODE_NAME);
        printk("char_dev device installed.\n");  

       gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
       gpfdat = gpfcon + 1; 

        return 0;  
    }  
  • 5 有注册就有注销函数
  // 设备注销   
    static void char_dev_exit(void)  
    {  
       class_device_destroy(led_dev_class, MKDEV(major, 0));
       class_destroy(led_dev_class);
       unregister_chrdev(major, "leds");
       iounmap(gpfcon);
       cdev_del(&led_dev_cdev); //字符设备的注销
//========  有中断的可以在此注销中断:free_irq  ======//
       printk("led_dev device uninstalled.\n");

    }  
  • 6 注册了设备驱动还不行,还需要添加 一些宏
    module_init(led_dev_init);   //模块初始化接口  
    module_exit(led_dev_exit);   //模块注销接口  
    //所有模块代码都应该指定所使用的许可证,该句不能省略,否则模块加载会报错  
    MODULE_LICENSE("Dual BSD/GPL");  
    MODULE_AUTHOR("Author");  
    MODULE_DESCRIPTION("Driver Description");  

到此字符设备驱动的led就搞定了。
经过我测试的程序 一会上传。
平台:Ubuntu 16.04
内核版本: linux-2.6.22.6
开发板:韦东山JZ2440
调试通过的代码,传送门:
http://download.csdn.net/download/qq_30951423/9910014

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值