原创 S3C44B0X公板Keyboard Driver For Uclinux

2010-1-25 13:33 3040 7 7 分类: MCU/ 嵌入式
S3C44B0X公板Keyboard Driver For Uclinux
By panasonic.lin@163.com


S3C44B0X公板Keyboard连接如下:
KEYIN0->EXINT4/GPG4
KEYIN1->EXINT5/GPG5
KEYIN2->EXINT6/GPG6
KEYIN3->EXINT7/GPG7
这四个按键通过OR的方式公用一条中断线EINT4567(编号21的外部中断),所以要通过EXTINPND寄存器的标志位来区别这四个按键。
通过这个实例,基本上LDD第十章之前的内容都有涉及,为后面打下坚实的基础。
注意的是2.4内核和2.6内核的驱动有很大区别,如:
1,read函数中的__user宏,2.4没有;
2,中断处理函数返回值,2.4是void;
3,定时器的setuptimer,2.4没有;
4,poll函数的poll_table。

第一步,创建s3c44b0key.c如下:
/*
************************************************************************
*Name            : s3c44b0key.c
*Author          : panasonic.lin@163.com
*Date            : 11/1/2010
*Copyright       : GPL
*Description     : s3c44b0x keyboard driver
************************************************************************
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/types.h>    /* size_t */
#include <asm/uaccess.h>    /* copy_*_user */
#include <asm/system.h>        /* cli(), *_flags */
#include <linux/fcntl.h>    /* O_ACCMODE */
#include <linux/module.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <asm/ioctl.h>
#include <asm/irq.h>
//#include <asm/arch/irqs.h>All IRQ numbers of the S3C44B0X CPU
//#include <mach/hardware.h>
#include <asm/arch/hardware.h>

#define DEVICE_NAME         "s3c44b0keyboard"    //设备名称
#define DEVICE_MAJOR        232                 //主设备号               
#define KEY_TIMER_DELAY1    (HZ/50)             //按键-按下-去抖延时20毫秒       
#define KEY_TIMER_DELAY2    (HZ/10)             //按键-抬起-去抖延时100毫秒

#define KEY_DOWN            0                   //按键按下                   
#define KEY_UP              1                   //按键抬起               
#define KEY_UNCERTAIN       2                   //按键不确定                   

#define KEY_COUNT           4                   //4个按键                   

#define EINT4               1<<4
#define EINT5               1<<5
#define EINT6               1<<6
#define EINT7               1<<7

#define PCONG_EINT4         0x3<<8
#define PCONG_EINT5         0x3<<10
#define PCONG_EINT6         0x3<<12
#define PCONG_EINT7         0x3<<14

#define EINT4567            21
#define MASK_BIT(bit)       (1<<(bit))

static volatile int ev_press = 0;                //按键按下产生标志
static volatile int key_status[KEY_COUNT];       //记录4个按键的状态   
static struct timer_list key_timers[KEY_COUNT];  //定义4个按键去抖动定时器
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);    //定义并初始化等待队列头

//组织硬件资源结构体
struct button_irq_desc {
    int irq;            //中断号uclinux/include/asm-armnommu/arch-S3C44B0X/irqs.h
    int pin;            //对应引脚
     int dev_id;         //id识别
    char *name;         //按键名称
};

//定义4个按键资源结构体数组#define S3C44B0X_INTERRUPT_EINT4567   21 /* External int. 4,5,6 and 7 */
static struct button_irq_desc button_irqs[KEY_COUNT] ={
        {S3C44B0X_INTERRUPT_EINT4567,EINT4,0,"KEY0"},
        {S3C44B0X_INTERRUPT_EINT4567,EINT5,1,"KEY1"},
        {S3C44B0X_INTERRUPT_EINT4567,EINT6,2,"KEY2"},
        {S3C44B0X_INTERRUPT_EINT4567,EINT7,3,"KEY3"},
};

/*--------------------------------------------------------------------
*获取GPGDATA引脚电平
*--------------------------------------------------------------------*/
static int s3c44b0_gpio_getpin(int pin_bit){
    return ((*(volatile unsigned *)S3C44B0X_PDATG)&pin_bit);   
}

/*--------------------------------------------------------------------
*中断服务例程
*--------------------------------------------------------------------*/
void buttons_interrupt(int irq,void *dev_id, struct pt_regs *regs){
     //获取当前按键资源的索引
    volatile int key;
    key=(*(volatile unsigned *)S3C44B0X_EXTINPND);//读取EXTINPND标志

    if(key>0){
    switch(key&0x0f){
    case 1 :{key=0;}break;
    case 2 :{key=1;}break;
    case 4 :{key=2;}break;
    case 8 :{key=3;}break;
    default:break;
    }
    //如果按键在其他状态下,直接退出中断
   if(key_status[key]== KEY_UP){
        //设置当前按键的状态为不确定
    key_status[key]= KEY_UNCERTAIN;
        //设置当前按键按下去抖定时器的延时并启动定时器后退出中断,然后在定时器服务程序里面判断
    key_timers[key].expires = jiffies + KEY_TIMER_DELAY1;
    add_timer(&key_timers[key]);
     }
    // 清除相应标志位
     (*(volatile unsigned *)S3C44B0X_EXTINPND) |= (1<<key);//write 1 to extintpend to clear
    (*(volatile unsigned *)S3C44B0X_I_ISPC) |=(1 << S3C44B0X_INTERRUPT_EINT4567);
    }
}

/*--------------------------------------------------------------------
**定时器服务例程
**
*--------------------------------------------------------------------*/
static void buttons_timer(unsigned long arg){
    //获取当前按键资源的索引dev_id
    int key = (int)arg;
    // 下降沿定时时间到,获取当前按键引脚上的电平值来判断按键是按下还是抬起
    int up = s3c44b0_gpio_getpin(button_irqs[key].pin);
    if(!up){//==0低电平,按键按下
    if(key_status[key]== KEY_UNCERTAIN){
    //标识当前按键状态为按下
    key_status[key]= KEY_DOWN;
    //标识当前按键已按下并唤醒等待队列
    ev_press = 1;
    wake_up_interruptible(&button_waitq);
     }    
        //设置当前按键- 抬起- 去抖定时器的延时并启动定时器
    key_timers[key].expires = jiffies + KEY_TIMER_DELAY2;
    add_timer(&key_timers[key]);
     }
    else{//高电平,按键抬起
       //标识当前按键状态为抬起
    key_status[key]= KEY_UP;
    }
}

/*--------------------------------------------------------------------
**fileoperation open funtion
**
*--------------------------------------------------------------------*/
static int buttons_open(struct inode *inode,struct file *file)
{
    int i;
    int ret=0;
    //设置PORTG4个IO口funtion是EINTX
    (*(volatile unsigned *)S3C44B0X_PCONG) |=0XFF00;
    //设置中断模式INTMOD和中断控制INTCON寄存器
    (*(volatile unsigned *)S3C44B0X_INTMOD) &=~MASK_BIT(EINT4567);
    (*(volatile unsigned *)S3C44B0X_INTCON) &=~MASK_BIT(1);
     //设置中断下降沿为有效触发
     (*(volatile unsigned *)S3C44B0X_EXTINT)=((*(volatile unsigned *)S3C44B0X_EXTINT)&0X0000FFFF)|0X33330000;

    ret = request_irq(button_irqs[0].irq,buttons_interrupt,SA_INTERRUPT,button_irqs[0].name,NULL);           
    for(i = 0;i<KEY_COUNT;i++){         
        //初始化4个按键的状态为抬起
    key_status= KEY_UP;
     //初始化并设置4个去抖定时器
    // setup_timer(&key_timers,buttons_timer,i);
    init_timer(&key_timers);
    key_timers.function = buttons_timer;
    key_timers.data = i;
     }
    if(ret){
     //中断申请失败处理
    printk(KERN_ALERT "request button_irqs[0].irq fail!\n");
    //释放已注册成功的中断
    disable_irq(button_irqs[0].irq);
    free_irq(button_irqs[0].irq,NULL);
    return -EBUSY;
    }
    else{
    printk(KERN_ALERT "request  irqs sucess!\n");
    }
    return 0;
}

/*--------------------------------------------------------------------
**fileoperation close funtion
**
*--------------------------------------------------------------------*/
static int buttons_close(struct inode *inode,struct file *file)
{
    int i;
    //释放4个定时器和中断
    for(i = 0;i < KEY_COUNT;i++){
    del_timer(&key_timers);
    }
    disable_irq(button_irqs[0].irq);
    free_irq(button_irqs[0].irq,NULL);
    return 0;
}

/*--------------------------------------------------------------------
**fileoperation read funtion
**
*--------------------------------------------------------------------*/
ssize_t buttons_read(struct file *filp,char *buf,size_t count,loff_t *offp)
{
    ssize_t ret=0;
    if(!ev_press){//==0判断按键按下产生标识,0没有产生//
    if(filp->f_flags & O_NONBLOCK){
     //应用程序若采用非阻塞方式读取则返回错误//
    return -EAGAIN;
     }
    else{
    //以阻塞方式读取且按键按下没有产生,让等待队列进入睡眠//
    wait_event_interruptible(button_waitq,ev_press);
     }
     }
    //1为按键按下产生,并清除标识为0,准备给下一次判断用//
    ev_press = 0;
    //将内核中的按键状态数据拷贝到用户空间给应用程序使用//
    ret = copy_to_user(buf,&key_status,min(sizeof(key_status),count));
    //ret==0,sucessful,other is fail
    return ret?-EFAULT:min(sizeof(key_status),count);
}


/*--------------------------------------------------------------------
**fileoperation poll funtion
**
*--------------------------------------------------------------------*/
//驱动程序中的轮询,用于应用程序中的轮询查询是否可对设备进行访问
static unsigned int buttons_poll(struct file *file,poll_table *wait)
{
    unsigned int mask = 0;
     //添加等待队列到等待队列表中(poll_table)
    poll_wait(file,&button_waitq,wait);
    if(ev_press){//==1
        //标识数据可以获得
    mask |= POLLIN|POLLRDNORM;
     }
    return mask;
}

/*--------------------------------------------------------------------
**file_operations
**
*--------------------------------------------------------------------*/
//设备操作列表
static struct file_operations buttons_fops =
{
    .owner        = THIS_MODULE,
    .open         = buttons_open,
    .release      = buttons_close,
    .read         = buttons_read,
    .poll         = buttons_poll,
};

/*--------------------------------------------------------------------
**button_init(void)
**
*--------------------------------------------------------------------*/
static int __init button_init(void)
{
    int ret;
    //注册字符设备
    ret = register_chrdev(DEVICE_MAJOR,DEVICE_NAME,&buttons_fops);
    if(ret< 0){
    printk(DEVICE_NAME"register faild!\n");
    return ret;
    }
    else{
    printk(KERN_ALERT "register_chardev sucessful!\n");
    }
    return 0;
}

/*--------------------------------------------------------------------
**button_exit(void)
**
*--------------------------------------------------------------------*/
static void __exit button_exit(void)
{
     //注销字符设备
    unregister_chrdev(DEVICE_MAJOR,DEVICE_NAME);
}

module_init(button_init);
module_exit(button_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("panasonic.lin@163.com");
MODULE_DESCRIPTION("s3c44box buttons driver");

第二步,创建Makefile如下:
注意-D__linux__不是-D__LINUX__,关于这些CFLAGS可以在编译uclinux的时候重定向到某个文本文件,然后任意找到一个字符设备驱动的字段拷贝过来就行了,当然理解还是很重要的。

# Change it here or specify it on the "make" command line
CC=arm-elf-gcc
KERNELDIR = /home/panasonic/Data/uClinux-dist20050311/linux-2.4.x
include $(KERNELDIR)/.config

CFLAGS = -D__KERNEL__ -D__linux__ -DMODULE -DNO_MM -mapcs-32 -march=armv4 -mshort-load-bytes -msoft-float -mtune=arm7tdmi -fno-strict-aliasing -fno-common -pipe -fno-builtin -fomit-frame-pointer -fsigned-char  -O -Wall -Wstrict-prototypes -Wno-trigraphs -I$(KERNELDIR)/include


all: s3c44b0key.o
s3c44b0key.o:s3c44b0key.c
    $(CC) $(CFLAGS) -c s3c44b0key.c
clean:
    rm -f s3c44b0key.o
第三步,make即可生成模块。

点击看大图

第四步,创建应用程序buttons_test.c如下:
/*
************************************************************************
*Name            : buttons_test.c
*Author          : panasonic.lin@163.com
*Date            : 11/1/2010
*Copyright       : GPL
*Description     : s3c44b0x key driver
************************************************************************
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, char **argv)
{
    int fd;
    int key_status[4];

    //以阻塞方式打开设备文件,非阻塞时flags=O_NONBLOCK
    fd = open("/dev/s3c44b0keyboard",O_RDONLY|O_NONBLOCK);

    if(fd < 0)
    {
        printf("Open Buttons Device Faild!\n");
        exit(1);
    }

    while(1)
    {
    int i;
    int ret;
    fd_set rds;
       
    FD_ZERO(&rds);
    FD_SET(fd, &rds);
       
        //应用程序进行轮询,查询是否可对设备进行访问
    ret = select(fd + 1, &rds, NULL, NULL, NULL);
       
    if(ret < 0)
     {
    printf("Read Buttons Device Faild!\n");
    exit(1);
     }
   
    if(ret == 0)
        {
    printf("Read Buttons Device Timeout!\n");
        }

    else if(FD_ISSET(fd, &rds))
    {
            //读设备
    ret = read(fd,&key_status, sizeof(key_status));
    if(ret != sizeof(key_status))
    {
    if(errno != EAGAIN)
    {
    printf("Read Button Device Faild!\n");
     }

    continue;
     }

    else
    {
    for(i = 0; i < 4; i++)
    {
      //对应驱动中按键的状态,为0即按键被按下
    if(key_status== 0)
    {
    printf("Key[%d] Press Down!\n",i);
    }
    }
    }
    }
    }

    close(fd);

    return 0;
}

第五步:
$arm-elf-gcc -static -elf2flt -o buttons_test buttons_test.c
将生成的buttons_test应用程序和s3c44b0key.o模块拷贝到nfs输出目录,这里拷贝到bin目录下。
第六步:启动minicom,打开你的板子,开始测试。

点击看大图

文章评论0条评论)

登录后参与讨论
我要评论
0
7
关闭 站长推荐上一条 /2 下一条