看门狗会喂了。今天让风扇转起来:系统不会上电自动出波形,要自己打开通道、设周期和占空比。

风扇座有电、PWM 脚也 okay,扇叶却一动不动。ls /sys/class/pwm 能看见 pwmchipN,但下面没有 pwm0/。PWM 子系统不会因为设备树 status = "okay" 就自动出波形。用户态要自己 export,再写 periodduty_cycle,最后 enable。少一步都是直流 0 或高阻,风扇当没接到信号。

第一次占空比先拉到 100%。很多 12 V 风扇有起转阈值,20% 占空比在桌上转、在风道里不转,你会误判脚号或极性。100% 转起来之后再往下降,才知道调速是不是这条通路。

产品 rk3568_evb,风扇走 pwm0

device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts
&pwm0 {
    status = "okay";
};

不要去使能 pwm5。pwm5 在这块板上和相机电源脚打架,板级是 disabled。风扇不是 pwm5。

内核:

CONFIG_PWM=y
CONFIG_PWM_ROCKCHIP=y

不要开 SENSORS_PWM_FAN。那是 hwmon 温控,会按温度自动改占空比。演示和验收用手写 sysfs 就够,自动温控会把你刚写的 100% 改掉,看起来像「写了没生效」。


1. chip 号不要写死

RK3568 有多路 PWM 控制器。pwm0 常见地址 fdd70000(PMU 侧)。pwmchip 的编号按 probe 顺序,不一定是 0

hdc shell "ls -l /sys/class/pwm"
hdc shell "ls -l /sys/class/pwm/pwmchip*/device"

看软链指向谁。名字或 uevent 里带 fdd70000.pwm / pwm0 的那颗,才是风扇。下面用 $CHIP 指它,例子里写 pwmchip0 只是常见情况。

for c in /sys/class/pwm/pwmchip*; do
    echo -n "$c -> "
    readlink $c/device
done

npwm 告诉你这个控制器有几路通道。pwm0 节点通常 npwm=1,export 的是通道 0,目录名仍叫 pwm0(相对这个 chip)。

hdc shell "cat /sys/class/pwm/pwmchip0/npwm"

2. export 之后三步:period、duty、enable

单位是 纳秒period=1000000 是 1 kHz;风扇更常见 20~25 kHz,听不见啸叫:period=40000 是 25 kHz。

必须先写 period,再写 duty_cycle。duty 不能大于当前 period,否则 -EINVAL。第一次 100% 就是 duty_cycle = period

CHIP=/sys/class/pwm/pwmchip0

# 前置:export 通道 0(已经 export 过会 Device or resource busy,可忽略)
echo 0 > $CHIP/export

# 三步
echo 40000 > $CHIP/pwm0/period
echo 40000 > $CHIP/pwm0/duty_cycle    # 先 100%
echo 1 > $CHIP/pwm0/enable

降速:

echo 20000 > $CHIP/pwm0/duty_cycle    # 50%
echo 10000 > $CHIP/pwm0/duty_cycle    # 25%

停:

echo 0 > $CHIP/pwm0/enable

不要 unexport 来停,下次还得再 export。enable=0 即可。

极性:

hdc shell "cat /sys/class/pwm/pwmchip0/pwm0/polarity"

normal:duty 越大,高电平越宽,多数风扇越快。inversed:100% 反而停转、0% 满转。现场「100% 不动、接近 0 狂转」先看 polarity,再怀疑脚。

有的内核 polarity 只读。那就在软件里把「速度 100」映射成 duty=0。


3. 为什么只 okay 控制器不够

PWM 控制器 probe 成功,只是寄存器能访问。sysfs 的 pwmN 是通道对象,export 才申请这一路、配 pinctrl、让脚从 GPIO 变成 PWM 功能。没 export 时,脚可能仍是 GPIO 或高阻。

时序:

dts &pwm0 okay  →  pwmchip 出现
export 0        →  pwm0/ 目录、脚切到 PWM
period          →  计数器周期
duty_cycle      →  比较值
enable=1        →  脚上真正出波

dmesg 里 pwm-rockchip probe 成功但风扇不转,停在后四格的某一格。

电源:PWM 是控制线,不是供电。12 V 风扇要风扇座的 12 V。PWM 脚是 3.3 V 逻辑,座上通常有三极管或专用驱动把 3.3 V 波变成风扇能认的电平。量 SoC 脚有 25 kHz、座上没有,查驱动管和座供电,不要改 dts。

1 kHz 能转,但能听见啸叫。验收用 25 kHz(period=40000)。示波器时基不够时,先用 1000000 看有没有波,再改回 40000。


4. 设备树和 pinctrl

板级常常只开控制器,pinctrl 用 SoC dtsi 默认的 pwm0m0_pins。若风扇不转且 SoC 脚上也没有波,把 pinctrl 写明白:

&pwm0 {
    status = "okay";
    pinctrl-names = "active";
    pinctrl-0 = <&pwm0m0_pins>;
};

具体脚以 dtsi 的 pwm0m0_pins 为准,不要和 uart、gpio-leds 抢。改之前:

dtc -I dtb -O dts /sys/firmware/fdt | grep -A20 "pwm@fdd70000"

pwm5 保持 disabled。相机那路如果误 okay,会和风扇无关的脚冲突,表现为相机不上电,不是风扇问题。

改 dts 刷 resource + boot_linux:

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"

5. 完整 C

/* pwm_fan.c
 * armv7-unknown-linux-ohos-clang --target=arm-linux-ohos \
 *   --sysroot=${OHOS_NATIVE}/sysroot \
 *   -o pwm_fan pwm_fan.c \
 *   -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1
 *
 * ./pwm_fan                 # 100%
 * ./pwm_fan 50              # 50%
 * ./pwm_fan 0               # enable=0
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.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 find_chip(char *out, int len)
{
    DIR *d = opendir("/sys/class/pwm");
    struct dirent *e;
    if (!d) {
        return -1;
    }
    while ((e = readdir(d)) != NULL) {
        char link[192], tgt[192];
        if (strncmp(e->d_name, "pwmchip", 7) != 0) {
            continue;
        }
        snprintf(link, sizeof(link), "/sys/class/pwm/%s/device", e->d_name);
        int n = (int)readlink(link, tgt, sizeof(tgt) - 1);
        if (n < 0) {
            continue;
        }
        tgt[n] = 0;
        if (strstr(tgt, "fdd70000") || strstr(tgt, "pwm0")) {
            snprintf(out, len, "/sys/class/pwm/%s", e->d_name);
            closedir(d);
            return 0;
        }
        if (out[0] == 0) {
            snprintf(out, len, "/sys/class/pwm/%s", e->d_name);
        }
    }
    closedir(d);
    return out[0] ? 0 : -1;
}

int main(int argc, char **argv)
{
    char chip[128] = {0}, p[192];
    int percent = (argc > 1) ? atoi(argv[1]) : 100;
    const char *period = "40000";

    if (find_chip(chip, sizeof(chip)) < 0) {
        fprintf(stderr, "没有 pwmchip。CONFIG_PWM_ROCKCHIP / &pwm0 okay?\n");
        return 1;
    }
    printf("chip %s percent=%d\n", chip, percent);

    snprintf(p, sizeof(p), "%s/export", chip);
    wr(p, "0");

    snprintf(p, sizeof(p), "%s/pwm0/period", chip);
    if (wr(p, period) < 0) {
        return 2;
    }

    if (percent <= 0) {
        snprintf(p, sizeof(p), "%s/pwm0/enable", chip);
        return wr(p, "0") == 0 ? 0 : 3;
    }
    if (percent > 100) {
        percent = 100;
    }
    {
        int duty = 40000 / 100 * percent;
        char ds[16];
        snprintf(ds, sizeof(ds), "%d", duty);
        snprintf(p, sizeof(p), "%s/pwm0/duty_cycle", chip);
        if (wr(p, ds) < 0) {
            return 4;
        }
    }
    snprintf(p, sizeof(p), "%s/pwm0/enable", chip);
    if (wr(p, "1") < 0) {
        return 5;
    }
    printf("enabled. first 100%%, then drop percent.\n");
    return 0;
}

HAP 写这些节点要 chmod。export 之后才有 pwm0 目录,开机脚本里要先 export 再 chmod duty/enable:

CHIP=/sys/class/pwm/pwmchip0
echo 0 > $CHIP/export 2>/dev/null
chmod 0666 $CHIP/export
chmod 0666 $CHIP/pwm0/period $CHIP/pwm0/duty_cycle $CHIP/pwm0/enable

6. ArkTS

import evbio from 'libevbio.so';

@Entry
@Component
struct PwmFan {
  @State percent: number = 100;
  @State log: string = '先 100%,转起来再往下';
  private chip: string = '';

  build() {
    Column() {
      Text('PWM 风扇').fontSize(22).fontWeight(FontWeight.Bold)
      Text(this.percent.toString() + '%').fontSize(28).margin({ top: 8 })
      Row() {
        Button('100%').onClick(() => { this.set(100); }).margin(6)
        Button('50%').onClick(() => { this.set(50); }).margin(6)
        Button('25%').onClick(() => { this.set(25); }).margin(6)
        Button('停').onClick(() => { this.set(0); }).margin(6)
      }
      Text(this.log).margin({ top: 12 }).fontSize(14)
    }.padding(20)
  }

  aboutToAppear() {
    this.chip = evbio.findPwmChip();
    if (this.chip.length === 0) {
      this.log = '没有 pwmchip,查 &pwm0';
      return;
    }
    evbio.writeText(this.chip + '/export', '0');
    this.set(100);
  }

  set(pct: number) {
    if (this.chip.length === 0) {
      return;
    }
    let base: string = this.chip + '/pwm0';
    if (pct <= 0) {
      evbio.writeText(base + '/enable', '0');
      this.percent = 0;
      this.log = 'enable=0';
      return;
    }
    let r1: string = evbio.writeText(base + '/period', '40000');
    let duty: number = Math.floor(40000 * pct / 100);
    let r2: string = evbio.writeText(base + '/duty_cycle', duty.toString());
    let r3: string = evbio.writeText(base + '/enable', '1');
    this.percent = pct;
    this.log = 'period ' + r1 + ' duty ' + r2 + ' en ' + r3;
  }
}

第一次进页面就 100%。不要默认 10% 然后抱怨「风扇坏了」。


7. 现象对照

现象原因处理
有 pwmchip 无 pwm0/没 export`echo 0 > export`
duty 写入 EINVAL先写了 duty 或 duty>period先 period,duty≤period
100% 不转、0% 狂转polarity inversed读 polarity,软件反着映射
25% 不转、100% 转起转阈值先 100% 再降
SoC 脚有波、风扇不转12 V 或驱动管量座供电,别改 dts
写了被改回去开了 pwm-fan 温控演示不要 SENSORS_PWM_FAN
相机同时挂了动了 pwm5pwm5 保持 disabled
啸叫period 太大(1 kHz)改 40000(25 kHz)
flowchart TD
  A[风扇不转] --> B{pwmchip 在?}
  B -->|无| C[&pwm0 okay / PWM_ROCKCHIP]
  B -->|有| D{pwm0/ 目录在?}
  D -->|无| E[export 0]
  D -->|有| F{enable=1 且 duty=period?}
  F -->|否| G[先 100%]
  F -->|是仍不转| H{脚上有 25kHz?}
  H -->|无| I[pinctrl / 写错 chip]
  H -->|有| J[12V 和座上驱动管]

export 之后才有通道。period → duty → enable,第一次 duty 等于 period。chip 按控制器地址找,不要写死编号。


系列第 28 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐