I recently discovered some kernel documentation Pulse Width Modulation (PWM) interface
Like much kernel documentation this is inscrutable. It states This API controls both the PWM period/duty_cycle config and the enable/disable state.
I will attempt to explore this in future but there is also Using PWMs with the sysfs interface which enabled me to get it working.
Load the overlay pwm
(or pwm-2chan
) with dtoverlay=pwm
in
config.sys
or on-the-fly with sudo dtoverlay pwm
You can use sudo dtoverlay pwm pin=12 func=4
to use GPIO12 rather than the default GPIO18.
Export a PWM channel for use with sysfs.
Set the period (in nanoseconds)
Set the duty_cycle (in nanoseconds)
Enable the PWM signal
The code below is testing code as part of my development process.
All listed code works but has minimal error checking and may not be the most elegant or efficient.
Only PWM0 code is listed but PWM1 also works (with appropriate changes).
The following script sets a single PWM output on pwm0 (pin12 GPIO18 or pin32 GPIO12).
#! /bin/sh
# Export channel 0
# Set the period 1,000,000 nS (1kHz)
# Set the duty_cycle 50%
# Enable the PWM signal
# 2023-07-06
cd /sys/class/pwm/pwmchip0
echo 0 > export
sleep 0.1
echo 10000000 > pwm0/period
echo 5000000 > pwm0/duty_cycle
echo 1 > pwm0/enable
echo 0 > /sys/class/pwm/pwmchip0/pwm0/enable
can be used to stop PWM.
I actually used a c program to setup both channels
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
//2023-07-05
#define PWM_SYSFS "/sys/class/pwm/pwmchip0/"
#define PWM_PATH0 "/sys/class/pwm/pwmchip0/pwm0/"
#define PWM_PATH1 "/sys/class/pwm/pwmchip0/pwm1/"
void writeSYS(char filename[], char value[]){
FILE* fp; // create a file pointer fp
fp = fopen(filename, "w"); // open file for writing
fprintf(fp, "%s", value); // send the value to the file
fclose(fp); // close the file using fp
}
int main(int argc, char* argv[]){
int chan = 0;
writeSYS(PWM_SYSFS "export", "0"); // export PWM channel (0)
writeSYS(PWM_SYSFS "export", "1"); // export PWM channel (1)
usleep(100000); // sleep for 100ms
writeSYS(PWM_PATH0 "period" , "1000000"); // set period
writeSYS(PWM_PATH0 "duty_cycle" , "250000"); // set duty_cycle
writeSYS(PWM_PATH0 "enable" , "1"); // enable
writeSYS(PWM_PATH1 "period" , "50000"); // set period
writeSYS(PWM_PATH1 "duty_cycle" , "25000"); // set duty_cycle
writeSYS(PWM_PATH1 "enable" , "1"); // enable
}
To use the following python code on PWM1 change pwm_channel = '1'
#! /usr/bin/env python3
# 2023-07-09
"""
Export channel (0 or 1)
Set the period 1,000,000 ns (1kHz)
Set the duty_cycle 50%
Enable the PWM signal
"""
pwm_channel = '0'
PERIOD = 1000000
DUTY_CYCLE = PERIOD//2
import sys, os, time
PWM_SYSFS = "/sys/class/pwm/pwmchip0"
if not os.path.isdir(PWM_SYSFS) :
print("Overlay not loaded")
exit()
pwm_dir = f"{PWM_SYSFS}/pwm{pwm_channel}"
if(not os.access(pwm_dir, os.F_OK)) :
with open(f"{PWM_SYSFS}/export", "w") as f:
f.write(f"{pwm_channel}\n")
time.sleep(0.1)
with open(os.path.join(pwm_dir, "period"), "w") as f:
f.write(f"{PERIOD}\n")
with open(os.path.join(pwm_dir, "duty_cycle"), "w") as f:
f.write(f"{DUTY_CYCLE}\n")
with open(os.path.join(pwm_dir, "enable"), "w") as f:
f.write("1\n")
I have recently been advised that only the GPIO sysfs
has been deprecated but the PWM sysfs
interface is still supported.
The sysfs
has been deprecated and "replaced" by a userspace char device interface introduced with Linux 4.8 which is intended to replace the Sysfs interface /sys/class/gpio
. Unfortunately this has limited functionality and is little used for gpio.
As of kernel 6.1 sysfs
remains but the possibility of its removal still exists. There seems to be no userspace functionality for PWM like I²C and SPI.