Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PWM no work on all gpio pins? #20

Open
PeerXu opened this issue May 31, 2018 · 9 comments
Open

PWM no work on all gpio pins? #20

PeerXu opened this issue May 31, 2018 · 9 comments

Comments

@PeerXu
Copy link

PeerXu commented May 31, 2018

go-rpio/rpio.go

Line 245 in f6236e5

case 12, 13, 40, 41, 45:

I try to use python with gpio in other pins (not only in above pins).

Why limit pins in the code?

Thanks.

@drahoslove
Copy link
Collaborator

drahoslove commented May 31, 2018

This is how it is limited by hardware implementation, see BCM2835-ARM-Peripherals - section 6.2

Your python library probably uses only software implementation for pwm on other pins. This means it actually uses regular output mode and some software timers. You can easily implement this by yourself in Go.

Problem is you can reliably achieve at most few kHz with software implementation. Which is like 10-1000× slower compared to hw pwm/clock mode.

And the accuracy of software timer is only about 5 to 50 microseconds at best. So even for slower frequences, if you need very precise output, yo have to use real pwm mode.

@PeerXu
Copy link
Author

PeerXu commented Jun 1, 2018

Thanks for reply.

But I look into python library RPi.GPIO, it is use /dev/gpiomem as same as go-rpio.

And not found any software implementation for pwm on other pins.

Other library pi-blaster enable pins as go-rpio. But it can be enable other pins by arguments (and not found any software implementation for pwm).

@stianeikeland
Copy link
Owner

From the project description of rpi.gpio:

Although hardware PWM is not available yet, software PWM is available to use on all channels.

@drahoslove
Copy link
Collaborator

drahoslove commented Jun 2, 2018

@PeerXu You are kind of right with pi-blaster. I went through the source code and it is interesting how it works.

In my eyes it lays somewhere between pure hw implementation as in go-rpio or wiringPi and sw based implementation as in RPi.GPIO and similar libs.

If I understand it well: it uses output mode instead of pwm, but the output value of pins is flipped using hw direct memmory access functionality and the timing is done by internal clock, so it does not waste much CPU and is pretty effective. Hovewer I think it is probably not as precise as our implementation and it is also pretty complex to implement, so I don't believe this will be implemented in go-rpio any time soon. Only advantage I see is more pwm pins.

If you need to use more pwm pins than go-rpio supports I'd suggest you to use pi-blaster, or if you don't need high accuracy and high frequences, you can implement custom sw pwm by yourself.

@stianeikeland Maybe there should be also software implementation of pwm in go-rpio? What do you think, does it makes sense, or should go-rpio be only low-level based as it is now?

@PeerXu
Copy link
Author

PeerXu commented Jun 4, 2018

Thanks again!

I am using pi-blaster now!

@stianeikeland
Copy link
Owner

@Drahoslav7 If someone is willing to put in the time for a decent and easy to use software PWM, then by all means, I'm open for it. The API just needs to make it very clear that this is software PWM, and the other one is hardware PWM :)

@morphar
Copy link

morphar commented Mar 27, 2022

I just want to share my experience with software PWM, in case it can help somebody else.

I am running a Raspberry PI 3 with a DAC hat and wanted to remote control my AMP with an IR LED.
Unfortunately the DAC uses both hardware PWMs, so those were out of the question.
The RC5 spec defines the signal as: 36kHz with duty cycle at 25%, which requires a precision of 144kHz or ~7 µs.
Trying to do this with native time.Sleep is impossible as it can easily fluctuate several µs.

As @drahoslove mentioned, software timers are slow and extremely unreliable.
I narrowed it down to time.Now() being the actual issue, which is used by time.Sleep.
The promise in Go is that time.Sleep will sleep at least the requested duration, but the possible extra time it spends varies a lot.

So with inspiration from a Rust library (spin_sleep), I created a more precise sleep for Go: powernap.
It trades CPU cycles for precision, but tries to use native time.Sleep for a safe part of the duration, when possible.

To get the fastest and most reliable result, I build in a scheduler (Plan), that can be used to add a function with a pre-calculated sleep durations.
When the plan is run, it will try to get as close as possible to the requested durations and execute the functions at the pre-calculated time.

This way, I'm able to pre-calculate a RC5(x) signal and build a plan for switching the GPIO pin on or off at the correct intervals.
So far this works fine on my Raspberry Pi 3 and it seems like the deviations is ~400 ns.
So depending on the tolerance of deviations, this seems to be good enough for µs precision.

I would expect it to get too imprecise before getting to 1MHz, but for the 144kHz signal through an LED, it works quite good.

One thing to notice, is that the "safe" zone for using native time.Sleep is way up in the millisecond zone (below 1kHz), which causes CPU cycles to be traded for precision constantly.
So expect close to 100% utilization of one core, while "sleeping" :)

Maybe it's possible to build an actual, decent soft PWM from this?

Hope these findings can help others.

@youngkin
Copy link
Contributor

youngkin commented Mar 27, 2022

Interestingly enough, there's already a PR, #24 , for this. The author just hasn't addressed the review comments.

@morphar interesting work! Software PWM would be a good addition to this library. If you'd like to have a go :) at it you could start with an implementation I did in another project.

A super minor comment. In the init() function in helpers.go, you can change

	for i := 0; i < runs; i++ {
		tmp := time.Now()
		_ = tmp
	}

to this

	for i := 0; i < runs; i++ {
		time.Now()
	}

Anyway, I'm going to keep powermap in the back of my mind as it looks very useful.

@morphar
Copy link

morphar commented Mar 27, 2022

Thanks @youngkin :)
And good catch! I will fix that in a couple of minutes :)
I actually hadn't seen #24 for some reason, though I have already gone through most of the code and some of the pull requests and issues to get a better understanding of how things works, what's possible and where there's pitfalls.

When I get some time, I will probably have a go :) at the soft PWM. This was just an initial PoC to see if it was possible and not a general purpose PWM.
I want to get a small streaming server up and running, so I will get back to this soon.

I think that the implementation you did, will probably "just work", if you replace the sleep with powernap.Sleep.
It seems that everything starts to stabilize around +1 microseconds - even better my initial real-world usage shows promising uniformity in the actual execution.
So even if there is ~400 ns diff in the expected execution time, that diff dosen't seem to change or slip.
This is when the powernap.Plan is used - so in theory the actual signal is probably extremely uniform, though the Plan might have started a bit to soon or late (~400ns).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants