嵌入式Linux驱动开发(按键驱动)

按键驱动

  • [1 设备树文件](#1 设备树文件)
  • [2 驱动代码](#2 驱动代码)
  • [3 编译文件](#3 编译文件)
  • [4 应用层测试文件](#4 应用层测试文件)
  • [5 完整代码运行](#5 完整代码运行)
    • [5.1 编译驱动](#5.1 编译驱动)
    • [5.2 加载驱动模块](#5.2 加载驱动模块)
    • [5.3 查看驱动加载 & 设备树匹配日志](#5.3 查看驱动加载 & 设备树匹配日志)
    • [5.4 手动创建设备节点](#5.4 手动创建设备节点)
    • [5.5 运行测试程序](#5.5 运行测试程序)
    • [5.6 退出](#5.6 退出)

大部分的驱动,从输入上来看,需要写四个文件,分别是驱动源码、设备树文件、编译文件、上层应用测试文件,上期的LED驱动是一个字符设备驱动模型,存在一个问题,当切换不同的硬件的时候,需要重新编译驱动代码,而如果换用了设备树,只需要编译设备树即可,不需要编译驱动代码,驱动代码软件逻辑不变,只需要修改设备树上的硬件逻辑。

1 设备树文件

key_overlay.dts:

c 复制代码
/dts-v1/;
/plugin/;

fragment@0 {
    target-path = "/";
    __overlay__ {
        platform_key {
            compatible = "rpi,platform-key";
            gpios = <&gpio 27 GPIO_ACTIVE_LOW>;
            status = "okay";
        };
    };
};

2 驱动代码

代码key_platform.c:

c 复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/err.h>

/* 主设备号,自定义一个未被占用的号 */
#define KEY_MAJOR  243
#define KEY_DEV_NAME "rpi_plat_key"

/* 私有数据:保存当前设备的GPIO句柄 */
struct key_dev_data {
    struct gpio_desc *gpiod;
};

/* ==================== 文件操作接口 ==================== */
static int key_open(struct inode *inode, struct file *file)
{
    pr_info("Key dev opened\n");
    return 0;
}

static int key_release(struct inode *inode, struct file *file)
{
    pr_info("Key dev closed\n");
    return 0;
}

/* 应用 read 时调用:读取按键状态 */
static ssize_t key_read(struct file *file, char __user *buf,
                        size_t count, loff_t *ppos)
{
    struct platform_device *pdev = container_of(inode->i_cdev,
                                                struct platform_device, dev);
    struct key_dev_data *priv = platform_get_drvdata(pdev);
    char key_status;
    int gpio_level;

    // 读取GPIO电平
    gpio_level = gpiod_get_value(priv->gpiod);

    // 内部上拉:松开=高电平(1),按下=低电平(0)
    key_status = (gpio_level == 0) ? 1 : 0;

    // 内核数据 → 拷贝到用户空间
    if (copy_to_user(buf, &key_status, 1)) {
        return -EFAULT;
    }

    return 1;
}

/* 文件操作集合 */
static struct file_operations key_fops = {
    .owner   = THIS_MODULE,
    .open    = key_open,
    .read    = key_read,
    .release = key_release,
};

/* ==================== Platform 驱动回调 ==================== */
static int key_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct key_dev_data *priv;
    int ret;

    // 1. 分配私有内存,存放GPIO等设备信息
    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv) {
        dev_err(dev, "kzalloc failed\n");
        return -ENOMEM;
    }
    platform_set_drvdata(pdev, priv);

    // 2. 从设备树获取GPIO(输入模式)
    priv->gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN);
    if (IS_ERR(priv->gpiod)) {
        ret = PTR_ERR(priv->gpiod);
        dev_err(dev, "get gpio from dts failed\n");
        return ret;
    }

    // 3. 注册字符设备
    ret = register_chrdev(KEY_MAJOR, KEY_DEV_NAME, &key_fops);
    if (ret < 0) {
        dev_err(dev, "register chrdev failed\n");
        return ret;
    }

    dev_info(dev, "Platform Key driver probe success\n");
    return 0;
}

static int key_remove(struct platform_device *pdev)
{
    // 注销字符设备
    unregister_chrdev(KEY_MAJOR, KEY_DEV_NAME);
    dev_info(&pdev->dev, "Platform Key driver removed\n");
    return 0;
}

/* 设备树匹配表:必须和DTS里 compatible 字符串完全一致 */
static const struct of_device_id key_of_match[] = {
    { .compatible = "rpi,platform-key" },
    { /* 结束标记 */ }
};
MODULE_DEVICE_TABLE(of, key_of_match);

/* 定义platform驱动主体 */
static struct platform_driver key_platform_driver = {
    .probe  = key_probe,
    .remove = key_remove,
    .driver = {
        .name = "rpi-key-driver",
        .of_match_table = key_of_match,
    },
};

/* 注册/卸载platform驱动 */
module_platform_driver(key_platform_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Demo");
MODULE_DESCRIPTION("Raspberry Pi Platform Key Driver with Device Tree");

3 编译文件

文件名Makefile:

c 复制代码
obj-m += key_platform.o

KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	make -C $(KERNELDIR) M=$(PWD) modules

clean:
	make -C $(KERNELDIR) M=$(PWD) clean

4 应用层测试文件

test_key.c:

c 复制代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
    int fd;
    char state;

    fd = open("/dev/key", O_RDONLY);
    if (fd < 0) {
        perror("open /dev/key failed");
        return -1;
    }

    while (1) {
        read(fd, &state, 1);
        if (state == 1) {
            printf("==== 按键按下 ====\n");
        } else {
            printf("按键松开\n");
        }
        usleep(200000);  // 200ms 简单防抖
    }

    close(fd);
    return 0;
}

5 完整代码运行

5.1 编译驱动

编译驱动生成 key_platform.ko

c 复制代码
make

5.2 加载驱动模块

c 复制代码
sudo insmod key_platform.ko

5.3 查看驱动加载 & 设备树匹配日志

c 复制代码
dmesg | grep -i key

5.4 手动创建设备节点

c 复制代码
sudo mknod /dev/key c 243 0

5.5 运行测试程序

c 复制代码
sudo ./test_key

5.6 退出

终止测试程序 Ctrl+C

c 复制代码
sudo rmmod key_platform
sudo rm /dev/key
相关推荐
HLC++9 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室10 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy13 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕13 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫15 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生15 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1116 小时前
linux基础IO
linux·运维·服务器
BelongPanda18 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯18 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983419 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器