how to blink an LED using a PWM pin on Debian with C

272 Views Asked by At

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.

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;
}
0

There are 0 best solutions below