How to Use PWM on Torizon OS
Introduction
PWMs are frequently used to control the power delivered by electrical signals to LEDs and motors. That requires a software layer to connect the OS system calls to the hardware. This article describes how to use PWMs on Torizon OS from user space through the sysfs interface. Therefore, it does not cover specific device driver usage. For further information, see Peripheral Access Overview.
This article provides the following sections:
This article complies with the Typographic Conventions for Torizon Documentation.
Prerequisites
- A Toradex System on Module with Torizon OS installed.
- A configured build environment, as described in the Configure Build Environment for Torizon Containers article.
- An understanding of Peripheral Access.
Connect Devices to PWM pins
Toradex SoMs offer multiple PWM pins for connecting peripherals, with some pins having PWM as their default or alternative function. Prioritize using pin functions compatible with all modules within the same family, ensuring the best compatibility with standard software and other modules in the family. To change pin functionalities, refer to the Pin Multiplexing article.
In addition, some standard PWM interfaces may be dedicated to specific uses, such as controlling LCD backlight intensity. To ensure compatibility and prevent issues, use PWM designed for general purposes. If necessary, you can modify the behavior of these interfaces. For detailed information, see Device Tree Overlays Overview.
You can find pin information in the SoM and carrier board datasheets.
Always check the IO voltage level of your modules and peripherals before connecting them. The TTL level for Colibri and Apalis IO is 3.3V, while for Verdin it is 1.8V. Do not apply 3.3V signals to Verdin IO.
The Verdin iMX8M Plus module provides multiple PWM buses, including the PWM_1
, which is designed for general use and is the one we will use in examples throughout this article. You can find this information in the PWM section of your module datasheet as follows:
X1 Pin# | Verdin Std Function | i.MX 8MP Ball Name | i.MX 8MP Function | I/O | Description |
---|---|---|---|---|---|
15 | PWM_1 | SPDIF_EXT_CLK | PWM1_OUT | O | General-purpose PWM |
16 | PWM_2 | GPIO1_IO11 | PWM2_OUT | O | General-purpose PWM |
To connect your device, search for the pin that carries the PWM_1
signal in your carrier board schematics or datasheet.
Backlight Control
Toradex modules have all PWM channels enabled by default. Some modules have dedicated a PWM channel for backlight control. You can use that channel for a different purpose if the backlight is not required.
To free up the PWM channel, you can remove backlight support by deleting the backlight node in the device tree file.
/delete-node/ backlight;
For detailed information, refer to the Device Tree Technical Overview article.
PWM Usage from Kernel Space
Depending on your use case, you may need to activate a device driver for controlling a peripheral connected to a PWM bus. In such cases, look for available drivers or add drivers to OS images. For additional information, see Device Drivers.
To activate a driver, proceed with the following steps:
Identify and test a PWM bus on Torizon OS by following the steps presented on PWM Usage from User Space.
Apply a device tree overlay that modifies the PWM bus node.
PWM Usage from User Space
Torizon OS, as a Linux-based distribution, treats peripheral devices as files. The PWM files are exposed through the sysfs interface in the /sys/class/pwm
directory. This section explains how to locate PWM buses by their address and interact with them using the sysfs interface. You can also use multimeters or oscilloscopes to ensure that pins work as expected.
The steps below describe how to locate the PWM_1
bus of the Verdin iMX8M Plus in the OS:
Identify the pin control group that contains the PWM pin: You can find this information in the module's device tree.
The following code snippet indicates that the
pinctrl_pwm_1
control group contains theSPDIF_EXT_CLK
pin with thePWM1_OUT
function.imx8mp-verdin.dtsipinctrl_pwm_1: pwm1grp {
fsl,pins =
<MX8MP_IOMUXC_SPDIF_EXT_CLK__PWM1_OUT 0x6>; /* SODIMM 15 */
};Locate the bus node that corresponds to the identified pin control group.
The snippet below shows that the
pwm1
node uses thepinctrl_pwm_1
pin control group.imx8mp-verdin.dtsi/* Verdin PWM_1 */
&pwm1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm_1>;
#pwm-cells = <3>;
};Search for the corresponding register address in the root node: The
reg
property contains the PWM address.In the following example, the address is
0x30660000
.imx8mp.dtsipwm1: pwm@30660000 {
compatible = "fsl,imx8mp-pwm", "fsl,imx27-pwm";
reg = <0x30660000 0x10000>;
interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MP_CLK_PWM1_ROOT>,
<&clk IMX8MP_CLK_PWM1_ROOT>;
clock-names = "ipg", "per";
#pwm-cells = <2>;
status = "disabled";
};List the available PWM pins: In your module's terminal, run the following command:
# sudo cat /sys/kernel/debug/pwm
The output below shows there are three PWMs available. Among them, it is possible to identify the
PWM_1
bus, which has the0x30660000
address.platform/30680000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normal
platform/30670000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normal
platform/30660000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normalThose shown as
null
are either not exported or in use by a driver.Export a PWM using the sysfs interface: PWM bus files are named
pwmchip<number>
.For example:
# echo 0 > /sys/class/pwm/pwmchip0/export
For additional information, see Using PWMs with the sysfs interface.
Check if the address of the exported PWM matches the selected bus: List the available PWM pins again.
# sudo cat /sys/kernel/debug/pwm
platform/30680000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normal
platform/30670000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normal
platform/30660000.pwm, 1 PWM device
pwm-0 (sysfs ): requested period: 2730667 ns duty: 0 ns polarity: normalThe
PWM_1
appears assysfs
, which means it is exported to userspace control and ready to use.
Container Access
To access the PWM files created by the sysfs interface within the container, you have to mount the directory into the container using the -v
flag as follows:
# docker run --rm -it -v sys/class/pwmchip<number>:<container-path> <container-image>
For comprehensive information, see Manage data in Docker.
Shell Example
This section shows an example of changing a PWM frequency and duty cycle using Linux shell commands from within a container.
Run a Debian Container for Torizon: Do not forget to mount the device data folder into the container.
# docker run -it --rm \
-v /sys/class/pwm/pwmchip2/:/PWM_1 \
--mount type=bind,source='/sys/kernel/debug/pwm',target='/pwm' \
torizon/debian:${CT_TAG_DEBIAN}Once inside the container, locate the device files.
## ls /PWM_1
device export npwm power subsystem uevent unexportConfigure the PWM parameters:
Export a PWM channel for use with sysfs.
## echo 0 > /PWM_1/export
List the PWM channel files.
## ls /PWM_1/pwm0
capture duty_cycle enable period polarity power ueventSet the PWM period and duty cycle, expressed in nanoseconds. The following commands set a period of 1 kHz and a duty cycle of 50%.
## echo 1000000 > /PWM_1/pwm0/period
## echo 500000 > /PWM_1/pwm0/duty_cycleEnable the PWM.
## echo 1 > /PWM_1/pwm0/enable
Finally, if you list the PWMs, you will get the following output:
## sudo cat /pwm
platform/30680000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normal
platform/30670000.pwm, 1 PWM device
pwm-0 ((null) ): period: 0 ns duty: 0 ns polarity: normal
platform/30660000.pwm, 1 PWM device
pwm-0 (sysfs ): requested period: 1000000 ns duty: 500000 ns polarity: normalThe preceding output confirms that the PWM bus is enabled and working with the configured parameters.
C Example
This section provides a step-by-step sample-based guide on using the sysfs interface for interacting with PWM buses in C. Note that while the steps in this approach vary by languages/frameworks and use cases, they provide a general idea of working with peripherals in user space.
The application uses C file-handling functions to configure and enable the PWM. If you set the frequency to 1 kHz and the duty cycle to 50%, you will get the following output:
frequency is 1000000 Hz
duty cycle is 50.00%
You can also check the results by listing the available PWMs as explained in the PWM Usage from User Space section. For example:
# sudo cat /sys/kernel/debug/pwm
platform/30660000.pwm, 1 PWM device
pwm-0 (sysfs ): requested period: 1000000 ns duty: 500000 ns polarity: normal
There are two ways to set the PWM parameters:
Modify the source code. The following code snippet shows the creation of frequency and duty cycle macros:
Pass the frequency and duty cycle as program arguments:
pwmC <Frequency (Hz)> <DUTY_CYCLE (0-100 %)>
To use this sample, perform the following steps:
Download the source code from GitHub: This application is part of the Toradex-provided Samples.
Modify the source code to match the PWM bus you are using: The second commit shows the modifications made to a Torizon IDE Extension template to use PWM buses.
For example, if using
pwmchip1
, you would modify the code as follows:- char pwm[25] = "/sys/class/pwm/pwmchip0/";
+ char pwm[25] = "/sys/class/pwm/pwmchip1/";
- Build and run the container. On Torizon OS, there are two possible approaches:
- Use the Torizon IDE extension for Visual Studio Code, which provides automated tasks and facilitates embedded application development. Since this sample is based on a Torizon IDE Extension template, the extension is the recommended approach for running it.
- Use Dockerfiles, Docker Compose files and Docker CLI commands to Deploy Container Images to Torizon OS.