TQ2440按键点亮LED驱动程序

一,硬件分析:

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.定义模块卸载函数:略。

三,Makefile文件的编写:

obj-m:=Button6.o
CC=arm-linux-gcc
KERNELDIR=/usr/local/opt/EmbedSky/linux-2.6.30.4
PWD:=$(shell pwd)
default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

四,驱动测试程序源码Test_Button6.c :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>

int main(void)
{
    int i;
    int buttons_fd;
    int key_value[4];
    int temp=0;
    //打开键盘点亮LED设备文件
    buttons_fd = open("/dev/button6", 0);//这里的open的实现在驱动的buttons_open
    if (buttons_fd < 0) 
    {
        perror("open device buttons");
        exit(1);
    }
    for (;;) //一直循环来监听是否有按键按下
    {
        fd_set rds;
        int ret;
        FD_ZERO(&rds); //清除文件描述符集rds 
        FD_SET(buttons_fd, &rds); //将文件描述符buttons_fd添加到文件描述符集rds
        //使用系统调用select检查是否能够从/dev/buttons设备读取数据
        ret = select(buttons_fd + 1, &rds, NULL, NULL, NULL); 
        if (ret == -1) //读取出错或select被某个信号中断  
        {
            printf("select error!\n");
            exit(-1);
        }  
        if (ret == 0) //经过了timeout等待后没有文件满足要求     
        {
            printf("Timeout ! \n");
        }
        else if (FD_ISSET(buttons_fd, &rds)) //测试是否可读  
        {
             //开始读取键盘驱动发出的数据,注意key_value和键盘驱动中定义为一致的类型
             int ret = read(buttons_fd, key_value, sizeof key_value);//read也是在buttons_read中实现的
             if (ret != sizeof key_value)
             {
                 if (errno != EAGAIN)
                 perror("read buttons\n");
                 continue;
             }
            else
            {
                 /*打印键值*/
                 for (i = 0; i < 4; i++)
                 if(key_value[i] != 0)
                 {
                     printf("K%d %s, key value = 0x%02x\n", i+1, (key_value[i] & 0x80) ? "released" : key_value[i] ? "pressed down" : "", key_value[i]);
                     if(temp==key_value[i])
                         ioctl(buttons_fd,0,i);
                     else
                         ioctl(buttons_fd,1,i); //判断按键值,点亮对应的LED
                     temp=key_value[i];
                 }
           }
    }
   }
   //关闭设备文件句柄
   close(buttons_fd);
   return 0;
}

源码分析:

本测试程序的核心实现在于select方法,select方法对应于在驱动程序中实现的设备poll方法,用于多路监控,当没有一个文件满足要求时,select将阻塞上层应用调用的进程。select方法的原型如下:

int select(int maxfd,fd_set*readfds,fd_set*writefds,fe_set*exceptfds,const struct timeval*timeout);

参数说明:

maxfd:文件描述符的范围,比待检测的最大文件描述符大1 。(说明:文件描述符就是我们使用open方法打开一个设备时,open函数的返回值)

readfds:被读监控的文件描述符集。

writefds:被写监控的文件描述符集。

exceptfds:被异常监控的文件描述符集。

timeout:定时器。

select系统调用的返回值:

正常情况下返回满足要求的文件描述符的个数。

经过了timeout等待后仍无文件满足要求,返回值为0 。

如果select被某个信号中断,它将返回 -1 并设置errno为EINTR。

如果出错,返回-1并设置相应的errno。

select系统调用的使用方法:

①将要监控的文件添加到文件描述符集。

②调用select开始监控。

③判断文件是否发生变化。

linux系统提供了4个宏对描述符集进行操作:

这些宏定义在select.h中

void FD_SET(int fd,fd_set*fdset); //将文件描述符fd添加到文件描述符集fdset中

void FD_CLR(int fd,fd_set*fdset); //从文件描述符集fdset中清除文件描述符fd

void FD_ZERO(fd_set*fdset); //清空文件描述符集fd_set

void FD_ISSET(int fd,fd_set *fdset); //在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件fd发生了变化

五,驱动测试:

首先要特别注意的一个问题是开发板自带的按键驱动的存在会影响我们自己写的这个按键点亮LED灯驱动程序的测试,解决办法是重新编译开发板系统的内核,在配置内核的时候把device driver->Input device support-->Keyboards这个配置去掉,这个时候开发板系统自带的按键驱动程序就不会编译到内核了,从而达到去掉系统本身按键驱动的目的,至于编译内核的方法请参照我前面写的一篇文章:《针对TQ2440开发板上linux2.6.30.4内核的交叉编译和驱动程序的移植》,然后重新烧写开发的linux系统,具体烧写方法请看你自己的开发的说明书,这里就不再赘述了。

1.在pc终端输入指令: sudo kermit -c ,连接开发板。

2.把编译好的驱动模块Button6.ko和编译好的测试程序Test_Button6.o使用sd卡拷贝至开发板系统的opt文件夹下。

3.在开发板系统的终端输入指令:insmod Button6.ko 将模块加载至系统内核,然后输入指令:./Test_Button6.o 进行驱动程序的测试。本人已经亲自通过测试。

时间: 2024-11-08 17:44:32

TQ2440按键点亮LED驱动程序的相关文章

TQ2440按键点亮LED灯的裸机程序

一,说到做ARM的裸机程序,很多人马上就会联想到一个名为ADS的开发工具,但是我们在linux下同样也可以做ARM的裸机程序,下面来说说其具体实施过程: 步骤一:编辑代码,这个没什么好说的. 步骤二:编译代码,编译代码分为三个方面的内容:1.链接脚本 2.用命令行确定链接时的文件顺序 3.用命令行编译.这三部分的内容可以全部写成一个Makefile文件,编译的时候执行make命令就可以了. 步骤三:把编译后的".bin"文件烧写进开发板,重新上电观察效果. 二,TQ2440按键点亮LE

STM32(三)- GPIO输入输出之按键检测点亮LED

本例程基于先前的库函数版demo工程模板,主要内容为:bsp_led.h.bsp_led.c.bsp_key.h.bsp_key.c.main.c  1.bsp_led.h 1 #ifndef __BSP_LED_H //条件编译,如果没有定义这个宏,向下执行:如果已经定义则不向下执行,结束条件编译. 2 #define __BSP_LED_H //没有定义,则定义该宏. 当下次该头文件被编译时,该宏已经编译,即直接结束条件编译. 3 4 #include "stm32f10x.h"

【Linux驱动】TQ2440 LED驱动程序

★总体介绍 LED驱动程序主要实现了TQ2440开发板上的4个LED灯的硬件驱动,实现了对引脚GPIOB5.GPIOB6.GPIOB7.GPIOB8的高低电平设置(common-smdk.c中已经实现了对引脚的配置),利用测试程序调用该驱动程序,通过命令控制LED灯的亮灭. ★详细介绍 1.驱动程序代码:My_led.c #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #

我的 FPGA 学习历程(02) &mdash;&mdash; 实验点亮 LED 灯

关于 Quartus 的操作可以使用 Quartus 自带的帮助,帮助中有全套的操作教程. 中文网络教程链接(链接至altera中文官网,点击观看) Quartus II 软件设计系列:基础 Quartus II 软件中的原理图设计 SignalTap II 嵌入式逻辑分析器 使用Nios II 处理器 系统控制台 Nios II 处理器开发软件:设计流程 Nios II 处理器开发软件:MMU和MPU 无论是英文帮助还是网络中文教程,他们的前提是你是有基础的,这些教程之会教给你我们的 quar

嵌入式学习笔记002-点亮led

正如编写第一个程序所打印"hello world"那样,对TQ2440开发板的初次使用也是从最简单的部分入手,点亮led灯无疑是最简单的,起码只要设置几个寄存器就好,比起打印容易得多了,后续会讲到串口部分再来实现我们这句金典的对白~~~~ 板子共有4个led灯,如果能够成功点亮其中一个,那么其他几个将不是问题,故而我们的目标是先点亮第一个先,在此我们必须理清几个问题: a. led的引脚连接的相关寄存器 b. 编写的语言 c. 用什么来编译链接成可执行文件 d. 怎么download到

STM32F407第一步之点亮LED

STM32F407第一步之点亮LED. 要点亮LED,首先了解一下F4的GPIO模块.首先看一下STM32F4数据手册,GPIO模块的内部结构图 看上去有点复杂,不要怕,慢慢理解就可以了.对外引脚那里二极管就是保护的作用.通过上.下拉对应的开关配置,控制引脚默认状态的电压,开启上拉的时候引脚电压为高电平,开启下拉的时候引脚电压为低电平,这样可以消除引脚不定状态的影响.但是这个不应该用来作为外部的上拉或下拉用,如按键的拉电阻不能用这个内部来作用,如果用可能会引起按键不稳定. GPIO 具有了“推挽

20150223 IMX257 LED驱动程序实现

20150223 IMX257 LED驱动程序实现 2015-02-23 李海沿 由于昨天对IMX257的地址分配不了解,所以前面只能用s3c24xx的驱动程序来了解ioremap等对IO端口的工作原理. 但是经过昨晚对IMX257芯片的细细梳理,今天早上起来又把IMX257的芯片资料看了一遍,终于成功看懂了,下面意义给大家道来. 我们此处使用ERR_LED 也就是GPIO3_23引脚 一.IMX257 芯片资料分析 1.确定相关寄存器基址 确定IOMUX地址 GPIO3的地址 2.确定相关寄存

Linux下的led驱动程序,ok6410

本程序采用动态映射的方法控制led,硬件平台为飞凌的ok6410 led.h:定义控制命令 #ifndef _LED_H #define _LED_H #define LED_MAGIC 'M' #define LED_ON _IO(LED_MAGIC, 0) #define LED_OFF _IO(LED_MAGIC, 1) #endif 驱动程序led.c #include <linux/module.h> #include <linux/init.h> #include &l

LED驱动程序 S3C6410

这两天写了个LED驱动程序,网上也看了好多的帖子. 开始思路很清晰了,就是先看电路图,发现LED灯是接在GPM端口上的, 然后看S3C6410数据手册,先向GPMCON口写命令字,让GPM0-5设置为输出,再向GPMDAT口写数据字,在GPM0-5引脚拉低或拉高电平, 从而控制LED的亮灭. 1.电路图 很显然LED灯是接在GPM口引脚下面的 2.数据手册 3.LED驱动程序 #include <linux/module.h> #include <linux/kernel.h> #