Marco de desarrollo de subsistemas de entrada bajo Linux

Marco de desarrollo de subsistemas de entrada bajo Linux

El artículo "Análisis del subsistema de entrada de Linux" presenta el marco del subsistema de entrada y el proceso de escritura del controlador de entrada. Sobre esta base, este artículo tomará el botón KEY0 en la placa de desarrollo IMX6ULL como ejemplo para presentar cómo implementar el controlador de entrada de entrada.

La figura anterior muestra el marco de la plantilla de desarrollo del controlador del subsistema de entrada, y lo siguiente se codificará de acuerdo con este marco

1. Modificar el árbol de dispositivos

⏩ Agregue el nodo pinctrl: cree un nodo pinctrl_key debajo del subnodo imx6ul-evk del nodo iomuxc y reutilice UART1_CTS_B

pinctrl_key: keygrp {
    
    
 fsl,pins = <
  MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080
 >;
};
//MX6UL_PAD_UART1_CTS_B__GPIO1_IO18用于设置pin的复用功能
//0xF080 用于设置pin的电气特性

⏩ Agregar nodo de dispositivo CLAVE: cree un nodo de dispositivo CLAVE debajo del nodo raíz, configure el nodo pinctrl correspondiente al PIN y especifique el GPIO utilizado

key {
    
    
 #address-cells = <1>;
 #size-cells = <1>;
 compatible = "andyxi-key";
 pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_key>;
 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
 status = "okay";
};

⏩ Verifique si el PIN entra en conflicto: verifique si la configuración en pinctrl y los pines especificados en el nodo del dispositivo son utilizados por otros periféricos

2. Escritura del controlador

Cree un nuevo archivo de controlador keyinput.c e ingrese el siguiente contenido
⏩ macro y definición de estructura de dispositivo

#define KEYINPUT_CNT  1           //设备号个数
#define KEYINPUT_NAME  "keyinput" //名字
#define KEY0VALUE   0X01          //KEY0按键值
#define INVAKEY    0XFF           //无效的按键值
#define KEY_NUM    1              //按键数量 
/* 中断IO描述结构体 */
struct irq_keydesc {
    
    
 int gpio;                  //gpio
 int irqnum;                //中断号
 unsigned char value;       //按键对应的键值
 char name[10];             //名字
 irqreturn_t (*handler)(int, void *); //中断服务函数
};
/* keyinput设备结构体 */
struct keyinput_dev{
    
    
 dev_t devid;                //设备号
 struct cdev cdev;           //cdev
 struct class *class;        //类
 struct device *device;      //设备
 struct device_node *nd;     //设备节点
 struct timer_list timer;    //定义一个定时器
 struct irq_keydesc irqkeydesc[KEY_NUM]; //按键描述数组
 unsigned char curkeynum;       //当前的按键号
 struct input_dev *inputdev;    //input结构体
};

struct keyinput_dev keyinputdev; //key input设备 */

⏩ Encienda el temporizador en la función de servicio de interrupción para el rebote del botón

/*中断服务函数 */
static irqreturn_t key0_handler(int irq, void *dev_id){
    
    
 struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;

 dev->curkeynum = 0;
 dev->timer.data = (volatile long)dev_id;
 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); //10ms定时
 return IRQ_RETVAL(IRQ_HANDLED);
}

⏩ En la función de servicio de temporizador, informe el valor clave

/* 定时器服务函数,用于按键消抖 */
void timer_function(unsigned long arg){
    
    
 unsigned char value;
 unsigned char num;
 struct irq_keydesc *keydesc;
 struct keyinput_dev *dev = (struct keyinput_dev *)arg;

 num = dev->curkeynum;
 keydesc = &dev->irqkeydesc[num];
 value = gpio_get_value(keydesc->gpio);  /* 读取IO值 */
 if(value == 0){
    
           /* 按下按键 */
  /* 上报按键值 */
  input_report_key(dev->inputdev, keydesc->value, 1);
  input_sync(dev->inputdev);
 } else {
    
              /* 按键松开 */
  input_report_key(dev->inputdev, keydesc->value, 0);
  input_sync(dev->inputdev);
 } 
}

⏩ En la función de inicialización de E/S clave, solicite y registre el dispositivo de entrada

static int keyio_init(void){
    
    
 unsigned char i = 0;
 char name[10];
 int ret = 0;
 
 keyinputdev.nd = of_find_node_by_path("/key");
 if (keyinputdev.nd== NULL){
    
    
  printk("key node not find!\r\n");
  return -EINVAL;
 } 
 /* 提取GPIO */
 for (i = 0; i < KEY_NUM; i++) {
    
    
  keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd ,"key-gpio", i);
  if (keyinputdev.irqkeydesc[i].gpio < 0) {
    
    
   printk("can't get key%d\r\n", i);
  }
 } 
 /* 初始化key所使用的IO,并且设置成中断模式 */
 for (i = 0; i < KEY_NUM; i++) {
    
    
  memset(keyinputdev.irqkeydesc[i].name, 0, sizeof(name)); /* 缓冲区清零 */
  sprintf(keyinputdev.irqkeydesc[i].name, "KEY%d", i);  /* 组合名字 */
  gpio_request(keyinputdev.irqkeydesc[i].gpio, name);
  gpio_direction_input(keyinputdev.irqkeydesc[i].gpio); 
  keyinputdev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keyinputdev.nd, i);
 }
 /* 申请中断 */
 keyinputdev.irqkeydesc[0].handler = key0_handler;
 keyinputdev.irqkeydesc[0].value = KEY_0;
 
 for (i = 0; i < KEY_NUM; i++) {
    
    
  ret = request_irq(keyinputdev.irqkeydesc[i].irqnum, keyinputdev.irqkeydesc[i].handler, 
  IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, keyinputdev.irqkeydesc[i].name,&keyinputdev);
  if(ret < 0){
    
    
   printk("irq %d request failed!\r\n", keyinputdev.irqkeydesc[i].irqnum);
   return -EFAULT;
  }
 }
 /* 创建定时器 */
 init_timer(&keyinputdev.timer);
 keyinputdev.timer.function = timer_function;
 /* 申请input_dev */
 keyinputdev.inputdev = input_allocate_device();
 keyinputdev.inputdev->name = KEYINPUT_NAME;
#if 0
 /* 初始化input_dev,设置产生哪些事件 */
 __set_bit(EV_KEY, keyinputdev.inputdev->evbit); /* 设置产生按键事件 */
 __set_bit(EV_REP, keyinputdev.inputdev->evbit); /* 重复事件,若按下不放会一直输出信息*/
 /* 初始化input_dev,设置产生哪些按键 */
 __set_bit(KEY_0, keyinputdev.inputdev->keybit); 
#endif

#if 0
 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endif

 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
 /* 注册输入设备 */
 ret = input_register_device(keyinputdev.inputdev);
 if (ret) {
    
    
  printk("register input device failed!\r\n");
  return ret;
 }
 return 0;
}

⏩ La función de inicialización de clave se llama en la entrada del controlador, y la función de salida elimina el temporizador, la interrupción y input_dev

/* 驱动入口函数 */
static int __init keyinput_init(void){
    
    
 keyio_init();
 return 0;
}
/* 驱动出口函数 */
static void __exit keyinput_exit(void){
    
    
 unsigned int i = 0;
 /* 删除定时器 */
 del_timer_sync(&keyinputdev.timer); /* 删除定时器 */  
 /* 释放中断 */
 for (i = 0; i < KEY_NUM; i++) {
    
    
  free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev);
 }
 /* 释放input_dev */
 input_unregister_device(keyinputdev.inputdev);
 input_free_device(keyinputdev.inputdev);
}

module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");

3. Escritura del programa de prueba

Cree un nuevo archivo de prueba keyinputApp.c y escriba el programa

/* 定义一个input_event变量,存放输入事件信息 */
static struct input_event inputevent;

int main(int argc, char *argv[]) {
    
    
 int fd;
 int err = 0;
 char *filename;

 filename = argv[1];
 if(argc != 2) {
    
    
  printf("Error Usage!\r\n");
  return -1;
 }

 fd = open(filename, O_RDWR);
 if (fd < 0) {
    
    
  printf("Can't open file %s\r\n", filename);
  return -1;
 }

 while (1) {
    
    
  err = read(fd, &inputevent, sizeof(inputevent));
  if (err > 0) {
    
     /* 读取数据成功 */
   switch (inputevent.type) {
    
    
    case EV_KEY:
     if (inputevent.code < BTN_MISC) {
    
     /* 键盘键值 */
      printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
     } else {
    
    
      printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
     }
     break;
    /* 其他类型的事件,自行处理 */
    case EV_REL:
     break;
    case EV_ABS:
     break;
    case EV_MSC:
     break;
    case EV_SW:
     break;
   }
  } else {
    
    
   printf("读取数据失败\r\n");
  }
 }
 return 0;
}

4. Compilar y probar

⏩ Compile el controlador: cree un Makefile en el directorio actual y compílelo con make

KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := keyinput.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

⏩ Compile el programa de prueba: no es necesario que participe en el kernel, solo compílelo directamente

arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputdApp

⏩ Copie los archivos del controlador y los archivos de prueba en el directorio rootfs/lib/modules/4.1.15 del sistema de archivos raíz. Antes de cargar el controlador, verifique los archivos en el directorio /dev/input

⏩ Una vez que el controlador se haya cargado correctamente, vuelva a verificar los archivos en el directorio /dev/input. El evento 2 recién agregado es el archivo del dispositivo correspondiente al controlador registrado.

depmod  #第一次加载驱动时,需使用“depmod”命令
modprobe keyinput.ko

⏩ La aplicación obtiene información del evento de entrada leyendo el archivo /dev/input/event2

⏩ Además, también puede usar el comando hexdump para probar directamente el controlador

Supongo que te gusta

Origin blog.csdn.net/Chuangke_Andy/article/details/125869073
Recomendado
Clasificación