• 3.修改第一个程序来点亮LED


    在上一节中已经将驱动程序框架搭建好了

    接下来开始写硬件的操作(控制LED):

    (1)看原理图,确定引脚

    (2)看2440手册

    (3)写代码(需要使用ioremap()函数映射虚拟地址,在linux中只能使用虚拟地址)

    (4)修改上一节的测试程序

    (5)使用次设备号来控制设备下不同的灯

    1.看led引脚

     

    最终确定: LED1 ->GPF4  LED2 ->GPF5   LED3 ->GPF6

    2.看2440手册

    配置GPFCON[15:0](0x56000050)的位[8:9]、位[10:11]、位[12:13] 都等于0x01(输出模式)

    控制GPFDAT[7:0](0x56000054)中的位4~6来使灯亮灭(低电平亮)

    3.写代码

    3.1添加全局变量:

    volatile unsigned long *GPFcon=NULL;       
    
    volatile unsigned long *GPFdat=NULL;

    3.2 first_drv_init入口函数中使用ioremap()映射虚拟地址:

    GPFcon = ioremap(0x56000050, 16);   //ioremap:物理地址映射,返回虚拟地址
    
    GPFdat=GPFcon+1;             //long:32位,所以GPFdat=0x56000050+(32/8)

    3.3 first_drv_exit出口函数中注销虚拟地址:

    iounmap(GPFcon);          //注销虚拟地址

    3.4 first_drv_open函数中添加配置GPFCON:

    *GPFcon&=~ ((0X11<<8)| (0X11<<10)| (0X11<<12)); 
    
    *GPFcon|=    ((0X01<<8)| (0X01<<10)| (0X01<<12)); 

    3.5 first_drv_write函数中添加拷贝应用层数据,然后来控制GPFDAT:

    /*copy_to_user():将数据上给用户*/
    copy_from_user(&val,buf,count);      //从用户(应用层)拷贝数据                                                            
     if(val==1)                  //点灯(低电平亮)
     {  
          *GPFdat&=~((0X1<<4)| (0X1<<5)| (0X1<<6)); 
     }
     else                  //灭灯
     {
         *GPFdat|=((0X1<<4)| (0X1<<5)| (0X1<<6));    
     }

    4.修改测试程序main()

    代码如下:

    int main(int argc,char **argv) //argc:参数个数,argv数组
    {
    int fd1, fd2;
    int val=1;
    fd1 = open("/dev/xyz",O_RDWR);  //打开/dev/xxx设备节点
    if(fd1<0)                   //无法打开,返回-1
      printf("can't open%d!
    ", fd1); 
       if(argc!=2)
         {
                printf("Usage:
    ");
                printf("%s <on|off>",argv[0]);
                return 0;
         }
    
      if(strcmp(argv[1],"on")==0)   //开灯
          {
              printf("led on...
    ");
              val=1;
          } 
      else                         //关灯
          {
              printf("led off...
    ");
              val=0;
          }
    
    write(fd1, &val, 4);
    return 0;
    }

    当输入first_driver_text on点3个灯, 否则关3个灯

    若参数不等于2时,不能控制点灯

    如果我们想分别控制不同的灯,该怎么做?

    可以使用此设备号,此设备号就是用来区分同一设备下不同子设备

    5使用次设备号来控制设备下不同的灯

    我们先来看下面两个函数MAJOR和MINOR,分别是提取主次设备号

    minor=MINOR(inode->i_rdev);    //open函数中提取次设备号
    major=MAJOR(inode->i_rdev);    //open函数中提取主设备号
    
    minor=MINOR (file->f_dentry->d_inode->i_rdev);  //write/read函数中提取次设备号
    major= MAJOR (file->f_dentry->d_inode->i_rdev); //write/read函数中提取主设备号

    思路如下:

    在测试程序中:

    通过dev[1]来open打开不同的子设备节点,然后通过dev[2]来write写入数据

    实例: first_driver_text led1 on        //点亮led1

    在first_dev.c驱动文件中:

    first_drv_init函数中创建不同的子设备节点

    first_drv_exti函数中注销不同的子设备节点

    first_drv_open函数中通过MINOR(inode->i_rdev)来初始化不同的灯

    first_drv_write函数中通过MINOR(file->f_dentry->d_inode->i_rdev)来控制不同的灯

    如下图,insmod后自动注册3个设备节点

     

    测试程序如下:     

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
     
     /*
      *  ledtest <dev> <on|off>
      */
    
    void print_usage(char *file)         //报错打印帮助
    {
        printf("Usage:
    ");
        printf("%s <dev> <on|off>
    ",file);
        printf("eg. 
    ");
        printf("%s /dev/leds on
    ", file);
        printf("%s /dev/leds off
    ", file);
        printf("%s /dev/led1 on
    ", file);
        printf("%s /dev/led1 off
    ", file);
    } 
    
    int main(int argc, char **argv)
    {
        int fd;
        char* filename;
        char val;
        
    if (argc != 3) { print_usage(argv[0]); return 0; } filename = argv[1]; fd = open(filename, O_RDWR); if (fd < 0) { printf("error, can't open %s ", filename); return 0; } if (!strcmp("on", argv[2])) { // 亮灯 val = 0; write(fd, &val, 1); } else if (!strcmp("off", argv[2])) { // 灭灯 val = 1; write(fd, &val, 1); } else //数据输入错误,打印帮助提示 { print_usage(argv[0]); return 0; } return 0; }

    驱动程序如下:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <asm/irq.h>
    #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <asm/uaccess.h> #include <asm/io.h> static struct class *firstdrv_class; //创建一个class类 static struct class_device *firstdrv_class_devs[4]; //创建类的设备,led,led1,led2,led3 volatile unsigned long *GPFcon=NULL; volatile unsigned long *GPFdat=NULL; /*1写出驱动程序first_drv_open first_drv_write */ static int first_drv_open(struct inode *inode, struct file *file) { int minor=MINOR(inode->i_rdev); printk("first_drv_open "); //打印,在内核中打印只能用printk() GPFcon = ioremap(0x56000050, 16); //ioremap:物理地址映射,返回虚拟地址 GPFdat=GPFcon+1; //long:32位,所以GPFdat=0x56000050+(32/8)
    switch(minor) { case 0: //进入led设备,控制所有led *GPFcon&=~ ((0X3<<8)| (0X3<<10)| (0X3<<12)); *GPFcon|= ((0X01<<8)| (0X01<<10)| (0X01<<12)); break; case 1: //进入led1设备,控制 led1 *GPFcon&=~ ((0X3<<8) ); *GPFcon|= (0X1<<8) ; break; case 2: //进入led2设备,控制 led2 *GPFcon&=~ ((0X3<<10) ); *GPFcon|= (0X1<<10) ; break; case 3: //进入led3设备,控制 led3 *GPFcon&=~ ((0X3<<12) ); *GPFcon|= ((0X1<<12) ); break; } return 0; } /*参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界*/ static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; int minor=MINOR(file->f_dentry->d_inode->i_rdev); copy_from_user(&val,buf,count); //通过用户(应用层)拷贝数据 switch(minor) { case 0: //进入led设备,控制所有led printk("led0,%d ",val); if(val) //开灯 {*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); *GPFdat|= ((0X0<<4)| (0X0<<5)| (0X0<<6)); } else //关灯 {*GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); *GPFdat|= ((0X1<<4)| (0X1<<5)| (0X1<<6)); } break; case 1: //进入led1设备,控制 led1 printk("led1,%d ",val); if(val) //开灯 {*GPFdat&=~ (0X1<<4); *GPFdat|= (0X0<<4); } else //关灯 { *GPFdat&=~ (0X1<<4); *GPFdat|= (0X1<<4); } break; case 2: //进入led2设备,控制 led2 printk("led2,%d ",val); if(val) //开灯 {*GPFdat&=~ (0X1<<5); *GPFdat|= (0X0<<5); } else //关灯 {*GPFdat&=~ (0X1<<5); *GPFdat|= (0X1<<5); } break; case 3: //进入led3设备,控制 led3 printk("led3,%d ",val); if(val) //开灯 {*GPFdat&=~ (0X1<<6); *GPFdat|= ( 0X0<<6); } else //关灯 {*GPFdat&=~ (0X1<<6); *GPFdat|= (0X1<<6); } break; } return 0; } /*2定义file_operations结构体来封装驱动函数first_drv_open first_drv_write */ static struct file_operations first_drv_fops = { .owner = THIS_MODULE, //被使用时阻止模块被卸载 .open = first_drv_open, .write = first_drv_write, }; int major; //定义一个全局变量,用来保存主设备号 int first_drv_init(void) { int i; /*3 register_chrdev注册字符设备*/ /*如果设置major为0,表示由内核动态分配主设备号,函数的返回值是主设备号*/ major=register_chrdev (0, "first_drv", &first_drv_fops);
    firstdrv_class
    = class_create(THIS_MODULE,"firstdrv"); //创建类,它会在sys目录下创建firstdrv这个类 firstdrv_class_devs[0]=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"led"); //创建类设备,它会在firstdrv_class类下创建led设备,然后mdev通过这个自动创建/dev/xyz这个设备节点 for(i=1;i<4;i++) //创建led1 led2 led3 设备节点,控制led1 led2 led3 { firstdrv_class_devs[i]=class_device_create(firstdrv_class,NULL,MKDEV(major,i),NULL,"led%d",i); } return 0; } /*6 写first_drv_exit出口函数*/ void first_drv_exit(void) { int i; unregister_chrdev (major, "first_drv"); //卸载驱动,只需要主设备号和设备名就行 class_destroy(firstdrv_class); //注销类,与class_create对应 for(i=0;i<4;i++) //注销类设备led,led1,led2,led3 class_device_unregister(firstdrv_class_devs[i]);
    iounmap(GPFcon);
    //注销虚拟地址 } /*5 module_init修饰入口函数*/ module_init(first_drv_init); /*7 module_exit修饰出口函数*/ module_exit(first_drv_exit); MODULE_LICENSE("GPL v2"); //声明许可证

     下章学习: 

    4.查询方式来写按键驱动程序(详解)

  • 相关阅读:

    泛型
    特性
    2.Linux基础入门
    1.Linux介绍及系统安装
    php十七种设计模式适用场合总结
    转载zendframework 插件方式
    常用的Jquery代码片段,没事的时候研究一下
    Zend Framework1.9 模块化搭建
    PHP的设计模式总结命令链模式
  • 原文地址:https://www.cnblogs.com/lifexy/p/7506355.html
Copyright © 2020-2023  润新知