PWM
1. Introduction to PWM
PWM, or Pulse Width Modulation, is a technique used to generate analog signal output by controlling the pulse width of a digital signal. It is commonly employed in embedded systems and electronic devices for applications such as motor speed control, LED brightness adjustment, and audio signal generation. In Linux systems, PWM devices are typically managed and configured via the sysfs filesystem, with the device directories usually located under /sys/class/pwm/.
2. Controlling PWM (Shell)
2.1 Pin Distribution
Luckfox Omni3576 Pin Diagram:
2.2 Device Directory and Attributes
The
pwmchip0(in this case, 2ade2000) is used for the screen backlight and is enabled by default:root@luckfox:/# ls -l /sys/class/pwm
total 0
lrwxrwxrwx 1 root root 0 Aug 25 23:07 pwmchip0 -> ../../devices/platform/2ade0000.pwm/pwm/pwmchip0When multiple PWM device tree overlays are enabled, the system assigns smaller
pwmchipnumbers to PWM controllers with smaller values. For instance, the default screen backlight may switch topwmchip1.root@luckfox:/# ls -l /sys/class/pwm
total 0
lrwxrwxrwx 1 root root 0 Aug 26 00:59 pwmchip0 -> ../../devices/platform/2ade0000.pwm/pwm/pwmchip0
lrwxrwxrwx 1 root root 0 Aug 26 00:59 pwmchip1 -> ../../devices/platform/2ade2000.pwm/pwm/pwmchip1Name Device Tree Name Address pwm2m0_ch0 pwm2_8ch_0 2ade0000 pwm2m0_ch2 pwm2_8ch_2 2ade2000 The
/sys/class/pwm/pwmchipN/pwm0/directory contains multiple attributes that can be configured to control the PWM.root@luckfox:/# ls /sys/class/pwm/pwmchip0/pwm0/
capture duty_cycle enable period polarity power ueventenable: Enables or disables the PWM channelperiod: Sets the PWM signal periodduty_cycle: Sets the PWM signal duty cyclepolarity: Configures PWM signal polaritypower/control: Manages power settings for power-saving modes
2.3 Controlling PWM
Export or Unexport PWM to/from User Space (e.g., GPIO0_D3_d).
echo 0 > /sys/class/pwm/pwmchip0/export
echo 0 > /sys/class/pwm/pwmchip0/unexportSet the PWM period unit to ns. For example, the period of a 1KHz frequency is 1000000ns (note that in any case, the value of period must be greater than or equal to the value of duty_cycle).
echo 1000000 > /sys/class/pwm/pwmchip10/pwm0/periodSet PWM polarity normal or inverted.
echo "normal" > /sys/class/pwm/pwmchip0/pwm0/polarity
echo "inversed" > /sys/class/pwm/pwmchip0/pwm0/polarityEnable and disable PWM.
echo 1 > /sys/class/pwm/pwmchip1/pwm0/enable
echo 0 > /sys/class/pwm/pwmchip1/pwm0/enableSet the duty cycle (less than or equal to period )
echo 100000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 200000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 300000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 400000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 600000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 700000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 800000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 900000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 0 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
3. Controlling PWM Using Python (python-periphery)
The complete example code for PWM output using the python-periphery library is as follows:
#!/usr/bin/python3
from periphery import PWM
import time
import numpy as np
pwm = PWM(0, 0)
pwm.frequency = 100
pwm.polarity = "normal"
pwm.duty_cycle = 0
pwm.enable()
try:
while True:
for i in np.linspace(0, 2 * np.pi, 100):
duty_cycle = (np.sin(i) + 1) / 2
pwm.duty_cycle = duty_cycle
time.sleep(0.02)
except KeyboardInterrupt:
pwm.disable()
pwm.close()Program analysis.
pwm = PWM(0, 0) # Enable PWM chip 0, channel 0
pwm.frequency = 100 # Set 100Hz frequency
pwm.polarity = "normal" # Set PWM polarity
pwm.duty_cycle = 0 # Initial duty cycle
pwm.enable() # Enable PWM- Create a duty cycle loop that gradually increases the duty cycle to a maximum value and then gradually decreases it to a minimum value. This loop can use the
np.sin()function to simulate a similar curve to produce smooth brightness changes.。
while True:
# Generate an array that changes smoothly from 0 to 2π. This range is used to control the input of the sin() function.
for i in np.linspace(0, 2 * np.pi, 100):
# (duty_cycle) changes smoothly between 0 and 1
duty_cycle = (np.sin(i) + 1) / 2
pwm.duty_cycle = duty_cycle
time.sleep(0.02)- The output value of y = sinx will vary between -1 and 1 in the interval between 0 and 2Π. Since we want the duty cycle to vary smoothly between 0 and 1, the output range of (np.sin(i) + 1) / 2 is 0 to 1 (by dividing by 2, the result is limited to between 0 and 1).

- Create a duty cycle loop that gradually increases the duty cycle to a maximum value and then gradually decreases it to a minimum value. This loop can use the
Run the Script:
chmod +x pwm.py
./pwm.pyObserve the output on Pin 12.

4. Control PWM (C program)
In addition to controlling the PWM pins through shell scripts and Python libraries, you can also use C language to operate sysfs to achieve the same function.
Export to User Space.
int pwm_export(int channel) {
FILE *pwm_export = fopen(PWM_PATH "/export", "w");
if (!pwm_export) {
perror("Failed to open PWM export");
return 1;
}
fprintf(pwm_export, "%d", channel);
fclose(pwm_export);
return 0;
}Unexport userspace.
int pwm_unexport(int channel) {
FILE *pwm_unexport = fopen(PWM_PATH "/unexport", "w");
if (!pwm_unexport) {
perror("Failed to open PWM unexport");
return 1;
}
fprintf(pwm_unexport, "%d", channel);
fclose(pwm_unexport);
return 0;
}Set the PWM period.
int pwm_set_period(int channel, int period_ns) {
char period_path[128];
snprintf(period_path, sizeof(period_path), PWM_PATH "/pwm%d/period", channel);
FILE *period_file = fopen(period_path, "w");
if (!period_file) {
perror("Failed to open PWM period");
return 1;
}
fprintf(period_file, "%d", period_ns);
fclose(period_file);
return 0;
}Set the duty cycle.
int pwm_set_duty_cycle(int channel, int duty_cycle_ns) {
char duty_cycle_path[128];
snprintf(duty_cycle_path, sizeof(duty_cycle_path), PWM_PATH "/pwm%d/duty_cycle", channel);
FILE *duty_cycle_file = fopen(duty_cycle_path, "w");
if (!duty_cycle_file) {
perror("Failed to open PWM duty cycle");
return 1;
}
fprintf(duty_cycle_file, "%d", duty_cycle_ns);
fclose(duty_cycle_file);
return 0;
}Enable PWM.
int pwm_enable(int channel, int enable) {
char enable_path[128];
snprintf(enable_path, sizeof(enable_path), PWM_PATH "/pwm%d/enable", channel);
FILE *enable_file = fopen(enable_path, "w");
if (!enable_file) {
perror("Failed to open PWM enable");
return 1;
}
fprintf(enable_file, "%d", enable);
fclose(enable_file);
return 0;
}Complete example program.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#define PWM_PATH "/sys/class/pwm/pwmchip10"
#define PERIOD_NS 1000000
#define MAX_DUTY_CYCLE_NS 1000000
#define MIN_DUTY_CYCLE_NS 0
#define PI 3.141592653589793
int pwm_export(int channel) {
FILE *pwm_export = fopen(PWM_PATH "/export", "w");
if (!pwm_export) {
perror("Failed to open PWM export");
return 1;
}
fprintf(pwm_export, "%d", channel);
fclose(pwm_export);
return 0;
}
int pwm_unexport(int channel) {
FILE *pwm_unexport = fopen(PWM_PATH "/unexport", "w");
if (!pwm_unexport) {
perror("Failed to open PWM unexport");
return 1;
}
fprintf(pwm_unexport, "%d", channel);
fclose(pwm_unexport);
return 0;
}
int pwm_set_period(int channel, int period_ns) {
char period_path[128];
snprintf(period_path, sizeof(period_path), PWM_PATH "/pwm%d/period", channel);
FILE *period_file = fopen(period_path, "w");
if (!period_file) {
perror("Failed to open PWM period");
return 1;
}
fprintf(period_file, "%d", period_ns);
fclose(period_file);
return 0;
}
int pwm_set_duty_cycle(int channel, int duty_cycle_ns) {
char duty_cycle_path[128];
snprintf(duty_cycle_path, sizeof(duty_cycle_path), PWM_PATH "/pwm%d/duty_cycle", channel);
FILE *duty_cycle_file = fopen(duty_cycle_path, "w");
if (!duty_cycle_file) {
perror("Failed to open PWM duty cycle");
return 1;
}
fprintf(duty_cycle_file, "%d", duty_cycle_ns);
fclose(duty_cycle_file);
return 0;
}
int pwm_enable(int channel, int enable) {
char enable_path[128];
snprintf(enable_path, sizeof(enable_path), PWM_PATH "/pwm%d/enable", channel);
FILE *enable_file = fopen(enable_path, "w");
if (!enable_file) {
perror("Failed to open PWM enable");
return 1;
}
fprintf(enable_file, "%d", enable);
fclose(enable_file);
return 0;
}
int main() {
int channel = 0;
if (pwm_export(channel)) {
return 1;
}
if (pwm_set_period(channel, PERIOD_NS)) {
return 1;
}
if (pwm_enable(channel, 1)) {
return 1;
}
double step_size = (2 * PI) / 100.0;
while (1) {
for (double i = 0; i <= 2 * PI; i += step_size) {
double duty_cycle_ratio = (sin(i) + 1) / 2;
int duty_cycle_ns = (int)(duty_cycle_ratio * MAX_DUTY_CYCLE_NS);
if (pwm_set_duty_cycle(channel, duty_cycle_ns)) {
return 1;
}
usleep(20000);
}
}
if (pwm_enable(channel, 0)) {
return 1;
}
if (pwm_unexport(channel)) {
return 1;
}
return 0;
}Compile and run the program.
gcc pwm.c -o pwm -lm
5. Device Tree Overview
The PWM DTS source file is defined in
kernel-6.10/arch/arm64/boot/dts/rockchip/rk3576.dtsiand can be directly utilized.pwm0_2ch_0: pwm@27330000 {
compatible = "rockchip,rk3576-pwm";
reg = <0x0 0x27330000 0x0 0x1000>;
interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
#pwm-cells = <3>;
pinctrl-names = "active";
pinctrl-0 = <&pwm0m0_ch0>;
clocks = <&cru CLK_PMU1PWM>, <&cru PCLK_PMU1PWM>;
clock-names = "pwm", "pclk";
status = "disabled";
};Example of enabling
pwm2m0_ch0in the device tree (luckfox-omni3576.dts): (Code snippet follows as required).&pwm2_8ch_0 {
status = "okay";
pinctrl-0 = <&pwm2m0_ch0>;
};