joan2937 / lg

Linux C libraries and Python modules for manipulating GPIO
The Unlicense
67 stars 27 forks source link

Hardware PWM #11

Open scottwillmoore opened 3 years ago

scottwillmoore commented 3 years ago

First off, thank you for your work this is an awesome project.

I ask mostly out of curiosity, but would it be possible to support hardware PWM using the /dev/gpiochip interface?

Also, do you know if the software PWM has much of an impact on the performance of the CPU? I assume the CPU frequency is so high that the software PWM is has a mostly negligible impact on performance anyway, but would love to hear your opinion.

joan2937 commented 3 years ago

The /dev/gpiochip API has no support for PWM. Of course that may change in the future.

Note that PWM is usually implemented by a separate hardware block whose outputs are multiplexed to the pin outputs. I.e. like the typical SPI/I2C/serial implementations. These do not fall within the scope of /dev/gpiochip.

lg makes use of SPI, I2C, serial as Linux has exposed an API for these blocks. lg will be extended to support PWM If and when PWM support is added to Linux at the same level.

danfrist commented 9 months ago

It would be great to add hardware PWM, even though it uses a different interface. I've built my own solution, but I can't find a way to switch back and forth between using PWM pins as GPIOs and PWMs. Currently, if you use them as a GPIO through lgpio, they cannot be used as hardware PWM until after rebooting. It seems the kernel is reserving them for one or the other, or lgpio is not releasing them fully.

https://github.com/Sttark/linux_GPIO_scope/blob/main/pigpio_lgpio.py#L121

I know this is an old topic, but I had to switch over to using lg instead of pigpio because the new Pi 5 doesn't seem to support pigpio.

vickash commented 3 months ago

Currently, if you use them as a GPIO through lgpio, they cannot be used as hardware PWM until after rebooting.

I'm having the opposite problem. I can use the GPIO in lgpio, then start PWM with the sysfs interface, and that works. Once I use PWM though, the pin is no longer available to lgpio without a reboot, even if disabled and unexported in sysfs.

Did you find a solution?

easytarget commented 2 months ago

My (allwinner D1 based) SBC has 8 internal PWM timers (channels), and GPIO pins can be mapped to these via the device tree (overlay). The timers can then be controlled via sysfs.

I think the kernel PWM API is the 'missing link' @joan2937 is referring to above. With it in place a consistent interface can be provided. https://www.kernel.org/doc/html/latest/driver-api/pwm.html

PWM timer functions could be provided, giving control of the PWM timers themselves, but leaving pin assignment to the Device Tree & overlays.

(comment edited for brevity)

vickash commented 2 months ago

I agree it would be nice if everything were integrated into one API, including automatic mapping of PWM channels to their multiplexed GPIOs, but I just did it myself in the meantime. Here's the approach for a library I'm building on top of lgpio:

It's not perfect, but it gets the job done.

easytarget commented 2 months ago

@vickash : On my system pins need to be muxed to the PWM timer in the Device Tree (or overlay). They cannot be 'dynamically' reassigned or connected to the timer after boot. I'm not sure the scheme you describe is possible.

Because this is done in the DT the pin cannot be exported, read or set as a digital IO pin. (You can of course drive it High or Low by setting the duty cycle to 100% or 0%, but that has to be done by setting the timer parameters.)

vickash commented 2 months ago

@vickash : On my system pins need to be muxed to the PWM timer in the Device Tree (or overlay). They cannot be 'dynamically' reassigned or connected to the timer after boot. I'm not sure the scheme you describe is possible.

Because this is done in the DT the pin cannot be exported, read or set as a digital IO pin. (You can of course drive it High or Low by setting the duty cycle to 100% or 0%, but that has to be done by setting the timer parameters.)

Yes, I have the PWM pins set up with dtoverlays, but what I described just works on my Orange Pi Zero 2W board? 🤷‍♂️

Maybe it's something in the hardware implementation for the chip it uses, but it looks like the PWM channel doesn't get multiplexed to the pin until you try to use it, so you can get away using it as GPIO before that.

Yesterday I was testing out a Radxa Zero 3W board, and it does the opposite, exactly what @danfrist describes above. PWM works if I use that first, then once used in lgpio, I can't use PWM anymore.

So in short, the implementation is inconsistent, and you're right. Trying to switch between modes (even the "lazy" way I described above) is probably a bad idea. I have to rework my project to only do the 0/100% duty thing.

easytarget commented 2 months ago

in short, the implementation is inconsistent

Yes, it seem to depend on how the multiplexer is implemented in hardware and drivers; each SOC maker supplies a PWM driver, and the PWM api doc says:

As a consumer, don’t rely on the output’s state for a disabled PWM.

And does not mention multiplexing at all.

So it's not surprising that there are differences in how the pins are assigned; on my MQ Pro the pins are pinmuxed to their PWM channel at boot and not available for use by lgpio. Electrically; they are high-impedance until the channel is enabled.

easytarget commented 2 months ago

Eventually there will be a /dev tree based solution and lg/lgpio/gpiod support (I hope) but until then:

https://github.com/easytarget/pyPWMd

A daemon process that runs as root, and clients (commandline and python classlib) to talk to it as normal users and control the timers. You can control access to the timers, and it even has some real documentation and examples. Tested on both my MQ Pro and a RPi 3A, but should work on any linux system supporting the /sys/class/pwm/ tree