uraimo / SwiftyGPIO

A Swift library for hardware projects on Linux/ARM boards with support for GPIOs/SPI/I2C/PWM/UART/1Wire.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

PWM Output Reconfigures the Clock Too Often For Smooth Ramping of Duty Cycles

Kaiede opened this issue · comments

Board Type

Raspberry Pi Zero / 3B

Operating System

Raspian Stretch

Swift Version

Swift 3.1.1

Description

I've been using pigpio in Python to use the hardware PWM outputs to drive LEDs while also dimming them. That worked fine, and I got a nice smooth ramp from 0% to 100% duty cycle that looked good.

Because of perf issues with Python on the Zero with the rapid changes to the duty cycle needed, I gave SwiftyGPIO a shot, and it also works very well, but the behavior of killing and reconfiguring the clock every time the PWM frequency or dutycycle is changed causes unwanted flashing and flickering.

There also appears to be an issue where turning on one PWM channel turns the other off, possibly because of this clock change?

I've forked the repo so I can put together a revised implementation, which would be loosely based on how pigpio handles the clock, which makes this smooth updating of the duty cycle possible. The key difference is that pigpio configures the clock once with a fixed divisor of 2 for the PLLD clock. By using M/S mode, it can create any frequency up to (PLLD / 2) Hz without having to reinitialize the clock. My plan is to implement a similar behavior in the RaspberryPWM class where it can initialize the clock once for both PWM channels.

Unfortunately, if I wanted to avoid too many changes, it would probably require a lazy Singleton so that the different RaspberryPWM instances can store and share the clock state data independent of a specific pin/channel.

The PWM pattern code would be left alone if possible.

FYI, I put together a fix that addresses this by refactoring away the PHY pieces of RaspberryPWM into an unfortunately named RaspberryPHY class. This is a semi-singleton, which is passed into the RaspberryPWM during init, which uses it to get at the hardware register addresses rather than doing it itself.

My early testing says that the flicker from rapid changes to the duty cycle is gone (24, 50 and 100 times a second), and I can control both channels simultaneously, also without inducing flicker. I haven't tested the pattern writing side of it though. If you have guidelines on how to test that against regressions, I'll take a look.

There does seem to be some interesting interactions when I use 480Hz as my PWM freq, and updating rapidly with my test LED strip which causes some new form of flicker at around 6% duty cycle. So I'm not sure it's entirely ready, but it's still a big improvement from the behavior I was seeing last night.

Just wanted to give an update on this.

I’ve been using the changes for the last ~3 weeks to drive aquarium lighting without any real issues. The 480Hz issue seems to be limited to the strip I was using for testing and not my actual lighting. I’ve only been testing PWM frequencies up to about 3kHz and there is some code I know needs to be fixed around the highFreqSampleReduction behavior.

If you wouldn’t mind, I’d like your eyes on the pull request so we can discuss the changes more concretely?

There also appears to be an issue where turning on one PWM channel turns the other off, possibly because of this clock change?

Yes, every time a PWM is started at the moment the control register (PWM CTL) is cleared disabling the PWM if it's currently active. It shouldn't be possible to set a different set of frequency parameters for the second channel without stopping everything first.
So, your approach of keeping the DIV&co fixed and changing only the M/S parameter makes sense.

My idea at the time was to force the configuration of both signals at the same time if both were needed, but this was more the result of an hard requirement for multiple PWMPatterns than anything else (when the PWM engine is used in FIFO mode with more than one channel the data needs to be put in an interleaved fashion in the queue... so, both signals must be configured at the same time).

If I recall correctly (it was some time ago, should have documented the process ;) ) at the time the implementation with the DIV obtained from the desired frequency was the one that resulted in a better signal (from low to high frequencies, regardless of the size of the slots of the M/S algorithm and with all the cycles looking the same with somewhat minimal jitter) when checked with an oscilloscope.
The more problematic were low frequency signals with duty cycles hard to represent with few M/S slots.

Hmm, at 480Hz with a PLLD of 500MHz and a DIVI of 2, how many M/S slots are you using to generate 6%? It feels like it could be a value too big to be able to generate a stable signal.

Sure open it.

Hmm, at 480Hz with a PLLD of 500MHz and a DIVI of 2, how many M/S slots are you using to generate 6%? It feels like it could be a value too big to be able to generate a stable signal.

The timing would be 520,833 in range and 31,250 in data. It was interesting because it only occurs when doing a rapid ramp from 0% to 100% over about 5 seconds, with that particular light strip. If I hold the duty cycle in the offending range, it looks fine. Slow ramps through the range also work fine.

I'll be honest, I didn't think about this approach all that much, since I had been using pigpio prior to converting the project to Swift. But I also think I hadn't tried it with pigpio at 480Hz with that particular light strip. I was doing mostly 1.5-3kHz at that point with the hardware PWM.

So, I figured out that my code was very likely not calculating the range correctly, I've fixed it with a new commit in the PR. I think. I've got an oscilloscope on the way so I can confirm that it actually calculates the range properly.

But it does explain the odd behavior I was seeing. Asking for 480Hz and getting 120Hz would make flicker easier to see at lower duty cycles.

That said, I have kinda started working on a different library. I was looking to refactor some of the code to make it easier to maintain and understand what's happening at the lower levels, but it started to be clear that I would get a usable version of that refactor faster if I started clean and ported what I could instead.

I’ve confirmed the bug did exist, it was putting the frequency at 1/4th what it should have been, and confirmed that the latest commit in the PR outputs the expected frequency.