Using PWM - FrankBau/meta-marsboard-bsp GitHub Wiki
Starting with commit 64701b8 (12 Dec 2016) all 4 PWMs are already enabled in the device tree and routed to pins 11, 9, 7, and 1 of header J10.
Prerequisites:
- ready to Boot Linux on the MarS Board
- Build Host with a bitbake shell
- Basic knowledge of the Device Tree
Extending the Device Tree for PWM4 function
Lets say, we want to use PWM4 function of the i.MX6 SoC. The [i.MX6 Reference Manual](iMX6 Reference Manual) shows that PWM4_OUT can be muxed to pad SD1_CMD (pad function Alt2) or SD4_DAT2 (pad function Alt2).
The MarS Board schematics show that SD1_CMD is available on extension header J10 pin 1 and not used otherwise, so we want to use this pin for PWM4_OUT.
We have to edit, re-compile and re-deploy the Device Tree in order to reflect this new board function:
The Device Tree on the Build Host
A working copy of the device tree is located under the BSPDIR
in build/tmp/work/marsboard-poky-linux-gnueabi/linux-marsboard/3.14.52-r0/git/arch/arm/boot/dts/imx6q-marsboard.dts
Note that this copy will be overwritten by a clean build (among others). So, finally we want to save the results under sources close to the kernel recipe: sources/meta-marsboard-bsp/recipes-kernel/linux/linux-marsboard-3.14.52/marsboard/imx6q-marsboard.dts
. But, using the working copy gives you a faster turn-around cycle during development.
Edit the Device Tree: Pad Configuration and Muxing
Near the end of the &iomuxc
section, after the closing }
of pinctrl_usbotg: usbotggrp {
add a new pin group:
pinctrl_pwm4: pwm4grp {
fsl,pins = <
MX6QDL_PAD_SD1_CMD__PWM4_OUT 0x1b0b1
>;
};
See iMX6 Pad Mux and Pad Control for detailed explanation of the settings.
Edit the Device Tree: Enabling PWM4 function of the i.MX6 SoC
At the very end of the Device Tree, add an entirely new top-level section:
&pwm4 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm4>;
status = "okay";
};
The line pinctrl-0 = <&pinctrl_pwm4>;
connects PWM4 to the pin group pinctrl_pwm4
defined above
and the line status = "okay";
enables the PWM4 function of the i.MX6 SoC.
For the curious: &iomuxc
and &pwm4
are labels referring to Device Tree nodes defined in the included file imx6qdl.dtsi
. See Device Tree for further information.
Compiling the Device Tree
Open a bitbake shell on the Build Host and enter:
bitbake -c devshell virtual/kernel
This will open a new window with a devshell (a command prompt prepared for cross-compilation of the Linux Kernel).
The Linux Kernel build system has the ability to build a Device Tree Blob (.dtb) by typing:
make imx6q-marsboard.dtb
Make finds out that the source (.dts) has changed and compiles it again. The resulting Device Tree Blob is located under the BSPDIR in build/tmp/work/marsboard-poky-linux-gnueabi/linux-marsboard/3.14.52-r0/build/arch/arm/boot/dts/imx6q-marsboard.dtb
Build errors are reported from the Device Tree Compiler (DTC) on the command line with a line number indicating the error position. In most cases, errors result from misplaced braces, semi-colons or mistyped identifiers.
Deploy the Device Tree Blob
Remove the micro SD card from the target and insert it into the host. Copy file imx6q-marsboard.dtb
to the BOOT partition of the microSD card. Use a plain old cp
command from a command prompt and do not use GUI tools directly as they tend to create a recycle bins and other fancy stuff on your precious (space limited) microSD card.
If you like to leave the microSD card on the target, you can mount the BOOT partition from a running Linux and copy the .dtb to it. In all cases, a reboot is needed to make the new Device Tree effective.
Now you are ready to use the PWM4 function on the MarS Board.
PWM Access from the Command Line
Use case: This is primarily a debugging aid.
Export the PWM pin
echo 0 > /sys/class/pwm/pwmchip3/export
Each pwmchip represents a single pwm device on the iMX6. There are 4 pwm devices pwmchip0 .. pwmchip3, so PWM4 is the last. Each pwmchip implements exactly one pwm pin with the number 0, therefore the echo 0
.
Set PWM Period and Duty Cycle
The values are in ns, we set a period of 1 ms and a duty cycle of 200 µs:
echo 1000000 > /sys/class/pwm/pwmchip3/pwm0/period
echo 200000 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
Enable the PWM pin
echo 1 > /sys/class/pwm/pwmchip3/pwm0/enable
If you connect a LED between header J10 pin 1 (SD1_CMD) and pin 2 (GND), it should be dimmed. By changing the duty_cycle you may change the LED brightness.
Shut down
To undo the effects, issue the following commands:
echo 0 > /sys/class/pwm/pwmchip3/pwm0/enable
echo 0 > /sys/class/pwm/pwmchip3/unexport
PWM Access from a standard Kernel Module
There are some common use cases like
- Backlight Control for a LCD display (pwm-backlight)
- PWM LED control (pwm-leds)
where kernel module drivers are readily available. They can be activated by extending the device tree.
See http://developer.toradex.com/knowledge-base/pwm-%28linux%29 for an example.
PWM Access from a custom Kernel Module
Use case: use when the available PWM kernel modules do not fit, e.g. when you need to control several PWMs at once for servo motor control, etc..
The above steps can be executed in a similar way from a kernel module:
define global variables
static const int my_pwm_id = 3; // header J10 pin 1 == GPIO1_18 == PWM4
struct pwm_device *my_pwm_dev;
request the pwm in the kernel module init function call
my_pwm_dev = pwm_request( my_pwm_id, "my pwm" );
to disable/enable the pwm pin call
pwm_enable(my_pwm_dev);
pwm_disable(my_pwm_dev);
to set the period and duty cycle call
pwm_config( my_pwm_dev, 200000, 1000000 );
to free the resource call in the module exit function
pwm_free(my_pwm_dev)
Discussion
The kernel module, as is, is a nice starting point during development. It has several drawbacks, however:
- it needs manual loading (
insmod
) and unloading (rmmod
) which is convenient during development but not suitable for autonomous embedded systems. - it uses board specific knowledge, because
my_pwm_id
is hard-coded in the source code.
Improvements (t.b.d.)
-
There are "of" functions to get the pwm number from the Device Tree. this is more portable as the same kernel module driver can be used on different boards with different pin assignments by simply using unique device trees for each board.
-
There are "devm" functions for resource management that automatically free resources when init fails or the module is unloaded.
devm_of_pwm_get
uses both approaches. Todo: give a working example usingdevm_of_pwm_get
. -
The module could be extended to a platform device driver which is listed in the Device Tree and available through sysfs. This unifies the resource access pattern and allows for automatic module loading on start-up.
Further Reading
- Jonathan Corbet; "The platform device API"; https://lwn.net/Articles/448499/
- Jonathan Corbet; "Platform devices and device trees"; https://lwn.net/Articles/448502/
- http://haifux.org/lectures/323/haifux-devres.pdf
- http://linuxseekernel.blogspot.de/2014/05/platform-device-driver-practical.html