dart_periphery icon indicating copy to clipboard operation
dart_periphery copied to clipboard

PWM issue on pi5

Open TheSkangel opened this issue 1 year ago • 4 comments

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?

TheSkangel avatar Oct 09 '24 13:10 TheSkangel

dart_periphery comes will all needed libs on board. // setCustomLibrary("/usr/local/lib/libperiphery_arm64.so"); What happens when you comment out this line?

pezi avatar Oct 09 '24 13:10 pezi

I'm still getting the same error whether I set a custom library or use the libperiphery.so which is shared.

TheSkangel avatar Oct 09 '24 14:10 TheSkangel

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

pezi avatar Oct 09 '24 19:10 pezi

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);

AllyTechEngineering avatar Jan 31 '25 15:01 AllyTechEngineering