(一)背景介绍
自从开始的一段时间学习4412以后,就没有怎么碰过LINUX驱动了,主要是太过繁琐,想要有所突破难度很大,所以我又转而去学习了JAVA,Android,还有流媒体的相关知识。 但是LINUX驱动始终是避不开的东西。最近要做的项目一定需要自己写一些驱动,只能回头再学习一下了。
之前学习的时候,对于驱动是没什么概念的只知道套用那些格式,什么platform_driver ,platform_devices,misc_device,搞得我真的有点懵,但是过了大半年以后我发现,自己又有了一些新的领悟。这一次,我将使用致远科技的IMX283开发板去实现通过4个按键分别控制4个LED灯的目标。
(二)电路以及原理
在 AP-283Demo 板上有 5 个按键:KEY1 ~ KEY5,其电路原理图如图 5.6 所示。每个按键都连接到一个 GPIO。当按键被按下时,对应 GPIO 被拉低;当按键松开后,对应 GPIO恢复高电平。
通过以上的电路图我们可以得到我们需要操控的引脚 。
//LED:3.26(1) ,3.22(2) ,3.20(3), 2.7(4)
//KEY:2.6(5) ,2.5(4) ,2.4(3),1.18(2),1.17(1)
(三)LINUX中的中断知识点
(1) 申请和释放 中断
中断是一个处理器的稀缺资源,在系统中非常重要,通过中断能够及时高效的响应外部
事件,提高系统的响应能力,增加系统吞吐量。在驱动中使用中断,其实比较简单,先申请中断号,并注册一个中断中断处理程序,在中断程序实现对外部事件的处理。
1. 申请中断
通过 request_irq()可以申请中断号,并同时安装中断处理程序。request_irq()在
</linux/interrupt.h>中声明,函数原型如下
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev);
irq
是要申请的硬件中断号。
handler
是指实际中断处理程序的函数指针。只要系统接收到中断,系统调用这个函数。
flags
设置与中断有关的一些选项。比较重要的有 SA_INTERRUPT,标明中断处理程序是快速处理程序(设置 SA_INTERRUPT)还是慢速处理程序(不设置 SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽。还有一个 SA_SHIRQ 属性,设置了以后运行多个设备共享中断,处理程序之间通过 dev 来进行区分。如果中断由某个处理程序独占,则 dev 可以设置为 NULL。
*name
传递给 request_irq 的字符串,用来在/proc/interrupts 显示中断的拥有者。使用 cat 命令查看。
*dev
在中断共享时会用到。一般设置为这个设备的 device 结构本身或者 NULL。中断处理程
序可以用 dev 找到相应的控制这个中断的设备。 在没有强制使用共享方式时,dev 可以被设置为 NULL,不过,将它指向设备的数据结构是比较好的方法。函数会将 dev 原封不动的传递给中断处理程序,因而可以很方便的用于向中断传递额外数据。
(2)释放中断
中断时系统的稀缺资源,一旦不再使用,最好将中断号释放。释放中断通过 free_irq()实现。与 request_irq()一样,free_ire()函数在</linux/ interrupt.h>中声明,其函数原型如下:
void free_irq(unsigned int irq, void *dev_id)
第一个参数是将要释放的 irq 中断号。
第二个参数标志设备。如果中断是该设备独占的,这里设置为 NULL;如果是共享中断,
需要设置为中断处理程序指针
(3)设置触发条件
中断需要设置触发条件,如上升沿中断或者下降沿中断等。Linux 提设置触发条件的接
口函数为 irq_set_irq_type(),在<linux/irq.h>定义,函数原型为:
extern int irq_set_irq_type(unsigned int irq, unsigned int type);
irq 为中断号,type 为终端类型。在<linux/irq.h>中定义了如下中断类型:
IRQ_TYPE_NONE = 0x00000000,
IRQ_TYPE_EDGE_RISING = 0x00000001,
IRQ_TYPE_EDGE_FALLING = 0x00000002,
IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
IRQ_TYPE_LEVEL_HIGH = 0x00000004,
IRQ_TYPE_LEVEL_LOW = 0x00000008,
IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
IRQ_TYPE_SENSE_MASK = 0x0000000f,
IRQ_TYPE_PROBE = 0x00000010,
通常情况下,一般采用边沿触发和电平触发类型,具体如何设置,还需与实际硬件匹配。
(4)中断服务程序
中断处理程序返回值 irqreturn_t,接受两个参数:中断号 irq 和 dev_id, dev_id 就是
request_irq 时传递给系统的参数 dev:
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
中断处理完毕,通常返回 IRQ_HANDLED。通常,一个中断处理程序如程序清单 2.24
所示。
(四)代码思路
-
在init 函数中首先初始化硬件
-
获取设备号
-
分配cdev结构,初始化结构,添加到系统
-
创建class 设备类
-
创建设备结点
-
完成cdev 结构体中的文件操作
-
完成中断服务函数
-
完成exit函数
(五)代码
//LED:3.26(1) ,3.22(2) ,3.20(3), 2.7(4)
//KEY:2.6(5) ,2.5(4) ,2.4(3),1.18(2),1.17(1)
//I need to use the key to control the led. Every time I press the key ,the Led will change
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#define DEVICE_NAME "key_ctl_led"
struct KEY_INFO{
int id;
char* describe;
}KEY_GPIO[]= {
{1*32+17,"KEY_GPIO1"},
{1*32+18,"KEY_GPIO2"},
{2*32+4 ,"KEY_GPIO3"},
{2*32+5 ,"KEY_GPIO4"},
{2*32+6 ,"KEY_GPIO5"},
};
struct LED_INFO{
int id;
char* describe;
}LED_GPIO[]= {
{3*32+26 ,"LED_GPIO1"},
{3*32+22 ,"LED_GPIO2"},
{3*32+20 ,"LED_GPIO3"},
{2*32+7 ,"LED_GPIO4"},
};
#define KEY_GPIO_IRQ(x) gpio_to_irq(KEY_GPIO[x].id)
const char irq_type[5]={
IRQ_TYPE_EDGE_RISING,
IRQ_TYPE_EDGE_FALLING,
IRQ_TYPE_EDGE_BOTH,
IRQ_TYPE_LEVEL_HIGH,
IRQ_TYPE_LEVEL_LOW
};
static irqreturn_t key_irq1_handler(unsigned int irq,void *dev_id)
{
printk(KERN_INFO DEVICE_NAME" pressed!\n");
int data= gpio_get_value(LED_GPIO[0].id);
gpio_direction_output(LED_GPIO[0].id,!data);
module_put(THIS_MODULE);
return 0;
}
static irqreturn_t key_irq2_handler(unsigned int irq,void *dev_id)
{
printk(KERN_INFO DEVICE_NAME" pressed!\n");
int data= gpio_get_value(LED_GPIO[1].id);
gpio_direction_output(LED_GPIO[1].id,!data);
module_put(THIS_MODULE);
return 0;
}
static irqreturn_t key_irq3_handler(unsigned int irq,void *dev_id)
{
printk(KERN_INFO DEVICE_NAME" pressed!\n");
int data= gpio_get_value(LED_GPIO[2].id);
gpio_direction_output(LED_GPIO[2].id,!data);
module_put(THIS_MODULE);
return 0;
}
static irqreturn_t key_irq4_handler(unsigned int irq,void *dev_id)
{
printk(KERN_INFO DEVICE_NAME" pressed!\n");
int data= gpio_get_value(LED_GPIO[3].id);
gpio_direction_output(LED_GPIO[3].id,!data);
module_put(THIS_MODULE);
return 0;
}
static irqreturn_t key_irq5_handler(unsigned int irq,void *dev_id)
{
printk(KERN_INFO DEVICE_NAME" pressed!\n");
int data= gpio_get_value(LED_GPIO[3].id);
gpio_direction_output(LED_GPIO[3].id,!data);
module_put(THIS_MODULE);
return 0;
}
void led_init()
{
int i=0;
for(i=0;i<4;i++){
gpio_free(LED_GPIO[i].id);
gpio_request(LED_GPIO[i].id, LED_GPIO[i].describe);
gpio_direction_output(LED_GPIO[i].id, 0);
}
}
void key_init()
{
int i=0;
for(i=0;i<5;i++){
gpio_free(KEY_GPIO[i].id);
gpio_request(KEY_GPIO[i].id, KEY_GPIO[i].describe);
gpio_direction_input(KEY_GPIO[i].id);
}
if(request_irq(KEY_GPIO_IRQ(0),key_irq1_handler,IRQF_DISABLED,"key_irq1",NULL))
{
printk(KERN_WARNING DEVICE_NAME":can't get irq1\n");
}
if(request_irq(KEY_GPIO_IRQ(1),key_irq2_handler,IRQF_DISABLED,"key_irq2",NULL))
{
printk(KERN_WARNING DEVICE_NAME":can't get irq2\n");
}
if(request_irq(KEY_GPIO_IRQ(2),key_irq3_handler,IRQF_DISABLED,"key_irq3",NULL))
{
printk(KERN_WARNING DEVICE_NAME":can't get irq3\n");
}
if(request_irq(KEY_GPIO_IRQ(3),key_irq4_handler,IRQF_DISABLED,"key_irq4",NULL))
{
printk(KERN_WARNING DEVICE_NAME":can't get irq4\n");
}
if(request_irq(KEY_GPIO_IRQ(4),key_irq5_handler,IRQF_DISABLED,"key_irq5",NULL))
{
printk(KERN_WARNING DEVICE_NAME":can't get irq5\n");
}
for(i=0;i<5;i++)
{
set_irq_type(KEY_GPIO_IRQ(i),irq_type[1]);
disable_irq(KEY_GPIO_IRQ(i));
enable_irq(KEY_GPIO_IRQ(i));
}
}
static int key_led_open(struct inode *inode,struct file *file)
{
try_module_get(THIS_MODULE);//count how many times the module has been opened
printk(KERN_INFO DEVICE_NAME" opened!\n");
return 0;
}
static int key_led_release(struct inode *inode,struct file *file)
{
printk(KERN_INFO DEVICE_NAME" closed!\n");
return 0;
}
struct cdev *keyirq;
static dev_t devno;
static struct class *key_irq_class;
struct file_operations key_irq_fops=
{
.owner =THIS_MODULE,
.open =key_led_open,
.release=key_led_release,
};
static int __init key_led_init(void)
{
int major,minor;
int ret;
led_init();
key_init();
ret =alloc_chrdev_region(&devno,minor,1,DEVICE_NAME);//get device id
major=MAJOR(devno);
if(ret<0)
{
printk(KERN_ERR"can not get id");
return -1;
}
keyirq=cdev_alloc();
if(keyirq!=NULL)
{
cdev_init(keyirq,&key_irq_fops);
keyirq->owner=THIS_MODULE;
if(cdev_add(keyirq,devno,1)!=0){
printk(KERN_ERR "add error !\n");
goto error;
}
}else{
printk(KERN_ERR " alloc error !\n");
return -1;
}
key_irq_class=class_create(THIS_MODULE,"key_irq_class");
if(IS_ERR(key_irq_class))
{
printk(KERN_ERR "create class error ! \n");
return -1;
}
device_create(key_irq_class,NULL,devno,NULL,DEVICE_NAME);
return 0;
error:
unregister_chrdev_region(devno,1);
return ret;
}
static void __exit key_led_exit(void)
{
int i=0;
for(i=0;i<4;i++){
gpio_free(LED_GPIO[i].id);
}
for(i=0;i<5;i++){
gpio_free(KEY_GPIO[i].id);
disable_irq(KEY_GPIO_IRQ(i));
free_irq(KEY_GPIO_IRQ(i),NULL);
}
cdev_del(keyirq);
unregister_chrdev_region(devno,1);
device_destroy(key_irq_class,devno);
class_destroy(key_irq_class);
}
module_init(key_led_init);
module_exit(key_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YangYue");
Makefile:
obj-m:=key_ctl_led.o
PWD:=$(shell pwd)
KDIR:=/home/swann/IMX_283A/linux-2.6.35.3
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions