基于树莓派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 明确硬件的需求
- 用的是哪个 GPIO / I2C / SPI / 中断
- 高电平有效还是低电平有效
- 要不要供电、复位、时钟
- 硬件怎么工作(时序、协议)
这决定了我的驱动代码怎么写,实际上是描述硬件工作的逻辑。
0.2 选择驱动的模型
- 字符设备(LED、按键、简单外设)
- 平台设备(platform_driver)(最常用,配合设备树)
- 内核子系统(LED 子系统、输入子系统、I2C 子系统)
字符设备:最简单、最原始,适合入门、简单 GPIO、自定义接口
平台设备:标准、通用、配合设备树,企业90% 驱动都用它
内核子系统:内核已经写好框架,不用自己写字符设备、直接用
字符设备:自己注册主设备号、自己实现 file_operations、自己创建设备节点 /dev/xxx、自己管理 open/read/write
0.3 写设备树
- compatible(匹配名)
- gpios
- interrupts
- reg(I2C/SPI 地址)
- pinctrl(引脚配置)
- 电源、时钟、复位
0.4 搭建驱动框架
- 头文件
- of_device_id 匹配表(和 DTS 匹配)
- probe 函数(硬件初始化核心)
- remove 函数(卸载清理)
- file_operations(open/read/write/ioctl)
- 入口出口函数(module_init/exit)
- MODULE_LICENSE("GPL")
0.5 在 probe 里完成硬件初始化
- 从设备树获取 GPIO / 中断 / 地址
- 申请硬件资源
- 初始化硬件(供电、复位、配置模式)
- 注册字符设备 / 创建设备节点
- 注册中断(如果需要)
0.6 实现操作函数(open /read/write /ioctl)
- open:初始化
- read:读数据(传感器、按键)
- write:写控制(LED、继电器)
- ioctl:复杂控制(模式、参数)
0.7 写 Makefile + Kconfig(编译配置)
- Makefile:控制如何编译成 .ko
- Kconfig:让驱动出现在 make menuconfig
0.8 编译驱动(.ko 文件)
- 模块编译:生成 .ko,动态加载
- 内置编译:直接编译到内核,开机自动运行
0.9 加载驱动 + 调试
- 有没有匹配设备树
- probe 是否执行
- 有无内核崩溃(oops/panic)
- 硬件是否正常工作
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");