Linux 驱动开发系列 —— LED 子系统框架全解析
一、前言
LED(Light Emitting Diode,发光二极管)作为嵌入式系统中最基础的外设之一,广泛应用于状态指示、调试、用户交互等场景。Linux 为此提供了专门的 LED 子系统,使得开发者可以以统一的方式操作不同平台、不同驱动的 LED 灯。
本文将从 LED 子系统的整体架构出发,深入分析其核心数据结构、工作机制、驱动开发流程以及设备树支持等内容,结合实际代码案例,帮助开发者全面掌握 LED 子系统的开发与调试方法。
二、LED 子系统概述
Linux 的 LED 子系统是通过 drivers/leds/
目录下的一系列驱动实现的,提供 /sys/class/leds/
接口供用户空间访问。它支持多种控制方式,如触发器(trigger)、亮度调节(brightness)、闪烁控制(blink)等。
主要组成部分包括:
- 核心框架代码
drivers/leds/led-core.c
- 各平台适配的 LED 驱动(如 gpio-led、pmic-led 等)
- 触发器机制实现
drivers/leds/trigger/
三、LED 子系统核心数据结构
LED 子系统以 struct led_classdev
为核心,描述一个 LED 设备。
struct led_classdev {
const char *name;
int brightness;
int max_brightness;
int (*brightness_set)(struct led_classdev *, enum led_brightness);
int (*brightness_get)(struct led_classdev *);
void (*blink_set)(struct led_classdev *, unsigned long *delay_on, unsigned long *delay_off);
// 触发器相关
const char *default_trigger;
struct device *dev;
struct list_head node;
// 省略其他字段
};
led_classdev
注册后会在 /sys/class/leds/
下生成对应目录,例如 led0
,其中包含:
- brightness:当前亮度值
- max_brightness:最大亮度
- trigger:可用触发器列表及当前选中项
四、LED 注册与注销流程
开发者通过调用如下两个接口完成注册和注销:
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev);
void led_classdev_unregister(struct led_classdev *led_cdev);
注册成功后,系统会为 LED 创建设备节点,用户可通过 echo
等方式控制 LED。
注册过程关键步骤包括:
- 创建 class 设备
device_create
- 创建 brightness、trigger 等属性文件
device_create_file
- 初始化默认 trigger
led_trigger_set_default
五、GPIO-LED 驱动详解
GPIO 控制的 LED 驱动是最常见的实现,驱动源码位于 drivers/leds/leds-gpio.c
。
其核心是 gpio_led
和 gpio_led_data
两个结构体:
struct gpio_led {
const char *name;
unsigned gpio;
enum led_brightness default_state;
const char *default_trigger;
bool retain_state_suspended;
};
struct gpio_led_data {
struct led_classdev cdev;
struct gpio_desc *gpiod;
// 其他字段
};
每个 gpio_led
描述一个 LED,通过 platform_data 或设备树传入。
六、设备树支持
设备树是 ARM 平台上主流的设备描述方式。GPIO LED 驱动支持通过设备树方式配置 LED。
设备树节点示例:
leds {
compatible = "gpio-leds";
led0 {
label = "status_led";
gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
default-state = "on";
linux,default-trigger = "heartbeat";
};
};
设备树中 gpios
定义引脚,default-state
定义默认状态,linux,default-trigger
定义触发器。
驱动通过 of_get_named_gpio()
或 gpiod_get_from_of_node()
解析节点,并注册为 led_classdev
。
七、LED Trigger 机制
Trigger 是 LED 子系统一大特色。其作用是为 LED 灯绑定系统事件(如定时器、硬盘活动、CPU 负载等),从而自动控制灯的亮灭。
典型 trigger 有:
timer
:周期闪烁heartbeat
:模仿心跳cpu0
:显示 CPU 活动状态
触发器注册接口:
void led_trigger_register(struct led_trigger *trigger);
void led_trigger_unregister(struct led_trigger *trigger);
驱动注册 LED 时,可设置 default_trigger
,触发器会绑定 LED,并控制亮度变化。
八、自定义 LED 驱动开发流程
- 定义
led_classdev
实例,指定brightness_set
实现 - 实现亮灭控制逻辑(如操作寄存器或 GPIO)
- 在
probe()
中注册 LED - 在
remove()
中注销 LED - 支持触发器可选
- 添加设备树支持(可选)
代码示例:
static void my_led_set(struct led_classdev *cdev, enum led_brightness brightness) {
gpio_set_value(LED_GPIO, brightness);
}
static int my_led_probe(struct platform_device *pdev) {
my_led_cdev.name = "my_led";
my_led_cdev.brightness_set = my_led_set;
my_led_cdev.max_brightness = 1;
return led_classdev_register(&pdev->dev, &my_led_cdev);
}
九、调试与测试方法
- 查看节点:
ls /sys/class/leds/
- 控制亮度:
echo 1 > /sys/class/leds/my_led/brightness
- 设置触发器:
echo heartbeat > /sys/class/leds/my_led/trigger
- 查看内核 log:
dmesg | grep led
- 使用
strace
跟踪系统调用
十、应用场景拓展
LED 子系统不仅限于状态指示,还可用于:
- 指示系统状态(电源、网络、故障)
- 工业控制设备状态反馈
- 自定义报警/信号系统
- 与输入设备联动提示
十一、未来演进方向
随着设备复杂性提升,LED 子系统也在逐步扩展,例如:
- 多色 LED 支持(RGB)
- PWM 调光集成
- 与 IIO、HWMON 等子系统协同
- 低功耗状态下保持状态
十二、结语
LED 子系统作为 Linux 设备模型中的一员,结构清晰、易于扩展、使用统一接口。在嵌入式开发中合理利用它,不仅能提升用户交互体验,也能加速开发效率。
通过本文的讲解,相信你已掌握 LED 子系统的核心结构、驱动实现及设备树适配方式,能自主编写定制化 LED 驱动,并灵活运用于实际项目中。