您的位置:首页 > 运维架构 > Linux

Linux嵌入式驱动初体验(七)--- LED驱动之字符设备篇

2010-09-26 21:58 513 查看
Linux中的设备可以分为三类:字符设备、块设备、网络设备,对于上一篇文章中的驱动编写的方法,是基于platform结构的,下面我们把它改变成字符设备的驱动编写模式,原理和方法基本是一样的,只是换了一个外壳而已。

首先还是看一下依照字符设备驱动编写所设计的数据结构:

static struct file_operations led_fops =
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
.ioctl = led_ioctl,
};
struct led_dev_t
{
struct cdev cdev;
};
struct led_dev_t dev;


很眼熟也很简单,对于后面的两个东西的定义,你可以理解成是我多此一举了,不过其实这只是因为这个设备比较简单的原因,如果是其他设备,led_dev_t里面的内容会更多的。对于cdev这个结构体大家可能不熟悉,我先贴出来给大家看看:

struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};


它在/include/linux/cdev.h中定义,其实也都是一些大家“喜闻乐见”的东西了。好了,下面我们就又要开始填充这些函数了:

static ssize_t led_write(struct file *filp, const char __user *buff, size_t s, loff_t *l)
{
int ret = 0;
int i;
unsigned char ctrl = 0;
get_user(ctrl, (u8 *)buff);
i = (ctrl - 0x30) & 0x03;
if(i == 0)
__raw_writel(_BIT(5), GPIO_P3_OUTP_CLR(GPIO_IOBASE));
else
__raw_writel(_BIT(5), GPIO_P3_OUTP_SET(GPIO_IOBASE));
return ret;
}
static int led_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long ul)
{
switch(cmd)
{
case LED_ON  :
__raw_writel(_BIT(5), GPIO_P3_OUTP_CLR(GPIO_IOBASE));
break;
case LED_OFF :
__raw_writel(_BIT(5), GPIO_P3_OUTP_SET(GPIO_IOBASE));
break;
default      :
__raw_writel(_BIT(5), GPIO_P3_OUTP_SET(GPIO_IOBASE));
break;
}
return 0;
}
static int led_open(struct inode *si, struct file *filp)
{
__raw_writel(_BIT(5), GPIO_P3_OUTP_SET(GPIO_IOBASE));
printk(DEV_NAME " success open!/n");
return 0;
}
static int led_release(struct inode *si, struct file *filp)
{
__raw_writel(_BIT(5), GPIO_P3_OUTP_SET(GPIO_IOBASE));
printk(DEV_NAME " success close!/n");
return 0;
}


其实只看上一篇文章就可以知道这些函数的实现原理和过程了,而且上面的这些函数写的更加简短了,因为我们的目的明确,而且“不拘小节”(就是忽略了一些除错处理,不要学我,呵呵。。。)。好了,其实上面也不是重点,它们还体现不出来我们在写一个字符驱动,真正能让我们有所区别的是init和exit的编写:

static void led_setup_cdev(void)
{
int err, devno = MKDEV(led_major, 0);
cdev_init(&dev.cdev, &led_fops);
dev.cdev.owner = THIS_MODULE;
dev.cdev.ops = &led_fops;
err = cdev_add(&dev.cdev, devno, 1);
if(err)
printk(KERN_NOTICE "Error %d adding led", err);
}
static int __init led_init(void)
{
int result;
dev_t devno = MKDEV(led_major, 0);
if(led_major)
result = register_chrdev_region(devno, 1, "leddrv");
else
{
result = alloc_chrdev_region(&devno, 0, 1, "leddrv");
led_major = MAJOR(devno);
}
if(result < 0)
return result;
led_setup_cdev();
printk(KERN_ALERT "led driver init/n");
return 0;
}
static void __exit led_exit(void)
{
unregister_chrdev_region(MKDEV(led_major, 0), 1);
cdev_del(&dev.cdev);
printk(KERN_ALERT "led driver exit/n");
}


MKDEV,用来给设备分配设备号,然后使用register_chrdev_region来进行字符设备的注册,使用cdev_add向系统中添加这个字符设备,使用cdev_del删除字符设备,这些也都是系统中已经有的宏定义。

最后我再把代码的剩余部分贴出来:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <../arch/arm/mach-lpc32xx/include/mach/hardware.h>
#include <../arch/arm/mach-lpc32xx/include/mach/lpc32xx_gpio.h>
#include "led.h"
int led_major = LED_MAJOR;
module_init(led_init);
module_exit(led_exit);
MODULE_AUTHOR("wzy");
MODULE_LICENSE("GPL");


不用说明,只是为了保持代码的完整性。当然,这还不是字符驱动开发的全部,我们还要有测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "../led.h"
#define NODE_NAME "/dev/led"
int main(int argc, char *argv[])
{
int fd;
int dat = 0;
int i;

fd = open(NODE_NAME, O_RDWR);
if(fd < 0)
{
printf("can not open the device/n");
exit(1);
}
printf("Test ioctl:/n");
for(i = 0; i < 3; i++)
{
printf("light %d time/n", i + 1);
ioctl(fd, LED_ON);
usleep(300000);
ioctl(fd, LED_OFF);
usleep(300000);
}
printf("ioctl test finish/n");
usleep(30000);
printf("Test write:/n");
for(i = 0; i < 3; i++)
{
printf("light %d time/n", i + 1);
dat = 0;
write(fd, &dat, 1);
usleep(300000);
dat = 1;
write(fd, &dat, 1);
usleep(300000);
}
printf("write test finish/n");
close(fd);
return 0;
}


其实和上一篇的那个测试程序是一样的,只是更好看了一点。。。不过还有一点不同时体现在代码之外的,就是对于一个字符设备来说,它还需要一个/dev下的对应的结点文件,这个文件是自己手动加入的,而且重启开发板后,这个手动创建的文件就没有了,所以为了方便起见,我写了一个简单的shell脚本,在运行这个测试程序之前运行脚本文件,就可以了:

#!/bin/sh
mod=led.ko
mknod /dev/led c 248 0
rmmod $mod
insmod $mod


其中的248是我自己设置的主设备号,这个号码要和驱动程序中的LED_MAJOR保持一致,而且要保证这个主设备号是空闲的。

好了,一个LED的字符驱动就写好了,之所以写的比较简单,分析的比较少,是因为其实它们和上一篇的驱动是比较类似的,在那里已经做了比较详细的分析,这里只是说明了一下字符设备驱动的编写和platform体系驱动的编写的不同,两者可以达到同样的目的,只是platform有一些面向对象的感觉,更加有章法一些。现在的学习都是在学习方法,其实对于简单的事物,我们千万不要看不起它,如果我们可以很重视的把它解决,那么遇到难的问题,用同样的方法还是可以行得通的。综上所述吧,就是学会举一反三,有很多东西,解决的模式是一定的,而且是一样的,都有相通的地方,我们学习的时候就要抓住重点,学习方法,而不是只针对一个事物学习,这样就真的是要“活到老学到老了”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: