PWM
In this chapter, we will learn how to use PWM at the application level.
Sample program : Code.zip
1. PWM
PWM, which stands for Pulse Width Modulation, is a technique used to generate analog-like signals by controlling the pulse width of a digital signal. It is commonly used in embedded systems and electronic devices for applications such as motor speed control, LED brightness control, audio signal generation, and more.
In the Linux operating system, PWM devices are typically managed and configured using the sysfs file system, and their device directories are usually located under the /sys/class/pwm/
directory.
2. PWM Control (Shell)
2.1 Pin Distribution
Due to pin multiplexing in LuckFox Pico, LuckFox Pico Mini A/B and LuckFox Pico Plus there can be pin function conflicts. Therefore, the default PWM pins for each board model are as follows:
- LuckFox Pico:
PWM0_0
- Pin 34PWM1_0
- Pin 4PWM10_1
- Pin 54PWM11_1
- Pin 55
- LuckFox Pico Mini A/B:
PWM10_1
- Pin 54PWM11_1
- Pin 55
- LuckFox Pico Plus:
PWM0_0
- Pin 34PWM1_0
- Pin 4PWM10_1
- Pin 54PWM11_1
- Pin 55
LuckFox Pico Diagram:
LuckFox Pico Mini A/B Diagram:
LuckFox Pico Plus Diagram:
2.2 Enable PWM
Use luckfox-config to enable the relevant configuration:
- Use the ↑ and ↓ keys on the keyboard to navigate through menu items, Enter to select, ←, →, or Tab to choose between the OK and cancel buttons, Esc to cancel and go back, and the spacebar to select options. Any changes will take effect after a reboot. Note: When logging in via ADB, arrow keys and Tab cannot be used; only numeric keys can be used to select options, and Enter to confirm.
Open the luckfox-config tool in the terminal:
luckfox-config
Select Advanced Options:
Select PWM:
Select the PWM interface you want to enable, here we use PWM7_M1 as an example:
Choose
enable
, and after selecting, pressEnter
to confirm, then pressEsc
to exit:Reboot the development board.
reboot
2.3 Device Directory
Under the /sys/class/pwm/
directory, each PWM device has its own separate subdirectory, usually named in the form pwmchipX
, where X is the PWM device number. You can check this using the following command:
# ls -l /sys/class/pwm
lrwxrwxrwx 1 root root 0 pwmchip10 -> ../../devices/platform/ff490020.pwm/pwm/pwmchip10
lrwxrwxrwx 1 root root 0 pwmchip1 -> ../../devices/platform/ff350010.pwm/pwm/pwmchip1
lrwxrwxrwx 1 root root 0 pwmchip11 -> ../../devices/platform/ff490030.pwm/pwm/pwmchip11
lrwxrwxrwx 1 root root 0 pwmchip0 -> ../../devices/platform/ff350000.pwm/pwm/pwmchip0
2.4 Device Properties
After successfully exporting a channel of PWM, a subdirectory corresponding to that channel is created within the PWM's device directory. This subdirectory contains property files specific to that channel. Users can configure and control various parameters of a specific PWM channel by modifying the contents of these files, enabling precise control over the characteristics of the PWM signal. This provides a convenient interface for users to interact with the PWM hardware and adjust the PWM signal's properties.
View the property files for a single channel, such as PWM10:
# ls /sys/class/pwm/pwmchip10/pwm0/
uevent capture duty_cycle output_type
polarity enable power periodProperty Files
By using these property files, you can easily configure and control the PWM channel to suit different application scenarios and requirements. Some key properties include:
enable
: Used to enable or disable the PWM channel.period
: Sets the period time of the PWM signal.duty_cycle
: Sets the duty cycle of the PWM signal.polarity
: Configures the polarity of the PWM signal.power/control
: Enables or disables power management for the PWM channel (typically used for power-saving modes).
2.5 Controlling PWM
Test PWM. Export PWM10_M1 (GPIO1_C6_d) to user space.
echo 0 > /sys/class/pwm/pwmchip10/export
Set the PWM period in nanoseconds. For example, for a 1KHz frequency, the period is 1000000ns (Note: ensure that the period value is greater than or equal to the duty_cycle value).
echo 1000000 > /sys/class/pwm/pwmchip10/pwm0/period
Set the duty cycle (must be less than or equal to the period).
echo 100000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 200000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 300000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 400000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 500000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 600000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 700000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 800000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 900000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 1000000 > /sys/class/pwm/pwmchip10/pwm0/duty_cycle
echo 0 > /sys/class/pwm/pwmchip10/pwm0/duty_cycleSet PWM polarity to normal or inverted.
echo "normal" > /sys/class/pwm/pwmchip1/pwm0/polarity
echo "inversed" > /sys/class/pwm/pwmchip1/pwm0/polarityEnable and disable PWM.
echo 1 > /sys/class/pwm/pwmchip1/pwm0/enable
echo 0 > /sys/class/pwm/pwmchip1/pwm0/enableUnexport PWM from user space.
echo 0 > /sys/class/pwm/pwmchip10/unexport
3. PWM Control (Python Program)
In the previous section, we demonstrated how to control devices by using the echo
command to modify device files. Next, we will implement PWM control using a Python program.
3.1 Sample Program
With the following program, PWM10 output can be controlled.
from periphery import PWM
import time
pwm = PWM(10, 0)
try:
pwm.frequency = 1000
pwm.duty_cycle = 0
pwm.polarity = "normal"
pwm.enable()
direction = 1
while True:
pwm.duty_cycle += 0.01 * direction
pwm.duty_cycle = round(pwm.duty_cycle, 2)
if pwm.duty_cycle == 1.0:
direction = -1
elif pwm.duty_cycle == 0.0:
direction = 1
time.sleep(0.05)
except KeyboardInterrupt:
pass
finally:
pwm.close()
3.2 Open PWM Device
This code segment uses the PWM
class from the periphery
library to create a PWM object. In this example, the PWM object is initialized for PWM10.
pwm = PWM(10, 0)
3.3 Configure PWM
In this code segment, some properties of the previously created PWM object are configured. The PWM frequency is set to 1000 Hz, the polarity is set to "normal," and the duty cycle is initialized to 0.
pwm.frequency = 1000
pwm.duty_cycle = 0
pwm.polarity = "normal"
3.4 PWM Output
This code segment implements a simple PWM output. By continuously adjusting the duty cycle in a loop, a breathing effect is achieved. The pwm.duty_cycle
is increased by 0.01 each time (adjustable as needed), and the round
function is used to keep it to two decimal places. When reaching the maximum or minimum value, the direction is reversed. After each PWM value update, the breathing speed is controlled using time.sleep
.
while True:
pwm.duty_cycle += 0.01 * direction
pwm.duty_cycle = round(pwm.duty_cycle, 2)
if pwm.duty_cycle == 1.0:
direction = -1
elif pwm.duty_cycle == 0.0:
direction = 1
print(pwm.duty_cycle)
time.sleep(0.05)
3.5 Running the Program
Use the vi tool in the terminal to create a Python file, paste the Python program, and save it.
# nano pwm.py
Run the program.
# python3 pwm.py
Experimental Observations
Control PWM10 to realize breathing light
4. PWM Control (C Program)
In the previous section, we demonstrated how to control PWM using the echo
command and a Python program. Additionally, you can also use the vi
editor to modify files, but be cautious about user permissions when making modifications. Furthermore, we can utilize C library functions or system calls to read and write device files for device control. Please note that to run programs on specific embedded systems, it is typically necessary to use cross-compilation tools to compile the code into an executable file that can run on the target development board. Let's explore the specific implementation steps together.
4.1 Complete Code
With the following program, you can control PWM output.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PWM_PATH "/sys/class/pwm/pwmchip10"
#define PERIOD_NS 1000000
#define MIN_DUTY_CYCLE_NS 0
#define MAX_DUTY_CYCLE_NS 1000000
int main() {
FILE *pwm_export = fopen(PWM_PATH "/export", "w");
if (!pwm_export) {
perror("Failed to open PWM export");
return 1;
}
fprintf(pwm_export, "0");
fclose(pwm_export);
FILE *period_file = fopen(PWM_PATH "/pwm0/period", "w");
if (!period_file) {
perror("Failed to open PWM period");
return 1;
}
fprintf(period_file, "%d", PERIOD_NS);
fclose(period_file);
FILE *enable_file = fopen(PWM_PATH "/pwm0/enable", "w");
if (!enable_file) {
perror("Failed to open PWM enable");
return 1;
}
fprintf(enable_file, "1");
fclose(enable_file);
int direction = 1; // 1 for increasing duty cycle, -1 for decreasing
int duty_cycle_ns = 0;
while (1) {
duty_cycle_ns += 10000 * direction;
if(duty_cycle_ns == MAX_DUTY_CYCLE_NS)
direction = -1;
else if(duty_cycle_ns == MIN_DUTY_CYCLE_NS)
direction = 1;
FILE *duty_cycle_file = fopen(PWM_PATH "/pwm0/duty_cycle", "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);
usleep(50000); // Adjust this value for desired breathing speed
}
FILE *pwm_unexport = fopen(PWM_PATH "/unexport", "w");
if (!pwm_unexport) {
perror("Failed to open PWM unexport");
return 1;
}
fprintf(pwm_unexport, "0");
fclose(pwm_unexport);
return 0;
}
4.2 Export PWM Channel to User Space
This code exports the specified PWM channel for user space control by writing the device index (usually 0) to /sys/class/pwm/pwmchip10/export
.
FILE *pwm_export = fopen(PWM_PATH "/export", "w");
if (!pwm_export) {
perror("Failed to open PWM export");
return 1;
}
fprintf(pwm_export, "0");
fclose(pwm_export);
4.3 Set PWM Period
This code sets the PWM period to 1KHZ by writing the period value "1000000" (in nanoseconds) to /sys/class/pwm/pwmchip10/pwm0/period
.
FILE *period_file = fopen(PWM_PATH "/pwm0/period", "w");
if (!period_file) {
perror("Failed to open PWM period");
return 1;
}
fprintf(period_file, "%d", PERIOD_NS);
fclose(period_file);
4.4 Enable PWM
This code enables the PWM signal output by writing the value "1" to the /sys/class/pwm/pwmchip10/pwm0/enable
file.
FILE *enable_file = fopen(PWM_PATH "/pwm0/enable", "w");
if (!enable_file) {
perror("Failed to open PWM enable");
return 1;
}
fprintf(enable_file, "1");
fclose(enable_file);
4.5 Implement PWM-Based Breathing LED Effect
This code opens the /sys/class/pwm/pwmchip10/pwm0/duty_cycle
file and achieves a breathing LED effect by continuously changing the duty_cycle_ns
variable from 0% to 100%, and then from 100% to 0%. The duty_cycle_ns
variable's changes are between 0 and 1000000, controlling the duty cycle variation. When the duty cycle reaches its minimum or maximum value, it reverses direction to create a back-and-forth motion. Additionally, the usleep(50000)
function is used to control the speed of the breathing LED. You can adjust this value as needed to change the breathing speed.
int direction = 1; // 1 for increasing duty cycle, -1 for decreasing
int duty_cycle_ns = 0;
while (1) {
duty_cycle_ns += 10000 * direction;
if(duty_cycle_ns == MAX_DUTY_CYCLE_NS)
direction = -1;
else if(duty_cycle_ns == MIN_DUTY_CYCLE_NS)
direction = 1;
FILE *duty_cycle_file = fopen(PWM_PATH "/pwm0/duty_cycle", "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);
usleep(50000); // Adjust this value for desired breathing speed
}
4.6 Unexport PWM Channel from User Space
This code cancels the export of the specified PWM channel to user space by writing the device index to /sys/class/pwm/pwmchip10/unexport
.
FILE *pwm_unexport = fopen(PWM_PATH "/unexport", "w");
if (!pwm_unexport) {
perror("Failed to open PWM unexport");
return 1;
}
fprintf(pwm_unexport, "0");
fclose(pwm_unexport);
4.7 Cross-Compilation
Specify the Cross-Compilation Tool
First, you need to add the path to the cross-compilation tool to the system's
PATH
environment variable so that you can use the cross-compilation tool from anywhere. You can add the following line to your shell configuration file (usually~/.bashrc
or~/.bash_profile
or~/.zshrc
, depending on your shell). Note that the path afterPATH=
should point to the directory where the cross-compilation tool is located.gcc path
<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
Open the shell configuration file.
nano ~/.bashrc
Add the path of the cross-compilation tool to the system's PATH environment variable. Replace
<SDK Directory>
with your own SDK path, such as/home/luckfox/luckfox-pico/
.export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
Reload the shell configuration file to apply the changes:
source ~/.bashrc
Compile the Program Using the Cross-Compilation Tool
arm-rockchip830-linux-uclibcgnueabihf-gcc pwm.c -o pwm
After successful cross-compilation, an executable file that can run on the development board will be generated in the current directory.
#ls
pwm pwm.c
4.8 Running the Program
File Transfer
First, transfer the
pwm
program from the virtual machine to Windows, and then transfer it to the development board via TFTP or ADB. Here are the steps to transfer the file from Windows to the development board using ADB:adb push path_to_file destination_on_development_board
eg: (Transferring the `pwm` file from the current directory to the root directory of the development board)
adb push pwm /Running the Program
Modify the permissions of the
pwm
file and then run the program:# chmod 777 pwm
# ./pwmExperimental Observations
Control PWM10 to realize breathing light
5. Modify Device Tree
The PWM10_M1 in all models of development boards is enabled by default and can be used directly. Next, let's take a look at how to enable other PWM channels if needed.
5.1 Modify Device Tree File
Device Tree File Paths
Development Board Configuration File
In the
<SDK directory>/project/cfg/BoardConfig_IPC/
directory, there are configuration files for different models of development boards. These files primarily include configuration parameters for various Luckfox Pico board models, covering aspects such as target architecture, boot medium, Uboot and kernel configurations, and partition settings. The SDK directory structure is as follows:├── build.sh -> project/build.sh ---- SDK compilation script
├── media --------------------------- Multimedia encoding, ISP, and related algorithms (can be compiled independently in the SDK)
├── sysdrv -------------------------- U-Boot, kernel, rootfs directory (can be compiled independently in the SDK)
├── project ------------------------- Reference applications, compilation configurations, and script directories
│ ├── cfg
│ ├── BoardConfig_IPC
│ ├── BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
│ ├── BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico_Mini_A-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Mini_B-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Plus-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1106_Luckfox_Pico_Pro_Max-IPC.mk
│ └── ...
├── output -------------------------- Directory for storing SDK compiled image files
├── docs ---------------------------- SDK documentation directory
└── tools --------------------------- Image burning packaging tools and burning toolsAmong them,
RK_KERNEL_DTS
specifies the Device Tree Source (DTS) file for the kernel. Taking Luckfox Pico as an example, by opening theBoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
file, we can see that the file pointed to byRK_KERNEL_DTS
isrv1103g-luckfox-pico.dts
Device Tree File Path
Based on
RK_KERNEL_DTS
, the device tree file path for Luckfox Pico can be determined as follows:<SDK directory>/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico.dts
Enable PWM0_M0
In the device tree, the code snippet to enable PWM0_M0 is as follows:
Enable PWM0_M1
If we want to use PWM0_M1 for output, we first need to uncomment the configuration statements for PWM0_M1 (please refer to line 201 in the code):
According to the interface diagram, PWM0_M1 (GPIO1_D2_d) has the default function of I2C3, so we need to comment out the I2C3 code snippet in the device tree:
5.2 Compile the Kernel
Recompile the kernel
luckfox@luckfox:~/Luckfox-Pico/luckfox-pico$ ./build.sh kernel