linux驱动分析之LED
2012-12-15 20:01
323 查看
本次LED驱动的编写是基于linux-2.6.32.6内核
驱动程序led.c
//相关头文件
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
static int my_gpio_major =0;
/*这里的主要目的是:当需要打印调试信息时使用#define DUG_printk printk
当不需要打印调试信息时使用#define DUG_printk(x...),可是在程序完成的时候方便地屏蔽打印信息
*/
//#define DUG_printk printk
#define DUG_printk(x...)
/*
先定义一个类,在这个类下定义一个设备,这样可以动态的创建设备节点,
避免手动输入的麻烦
*/
static struct class *my_gpio_class;
static void my_gpio_cedv(void);//函数的声明
/*
定义一个cdev结构,这是2.6内核相对2.4的一个新变化
*/
struct my_gpio_dev_t{
struct cdev mycdev;
} my_gpio_cdev;
//应用程序执行open时,该函数将被调用
static int my_gpio_open(struct inode *inode, struct file *file)
{
unsigned int i;
DUG_printk("my_gpio_open \n");
for(i=5;i<9;i++)
{
/*
注意从linux2.6.30.4到linux-2.6.32.60,GPIO物理地址定义时的方式发发生变化
例如 linux2.6.30.4 中
#define S3C2410_GPH6 S3C2410_GPIONO(S3C2410_GPIO_BANKH, 6)定义GPH6
但是linux-2.6.32.60中这样定义S3C2410_GPH(1) 宏定义为
#define S3C2410_GPH(_nr) (S3C2410_GPIO_H_START + (_nr))
在2.6.32内核中,s3c2410_gpio_cfgpin是在include/linux/gpio.h中定义的,要添加这个文件
在sound/soc/s3c24xx/s3c24xx-i2s.c中,第24行添加:
#include <linux/gpio.h>
*/
s3c2410_gpio_cfgpin(S3C2410_GPB(i),S3C2410_GPIO_OUTPUT);//配置端口为输出
s3c2410_gpio_setpin(S3C2410_GPB(i), 0);//刚打开时输出0,四个LED点亮
DUG_printk("LEDS ALL OFF %d \n",i);
}
return 0;
}
//应用程序执行IOCTL时内核该函数被调用
static int my_gpio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
s3c2410_gpio_setpin(S3C2410_GPB(5+arg), cmd);//根据应用程序IOCTL传来的命令控制相应的LED
return 0;
}
//这是一个结构体,也是字符设备的核心,
static struct file_operations my_gpio_fops ={
.owner = THIS_MODULE,
.open = my_gpio_open,
.ioctl= my_gpio_ioctl,
} ;
//初始化部分,执行insmod xxxx.ko时该函数被调用
static int __init my_gpio_init(void)
{ u8 result,minor;
dev_t devno ;//定义一个设备号
if(my_gpio_major)
{
devno= MKDEV(my_gpio_major,0);
/*2.6内核中,当主设备号已知时执行这部分语句,1代表此设备号只有一个,而devno= MKDEV(my_gpio_major,0);里次设备号为0,所以注册的这个设备 次设备号就是0
*/
result = register_chrdev_region(devno, 0, "my_gpio0");
DUG_printk("register_chrdev_region \n");
}
else
{
//当主设备号未知时,执行该语句,下面有相详细分析
result = alloc_chrdev_region(&devno, 0, 1, "my_gpio");
my_gpio_major = MAJOR(devno);//获得主设备号
minor = MINOR(devno);//获得次设备号
DUG_printk("minor =%d \n",minor);
DUG_printk("alloc_chrdev_region my_gpio_major =%d \n",my_gpio_major);
}
if(result)//成功申请设备号 返回0
DUG_printk("register faill \n");
my_gpio_cedv();//调用函数完成cdevd的相关设置
//动态分配设备节点
my_gpio_class = class_create(THIS_MODULE, "my_gpio2");
device_create(my_gpio_class, NULL, MKDEV(my_gpio_major,0),NULL, "my_gpio");
return 0;
}
//完成cdevd的相关设置
static void my_gpio_cedv(void)
{
u8 ret;
dev_t devno ; //定义一个dev_t结构体变量,存储设备号
devno = MKDEV(my_gpio_major,0);//可以通过主次设备号生成dev_t
cdev_init(&my_gpio_cdev.mycdev,&my_gpio_fops);//初始化cedv并建立cdev与file_operations的连接
my_gpio_cdev.mycdev.owner = THIS_MODULE;
ret = cdev_add(&my_gpio_cdev.mycdev,devno,1);;//注册一个cdev
if(ret<0)
DUG_printk("cdev_add faill \n");
}
//模块卸载函数,在执行rmmod时执行,
static void __exit my_gpio_exit(void)
{
cdev_del(&my_gpio_cdev.mycdev);
device_destroy(my_gpio_class, MKDEV(my_gpio_major, 0));
class_destroy(my_gpio_class);
unregister_chrdev_region(MKDEV(my_gpio_major,0), 1);
DUG_printk("my_gpio_exit \n");
}
module_init(my_gpio_init);
module_exit(my_gpio_exit);
MODULE_LICENSE("GPL");
有关cdev的一些操作函数
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
在 调用 cdev_add() 函 数 向系统注册 字符 设备 之前 , 应首先调用
regist er_chrdev_regi on()或 alloc_chrdev_region()函数向系统申请设备号, 这两个函数
的型如下:
int register_chrdev_region(dev_t from, unsigned count, const char
*name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
当设备号已知情况下用
int register_chrdev_region(dev_t from, unsigned count, const char *name )
中from,是dev_t结构,用MKDEV(int major, int minor) 可以通过主次设备号生成dev_t,
其中的count是定义的次设备号的个数,MKDEV(int major, int minor)中的minor是起始次设备号,当minor=0,如果count=5,那么申请的次设备号就是 0,12,3,4,const char *name是设备名称,当使用cat /proc/devicess命令时会在相应的主设备号处看到该名字
当设备号未知的情况下
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
被执行,将动态分配设备号,并保存在dev_t *dev,里,unsigned baseminor是次设备号的起始值,其他参数与int register_chrdev_region(dev_t from, unsigned count, const char *name )意义相同,
2.4内核的注册方法
register_chrdev()仅支持0到255的主设备号,已经不满足
现在多主机的系统,所以cdev出现,2.4内核以前用register_chrdev,
2.6以后用cdev
register_chrdev()注册时,一个主设备号对应0~255个次设备号,
但使用cedv的方法,一个主设备号对应的次设备号的起始值和个数已经确定
自动创建设备节点
头文件#include <linux/device.h>
第一步;创建一个类static struct class *forthdrv_class;
类下创建设备static struct device *forthdrv_class_dev;
第二步
forthdrv_class = class_create(THIS_MODULE, "forth_drv");
forthdrv_class_dev = device_create(forthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */
注意:如果使用cedv,则MKDEV(major, 0)的次设备号需要与注册时对应
释放:device_unregister(my_gpio_device);
class_destroy(my_gpio_class);
我发现也释放时可以使用
class_destroy(led_class);
unregister_chrdev(major, "led");也可以
这个驱动只是一个简单的例子,完整的驱动还需要添加各个函数返回值的判断部分,
编写测试程序,
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#define dug_printf printf
int main(int argc,char **argv)
{
int fd;
int i;
fd = open("/dev/my_gpio",O_RDWR);//打开
while(1)
{
for(i=0;i<4;i++)
{
ioctl(fd,1,i);
sleep(1);//睡眠1S是LED凉1S实现流水灯效果
}
for(i=3;i>=0;i--)
{
ioctl(fd,0,i);
sleep(1);
}
}
return 0;
}
如果想实现开发板启动后自动运行流水灯的话,
第一
把编好的驱动程序放在目录/drivers/char下并修改这个目录下的Kconfig文件添加
config MY_LED
tristate"MY_LED FALSH"
depends on ARCH_S3C2440
---help---
LEDS_flash
并修改这个目录下的Makefile 添加obj-$(CONFIG_MY_LED) += led.o
配置内核,选择
-> Device Drivers
-> Character devices
<*> MY_LED FALSH
*代表编译进内核
编译测试程序生成led_flash,
上传led测试程序led_flash到/xxx目录(
在etc/init.d/rcS 文件添加/xxx/led_test & //&代表后台启动LED测试程序
重启开发板即可
驱动程序led.c
//相关头文件
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
static int my_gpio_major =0;
/*这里的主要目的是:当需要打印调试信息时使用#define DUG_printk printk
当不需要打印调试信息时使用#define DUG_printk(x...),可是在程序完成的时候方便地屏蔽打印信息
*/
//#define DUG_printk printk
#define DUG_printk(x...)
/*
先定义一个类,在这个类下定义一个设备,这样可以动态的创建设备节点,
避免手动输入的麻烦
*/
static struct class *my_gpio_class;
static void my_gpio_cedv(void);//函数的声明
/*
定义一个cdev结构,这是2.6内核相对2.4的一个新变化
*/
struct my_gpio_dev_t{
struct cdev mycdev;
} my_gpio_cdev;
//应用程序执行open时,该函数将被调用
static int my_gpio_open(struct inode *inode, struct file *file)
{
unsigned int i;
DUG_printk("my_gpio_open \n");
for(i=5;i<9;i++)
{
/*
注意从linux2.6.30.4到linux-2.6.32.60,GPIO物理地址定义时的方式发发生变化
例如 linux2.6.30.4 中
#define S3C2410_GPH6 S3C2410_GPIONO(S3C2410_GPIO_BANKH, 6)定义GPH6
但是linux-2.6.32.60中这样定义S3C2410_GPH(1) 宏定义为
#define S3C2410_GPH(_nr) (S3C2410_GPIO_H_START + (_nr))
在2.6.32内核中,s3c2410_gpio_cfgpin是在include/linux/gpio.h中定义的,要添加这个文件
在sound/soc/s3c24xx/s3c24xx-i2s.c中,第24行添加:
#include <linux/gpio.h>
*/
s3c2410_gpio_cfgpin(S3C2410_GPB(i),S3C2410_GPIO_OUTPUT);//配置端口为输出
s3c2410_gpio_setpin(S3C2410_GPB(i), 0);//刚打开时输出0,四个LED点亮
DUG_printk("LEDS ALL OFF %d \n",i);
}
return 0;
}
//应用程序执行IOCTL时内核该函数被调用
static int my_gpio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
s3c2410_gpio_setpin(S3C2410_GPB(5+arg), cmd);//根据应用程序IOCTL传来的命令控制相应的LED
return 0;
}
//这是一个结构体,也是字符设备的核心,
static struct file_operations my_gpio_fops ={
.owner = THIS_MODULE,
.open = my_gpio_open,
.ioctl= my_gpio_ioctl,
} ;
//初始化部分,执行insmod xxxx.ko时该函数被调用
static int __init my_gpio_init(void)
{ u8 result,minor;
dev_t devno ;//定义一个设备号
if(my_gpio_major)
{
devno= MKDEV(my_gpio_major,0);
/*2.6内核中,当主设备号已知时执行这部分语句,1代表此设备号只有一个,而devno= MKDEV(my_gpio_major,0);里次设备号为0,所以注册的这个设备 次设备号就是0
*/
result = register_chrdev_region(devno, 0, "my_gpio0");
DUG_printk("register_chrdev_region \n");
}
else
{
//当主设备号未知时,执行该语句,下面有相详细分析
result = alloc_chrdev_region(&devno, 0, 1, "my_gpio");
my_gpio_major = MAJOR(devno);//获得主设备号
minor = MINOR(devno);//获得次设备号
DUG_printk("minor =%d \n",minor);
DUG_printk("alloc_chrdev_region my_gpio_major =%d \n",my_gpio_major);
}
if(result)//成功申请设备号 返回0
DUG_printk("register faill \n");
my_gpio_cedv();//调用函数完成cdevd的相关设置
//动态分配设备节点
my_gpio_class = class_create(THIS_MODULE, "my_gpio2");
device_create(my_gpio_class, NULL, MKDEV(my_gpio_major,0),NULL, "my_gpio");
return 0;
}
//完成cdevd的相关设置
static void my_gpio_cedv(void)
{
u8 ret;
dev_t devno ; //定义一个dev_t结构体变量,存储设备号
devno = MKDEV(my_gpio_major,0);//可以通过主次设备号生成dev_t
cdev_init(&my_gpio_cdev.mycdev,&my_gpio_fops);//初始化cedv并建立cdev与file_operations的连接
my_gpio_cdev.mycdev.owner = THIS_MODULE;
ret = cdev_add(&my_gpio_cdev.mycdev,devno,1);;//注册一个cdev
if(ret<0)
DUG_printk("cdev_add faill \n");
}
//模块卸载函数,在执行rmmod时执行,
static void __exit my_gpio_exit(void)
{
cdev_del(&my_gpio_cdev.mycdev);
device_destroy(my_gpio_class, MKDEV(my_gpio_major, 0));
class_destroy(my_gpio_class);
unregister_chrdev_region(MKDEV(my_gpio_major,0), 1);
DUG_printk("my_gpio_exit \n");
}
module_init(my_gpio_init);
module_exit(my_gpio_exit);
MODULE_LICENSE("GPL");
有关cdev的一些操作函数
void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
在 调用 cdev_add() 函 数 向系统注册 字符 设备 之前 , 应首先调用
regist er_chrdev_regi on()或 alloc_chrdev_region()函数向系统申请设备号, 这两个函数
的型如下:
int register_chrdev_region(dev_t from, unsigned count, const char
*name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
当设备号已知情况下用
int register_chrdev_region(dev_t from, unsigned count, const char *name )
中from,是dev_t结构,用MKDEV(int major, int minor) 可以通过主次设备号生成dev_t,
其中的count是定义的次设备号的个数,MKDEV(int major, int minor)中的minor是起始次设备号,当minor=0,如果count=5,那么申请的次设备号就是 0,12,3,4,const char *name是设备名称,当使用cat /proc/devicess命令时会在相应的主设备号处看到该名字
当设备号未知的情况下
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
被执行,将动态分配设备号,并保存在dev_t *dev,里,unsigned baseminor是次设备号的起始值,其他参数与int register_chrdev_region(dev_t from, unsigned count, const char *name )意义相同,
2.4内核的注册方法
register_chrdev()仅支持0到255的主设备号,已经不满足
现在多主机的系统,所以cdev出现,2.4内核以前用register_chrdev,
2.6以后用cdev
register_chrdev()注册时,一个主设备号对应0~255个次设备号,
但使用cedv的方法,一个主设备号对应的次设备号的起始值和个数已经确定
自动创建设备节点
头文件#include <linux/device.h>
第一步;创建一个类static struct class *forthdrv_class;
类下创建设备static struct device *forthdrv_class_dev;
第二步
forthdrv_class = class_create(THIS_MODULE, "forth_drv");
forthdrv_class_dev = device_create(forthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */
注意:如果使用cedv,则MKDEV(major, 0)的次设备号需要与注册时对应
释放:device_unregister(my_gpio_device);
class_destroy(my_gpio_class);
我发现也释放时可以使用
class_destroy(led_class);
unregister_chrdev(major, "led");也可以
这个驱动只是一个简单的例子,完整的驱动还需要添加各个函数返回值的判断部分,
编写测试程序,
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#define dug_printf printf
int main(int argc,char **argv)
{
int fd;
int i;
fd = open("/dev/my_gpio",O_RDWR);//打开
while(1)
{
for(i=0;i<4;i++)
{
ioctl(fd,1,i);
sleep(1);//睡眠1S是LED凉1S实现流水灯效果
}
for(i=3;i>=0;i--)
{
ioctl(fd,0,i);
sleep(1);
}
}
return 0;
}
如果想实现开发板启动后自动运行流水灯的话,
第一
把编好的驱动程序放在目录/drivers/char下并修改这个目录下的Kconfig文件添加
config MY_LED
tristate"MY_LED FALSH"
depends on ARCH_S3C2440
---help---
LEDS_flash
并修改这个目录下的Makefile 添加obj-$(CONFIG_MY_LED) += led.o
配置内核,选择
-> Device Drivers
-> Character devices
<*> MY_LED FALSH
*代表编译进内核
编译测试程序生成led_flash,
上传led测试程序led_flash到/xxx目录(
在etc/init.d/rcS 文件添加/xxx/led_test & //&代表后台启动LED测试程序
重启开发板即可
相关文章推荐
- Linux字符设备驱动之Tiny6410 LED驱动分析
- imx6ul linux4.1.15 LED驱动配置及heartbeat源码分析
- 第四部分 linux led驱动代码分析
- linux LCD驱动(一)--硬件分析
- Linux led 驱动
- linux驱动分析之DS1302 RTC
- linux设备模型之uart驱动架构分析
- linux驱动学习之内核线程分析
- ARM-Linux驱动--DM9000网卡驱动分析(二)
- Linux输入子系统框架分析及输入设备驱动编程
- linux驱动注册过程分析--driver_register(一)
- Linux-2.6.20的LCD驱动分析
- linux驱动学习(八) i2c驱动架构(史上最全) davinc dm368 i2c驱动分析【转】
- Linux-Flash驱动(2)-块设备驱动实例分析
- 11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结
- Linux I2C子系统分析-I2C总线驱动&&Linux I2C子系统分析-I2C设备驱动
- s3c2440基于linux的gpio led字符设备驱动实践
- 基于linux-2.6.38.8内核的wifi驱动分析
- Linux-USB总线驱动分析
- Linux字符设备驱动分析