Skip to content

Instantly share code, notes, and snippets.

@Gadgetoid
Last active May 20, 2026 21:06
Show Gist options
  • Select an option

  • Save Gadgetoid/b92ad3db06ff8c264eef2abf0e09d569 to your computer and use it in GitHub Desktop.

Select an option

Save Gadgetoid/b92ad3db06ff8c264eef2abf0e09d569 to your computer and use it in GitHub Desktop.
Raspberry Pi 5 - All channels on pwm0

Hardware PWM on the Raspberry Pi 5 b

Since PWM is a little fraught with gotchas, this is mostly a message to future me-

(Note to self, rtfm - https://datasheets.raspberrypi.com/rp1/rp1-peripherals.pdf)

pin a0 a3
GPIO19 PWM0_CHAN3
GPIO18 PWM0_CHAN2
GPIO15 PWM0_CHAN3
GPIO14 PWM0_CHAN2
GPIO13 PWM0_CHAN1
GPIO12 PWM0_CHAN0

TODO: Figure out how to tell if pwm0 is on /sys/class/pwm/pwmchip1 or /sys/class/pwm/pwmchip2. pwm1 on the Pi 5 might have device/consumer:platform:cooling_fan/

Life is short, this single dtoverlay configures GPIO12, GPIO13, GPIO18 and GPIO19 to their respective alt modes on boot and enables pwm0:

/dts-v1/;
/plugin/;

/{
	compatible = "brcm,bcm2712";

	fragment@0 {
		target = <&rp1_gpio>;
		__overlay__ {
			pwm_pins: pwm_pins {
				pins = "gpio12", "gpio13", "gpio18", "gpio19";
				function = "pwm0", "pwm0", "pwm0", "pwm0";
			};
		};
	};

	fragment@1 {
		target = <&rp1_pwm0>;
		frag1: __overlay__ {
			pinctrl-names = "default";
			pinctrl-0 = <&pwm_pins>;
			status = "okay";
		};
	};
};

Save as "pwm-pi5-overlay.dts" and compile with:

dtc -I dts -O dtb -o pwm-pi5.dtbo pwm-pi5-overlay.dts

Install:

sudo cp pwm-pi5.dtbo /boot/firmware/overlays/

Don't forget to add dtoverlay=pwm-pi5 to /boot/firmware/config.txt...

Then use this janky script to stick some safety rails on poking PWM:

#!/bin/bash
NODE=/sys/class/pwm/pwmchip1
CHANNEL="$1"
PERIOD="$2"
DUTY_CYCLE="$3"

function usage {
	printf "Usage: $0 <channel> <period> <duty_cycle>\n"
	printf "    channel - number from 0-3\n"
	printf "    period - PWM period in nanoseconds\n"
	printf "    duty_cycle - Duty Cycle (on period) in nanoseconds\n"
	exit 1
}

if [[ ! $CHANNEL =~ ^[0-3]+$ ]]; then
	usage
fi

if [ -d "$NODE/device/consumer:platform:cooling_fan/" ]; then
	echo "Hold your horses, looks like this is pwm1?"
	exit 1
fi

if [ ! -d "$NODE/pwm$CHANNEL" ]; then
	echo "0" | sudo tee -a "$NODE/export"
fi

echo "0" | sudo tee -a "$NODE/pwm$CHANNEL/enable" > /dev/null
echo "$PERIOD" | sudo tee -a "$NODE/pwm$CHANNEL/period" > /dev/null
if [ $? -ne 0 ]; then
	echo "^ don't worry, handling it!"
	echo "$DUTY_CYCLE" | sudo tee -a "$NODE/pwm$CHANNEL/duty_cycle" > /dev/null
	echo "$PERIOD" | sudo tee -a "$NODE/pwm$CHANNEL/period" > /dev/null
else
	echo "$DUTY_CYCLE" | sudo tee -a "$NODE/pwm$CHANNEL/duty_cycle" > /dev/null
fi
echo "1" | sudo tee -a "$NODE/pwm$CHANNEL/enable" > /dev/null


case $CHANNEL in
	"0")
	PIN="12"
	FUNC="a0"
	;;
	"1")
	PIN="13"
	FUNC="a0"
	;;
	"2")
	PIN="18"
	FUNC="a3"
	;;
	"3")
	PIN="19"
	FUNC="a3"
esac

# Sure, the pin is set to the correct alt mode by the dtoverlay at startup...
# But we'll do this to protect the user (me, the user is me) from themselves:
pinctrl set $PIN $FUNC

echo "PWM$CHANNEL set to $PERIOD ns, $DUTY_CYCLE, on pin $PIN (func $FUNC)."
@db2053

db2053 commented May 20, 2026

Copy link
Copy Markdown

Hi,

I am submitting a README.md modification to fix a bug in the first script version:

if [ ! -d "$NODE/pwm$CHANNEL" ]; then
	echo "0" | sudo tee -a "$NODE/export"
fi

echo "0" allow only PWM0 channel usage.
The following lines enable correct channel management for all channels:

if [ ! -d "$NODE/pwm$CHANNEL" ]; then
	echo "$CHANNEL" | sudo tee -a "$NODE/export"
fi

This bug is fixed in the second script version.

I have updated the 2nd script version, with new comments and more explicit writing:

#!/bin/bash
NODE=/sys/class/pwm/pwmchip1
PIN="$1"
PERIOD="$2"
DUTY_CYCLE="$3"

# *******************************
# Function to display script usage
# *******************************
function usage {
	printf "Usage: $0 <pin> <period> <duty_cycle>\n"
	printf "    pin - one of 12, 13, 14, 15, 18 or 19 (14 and 18 on the same channel, 15 and 19 on the same chanel\n"
	printf "    period - PWM period in nanoseconds\n"
	printf "    duty_cycle - Duty Cycle (on period) in nanoseconds\n"
	exit 1
}

# **************************************************
# Function to modify a setting value on the PWM node
#***************************************************
function pwmset {
	SETTING=$1
	VALUE=$2
	echo "$VALUE" | sudo tee -a "$NODE/$SETTING" > /dev/null
}

# ************
# *** Main ***
# ************
if [ -d "$NODE/device/consumer:platform:cooling_fan/" ]; then
	echo "$NODE is reserved for platform cooling fan. Use another free PWM node !"
	exit 1
fi

# Select PWM settings for the asqed PIN
case $PIN in
	"12")
	CHANNEL="0"
	FUNC="a0"
	;;
	"13")
	CHANNEL="1"
	FUNC="a0"
	;;
	"14")
	CHANNEL="2"
	FUNC="a0"
	;;
	"15")
	CHANNEL="3"
	FUNC="a0"
	;;
	"18")
	CHANNEL="2"
	FUNC="a3"
	;;
	"19")
	CHANNEL="3"
	FUNC="a3"
	;;
	*)
	echo "Unknown pin $PIN."
	exit 1
esac

# PWM pin desactivation if asked
if [[ "$PERIOD" == "off" ]]; then	
	if [ -d "$NODE/pwm$CHANNEL" ]; then
		pinctrl set $PIN no
		pwmset "pwm$CHANNEL/enable" "0"
		pwmset "unexport" "$CHANNEL"
	fi
	exit 0
fi

# Setting validity control
if [[ ! $PERIOD =~ ^[0-9]+$ ]]; then
	usage
fi

# Setting validity control
if [[ ! $DUTY_CYCLE =~ ^[0-9]+$ ]]; then
	usage
fi

# Activate channel (create drivers file)
if [ ! -d "$NODE/pwm$CHANNEL" ]; then
	pwmset "export" "$CHANNEL"
fi

# PWM channel configuration
pwmset "pwm$CHANNEL/enable" "0"
pwmset "pwm$CHANNEL/period" "$PERIOD"
if [ $? -ne 0 ]; then
	echo "^ don't worry, handling it!"
	pwmset "pwm$CHANNEL/duty_cycle" "$DUTY_CYCLE"
	pwmset "pwm$CHANNEL/period" "$PERIOD"
else
	pwmset "pwm$CHANNEL/duty_cycle" "$DUTY_CYCLE"
fi
pwmset "pwm$CHANNEL/enable" "1"

# Sure, the pin is set to the correct alt mode by the dtoverlay at startup...
# But we'll do this to protect the user from themselves:
pinctrl set $PIN $FUNC

echo "GPIO $PIN (Ch. $CHANNEL, Fn. $FUNC) set to $PERIOD ns, $DUTY_CYCLE."

In my case, platform cooling fan is on NODE=/sys/class/pwm/pwmchip1 and I need to use NODE=/sys/class/pwm/pwmchip0

Thanks a lot for this project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment