学习驱动程序不久,看LDD3大概有4章吧,最开始写了个hello world驱动,后来是个面向内存的字符设备驱动,后者让我清楚了量子和量子集的使用,但是没有个真正的,肉眼看得见的设备真是有点不爽,查了些资料,参考了下其他书,打算自己写个LED的linux驱动,学了字符设备驱动的话写这个其实挺简单的。
带系统的驱动跟裸机的驱动可不一样啊,裸机的驱动像单片机那种,你只要把端口设0或者设1就可以控制那些设备,多简单,与带操作系统的驱动程序不同。第一,单片机驱动控制的地址是物理地址,而带操作系统的驱动控制的是虚拟地址,这个在linux上可以很方便的用ioremap函数把物理地址映射为虚拟地址,这个函数在
看下图,是linux的应用程序调用的基本框架,上部黄色的是用户空间,下部蓝色的是内核空间。从上往下,程序员在最上层写程序,操作一些文件,这些文件可以是普通的文件,也可能是设备文件等等,这些操作例如open、read、write、ioctl等,这些操作,到了C库这一层,C库会产生一个swi val中断或者说异常,这个异常就会使应用程序从用户空间进入到内核空间,下面一层叫System all interface,系统调用接口,这一层会调用下面的虚拟文件系统VFS(Virtual File System)的sys_open、sys_write这些函数,这些函数就会根据不同的文件不同的属性,设备文件中还会根据设备号,来寻找特定的驱动程序,这些驱动程序就是我们要写的,里面带led_open、led_write这些函数。
这些虚拟文件系统函数sys_open等是怎么找到我们的驱动程序里的led_open等的呢?其实在虚拟文件系统中有我们的很多种类的文件,普通文件、目录文件、设备文件等,就设备文件中的字符设备举例,它会建立一个字符设备的数组,这些数组以主设备号为下标,貌似从0到255,这些数组里面装的是一些file_operations结构指针,这些file_operations是在驱动程序里面定义的,而file_operations结构里面就包含了我们的驱动的操作函数led_open、led_write等,数组成员不同,file_operations结构就不同,驱动程序就不同,操作的设备就不同......大概是这样
上面是为了帮助大家理清思路,大牛无视就行了,
下面正题了,简单的在linux下的led驱动程序。
开发平台:
ubuntu10.04
测试平台:
TQ2440,操作系统为linux,内核版本linux-2.6.30.4
//leds.c
#include
#include
#include
#include
#include
#include
#include
#define GPBOUT (1<<(5*2)) |(1<<(6*2)) | (1<<(7*2)) | (1<<(8*2)); //设置GPB5/6/7/8为输出
int major = 0;
struct cdev* leds_cdev;
volatile unsigned long *GPBCON = NULL;
volatile unsigned long *GPBDAT = NULL;
static int leds_open(struct inode *inode, struct file *filp)
{
*GPBCON = 0; //控制端口清零
*GPBCON = GPBOUT;
return 0;
}
static int leds_ioctl(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg)
{
if (arg > 4)
{
printk("deyond the led no\n");
}
switch (cmd)
{
case 1:
*GPBDAT |= (1<<(arg + 4)); //灭灯
break;
case 0:
*GPBDAT &= ~(1<<(arg + 4)); //亮灯
break;
default:
printk("cmd error\n");
break;
}
return 0;
}
struct file_operations leds_fops =
{
.owner = THIS_MODULE,
.open = leds_open,
.ioctl = leds_ioctl,
};
static int __init leds_init(void)
{
dev_t dev;
leds_cdev = cdev_alloc();
alloc_chrdev_region(&dev, 0, 1, "leds");
major = MAJOR(dev);
leds_cdev->ops = &leds_fops;
cdev_init(&leds_cdev, &leds_fops);
cdev_add(&leds_cdev, dev, 1);
GPBCON = (volatile unsigned int *)ioremap(0x56000010, 16); //将物理地址映射到虚拟地址,GPBCON物理地址为0x56000010
GPBDAT = GPBCON + 1; //GPBDAT物理地址为0x56000014
return 0;
}
static void __exit leds_exit(void)
{
dev_t dev;
dev = MKDEV(major, 0);
cdev_del(&leds_cdev);
unregister_chrdev_region(dev, 1);
}
module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("不做超哥已多年");
MODULE_DESCRIPTION("leds");
测试程序:
//leds_test.c
#include
#include
#include
#include
#include
#define ON 0
#define OFF 1
int main(int argc, char* argv[])
{
int led_no;
if (argc != 3)
{
printf("Usage: %s \n", argv[0]);
exit(0);
}
int fd;
fd = open("/dev/leds", O_RDWR);
if (fd < 0)
{
printf("open error\n");
exit(0);
}
led_no = strtoul(argv[1], 0, 0); //将字符串转换成无符号长整型数
if (!strcmp(argv[2], "ON"))
ioctl(fd, ON, led_no);
else if (!strcmp(argv[2], "OFF"))
ioctl(fd, OFF, led_no);
else
exit(0);
return 0;
}
编译驱动模块,在arm板上加载,交叉编译测试程序,在板子上运行成功
命令是./leds_test led_no ON/OFF 如 ./led_test 1 ON就是点亮led1灯
点亮四个灯后效果:
看到可以控制灯亮灭了,你是否想起了当初玩单片机时点led灯时的兴奋呢......哈哈哈哈,好兴奋啊