嵌入式Linux驱动开发(基于树莓派rasberrypi 5的LED驱动开发)

基于树莓派rasberrypi 5的LED驱动开发

  • LED驱动代码详细解读
  • 0.驱动开发的操作流程
    • [0.1 明确硬件的需求](#0.1 明确硬件的需求)
    • [0.2 选择驱动的模型](#0.2 选择驱动的模型)
    • [0.3 写设备树](#0.3 写设备树)
    • [0.4 搭建驱动框架](#0.4 搭建驱动框架)
    • [0.5 在 probe 里完成硬件初始化](#0.5 在 probe 里完成硬件初始化)
    • [0.6 实现操作函数(open /read/write /ioctl)](#0.6 实现操作函数(open /read/write /ioctl))
    • [0.7 写 Makefile + Kconfig(编译配置)](#0.7 写 Makefile + Kconfig(编译配置))
    • [0.8 编译驱动(.ko 文件)](#0.8 编译驱动(.ko 文件))
    • [0.9 加载驱动 + 调试](#0.9 加载驱动 + 调试)
    • [1.0 写应用程序测试(APP)](#1.0 写应用程序测试(APP))
  • [1 头文件代码解读](#1 头文件代码解读)
    • [1.1 内核驱动模块](#1.1 内核驱动模块)
    • [1.2 文件系统](#1.2 文件系统)
    • [1.3 内核与应用层数据交换](#1.3 内核与应用层数据交换)
    • [1.4 内核中GPIO的接口API提供](#1.4 内核中GPIO的接口API提供)
  • [2 宏定义](#2 宏定义)
  • [3 驱动接口实现](#3 驱动接口实现)
  • [4 内核模块的加载](#4 内核模块的加载)
  • [5 完整代码](#5 完整代码)

LED驱动代码详细解读

嵌入式Linux驱动内容的主要的工作内容是拿到一块硬件板子,首先下载一个开源驱动,对开源驱动进行移植,能够让其在硬件板子上执行,接着就是调试代码,对其中不必要的功能进行裁剪,对自己产品的功能进行新增,最后整体调试。常见的驱动有:LED 驱动、按键驱动、PWM 驱动、I2C 传感器驱动、SPI 屏幕驱动、驱动移植、BSP 开发,核心就是调试驱动代码,裁剪内核,增加新的功能,事实上还是接触软件多。

0.驱动开发的操作流程

0.1 明确硬件的需求

  1. 用的是哪个 GPIO / I2C / SPI / 中断
  2. 高电平有效还是低电平有效
  3. 要不要供电、复位、时钟
  4. 硬件怎么工作(时序、协议)
    这决定了我的驱动代码怎么写,实际上是描述硬件工作的逻辑。

0.2 选择驱动的模型

  1. 字符设备(LED、按键、简单外设)
  2. 平台设备(platform_driver)(最常用,配合设备树)
  3. 内核子系统(LED 子系统、输入子系统、I2C 子系统)

字符设备:最简单、最原始,适合入门、简单 GPIO、自定义接口

平台设备:标准、通用、配合设备树,企业90% 驱动都用它

内核子系统:内核已经写好框架,不用自己写字符设备、直接用

字符设备:自己注册主设备号、自己实现 file_operations、自己创建设备节点 /dev/xxx、自己管理 open/read/write

0.3 写设备树

  1. compatible(匹配名)
  2. gpios
  3. interrupts
  4. reg(I2C/SPI 地址)
  5. pinctrl(引脚配置)
  6. 电源、时钟、复位

0.4 搭建驱动框架

  1. 头文件
  2. of_device_id 匹配表(和 DTS 匹配)
  3. probe 函数(硬件初始化核心)
  4. remove 函数(卸载清理)
  5. file_operations(open/read/write/ioctl)
  6. 入口出口函数(module_init/exit)
  7. MODULE_LICENSE("GPL")

0.5 在 probe 里完成硬件初始化

  1. 从设备树获取 GPIO / 中断 / 地址
  2. 申请硬件资源
  3. 初始化硬件(供电、复位、配置模式)
  4. 注册字符设备 / 创建设备节点
  5. 注册中断(如果需要)

0.6 实现操作函数(open /read/write /ioctl)

  1. open:初始化
  2. read:读数据(传感器、按键)
  3. write:写控制(LED、继电器)
  4. ioctl:复杂控制(模式、参数)

0.7 写 Makefile + Kconfig(编译配置)

  1. Makefile:控制如何编译成 .ko
  2. Kconfig:让驱动出现在 make menuconfig

0.8 编译驱动(.ko 文件)

  1. 模块编译:生成 .ko,动态加载
  2. 内置编译:直接编译到内核,开机自动运行

0.9 加载驱动 + 调试

  1. 有没有匹配设备树
  2. probe 是否执行
  3. 有无内核崩溃(oops/panic)
  4. 硬件是否正常工作

1.0 写应用程序测试(APP)

应用层通过 /dev/xxx 节点控制驱动:

  • open
  • write
  • read
  • close
    测试功能是否正常:亮灭、数据、中断、稳定性。

1 头文件代码解读

1.1 内核驱动模块

c 复制代码
#include <linux/module.h>

#include命令是把某个文件下的代码复制到当前的代码空间,这里就是把linux内核下的linux文件夹下的module.h的文件的代码复制到当前的代码里面。这是因为在这个内核模块module.h文件中定义了驱动的入口和驱动出口以及一些开源协议、作者、描述:

c 复制代码
module_init(xxx);   // 驱动入口
module_exit(xxx);   //驱动出口
MODULE_LICENSE("GPL"); // 开源协议(必须写)
MODULE_AUTHOR("xx");   // 作者
MODULE_DESCRIPTION("");// 描述

1.2 文件系统

c 复制代码
#include <linux/fs.h>

同理#include命令是把某个文件下的代码复制到当前的代码空间,这里就是把linux内核下的linux文件夹下的fs.h的文件的代码复制到当前的代码里面。这个头文件主要定义了文件系统,主要是应用层与驱动之间的交互,通过文件系统提供字符设备驱动核心功能,让你能注册 /dev/xxx 设备节点(应用层),提供 open/read/write 对应的内核接口(驱动层):

c 复制代码
struct file_operations;  // 驱动灵魂结构体
register_chrdev();       // 注册字符设备
unregister_chrdev();     // 注销字符设备

1.3 内核与应用层数据交换

c 复制代码
#include <linux/uaccess.h>

应用层与内核层之间不能直接进行内存访问,必须通过函数之间的复制

c 复制代码
copy_from_user();  // 应用 → 内核(写数据)
copy_to_user();    // 内核 → 应用(读数据)

1.4 内核中GPIO的接口API提供

在内核中提供对硬件的操作的GPIO接口

c 复制代码
gpio_request();         // 申请GPIO
gpio_free();            // 释放GPIO
gpio_direction_output();// 设置为输出
gpio_set_value();       // 写 1/0 控制亮灭

以上四个头文件我们可以得到,内核与硬件层以及应用层以及驱动层之间相互不直接联系,必须增加头文件来间接建立他们的联系。

2 宏定义

宏定义驱动名称、主设备号、连接的引脚。

c 复制代码
// ==================== 宏定义 ====================
#define DEVICE_NAME   "led_driver"    // 驱动名称
#define MAJOR_NUM     240             // 主设备号
#define LED_GPIO_PIN  17              // LED 接在 BCM GPIO17

3 驱动接口实现

实现从应用层到底层硬件的驱动接口,通过gpio.h文件中的GPIO的API建立硬件与驱动之间的接口,通过文件系统建立驱动与应用层之间的接口,两者贯穿,最终建立了从硬件到上层应用的接口。

c 复制代码
// ==================== 驱动接口实现 ====================

// 应用 open("/dev/led") 时调用
static int led_open(struct inode *inode, struct file *file)
{
    printk("LED Driver Opened\n");
    return 0;
}

// 应用 close 时调用
static int led_release(struct inode *inode, struct file *file)
{
    printk("LED Driver Closed\n");
    return 0;
}

// 应用 write 时调用(核心:控制亮灭)
static ssize_t led_write(struct file *file, const char __user *buf,
                         size_t size, loff_t *ppos)
{
    unsigned char value;

    // 从用户空间读取 1 字节数据(0 或 1)
    if (copy_from_user(&value, buf, 1)) {
        return -EFAULT;
    }

    // 根据值控制 GPIO 电平
    if (value == 1) {
        gpio_set_value(LED_GPIO_PIN, 1);  // 亮
    } else {
        gpio_set_value(LED_GPIO_PIN, 0);  // 灭
    }

    return size;
}
// ==================== 文件操作集合 ====================
static struct file_operations fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .write   = led_write,
    .release = led_release,
};

4 内核模块的加载

在这里写内核中驱动模块的加载与退出,以及相关的协议

c 复制代码
// ==================== 驱动入口(加载时执行) ====================
static int __init led_init(void)
{
    int ret;

    // 1. 注册字符设备
    ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);
    if (ret < 0) {
        printk("register_chrdev failed\n");
        return ret;
    }

    // 2. 申请 GPIO 资源
    ret = gpio_request(LED_GPIO_PIN, "LED_PIN");
    if (ret < 0) {
        printk("gpio_request failed\n");
        unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
        return ret;
    }

    // 3. 设置为输出模式,默认低电平(灭)
    gpio_direction_output(LED_GPIO_PIN, 0);

    printk("LED Driver Initialized Successfully\n");
    return 0;
}

// ==================== 驱动出口(卸载时执行) ====================
static void __exit led_exit(void)
{
    // 灭灯
    gpio_set_value(LED_GPIO_PIN, 0);

    // 释放 GPIO
    gpio_free(LED_GPIO_PIN);

    // 注销字符设备
    unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

    printk("LED Driver Exited\n");
}

// 注册入口/出口
module_init(led_init);
module_exit(led_exit);

// 必须加的协议与描述
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Linux");
MODULE_DESCRIPTION("Standard LED Character Device Driver");

5 完整代码

c 复制代码
# 1.确定硬件的工作逻辑(主要看CPU和挂载的硬件) - 使用GPIO,GPIO输出高电平亮,输出低电平灭,连接在引脚17上
# 2.选择驱动模型(字符模型、平台模型、内核子系统) 这边先不用设备树,直接进行字符模型
#include <linux/module.h>    // 内核模块核心头文件,提供了
#include <linux/fs.h>        // 文件操作:register_chrdev,提供了
#include <linux/uaccess.h>   // 用户<->内核数据拷贝:copy_from_user,提供了
#include <linux/gpio.h>      // GPIO 操作 API,提供了

// ==================== 宏定义 ====================
#define DEVICE_NAME   "led_driver"    // 驱动名称
#define MAJOR_NUM     240             // 主设备号
#define LED_GPIO_PIN  17              // LED 接在 BCM GPIO17

// ==================== 驱动接口实现 ====================

// 应用 open("/dev/led") 时调用
static int led_open(struct inode *inode, struct file *file)
{
    printk("LED Driver Opened\n");
    return 0;
}

// 应用 close 时调用
static int led_release(struct inode *inode, struct file *file)
{
    printk("LED Driver Closed\n");
    return 0;
}

// 应用 write 时调用(核心:控制亮灭)
static ssize_t led_write(struct file *file, const char __user *buf,
                         size_t size, loff_t *ppos)
{
    unsigned char value;

    // 从用户空间读取 1 字节数据(0 或 1)
    if (copy_from_user(&value, buf, 1)) {
        return -EFAULT;
    }

    // 根据值控制 GPIO 电平
    if (value == 1) {
        gpio_set_value(LED_GPIO_PIN, 1);  // 亮
    } else {
        gpio_set_value(LED_GPIO_PIN, 0);  // 灭
    }

    return size;
}

// ==================== 文件操作集合 ====================
static struct file_operations fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .write   = led_write,
    .release = led_release,
};

// ==================== 驱动入口(加载时执行) ====================
static int __init led_init(void)
{
    int ret;

    // 1. 注册字符设备
    ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);
    if (ret < 0) {
        printk("register_chrdev failed\n");
        return ret;
    }

    // 2. 申请 GPIO 资源
    ret = gpio_request(LED_GPIO_PIN, "LED_PIN");
    if (ret < 0) {
        printk("gpio_request failed\n");
        unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
        return ret;
    }

    // 3. 设置为输出模式,默认低电平(灭)
    gpio_direction_output(LED_GPIO_PIN, 0);

    printk("LED Driver Initialized Successfully\n");
    return 0;
}

// ==================== 驱动出口(卸载时执行) ====================
static void __exit led_exit(void)
{
    // 灭灯
    gpio_set_value(LED_GPIO_PIN, 0);

    // 释放 GPIO
    gpio_free(LED_GPIO_PIN);

    // 注销字符设备
    unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

    printk("LED Driver Exited\n");
}

// 注册入口/出口
module_init(led_init);
module_exit(led_exit);

// 必须加的协议与描述
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Linux");
MODULE_DESCRIPTION("Standard LED Character Device Driver");
相关推荐
CodeStats1 小时前
从 CPU 指令执行到权限管控:对比三大操作系统,梳理编程语言演进,解读 HTML/CSS/JS 浏览器解析的共通底层逻辑
java·linux·windows
2601_961194021 小时前
考研专业课在哪里参加考试|考点|流程|资料已整理
linux·考研·ubuntu·缓存·centos·负载均衡
上海云盾安全满满2 小时前
服务器CPU跑满的原因及解决办法
运维·服务器
tobias.b2 小时前
供电不稳定、无UPS/无双电源环境下服务器高可用完整方案
运维·服务器
团象科技2 小时前
从出海业务落地视角观察 海外服务器跑开源软件的实操逻辑演变
运维·服务器·开源软件
Wireless_wifi62 小时前
Why IPQ5018 Continues to Thrive in the Wi-Fi 7 Era
linux·5g
加成BUFF2 小时前
第七天 ROS《 参数服务器与Launch文件》
运维·ros·参数服务器
snow@li2 小时前
CI/CD:深入理解 CI/CD(2026版)
运维·ci/cd
java_cj2 小时前
K8s入门第一课:从零理解Kubernetes核心概念与架构设计
运维·云原生·容器·架构·kubernetes