驱动开发硬核特训 · Day 19:字符设备驱动实战(控制 LED)

视频教程请关注 B 站:"嵌入式Jerry"

一、背景引入:字符设备 + 实际硬件控制

在 Linux 驱动开发中,字符设备(Character Device)是一类通过 read / write 接口与用户空间进行数据交互的基本设备类型。今天我们将结合 i.MX8MP EVK 开发板的 gpio-leds 节点,构造一个简单、直观的字符设备驱动,实现对物理 LED 的开关控制。

这一过程不仅涉及字符设备驱动的基础流程,还能帮助我们理解设备树与实际硬件的结合方式。


二、设备树分析:找到目标 GPIO

开发板设备树 arch/arm64/boot/dts/freescale/imx8mp-evk.dts 中,预定义了一个 GPIO 控制的 LED 节点:

dts 复制代码
gpio-leds {
    compatible = "gpio-leds";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_gpio_led>;

    status {
        label = "yellow:status";
        gpios = <&gpio3 16 GPIO_ACTIVE_HIGH>;
        default-state = "on";
    };
};

这段节点中定义了一个黄色 LED,使用 gpio3_16 进行控制。GPIO 的编号计算方式为:

复制代码
GPIO 编号 = bank × 32 + offset = 3 × 32 + 16 = 112

我们将在驱动中直接控制该 GPIO(编号 112)进行点亮和熄灭。


三、字符设备驱动实现

我们不再依赖 gpio-leds 子系统,而是直接注册一个字符设备,通过 echo 1 > /dev/ledchar 控制物理 LED 点亮,echo 0 熄灭。

3.1 驱动代码

c 复制代码
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define GPIO_LED_NUM 112  // gpio3_16 -> 3*32 + 16

static dev_t devt;
static struct cdev cdev;
static struct class *led_class;

static ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
    char kbuf[4];
    if (copy_from_user(kbuf, buf, len)) // 从用户空间拷贝数据到内核
        return -EFAULT;

    gpio_set_value(GPIO_LED_NUM, kbuf[0] == '1' ? 1 : 0); // 设置 GPIO 电平
    return len;
}

static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .write = led_write,  // 用户空间 write 映射此函数
};

static int __init led_dev_init(void)
{
    gpio_request(GPIO_LED_NUM, "led_gpio");             // 请求 GPIO 控制权
    gpio_direction_output(GPIO_LED_NUM, 1);             // 设置为输出方向,初始高电平

    alloc_chrdev_region(&devt, 0, 1, "led_char");        // 分配设备号
    cdev_init(&cdev, &led_fops);                         // 初始化字符设备结构
    cdev_add(&cdev, devt, 1);                            // 注册字符设备

    led_class = class_create(THIS_MODULE, "led_class");  // 创建 sysfs 类
    device_create(led_class, NULL, devt, NULL, "ledchar"); // 创建设备节点 /dev/ledchar

    pr_info("ledchar device init done\n");
    return 0;
}

static void __exit led_dev_exit(void)
{
    gpio_set_value(GPIO_LED_NUM, 0);                     // 卸载前熄灭 LED
    gpio_free(GPIO_LED_NUM);                             // 释放 GPIO

    device_destroy(led_class, devt);
    class_destroy(led_class);
    cdev_del(&cdev);
    unregister_chrdev_region(devt, 1);
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("嵌入式Jerry");

3.2 编译说明

使用 Makefile 编译为模块:

makefile 复制代码
obj-m += ledchar.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

加载模块:

bash 复制代码
insmod ledchar.ko

控制 LED:

bash 复制代码
echo 1 > /dev/ledchar   # 点亮 LED
echo 0 > /dev/ledchar   # 熄灭 LED

四、字符设备和设备树的关联解读

❗ 重点:我们没有直接解析设备树节点 ,而是"硬编码"了 GPIO 号 112

这是一种"直接控制硬件资源"的方式,适用于教学或特定场景,但不具备移植性和灵活性。在生产环境中,我们推荐:

  • 使用 of_find_node_by_name 等接口动态获取设备树中的 gpio
  • 或者用 platform_driver 的形式自动注册字符设备,配合 of_device_id 匹配设备树。

五、字符设备与设备模型的区别与融合

项目 字符设备 设备模型
本质 文件操作接口 系统框架结构
接口表现 file_operations (read/write 等) device、driver、bus 等结构体
功能侧重 面向用户空间的数据操作 面向系统资源管理与驱动匹配
是否依赖 可独立存在 统一纳入 driver model 框架更佳

字符设备是数据访问的"通道",而设备模型是驱动统一管理的"系统总线"。两者并不冲突,在现代驱动开发中,常常将字符设备纳入设备模型管理之中。


六、实战拓展建议

  • 下一步建议学习内容

    • 如何通过 platform_driver 注册字符设备;
    • 如何用 device_create_file() 添加 sysfs 接口;
    • 将 GPIO 控制"封装成子系统"接口(如 LED 子系统);
  • 推荐实战目标

    • 为一个温度传感器或输入按键编写字符驱动;
    • 理解 udev 如何自动创建设备节点;
    • 进一步结合设备树进行设备节点注册。

七、总结

本篇训练以字符设备驱动为核心,结合 i.MX8MP EVK 实际 GPIO,完成从驱动编写、设备注册、到用户空间控制的完整流程。关键点如下:

  • 使用 alloc_chrdev_region / cdev_add 完成字符设备注册;
  • 通过 gpio_request / gpio_set_value 操作 GPIO;
  • 构建了 /dev/ledchar 控制节点,实现最基础的内核设备控制通道。

接下来我们将深入 platform_driver 与 sysfs 接口,逐步构建更完整的驱动结构。


📢 视频教程请关注 B 站:"嵌入式Jerry"

📚 本系列每日更新,持续训练,打造 Linux 驱动开发硬核能力!

相关推荐
程序猿(雷霆之王)5 小时前
Linux——进程间通信
linux·运维·服务器
riveting6 小时前
SD2351核心板:重构AI视觉产业价值链的“超级节点”
大数据·linux·图像处理·人工智能·重构·智能硬件
易保山7 小时前
MIT6.S081 - Lab10 mmap(文件&内存映射)
linux·操作系统·c
禅与Bug的修复艺术7 小时前
JAVA后端开发常用的LINUX命令总结
java·linux·面试·java面试·后端开发·java后端·面试经验
北冥有鱼被烹8 小时前
【微知】/proc中如何查看Linux内核是否允许加载内核模块?(/proc/sys/kernel/modules_disabled)
linux·服务器
qq_273900239 小时前
CentOS系统防火墙服务介绍
linux·运维·centos
小余吃大鱼9 小时前
CentOS中在线安装Docker(超详细)
linux·docker·centos
小灰灰__9 小时前
Linux安装ffmpeg7.1操作说明
linux·运维·服务器
Zz_waiting.9 小时前
网络原理 - 9
linux·服务器·网络·网络协议·tcp/ip