使用IO内存控制硬件点亮LED灯

来源:互联网 发布:工业数据采集卡 编辑:程序博客网 时间:2024/06/06 00:30

Linux中控制GPIO点亮LED的方法有好几种。一种是使用内核提供的专门用来控制GPIO的函数来点亮LED,如:

s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);

              s3c2410_gpio_setpin(led_table[i], 0);

一种是通过IO内存获取硬件地址从而控制GPIO来点亮LED,如:

void *ioremap(unsigned long phys_addr, unsigned long size);

void ioumap(void *addr);

这里主要介绍第二种点亮LED方式。

1. 理论支撑

1.1 IO内存分配和映射

1.1.1在使用IO内存之前首先分配IO内存区域。分配和撤销IO内存区域使用的函数如下:

       #include <linux/ioport.h>

       struct resource *request_mem_region(unsigned long start,unsigned long len,char *name);

参数说明:start 分配内存起始地址

                len  分配内存长度

          返回 成功非NULL,否则返回NULL。

相应的释放函数:

              void release_mem_region(unsigned long start,unsigned long len);

 

1.1.2在linux中不能使用实际的物理地址,要对指定的物理地址进行操作必须先将物理地址映射到虚拟地址中。下面的函数就是实现物理地址到虚拟地址的映射:

#include <asm/io.h>

void *ioremap(unsigned long phys_addr, unsigned long size);

参数说明:phys_addr 需要访问的物理内存(寄存器)的首地址

                size       内存区域大小

返回与该段物理地址对应的虚拟地址

      

相应的撤销映射关系的函数是:

void ioumap(void *addr);

 

1.1.3使用IO内存时,request_mem_region函数并不是必须的,该函数只是在内核中标志该内存区域已经分配出去,不能再进行分配出去。但是,这不不是说别的驱动不能再使用该IO内存。至于能不能使用,分下面2种情况:

       1. 两个驱动都用request_mem_region分配相同的IO内存,则两个驱动只能有一个驱动可以使用。

       2. 两个驱动只有一个使用了request_mem_region函数,那么这两个驱动可以同时使用,并可以同时访问该IO内存。

 

1.2 访问IO内存

       ioremap函数的返回值可以直接当做指针(指向对应的物理内存(寄存器)地址)使用,但是这种使用方式不具有可移植性。使用下面的访问IO内存的专用函数符合可移植性要求。

 

从 I/O 内存读取, 使用下列函数之一:

unsigned int ioread8(void *addr);

unsigned int ioread16(void *addr);

unsigned int ioread32(void *addr);

参数:addr是从 ioremap 获得的地址(可能包含一个整数偏移量)

返回值: 从给定 I/O 内存读取的到的值

 

相应的有一系列函数来写 I/O 内存:

void iowrite8(u8 value, void *addr);

void iowrite16(u16 value, void *addr);

void iowrite32(u32 value, void *addr);

参数:value 要写入IO内存值

      addr 所要写入的IO内存地址

 

2. 具体操作与实现

2.1只使用ioremap映射GPIO寄存器实现LED点亮

      

内核模块代码(基于mini2440开发板的4个LED) 如下:

/*************************************************************************/

#include <linux/module.h>

#include <linux/init.h>

#include <asm/io.h>

 

volatile unsigned long virt, phys;//用于存放虚拟地址和物理地址

volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;//用与存放三个寄存器的地址

 

void led_device_init(void)

{

      // 0x56000000 + 0xd0 包揽全所有的IO引脚寄存器地址

       phys = 0x56000000; // 0x56000000=GPACON

       //在虚拟地址空间中申请一块长度为0xd0的连续空间

       //这样,物理地址phys到phys+0xd0对应虚拟地址virt到virt+0xd0

       virt =(unsigned long)ioremap(phys, 0xd0);                                                                                           

       GPBCON = (unsigned long *)(virt + 0x10);//指定需要操作的三个寄存器的地址

       GPBDAT = (unsigned long *)(virt + 0x14);

       GPBUP  = (unsigned long *)(virt + 0x18);

}

//led配置函数,这种是裸板控制寄存器的方式

void led_configure(void)

{

       *GPBCON &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);//GPB12 defaule

       *GPBCON |= (1 << 10)|(1<<12)|(1<<14)|(1<<16); //output

       *GPBUP |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8);  //禁止上拉电阻

}

void led_on(void) //点亮led

{

       *GPBDAT &= ~(1 << 5)&~(1 << 6)&~(1 << 7)&~(1 << 8);

}

void led_off(void) //灭掉led

{

       *GPBDAT |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);

}

static int __init test_init(void) //模块初始化函数

{

       led_device_init(); //实现IO内存的映射

       led_configure();  //配置GPB5-8为输出

       led_on();

       printk("hello led!\n");

       return 0;

}

static void __exit test_exit(void) //模块卸载函数

{

       led_off();

       iounmap((void *)virt); //撤销映射关系

       printk("bye\n");

}

module_init(test_init);

module_exit(test_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("hrz.2ml");

MODULE_VERSION("v0.1");

/*************************************************************************/

该模块在安装时点亮所有的LED灯,模块卸载时关闭所有LED灯。这个模块只使用了ioremap一个函数,把寄存器地址映射到虚拟地址,其余的操作都是基于裸板控制GPIO寄存器的方式。

2.2使用ioremap映射GPIO寄存器,内核函数读写寄存器实现LED点亮

上面模块的实现方式像我们在裸板上点亮LED一样,对于从裸机转到操作系统的同学,这样的控制方式很容易理解。但是,这样的方式不具有可移植性,我们应该尽量使用内核提供的IO内存访问接口,即我们上面介绍的函数:

       unsigned int ioread32(void *addr);

       void iowrite32(u32 value, void *addr);

 

接着修改一下函数(蓝色字体为添加代码),以内核接口实现LED点亮:

/*************************************************************************/

#include <linux/module.h>

#include <linux/init.h>

#include <asm/io.h>

    

volatile unsigned long virt, phys;//用于存放虚拟地址和物理地址

volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;//用与存放三个寄存器的地址

unsigned long reg;

void led_device_init(void)

{

       phys = 0x56000000; //指定物理地址 0x56000000=GPACON

       virt =(unsigned long)ioremap(phys, 0xd0);                                                         

       GPBCON = (unsigned long *)(virt + 0x10);

       GPBDAT = (unsigned long *)(virt + 0x14);

       GPBUP  = (unsigned long *)(virt + 0x18);

}

void led_configure(void) //led配置函数

{

       //*GPBCON &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);//GPB12 defaule

       //*GPBCON |= (1 << 10)|(1<<12)|(1<<14)|(1<<16); //output

       reg=ioread32(GPBCON);//读取GPBCON值到reg中

       reg &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);

       reg |=(1 << 10)|(1<<12)|(1<<14)|(1<<16); //output

       iowrite32(reg,GPBCON);//把修改后的reg值写回GPBCON

 

       //*GPBUP |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8);  //禁止上拉电阻

       reg=ioread32(GPBUP);

       reg |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8);  //禁止上拉电阻

       iowrite32(reg,GPBUP);

}

void led_on(void) //点亮led

{

       //*GPBDAT &= ~(1 << 5)&~(1 << 6)&~(1 << 7)&~(1 << 8);

       reg=ioread32(GPBDAT);

       reg &= ~(1 << 5)&~(1 <<6)&~(1 << 7)&~(1 <<8);

       iowrite32(reg,GPBDAT);

}

void led_off(void) //灭掉led

{

       //*GPBDAT |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);

       reg=ioread32(GPBDAT);

       reg |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);

       iowrite32(reg,GPBDAT);

}

static int __init test_init(void) //模块初始化函数

{

       led_device_init();

       led_configure();

       led_on();

       printk("hello led!\n");

       return 0;

}

static void __exit test_exit(void) //模块卸载函数

{

       led_off();

       iounmap((void *)virt);

       printk("bye\n");

}

module_init(test_init);

module_exit(test_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("hrz.2ml");

MODULE_VERSION("v0.2");

/***********************************************************************/

这里只是把访问内存的方式改了,由原来的一行代码变成三行。把模块安装到内核中,结果一样。

2.3添加内存分配函数实现LED点亮

在这个模块中添加request_mem_region()进行内存的分配。而调用request_mem_region()不是必须的,但是建议使用。该函数的任务是检查申请的资源是否可用,如果可用则申请成功,并标志为已经使用,其他驱动想再申请该资源时就会失败。

模块代码如下:

/*************************************************************************/

#include <linux/module.h>

#include <linux/init.h>

#include <asm/io.h>

#include <linux/ioport.h>

 

volatile unsigned long virt, phys;//用于存放虚拟地址和物理地址

volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;//用与存放三个寄存器的地址

unsigned long reg;

struct resource *led_resource;

void led_device_init(void)

{

       phys = 0x56000000;

       virt =(unsigned long)ioremap(phys, 0xd0);

                                                                     

       GPBCON = (unsigned long *)(virt + 0x10);

       GPBDAT = (unsigned long *)(virt + 0x14);

       GPBUP = (unsigned long *)(virt + 0x18);

}

void led_configure(void) //led配置函数

{

       //*GPBCON &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);//GPB12 defaule

       //*GPBCON |= (1 << 10)|(1<<12)|(1<<14)|(1<<16); //output

       reg=ioread32(GPBCON);

       reg &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);

       reg |=(1 << 10)|(1<<12)|(1<<14)|(1<<16); //output

       iowrite32(reg,GPBCON);

 

       //*GPBUP |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8);  //禁止上拉电阻

       reg=ioread32(GPBUP);

       reg |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8);  //禁止上拉电阻

       iowrite32(reg,GPBUP);

}

void led_on(void) //点亮led

{

       //*GPBDAT &= ~(1 << 5)&~(1 << 6)&~(1 << 7)&~(1 << 8);

       reg=ioread32(GPBDAT);

       reg &= ~(1 << 5)&~(1 <<6)&~(1 << 7)&~(1 <<8);

       iowrite32(reg,GPBDAT);

}

void led_off(void) //灭掉led

{

       //*GPBDAT |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);

       reg=ioread32(GPBDAT);

       reg |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);

       iowrite32(reg,GPBDAT);

}

static int __init test_init(void) //模块初始化函数

{

       led_device_init();

      

       led_resource=request_mem_region(phys,0xd0,"LED_MEM");

       if(led_resource==NULL){

              printk("request mem for led error!\n");

              return -ENOMEM;

       }

       led_configure();

       led_on();

       printk("hello led!\n");

       return 0;

}

static void __exit test_exit(void) //模块卸载函数

{

       if(led_resource!=NULL){

              led_off();

              iounmap((void *)virt);

              release_mem_region(phys,0xd0);

       }

       printk("bye\n");

}

module_init(test_init);

module_exit(test_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("hrz.2ml");

MODULE_VERSION("v0.3");

/*************************************************************************/

结果还是一样。安装后执行:

# cat /proc/iomem

就会看到:

56000000-560000cf : LED_MEM

 

2.4最终的LED驱动程序

/*********************************************************************/

#include <asm/io.h>

#include <asm/sizes.h>

#include <linux/ioport.h>

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <mach/regs-gpio.h>

#include <mach/hardware.h>

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/types.h>

#include <linux/moduleparam.h>

#include <linux/slab.h>

#include <linux/errno.h>

#include <linux/ioctl.h>

#include <linux/cdev.h>

#include <linux/string.h>

#include <linux/list.h>

#include <linux/pci.h>

#include <linux/gpio.h>

#include <asm/uaccess.h>

#include <asm/unistd.h>

#include <linux/init.h>

#include <linux/mm.h>

#include <linux/fs.h>

#include <asm/atomic.h>

 

volatile unsigned long virt, phys;

volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;

unsigned long reg;

struct resource *led_resource;

 

dev_t my_major=0;//主设备号

#define MY_DEVNAME " leds" //设备名称

//设备结构体

struct my_led {

       struct cdev my_cdev;

};

struct my_led *my_ledp;

struct class *my_class;

 

void led_device_init(void)

{

       //0x56000000=GPACON 0x56000000 + 0xd0 包揽所有的IO引脚寄存器地址

       phys = 0x56000000;

       //在虚拟地址空间中申请一块长度为0xd0的连续空间

       //这样,物理地址phys到phys+0xd0对应虚拟地址virt到virt+0xd0

       virt =(unsigned long)ioremap(phys, 0xd0);

                                        

       GPBCON = (unsigned long *)(virt + 0x10);

       GPBDAT = (unsigned long *)(virt + 0x14);

       GPBUP = (unsigned long *)(virt + 0x18);

}

void led_configure(void)

{

       reg=ioread32(GPBCON);

       reg &= ~(3 << 10)&~(3<<12)&~(3 << 14)&~(3<<16);

       reg |=(1 << 10)|(1<<12)|(1<<14)|(1<<16); //output

       iowrite32(reg,GPBCON);

 

       reg=ioread32(GPBUP);

       reg |= (1 << 5)|(1 <<6)|(1 <<7)|(1 <<8);

       iowrite32(reg,GPBUP);

}

void led_on(void)

{

       //*GPBDAT &= ~(1 << 5)&~(1 << 6)&~(1 << 7)&~(1 << 8);

       reg=ioread32(GPBDAT);

       reg &= ~(1 << 5)&~(1 <<6)&~(1 << 7)&~(1 <<8);

       iowrite32(reg,GPBDAT);

}

static int leds_ioctl(struct        inode *inode, struct        file *file, unsigned int  cmd, unsigned long arg)

{

       switch(cmd) {

       case 0:

       case 1:

              if (arg > 4)

              {

                     return -EINVAL;

              }

              reg=ioread32(GPBDAT);

              if(cmd==1)

                     reg &= ~(1 << (arg+5));

              if(cmd==0)

                     reg |= (1 << (arg+5));

              iowrite32(reg,GPBDAT);

 

              return 0;

       default:

              return -EINVAL;

       }

}

void led_off(void)

{

       reg=ioread32(GPBDAT);

       reg |= (1 << 5)|(1 << 6)|(1 << 7)|(1 << 8);

       iowrite32(reg,GPBDAT);

}

static const struct file_operations my_led_fops={

       .owner=THIS_MODULE,

       .ioctl=leds_ioctl,

};

static int __init test_init(void)

{

       dev_t devno=MKDEV(my_major,0);

       int result;

 

       led_device_init();

      

       led_resource=request_mem_region(phys,0xd0,"LED_MEM");

       if(led_resource==NULL){

              printk("request mem for led error!\n");

              return -ENOMEM;

       }

       led_configure();

       led_on();

       //设备号申请

       if(my_major) {

              result=register_chrdev_region(devno,1,MY_DEVNAME);

       }

       else {

              result=alloc_chrdev_region(&devno,0,1,MY_DEVNAME);

              my_major=MAJOR(devno);

       }

       if(result<0)

              return result;

       //设备结构体内存申请并初始化

       my_ledp=kmalloc(sizeof(struct my_led),GFP_KERNEL);

       if(!my_ledp) {

              result=-ENOMEM;

              goto fail_malloc;

       }

       memset(my_ledp,0,sizeof(struct my_led));

 

       //添加led的cdev

       cdev_init(&my_ledp->my_cdev,&my_led_fops);

       my_ledp->my_cdev.owner=THIS_MODULE;

       cdev_add(&my_ledp->my_cdev,devno,1);

 

       //创建设备文件

       my_class=class_create(THIS_MODULE,"my_class");

       device_create(my_class,NULL,MKDEV(my_major,0),NULL,"my_led""%d",0);

 

       printk("hello led!\n");

       return 0;

 

fail_malloc:

       unregister_chrdev_region(devno,1);

       return 0;

}

static void __exit test_exit(void)

{

       if(led_resource!=NULL){

              led_off();

              iounmap((void *)virt);

              release_mem_region(phys,0xd0);

       }

       cdev_del(&my_ledp->my_cdev);

       kfree(my_ledp);

       unregister_chrdev_region(MKDEV(my_major,0),1);

 

       device_destroy(my_class,MKDEV(my_major,0));

       class_destroy(my_class);

       printk("bye\n");

}

module_init(test_init);

module_exit(test_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("hrz.2ml");

MODULE_VERSION("v0.4");

/*********************************************************************/

3. 总结

IO内存的分配以及使用如下图所示:

其中第一步和最后一步可以省去。如果没有省去,那么所分配IO内存就会在内核中标志已经被使用,其他驱动再去分配该IO内存就会失败。但是如果驱动程序不使用request_mem_region()分配IO内存而是映射后直接操作该IO内存是允许的。

原创粉丝点击