使用IO内存控制硬件点亮LED灯.docx
- 文档编号:30143445
- 上传时间:2023-08-05
- 格式:DOCX
- 页数:19
- 大小:30.62KB
使用IO内存控制硬件点亮LED灯.docx
《使用IO内存控制硬件点亮LED灯.docx》由会员分享,可在线阅读,更多相关《使用IO内存控制硬件点亮LED灯.docx(19页珍藏版)》请在冰豆网上搜索。
使用IO内存控制硬件点亮LED灯
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(unsignedlongphys_addr,unsignedlongsize);
voidioumap(void*addr);
这里主要介绍第二种点亮LED方式。
1.理论支撑
1.1IO内存分配和映射
1.1.1在使用IO内存之前首先分配IO内存区域。
分配和撤销IO内存区域使用的函数如下:
#include
structresource*request_mem_region(unsignedlongstart,unsignedlonglen,char*name);
参数说明:
start分配内存起始地址
len 分配内存长度
返回成功非NULL,否则返回NULL。
相应的释放函数:
voidrelease_mem_region(unsignedlongstart,unsignedlonglen);
1.1.2在linux中不能使用实际的物理地址,要对指定的物理地址进行操作必须先将物理地址映射到虚拟地址中。
下面的函数就是实现物理地址到虚拟地址的映射:
#include
void*ioremap(unsignedlongphys_addr,unsignedlongsize);
参数说明:
phys_addr需要访问的物理内存(寄存器)的首地址
size 内存区域大小
返回与该段物理地址对应的虚拟地址
相应的撤销映射关系的函数是:
voidioumap(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内存读取,使用下列函数之一:
unsignedintioread8(void*addr);
unsignedintioread16(void*addr);
unsignedintioread32(void*addr);
参数:
addr是从ioremap获得的地址(可能包含一个整数偏移量)
返回值:
从给定I/O内存读取的到的值
相应的有一系列函数来写I/O内存:
voidiowrite8(u8value,void*addr);
voidiowrite16(u16value,void*addr);
voidiowrite32(u32value,void*addr);
参数:
value要写入IO内存值
addr所要写入的IO内存地址
2.具体操作与实现
2.1只使用ioremap映射GPIO寄存器实现LED点亮
内核模块代码(基于mini2440开发板的4个LED)如下:
/*************************************************************************/
#include
#include
#include
volatileunsignedlongvirt,phys;//用于存放虚拟地址和物理地址
volatileunsignedlong*GPBCON,*GPBDAT,*GPBUP;//用与存放三个寄存器的地址
voidled_device_init(void)
{
//0x56000000+0xd0包揽全所有的IO引脚寄存器地址
phys=0x56000000;//0x56000000=GPACON
//在虚拟地址空间中申请一块长度为0xd0的连续空间
//这样,物理地址phys到phys+0xd0对应虚拟地址virt到virt+0xd0
virt=(unsignedlong)ioremap(phys,0xd0);
GPBCON=(unsignedlong*)(virt+0x10);//指定需要操作的三个寄存器的地址
GPBDAT=(unsignedlong*)(virt+0x14);
GPBUP =(unsignedlong*)(virt+0x18);
}
//led配置函数,这种是裸板控制寄存器的方式
voidled_configure(void)
{
*GPBCON&=~(3<<10)&~(3<<12)&~(3<<14)&~(3<<16);//GPB12defaule
*GPBCON|=(1<<10)|(1<<12)|(1<<14)|(1<<16);//output
*GPBUP|=(1<<5)|(1<<6)|(1<<7)|(1<<8); //禁止上拉电阻
}
voidled_on(void)//点亮led
{
*GPBDAT&=~(1<<5)&~(1<<6)&~(1<<7)&~(1<<8);
}
voidled_off(void)//灭掉led
{
*GPBDAT|=(1<<5)|(1<<6)|(1<<7)|(1<<8);
}
staticint__inittest_init(void)//模块初始化函数
{
led_device_init();//实现IO内存的映射
led_configure(); //配置GPB5-8为输出
led_on();
printk("helloled!
\n");
return0;
}
staticvoid__exittest_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内存访问接口,即我们上面介绍的函数:
unsignedintioread32(void*addr);
voidiowrite32(u32value,void*addr);
接着修改一下函数(蓝色字体为添加代码),以内核接口实现LED点亮:
/*************************************************************************/
#include
#include
#include
volatileunsignedlongvirt,phys;//用于存放虚拟地址和物理地址
volatileunsignedlong*GPBCON,*GPBDAT,*GPBUP;//用与存放三个寄存器的地址
unsignedlongreg;
voidled_device_init(void)
{
phys=0x56000000;//指定物理地址0x56000000=GPACON
virt=(unsignedlong)ioremap(phys,0xd0);
GPBCON=(unsignedlong*)(virt+0x10);
GPBDAT=(unsignedlong*)(virt+0x14);
GPBUP =(unsignedlong*)(virt+0x18);
}
voidled_configure(void)//led配置函数
{
//*GPBCON&=~(3<<10)&~(3<<12)&~(3<<14)&~(3<<16);//GPB12defaule
//*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);
}
voidled_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);
}
voidled_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);
}
staticint__inittest_init(void)//模块初始化函数
{
led_device_init();
led_configure();
led_on();
printk("helloled!
\n");
return0;
}
staticvoid__exittest_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
#include
#include
#include
volatileunsignedlongvirt,phys;//用于存放虚拟地址和物理地址
volatileunsignedlong*GPBCON,*GPBDAT,*GPBUP;//用与存放三个寄存器的地址
unsignedlongreg;
structresource*led_resource;
voidled_device_init(void)
{
phys=0x56000000;
virt=(unsignedlong)ioremap(phys,0xd0);
GPBCON=(unsignedlong*)(virt+0x10);
GPBDAT=(unsignedlong*)(virt+0x14);
GPBUP=(unsignedlong*)(virt+0x18);
}
voidled_configure(void)//led配置函数
{
//*GPBCON&=~(3<<10)&~(3<<12)&~(3<<14)&~(3<<16);//GPB12defaule
//*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);
}
voidled_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);
}
voidled_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);
}
staticint__inittest_init(void)//模块初始化函数
{
led_device_init();
led_resource=request_mem_region(phys,0xd0,"LED_MEM");
if(led_resource==NULL){
printk("requestmemforlederror!
\n");
return-ENOMEM;
}
led_configure();
led_on();
printk("helloled!
\n");
return0;
}
staticvoid__exittest_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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
volatileunsignedlongvirt,phys;
volatileunsignedlong*GPBCON,*GPBDAT,*GPBUP;
unsignedlongreg;
structresource*led_resource;
dev_tmy_major=0;//主设备号
#defineMY_DEVNAME"leds"//设备名称
//设备结构体
structmy_led{
structcdevmy_cdev;
};
structmy_led*my_ledp;
structclass*my_class;
voidled_device_init(void)
{
//0x56000000=GPACON0
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 使用 IO 内存 控制 硬件 点亮 LED