El método para agregar un controlador de botón al árbol de dispositivos del controlador en el sistema Linux

Hola a todos, un controlador simple todos los días. A medida que pasa el tiempo, me familiarizo cada vez más con los controladores de Linux y se vuelve más fácil aprender a escribir controladores. Hoy haré un sencillo accionamiento con botón.

1. Principio de control de botones en Linux

El controlador clave y el controlador LED son básicamente los mismos en principio: ambos operan GPIO, pero uno es para leer los niveles altos y bajos de GPIO, y el otro es para generar niveles altos y bajos de GPIO. Esta vez, para realizar la entrada del botón, se utiliza una variable entera en el controlador para representar el valor del botón, y la aplicación lee el valor del botón a través de la función de lectura para determinar si el botón está presionado o no.

Aquí, la variable que almacena el valor clave es un recurso compartido, el programa controlador debe escribir el valor clave en ella y el programa de aplicación debe leer el valor clave. Entonces necesitamos protegerlo. Para variables enteras, nuestra primera opción son las operaciones atómicas, usando operaciones atómicas para asignar y leer variables. El principio del controlador clave en Linux es muy simple y luego comienza a escribir el controlador.

Tenga en cuenta que las rutinas de este capítulo son sólo para demostrar la escritura de los controladores de entrada GPIO en Linux. Los controladores de botones reales no utilizarán el método explicado en este capítulo. ¡El subsistema de entrada en Linux se utiliza especialmente para dispositivos de entrada!

2. Análisis esquemático de hardware

imagen

Como se puede ver en la figura anterior, la clave KEY0 está conectada al UART1_CTS IO de I.MX6U, y KEY0 está conectada a una resistencia pull-up de 10K, por lo que UART1_CTS debe estar alto cuando no se presiona KEY0 y cuando KEY0 está presionado Después de esto, UART1_CTS es de nivel bajo.

3. Entorno de desarrollo

  • Procesador:IMX6ULL

  • Versión del kernel: Linux-5.19

4. Modifique el archivo del árbol de dispositivos.

1. Agregar nodo pinctrl

La CLAVE en la placa de desarrollo I.MX6U-ALPHA usa el PIN UART1_CTS_B, abre imx6ul-14x14-evk.dtsi y crea un subnodo llamado "pinctrl_key" debajo del subnodo imx6ul-evk del nodo iomuxc. El contenido del nodo es como sigue Mostrar:

pinctrl_key: keygrp {
    fsl,pins = <
        MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* key0 */
    >;
};

En la línea 3, el PIN de GPIO_IO18 se multiplexa como GPIO1_IO18.

2. Agregar nodo de dispositivo CLAVE

Cree un nodo CLAVE debajo del nodo raíz "/", el nombre del nodo es "clave" y el contenido del nodo es el siguiente:

key {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "imx6ull-key";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_key>;
    key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
    status = "okay";
};
  • En la línea 6, la propiedad pinctrl-0 establece el nodo pinctrl correspondiente al PIN utilizado por KEY.

  • En la línea 7, el atributo key-gpio especifica el GPIO utilizado por KEY.

3. Compruebe si el PIN es utilizado por otros periféricos.

El PIN utilizado por el botón en este experimento es UART1_CTS_B, así que primero verifique si el PIN es UART1_CTS_B y si este PIN es usado por otros nodos pinctrl. Si se usa, debe bloquearse y luego verifique si GPIO1_IO18 es usado por Otros periféricos, si los hubiera, también se deben bloquear.

Una vez escrito el árbol de dispositivos, utilice el comando "make dtbs" para recompilar el árbol de dispositivos y luego utilice el archivo imx6ull-toto.dtb recién compilado para iniciar el sistema Linux. Después de que el inicio sea exitoso, ingrese al directorio "/proc/device-tree" para verificar si el nodo "clave" existe. Si existe, significa que el árbol de dispositivos se ha modificado básicamente con éxito (específicamente, se requiere verificación del controlador) . Los resultados son los siguientes:

/ # ls /proc/device-tree/
#address-cells      clock-osc           pmu
#size-cells         compatible          regulator-can-3v3
aliases             cpus                regulator-peri-3v3
backlight-display   dts_led             regulator-sd1-vmmc
beep                key                 soc
chosen              memory@80000000     sound-wm8960
clock-cli           model               spi4
clock-di0           name                timer
clock-di1           panel

5. Programa de controlador de botón de escritura

Una vez que el árbol de dispositivos esté listo, puede escribir el controlador. Ingrese el siguiente contenido en key.c:

/************************************************************
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * Description: 
 * Version: 1.0
 * Autor: toto
 * Date: Do not edit
 * LastEditors: Seven
 * LastEditTime: Do not edit
************************************************************/

#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define KEY_CNT         1       /* 设备号数量 */
#define KEY_NAME        "key"   /* 设备名字 */

/*定义按键值*/
#define KEY0_VALUE      0xF0    /* 按键值 */
#define INVAKEY         0x00    /* 无效按键值 */

/* key 设备结构体 */
struct key_dev
{
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int key_gpio;           /* key 所使用的GPIO编号 */
    atomic_t keyvalue;      /* 按键值 */
};

struct key_dev keydev;      /* key 设备 */

/*
 * @Brief   初始化按键使用的GPIO管脚
 * @Call    Internal or External
 * @Param   
 * @Note    NOne
 * @RetVal  无
 */
int keyio_init(void)
{
    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL)
    {
        return -EINVAL;
    }

    keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);
    if(keydev.key_gpio < 0)
    {
        printk("can't get key-gpio\n");
        return -EINVAL;
    }
    printk("key_gpio:%d\n", keydev.key_gpio);

    /* c初始化 key 使用的IO */
    gpio_request(keydev.key_gpio, "key0");  /* 请求IO */
    gpio_direction_input(keydev.key_gpio);  /* 设置为输入 */

    return 0;
}

/*
 * @Brief   打开设备
 * @Call    Internal or External
 * @Param   inode:
 * @Param   filp:设备文件
 * @Note    NOne
 * @RetVal  0:成功 其他值:失败
 */
static int beep_open(struct inode *inode, struct file *filp)
{
    int ret = 0;

    /* 设置私有数据 */
    filp->private_data = &keydev;

    /* 初始化按键IO */
    ret = keyio_init();
    if(ret < 0)
    {
        return ret;
    }

    return 0;
}

/*
 * @Brief   从设备读数据
 * @Call    Internal or External
 * @Param   filp:要打开的设备文件描述符
 * @Param   buf:返回给用户空间的数据地址
 * @Param   cnt:要读取的数据长度
 * @Param   offt:相对于文件首地址的偏移
 * @Note    NOne
 * @RetVal  读取的字节数,若为负值,表示读失败
 */
static ssize_t beep_read(struct file *filp, char __user *buf,
                        size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char value;
    struct key_dev *dev = filp->private_data;

    /* key0 按下 */
    if(gpio_get_value(dev->key_gpio) == 0)
    {
        /* 等待按键释放 */
        while(!gpio_get_value(dev->key_gpio));
        atomic_set(&dev->keyvalue, KEY0_VALUE);
    }
    else
    {
        /* 无效的按键值 */
        atomic_set(&dev->keyvalue, INVAKEY);
    }

    /* 保存按键值 */
    value = atomic_read(&dev->keyvalue);
    ret = copy_to_user(buf, &value, sizeof(value));
    
    return ret;
}

/*
 * @Brief   写数据到设备
 * @Call    Internal or External
 * @Param   filp:要打开的设备文件描述符
 * @Param   buf:要写入设备的数据地址
 * @Param   cnt:要写入的数据长度
 * @Param   offt:相对于文件首地址的偏移
 * @Note    NOne
 * @RetVal  写入的字节数,若为负值,表示写失败
 */
static ssize_t beep_write(struct file *filp, const char __user *buf,
                        size_t cnt, loff_t *offt)
{
    return 0;
}


/*
 * @Brief   关闭/释放设备
 * @Call    Internal or External
 * @Param   inode:
 * @Param   filp:要关闭的设备文件描述符
 * @Note    NOne
 * @RetVal  NOne
 */
static int beep_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open  = beep_open,
    .read  = beep_read,
    .write = beep_write,
    .release = beep_release,
};

/*
 * @Brief   驱动入口函数
 * @Call    Internal or External
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int __init mykey_init(void)
{
    /* 初始化原子变量 */
    atomic_set(&keydev.keyvalue, INVAKEY);

    /* 注册字符设备驱动 */
    /* 创建设备号 */
    if(keydev.major) /* 定义了设备号 */
    {
        keydev.devid = MKDEV(keydev.major, 0);
        register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
    }
    else
    {
        /* 申请设备号 */
        alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);
        /* 获取主设备号 */
        keydev.major = MAJOR(keydev.devid);
        /* 获取次设备号 */
        keydev.minor = MINOR(keydev.devid);
    }
    printk("%s new_chrdev major:%d minor:%d\n", __func__,
            keydev.major, keydev.minor);

    /* 初始化cdev */
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);

    /* 添加一个cdev */
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);

    /* 创建类 */
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(keydev.class))
    {
        return PTR_ERR(keydev.class);
    }

    /* 创建设备 */
    keydev.device = device_create(keydev.class, NULL,
                        keydev.devid, NULL, KEY_NAME);
    if(IS_ERR(keydev.device))
    {
        return PTR_ERR(keydev.device);
    }

    return 0;
}

/*
 * @Brief   驱动出口函数
 * @Call    Internal or External
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static void __exit mykey_exit(void)
{
    /* 注销字符设备驱动 */
    gpio_free(keydev.key_gpio);
    /* 注销字符设备 */
    /* 删除cdev */
    cdev_del(&keydev.cdev);
    /* 释放分配的设备号 */
    unregister_chrdev_region(keydev.devid, KEY_CNT);

    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");

6. Escribe la aplicación de prueba.

El código específico del programa de prueba key_app.c es el siguiente:

/********************************************
 *Description: 
 *Version: 1.0
 *Autor: toto
 *Date: Do not edit
 *LastEditors: Seven
 *LastEditTime: Do not edit
********************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#define KEY0VALUE   0xF0
#define INVAKEY     0x00

/*
 * @Brief   main 主程序
 * @Call    Internal or External
 * @Param   argc:
 * @Param   argv:
 * @Note    NOne
 * @RetVal  0-成功;其他-失败
 */
int main(int argc, char *argv[])
{
    int fd, retval;
    char *filename;
    unsigned char keyvalue;

    if(argc != 2)
    {
        printf("argc != 2\n");
        return -1;
    }

    filename = argv[1];

    /*打开驱动文件*/
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("open filename:%d failed\n", filename);
        return -1;
    }

    /* 要执行的操作:打开或关闭 */
    while(1)
    {
        read(fd, &keyvalue, sizeof(keyvalue));
        if(keyvalue == KEY0VALUE)
        {
            /* 按下 */
            printf("KEY0 down, value:0x%x\n", keyvalue);
        }
    }

    /*关闭文件*/
    close(fd);

    return 0;
}

7. Verificación de operación

Encienda la placa de desarrollo, copie los dos archivos key.ko y key_app al directorio /lib/modules/5.19.0-g794a2f7be62d-dirty/ e ingrese el siguiente comando para cargar el módulo del controlador key.ko:

/ # insmod /lib/modules/5.19.0-g794a2f7be62d-dirty/key.ko 
[  108.608375] key_driver: loading out-of-tree module taints kernel.
[  108.619185] mykey_init new_chrdev major:242 minor:0

Una vez que el controlador se haya cargado correctamente, puede utilizar el siguiente comando para realizar la prueba:

./key_app /dev/key

Presione el botón KEY0 en la placa de desarrollo, key_app obtendrá y generará la información clave, como se muestra a continuación:

/home/app # ./key_app /dev/key 
[  155.043576] key_gpio:18
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0

Como se puede ver en lo anterior, cuando presionamos KEY0, imprimirá "KEY0 abajo, valor = 0XF0", lo que indica que la tecla está presionada. Descubrirá que, a veces, al presionar KEY0 una vez se generarán varias líneas de "KEY0 down, value = 0XF0", esto se debe a que nuestro código no realiza el procesamiento antirrebote de teclas. Si desea desinstalar el controlador, ingrese el siguiente comando:

rmmod key.ko

Supongo que te gusta

Origin blog.csdn.net/weixin_41114301/article/details/132776511
Recomendado
Clasificación