- Notifications
You must be signed in to change notification settings - Fork13.3k
Re-implement PWM generator logic#7231
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
Add special-purpose PWM logic to preserve alignment of PWM signals forthings like RGB LEDs.Keep a sorted list of GPIO changes in memory. At time 0 of the PWMcycle, set all pins to high. As time progresses bring down theadditional pins as their duty cycle runs out. This way all PWM signalsare time aligned by construction.This also reduces the number of PWM interrupts by up to 50%. Before,both the rising and falling edge of a PWM pin required an interrupt (andcould shift arround accordingly). Now, a single IRQ sets all PWM risingedges (so 1 no matter how many PWM pins) and individual interruptsgenerate the falling edges.The code favors duty cycle accuracy over PWM period accuracy (since PWMis simulating an analog voltage it's the %age of time high that's thecritical factor in most apps, not the refresh rate). Measurements giveit about 35% less total error over full range at 20khz than master.@me-no-dev used something very similar in the original PWM generator.
Use fixed point math to adjust running PWM channels to the newfrequency.
Copy over full high/low periods only on the falling edge of a cycle,ensuring phase alignment for Tone and Servo.
dok-net commentedApr 19, 2020
Here is an unsolicited test, for comparing against PR#7022,#7022 (comment): |
earlephilhower commentedApr 19, 2020
Thanks! Can you give test parameters for the above graph? |
earlephilhower commentedApr 19, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
-edit: Superseded below. This graph had 6 total PWMs running...
Test Master (yes, it's in PowerShell for now) |
s-hadinger commentedApr 20, 2020
As stated, I confirm there is no more visible flickering with leds in Tasmota. Any chance this PR is integrated in v2.7? |
earlephilhower commentedApr 20, 2020
With multiple (different) PWMs there's the non-linear bumps you see on the graph in#7231 (comment) (kind of a worst-case test where we have the PWM periods going in opposite directions). I wrote it literally during a bout of insomnia yesterday AM and haven't optimized it at all. There's also#7022 which@dok-net has worked hard on and which he seems to get better performance for his app from. It does show better linearity than this one, on the little logic analyzer I'm using, looked pretty stable, too, but I've not tried 3 channels or other stuff yet. Net result, it's very complicated. You can always merge this PR into your own master before building if needed, while we try and figure out the best solution... |
Ensure both the general purpose waveform generator and the PWM generatorare disabled on a pin used for Tone/digitalWrite.
earlephilhower commentedApr 20, 2020
dok-net commentedApr 20, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@earlephilhower I found that aggregating the GPIO changes and writing to the two set and clear registers just once each, had overall worse performance, due to the added code for collecting state, I guess. While it does give perfect phase sync, the performance impact was unacceptable. I've reverted this now in PR#7202. |
dok-net commentedApr 20, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@earlephilhower We additionally need performance figures - I take it you were speaking from experience, not hard numbers. And built binary memory sizes. Can you please look into how you'll get well defined 0V and Vmax bands? I derive from the images that those are maintained only for exactly 0 and 1023, if at all. |
earlephilhower commentedApr 20, 2020
Thanks for the screenshots! Jitter's actually recorded and in the CSV/XLS. I just didn't graph it to keep things simple. With the jitter, you can't see the average value which it sawtooth in#7022, too, so I don't think there's a good single answer. Here's what he HW recorded. I took the std.dev of the high period (i.e. duty cycle jitter) and divided by the average period reported over the ~100 samples it examined. I can measure anything we need if the analyzer can record it and I can teach PS or Python how to look for it, no sweat! For 0 and 100%, I'm not sure I understand you. Aren't those special-cased as digitalWrite(0/1)? That's what's in master now and in this PR (although, as you noticed, there was a bug and that analogWrite(100%)->digitalWrite(1) mapping was busted...it's fixed with last push) |
earlephilhower commentedApr 20, 2020
Oh, and it's very cool that your performance shows the same dip at about the same time (the bump on the 1 pin has a corresponding dip on the other pin, so I guess we're just looking at different pins of the pair)! Independent verification is always best. |
dok-net commentedApr 20, 2020
@earlephilhower I don't know what commit 160435d was exactly, but since I've rebased and squashed a serious bug fix and a commit (GPOS and GPOC use) that was so bad in performance that wanted to get rid of that completely, it just may be that you are still testing that badly badly broken revision!!! Please rerun and probably take down your graphs with that revision... I'm sorry. |
earlephilhower commentedApr 20, 2020
@dok-net "106-435d" is "160mhz, commit 435d." It's the last force-push. Do you have unpushed commits, maybe? |
dok-net commentedApr 20, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@earlephilhower 0 and 1023, as digitalWrite(…) etc. in master, is one of the things that got me started on#7022, because that totally loses phase. Let me think, try switching off servos perfectly with your code such that they don't ever sweep into nirvana on detach. My hope is still to use waveform for communications signal generation, like software serial. For that, exact level on exit from a wave defined by precise runtime is a must, such as to finish a byte on stop, which is high, not low. You see, the features I've built in are what I'm fighting for here, your PR wouldn't cut it for those (imaged) uses (just yet). My worry is that this competion, where you keep merging small portions, are going to drive me nuts rebasing or merging my code - probably I'll have to keep forcing my stuff over everything, which ends up badly, too, if that goes on for too long. |
dok-net commentedApr 20, 2020
@earlephilhower OMG, 160 "-" 435d - I'm sorry, your graphics is slightly blurry and small, I thought it was 160435d :-) :-) :-) So, 435d5d8fd750c6c5631244ee0592b42fd24421bb is just right. |
earlephilhower commentedApr 21, 2020
Ah, I see. For master and this PR, then So I think I get:
|
dok-net commentedApr 21, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
#7022 maintains phase if the waveform generation is running, but with either zero duty or 100% duty. Also, the runtime parameter exits on the exact level at that time, I am sure anymore, but master at least always switches back to LOW, which is not what I expect. |
dok-net commentedApr 21, 2020
And, you haven't picked that up yet, while I have removed ns-precise phase alignment due to the overall performance issue with that, one can specify phase delay to another previously started wave, so you could generate three waves at 120° phase shift to each other, for instance :-) |
earlephilhower commentedApr 21, 2020
A hump in the dueling PWMs was very prominent in prior pulls.The hump was caused by having a PWM falling edge just before the cyclerestart, while having the other channel requesting a 1->0 transitionjust outside the busy-loop window of 10us. So it gets an IRQ for channelB 0->1, then waits 2..8us for the next PWM full cycle 0->1, and ends upreturning from interrupt and not scheduling another IRQ for 10us...hencethe horizontal leg of the bump...Reduce the minimum IRQ latency a little bit to minimize this effect.There will still be a (significantly smaller) hump when things cross, butit won't be anywhere near as bad or detectable.
earlephilhower commentedApr 21, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
earlephilhower commentedApr 21, 2020
Results in much closer PWM frequency range over any number of PWM pins,while taking 0 add'l overhead in IRAM or in the IRQ.
When _setPWMFreq was called the initial PWM mask was not set to 0leading to occasional issues where non-PWM pins would be set to 1on the nextPWM cycle. Manifested itself as an overtone at the PWMfrequency +/-.
Borrow a trick fromesp8266#7022 to exit the busy loop when the next event istoo far out. Also reduce the IRQ delta subtraction because it wasinitially not NMI so there was much more variation than now.Keep the PWM state machine active at a higher prio than the standardtone generation when the next edge is very close (i.e. when we're atthe max or min of the range and have 2 or more near edges). Adds alot of resolution to the response at low and high ranges.Go from relative to absolute cycle counts in the main IRQ loop so thatwe don't mingle delta-cycles when the delta start was significantlydifferent.
Keep PWM error <2.0% on entire range, from 0-100%, and remove thehump seen in testC by fixing the min IRQ delay setting.
The IRQ lead time was a tiny bit undersized, causing IRQs to come backtoo late for about .25us worth of PWM range. Adjust the constantaccordingly
Since the adjustment for when a 160mhz compile is running at 80mhzis giving bad behavior, simply remove it and revert to old behavior.
earlephilhower commentedAug 23, 2020
Updated to new PWMRANGE, but not yet tested with new core/GCC |
BigMike71 commentedAug 31, 2020
Hello, with the latest versions of tasmota (lite) 8.4.x I have strong visible flickering again with my LED lamps at low brightness! |
s-hadinger commentedAug 31, 2020
@BigMike71 Tasmota currently uses code from#7022. |
Jason2866 commentedOct 16, 2020
@BigMike71 Tasmota is back to#7231 we had sometimes execptions when using PWMi with#7022 Using#7231 there is no flicker and no execption. |
Includes patches to allow side-by-side existance of the two versions anda slight change such that the #define ..._PWM is unneeded (i.e. allowexisting makefiles/scripts/etc. to get expected behavior w/o any changeson their part).








Add special-purpose PWM logic to preserve alignment of PWM signals for
things like RGB LEDs.
Keep a sorted list of GPIO changes in memory. At time 0 of the PWM
cycle, set all pins to high. As time progresses bring down the
additional pins as their duty cycle runs out. This way all PWM signals
are time aligned by construction.
This also reduces the number of PWM interrupts by up to 50%. Before,
both the rising and falling edge of a PWM pin required an interrupt (and
could shift arround accordingly). Now, a single IRQ sets all PWM rising
edges (so 1 no matter how many PWM pins) and individual interrupts
generate the falling edges.
The code favors duty cycle accuracy over PWM period accuracy (since PWM
is simulating an analog voltage it's the %age of time high that's the
critical factor in most apps, not the refresh rate). Measurements give
it about 35% less total error over full range at 20khz than master.
@me-no-dev used something very similar in the original PWM generator.