TL;DR Raspberry Pi 4 Case Fanはソフト的にもPWM制御できていない(かもしれない)。
なお、この記事はRaspi買ってない人がソースコードベースで調べた結果なので、間違っているかもしれないし、その通りにやるともしかするとソフトやハードにダメージがあるかもしれません。その前提でご確認ください。
- gpio-fan だと、onとoffとか、レベル制御しかできない。
- HWスペックとしてはPWM制御できると書いている。
- もったいない!もったいない!!
ここまでの調査メモ。
はじめに
@nekokurono7 さんがなかなか興味深い記事を書いていた。
これについて、もう少しソフトウェア方面で追加検証をしてみるよう。
なお、私自身はRaspberry pi 4持っていないので、「多分こうなるんじゃないのかなー」レベルです。
公式ファンとはどういう物なのか? どういう商品なのか?
Raspberry Piの記事がこちら。
ここにもしっかりと"ユーザーが選択したGPIO pinを介して、PWM制御ができる"と書いてある。
Fan speed control: Pulse width modulation control via user-selectable GPIO pin
PWMでファン制御するとは?
@sh_o さんのRaspberry Pi でPWM信号を使って5Vファンを制御するが非常にわかりやすい。
簡単に言うと以下となる。概念を雑に説明すると、PWMの信号を高速にHigh/Lowすると、これに合わせて電源-FAN-GNDが通電する。
- 電源(5V)
- GND
- PWM
Raspi公式の設定方法では?
raspberry-pi-4-case-fan 公式サイト に書いてある通り、Raspberry Pi Configuration toolから設定する。
Then open the Raspberry Pi Configuration tool:
- Click on the Raspberry Pi icon in the top left corner and select Preferences then Raspberry Pi Configuration.
- Select the Performance tab.
- Next to Fan, click Enabled.
- If you have connected your fan as shown above, the default of 14 for Fan GPIO does not need to be changed.
- Select the Fan Temperature at which you want your fan to turn on. The default is 80°C, which will stop the Raspberry Pi throttling on difficult tasks without having the fan on all the time.
Raspberry Pi Configuration toolのソースコードでは?
ここの設定を見ると、要するにkernel のcommand line引数に以下を追加するに見える。むむむ...
sed $CONFIG -i -e "$adtoverlay=gpio-fan,gpiopin=$GPIO,temp=$TEMP"
Linux Kernelのソースコードでは?
Raspberry pi 4のlinux kernelツリーは https://github.com/raspberrypi/linux
gpio-fanのソースコードは https://github.com/raspberrypi/linux/blob/rpi-5.10.y/drivers/hwmon/gpio-fan.c
static const struct of_device_id of_gpio_fan_match[] = {
{ .compatible = "gpio-fan", },
{},
};
MODULE_DEVICE_TABLE(of, of_gpio_fan_match);
このコードを追うと、疑問が出てくる...
PWM制御方法が若干違う・・・かも?
起動時や設定変更時でファン速度が変わる場合、set_fan_speed()関数をcallする。
ここから、__set_fan_ctrl()が呼ばれる。
/* Must be called with fan_data->lock held, except during initialization. */
static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index)
{
if (fan_data->speed_index == speed_index)
return;
__set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val);
fan_data->speed_index = speed_index;
}
/*
* Control GPIOs.
*/
/* Must be called with fan_data->lock held, except during initialization. */
static void __set_fan_ctrl(struct gpio_fan_data *fan_data, int ctrl_val)
{
int i;
for (i = 0; i < fan_data->num_gpios; i++)
gpiod_set_value_cansleep(fan_data->gpios[i],
(ctrl_val >> i) & 1);
}
あれ…… これ、指定されたindexに合わせて、GPIOでレベル指定して制御しているだけに見える・・・ ちょっと待ってあれ?
gpio-fanのdocumentを読んでみる
最近のデバイスドライバは、Documentation/devicetreeの下にも簡易的にだがマニュアルがある。
gpio_fan {
compatible = "gpio-fan";
gpios = <&gpio1 14 1
&gpio1 13 1>;
gpio-fan,speed-map = <0 0
3000 1
6000 2>;
alarm-gpios = <&gpio1 15 1>;
};
このドライバは今回のような1本の信号線上でパルス信号を送るのではなく、複数のGPIO信号線でレベル指定してそれをICでPWM化/DC-ACするパターンなのでは…? 例えば、buffaloのGPIO fan制御とかはこのパターンに見える... (名前は、リニア制御方式、でいいのかな?)
つまりどうやって動いていたように見えるのか?
簡単にいうと「PWM制御できるハードを、On/Offのレベル制御でしかつかっていない」ですね…。 もったいない、もったいない。
GPIO | |
---|---|
停止 | LOW |
全力 | HIGH |
どうやったらPWM制御になりそうか(予想)
使うべきドライバは"pwm-fan"
gpio-fanではなく、pwm-fanを使うと制御できそう。
PWM制御はHW?SW?
SoftでPWM制御もできる話にはなっているけど、そんなことlinux動作中にやると普通に厳しいので、HW制御を前提にする。
BCM2711のdata sheetを見る限り、Channel0/1は分離していないので、片方で設定をすればよさそう。
pwm1: pwm@7e20c800 {
compatible = "brcm,bcm2835-pwm";
reg = <0x7e20c800 0x28>;
clocks = <&clocks BCM2835_CLOCK_PWM>;
assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
assigned-clock-rates = <10000000>;
#pwm-cells = <2>;
status = "disabled";
};
接続先はGPIOのどこにする? ー> 18とかかなあ…
まず、linux kernel側のdevice tree設定で、GPIO40/41はPWM定義されている。これを真似して、PWM0を定義しないとダメっぽい。
pwm1_0_gpio40: pwm1_0_gpio40 {
pin-pwm {
pins = "gpio40";
function = "alt0";
bias-disable;
};
};
&pwm1 {
pinctrl-names = "default";
pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>;
status = "okay";
};
device treeに付け足すべきは?
多分、こんな感じになると思うんだけど、、、 ここからは現物がないので妄想ですね。
pwm-fan {
compatible = "pwm-fan";
#cooling-cells = <2>;
pwms = <&pwm1 1 1250000>;
cooling-levels = <16 128 192 240>;
};
pwmsの引数
- https://www.kernel.org/doc/Documentation/devicetree/bindings/pwm/pwm.txt
- 1 : た、たぶんこちらは1chに設定でいいはず。
- 1250000: nano sec。PWMのperiod(1サイクルの長さ)。
- (設定なし): PWM_POLARITY_INVERTEDが指定できるらしい
数字はかなり適当に、3000 rpm中に16分割で制御で、1.3ミリ秒間隔。もーっとおそくても(時間が長くても)いいかもしれない。
60 * 1,000,000,000 ns / 3000 rpm / 16 = 1,250,000
まとめ
実ハードウェアが無い状況で調べたことなので、間違っているかもしれません。興味のある方はこのあたり、夏休みの課題でチャレンジしてもいいかもしれませんね!
以上になります。