PWM散热风扇设置—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

文章来源声明: 原文作者:万物智能; 来源站点:掘金; 原文链接:https://juejin.cn/post/7686408837754028082; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

把「风扇不转」拆成 export、period/duty、极性、供电四条线,并附现象对照表,是 PWM 调试少见的实操清单,适合 RK3568 与 OpenHarmony 驱动开发者排障。

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

风扇座有电、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 = <span>"okay"</span>;
};

不要去使能 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 <span>"ls -l /sys/class/pwm"</span>
hdc shell <span>"ls -l /sys/class/pwm/pwmchip*/device"</span>

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

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

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

hdc shell <span>"cat /sys/class/pwm/pwmchip0/npwm"</span>


  1. 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

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

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

降速:

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

停:

<span>echo</span> 0 > <span>$CHIP</span>/pwm0/enable

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

极性:

hdc shell <span>"cat /sys/class/pwm/pwmchip0/pwm0/polarity"</span>

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

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


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

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

时序:

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

dmesgpwm-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。


  1. 设备树和 pinctrl

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

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

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

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

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

改 dts 刷 resource + boot_linux:

hdc shell <span>"cat /proc/partitions"</span>
hdc shell <span>"dd if=/data/resource.img of=/dev/block/mmcblk1p4 bs=4M conv=fsync"</span>
hdc shell <span>"dd if=/data/boot_linux.img of=/dev/block/mmcblk1p5 bs=4M conv=fsync"</span>


  1. 完整 C

/* pwm_fan.c
 * armv7-unknown-linux-ohos-clang --target=arm-linux-ohos \
 *   --sysroot=<span>${OHOS_NATIVE}</span>/sysroot \
 *   -o pwm_fan pwm_fan.c \
 *   -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1
 *
 * ./pwm_fan                 <span># 100%</span>
 * ./pwm_fan 50              <span># 50%</span>
 * ./pwm_fan 0               <span># enable=0</span>
 */
<span>#include <stdio.h></span>
<span>#include <stdlib.h></span>
<span>#include <string.h></span>
<span>#include <dirent.h></span>
<span>#include <fcntl.h></span>
<span>#include <unistd.h></span>
<span>#include <errno.h></span>

static int wr(const char *path, const char *s)
{
    int fd = open(path, O_WRONLY);
    <span>if</span> (fd < 0) {
        fprintf(stderr, <span>"open %s: %s\n"</span>, path, strerror(errno));
        <span>return</span> -1;
    }
    int n = (int)write(fd, s, strlen(s));
    close(fd);
    <span>return</span> n < 0 ? -1 : 0;
}

static int find_chip(char *out, int len)
{
    DIR *d = opendir(<span>"/sys/class/pwm"</span>);
    struct dirent *e;
    <span>if</span> (!d) {
        <span>return</span> -1;
    }
    <span>while</span> ((e = readdir(d)) != NULL) {
        char <span>link</span>[192], tgt[192];
        <span>if</span> (strncmp(e->d_name, <span>"pwmchip"</span>, 7) != 0) {
            <span>continue</span>;
        }
        snprintf(<span>link</span>, sizeof(<span>link</span>), <span>"/sys/class/pwm/%s/device"</span>, e->d_name);
        int n = (int)<span>readlink</span>(<span>link</span>, tgt, sizeof(tgt) - 1);
        <span>if</span> (n < 0) {
            <span>continue</span>;
        }
        tgt[n] = 0;
        <span>if</span> (strstr(tgt, <span>"fdd70000"</span>) || strstr(tgt, <span>"pwm0"</span>)) {
            snprintf(out, len, <span>"/sys/class/pwm/%s"</span>, e->d_name);
            closedir(d);
            <span>return</span> 0;
        }
        <span>if</span> (out[0] == 0) {
            snprintf(out, len, <span>"/sys/class/pwm/%s"</span>, e->d_name);
        }
    }
    closedir(d);
    <span>return</span> 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 = <span>"40000"</span>;

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

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

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

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

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

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


  1. ArkTS

import evbio from <span>'libevbio.so'</span>;

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

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

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

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

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


  1. 现象对照

现象原因处理
有 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[<span>export</span> 0]
  D -->|有| F{<span>enable</span>=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