S3C44B0X公板Keyboard Driver For Uclinux
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[i]= KEY_UP;
//初始化并设置4个去抖定时器
// setup_timer(&key_timers[i],buttons_timer,i);
init_timer(&key_timers[i]);
key_timers[i].function = buttons_timer;
key_timers[i].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[i]);
}
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[i]== 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,打开你的板子,开始测试。
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。