I have a VisionFive 2 SBC with Debian and I want to blink an LED attached to GPIO46 PWM0 pin using the Linux PWM library with a C program.
The goal is to modify an old existing program from a Raspberry Pi 3 using the wiringPi library and rewrite the PWM interface code to use a similar library to exercise the PWM interfaces.
What I'm hoping for is something that is simple and straightforward similar to the wiringPi library example below with a set of function calls to set up the PWM pin with a duty cycle, etc. and then let it run.
In case you're curious, this is the article I've written about my experience with the VisionFive 2 thus far, Setting Up a New VisionFive 2 RISC-V SBC.
I have found a couple of SO postings that are not much help as they are more about Linux kernel development rather than a user space application.
- Example of use pwm_get() in linux kernel
- rpi - pwm_get() - how to use static lookup tables or device-tree
This board does have a bit of instability however I've already implemented a blinking LED connected to a GPIO pin using the standard libgpiod library with a C program to control the GPIO pin. The PWM functionality is not available through libgpiod and appears to have its own library.
I found a few man pages such as this one pwm_get however I'm not sure how to use this function. It appears to be looking for some kind of a descriptor or identifier from the Linux device tree.
I found what looks to be the configuration file, /boot/config-5.15.0-starfive, which contains the following directives:
CONFIG_PWM=y
CONFIG_PWM_SYSFS=y
... a bunch of CONFIG_PWM_ directives commented out
CONFIG_PWM_STARFIVE_PTC=y
This documentation JH7110 PWM Developing Guide has this bit of code from some kind of a configuration or description file:
For example, the pin configuration of PWM on the VisionFive 2 SBC is listed under the pwm_pins node in the file jh7110-visionfive-v2.dtsi.
The following code block provides an example of the pins used by PWM including dout (Data Output), doen (Data Output Enable), and so on.
pwm_pins: pwm-pins {
pwm_ch0-pins {
sf,pins = <PAD_GPIO46>;
sf,pinmux = <PAD_GPIO46_FUNC_SEL 0>;
sf,pin-ioconfig = <IO(GPIO_IE(1))>;
sf,pin-gpio-dout = <GPO_PTC0_PWM_0>;
sf,pin-gpio-doen = <OEN_PTC0_PWM_0_OE_N>;
};
pwm_ch1-pins {
sf,pins = <PAD_GPIO59>;
sf,pinmux = <PAD_GPIO59_FUNC_SEL 0>;
sf,pin-ioconfig = <IO(GPIO_IE(1))>;
sf,pin-gpio-dout = <GPO_PTC0_PWM_1>;
sf,pin-gpio-doen = <OEN_PTC0_PWM_1_OE_N>;
};
};
In a later section, "4.1 Sysfs Interface Example", there is an example of accessing the PWM drivers through the Sysfs interface in /sys/class/pwm/. When I attempted to use the procedure as described, it did not work displaying an error message of -bash: export: Permission denied.
user@starfive:/sys/class/pwm$ ls -l pwmchip0
lrwxrwxrwx 1 root root 0 Dec 31 2000 pwmchip0 -> ../../devices/platform/soc/120 d0000.pwm/pwm/pwmchip0
user@starfive:/sys/class/pwm$ sudo echo 0 > export
-bash: export: Permission denied
user@starfive:/sys/class/pwm$ cd pwmchip0
user@starfive:/sys/class/pwm/pwmchip0$ ls
device export npwm power subsystem uevent unexport
user@starfive:/sys/class/pwm/pwmchip0$ sudo echo 0 > export
-bash: export: Permission denied
Then I discovered this SO post, Permission denied with sudo, which explained the problem and the work around needed.
user@starfive:/sys/class/pwm/pwmchip0$ sudo sh -c 'echo 0 > export'
[sudo] password for user:
user@starfive:/sys/class/pwm/pwmchip0$ ls -l
total 0
lrwxrwxrwx 1 root root 0 Nov 13 23:41 device -> ../../../120d0000.pwm
--w------- 1 root root 4096 Nov 13 23:44 export
-r--r--r-- 1 root root 4096 Nov 13 23:41 npwm
drwxr-xr-x 2 root root 0 Nov 13 23:41 power
drwxr-xr-x 3 root root 0 Nov 13 23:44 pwm0
lrwxrwxrwx 1 root root 0 Dec 31 2000 subsystem -> ../../../../../../class/pwm
-rw-r--r-- 1 root root 4096 Dec 31 2000 uevent
--w------- 1 root root 4096 Nov 13 23:41 unexport
user@starfive:/sys/class/pwm/pwmchip0$ ls pwm0
capture duty_cycle enable period polarity power uevent
user@starfive:/sys/class/pwm/pwmchip0$
With the PWM device exported and usable, I was then able to drive a blinking LED using the Sysfs interface to the PWM drivers with the following settings which indicates the VisionFive 2 does have function PWM drivers in the Debian OS build.
user@starfive:/sys/class/pwm/pwmchip0/pwm0$ sudo sh -c 'echo 1 >enable'
[sudo] password for user:
user@starfive:/sys/class/pwm/pwmchip0/pwm0$ sudo sh -c 'echo 1000000000 >period'
user@starfive:/sys/class/pwm/pwmchip0/pwm0$ sudo sh -c 'echo 50000000 >duty_cycle'
user@starfive:/sys/class/pwm/pwmchip0/pwm0$ sudo sh -c 'echo 0 >enable'
The existing wiringPi library code is as follows:
/*
* This test program performs a test of a simple LED circuit
* that is connected to GPIO #18 to exercise the PWM by changing
* the brightness of an LED.
*
* WARNING: running this program must be done with sudo in order for
* the wiringPi library to allow access to the PWM functionality
* of GPIO #18.
*
* Compile with the following command line:
* cc pwmtest.c -lwiringPi
*
* This command line compiles this test program and links it with the
* wiringPi.lib library for manipulating the GPIO pins of a Raspberry Pi.
*/
#include <wiringPi.h>
#include <stdio.h>
int main ()
{
// LEDPIN is wiringPi Pin #1 or GPIO #18
// we choose this pin since it supports PWM as
// PWM is not supported by any other GPIO pins.
const int LEDPIN = 1;
if (wiringPiSetup() == -1) {
printf ("Setup wiringPi Failed!\n");
return -1;
}
printf ("Reminder: this program must be run with sudo. Delay 5 seconds.\n");
delay (5000);
printf (" starting now.\n");
pinMode (LEDPIN, PWM_OUTPUT);
// set the PWM mode to Mark Space
pwmSetMode(PWM_MODE_MS);
// set the clock divisor to reduce the 19.2 Mhz clock
// to something slower, 5 Khz.
// Range of pwmSetClock() is 2 to 4095.
pwmSetClock (3840); // 19.2 Mhz divided by 3840 is 5 Khz.
// set the PWM range which is the value for the range counter
// which is decremented at the modified clock frequency.
// in this case we are decrementing the range counter 5,000
// times per second since the clock at 19.2 Mhz is being
// divided by 3840 to give us 5 Khz.
pwmSetRange (2500); // range is 2500 counts to give us half second.
delay (1); // delay a moment to let hardware settings settle.
{
int i;
int list[] = {50, 500, 1024, 2500, -1};
for (i = 0; list[i] > 0; i++) {
// range for the value written is 0 to 1024.
pwmWrite (LEDPIN, list[i]); // set the Duty Cycle for this range.
// delay 10 seconds to watch the LED flash due to the PWM hardware.
printf (" PWM Duty Cycle %d\n", list[i]);
delay (10000);
}
}
// cleanup the environment. set each pin to low
// and set the mode to INPUT. These steps make sure
// the equipment is safe to manipulate and prevents
// possible short and equipment damage from energized pin.
pinMode(LEDPIN, INPUT);
digitalWrite (LEDPIN, LOW);
return 0;
}