mirror of https://github.com/arendst/Tasmota.git
Merge pull request #8326 from s-hadinger/pwm_7231_0305
Change PWM updated to latest Arduino Core #7213
This commit is contained in:
commit
315744b548
|
@ -3,6 +3,7 @@
|
||||||
### 8.2.0.6 20200501
|
### 8.2.0.6 20200501
|
||||||
|
|
||||||
- Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt
|
- Add experimental basic support for Tasmota on ESP32 based on work by Jörg Schüler-Maroldt
|
||||||
|
- Change PWM updated to latest Arduino Core #7213
|
||||||
|
|
||||||
### 8.2.0.5 20200425
|
### 8.2.0.5 20200425
|
||||||
|
|
||||||
|
|
|
@ -5,23 +5,23 @@
|
||||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
The core idea is to have a programmable waveform generator with a unique
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1
|
||||||
set to 1-shot mode and is always loaded with the time until the next edge
|
is set to 1-shot mode and is always loaded with the time until the next
|
||||||
of any live waveforms.
|
edge of any live waveforms.
|
||||||
|
|
||||||
Up to one waveform generator per pin supported.
|
Up to one waveform generator per pin supported.
|
||||||
|
|
||||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
Each waveform generator is synchronized to the ESP clock cycle counter, not
|
||||||
timer. This allows for removing interrupt jitter and delay as the counter
|
the timer. This allows for removing interrupt jitter and delay as the
|
||||||
always increments once per 80MHz clock. Changes to a waveform are
|
counter always increments once per 80MHz clock. Changes to a waveform are
|
||||||
contiguous and only take effect on the next waveform transition,
|
contiguous and only take effect on the next waveform transition,
|
||||||
allowing for smooth transitions.
|
allowing for smooth transitions.
|
||||||
|
|
||||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||||
|
|
||||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
clock cycle count, or an interval measured in CPU clock cycles, but not
|
||||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
This library is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU Lesser General Public
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
@ -65,60 +65,62 @@ typedef struct {
|
||||||
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; // Currently running waveform period
|
||||||
uint32_t timeLowCycles; //
|
uint32_t timeLowCycles; //
|
||||||
|
uint32_t desiredHighCycles; // Currently running waveform period
|
||||||
|
uint32_t desiredLowCycles; //
|
||||||
uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase
|
uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase
|
||||||
uint32_t gotoTimeLowCycles; //
|
uint32_t gotoTimeLowCycles; //
|
||||||
|
uint32_t lastEdge; //
|
||||||
} Waveform;
|
} Waveform;
|
||||||
|
|
||||||
static Waveform waveform[17]; // State of all possible pins
|
class WVFState {
|
||||||
static volatile uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
public:
|
||||||
static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
Waveform waveform[17]; // State of all possible pins
|
||||||
|
uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||||
|
uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||||
|
|
||||||
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
||||||
static volatile 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
|
||||||
static volatile 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
|
||||||
|
|
||||||
volatile int32_t waveformToChange = -1;
|
int32_t waveformToChange = -1;
|
||||||
volatile uint32_t waveformNewHigh = 0;
|
uint32_t waveformNewHigh = 0;
|
||||||
volatile uint32_t waveformNewLow = 0;
|
uint32_t waveformNewLow = 0;
|
||||||
|
|
||||||
static uint32_t (*timer1CB)() = NULL;
|
uint32_t (*timer1CB)() = NULL;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// we can avoid looking at the other pins.
|
||||||
|
int startPin = 0;
|
||||||
|
int endPin = 0;
|
||||||
|
};
|
||||||
|
static WVFState wvfState;
|
||||||
|
|
||||||
|
|
||||||
|
// Ensure everything is read/written to RAM
|
||||||
|
#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
|
||||||
|
|
||||||
// Non-speed critical bits
|
// Non-speed critical bits
|
||||||
#pragma GCC optimize ("Os")
|
#pragma GCC optimize ("Os")
|
||||||
|
|
||||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() {
|
|
||||||
uint32_t ccount;
|
|
||||||
__asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount));
|
|
||||||
return ccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt on/off control
|
// Interrupt on/off control
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||||
static bool timerRunning = false;
|
static bool timerRunning = false;
|
||||||
|
|
||||||
static void initTimer() {
|
static void initTimer() {
|
||||||
|
if (!timerRunning) {
|
||||||
timer1_disable();
|
timer1_disable();
|
||||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||||
timerRunning = true;
|
timerRunning = true;
|
||||||
|
timer1_write(microsecondsToClockCycles(10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ICACHE_RAM_ATTR deinitTimer() {
|
static ICACHE_RAM_ATTR void forceTimerInterrupt() {
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
if (T1L > microsecondsToClockCycles(10)) {
|
||||||
timer1_disable();
|
T1L = microsecondsToClockCycles(10);
|
||||||
timer1_isr_init();
|
|
||||||
timerRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a callback. Pass in NULL to stop it
|
|
||||||
void setTimer1Callback(uint32_t (*fn)()) {
|
|
||||||
timer1CB = fn;
|
|
||||||
if (!timerRunning && fn) {
|
|
||||||
initTimer();
|
|
||||||
timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste
|
|
||||||
} else if (timerRunning && !fn && !waveformEnabled) {
|
|
||||||
deinitTimer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,24 +137,31 @@ void setTimer1Callback(uint32_t (*fn)()) {
|
||||||
|
|
||||||
constexpr int maxPWMs = 8;
|
constexpr int maxPWMs = 8;
|
||||||
|
|
||||||
// PWM edge definition
|
|
||||||
typedef struct {
|
|
||||||
unsigned int pin : 8;
|
|
||||||
unsigned int delta : 24;
|
|
||||||
} PWMEntry;
|
|
||||||
|
|
||||||
// PWM machine state
|
// PWM machine state
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t mask; // Bitmask of active pins
|
uint32_t mask; // Bitmask of active pins
|
||||||
uint8_t cnt; // How many entries
|
uint32_t cnt; // How many entries
|
||||||
uint8_t idx; // Where the state machine is along the list
|
uint32_t idx; // Where the state machine is along the list
|
||||||
PWMEntry edge[maxPWMs + 1]; // Include space for terminal element
|
uint8_t pin[maxPWMs + 1];
|
||||||
|
uint32_t delta[maxPWMs + 1];
|
||||||
uint32_t nextServiceCycle; // Clock cycle for next step
|
uint32_t nextServiceCycle; // Clock cycle for next step
|
||||||
} PWMState;
|
} PWMState;
|
||||||
|
|
||||||
static PWMState pwmState;
|
static PWMState pwmState;
|
||||||
static volatile PWMState *pwmUpdate = nullptr; // Set by main code, cleared by ISR
|
static PWMState *pwmUpdate = nullptr; // Set by main code, cleared by ISR
|
||||||
static uint32_t pwmPeriod = (1000000L * system_get_cpu_freq()) / 1000;
|
static uint32_t pwmPeriod = microsecondsToClockCycles(1000000UL) / 1000;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static ICACHE_RAM_ATTR void disableIdleTimer() {
|
||||||
|
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
|
||||||
|
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||||
|
timer1_disable();
|
||||||
|
timer1_isr_init();
|
||||||
|
timerRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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 _setPWMPeriodCC(uint32_t cc) {
|
||||||
|
@ -168,48 +177,48 @@ void _setPWMPeriodCC(uint32_t cc) {
|
||||||
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;
|
||||||
uint32_t ttl = 0;
|
uint32_t ttl = 0;
|
||||||
for (auto i = 0; i < p.cnt; i++) {
|
for (uint32_t i = 0; i < p.cnt; i++) {
|
||||||
uint64_t val64p16 = ((uint64_t)p.edge[i].delta) << 16;
|
uint64_t val64p16 = ((uint64_t)p.delta[i]) << 16;
|
||||||
uint64_t newVal64p32 = val64p16 * ratio64p16;
|
uint64_t newVal64p32 = val64p16 * ratio64p16;
|
||||||
p.edge[i].delta = newVal64p32 >> 32;
|
p.delta[i] = newVal64p32 >> 32;
|
||||||
ttl += p.edge[i].delta;
|
ttl += p.delta[i];
|
||||||
}
|
}
|
||||||
p.edge[p.cnt].delta = cc - ttl; // Final cleanup exactly cc total cycles
|
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;
|
pwmUpdate = &p;
|
||||||
|
MEMBARRIER();
|
||||||
|
forceTimerInterrupt();
|
||||||
while (pwmUpdate) {
|
while (pwmUpdate) {
|
||||||
delay(0);
|
delay(0);
|
||||||
|
// No mem barrier. The external function call guarantees it's re-read
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pwmPeriod = cc;
|
pwmPeriod = cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper routine to remove an entry from the state machine
|
// Helper routine to remove an entry from the state machine
|
||||||
static void _removePWMEntry(int pin, PWMState *p) {
|
static ICACHE_RAM_ATTR void _removePWMEntry(int pin, PWMState *p) {
|
||||||
if (!((1<<pin) & p->mask)) {
|
uint32_t i;
|
||||||
return;
|
|
||||||
}
|
// Find the pin to pull out...
|
||||||
|
for (i = 0; p->pin[i] != pin; i++) { /* no-op */ }
|
||||||
|
auto delta = p->delta[i];
|
||||||
|
|
||||||
int delta = 0;
|
|
||||||
int i;
|
|
||||||
for (i=0; i < p->cnt; i++) {
|
|
||||||
if (p->edge[i].pin == pin) {
|
|
||||||
delta = p->edge[i].delta;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add the removed previous pin delta to preserve absolute position
|
// Add the removed previous pin delta to preserve absolute position
|
||||||
p->edge[i+1].delta += delta;
|
p->delta[i+1] += delta;
|
||||||
// Move everything back one and clean up
|
|
||||||
|
// Move everything back one
|
||||||
for (i++; i <= p->cnt; i++) {
|
for (i++; i <= p->cnt; i++) {
|
||||||
p->edge[i-1] = p->edge[i];
|
p->pin[i-1] = p->pin[i];
|
||||||
|
p->delta[i-1] = p->delta[i];
|
||||||
}
|
}
|
||||||
|
// Remove the pin from the active list
|
||||||
p->mask &= ~(1<<pin);
|
p->mask &= ~(1<<pin);
|
||||||
p->cnt--;
|
p->cnt--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by analogWrite(0/100%) to disable PWM on a specific pin
|
// Called by analogWrite(0/100%) to disable PWM on a specific pin
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -217,62 +226,69 @@ 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);
|
_removePWMEntry(pin, &p);
|
||||||
|
|
||||||
// Update and wait for mailbox to be emptied
|
// Update and wait for mailbox to be emptied
|
||||||
pwmUpdate = &p;
|
pwmUpdate = &p;
|
||||||
|
MEMBARRIER();
|
||||||
|
forceTimerInterrupt();
|
||||||
while (pwmUpdate) {
|
while (pwmUpdate) {
|
||||||
delay(0);
|
MEMBARRIER();
|
||||||
}
|
/* Busy wait, could be in ISR */
|
||||||
// Possibly shut doen the timer completely if we're done
|
|
||||||
if (!waveformEnabled && !pwmState.cnt && !timer1CB) {
|
|
||||||
deinitTimer();
|
|
||||||
}
|
}
|
||||||
|
// Possibly shut down the timer completely if we're done
|
||||||
|
disableIdleTimer();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
|
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
|
||||||
bool _setPWM(int pin, uint32_t cc) {
|
bool _setPWM(int pin, uint32_t cc) {
|
||||||
|
stopWaveform(pin);
|
||||||
PWMState p; // Working copy
|
PWMState p; // Working copy
|
||||||
p = pwmState;
|
p = pwmState;
|
||||||
// Get rid of any entries for this pin
|
// Get rid of any entries for this pin
|
||||||
|
if ((1<<pin) & p.mask) {
|
||||||
_removePWMEntry(pin, &p);
|
_removePWMEntry(pin, &p);
|
||||||
|
}
|
||||||
// And add it to the list, in order
|
// And add it to the list, in order
|
||||||
if (p.cnt >= maxPWMs) {
|
if (p.cnt >= maxPWMs) {
|
||||||
return false; // No space left
|
return false; // No space left
|
||||||
} else if (p.cnt == 0) {
|
} 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.edge[0].pin = pin;
|
p.pin[0] = pin;
|
||||||
p.edge[0].delta = cc;
|
p.delta[0] = cc;
|
||||||
p.edge[1].pin = 0xff;
|
p.pin[1] = 0xff;
|
||||||
p.edge[1].delta = pwmPeriod - cc;
|
p.delta[1] = pwmPeriod - cc;
|
||||||
p.cnt = 1;
|
p.cnt = 1;
|
||||||
p.mask = 1<<pin;
|
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.edge[i].delta < cc); i++) {
|
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
|
||||||
ttl += p.edge[i].delta;
|
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.edge[i + 1], &p.edge[i], (1 + p.cnt - i) * sizeof(p.edge[0]));
|
memmove(&p.pin[i + 1], &p.pin[i], (1 + p.cnt - i) * sizeof(p.pin[0]));
|
||||||
|
memmove(&p.delta[i + 1], &p.delta[i], (1 + p.cnt - i) * sizeof(p.delta[0]));
|
||||||
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.edge[i].pin = pin;
|
p.pin[i] = pin;
|
||||||
p.edge[i].delta = off; // Add the delta to this new pin
|
p.delta[i] = off; // Add the delta to this new pin
|
||||||
p.edge[i + 1].delta -= 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.cnt++;
|
||||||
p.mask |= 1<<pin;
|
p.mask |= 1<<pin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set mailbox and wait for ISR to copy it over
|
// Set mailbox and wait for ISR to copy it over
|
||||||
pwmUpdate = &p;
|
pwmUpdate = &p;
|
||||||
if (!timerRunning) {
|
MEMBARRIER();
|
||||||
initTimer();
|
initTimer();
|
||||||
timer1_write(microsecondsToClockCycles(10));
|
forceTimerInterrupt();
|
||||||
|
while (pwmUpdate) {
|
||||||
|
delay(0);
|
||||||
}
|
}
|
||||||
while (pwmUpdate) { delay(0); }
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||||
// first, then it will immediately begin.
|
// first, then it will immediately begin.
|
||||||
|
@ -284,44 +300,59 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time
|
||||||
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Waveform *wave = &waveform[pin];
|
Waveform *wave = &wvfState.waveform[pin];
|
||||||
wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0;
|
wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0;
|
||||||
if (runTimeCycles && !wave->expiryCycle) {
|
if (runTimeCycles && !wave->expiryCycle) {
|
||||||
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
|
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_stopPWM(pin); // Make sure there's no PWM live here
|
||||||
|
|
||||||
uint32_t mask = 1<<pin;
|
uint32_t mask = 1<<pin;
|
||||||
if (waveformEnabled & mask) {
|
MEMBARRIER();
|
||||||
waveformNewHigh = timeHighCycles;
|
if (wvfState.waveformEnabled & mask) {
|
||||||
waveformNewLow = timeLowCycles;
|
wvfState.waveformNewHigh = timeHighCycles;
|
||||||
waveformToChange = pin;
|
wvfState.waveformNewLow = timeLowCycles;
|
||||||
while (waveformToChange >= 0) {
|
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
|
||||||
}
|
}
|
||||||
} else { // if (!(waveformEnabled & mask)) {
|
} else { // if (!(wvfState.waveformEnabled & mask)) {
|
||||||
wave->timeHighCycles = timeHighCycles;
|
wave->timeHighCycles = timeHighCycles;
|
||||||
wave->timeLowCycles = timeLowCycles;
|
wave->timeLowCycles = timeLowCycles;
|
||||||
|
wave->desiredHighCycles = wave->timeHighCycles;
|
||||||
|
wave->desiredLowCycles = wave->timeLowCycles;
|
||||||
|
wave->lastEdge = 0;
|
||||||
wave->gotoTimeHighCycles = wave->timeHighCycles;
|
wave->gotoTimeHighCycles = wave->timeHighCycles;
|
||||||
wave->gotoTimeLowCycles = wave->timeLowCycles; // Actually set the pin high or low in the IRQ service to guarantee times
|
wave->gotoTimeLowCycles = wave->timeLowCycles; // Actually set the pin high or low in the IRQ service to guarantee times
|
||||||
wave->nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1);
|
wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
|
||||||
waveformToEnable |= mask;
|
wvfState.waveformToEnable |= mask;
|
||||||
if (!timerRunning) {
|
MEMBARRIER();
|
||||||
initTimer();
|
initTimer();
|
||||||
timer1_write(microsecondsToClockCycles(10));
|
forceTimerInterrupt();
|
||||||
} else {
|
while (wvfState.waveformToEnable) {
|
||||||
// Ensure timely service....
|
|
||||||
if (T1L > microsecondsToClockCycles(10)) {
|
|
||||||
timer1_write(microsecondsToClockCycles(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (waveformToEnable) {
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set a callback. Pass in NULL to stop it
|
||||||
|
void setTimer1Callback(uint32_t (*fn)()) {
|
||||||
|
wvfState.timer1CB = fn;
|
||||||
|
if (fn) {
|
||||||
|
initTimer();
|
||||||
|
forceTimerInterrupt();
|
||||||
|
}
|
||||||
|
disableIdleTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -349,76 +380,87 @@ 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
|
||||||
uint32_t mask = 1<<pin;
|
if (wvfState.waveformEnabled & (1UL << pin)) {
|
||||||
if (!(waveformEnabled & mask)) {
|
wvfState.waveformToDisable = 1UL << pin;
|
||||||
return false; // It's not running, nothing to do here
|
forceTimerInterrupt();
|
||||||
}
|
while (wvfState.waveformToDisable) {
|
||||||
waveformToDisable |= mask;
|
MEMBARRIER(); // If it wasn't written yet, it has to be by now
|
||||||
// Ensure timely service....
|
|
||||||
if (T1L > microsecondsToClockCycles(10)) {
|
|
||||||
timer1_write(microsecondsToClockCycles(10));
|
|
||||||
}
|
|
||||||
while (waveformToDisable) {
|
|
||||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||||
}
|
}
|
||||||
if (!waveformEnabled && !pwmState.cnt && !timer1CB) {
|
|
||||||
deinitTimer();
|
|
||||||
}
|
}
|
||||||
|
disableIdleTimer();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The SDK and hardware take some time to actually get to our NMI code, so
|
// The SDK and hardware take some time to actually get to our NMI code, so
|
||||||
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
||||||
// real CPU cycle counter we want for the waveforms.
|
// real CPU cycle counter we want for the waveforms.
|
||||||
|
|
||||||
|
// The SDK also sometimes is running at a different speed the the Arduino core
|
||||||
|
// so the ESP cycle counter is actually running at a variable speed.
|
||||||
|
// adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
|
||||||
#if F_CPU == 80000000
|
#if F_CPU == 80000000
|
||||||
#define DELTAIRQ (microsecondsToClockCycles(3))
|
#define DELTAIRQ (microsecondsToClockCycles(3))
|
||||||
|
#define adjust(x) ((x) << (turbo ? 1 : 0))
|
||||||
#else
|
#else
|
||||||
#define DELTAIRQ (microsecondsToClockCycles(2))
|
#define DELTAIRQ (microsecondsToClockCycles(2))
|
||||||
|
#define adjust(x) ((x) >> (turbo ? 0 : 1))
|
||||||
|
#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
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||||
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
// Flag if the core is at 160 MHz, for use by adjust()
|
||||||
// are generating. In the common case (1 PWM) these may be the same pin and
|
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
|
||||||
// we can avoid looking at the other pins.
|
|
||||||
static int startPin = 0;
|
|
||||||
static int endPin = 0;
|
|
||||||
|
|
||||||
uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
||||||
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
||||||
|
|
||||||
if (waveformToEnable || waveformToDisable) {
|
if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
|
||||||
// Handle enable/disable requests from main app
|
// Handle enable/disable requests from main app
|
||||||
waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off
|
wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
|
||||||
waveformState &= ~waveformToEnable; // And clear the state of any just started
|
wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
|
||||||
waveformToEnable = 0;
|
wvfState.waveformToEnable = 0;
|
||||||
waveformToDisable = 0;
|
wvfState.waveformToDisable = 0;
|
||||||
|
// No mem barrier. Globals must be written to RAM on ISR exit.
|
||||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||||
startPin = __builtin_ffs(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)
|
||||||
endPin = 32 - __builtin_clz(waveformEnabled);
|
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
|
||||||
|
#ifdef ENABLE_PWM
|
||||||
} else if (!pwmState.cnt && 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 = *(PWMState*)pwmUpdate;
|
pwmState.cnt = 1;
|
||||||
pwmUpdate = nullptr;
|
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!
|
||||||
pwmState.idx = pwmState.cnt; // Cause it to start at t=0
|
// No need for mem barrier here. Global must be written by IRQ exit
|
||||||
} else if (waveformToChange >=0) {
|
#endif
|
||||||
waveform[waveformToChange].gotoTimeHighCycles = waveformNewHigh;
|
} else if (wvfState.waveformToChange >= 0) {
|
||||||
waveform[waveformToChange].gotoTimeLowCycles = waveformNewLow;
|
wvfState.waveform[wvfState.waveformToChange].gotoTimeHighCycles = wvfState.waveformNewHigh;
|
||||||
waveformToChange = -1;
|
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;
|
||||||
if (waveformEnabled || pwmState.cnt) {
|
if (wvfState.waveformEnabled || pwmState.cnt) {
|
||||||
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();
|
uint32_t now = GetCycleCountIRQ();
|
||||||
int32_t cyclesToGo = pwmState.nextServiceCycle - now;
|
int32_t cyclesToGo = pwmState.nextServiceCycle - now;
|
||||||
if (cyclesToGo <= 10) {
|
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 (pwmUpdate) {
|
||||||
// Do the memory copy from temp to global and clear mailbox
|
// Do the memory copy from temp to global and clear mailbox
|
||||||
|
@ -427,39 +469,39 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||||
}
|
}
|
||||||
GPOS = pwmState.mask; // Set all active pins high
|
GPOS = pwmState.mask; // Set all active pins high
|
||||||
// GPIO16 isn't the same as the others
|
// GPIO16 isn't the same as the others
|
||||||
if (pwmState.mask & 0x100) {
|
if (pwmState.mask & (1<<16)) {
|
||||||
GP16O |= 1;
|
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.edge[pwmState.idx].pin;
|
GPOC = 1<<pwmState.pin[pwmState.idx];
|
||||||
// GPIO16 still needs manual work
|
// GPIO16 still needs manual work
|
||||||
if (pwmState.edge[pwmState.idx].pin == 16) {
|
if (pwmState.pin[pwmState.idx] == 16) {
|
||||||
GP16O &= ~1;
|
GP16O = 0;
|
||||||
}
|
}
|
||||||
pwmState.idx++;
|
pwmState.idx++;
|
||||||
// Any other pins at this same PWM value will have delta==0, drop them too.
|
// Any other pins at this same PWM value will have delta==0, drop them too.
|
||||||
} while (pwmState.edge[pwmState.idx].delta == 0);
|
} 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
|
||||||
pwmState.nextServiceCycle = now + pwmState.edge[pwmState.idx].delta;
|
cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
|
||||||
cyclesToGo = pwmState.nextServiceCycle - now;
|
pwmState.nextServiceCycle = GetCycleCountIRQ() + cyclesToGo;
|
||||||
if (cyclesToGo<0) cyclesToGo=0;
|
|
||||||
}
|
}
|
||||||
nextEventCycles = min_u32(nextEventCycles, cyclesToGo);
|
nextEventCycles = min_u32(nextEventCycles, cyclesToGo);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
for (int i = startPin; i <= endPin; i++) {
|
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
|
||||||
uint32_t mask = 1<<i;
|
uint32_t mask = 1<<i;
|
||||||
|
|
||||||
// If it's not on, ignore!
|
// If it's not on, ignore!
|
||||||
if (!(waveformEnabled & mask)) {
|
if (!(wvfState.waveformEnabled & mask)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Waveform *wave = &waveform[i];
|
Waveform *wave = &wvfState.waveform[i];
|
||||||
uint32_t now = GetCycleCountIRQ();
|
uint32_t now = GetCycleCountIRQ();
|
||||||
|
|
||||||
// Disable any waveforms that are done
|
// Disable any waveforms that are done
|
||||||
|
@ -467,9 +509,9 @@ 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!
|
||||||
waveformEnabled &= ~mask;
|
wvfState.waveformEnabled &= ~mask;
|
||||||
if (i == 16) {
|
if (i == 16) {
|
||||||
GP16O &= ~1;
|
GP16O = 0;
|
||||||
} else {
|
} else {
|
||||||
ClearGPIO(mask);
|
ClearGPIO(mask);
|
||||||
}
|
}
|
||||||
|
@ -480,27 +522,58 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||||
// Check for toggles
|
// Check for toggles
|
||||||
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
||||||
if (cyclesToGo < 0) {
|
if (cyclesToGo < 0) {
|
||||||
waveformState ^= mask;
|
uint32_t nextEdgeCycles;
|
||||||
if (waveformState & mask) {
|
uint32_t desired = 0;
|
||||||
|
uint32_t *timeToUpdate;
|
||||||
|
wvfState.waveformState ^= mask;
|
||||||
|
if (wvfState.waveformState & mask) {
|
||||||
if (i == 16) {
|
if (i == 16) {
|
||||||
GP16O |= 1; // GPIO16 write slow as it's RMW
|
GP16O = 1; // GPIO16 write slow as it's RMW
|
||||||
} else {
|
} else {
|
||||||
SetGPIO(mask);
|
SetGPIO(mask);
|
||||||
}
|
}
|
||||||
wave->nextServiceCycle = now + wave->timeHighCycles;
|
if (wave->gotoTimeHighCycles) {
|
||||||
nextEventCycles = min_u32(nextEventCycles, wave->timeHighCycles);
|
// Copy over next full-cycle timings
|
||||||
|
wave->timeHighCycles = wave->gotoTimeHighCycles;
|
||||||
|
wave->desiredHighCycles = wave->gotoTimeHighCycles;
|
||||||
|
wave->timeLowCycles = wave->gotoTimeLowCycles;
|
||||||
|
wave->desiredLowCycles = wave->gotoTimeLowCycles;
|
||||||
|
wave->gotoTimeHighCycles = 0;
|
||||||
|
} else {
|
||||||
|
#ifdef ENABLE_FEEDBACK
|
||||||
|
if (wave->lastEdge) {
|
||||||
|
desired = wave->desiredLowCycles;
|
||||||
|
timeToUpdate = &wave->timeLowCycles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
nextEdgeCycles = wave->timeHighCycles;
|
||||||
} else {
|
} else {
|
||||||
if (i == 16) {
|
if (i == 16) {
|
||||||
GP16O &= ~1; // GPIO16 write slow as it's RMW
|
GP16O = 0; // GPIO16 write slow as it's RMW
|
||||||
} else {
|
} else {
|
||||||
ClearGPIO(mask);
|
ClearGPIO(mask);
|
||||||
}
|
}
|
||||||
wave->nextServiceCycle = now + wave->timeLowCycles;
|
#ifdef ENABLE_FEEDBACK
|
||||||
nextEventCycles = min_u32(nextEventCycles, wave->timeLowCycles);
|
desired = wave->desiredHighCycles;
|
||||||
// Copy over next full-cycle timings
|
timeToUpdate = &wave->timeHighCycles;
|
||||||
wave->timeHighCycles = wave->gotoTimeHighCycles;
|
#endif
|
||||||
wave->timeLowCycles = wave->gotoTimeLowCycles;
|
nextEdgeCycles = wave->timeLowCycles;
|
||||||
}
|
}
|
||||||
|
#ifdef ENABLE_FEEDBACK
|
||||||
|
if (desired) {
|
||||||
|
desired = adjust(desired);
|
||||||
|
int32_t err = desired - (now - wave->lastEdge);
|
||||||
|
if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
|
||||||
|
err /= 2;
|
||||||
|
*timeToUpdate += err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
nextEdgeCycles = adjust(nextEdgeCycles);
|
||||||
|
wave->nextServiceCycle = now + nextEdgeCycles;
|
||||||
|
nextEventCycles = min_u32(nextEventCycles, nextEdgeCycles);
|
||||||
|
wave->lastEdge = now;
|
||||||
} else {
|
} else {
|
||||||
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
||||||
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
|
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
|
||||||
|
@ -513,10 +586,10 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||||
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
||||||
done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0);
|
done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0);
|
||||||
} while (!done);
|
} while (!done);
|
||||||
} // if (waveformEnabled)
|
} // if (wvfState.waveformEnabled)
|
||||||
|
|
||||||
if (timer1CB) {
|
if (wvfState.timer1CB) {
|
||||||
nextEventCycles = min_u32(nextEventCycles, timer1CB());
|
nextEventCycles = min_u32(nextEventCycles, wvfState.timer1CB());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextEventCycles < microsecondsToClockCycles(5)) {
|
if (nextEventCycles < microsecondsToClockCycles(5)) {
|
||||||
|
@ -525,11 +598,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||||
nextEventCycles -= DELTAIRQ;
|
nextEventCycles -= DELTAIRQ;
|
||||||
|
|
||||||
// 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
|
||||||
#if F_CPU == 160000000
|
T1L = nextEventCycles >> (turbo ? 1 : 0);
|
||||||
T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS
|
|
||||||
#else
|
|
||||||
T1L = nextEventCycles; // Already know we're in range by MAXIRQUS
|
|
||||||
#endif
|
|
||||||
TEIE |= TEIE1; // Edge int enable
|
TEIE |= TEIE1; // Edge int enable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,8 @@ extern void __analogWriteRange(uint32_t range) {
|
||||||
extern void __analogWriteFreq(uint32_t freq) {
|
extern void __analogWriteFreq(uint32_t freq) {
|
||||||
if (freq < 100) {
|
if (freq < 100) {
|
||||||
analogFreq = 100;
|
analogFreq = 100;
|
||||||
} else if (freq > 40000) {
|
} else if (freq > 60000) {
|
||||||
analogFreq = 40000;
|
analogFreq = 60000;
|
||||||
} else {
|
} else {
|
||||||
analogFreq = freq;
|
analogFreq = freq;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue