I’m using Ubuntu 22.04 on Rockpi 4A+ Ver1.72, but
didn’t work for me, so I created my own solution.
I’m enabling PWM0 and PWM1 with rsetup.
I’m sharing this here since I believe others might be facing the same issue.
Please make any modifications yourself as needed.
#!/usr/bin/env python3
import os
import time
import glob
from configparser import ConfigParser
# Constants
GPIO_FAN = 154 # GPIO154 for fan ON/OFF control
PERIOD = 1000000 # 1ms (1kHz)
CONFIG_PATH = "/etc/rockpi-poe.conf"
PWM1_DEVICE = "ff420010.pwm" # Device corresponding to PWM1
def get_pwm_chip():
"""Automatically get the pwmchipX corresponding to PWM1 (ff420010.pwm)"""
pwm_chips = glob.glob("/sys/class/pwm/pwmchip*")
for chip in pwm_chips:
device_path = os.path.realpath(f"{chip}/device")
if PWM1_DEVICE in device_path:
return os.path.basename(chip) # Get pwmchipX
return None
# Dynamically get pwmchipX
PWM_CHIP = get_pwm_chip()
PWM_CHANNEL = "pwm0"
PWM_PATH = f"/sys/class/pwm/{PWM_CHIP}/{PWM_CHANNEL}/"
if PWM_CHIP is None:
raise RuntimeError(f"PWM1 ({PWM1_DEVICE}) pwmchipX not found!")
def export_gpio():
"""Export GPIO154 (only on first execution)"""
if not os.path.exists(f"/sys/class/gpio/gpio{GPIO_FAN}"):
with open("/sys/class/gpio/export", "w") as f:
f.write(str(GPIO_FAN))
time.sleep(1) # Wait for reflection
def set_gpio(value):
"""Control ON/OFF of GPIO154"""
with open(f"/sys/class/gpio/gpio{GPIO_FAN}/direction", "w") as f:
f.write("out")
with open(f"/sys/class/gpio/gpio{GPIO_FAN}/value", "w") as f:
f.write(str(value))
def enable_pwm():
"""Enable PWM"""
if not os.path.exists(PWM_PATH):
with open(f"/sys/class/pwm/{PWM_CHIP}/export", "w") as f:
f.write("0")
time.sleep(2) # Wait for reflection
# Wait until the pwm0 directory is created
timeout = 5
while not os.path.exists(PWM_PATH) and timeout > 0:
time.sleep(0.5)
timeout -= 1
if not os.path.exists(PWM_PATH):
raise RuntimeError(f"PWM0 directory was not created: {PWM_PATH}")
# Apply settings
write_pwm("period", PERIOD)
write_pwm("enable", 1)
write_pwm("polarity", "normal")
def write_pwm(filename, value):
"""Write PWM settings"""
with open(f"{PWM_PATH}{filename}", "w") as f:
f.write(str(value))
def read_sensor_temp():
"""Get temperature from PoE HAT sensor"""
try:
v2t = lambda x: 42 + (960 - x) * 0.05 # Temperature conversion formula
with open('/sys/bus/iio/devices/iio:device0/in_voltage0_raw') as f:
raw_value = f.read().strip()
if not raw_value.isdigit(): # If not a number, return an invalid value
return 0
return v2t(int(raw_value))
except Exception:
return 0 # Return 0 if unable to retrieve
def read_soc_temp(zone=0):
"""Get SoC (CPU/GPU) temperature"""
try:
with open(f"/sys/class/thermal/thermal_zone{zone}/temp") as f:
return int(f.read().strip()) / 1000.0 # Convert from milli-degrees Celsius to Celsius
except Exception:
return 0 # Return 0 if unable to retrieve
def read_temp():
"""Get the highest temperature among PoE HAT, CPU, and GPU"""
return max(read_sensor_temp(), read_soc_temp(0), read_soc_temp(1))
def read_conf():
"""Read temperature thresholds from the configuration file"""
conf = {"lv0": 40, "lv1": 45, "lv2": 50, "lv3": 55}
try:
cfg = ConfigParser()
cfg.read(CONFIG_PATH)
conf["lv0"] = cfg.getint("fan", "lv0", fallback=40)
conf["lv1"] = cfg.getint("fan", "lv1", fallback=45)
conf["lv2"] = cfg.getint("fan", "lv2", fallback=50)
conf["lv3"] = cfg.getint("fan", "lv3", fallback=55)
except Exception:
pass
return conf
def set_fan_speed():
"""Adjust fan speed according to temperature"""
conf = read_conf()
while True:
temp = read_temp()
# Default to minimum rotation (25%)
duty_cycle = 0.75 * PERIOD
level = "25%" # Initial value
if temp == 0:
print("Temperature sensor data unavailable!")
level = "25%" # Set to minimum rotation for safety
elif temp >= conf["lv3"]:
duty_cycle = PERIOD
level = "100%"
elif temp >= conf["lv2"]:
duty_cycle = 0.75 * PERIOD
level = "75%"
elif temp >= conf["lv1"]:
duty_cycle = 0.50 * PERIOD
level = "50%"
elif temp >= conf["lv0"]:
duty_cycle = 0.25 * PERIOD
level = "25%"
else:
duty_cycle = 0
level = "Stopped"
# Output temperature + fan speed level only
print(f"{temp}°C → Fan speed: {level}")
write_pwm("duty_cycle", int(duty_cycle))
time.sleep(10)
def start():
"""Start the fan"""
export_gpio()
set_gpio(1) # Turn GPIO154 ON
enable_pwm()
set_fan_speed()
def stop():
"""Stop the fan"""
set_gpio(0) # Turn GPIO154 OFF
if os.path.exists(PWM_PATH):
write_pwm("enable", 0)
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: python3 rockpi-poe.py start|stop")
sys.exit(1)
if sys.argv[1] == "start":
start()
elif sys.argv[1] == "stop":
stop()
else:
print("Usage: python3 rockpi-poe.py start|stop")