查看: 314|回复: 1
打印 上一主题 下一主题

再谈BananaPI的键盘扩展PCF8574

[复制链接] qrcode

21

主题

26

帖子

78

积分

注册会员

Rank: 2

积分
78
楼主
跳转到指定楼层
发表于 2015-8-30 11:12 AM | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在论坛上看到一篇讲PCF8574的,原文在http://bbs.ickey.cn/group-topic-id-52990.html

之前我还有看到利用BananaPI内置ADC做键盘扫描的,发现大家思路很广,所以我也来献个丑,把我做的PCF8574拉到BananaPI跑一跑。

PCF8574模块有4根线,I2C的SCL/SDA+GND/GPIO。SCL/SDA作为通信口管理8个IO口,GND/GPIO作为中断源使用。其实linux内核中咋driver/input下面有pcf8574_keypad.c驱动,将其作为键盘使用。

实际上PCF8574实际上是8位IO口的,既可以输入也可以输出,代码中也是先高4位IO拉高,再读取高4位行线的扫描结果,再拉高低4位IO,读取低4位列线的扫描结果,最后计算实际的按键键码。下面是Linux内核标准驱动中按行列模式的扫描代码:

static short read_state(struct kp_data *lp)
{
 unsigned char x, y, a, b;

 i2c_smbus_write_byte(lp->client, 240);
 x = 0xF & (~(i2c_smbus_read_byte(lp->client) >> 4));

 i2c_smbus_write_byte(lp->client, 15);
 y = 0xF & (~i2c_smbus_read_byte(lp->client));

 for (a = 0; x > 0; a++)
  x = x >> 1;
 for (b = 0; y > 0; b++)
  y = y >> 1;

 return ((a - 1) * 4) + b;
}

然后整个驱动实际是依靠INT(连接到CPU的GPIO端口)的中断来触发按键扫描的,因为PCF8574如果检测到IO任意输入变化后,将会将INT输出拉低,直到I2C的读写信号来临才会将INT输出复位为高电平。

我修改后的代码如下:这里IO口只使用1x8的扫描模式,而不是4x4的行列扫描模式,而且有4个模块共享I2C总线和INT中断(与门),同时修改了一下扫描规则保证能支持同时多个按键按下(Linux按键机制支持按下事件和释放事件)。
#include
#include
#include
#include
#include
#include
#include

#define DRV_NAME "pcf8574_keypad"

#define KEYS_PER_PCF8574 (8)
static const unsigned char pcf8574_kp_btncode[4][8] = 
{
{
 [0] = KEY_F1,
 [1] = KEY_F2,
 [2] = KEY_F3,
 [3] = KEY_F4,
 [4] = KEY_F5,
 [5] = KEY_F6,
 [6] = KEY_F7,
 [7] = KEY_F8,
},
{
 [0] = KEY_Q,
 [1] = KEY_W,
 [2] = KEY_E,
 [3] = KEY_R,
 [4] = KEY_T,
 [5] = KEY_Y,
 [6] = KEY_U,
 [7] = KEY_I,
},
{
 [0] = KEY_A,
 [1] = KEY_S,
 [2] = KEY_D,
 [3] = KEY_F,
 [4] = KEY_G,
 [5] = KEY_H,
 [6] = KEY_J,
 [7] = KEY_K,
},
//ZXCVBNM,
{
 [0] = KEY_Z,
 [1] = KEY_X,
 [2] = KEY_C,
 [3] = KEY_V,
 [4] = KEY_B,
 [5] = KEY_N,
 [6] = KEY_M,
 [7] = KEY_COMMA,
}


};



struct kp_data {
 unsigned short btncode[KEYS_PER_PCF8574];
 struct input_dev *idev;
 struct i2c_client *client;
 char name[64];
 char phys[32];
 unsigned char laststate;
 unsigned char index;
};

static short read_state(struct kp_data *lp)
{
 unsigned char x;

 x = ~(i2c_smbus_read_byte(lp->client));

 return x;
}

static irqreturn_t pcf8574_kp_irq_handler(int irq, void *dev_id)
{
 struct kp_data *lp = dev_id;

 unsigned char nextstate = read_state(lp);
 unsigned char i = 0 ;
 int key_down = 0;
 //printk("[PCF8574]irq %d for %sn",irq,lp->name);
 if (lp->laststate != nextstate) 
 {
  for(i = 0;i < KEYS_PER_PCF8574;i++)
  {
   if( (nextstate & (1<laststate & (1<
   {
    key_down = (nextstate & (1< 0;
    input_report_key(lp->idev, lp->btncode, key_down);
    //printk("[PCF8574]report key %d state %dn",lp->btncode,key_down);
   }
  
  }

  input_sync(lp->idev);

  lp->laststate = nextstate;
 }

 return IRQ_HANDLED;
}

static int __devinit pcf8574_kp_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
 int i, ret;
 struct input_dev *idev;
 struct kp_data *lp;

 if (i2c_smbus_read_byte(client) < 0) {
  dev_err(&client->dev, "probe: read failn");
  return -ENODEV;
 }

 lp = kzalloc(sizeof(*lp), GFP_KERNEL);
 if (!lp)
  return -ENOMEM;

 idev = input_allocate_device();
 if (!idev) {
  dev_err(&client->dev, "Can\'t allocate input devicen");
  ret = -ENOMEM;
  goto fail_allocate;
 }

 lp->idev = idev;
 lp->client = client;
 lp->index = (client->addr & 0x3);
 idev->evbit[0] = BIT_MASK(EV_KEY);
 idev->keycode = lp->btncode;
 idev->keycodesize = sizeof(lp->btncode[0]);
 idev->keycodemax = KEYS_PER_PCF8574;

 for (i = 0; i < KEYS_PER_PCF8574; i++) 
 {
  lp->btncode = pcf8574_kp_btncode[lp->index];
  __set_bit(lp->btncode & KEY_MAX, idev->keybit);
 }

 sprintf(lp->name,DRV_NAME);
 sprintf(lp->phys,"kp_data/input0");

 idev->name = lp->name;
 idev->phys = lp->phys;
 idev->id.bustype = BUS_I2C;
 idev->id.vendor = 0x0001;
 idev->id.product = 0x0001;
 idev->id.version = 0x0100;

 lp->laststate = read_state(lp);

 ret = request_threaded_irq(client->irq, NULL, pcf8574_kp_irq_handler,
       IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED,
       DRV_NAME, lp);
 if (ret) {
  dev_err(&client->dev, "IRQ %d is not freen", client->irq);
  goto fail_free_device;
 }
 ret = input_register_device(idev);
 if (ret) {
  dev_err(&client->dev, "input_register_device() failedn");
  goto fail_free_irq;
 }

 i2c_set_clientdata(client, lp);
 printk("[PCF8574] probe device %s,addr 0x%x ,irq %dn",lp->name,client->addr,client->irq);
 return 0;

 fail_free_irq:
 free_irq(client->irq, lp);
 fail_free_device:
 input_free_device(idev);
 fail_allocate:
 kfree(lp);

 return ret;
}

static int __devexit pcf8574_kp_remove(struct i2c_client *client)
{
 struct kp_data *lp = i2c_get_clientdata(client);

 free_irq(client->irq, lp);

 input_unregister_device(lp->idev);
 kfree(lp);

 return 0;
}

#ifdef CONFIG_PM
static int pcf8574_kp_resume(struct device *dev)
{
 struct i2c_client *client = to_i2c_client(dev);

 enable_irq(client->irq);

 return 0;
}

static int pcf8574_kp_suspend(struct device *dev)
{
 struct i2c_client *client = to_i2c_client(dev);

 disable_irq(client->irq);

 return 0;
}

static const struct dev_pm_ops pcf8574_kp_pm_ops = {
 .suspend = pcf8574_kp_suspend,
 .resume = pcf8574_kp_resume,
};

#else
# define pcf8574_kp_resume  NULL
# define pcf8574_kp_suspend NULL
#endif

static const struct i2c_device_id pcf8574_kp_id[] = {
 { DRV_NAME, 0 },
 { }
};
MODULE_DEVICE_TABLE(i2c, pcf8574_kp_id);

static struct i2c_driver pcf8574_kp_driver = {
 .driver = {
  .name  = DRV_NAME,
  .owner = THIS_MODULE,
#ifdef CONFIG_PM
  .pm = &pcf8574_kp_pm_ops,
#endif
 },
 .probe    = pcf8574_kp_probe,
 .remove   = __devexit_p(pcf8574_kp_remove),
 .id_table = pcf8574_kp_id,
};

static int __init pcf8574_kp_init(void)
{
 int ret;
 ret = i2c_add_driver(&pcf8574_kp_driver);
 return ret;
}
module_init(pcf8574_kp_init);

static void __exit pcf8574_kp_exit(void)
{
 i2c_del_driver(&pcf8574_kp_driver);
}
module_exit(pcf8574_kp_exit);

MODULE_AUTHOR("Michael Hennerich");
MODULE_DESCRIPTION("Keypad input driver for  PCF8574");
MODULE_LICENSE("GPL");

实际使用时候还要在BSP中为PCF8574注册GPIO口作为INT线并指定i2c_client的irq号。

最后使用标准Linux的输入输出系统就可以读取键盘了。
#ifndef _INPUT_API_H
#define _INPUT_API_H

#include        
#include      
#include       
#include    
#include     
#include  
#include        
#include      
#include        
#include  
#include  
#include  


typedef struct 
{
int fd;
struct input_event ev;
char *device;
}INPUT_HANDLE;


int input_open(INPUT_HANDLE *h,char *device);

struct input_event * input_read(INPUT_HANDLE *h);

int input_close(INPUT_HANDLE *h);

struct input_event * input_get(INPUT_HANDLE *h,char *device);

#endif

#include "input_api.h"


int input_open(INPUT_HANDLE *h,char *device)
{
  h->fd = open(device,O_RDONLY);
 if (h->fd < 0)
 {
  return h->fd;
 }
 return h->fd;
}

struct input_event * input_read(INPUT_HANDLE *h)
{
 int ret;
 ret = read(h->fd,&(h->ev),sizeof(struct input_event));
 if(ret < 0 )
 {
  perror("read event fail");
  return NULL;
 }
 if(ret < sizeof(struct input_event) )
 {
  return NULL;
 }

 return &(h->ev);
}

int input_close(INPUT_HANDLE *h)
{
 if (h->fd)
 {
  close(h->fd);
  h->fd = 0;
 }
 return 0;
}

int main(int argc, char *argv[])
{
 
 int ret;
 INPUT_HANDLE h;
 char *dev;
 
 if(argc<2)
 {
  return -1;
 }
 
 dev = argv[1];
 
 
 struct input_event * ev;
 char *type = "unknown";  
 char *action = "unknown";  
 ret = input_open(&h,dev);
 if(ret < 0)
 {
  perror("open fail");
  return 0;
 }
 int i = 0;
 while(1)
 {
  ev = input_read(&h);
  if(ev)
  {
   i = 0;
      printf("[%2d-%02d-%08d]",ev->type,ev->code,ev->value);
               
   switch(ev->type)
   {  
    case EV_SYN: type = "syn"; break;
    case EV_KEY: type = "keys/buttons"; break;  
    case EV_REL: type = "relative";     break;  
    case EV_ABS: type = "absolute";     break;  
    case EV_MSC: type = "reserved";     break;  
    case EV_FF_STATUS: type = "ff state";     break;
    default:type = "unknow";     break;
   }  
   
   printf("%s ",type);
   
   if(ev->type ==  EV_REL)
   {
    switch(ev->code)
    {
     case ABS_X:       action = "X";        break;  
     case ABS_Y:       action = "Y";        break;  
     case REL_DIAL:    action = "DIAL";    break;
     case REL_WHEEL:   action = "WHEEL";    break;  
     default:  action = "unknow";     break;
    }
    printf("%s %d",action,ev->value);
   }
   if(ev->type ==  EV_KEY)
   {
    if (ev->code > BTN_MISC)
    {
     printf("Btn %d %s", ev->code & 0xff,ev->value ? "press" : "release");  
    }
    else
    {
     printf("Key %d %s", ev->code & 0xff,ev->value ? "press" : "release");
    }
   
   }
   
   printf("n");
  }
  else
  {
   printf("input device invalidn");
   break;
  }
 }
 input_close(&h);

 return 0;
}



回复

使用道具 举报

3

主题

97

帖子

23

积分

新手上路

Rank: 1

积分
23
沙发
发表于 2015-9-6 10:16 PM | 只看该作者
好帖当顶

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表