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
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