众所周知,LED灯闪烁实验,就相当于编程中的helloworld,是最简单入门,也是可以帮助广大爱好学习者建立自信的,看了设备驱动一个多月,终于我也让自己的led灯闪亮了。
要让led灯闪烁,就是要控制引脚输入高低电平,不断交替变化,然而Linux中,上面的文章提到,驱动和应用是分开的,驱动层对硬件直接操作,应用层通过调用驱动层接口,来实现逻辑功能。
我们先来看驱动程序:(驱动程序涉及太多知识,建议大家去看国嵌Linux的设备驱动的视频,讲的很详细,基础知识不赘述,只是简单总结一下)
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <mach/gpio.h>
//定义命令
#define LED_ON _IOW('h',0x01,unsigned long) //LED开的命令
#define LED_OFF _IOW('h',0x02,unsigned long) //LED关的命令
int major_no = 255; //定义主设备号
struct cdev cdev; //字符设备的数据结构
dev_t devno; //设备号
int open_state = 0; //1为打开,0为关闭
/*-----------------------------------------------------------------------------
函数名: led_open
参数: struct inode *inode,struct file *filp
返回值: int
描述: open对应的驱动函数
*-----------------------------------------------------------------------------*/
int led_open(struct inode *inode,struct file *filp )
{
if(open_state == 0)
{
open_state = 1;
printk("Open file suc!\n");
return 0;
}
else
{
printk("The file has opened!\n");
return -1;
}
}
/*-----------------------------------------------------------------------------
函数名: led_ioctl
参数: struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg
返回值: int
描述: ioctl对应的驱动函数
*-----------------------------------------------------------------------------*/
int led_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
switch(cmd)
{
case LED_ON: printk("ON!\n");
at91_set_gpio_value(AT91_PIN_PC0, 1); //灯亮起来
break;
case LED_OFF:printk("OFF\n");
at91_set_gpio_value(AT91_PIN_PC0, 0); //灯灭掉
break;
default :printk("Error command!\n");
}
return 0;
}
const struct file_operations led_fop =
{
.owner = THIS_MODULE,
.open = led_open,
.ioctl = led_ioctl,
};
/*-----------------------------------------------------------------------------
函数名: gpio_init
参数: void
返回值: int
描述: 模块初始化函数,在安装模块时候执行
*-----------------------------------------------------------------------------*/
static int __init gpio_init(void)
{
int ret;
printk("------GPIO test init-----\n");
printk("Register the char dev!\n");
/**
* 静态创建前,可以查看/proc/devices 查看已经被使用的设备号
*/
devno = MKDEV(major_no,0);
ret = register_chrdev_region(devno,1,"led"); //向内核注册设备号
if(ret < 0)
{
printk("Register Error!\n");
return ret;
}
/**
* 初始化字符设备
*/
cdev_init(&cdev,&led_fop);
cdev.owner = THIS_MODULE;
cdev.ops = &led_fop;
cdev_add(&cdev,devno,1); //向内核注册添加字符设备
at91_set_gpio_output(AT91_PIN_PC0,1); //设置引脚为输出功能,且为高电平
return 0;
}
/*-----------------------------------------------------------------------------
函数名: gpio_exit
参数: void
返回值: void
描述: 模块卸载函数,在卸载模块时候执行
*-----------------------------------------------------------------------------*/
static void __exit gpio_exit(void)
{
unregister_chrdev_region(devno,1);
open_state = 0;
printk("GPIO test End\n");
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");
这是笔者自己写的驱动程序,注释非常详细,这里也只是做几个简单说明:
1.笔者的LED灯连接的是AT91sam9260芯片的PC0引脚,高电平点亮,而at91_set_gpio_value类似的函数,就是Linux封装好的,可以清楚的看到,第一个参数是引脚编号,第二个参数是要输出的电平,其它的我们都不用管。不用再对寄存器初始化什么的了。前面提过,这些函数就分装在linux内核源代码下的arch/arm/mach_at91下,在arch/arm/下有很多的目录,里面都封装着对应芯片实现相同功能的函数,读者可以自己去看看。
2.一般设备初始化步骤,首先先申请一个设备号,然后想内核注册字符设备,主要就是关联file_operations这个结构,每次应用程序调用open,其实是通过VFS系统调用成我上面驱动的led_open(这个国嵌的视频讲的也很清楚,看完视频再来看这个驱动程序,一切都很明了)
3.应用程序通过ioctl发送命令给驱动,实际是调用了led_ioctl,这个函数就是解析各种命令,执行相应的动作,就是典型的命令-反应模型!
可能你会觉得很烦,但是不急,看看应用程序,你就会知道,这种分而治之的形式其实很好:
#include <stdio.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#define LED_ON _IOW('h',0x01,unsigned long) //LED开的命令
#define LED_OFF _IOW('h',0x02,unsigned long) //LED关的命令
int fd;
void close_file(int sig_no)
{
if(SIGINT == sig_no)
{
close(fd);
printf("Close the file suc!\n");
exit(0);
}
}
int main(void)
{
fd = open("/dev/led",0);
if(fd < 0)
{
perror("Open file Error:");
return -1;
}
signal(SIGINT,close_file);
while(1)
{
ioctl(fd,LED_ON); //led on
usleep(500000); //delay 500 ms
ioctl(fd,LED_OFF); //led off
usleep(500000);
}
return 0;
}
应用程序,真的就像操作文件一样操作设备了,先打开了设备文件,然后只要往驱动程序发送命令就行了,应用层开发者完全不用理会设备是怎么实现的,这样大大加快开发速度,驱动和应用可以并行。
运行结果如下:
首先要创建设备文件:mknod /dev/led c 255 0
c 代表是字符设备,255 是主设备号,0是次设备号
root@at91sam9260ek:/mnt/char_dev# insmod char_dev.ko
------GPIO test init-----
Register the char dev!
root@at91sam9260ek:/mnt/char_dev# mknod /dev/led c 255 0
root@at91sam9260ek:/mnt/char_dev# ./test
Open file suc!
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
ON!
OFF
Close the file suc!
PS:linux延迟真是简单!