歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> TQ2440按鍵點亮LED驅動程序

TQ2440按鍵點亮LED驅動程序

日期:2017/3/1 9:38:41   编辑:Linux編程

一,硬件分析:

1.打開TQ2440的底板原理圖找到按鍵測試的模塊,如下圖所示:

從圖我們知道,控制按鍵k1 k2 k3 k4 的管腳為EINT1 EINT4 EINT2 EINT0 ,當按鍵按下時,管腳輸出低電平,當按鍵沒有被按下時,管腳輸出高電平。

2.打開TQ2440核心板原理圖找到EINT1 EINT4 EINT2 EINT0所對應的cpu控制引腳,如下圖所示:

從圖我們可以知道,EINT1 EINT4 EINT2 EINT0 對應的cpu控制引腳為GPF1 GPF4 GPF2 GPF0 。

3.打開s3c2410的芯片手冊查看引腳GPF1 GPF4 GPF2 GPF0 ,如下圖所示 :

如圖我們知道如何將cpu引腳 GPF1 GPF4 GPF2 GPF0 設置成中斷模式,比如我要將GPF0設置成為中斷模式,我只需要將其寄存器二進制的第1位和第0位設置成為1和0即可,但是我們只需要知道原理即可,具體將引腳設置成為中斷模式,我們可以利用linux內核中的一些宏來實現。

二,按鍵點亮LED驅動程序源碼Button6.c :

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/irq.h> //定義 IRQ_TYPE_EDGE_BOTH
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/device.h>
#include <linux/poll.h>
#include <linux/wait.h>

#define DEVICE_NAME "button6" //設備名稱
#define DEVICE_MAJOR 2898 //主設備號
#define LED_ON 1 //定義LED亮為1
#define LED_OFF 0 //定義LED暗為0

//定義按鍵中斷結構體
struct button_irq
{
int irq; //中斷號
int pin; //中斷控制引腳
int pin_setting; //中斷控制引腳的設置(設置控制引腳為中斷模式)
int number; //按鍵編號
char *name; //按鍵名稱
};

//按鍵數組,包含4個按鍵的信息
//S3C2410_GPF0_EINT0在Regs-gpio.h中
static struct button_irq button_irqs [] =
{
{IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, "KEY1"}, //按鍵K1
{IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, "KEY2"}, //按鍵K2
{IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, 2, "KEY3"}, //按鍵K3
{IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, "KEY4"}, //按鍵K4
};

//保存按鍵值的數組
//volatile關鍵字意義:每次讀和寫都是用內存中的值而不是CPU寄存器的值
static volatile int key_values [] = {0, 0, 0, 0};

static unsigned long led_table [] ={ S3C2410_GPB5, S3C2410_GPB6, S3C2410_GPB7, S3C2410_GPB8,};//LED控制引腳
//LED控制引腳的設置(設置引腳為輸出模式)
static unsigned int led_cfg_table [] ={ S3C2410_GPB5_OUTP, S3C2410_GPB6_OUTP, S3C2410_GPB7_OUTP, S3C2410_GPB8_OUTP,};

//等待隊列:
//等待隊列頭,當應用程序讀取按鍵時,如果此時沒有按鍵按下,程序就休眠
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //定義並初始化等待隊列
static volatile int ev_press = 0; //按鍵是否被按的標志(按下和松開都算)

//中斷處理函數
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
struct button_irq *button_irqs = (struct button_irq*)dev_id; //獲取request_irq注冊中斷時關聯的參數信息
int up = s3c2410_gpio_getpin(button_irqs->pin); //注冊中斷
if (up)
key_values[button_irqs->number] = (button_irqs->number + 1) + 0x80;
else
key_values[button_irqs->number] = (button_irqs->number + 1); //根據中斷注冊情況設置按鍵的值
ev_press = 1; //表示中斷發生了
wake_up_interruptible(&button_waitq); //喚醒休眠的進程
return IRQ_RETVAL(IRQ_HANDLED);//返回中斷信息
}

//定義open方法
static int buttons_open(struct inode *inode, struct file *file)
{
int i;
int err;
for (i = 0; i < 4; i++)
{
s3c2410_gpio_cfgpin(button_irqs[i].pin,button_irqs[i].pin_setting); //設置中斷控制引腳為中斷模式
err = request_irq(button_irqs[i].irq, buttons_interrupt, NULL, button_irqs[i].name, (void *)&button_irqs[i]); //申請中斷
if (err) //返回值為非零值表示申請中斷不成功
break;
}
if (err) //申請中斷錯誤處理
{
for (; i >= 0; i--)
{
//釋放已經注冊的中斷
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return -EBUSY; //返回值表示中斷已被占用且不能共享
}
//配置LED引腳為輸出模式
for (i = 0; i < 4; i++)
{
s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
}
return 0;
}

//定義close方法
static int buttons_close(struct inode *inode, struct file *file)
{
int i;
for (i = 0; i < 4; i++)
{
// 釋放已經注冊的中斷
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return 0;
}

//定義read方法
static int buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
unsigned long err;
//if (!ev_press) //如果沒有按鍵按下
while(!ev_press) //如果沒有按鍵按下
{
if (filp->f_flags & O_NONBLOCK) //如果是非阻塞讀取設備,那麼程序就直接返回 -EAGAIN
return -EAGAIN; //不阻塞,下次再來read
else
wait_event_interruptible(button_waitq, ev_press); //阻塞讀取設備
}
ev_press = 0;
//把按鍵值的信息從內核空間復制到用戶空間
err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));
memset((void *)key_values, 0, sizeof(key_values));//清零
return err ? -EFAULT : min(sizeof(key_values), count);
}

//select方法
static unsigned int buttons_poll( struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); //將等待隊列添加到poll_table中
if (ev_press)
mask |= POLLIN | POLLRDNORM; //設備可讀的掩碼
return mask; //返回設備可讀的掩碼
}

//定義ioctl方法
static int leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
if(arg > 4)
{
return -EINVAL;
}
switch(cmd)
{
case LED_ON: //如果是點亮
s3c2410_gpio_setpin(led_table[arg], 0);//LED控制引腳輸出低電平,此時燈亮
return 0;
case LED_OFF://如果是熄滅
s3c2410_gpio_setpin(led_table[arg], 1);//LED控制引腳輸出高電平,此時燈滅
return 0;
default:
return -EINVAL;
}
return 0;
}

//定義file_operations方法
static struct file_operations buttons_fops =
{
.owner = THIS_MODULE,
.open = buttons_open,
.release = buttons_close,
.read = buttons_read,
.poll = buttons_poll,
.ioctl = leds_ioctl,
};

//聲明自動創建設備文件的類
static struct class *button_class;

//驅動程序加載函數的實現
static int __init buttons_init(void)
{
int ret;
int i;
printk("TQ2440/SKY2440 LEDS!\n"); //輸出初始化信息
ret = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &buttons_fops); //注冊設備
if (ret)
{
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
//設備節點文件自動創建的實現
button_class = class_create(THIS_MODULE, DEVICE_NAME);//注冊一個類,使mdev可以在"/dev/"目錄下面建立設備節點
if(IS_ERR(button_class))
{
printk("Err: failed in Button_leds class. \n");
return -1;
}
device_create(button_class, NULL, MKDEV(DEVICE_MAJOR, 0), NULL, DEVICE_NAME);//創建一個設備節點,節點名為DEVICE_NAME
for(i=0;i<4;++i) //熄滅4盞LED燈
{
s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]); //配置LED的控制引腳為輸出模式
s3c2410_gpio_setpin(led_table[i],1); //使LED控制引腳輸出高電平,燈滅
}
printk(DEVICE_NAME " initialized\n");//打印信息,內核中的打印用printk函數
return 0;
}

//驅動程序卸載函數的實現
static void __exit buttons_exit(void)
{
unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME);//注銷設備
device_destroy(button_class, MKDEV(DEVICE_MAJOR, 0)); //刪掉設備節點
class_destroy(button_class); //注銷類
}

module_init(buttons_init);//驅動模塊加載聲明,執行“insmod tope-buttons.ko”命令時調用的函數
module_exit(buttons_exit); //驅動模塊加載聲明,執行“rmmod tope-buttons”命令時調用的函數
MODULE_LICENSE("GPL");//遵循的協議

源碼分析:

1.程序的一開始我們定義了一個按鍵中斷的結構體,我們可以將這個結構體看做是本程序要處理的按鍵中斷的數據類型。然後我們分別對四個按鍵進行初始化。這裡要說明下實現cpu引腳工作在中斷模式下的宏S3C2410_GPF1_EINT1,S3C2410_GPF4_EINT4 ,S3C2410_GPF2_EINT2 和S3C2410_GPF0_EINT0 ,他們定義在在Regs-gpio.h中,源碼路徑:arch/arm/mach-s3c2410/include/mach/regs-gpio.h ,我們來看下宏S3C2410_GPF1_EINT1:#define S3C2410_GPF1_EINT1 (0x02 << 2) ,我們分析可以知道這裡實際上就是將GPF1的寄存器二進制位的第3位和第2位設置成為1和0,從而使得GPF1工作在中斷模式下。接著定義LED燈的控制引腳的數組和將LED控制引腳設置成為輸出模式的宏,這裡不做解釋,因為前面我寫過一篇TQ2440LED燈的驅動程序的文章,不懂請看那篇文章。

2.設置中斷處理函數,當發生中斷時就調用這個中斷處理函數來處理中斷,這之後是定義設備的open方法,對於本文的程序open方法主要的職責是:設置cpu的中斷控制引腳工作在中斷模式下並且申請中斷;設置LED的cpu控制引腳工作在輸出模式下。

3.定義設備的close方法,close方法的主要職責:釋放系統的中斷,如果申請的中斷沒有釋放會占用系統的中斷資源,導致某些程序無法運行,比如我一開始由於中斷的釋放這裡沒有處理好,導致的問題是當我將驅動模塊加載進系統內核,然後運行我的驅動測試程序,這些都沒有問題,但是當我關掉測試程序再次運行時,本人發現測試程序打不開設備了,不能運行了,提示錯誤設備正忙,原因就是系統的中斷資源沒有及時釋放。

4.定義設備的read方法,read方法的主要職責是:當我上層應用程序需要讀我的設備的時候,當我設備沒有數據可讀的時候,這個時候該如何處理的問題。

5.定義設備的poll方法,poll設備方法負責完成:

①使用poll_wait將等待隊列添加到poll_table中。

②返回描述設備是否可讀或可寫的掩碼。

位掩碼:

POLLIN 設備可讀

POLLRDNORM 數據可讀

POLLOUT 設備可寫

POLLWRNORM 數據可寫

設備可讀通常返回 (POLLIN|POLLRDNORM)

設備可寫通常返回 (POLLOUT|POLLWRNORM)

6.定義ioctl方法,這個設備方法不做解釋,不懂的話請看我前面寫的那篇TQ2440LED驅動程序的文章。

7.定義file_operations 。file_operations結構中的每一個成員的名字都對應著一個上層應用程序的調用。

8.定義模塊加載函數:這裡需要特別說明的是,我們要在驅動模塊加載函數裡面要加入熄滅4盞LED燈的代碼,以便等下測試的時候能夠很好的看到按鍵點亮LED燈的效果。

9.定義模塊卸載函數:略。

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-10/107954p2.htm

Copyright © Linux教程網 All Rights Reserved