PWM issue on pi5
Hello, I'm currently trying to run the PWM example on my pi5 but I keep getting the following error:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PWMerrorCode.pwmErrorOpen
#0 _checkError (package:dart_periphery/src/pwm.dart:120)
#1 PWM._openPWM (package:dart_periphery/src/pwm.dart:165)
#2 new PWM (package:dart_periphery/src/pwm.dart:150)
#3 main (package:flutter_pi_demo_app/main.dart:42)
#4 _runMain.<anonymous closure> (dart:ui/hooks.dart:301)
#5 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297)
#6 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184)
I'm using the GPIO18 and the following code:
import 'package:dart_periphery/dart_periphery.dart';
import 'dart:io';
void main() {
setCustomLibrary("/usr/local/lib/libperiphery_arm64.so");
final pwm = PWM(2, 2);
try {
print('start');
print(pwm.getPWMinfo());
pwm.setPeriodNs(10000000);
pwm.setDutyCycleNs(8000000);
print(pwm.getPeriodNs());
pwm.enable();
print("Wait 20 seconds");
sleep(Duration(seconds: 20));
pwm.disable();
} finally {
pwm.dispose();
print('end');
}
}
I tried accessing the provided documentation for pwm in the example but it seems like the provided url was hacked and now points to another website...
Linux kernel Linux 6.6.31+rpt-rpi-2712 aarch64
configurations are dtoverlay=pwm,pin=18,func=2.
I also tried using pwm directly with a bash file and there it works just fine:
#!/bin/bash
CHANNEL=2
CHIP=pwmchip2
PWM=pwm$CHANNEL
echo $CHANNEL > /sys/class/pwm/$CHIP/export
sleep 1
echo 1000 > /sys/class/pwm/$CHIP/$PWM/period
echo 500 > /sys/class/pwm/$CHIP/$PWM/duty_cycle
echo 1 > /sys/class/pwm/$CHIP/$PWM/enable
Does anybody had this issue before or does know how to fix it?
dart_periphery comes will all needed libs on board. // setCustomLibrary("/usr/local/lib/libperiphery_arm64.so"); What happens when you comment out this line?
I'm still getting the same error whether I set a custom library or use the libperiphery.so which is shared.
Thanks for the hijacked PWM website - I wiil replace the URL.
I added your dtoverlay=pwm,pin=18,func=2 defintion to my RPI.
But on my RPI3 (32bit) this definition creates a /sys/class/pwm/pwmchip0/pwm0 device tree
and the demo works: https://github.com/pezi/dart_periphery/blob/main/example/pwm_example.dart dart pwm_example.dart
PWM 0, chip 0 (period=0.000000 sec, duty_cycle=nan%, polarity=normal, enabled=false)
10000000
Wait 20 seconds
My Fix For Model 5 PWM issues - Model 4B solutions in upcoming section.
Step One Create a Custom dtoverlay
This link has all of the details for creating the dtoverlay for pwmchip2, pwm0 - pwm3 channels and assigns to the pins
12: a0 pd | lo // GPIO12 = PWM0_CHAN0
13: a0 pd | lo // GPIO13 = PWM0_CHAN1
18: a3 pd | lo // GPIO18 = PWM0_CHAN2
19: a3 pd | lo // GPIO19 = PWM0_CHAN3
https://gist.github.com/Gadgetoid/b92ad3db06ff8c264eef2abf0e09d569
In a new terminal window: Use sudo nano and copy this code and save as pwm-pi5-overlay.dts
/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";
};
};
};
To compile (copy this at the command line):
dtc -I dts -O dtb -o pwm-pi5.dtbo pwm-pi5-overlay.dts
Then to install (copy this at the command line):
sudo cp pwm-pi5.dtbo /boot/firmware/overlays/
Comment out any pwm dtoverlays and add the dtoverlay config.txt in /boot/firmware/
sudo nano config.txt
dtoverlay=pwm-pi5
Step Two: Create a udev rule
We need to create a new udev rule that will export the pwm channels at boot. Linux by default does not do this. Note: from my research, there are only four pins exposed on the 40-pin header that have the alt function for PWM. GPIO 12,13,18 and 19. You can modify the rule to only expose pwmchip2, pwm0 to pwm3.
sudo nano /etc/udev/rules.d/99-pwm.rules
Copy and paste the following - if you changed the overlay and the pwmchip_ number change the following to match your changes.
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip0", RUN+="/bin/sh -c 'echo 0 > /sys/class/pwm/pwmchip0/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip2", RUN+="/bin/sh -c 'echo 0 > /sys/class/pwm/pwmchip2/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip6", RUN+="/bin/sh -c 'echo 0 > /sys/class/pwm/pwmchip6/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip0", RUN+="/bin/sh -c 'echo 1 > /sys/class/pwm/pwmchip0/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip2", RUN+="/bin/sh -c 'echo 1 > /sys/class/pwm/pwmchip2/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip6", RUN+="/bin/sh -c 'echo 1 > /sys/class/pwm/pwmchip6/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip2", RUN+="/bin/sh -c 'echo 2 > /sys/class/pwm/pwmchip2/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip6", RUN+="/bin/sh -c 'echo 2 > /sys/class/pwm/pwmchip6/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip2", RUN+="/bin/sh -c 'echo 3 > /sys/class/pwm/pwmchip2/export'"
ACTION=="add", SUBSYSTEM=="pwm", KERNEL=="pwmchip6", RUN+="/bin/sh -c 'echo 3 > /sys/class/pwm/pwmchip6/export'"
After saving the file, reload the udev rules.
sudo udevadm control --reload-rules && sudo udevadm trigger
You will need to reboot.
Step Three: Check the PWMs
These commands will check that pwm0 has been exported. If you do not see this, retrace your steps and try again. Note: The overlay file uses the four GPIOs that have the alt function for PWM. GPIO 12,13,18 and 19. From my research, you can only have four independent hardware PWMs.
ls /sys/class/pwmchip0
Response:
device export npwm power pwm0 pwm1 subsystem uevent unexport
ls /sys/class/pwmchip2
Response:
device export npwm power pwm0 pwm1 pwm2 pwm3 subsystem uevent unexport
ls /sys/class/pwmchip6
Response:
device export npwm power pwm0 pwm1 pwm2 pwm3 subsystem uevent unexport
Other usefull command line instructions: To check for how many channels per pwmchip
grep . /sys/class/pwm/pwmchip*/npwm
- Response - 2 channels:
/sys/class/pwm/pwmchip0/npwm:2 - Response - 4 channels:
/sys/class/pwm/pwmchip2/npwm:4 - Response - 4 channels:
/sys/class/pwm/pwmchip6/npwm:4
This command will check to see if a pwm is enabled.
cat /sys/class/pwm/pwmchip0/pwm0/enable
cat /sys/class/pwm/pwmchip2/pwm/enable
cat /sys/class/pwm/pwmchip6/pwm0/enable
If the response is 0, the pwm has been exported but not enabled. If the response is 1, the pwm has been enabled.
pinctrl is showing that on the RP1 controller, the pwmchip2 is considered pwmchip0.
12: a0 pd | lo // GPIO12 = PWM0_CHAN0
13: a0 pd | lo // GPIO13 = PWM0_CHAN1
18: a3 pd | lo // GPIO18 = PWM0_CHAN2
19: a3 pd | lo // GPIO19 = PWM0_CHAN3
Step Four: Test the PWMs with a Bash Script
In this script I am testing pwmchip2, pwm0 If you changed your dtoverlay and udev rules you will need to change the script.
The alt function for each GPIO pin is shown in the pinctrl (see above)
To be completely transparent, I am not an expert in bash scripts, so if you have a better solution, please share it!
In the terminal, in a folder of your choice, create the script file. Follow the correct pwmchip6 and pwm channel along with the corresponding GPIO and alt number.
12: a0 pd | lo // GPIO12 = PWM0_CHAN0
13: a0 pd | lo // GPIO13 = PWM0_CHAN1
18: a3 pd | lo // GPIO18 = PWM0_CHAN2
19: a3 pd | lo // GPIO19 = PWM0_CHAN3
sudo nano pwmchip2_pwm0.sh
If you have changed pwmchip, channel or pin change the script to your settings. Copy and paste the script.
#!/bin/bash # Ensure the script runs with bash
NODE=/sys/class/pwm/pwmchip2 # Correct PWM chip
CHANNEL="0" # Force PWM 0
PIN="12" # GPIO 12 for PWM 0
FUNC="a0"
PERIOD="$1"
DUTY_CYCLE="$2"
usage() {
echo "Usage: $0 <period> <duty_cycle>"
echo " period - PWM period in nanoseconds"
echo " duty_cycle - Duty Cycle (on period) in nanoseconds"
exit 1
}
pwmset() {
echo "$2" | sudo tee -a "$NODE/$1" > /dev/null
}
if [[ "$PERIOD" == "off" ]]; then
if [ -d "$NODE/pwm$CHANNEL" ]; then
pinctrl set $PIN no
pwmset "pwm$CHANNEL/enable" "0"
pwmset "unexport" "$CHANNEL"
fi
echo "PWM 0 disabled."
exit 0
fi
if [[ ! $PERIOD =~ ^[0-9]+$ ]]; then usage; fi
if [[ ! $DUTY_CYCLE =~ ^[0-9]+$ ]]; then usage; fi
if [ ! -d "$NODE/pwm$CHANNEL" ]; then
pwmset "export" "$CHANNEL"
sleep 0.1
fi
pwmset "pwm$CHANNEL/period" "$PERIOD"
pwmset "pwm$CHANNEL/duty_cycle" "$DUTY_CYCLE"
sleep 0.1 # Allow settings to apply
pwmset "pwm$CHANNEL/enable" "1"
pinctrl set $PIN $FUNC
echo "PWM 0 (GPIO $PIN, Fn. $FUNC) set to $PERIOD ns, $DUTY_CYCLE ns."
Save the file with a name that makes sense to you. For example, pwmchip2_pwm0.sh
To set permissions so you can run the script - make sure the file name is the same as the script.
sudo chmod +x pwmchip2_pwm0.sh
Use this command in terminal (assuming you are in the same folder) to run the script:
sudo bash ./pwmchip2_pwm0.sh 1000000 500000
This sets the period in ns and the duty cycle in ns which results in a 50% duty cycle square wave. If you set the duty cycle to 0 the PWM is low and if you set it at 100000 it is high.
Step Five, Test in your Flutter App with dart_periphery
Using the dtoverlay and udev as shown, I have successfully ran all four PWMs at the same time in my app.
- pwm0 = PWM(2, 0);
- pwm1 = PWM(2, 1);
- pwm2 = PWM(2, 2);
- pwm3 = PWM(2, 3);