Change PWM updated to the latest version of Arduino PR #7231

This commit is contained in:
Stephan Hadinger 2020-05-08 17:52:24 +02:00
parent f66a0ee561
commit 87a1cd0ea0
4 changed files with 198 additions and 207 deletions

View File

@ -10,6 +10,7 @@
- Change HAss discovery by Federico Leoni (#8370) - Change HAss discovery by Federico Leoni (#8370)
- Change default PWM Frequency to 977 Hz from 223 Hz - Change default PWM Frequency to 977 Hz from 223 Hz
- Change minimum PWM Frequency from 100 Hz to 40 Hz - Change minimum PWM Frequency from 100 Hz to 40 Hz
- Change PWM updated to the latest version of Arduino PR #7231
### 8.2.0.5 20200425 ### 8.2.0.5 20200425

View File

@ -47,29 +47,23 @@
extern "C" { extern "C" {
// Internal-only calls, not for applications // Internal-only calls, not for applications
extern void _setPWMPeriodCC(uint32_t cc); extern void _setPWMFreq(uint32_t freq);
extern bool _stopPWM(int pin); extern bool _stopPWM(int pin);
extern bool _setPWM(int pin, uint32_t cc); extern bool _setPWM(int pin, uint32_t val, uint32_t range);
extern int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); extern int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
// Maximum delay between IRQs // Maximum delay between IRQs
#define MAXIRQUS (10000) #define MAXIRQUS (10000)
// Set/clear GPIO 0-15 by bitmask
#define SetGPIO(a) do { GPOS = a; } while (0)
#define ClearGPIO(a) do { GPOC = a; } while (0)
// Waveform generator can create tones, PWM, and servos // Waveform generator can create tones, PWM, and servos
typedef struct { typedef struct {
uint32_t nextServiceCycle; // ESP cycle timer when a transition required uint32_t nextServiceCycle; // ESP cycle timer when a transition required
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
uint32_t timeHighCycles; // Currently running waveform period uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
uint32_t timeLowCycles; // uint32_t timeLowCycles; //
uint32_t desiredHighCycles; // Currently running waveform period uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
uint32_t desiredLowCycles; // uint32_t desiredLowCycles; //
uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase uint32_t lastEdge; // Cycle when this generator last changed
uint32_t gotoTimeLowCycles; //
uint32_t lastEdge; //
} Waveform; } Waveform;
class WVFState { class WVFState {
@ -82,7 +76,7 @@ public:
uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
int32_t waveformToChange = -1; uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
uint32_t waveformNewHigh = 0; uint32_t waveformNewHigh = 0;
uint32_t waveformNewLow = 0; uint32_t waveformNewLow = 0;
@ -91,8 +85,8 @@ public:
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we // Optimize the NMI inner loop by keeping track of the min and max GPIO that we
// are generating. In the common case (1 PWM) these may be the same pin and // are generating. In the common case (1 PWM) these may be the same pin and
// we can avoid looking at the other pins. // we can avoid looking at the other pins.
int startPin = 0; uint16_t startPin = 0;
int endPin = 0; uint16_t endPin = 0;
}; };
static WVFState wvfState; static WVFState wvfState;
@ -107,7 +101,7 @@ static WVFState wvfState;
static ICACHE_RAM_ATTR void timer1Interrupt(); static ICACHE_RAM_ATTR void timer1Interrupt();
static bool timerRunning = false; static bool timerRunning = false;
static void initTimer() { static __attribute__((noinline)) void initTimer() {
if (!timerRunning) { if (!timerRunning) {
timer1_disable(); timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
@ -138,21 +132,22 @@ static ICACHE_RAM_ATTR void forceTimerInterrupt() {
constexpr int maxPWMs = 8; constexpr int maxPWMs = 8;
// PWM machine state // PWM machine state
typedef struct { typedef struct PWMState {
uint32_t mask; // Bitmask of active pins uint32_t mask; // Bitmask of active pins
uint32_t cnt; // How many entries uint32_t cnt; // How many entries
uint32_t idx; // Where the state machine is along the list uint32_t idx; // Where the state machine is along the list
uint8_t pin[maxPWMs + 1]; uint8_t pin[maxPWMs + 1];
uint32_t delta[maxPWMs + 1]; uint32_t delta[maxPWMs + 1];
uint32_t nextServiceCycle; // Clock cycle for next step uint32_t nextServiceCycle; // Clock cycle for next step
struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
} PWMState; } PWMState;
static PWMState pwmState; static PWMState pwmState;
static PWMState *pwmUpdate = nullptr; // Set by main code, cleared by ISR static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000;
static uint32_t pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000;
// If there are no more scheduled activities, shut down Timer 1.
// Otherwise, do nothing.
static ICACHE_RAM_ATTR void disableIdleTimer() { static ICACHE_RAM_ATTR void disableIdleTimer() {
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
@ -162,62 +157,78 @@ static ICACHE_RAM_ATTR void disableIdleTimer() {
} }
} }
// Notify the NMI that a new PWM state is available through the mailbox.
// Wait for mailbox to be emptied (either busy or delay() as needed)
static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
p->pwmUpdate = nullptr;
pwmState.pwmUpdate = p;
MEMBARRIER();
forceTimerInterrupt();
while (pwmState.pwmUpdate) {
if (idle) {
delay(0);
}
MEMBARRIER();
}
}
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
// Called when analogWriteFreq() changed to update the PWM total period // Called when analogWriteFreq() changed to update the PWM total period
void _setPWMPeriodCC(uint32_t cc) { void _setPWMFreq(uint32_t freq) {
if (cc == pwmPeriod) { // Convert frequency into clock cycles
return; uint32_t cc = microsecondsToClockCycles(1000000UL) / freq;
// Simple static adjustment to bring period closer to requested due to overhead
#if F_CPU == 80000000
cc -= microsecondsToClockCycles(2);
#else
cc -= microsecondsToClockCycles(1);
#endif
if (cc == _pwmPeriod) {
return; // No change
} }
_pwmPeriod = cc;
if (pwmState.cnt) { if (pwmState.cnt) {
// Adjust any running ones to the best of our abilities by scaling them
// Used FP math for speed and code size
uint64_t oldCC64p0 = ((uint64_t)pwmPeriod);
uint64_t newCC64p16 = ((uint64_t)cc) << 16;
uint64_t ratio64p16 = (newCC64p16 / oldCC64p0);
PWMState p; // The working copy since we can't edit the one in use PWMState p; // The working copy since we can't edit the one in use
p = pwmState; p.cnt = 0;
uint32_t ttl = 0; for (uint32_t i = 0; i < pwmState.cnt; i++) {
for (uint32_t i = 0; i < p.cnt; i++) { auto pin = pwmState.pin[i];
uint64_t val64p16 = ((uint64_t)p.delta[i]) << 16; _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
uint64_t newVal64p32 = val64p16 * ratio64p16;
p.delta[i] = newVal64p32 >> 32;
ttl += p.delta[i];
} }
p.delta[p.cnt] = cc - ttl; // Final cleanup exactly cc total cycles
// Update and wait for mailbox to be emptied // Update and wait for mailbox to be emptied
pwmUpdate = &p; initTimer();
MEMBARRIER(); _notifyPWM(&p, true);
forceTimerInterrupt(); disableIdleTimer();
while (pwmUpdate) {
delay(0);
// No mem barrier. The external function call guarantees it's re-read
}
} }
pwmPeriod = cc;
} }
// Helper routine to remove an entry from the state machine // Helper routine to remove an entry from the state machine
static ICACHE_RAM_ATTR void _removePWMEntry(int pin, PWMState *p) { // and clean up any marked-off entries
uint32_t i; static void _cleanAndRemovePWM(PWMState *p, int pin) {
uint32_t leftover = 0;
// Find the pin to pull out... uint32_t in, out;
for (i = 0; p->pin[i] != pin; i++) { /* no-op */ } for (in = 0, out = 0; in < p->cnt; in++) {
auto delta = p->delta[i]; if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) {
p->pin[out] = p->pin[in];
// Add the removed previous pin delta to preserve absolute position p->delta[out] = p->delta[in] + leftover;
p->delta[i+1] += delta; leftover = 0;
out++;
// Move everything back one } else {
for (i++; i <= p->cnt; i++) { leftover += p->delta[in];
p->pin[i-1] = p->pin[i]; p->mask &= ~(1<<p->pin[in]);
p->delta[i-1] = p->delta[i]; }
} }
// Remove the pin from the active list p->cnt = out;
p->mask &= ~(1<<pin); // Final pin is never used: p->pin[out] = 0xff;
p->cnt--; p->delta[out] = p->delta[in] + leftover;
} }
// Called by analogWrite(0/100%) to disable PWM on a specific pin
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
ICACHE_RAM_ATTR bool _stopPWM(int pin) { ICACHE_RAM_ATTR bool _stopPWM(int pin) {
if (!((1<<pin) & pwmState.mask)) { if (!((1<<pin) & pwmState.mask)) {
return false; // Pin not actually active return false; // Pin not actually active
@ -225,67 +236,88 @@ ICACHE_RAM_ATTR bool _stopPWM(int pin) {
PWMState p; // The working copy since we can't edit the one in use PWMState p; // The working copy since we can't edit the one in use
p = pwmState; p = pwmState;
_removePWMEntry(pin, &p);
// Update and wait for mailbox to be emptied // In _stopPWM we just clear the mask but keep everything else
pwmUpdate = &p; // untouched to save IRAM. The main startPWM will handle cleanup.
MEMBARRIER(); p.mask &= ~(1<<pin);
forceTimerInterrupt(); if (!p.mask) {
while (pwmUpdate) { // If all have been stopped, then turn PWM off completely
MEMBARRIER(); p.cnt = 0;
/* Busy wait, could be in ISR */
} }
// Update and wait for mailbox to be emptied, no delay (could be in ISR)
_notifyPWM(&p, false);
// Possibly shut down the timer completely if we're done // Possibly shut down the timer completely if we're done
disableIdleTimer(); disableIdleTimer();
return true; return true;
} }
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
bool _setPWM(int pin, uint32_t cc) { // Stash the val and range so we can re-evaluate the fraction
stopWaveform(pin); // should the user change PWM frequency. This allows us to
PWMState p; // Working copy // give as great a precision as possible. We know by construction
p = pwmState; // that the waveform for this pin will be inactive so we can borrow
// Get rid of any entries for this pin // memory from that structure.
if ((1<<pin) & p.mask) { wvfState.waveform[pin].desiredHighCycles = val; // Numerator == high
_removePWMEntry(pin, &p); wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low
uint32_t cc = (_pwmPeriod * val) / range;
if (cc == 0) {
_stopPWM(pin);
digitalWrite(pin, LOW);
return;
} else if (cc >= _pwmPeriod) {
_stopPWM(pin);
digitalWrite(pin, HIGH);
return;
} }
// And add it to the list, in order
if (p.cnt >= maxPWMs) { if (p.cnt == 0) {
return false; // No space left
} else if (p.cnt == 0) {
// Starting up from scratch, special case 1st element and PWM period // Starting up from scratch, special case 1st element and PWM period
p.pin[0] = pin; p.pin[0] = pin;
p.delta[0] = cc; p.delta[0] = cc;
p.pin[1] = 0xff; // Final pin is never used: p.pin[1] = 0xff;
p.delta[1] = pwmPeriod - cc; p.delta[1] = _pwmPeriod - cc;
p.cnt = 1;
p.mask = 1<<pin;
} else { } else {
uint32_t ttl=0; uint32_t ttl = 0;
uint32_t i; uint32_t i;
// Skip along until we're at the spot to insert // Skip along until we're at the spot to insert
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) { for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
ttl += p.delta[i]; ttl += p.delta[i];
} }
// Shift everything out by one to make space for new edge // Shift everything out by one to make space for new edge
memmove(&p.pin[i + 1], &p.pin[i], (1 + p.cnt - i) * sizeof(p.pin[0])); for (int32_t j = p.cnt; j >= (int)i; j--) {
memmove(&p.delta[i + 1], &p.delta[i], (1 + p.cnt - i) * sizeof(p.delta[0])); p.pin[j + 1] = p.pin[j];
p.delta[j + 1] = p.delta[j];
}
int off = cc - ttl; // The delta from the last edge to the one we're inserting int off = cc - ttl; // The delta from the last edge to the one we're inserting
p.pin[i] = pin; p.pin[i] = pin;
p.delta[i] = off; // Add the delta to this new pin p.delta[i] = off; // Add the delta to this new pin
p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
p.cnt++; }
p.mask |= 1<<pin; p.cnt++;
p.mask |= 1<<pin;
}
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
bool _setPWM(int pin, uint32_t val, uint32_t range) {
stopWaveform(pin);
PWMState p; // Working copy
p = pwmState;
// Get rid of any entries for this pin
_cleanAndRemovePWM(&p, pin);
// And add it to the list, in order
if (p.cnt >= maxPWMs) {
return false; // No space left
} }
_addPWMtoList(p, pin, val, range);
// Set mailbox and wait for ISR to copy it over // Set mailbox and wait for ISR to copy it over
pwmUpdate = &p;
MEMBARRIER();
initTimer(); initTimer();
forceTimerInterrupt(); _notifyPWM(&p, true);
while (pwmUpdate) { disableIdleTimer();
delay(0);
}
return true; return true;
} }
@ -311,22 +343,22 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time
uint32_t mask = 1<<pin; uint32_t mask = 1<<pin;
MEMBARRIER(); MEMBARRIER();
if (wvfState.waveformEnabled & mask) { if (wvfState.waveformEnabled & mask) {
wvfState.waveformNewHigh = timeHighCycles; // Make sure no waveform changes are waiting to be applied
wvfState.waveformNewLow = timeLowCycles; while (wvfState.waveformToChange) {
MEMBARRIER();
wvfState.waveformToChange = pin;
while (wvfState.waveformToChange >= 0) {
delay(0); // Wait for waveform to update delay(0); // Wait for waveform to update
// No mem barrier here, the call to a global function implies global state updated // No mem barrier here, the call to a global function implies global state updated
} }
wvfState.waveformNewHigh = timeHighCycles;
wvfState.waveformNewLow = timeLowCycles;
MEMBARRIER();
wvfState.waveformToChange = mask;
// The waveform will be updated some time in the future on the next period for the signal
} else { // if (!(wvfState.waveformEnabled & mask)) { } else { // if (!(wvfState.waveformEnabled & mask)) {
wave->timeHighCycles = timeHighCycles; wave->timeHighCycles = timeHighCycles;
wave->desiredHighCycles = timeHighCycles;
wave->timeLowCycles = timeLowCycles; wave->timeLowCycles = timeLowCycles;
wave->desiredHighCycles = wave->timeHighCycles; wave->desiredLowCycles = timeLowCycles;
wave->desiredLowCycles = wave->timeLowCycles;
wave->lastEdge = 0; wave->lastEdge = 0;
wave->gotoTimeHighCycles = wave->timeHighCycles;
wave->gotoTimeLowCycles = wave->timeLowCycles; // Actually set the pin high or low in the IRQ service to guarantee times
wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
wvfState.waveformToEnable |= mask; wvfState.waveformToEnable |= mask;
MEMBARRIER(); MEMBARRIER();
@ -355,10 +387,10 @@ void setTimer1Callback(uint32_t (*fn)()) {
// Speed critical bits // Speed critical bits
#pragma GCC optimize ("O2") #pragma GCC optimize ("O2")
// Normally would not want two copies like this, but due to different // Normally would not want two copies like this, but due to different
// optimization levels the inline attribute gets lost if we try the // optimization levels the inline attribute gets lost if we try the
// other version. // other version.
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
uint32_t ccount; uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); __asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
@ -380,8 +412,13 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
} }
// If user sends in a pin >16 but <32, this will always point to a 0 bit // If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false // If they send >=32, then the shift will result in 0 and it will also return false
if (wvfState.waveformEnabled & (1UL << pin)) { uint32_t mask = 1<<pin;
wvfState.waveformToDisable = 1UL << pin; if (wvfState.waveformEnabled & mask) {
wvfState.waveformToDisable = mask;
// Cancel any pending updates for this waveform, too.
if (wvfState.waveformToChange & mask) {
wvfState.waveformToChange = 0;
}
forceTimerInterrupt(); forceTimerInterrupt();
while (wvfState.waveformToDisable) { while (wvfState.waveformToDisable) {
MEMBARRIER(); // If it wasn't written yet, it has to be by now MEMBARRIER(); // If it wasn't written yet, it has to be by now
@ -407,15 +444,6 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
#define adjust(x) ((x) >> (turbo ? 0 : 1)) #define adjust(x) ((x) >> (turbo ? 0 : 1))
#endif #endif
#define ENABLE_ADJUST // Adjust takes 36 bytes
#define ENABLE_FEEDBACK // Feedback costs 68 bytes
#define ENABLE_PWM // PWM takes 160 bytes
#ifndef ENABLE_ADJUST
#undef adjust
#define adjust(x) (x)
#endif
static ICACHE_RAM_ATTR void timer1Interrupt() { static ICACHE_RAM_ATTR void timer1Interrupt() {
// Flag if the core is at 160 MHz, for use by adjust() // Flag if the core is at 160 MHz, for use by adjust()
@ -435,19 +463,12 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
#ifdef ENABLE_PWM } else if (!pwmState.cnt && pwmState.pwmUpdate) {
} else if (!pwmState.cnt && pwmUpdate) {
// Start up the PWM generator by copying from the mailbox // Start up the PWM generator by copying from the mailbox
pwmState.cnt = 1; pwmState.cnt = 1;
pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
// No need for mem barrier here. Global must be written by IRQ exit // No need for mem barrier here. Global must be written by IRQ exit
#endif
} else if (wvfState.waveformToChange >= 0) {
wvfState.waveform[wvfState.waveformToChange].gotoTimeHighCycles = wvfState.waveformNewHigh;
wvfState.waveform[wvfState.waveformToChange].gotoTimeLowCycles = wvfState.waveformNewLow;
wvfState.waveformToChange = -1;
// No need for memory barrier here. The global has to be written before exit the ISR.
} }
bool done = false; bool done = false;
@ -455,35 +476,32 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
do { do {
nextEventCycles = microsecondsToClockCycles(MAXIRQUS); nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
#ifdef ENABLE_PWM
// PWM state machine implementation // PWM state machine implementation
if (pwmState.cnt) { if (pwmState.cnt) {
uint32_t now = GetCycleCountIRQ(); int32_t cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
int32_t cyclesToGo = pwmState.nextServiceCycle - now;
if (cyclesToGo < 0) { if (cyclesToGo < 0) {
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
if (pwmUpdate) { if (pwmState.pwmUpdate) {
// Do the memory copy from temp to global and clear mailbox // Do the memory copy from temp to global and clear mailbox
pwmState = *(PWMState*)pwmUpdate; pwmState = *(PWMState*)pwmState.pwmUpdate;
pwmUpdate = nullptr; }
} GPOS = pwmState.mask; // Set all active pins high
GPOS = pwmState.mask; // Set all active pins high if (pwmState.mask & (1<<16)) {
// GPIO16 isn't the same as the others GP16O = 1;
if (pwmState.mask & (1<<16)) { }
GP16O = 1; pwmState.idx = 0;
}
pwmState.idx = 0;
} else { } else {
do { do {
// Drop the pin at this edge // Drop the pin at this edge
GPOC = 1<<pwmState.pin[pwmState.idx]; if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) {
// GPIO16 still needs manual work GPOC = 1<<pwmState.pin[pwmState.idx];
if (pwmState.pin[pwmState.idx] == 16) { if (pwmState.pin[pwmState.idx] == 16) {
GP16O = 0; GP16O = 0;
} }
pwmState.idx++; }
// Any other pins at this same PWM value will have delta==0, drop them too. pwmState.idx++;
} while (pwmState.delta[pwmState.idx] == 0); // Any other pins at this same PWM value will have delta==0, drop them too.
} while (pwmState.delta[pwmState.idx] == 0);
} }
// Preserve duty cycle over PWM period by using now+xxx instead of += delta // Preserve duty cycle over PWM period by using now+xxx instead of += delta
cyclesToGo = adjust(pwmState.delta[pwmState.idx]); cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
@ -491,7 +509,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
} }
nextEventCycles = min_u32(nextEventCycles, cyclesToGo); nextEventCycles = min_u32(nextEventCycles, cyclesToGo);
} }
#endif
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) { for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
uint32_t mask = 1<<i; uint32_t mask = 1<<i;
@ -509,12 +526,11 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
int32_t expiryToGo = wave->expiryCycle - now; int32_t expiryToGo = wave->expiryCycle - now;
if (expiryToGo < 0) { if (expiryToGo < 0) {
// Done, remove! // Done, remove!
wvfState.waveformEnabled &= ~mask;
if (i == 16) { if (i == 16) {
GP16O = 0; GP16O = 0;
} else { }
ClearGPIO(mask); GPOC = mask;
} wvfState.waveformEnabled &= ~mask;
continue; continue;
} }
} }
@ -528,39 +544,33 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
wvfState.waveformState ^= mask; wvfState.waveformState ^= mask;
if (wvfState.waveformState & mask) { if (wvfState.waveformState & mask) {
if (i == 16) { if (i == 16) {
GP16O = 1; // GPIO16 write slow as it's RMW GP16O = 1;
} else {
SetGPIO(mask);
} }
if (wave->gotoTimeHighCycles) { GPOS = mask;
if (wvfState.waveformToChange & mask) {
// Copy over next full-cycle timings // Copy over next full-cycle timings
wave->timeHighCycles = wave->gotoTimeHighCycles; wave->timeHighCycles = wvfState.waveformNewHigh;
wave->desiredHighCycles = wave->gotoTimeHighCycles; wave->desiredHighCycles = wvfState.waveformNewHigh;
wave->timeLowCycles = wave->gotoTimeLowCycles; wave->timeLowCycles = wvfState.waveformNewLow;
wave->desiredLowCycles = wave->gotoTimeLowCycles; wave->desiredLowCycles = wvfState.waveformNewLow;
wave->gotoTimeHighCycles = 0; wave->lastEdge = 0;
} else { wvfState.waveformToChange = 0;
#ifdef ENABLE_FEEDBACK }
if (wave->lastEdge) { if (wave->lastEdge) {
desired = wave->desiredLowCycles; desired = wave->desiredLowCycles;
timeToUpdate = &wave->timeLowCycles; timeToUpdate = &wave->timeLowCycles;
}
} }
#endif
nextEdgeCycles = wave->timeHighCycles; nextEdgeCycles = wave->timeHighCycles;
} else { } else {
if (i == 16) { if (i == 16) {
GP16O = 0; // GPIO16 write slow as it's RMW GP16O = 0;
} else {
ClearGPIO(mask);
} }
#ifdef ENABLE_FEEDBACK GPOC = mask;
desired = wave->desiredHighCycles; desired = wave->desiredHighCycles;
timeToUpdate = &wave->timeHighCycles; timeToUpdate = &wave->timeHighCycles;
#endif
nextEdgeCycles = wave->timeLowCycles; nextEdgeCycles = wave->timeLowCycles;
} }
#ifdef ENABLE_FEEDBACK
if (desired) { if (desired) {
desired = adjust(desired); desired = adjust(desired);
int32_t err = desired - (now - wave->lastEdge); int32_t err = desired - (now - wave->lastEdge);
@ -569,7 +579,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
*timeToUpdate += err; *timeToUpdate += err;
} }
} }
#endif
nextEdgeCycles = adjust(nextEdgeCycles); nextEdgeCycles = adjust(nextEdgeCycles);
wave->nextServiceCycle = now + nextEdgeCycles; wave->nextServiceCycle = now + nextEdgeCycles;
nextEventCycles = min_u32(nextEventCycles, nextEdgeCycles); nextEventCycles = min_u32(nextEventCycles, nextEdgeCycles);
@ -599,7 +608,6 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
// Do it here instead of global function to save time and because we know it's edge-IRQ // Do it here instead of global function to save time and because we know it's edge-IRQ
T1L = nextEventCycles >> (turbo ? 1 : 0); T1L = nextEventCycles >> (turbo ? 1 : 0);
TEIE |= TEIE1; // Edge int enable
} }
}; };

View File

@ -34,9 +34,9 @@
extern "C" { extern "C" {
// Internal-only calls, not for applications // Internal-only calls, not for applications
extern void _setPWMPeriodCC(uint32_t cc); extern void _setPWMFreq(uint32_t freq);
extern bool _stopPWM(int pin); extern bool _stopPWM(int pin);
extern bool _setPWM(int pin, uint32_t cc); extern bool _setPWM(int pin, uint32_t val, uint32_t range);
extern void resetPins(); extern void resetPins();
volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 }; volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 };

View File

@ -29,13 +29,11 @@
extern "C" { extern "C" {
// Internal-only calls, not for applications // Internal-only calls, not for applications
extern void _setPWMPeriodCC(uint32_t cc); extern void _setPWMFreq(uint32_t freq);
extern bool _stopPWM(int pin); extern bool _stopPWM(int pin);
extern bool _setPWM(int pin, uint32_t cc); extern bool _setPWM(int pin, uint32_t val, uint32_t range);
static uint32_t analogMap = 0;
static int32_t analogScale = PWMRANGE; static int32_t analogScale = PWMRANGE;
static uint16_t analogFreq = 1000;
extern void __analogWriteRange(uint32_t range) { extern void __analogWriteRange(uint32_t range) {
if (range > 0) { if (range > 0) {
@ -43,17 +41,15 @@ extern void __analogWriteRange(uint32_t range) {
} }
} }
extern void __analogWriteFreq(uint32_t freq) { extern void __analogWriteFreq(uint32_t freq) {
if (freq < 40) { // Arduino sets a minimum of 100Hz, waiting for them to change this one. if (freq < 40) {
analogFreq = 40; freq = 40;
} else if (freq > 60000) { } else if (freq > 60000) {
analogFreq = 60000; freq = 60000;
} else { } else {
analogFreq = freq; freq = freq;
} }
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; _setPWMFreq(freq);
_setPWMPeriodCC(analogPeriod);
} }
extern void __analogWrite(uint8_t pin, int val) { extern void __analogWrite(uint8_t pin, int val) {
@ -61,28 +57,14 @@ extern void __analogWrite(uint8_t pin, int val) {
return; return;
} }
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq;
_setPWMPeriodCC(analogPeriod);
if (val < 0) { if (val < 0) {
val = 0; val = 0;
} else if (val > analogScale) { } else if (val > analogScale) {
val = analogScale; val = analogScale;
} }
analogMap &= ~(1 << pin);
uint32_t high = (analogPeriod * val) / analogScale;
uint32_t low = analogPeriod - high;
pinMode(pin, OUTPUT); pinMode(pin, OUTPUT);
if (low == 0) { _setPWM(pin, val, analogScale);
_stopPWM(pin);
digitalWrite(pin, HIGH);
} else if (high == 0) {
_stopPWM(pin);
digitalWrite(pin, LOW);
} else {
_setPWM(pin, high);
analogMap |= (1 << pin);
}
} }
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));