GPIO控制状态灯—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
键会认了。今天看四颗用户灯为什么会一起灭——写在同一个节点里,有一个脚申请失败,整组都不会出现。
ls /sys/class/leds/ 只有 work,没有 user-led0、user-led1、user-led2、user-led3。四颗用户灯全灭。work 还在心跳,说明 leds-gpio 这个驱动本身没坏,内核也没把 LED 子系统编掉。
四颗用户灯写在同一个 gpio-leds 节点里。驱动遍历子节点,对每一颗 devm_fwnode_get_gpiod_from_child()。任意一颗申请 GPIO 失败,整个节点 probe 返回错误,已经登记的也会跟着没了。于是你看见的是「四颗一起没有」,不是「坏了两颗」。
失败的那两颗脚,被公版 dtsi 里两个 regulator 先占了:
vcc3v3_vga用 GPIO0_D5 当使能,正是 user-led2pcie30_3v3用 GPIO0_D4 当使能,正是 user-led1
这块板没有 VGA 座,PCIe 3.3 V 已经走另一路固定电源。两个节点在 dtb 里没有真正的消费者,却把脚占死。leds-gpio 对 D4/D5 拿到 -EBUSY,整节点退出。
work 是公版另一个 gpio-leds 节点,脚是 GPIO0_C0,独占,所以还在闪。不要用 work 亮着证明用户灯驱动通了,也不要把 work 当成 user-led0。
产品 rk3568_evb:
device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts



官方这张图:申请、配置方向、写电平、释放。gpio-leds 在内核里替你做了前三步,用户态只写 brightness。脚被别人申请走了,这张图在 probe 第一格就停。
【截图 26-leds-on】 四颗 user-led 点亮,旁边 work 心跳,能分清哪四颗是用户灯。 文件:
assets/shots/26-leds-on.jpg
1. 四颗灯的脚,高电平亮
| sysfs 名 | 脚 | linux 编号 | 默认 trigger |
|---|---|---|---|
| user-led0 | GPIO0_D3 | 27 | heartbeat(可改 none) |
| user-led1 | GPIO0_D4 | 28 | none |
| user-led2 | GPIO0_D5 | 29 | none |
| user-led3 | GPIO0_D6 | 30 | none |
| work | GPIO0_C0 | 16 | heartbeat |
编号 = bank × 32 + 组内偏移 + 位号
D=24
GPIO0_D3 = 0×32 + 24 + 3 = 27
GPIO0_D4 = 28
GPIO0_D5 = 29
GPIO0_D6 = 30
GPIO0_C0 = 0×32 + 16 + 0 = 16
硬件是高电平亮。设备树必须 GPIO_ACTIVE_HIGH。写成 ACTIVE_LOW 时,brightness=1 灯灭、0 灯亮,看起来像极性反了。说明书写高电平,就按高电平。
不要和扩展座上的「流水灯」模块混。那是移位寄存器或一串 GPIO3,走电机那组脚,应用里另一张卡片。板载四颗在核心板/底板丝印 D3~D6 附近,sysfs 名带 user-led。
2. 驱动为什么整节点失败
drivers/leds/leds-gpio.c 里创建子灯:
led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
GPIOD_ASIS,
NULL);
if (IS_ERR(led.gpiod)) {
fwnode_handle_put(child);
return ERR_CAST(led.gpiod);
}
没有「跳过失败的那颗、继续登记其它」。D4 被 regulator-gpio 占用,这里返回 -EBUSY,gpio_led_create_of 失败,gpio_led_probe 失败。dmesg 类似:
leds-gpio: probe of leds failed with error -16
或者节点名是你板级起的 gpio-leds / board-leds:
leds-gpio: probe of board-leds failed with error -16
-16 是 -EBUSY。-22 是参数。没有这行、也没有 user-led:节点没编译进 dtb,或 compatible 不是 gpio-leds。
work 能亮,只说明另一份 gpio-leds 节点成功了。两份节点互不影响。
公版占用长这样(rk3568-evb 公版 dtsi,不要在板级再抄一份 gpio):
vcc3v3_vga: vcc3v3-vga {
compatible = "regulator-fixed";
regulator-name = "vcc3v3_vga";
regulator-always-on;
gpio = <&gpio0 RK_PD5 GPIO_ACTIVE_HIGH>;
enable-active-high;
};
pcie30_3v3: gpio-regulator {
compatible = "regulator-gpio";
regulator-name = "pcie30_3v3";
gpios = <&gpio0 RK_PD4 GPIO_ACTIVE_HIGH>;
states = <100000 0x0
3300000 0x1>;
};
本板无 VGA。PCIe 供电板级改成了不占 GPIO 的 fixed regulator。这两个节点在本板 dtb 里搜不到消费者,纯占脚。
3. 设备树:用户灯节点 + 关掉两个 regulator
board_leds: gpio-leds {
compatible = "gpio-leds";
led0 {
label = "user-led0";
gpios = <&gpio0 RK_PD3 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "none";
};
led1 {
label = "user-led1";
gpios = <&gpio0 RK_PD4 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "none";
};
led2 {
label = "user-led2";
gpios = <&gpio0 RK_PD5 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "none";
};
led3 {
label = "user-led3";
gpios = <&gpio0 RK_PD6 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "none";
};
};
&vcc3v3_vga {
status = "disabled";
};
&pcie30_3v3 {
status = "disabled";
};
user-led0 如果默认 heartbeat,开机就会闪,演示「点灯」时先:
echo none > /sys/class/leds/user-led0/trigger
否则你写 brightness 会被 trigger 抢回去。
内核:
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_TRIGGERS=y
改 DTS 之后刷 resource + boot_linux。只刷 p5 不够。
hdc shell "cat /proc/partitions"
hdc shell "dd if=/data/resource.img of=/dev/block/mmcblk1p4 bs=4M conv=fsync"
hdc shell "dd if=/data/boot_linux.img of=/dev/block/mmcblk1p5 bs=4M conv=fsync"
4. 加节点之前,先解包查谁占脚
dtc -I dtb -O dts -o /tmp/live.dts /sys/firmware/fdt
grep -n "RK_PD4\|RK_PD5\|gpio0 28\|gpio0 29\|PD4\|PD5" /tmp/live.dts
或者在编译机对 resource 里的 rk-kernel.dtb 解包。搜到 vcc3v3_vga / pcie30_3v3 仍 okay,就是还没 disable。
运行时:
hdc shell "ls /sys/class/leds/"
hdc shell "cat /sys/kernel/debug/gpio" | grep -E "gpio-(16|27|28|29|30)"
修好之后期望:
user-led0
user-led1
user-led2
user-led3
work
debugfs 里 27~30 的 consumer 是 user-ledN 或 leds。16 是 work。若 28 仍写着 pcie30_3v3、29 写着 vcc3v3_vga,disable 没进生效 DTB。
点灯:
hdc shell "echo none > /sys/class/leds/user-led0/trigger"
hdc shell "echo 1 > /sys/class/leds/user-led0/brightness"
hdc shell "echo 1 > /sys/class/leds/user-led1/brightness"
hdc shell "echo 1 > /sys/class/leds/user-led2/brightness"
hdc shell "echo 1 > /sys/class/leds/user-led3/brightness"
brightness 取值 0 / 1(GPIO 灯没有硬件 PWM 调光,写 255 也是亮)。max_brightness 读出来通常是 1。
循环:
hdc shell 'for n in 0 1 2 3; do
echo 1 > /sys/class/leds/user-led$n/brightness
sleep 0.2
echo 0 > /sys/class/leds/user-led$n/brightness
done'
四颗会依次亮。只有 user-led0 会、1/2 不会:D4/D5 仍被占,但那会整节点失败,按理 0 也不会出现。若出现「只有两颗」,你把四颗拆成了两个 gpio-leds 节点,那是另一份 dts,按节点各自看 probe。
5. 完整 C
/* leds_walk.c
* armv7-unknown-linux-ohos-clang --target=arm-linux-ohos \
* --sysroot=${OHOS_NATIVE}/sysroot \
* -o leds_walk leds_walk.c \
* -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1
*/
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
static int wr(const char *path, const char *s)
{
int fd = open(path, O_WRONLY);
if (fd < 0) {
fprintf(stderr, "open %s: %s\n", path, strerror(errno));
return -1;
}
int n = (int)write(fd, s, strlen(s));
close(fd);
return n < 0 ? -1 : 0;
}
static int led_set(const char *name, int on)
{
char p[128];
snprintf(p, sizeof(p), "/sys/class/leds/%s/trigger", name);
wr(p, "none");
snprintf(p, sizeof(p), "/sys/class/leds/%s/brightness", name);
return wr(p, on ? "1" : "0");
}
int main(void)
{
DIR *d = opendir("/sys/class/leds");
struct dirent *e;
int found = 0;
if (!d) {
perror("leds");
return 1;
}
printf("leds:");
while ((e = readdir(d)) != NULL) {
if (e->d_name[0] == '.') {
continue;
}
printf(" %s", e->d_name);
if (strncmp(e->d_name, "user-led", 8) == 0) {
found++;
}
}
printf("\n");
closedir(d);
if (found == 0) {
fprintf(stderr,
"没有 user-led*。work 在不算数。\n"
"查 vcc3v3_vga / pcie30_3v3 是否还 okay,"
"dmesg 搜 leds-gpio probe failed\n");
return 2;
}
if (found != 4) {
fprintf(stderr, "user-led 只有 %d 颗,期望 4。\n", found);
}
int i, k;
for (k = 0; k < 3; k++) {
for (i = 0; i < 4; i++) {
char name[32];
snprintf(name, sizeof(name), "user-led%d", i);
led_set(name, 1);
usleep(150000);
led_set(name, 0);
}
}
return 0;
}
HAP 写 sysfs 需要 chmod。开机脚本:
for n in 0 1 2 3; do
chmod 0666 /sys/class/leds/user-led$n/brightness
chmod 0666 /sys/class/leds/user-led$n/trigger
done
不要 chmod work 的 brightness 给应用乱写,心跳没了你会以为内核死了。
6. ArkTS
import evbio from 'libevbio.so';
@Entry
@Component
struct UserLeds {
@State on: boolean[] = [false, false, false, false];
@State log: string = '';
build() {
Column() {
Text('user-led0..3').fontSize(22).fontWeight(FontWeight.Bold)
Text('不是 work,也不是扩展座流水灯').fontSize(14).margin({ bottom: 12 })
Row() {
ForEach([0, 1, 2, 3], (i: number) => {
Button('LED' + i.toString())
.backgroundColor(this.on[i] ? '#C62828' : '#9E9E9E')
.onClick(() => { this.toggle(i); })
.margin(6)
})
}
Button('跑马').onClick(() => { this.walk(); }).margin({ top: 12 })
Text(this.log).margin({ top: 8 }).fontSize(14)
}.padding(20)
}
path(i: number, leaf: string): string {
return '/sys/class/leds/user-led' + i.toString() + '/' + leaf;
}
toggle(i: number) {
evbio.writeText(this.path(i, 'trigger'), 'none');
this.on[i] = !this.on[i];
let r: string = evbio.writeText(this.path(i, 'brightness'),
this.on[i] ? '1' : '0');
this.log = 'led' + i.toString() + ' -> ' + r;
}
walk() {
for (let i = 0; i < 4; i++) {
evbio.writeText(this.path(i, 'trigger'), 'none');
evbio.writeText(this.path(i, 'brightness'), '0');
this.on[i] = false;
}
this.log = '开始跑马';
let i: number = 0;
let t: number = setInterval(() => {
let prev: number = (i + 3) % 4;
evbio.writeText(this.path(prev, 'brightness'), '0');
this.on[prev] = false;
evbio.writeText(this.path(i, 'brightness'), '1');
this.on[i] = true;
i++;
if (i >= 12) {
clearInterval(t);
evbio.writeText(this.path(3, 'brightness'), '0');
this.on[3] = false;
}
}, 200);
}
aboutToAppear() {
let n: string = evbio.readText('/sys/class/leds/user-led0/brightness');
if (n.length === 0) {
this.log = '没有 user-led0。四颗一起没 = 整节点 probe 失败,查 VGA/PCIe regulator';
}
}
}
读 user-led0/brightness 空,不要去点 work 凑合演示。那会把心跳关掉,且验收对不上四颗用户灯。
7. 现象对照
| 现象 | 原因 | 处理 |
|---|---|---|
| 四颗全无,work 在 | D4/D5 被 regulator 占,整节点失败 | disable `vcc3v3_vga`、`pcie30_3v3` |
| 有节点,写 1 灯灭 | ACTIVE_LOW 写反了 | 改 HIGH |
| 写 1 马上灭 | trigger 还是 heartbeat | echo none |
| 只有 work 能闪 | 看错灯 | user-led 在 D3~D6 |
| 应用流水灯在亮、板载不亮 | 那是扩展模块 | 看 `/sys/class/leds/user-led*` |
| dmesg -16 | GPIO busy | dtc 搜 PD4/PD5 谁先 claim |
| 改 dts 无效 | 只刷了 boot_linux | 刷 resource p4 |
flowchart TD
A[四颗用户灯全灭] --> B{ls leds 有 user-led0?}
B -->|有 work 无 user-led| C{dmesg leds-gpio -16?}
C -->|是| D[dtc 搜 PD4/PD5]
D --> E[disable vcc3v3_vga 和 pcie30_3v3]
B -->|四颗都在| F{写 brightness=1?}
F -->|灭| G[极性 / trigger]
F -->|亮| H[通]
B -->|一个都没有含 work| I[LEDS_GPIO 没编进内核]
四颗写在一个 gpio-leds 节点里,一颗脚 busy,四颗都不登记。公版 VGA / PCIe 电源占了 D5/D4,关掉。work 是另一颗灯,GPIO0_C0,别拿它充数。
系列第 26 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10
更多推荐

所有评论(0)