Skip to content

Commit

Permalink
Waveform: fix significant jitter, that stresses servos and is clearly…
Browse files Browse the repository at this point in the history
… audible in Tone output (#7022)

* Allow 100% high or low periods.

Let output remain at current level on stopping instead of always turning to low.

* Fix serious jitter issues in previous versions.

* Use ESP.getCycleCount() just like everyone else.

* Highest timer rate at which this runs stable appears to be 2µs (500kHz).

* Guard for zero period length undefined waveforms.

Fix for zero duty or off cycles and expiring from them.

* Cycle precision for expiry instead of special treatment for 0 value.

* Give expiry proper precedence over updating a waveform

* Important comment

* Refactored, identical behavior.

* Use plural for bit arrays.

* Fix for completely duty or all off cycle period case.

* Expiration is explicitly relative to service time.

* Comment updated, here it's about cycles not usecs.

* Revert misconception of how waveformToEnable/Disable communicates with the NMI handler.

* Rewrite to keep phase in sync if period remains same during duty cycle change.

Refactor identifies to distinguish CPU clock cycle from waveform cycle.

* Rather iterate even if full-duty or no-duty cycle in period, than too many calculations in NMI handler.

* Must fire timer early to reach waveform deadlines, otherwise under some load aggressive jitter occurs.

* Schedule expiry explicitly, too.

Needed to keep track of next timer ccy in each iteration, not just when changing level.

* Quick change lets analogWrite keep phase for any duty cycle (including 0% and 100%).

* Set duration to multiple of period,  so tone stops on LOW pin output.

* Improve phase timing

* Eror causing next Timer IRQ to fail busy-to-off cycle transitions.

* Regression fix, don't reset timer if pending shortly.

* Rather reschedule ISR instead of busy looping during permitted maximum time.

* Lead time improved for ISR

* Reduce number of cycle calculations.

* Reactive the gcc optimize pragmas.

* Simplify calculation.

* handles overshoot where an updated period is shorter than the previous duty cycle

* Misleading code, there must ever be only one bit set at a time, start and stop block until the ISR has handled and reset the token.

* Prevent missing a duty cycle unless it is overshot already.

* Continuously remove distant pending waveform edges from the loop, continuously update now.

* Replace volatile for one-way exchange into ISR with memory fence.

* Remove redundant stack object.

* Revert pending waveform removal from loop - corrupts continuous next event computation.

* Reduce if/do ... while to while

* Convert relative timings to absolute.

* Relax waveform start to possibly cluster phases into same IRQ interval.

* max 12us in ISR seems to work best for servo/fan/led/tone combo test.

* Restructured code in ISR for expiration, this saves 36 byte IRAM, and improves PWM resolution.

* Simplified overshot detection and 0% / 100% duty cycle.

* Leave ISR early if rescheduling is more promising than busy-waiting until next edge.

* Stabilized timings.

* Prevent WDT under load.

* Use clock cycle resolution instead of us for analogWrite.

* Reduce idle calculations in ISR.

* Optimize in-ISR time.

* Support starting new waveform in phase with another running waveform.

* Align phase for analogWrite PWMs.

* Tune preshoot, add lost period fast forward.

* Adapt phase sync code from analogWrite to Servo

* Fix for going off 100% duty cycle period.

* Eschew obfuscation.

* Fixed logic for zero duty cycle.

* Determine generator quantum during same IRQ - this is better than timer resolution, but non-zero.

* Tune timings, fix write barriers and overshoot logic.

* Migrate Tone to waveform with CPU cycle precision

* Can do 60kHz PWM.

* Recalibrated timings after performance optimizations.

Initialize GPIO if needed.

* Fix regression for waveform runtime.

* Test cycle duration values for signed arithmetic safety.

* Performance tuning.

* Performance tweak, in-ISR quantum is now 1.12µs.

* Round up duration instead of down - possibly to zero, which means forever.

* Extend phase alignment with optional phase offset.

* Slightly better in-ISR quantum approximation for steadier increments.

* Waveform stopped by runtime limit in iSR doesn't deinit the timer, but stopWaveform refuses

to do anything if the waveform was stopped by runtime, either.

* Improved quantum correction code.

* Fix broken multi-wave generation.

* Aggregate GPIO output across inner loop. True phase sync, and now better performance.

* IRQ latency can be reduced from 2 to 1 us now, no WDT etc.

* Improved handling of complete idle cycle miss, progress directly into duty cycle.

* Recalibrated after latest changes and reverts.

* Overshoot compensation for duty cycle results in PWM milestone.

* Adjustments to duty/idle cycle to mitigate effects of floating duty cycle logic.

* Remove implicit condition from loop guard and fix timer restart duration

* Host all static globals in an anonymous static struct.

* Busy wait directly for next pending event and go to that pin.

* Record nextEventCcy in waveform struct to save a few cycles.

* Adapt duty cycle modification to only fix full duty and all idle cases.

* Remember next pin to operate between IRQs.

* Don't set pinMode each time on already running PWM or Tone.

* Remove quantum, correct irq latency from testing,reuse isr timeout from master et al

* Move updating "now" out of inner loop, prevents float between pins that are in phase lock.

* Merge init loop with action loop again.

* Adaptive PWM frequency and floating duty cycle.

* Predictive static frequency scaling.

* Dynamic frequency down-scaling

* Frequency scaling is only for PWM-like applications, anything needing real time duty cycles or frequency must be able to fail on overload.

* Conserve IRAM cache, resort to best effort.

* Directly scale frequency for all duty/all idle waves to reasonable maximum, reduces thrashing.

* Getting the math right beats permanently reducing PWM frequency.

* Rename identifier to help think about the problem.

* AutoPwm correction moved to correct location - after overshoot recalc - and allow limited duty floating

* Finish overshoot math fixes.

* First set pin mode, then digital write.

* Simplify calculations, fix non-autoPwm for servo use, where exact duty is needed, idle is elastic.

* Move wave initialization and modification outside the inner loop.

* Some optimizing.

* Updating "now" in the inner loop should lessen interference

* Finally get rid of volatile and use atomic thread fence memory barriers, great for ISR performance.

* Improved idle cycle overshoot mitigation.

* Improved duty cycle overshoot mitigation.

Case for investigation: 3% (shl 5) vs. 1.5% (shl 6), either less fuzz, but a few marked stray spots, or more fuzz, but no bumps in counter-PWM travel test.

* Move startPin etc. into common static struct

* Persist next event cycle across ISR invocations, like initPin was before.

* Recalibrated DELTAIRQ and IRQLATENCY. Tested @ 3x 40kHz PWM + 440Hz Tone

* CPU clock to Timer1 ccy correction must be dynamic even when BSP is compiled for fixed CPU clock.

* Corrected use of Timer1 registers and add rationale to Timer1 use in comment.

Recalibrate for improved frequence downscaling @ 80MHz and 160MHz.

* Let duty cycle overshoot correction depend on relative impact compareed to both period and duty.

* 80MHz/160MHz specific code can be compile-time selected in general, only NMI is affected by

apparent CPU frequency scaling in SDK code.

* Seems that removing the redudant resetting of edge interrupt mode shaves 0.5us off rearm latency.

* Recalibrated delta irq ccys.

* Off-by-one in 100% duty overshoot correction.

* Simple register writes.

* Memory fences checked and joining events into same loop iteration that are close to one another.

* Shorten progression when going off 100% duty.

* Code simplifications.

* Dynamically map pins out from in-ISR handling based on next event timing.

Major performance boost.

* Reverting maximum IRQ period to 10ms. This sets the wave reprogramming rate to 100Hz max.

* Revert recent change that is the most likely cause of reported PWM frequency drop regression.

* Much simplified overshoot mitigation code.

* Fixing overshoot mitigation, 3x 880Hz, 256 states now.

* Increase resolution by keeping reference time moving forward earlier.

* Mitigation logic for ESP8266 SDK boosting to 160MHz during some WiFi ops.

* Event timestamps are all recorded for compile-time CPU frequency, the timer ticks conversion

must be set at compile-time also. The SDK WiFi 160MHz boost mitigation temporarily handles
the CPU clock running twice as fast.

* Expired pins must not be checked for next event.

* Recalibrate after latest changes.

* Save a few bytes code.

* Guards are in place, so xor rather than and bitwise not.

* Reduce memory use.

* SDK boost to 160MHz may last across multiple ISR invocations, therefore adjust target ccy instead of ccount.

* Overshoot mitigation w/o PWM frequency change.

* New PWM overshoot mitigation code keeps frequency. Averages duty between consecutive periods.

* Small refactoring, remove code path that is never taken even at 3x25kHz/1023 PWM.

* Don't ever skip off duty, no matter if late or infinitely short.

* Shed speed-up code that didn't speed up things.

* Must always recompute new waveform.nextEventCcy if there is any busy pin.

* Break out of ISR if timespan to next event allows, instead of busy waiting and stealing CPU cycles from userland.

* Minor code simplification.

* Improve code efficiency.

* Improved performance of loop.

* Recalibrated.

* No positive effect of lead time inclusion was found during testing, remove this code.

Maximum period duration limit is implicit to timer, consider it documented constraint, don't runtime
check in ISR.

* Fix WDT when at 160MHz CPU clock the Timer1 is set below 1µs.

* Consolidate 160MHz constexpr check, finish 1µs minimum for Timer1 fix.

* Test for non-zero before subtract should improve performance.

* Reviewers/tested noted they were seeing WDT, and this change appeared to fix that.

* More expressive use of parentheses and alias CPU2X for reduced code size.

* Bug fix: at 160MHz compiled, don't force minimum Timer1 latency to 2µs.

* Alternate CPU frequency scaling mitigation.

* Handle time-of-flight in the right spot.

* Remove _toneMap from Tone.cpp

Co-authored-by: david gauchard <[email protected]>
  • Loading branch information
dok-net and d-a-v committed Nov 19, 2020
1 parent 8fe80f1 commit 0e735e3
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 212 deletions.
9 changes: 1 addition & 8 deletions cores/esp8266/Tone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@
#include "core_esp8266_waveform.h"
#include "user_interface.h"

// Which pins have a tone running on them?
static uint32_t _toneMap = 0;


static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) {
if (_pin > 16) {
return;
Expand All @@ -42,9 +38,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat
duration = microsecondsToClockCycles(duration * 1000UL);
duration += high + low - 1;
duration -= duration % (high + low);
if (startWaveformClockCycles(_pin, high, low, duration)) {
_toneMap |= 1 << _pin;
}
startWaveformClockCycles(_pin, high, low, duration);
}


Expand Down Expand Up @@ -86,6 +80,5 @@ void noTone(uint8_t _pin) {
return;
}
stopWaveform(_pin);
_toneMap &= ~(1 << _pin);
digitalWrite(_pin, 0);
}
Loading

0 comments on commit 0e735e3

Please sign in to comment.