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/pwmtotal 0lrwxrwxrwx 1 root root 0 Aug 25 23:07 pwmchip0 -> ../../devices/platform/2ade0000.pwm/pwm/pwmchip0 -
When 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/pwmtotal 0lrwxrwxrwx 1 root root 0 Aug 26 00:59 pwmchip0 -> ../../devices/platform/2ade0000.pwm/pwm/pwmchip0lrwxrwxrwx 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/exportecho 0 > /sys/class/pwm/pwmchip0/unexport -
Set 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/period -
Set PWM polarity normal or inverted.
echo "normal" > /sys/class/pwm/pwmchip0/pwm0/polarityecho "inversed" > /sys/class/pwm/pwmchip0/pwm0/polarity -
Enable and disable PWM.
echo 1 > /sys/class/pwm/pwmchip1/pwm0/enableecho 0 > /sys/class/pwm/pwmchip1/pwm0/enable -
Set the duty cycle (less than or equal to period )
echo 100000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 200000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 300000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 400000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 600000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 700000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 800000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 900000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycleecho 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/python3from periphery import PWMimport timeimport numpy as nppwm = PWM(0, 0)pwm.frequency = 100pwm.polarity = "normal"pwm.duty_cycle = 0pwm.enable()try:while True:for i in np.linspace(0, 2 * np.pi, 100):duty_cycle = (np.sin(i) + 1) / 2pwm.duty_cycle = duty_cycletime.sleep(0.02)except KeyboardInterrupt:pwm.disable()pwm.close() -
Program analysis.
pwm = PWM(0, 0) # Enable PWM chip 0, channel 0pwm.frequency = 100 # Set 100Hz frequencypwm.polarity = "normal" # Set PWM polaritypwm.duty_cycle = 0 # Initial duty cyclepwm.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 1duty_cycle = (np.sin(i) + 1) / 2pwm.duty_cycle = duty_cycletime.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.py -
Observe 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.141592653589793int 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>;};