From 454e09f575e272c59cb38ff1ed8785135631f673 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 19:16:30 +0200 Subject: [PATCH 01/10] Delete core_esp8266_waveform.cpp --- tasmota/core_esp8266_waveform.cpp | 615 ------------------------------ 1 file changed, 615 deletions(-) delete mode 100644 tasmota/core_esp8266_waveform.cpp diff --git a/tasmota/core_esp8266_waveform.cpp b/tasmota/core_esp8266_waveform.cpp deleted file mode 100644 index 29a485874..000000000 --- a/tasmota/core_esp8266_waveform.cpp +++ /dev/null @@ -1,615 +0,0 @@ -/* - esp8266_waveform - General purpose waveform generation and control, - supporting outputs on all pins in parallel. - - Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. - - 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 set to 1-shot mode and is always loaded with the time until the next - edge of any live waveforms. - - Up to one waveform generator per pin supported. - - Each waveform generator is synchronized to the ESP clock cycle counter, not - the timer. This allows for removing interrupt jitter and delay as the - counter always increments once per 80MHz clock. Changes to a waveform are - contiguous and only take effect on the next waveform transition, - allowing for smooth transitions. - - This replaces older tone(), analogWrite(), and the Servo classes. - - 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 cycles (which may be 2 CPU clock cycles @ 160MHz). - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifdef ESP8266 - -#include -#include "ets_sys.h" -#include "core_esp8266_waveform.h" -#include "user_interface.h" -extern "C" { - -// Internal-only calls, not for applications -extern void _setPWMFreq(uint32_t freq); -extern bool _stopPWM(int pin); -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); - -// Maximum delay between IRQs -#define MAXIRQUS (10000) - -// Waveform generator can create tones, PWM, and servos -typedef struct { - 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 timeHighCycles; // Actual running waveform period (adjusted using desiredCycles) - uint32_t timeLowCycles; // - uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal - uint32_t desiredLowCycles; // - uint32_t lastEdge; // Cycle when this generator last changed -} Waveform; - -class WVFState { -public: - 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 - 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 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 waveformNewLow = 0; - - 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. - uint16_t startPin = 0; - uint16_t endPin = 0; -}; -static WVFState wvfState; - - -// Ensure everything is read/written to RAM -#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); } - -// Non-speed critical bits -#pragma GCC optimize ("Os") - -// Interrupt on/off control -static ICACHE_RAM_ATTR void timer1Interrupt(); -static bool timerRunning = false; - -static __attribute__((noinline)) void initTimer() { - if (!timerRunning) { - timer1_disable(); - ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); - ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); - timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); - timerRunning = true; - timer1_write(microsecondsToClockCycles(10)); - } -} - -static ICACHE_RAM_ATTR void forceTimerInterrupt() { - if (T1L > microsecondsToClockCycles(10)) { - T1L = microsecondsToClockCycles(10); - } -} - -// PWM implementation using special purpose state machine -// -// Keep an ordered list of pins with the delta in cycles between each -// element, with a terminal entry making up the remainder of the PWM -// period. With this method sum(all deltas) == PWM period clock cycles. -// -// At t=0 set all pins high and set the timeout for the 1st edge. -// On interrupt, if we're at the last element reset to t=0 state -// Otherwise, clear that pin down and set delay for next element -// and so forth. - -constexpr int maxPWMs = 8; - -// PWM machine state -typedef struct PWMState { - uint32_t mask = 0; // Bitmask of active pins - uint32_t cnt = 0; // How many entries - uint32_t idx = 0; // Where the state machine is along the list - uint8_t pin[maxPWMs + 1]; - uint32_t delta[maxPWMs + 1]; - uint32_t nextServiceCycle; // Clock cycle for next step - struct PWMState *pwmUpdate; // Set by main code, cleared by ISR -} PWMState; - -static PWMState pwmState; -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() { - if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { - ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); - timer1_disable(); - timer1_isr_init(); - timerRunning = false; - } -} - -// 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 -void _setPWMFreq(uint32_t freq) { - // Convert frequency into clock cycles - 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) { - PWMState p; // The working copy since we can't edit the one in use - p.cnt = 0; - for (uint32_t i = 0; i < pwmState.cnt; i++) { - auto pin = pwmState.pin[i]; - _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles); - } - // Update and wait for mailbox to be emptied - initTimer(); - _notifyPWM(&p, true); - disableIdleTimer(); - } -} - -// Helper routine to remove an entry from the state machine -// and clean up any marked-off entries -static void _cleanAndRemovePWM(PWMState *p, int pin) { - uint32_t leftover = 0; - uint32_t in, out; - for (in = 0, out = 0; in < p->cnt; in++) { - if ((p->pin[in] != pin) && (p->mask & (1<pin[in]))) { - p->pin[out] = p->pin[in]; - p->delta[out] = p->delta[in] + leftover; - leftover = 0; - out++; - } else { - leftover += p->delta[in]; - p->mask &= ~(1<pin[in]); - } - } - p->cnt = out; - // Final pin is never used: p->pin[out] = 0xff; - p->delta[out] = p->delta[in] + leftover; -} - - -// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%)) -ICACHE_RAM_ATTR bool _stopPWM(int pin) { - if (!((1<= _pwmPeriod) { - _stopPWM(pin); - digitalWrite(pin, HIGH); - return; - } - - if (p.cnt == 0) { - // Starting up from scratch, special case 1st element and PWM period - p.pin[0] = pin; - p.delta[0] = cc; - // Final pin is never used: p.pin[1] = 0xff; - p.delta[1] = _pwmPeriod - cc; - } else { - uint32_t ttl = 0; - uint32_t i; - // Skip along until we're at the spot to insert - for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) { - ttl += p.delta[i]; - } - // Shift everything out by one to make space for new edge - for (int32_t j = p.cnt; j >= (int)i; j--) { - 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 - p.pin[i] = 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.cnt++; - p.mask |= 1<= maxPWMs) { - return false; // No space left - } - - _addPWMtoList(p, pin, val, range); - - // Set mailbox and wait for ISR to copy it over - initTimer(); - _notifyPWM(&p, true); - disableIdleTimer(); - return true; -} - -// 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() -// first, then it will immediately begin. -int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) { - return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS)); -} - -int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) { - if ((pin > 16) || isFlashInterfacePin(pin)) { - return false; - } - Waveform *wave = &wvfState.waveform[pin]; - wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0; - if (runTimeCycles && !wave->expiryCycle) { - 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<timeHighCycles = timeHighCycles; - wave->desiredHighCycles = timeHighCycles; - wave->timeLowCycles = timeLowCycles; - wave->desiredLowCycles = timeLowCycles; - wave->lastEdge = 0; - wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); - wvfState.waveformToEnable |= mask; - MEMBARRIER(); - initTimer(); - forceTimerInterrupt(); - while (wvfState.waveformToEnable) { - delay(0); // Wait for waveform to update - // No mem barrier here, the call to a global function implies global state updated - } - } - - 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 -#pragma GCC optimize ("O2") - -// Normally would not want two copies like this, but due to different -// optimization levels the inline attribute gets lost if we try the -// other version. -static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { - uint32_t ccount; - __asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); - return ccount; -} - -static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) { - if (a < b) { - return a; - } - return b; -} - -// Stops a waveform on a pin -int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { - // Can't possibly need to stop anything if there is no timer active - if (!timerRunning) { - return false; - } - // 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 - uint32_t mask = 1<> (turbo ? 0 : 1)) -#endif - - -static ICACHE_RAM_ATTR void timer1Interrupt() { - // Flag if the core is at 160 MHz, for use by adjust() - bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false; - - uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS); - uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14); - - if (wvfState.waveformToEnable || wvfState.waveformToDisable) { - // Handle enable/disable requests from main app - wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off - wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started - wvfState.waveformToEnable = 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) - wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; - // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) - wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); - } else if (!pwmState.cnt && pwmState.pwmUpdate) { - // Start up the PWM generator by copying from the mailbox - pwmState.cnt = 1; - pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 - pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! - // No need for mem barrier here. Global must be written by IRQ exit - } - - bool done = false; - if (wvfState.waveformEnabled || pwmState.cnt) { - do { - nextEventCycles = microsecondsToClockCycles(MAXIRQUS); - - // PWM state machine implementation - if (pwmState.cnt) { - int32_t cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ(); - if (cyclesToGo < 0) { - if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new - if (pwmState.pwmUpdate) { - // Do the memory copy from temp to global and clear mailbox - pwmState = *(PWMState*)pwmState.pwmUpdate; - } - GPOS = pwmState.mask; // Set all active pins high - if (pwmState.mask & (1<<16)) { - GP16O = 1; - } - pwmState.idx = 0; - } else { - do { - // Drop the pin at this edge - if (pwmState.mask & (1<expiryCycle) { - int32_t expiryToGo = wave->expiryCycle - now; - if (expiryToGo < 0) { - // Done, remove! - if (i == 16) { - GP16O = 0; - } - GPOC = mask; - wvfState.waveformEnabled &= ~mask; - continue; - } - } - - // Check for toggles - int32_t cyclesToGo = wave->nextServiceCycle - now; - if (cyclesToGo < 0) { - uint32_t nextEdgeCycles; - uint32_t desired = 0; - uint32_t *timeToUpdate; - wvfState.waveformState ^= mask; - if (wvfState.waveformState & mask) { - if (i == 16) { - GP16O = 1; - } - GPOS = mask; - - if (wvfState.waveformToChange & mask) { - // Copy over next full-cycle timings - wave->timeHighCycles = wvfState.waveformNewHigh; - wave->desiredHighCycles = wvfState.waveformNewHigh; - wave->timeLowCycles = wvfState.waveformNewLow; - wave->desiredLowCycles = wvfState.waveformNewLow; - wave->lastEdge = 0; - wvfState.waveformToChange = 0; - } - if (wave->lastEdge) { - desired = wave->desiredLowCycles; - timeToUpdate = &wave->timeLowCycles; - } - nextEdgeCycles = wave->timeHighCycles; - } else { - if (i == 16) { - GP16O = 0; - } - GPOC = mask; - desired = wave->desiredHighCycles; - timeToUpdate = &wave->timeHighCycles; - nextEdgeCycles = wave->timeLowCycles; - } - 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; - } - } - nextEdgeCycles = adjust(nextEdgeCycles); - wave->nextServiceCycle = now + nextEdgeCycles; - nextEventCycles = min_u32(nextEventCycles, nextEdgeCycles); - wave->lastEdge = now; - } else { - uint32_t deltaCycles = wave->nextServiceCycle - now; - nextEventCycles = min_u32(nextEventCycles, deltaCycles); - } - } - - // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur - uint32_t now = GetCycleCountIRQ(); - int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles); - int32_t cyclesLeftTimeout = timeoutCycle - now; - done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0); - } while (!done); - } // if (wvfState.waveformEnabled) - - if (wvfState.timer1CB) { - nextEventCycles = min_u32(nextEventCycles, wvfState.timer1CB()); - } - - if (nextEventCycles < microsecondsToClockCycles(5)) { - nextEventCycles = microsecondsToClockCycles(5); - } - nextEventCycles -= DELTAIRQ; - - // Do it here instead of global function to save time and because we know it's edge-IRQ - T1L = nextEventCycles >> (turbo ? 1 : 0); -} - -}; - -#endif // ESP8266 \ No newline at end of file From 187809300b9d1d2c7b2bf55958249251602fccd0 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 19:17:37 +0200 Subject: [PATCH 02/10] Delete core_esp8266_wiring_digital.cpp --- tasmota/core_esp8266_wiring_digital.cpp | 274 ------------------------ 1 file changed, 274 deletions(-) delete mode 100644 tasmota/core_esp8266_wiring_digital.cpp diff --git a/tasmota/core_esp8266_wiring_digital.cpp b/tasmota/core_esp8266_wiring_digital.cpp deleted file mode 100644 index 7fb3ccb00..000000000 --- a/tasmota/core_esp8266_wiring_digital.cpp +++ /dev/null @@ -1,274 +0,0 @@ -/* - digital.c - wiring digital implementation for esp8266 - - Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifdef ESP8266 - -#define ARDUINO_MAIN -#include "wiring_private.h" -#include "pins_arduino.h" -#include "c_types.h" -#include "eagle_soc.h" -#include "ets_sys.h" -#include "user_interface.h" -#include "core_esp8266_waveform.h" -#include "interrupts.h" - -extern "C" { - -// Internal-only calls, not for applications -extern void _setPWMFreq(uint32_t freq); -extern bool _stopPWM(int pin); -extern bool _setPWM(int pin, uint32_t val, uint32_t range); -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 }; - -extern void __pinMode(uint8_t pin, uint8_t mode) { - if(pin < 16){ - if(mode == SPECIAL){ - GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) - GPEC = (1 << pin); //Disable - GPF(pin) = GPFFS(GPFFS_BUS(pin));//Set mode to BUS (RX0, TX0, TX1, SPI, HSPI or CLK depending in the pin) - if(pin == 3) GPF(pin) |= (1 << GPFPU);//enable pullup on RX - } else if(mode & FUNCTION_0){ - GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) - GPEC = (1 << pin); //Disable - GPF(pin) = GPFFS((mode >> 4) & 0x07); - if(pin == 13 && mode == FUNCTION_4) GPF(pin) |= (1 << GPFPU);//enable pullup on RX - } else if(mode == OUTPUT || mode == OUTPUT_OPEN_DRAIN){ - GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO - GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) - if(mode == OUTPUT_OPEN_DRAIN) GPC(pin) |= (1 << GPCD); - GPES = (1 << pin); //Enable - } else if(mode == INPUT || mode == INPUT_PULLUP){ - GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO - GPEC = (1 << pin); //Disable - GPC(pin) = (GPC(pin) & (0xF << GPCI)) | (1 << GPCD); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) - if(mode == INPUT_PULLUP) { - GPF(pin) |= (1 << GPFPU); // Enable Pullup - } - } else if(mode == WAKEUP_PULLUP || mode == WAKEUP_PULLDOWN){ - GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO - GPEC = (1 << pin); //Disable - if(mode == WAKEUP_PULLUP) { - GPF(pin) |= (1 << GPFPU); // Enable Pullup - GPC(pin) = (1 << GPCD) | (4 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(LOW) | WAKEUP_ENABLE(ENABLED) - } else { - GPF(pin) |= (1 << GPFPD); // Enable Pulldown - GPC(pin) = (1 << GPCD) | (5 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(HIGH) | WAKEUP_ENABLE(ENABLED) - } - } - } else if(pin == 16){ - GPF16 = GP16FFS(GPFFS_GPIO(pin));//Set mode to GPIO - GPC16 = 0; - if(mode == INPUT || mode == INPUT_PULLDOWN_16){ - if(mode == INPUT_PULLDOWN_16){ - GPF16 |= (1 << GP16FPD);//Enable Pulldown - } - GP16E &= ~1; - } else if(mode == OUTPUT){ - GP16E |= 1; - } - } -} - -extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) { - stopWaveform(pin); // Disable any tone - _stopPWM(pin); // ...and any analogWrite - if(pin < 16){ - if(val) GPOS = (1 << pin); - else GPOC = (1 << pin); - } else if(pin == 16){ - if(val) GP16O |= 1; - else GP16O &= ~1; - } -} - -extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) { - if(pin < 16){ - return GPIP(pin); - } else if(pin == 16){ - return GP16I & 0x01; - } - return 0; -} - -/* - GPIO INTERRUPTS -*/ - -typedef void (*voidFuncPtr)(void); -typedef void (*voidFuncPtrArg)(void*); - -typedef struct { - uint8_t mode; - voidFuncPtr fn; - void * arg; - bool functional; -} interrupt_handler_t; - -//duplicate from functionalInterrupt.h keep in sync -typedef struct InterruptInfo { - uint8_t pin; - uint8_t value; - uint32_t micro; -} InterruptInfo; - -typedef struct { - InterruptInfo* interruptInfo; - void* functionInfo; -} ArgStructure; - -static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, }; -static uint32_t interrupt_reg = 0; - -void ICACHE_RAM_ATTR interrupt_handler(void*) -{ - uint32_t status = GPIE; - GPIEC = status;//clear them interrupts - uint32_t levels = GPI; - if(status == 0 || interrupt_reg == 0) return; - ETS_GPIO_INTR_DISABLE(); - int i = 0; - uint32_t changedbits = status & interrupt_reg; - while(changedbits){ - while(!(changedbits & (1 << i))) i++; - changedbits &= ~(1 << i); - interrupt_handler_t *handler = &interrupt_handlers[i]; - if (handler->fn && - (handler->mode == CHANGE || - (handler->mode & 1) == !!(levels & (1 << i)))) { - // to make ISR compatible to Arduino AVR model where interrupts are disabled - // we disable them before we call the client ISR - esp8266::InterruptLock irqLock; // stop other interrupts - if (handler->functional) - { - ArgStructure* localArg = (ArgStructure*)handler->arg; - if (localArg && localArg->interruptInfo) - { - localArg->interruptInfo->pin = i; - localArg->interruptInfo->value = __digitalRead(i); - localArg->interruptInfo->micro = micros(); - } - } - if (handler->arg) - { - ((voidFuncPtrArg)handler->fn)(handler->arg); - } - else - { - handler->fn(); - } - } - } - ETS_GPIO_INTR_ENABLE(); -} - -extern void cleanupFunctional(void* arg); - -static void set_interrupt_handlers(uint8_t pin, voidFuncPtr userFunc, void* arg, uint8_t mode, bool functional) -{ - interrupt_handler_t* handler = &interrupt_handlers[pin]; - handler->mode = mode; - handler->fn = userFunc; - if (handler->functional && handler->arg) // Clean when new attach without detach - { - cleanupFunctional(handler->arg); - } - handler->arg = arg; - handler->functional = functional; -} - -extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode, bool functional) -{ - // #5780 - // https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map - if ((uint32_t)userFunc >= 0x40200000) - { - // ISR not in IRAM - ::printf((PGM_P)F("ISR not in IRAM!\r\n")); - abort(); - } - - if(pin < 16) { - ETS_GPIO_INTR_DISABLE(); - set_interrupt_handlers(pin, (voidFuncPtr)userFunc, arg, mode, functional); - interrupt_reg |= (1 << pin); - GPC(pin) &= ~(0xF << GPCI);//INT mode disabled - GPIEC = (1 << pin); //Clear Interrupt for this pin - GPC(pin) |= ((mode & 0xF) << GPCI);//INT mode "mode" - ETS_GPIO_INTR_ATTACH(interrupt_handler, &interrupt_reg); - ETS_GPIO_INTR_ENABLE(); - } -} - -extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode) -{ - __attachInterruptFunctionalArg(pin, userFunc, arg, mode, false); -} - -extern void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin) { - if (pin < 16) - { - ETS_GPIO_INTR_DISABLE(); - GPC(pin) &= ~(0xF << GPCI);//INT mode disabled - GPIEC = (1 << pin); //Clear Interrupt for this pin - interrupt_reg &= ~(1 << pin); - set_interrupt_handlers(pin, nullptr, nullptr, 0, false); - if (interrupt_reg) - { - ETS_GPIO_INTR_ENABLE(); - } - } -} - -extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int mode) -{ - __attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, 0, mode, false); -} - -extern void __resetPins() { - for (int i = 0; i <= 16; ++i) { - if (!isFlashInterfacePin(i)) - pinMode(i, INPUT); - } -} - -extern void initPins() { - //Disable UART interrupts - system_set_os_print(0); - U0IE = 0; - U1IE = 0; - - resetPins(); -} - -extern void resetPins() __attribute__ ((weak, alias("__resetPins"))); -extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode"))); -extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite"))); -extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead"), nothrow)); -extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt"))); -extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void* arg, int mode) __attribute__((weak, alias("__attachInterruptArg"))); -extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt"))); - -}; - -#endif // ESP8266 \ No newline at end of file From a987a60c507d90db0456c34fcd96efd1a9bc38a4 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 19:17:50 +0200 Subject: [PATCH 03/10] Delete core_esp8266_wiring_pwm.cpp --- tasmota/core_esp8266_wiring_pwm.cpp | 76 ----------------------------- 1 file changed, 76 deletions(-) delete mode 100644 tasmota/core_esp8266_wiring_pwm.cpp diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp deleted file mode 100644 index 5189fa8f5..000000000 --- a/tasmota/core_esp8266_wiring_pwm.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - pwm.c - analogWrite implementation for esp8266 - - Use the shared TIMER1 utilities to generate PWM signals - - Original Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifdef ESP8266 - -#include -#include "core_esp8266_waveform.h" - -extern "C" { - -// Internal-only calls, not for applications -extern void _setPWMFreq(uint32_t freq); -extern bool _stopPWM(int pin); -extern bool _setPWM(int pin, uint32_t val, uint32_t range); - -static int32_t analogScale = PWMRANGE; - -extern void __analogWriteRange(uint32_t range) { - if (range > 0) { - analogScale = range; - } -} - -extern void __analogWriteFreq(uint32_t freq) { - if (freq < 40) { - freq = 40; - } else if (freq > 60000) { - freq = 60000; - } else { - freq = freq; - } - _setPWMFreq(freq); -} - -extern void __analogWrite(uint8_t pin, int val) { - if (pin > 16) { - return; - } - - if (val < 0) { - val = 0; - } else if (val > analogScale) { - val = analogScale; - } - - pinMode(pin, OUTPUT); - _setPWM(pin, val, analogScale); -} - -extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); -extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq"))); -extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange"))); - -}; - -#endif // ESP8266 \ No newline at end of file From e7c6f4645f1761bffac6d606e5ce01bcf0f59702 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 19:21:48 +0200 Subject: [PATCH 04/10] Use PWM files from Arduino PR 7022 --- tasmota/core_esp8266_waveform.cpp | 436 ++++++++++++++++++++++++ tasmota/core_esp8266_wiring_digital.cpp | 264 ++++++++++++++ tasmota/core_esp8266_wiring_pwm.cpp | 79 +++++ 3 files changed, 779 insertions(+) create mode 100644 tasmota/core_esp8266_waveform.cpp create mode 100644 tasmota/core_esp8266_wiring_digital.cpp create mode 100644 tasmota/core_esp8266_wiring_pwm.cpp diff --git a/tasmota/core_esp8266_waveform.cpp b/tasmota/core_esp8266_waveform.cpp new file mode 100644 index 000000000..952e1fd19 --- /dev/null +++ b/tasmota/core_esp8266_waveform.cpp @@ -0,0 +1,436 @@ +/* + esp8266_waveform - General purpose waveform generation and control, + supporting outputs on all pins in parallel. + + Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + Copyright (c) 2020 Dirk O. Kaar. + + 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 + set to 1-shot mode and is always loaded with the time until the next edge + of any live waveforms. + + Up to one waveform generator per pin supported. + + Each waveform generator is synchronized to the ESP clock cycle counter, not the + timer. This allows for removing interrupt jitter and delay as the counter + always increments once per 80MHz clock. Changes to a waveform are + contiguous and only take effect on the next waveform transition, + allowing for smooth transitions. + + This replaces older tone(), analogWrite(), and the Servo classes. + + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() + clock cycle time, or an interval measured in clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "core_esp8266_waveform.h" +#include +#include "ets_sys.h" +#include + +// Timer is 80MHz fixed. 160MHz CPU frequency need scaling. +constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160; +// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz +constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); +// Maximum servicing time for any single IRQ +constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); +// The latency between in-ISR rearming of the timer and the earliest firing +constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2); +// The SDK and hardware take some time to actually get to our NMI code +constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ? + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); + +// for INFINITE, the NMI proceeds on the waveform without expiry deadline. +// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. +// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. +// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. +enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3}; + +// Waveform generator can create tones, PWM, and servos +typedef struct { + uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count + uint32_t endDutyCcy; // ESP clock cycle when going from duty to off + int32_t dutyCcys; // Set next off cycle at low->high to maintain phase + int32_t adjDutyCcys; // Temporary correction for next period + int32_t periodCcys; // Set next phase cycle at low->high to maintain phase + uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count + WaveformMode mode; + int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin + bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings +} Waveform; + +namespace { + + static struct { + Waveform pins[17]; // State of all possible pins + uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code + uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code + + // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine + int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform + int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation + + uint32_t(*timer1CB)() = nullptr; + + bool timer1Running = false; + + uint32_t nextEventCcy; + } waveform; + +} + +// Interrupt on/off control +static ICACHE_RAM_ATTR void timer1Interrupt(); + +// Non-speed critical bits +#pragma GCC optimize ("Os") + +static void initTimer() { + timer1_disable(); + ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); + ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); + timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); + waveform.timer1Running = true; + timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste +} + +static void ICACHE_RAM_ATTR deinitTimer() { + ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); + timer1_disable(); + timer1_isr_init(); + waveform.timer1Running = false; +} + +extern "C" { + +// Set a callback. Pass in NULL to stop it +void setTimer1Callback(uint32_t (*fn)()) { + waveform.timer1CB = fn; + std::atomic_thread_fence(std::memory_order_acq_rel); + if (!waveform.timer1Running && fn) { + initTimer(); + } else if (waveform.timer1Running && !fn && !waveform.enabled) { + deinitTimer(); + } +} + +int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, + uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { + return startWaveformClockCycles(pin, + microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS), + microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm); +} + +// 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() +// first, then it will immediately begin. +int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, + uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) { + uint32_t periodCcys = highCcys + lowCcys; + if (periodCcys < MAXIRQTICKSCCYS) { + if (!highCcys) { + periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; + } + else if (!lowCcys) { + highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; + } + } + // sanity checks, including mixed signed/unsigned arithmetic safety + if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || + static_cast(periodCcys) <= 0 || + static_cast(highCcys) < 0 || static_cast(lowCcys) < 0) { + return false; + } + Waveform& wave = waveform.pins[pin]; + wave.dutyCcys = highCcys; + wave.adjDutyCcys = 0; + wave.periodCcys = periodCcys; + wave.autoPwm = autoPwm; + + std::atomic_thread_fence(std::memory_order_acquire); + const uint32_t pinBit = 1UL << pin; + if (!(waveform.enabled & pinBit)) { + // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR + wave.nextPeriodCcy = phaseOffsetCcys; + wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count + wave.mode = WaveformMode::INIT; + wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; + if (!wave.dutyCcys) { + // If initially at zero duty cycle, force GPIO off + if (pin == 16) { + GP16O = 0; + } + else { + GPOC = pinBit; + } + } + std::atomic_thread_fence(std::memory_order_release); + waveform.toSetBits = 1UL << pin; + std::atomic_thread_fence(std::memory_order_release); + if (!waveform.timer1Running) { + initTimer(); + } + else if (T1V > IRQLATENCYCCYS) { + // Must not interfere if Timer is due shortly + timer1_write(IRQLATENCYCCYS); + } + } + else { + wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI + std::atomic_thread_fence(std::memory_order_release); + wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count + if (runTimeCcys) { + wave.mode = WaveformMode::UPDATEEXPIRY; + std::atomic_thread_fence(std::memory_order_release); + waveform.toSetBits = 1UL << pin; + } + } + std::atomic_thread_fence(std::memory_order_acq_rel); + while (waveform.toSetBits) { + delay(0); // Wait for waveform to update + std::atomic_thread_fence(std::memory_order_acquire); + } + return true; +} + +// Stops a waveform on a pin +int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { + // Can't possibly need to stop anything if there is no timer active + if (!waveform.timer1Running) { + return false; + } + // 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 + std::atomic_thread_fence(std::memory_order_acquire); + const uint32_t pinBit = 1UL << pin; + if (waveform.enabled & pinBit) { + waveform.toDisableBits = 1UL << pin; + std::atomic_thread_fence(std::memory_order_release); + // Must not interfere if Timer is due shortly + if (T1V > IRQLATENCYCCYS) { + timer1_write(IRQLATENCYCCYS); + } + while (waveform.toDisableBits) { + /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ + std::atomic_thread_fence(std::memory_order_acquire); + } + } + if (!waveform.enabled && !waveform.timer1CB) { + deinitTimer(); + } + return true; +} + +}; + +// Speed critical bits +#pragma GCC optimize ("O2") + +// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. +// Using constexpr makes sure that the CPU clock frequency is compile-time fixed. +static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) { + if (ISCPUFREQ160MHZ) { + return isCPU2X ? ccys : (ccys >> 1); + } + else { + return isCPU2X ? (ccys << 1) : ccys; + } +} + +static ICACHE_RAM_ATTR void timer1Interrupt() { + const uint32_t isrStartCcy = ESP.getCycleCount(); + int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; + const bool isCPU2X = CPU2X & 1; + if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { + // Handle enable/disable requests from main app. + waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off + // 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) + waveform.toDisableBits = 0; + } + + if (waveform.toSetBits) { + const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1; + Waveform& wave = waveform.pins[toSetPin]; + switch (wave.mode) { + case WaveformMode::INIT: + waveform.states &= ~waveform.toSetBits; // Clear the state of any just started + if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { + wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; + } + else { + wave.nextPeriodCcy = waveform.nextEventCcy; + } + if (!wave.expiryCcy) { + wave.mode = WaveformMode::INFINITE; + break; + } + // fall through + case WaveformMode::UPDATEEXPIRY: + // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X); + wave.mode = WaveformMode::EXPIRES; + break; + default: + break; + } + waveform.toSetBits = 0; + } + + // Exit the loop if the next event, if any, is sufficiently distant. + const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; + uint32_t busyPins = waveform.enabled; + waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; + + uint32_t now = ESP.getCycleCount(); + uint32_t isrNextEventCcy = now; + while (busyPins) { + if (static_cast(isrNextEventCcy - now) > IRQLATENCYCCYS) { + waveform.nextEventCcy = isrNextEventCcy; + break; + } + isrNextEventCcy = waveform.nextEventCcy; + uint32_t loopPins = busyPins; + while (loopPins) { + const int pin = __builtin_ffsl(loopPins) - 1; + const uint32_t pinBit = 1UL << pin; + loopPins ^= pinBit; + + Waveform& wave = waveform.pins[pin]; + + if (clockDrift) { + wave.endDutyCcy += clockDrift; + wave.nextPeriodCcy += clockDrift; + wave.expiryCcy += clockDrift; + } + + uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; + if (WaveformMode::EXPIRES == wave.mode && + static_cast(waveNextEventCcy - wave.expiryCcy) >= 0 && + static_cast(now - wave.expiryCcy) >= 0) { + // Disable any waveforms that are done + waveform.enabled ^= pinBit; + busyPins ^= pinBit; + } + else { + const int32_t overshootCcys = now - waveNextEventCcy; + if (overshootCcys >= 0) { + const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X); + if (waveform.states & pinBit) { + // active configuration and forward are 100% duty + if (wave.periodCcys == wave.dutyCcys) { + wave.nextPeriodCcy += periodCcys; + wave.endDutyCcy = wave.nextPeriodCcy; + } + else { + if (wave.autoPwm) { + wave.adjDutyCcys += overshootCcys; + } + waveform.states ^= pinBit; + if (16 == pin) { + GP16O = 0; + } + else { + GPOC = pinBit; + } + } + waveNextEventCcy = wave.nextPeriodCcy; + } + else { + wave.nextPeriodCcy += periodCcys; + if (!wave.dutyCcys) { + wave.endDutyCcy = wave.nextPeriodCcy; + } + else { + int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X); + if (dutyCcys <= wave.adjDutyCcys) { + dutyCcys >>= 1; + wave.adjDutyCcys -= dutyCcys; + } + else if (wave.adjDutyCcys) { + dutyCcys -= wave.adjDutyCcys; + wave.adjDutyCcys = 0; + } + wave.endDutyCcy = now + dutyCcys; + if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) > 0) { + wave.endDutyCcy = wave.nextPeriodCcy; + } + waveform.states |= pinBit; + if (16 == pin) { + GP16O = 1; + } + else { + GPOS = pinBit; + } + } + waveNextEventCcy = wave.endDutyCcy; + } + + if (WaveformMode::EXPIRES == wave.mode && static_cast(waveNextEventCcy - wave.expiryCcy) > 0) { + waveNextEventCcy = wave.expiryCcy; + } + } + + if (static_cast(waveNextEventCcy - isrTimeoutCcy) >= 0) { + busyPins ^= pinBit; + if (static_cast(waveform.nextEventCcy - waveNextEventCcy) > 0) { + waveform.nextEventCcy = waveNextEventCcy; + } + } + else if (static_cast(isrNextEventCcy - waveNextEventCcy) > 0) { + isrNextEventCcy = waveNextEventCcy; + } + } + now = ESP.getCycleCount(); + } + clockDrift = 0; + } + + int32_t callbackCcys = 0; + if (waveform.timer1CB) { + callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB()), isCPU2X); + } + now = ESP.getCycleCount(); + int32_t nextEventCcys = waveform.nextEventCcy - now; + // Account for unknown duration of timer1CB(). + if (waveform.timer1CB && nextEventCcys > callbackCcys) { + waveform.nextEventCcy = now + callbackCcys; + nextEventCcys = callbackCcys; + } + + // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. + int32_t deltaIrqCcys = DELTAIRQCCYS; + int32_t irqLatencyCcys = IRQLATENCYCCYS; + if (isCPU2X) { + nextEventCcys >>= 1; + deltaIrqCcys >>= 1; + irqLatencyCcys >>= 1; + } + + // Firing timer too soon, the NMI occurs before ISR has returned. + if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) { + waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS; + nextEventCcys = irqLatencyCcys; + } + else { + nextEventCcys -= deltaIrqCcys; + } + + // Register access is fast and edge IRQ was configured before. + T1L = nextEventCcys; +} diff --git a/tasmota/core_esp8266_wiring_digital.cpp b/tasmota/core_esp8266_wiring_digital.cpp new file mode 100644 index 000000000..9c15703e0 --- /dev/null +++ b/tasmota/core_esp8266_wiring_digital.cpp @@ -0,0 +1,264 @@ +/* + digital.c - wiring digital implementation for esp8266 + + Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#define ARDUINO_MAIN +#include "wiring_private.h" +#include "pins_arduino.h" +#include "c_types.h" +#include "eagle_soc.h" +#include "ets_sys.h" +#include "user_interface.h" +#include "core_esp8266_waveform.h" +#include "interrupts.h" + +extern "C" { + +volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 }; + +extern void __pinMode(uint8_t pin, uint8_t mode) { + if(pin < 16){ + if(mode == SPECIAL){ + GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) + GPEC = (1 << pin); //Disable + GPF(pin) = GPFFS(GPFFS_BUS(pin));//Set mode to BUS (RX0, TX0, TX1, SPI, HSPI or CLK depending in the pin) + if(pin == 3) GPF(pin) |= (1 << GPFPU);//enable pullup on RX + } else if(mode & FUNCTION_0){ + GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) + GPEC = (1 << pin); //Disable + GPF(pin) = GPFFS((mode >> 4) & 0x07); + if(pin == 13 && mode == FUNCTION_4) GPF(pin) |= (1 << GPFPU);//enable pullup on RX + } else if(mode == OUTPUT || mode == OUTPUT_OPEN_DRAIN){ + GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO + GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) + if(mode == OUTPUT_OPEN_DRAIN) GPC(pin) |= (1 << GPCD); + GPES = (1 << pin); //Enable + } else if(mode == INPUT || mode == INPUT_PULLUP){ + GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO + GPEC = (1 << pin); //Disable + GPC(pin) = (GPC(pin) & (0xF << GPCI)) | (1 << GPCD); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) + if(mode == INPUT_PULLUP) { + GPF(pin) |= (1 << GPFPU); // Enable Pullup + } + } else if(mode == WAKEUP_PULLUP || mode == WAKEUP_PULLDOWN){ + GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO + GPEC = (1 << pin); //Disable + if(mode == WAKEUP_PULLUP) { + GPF(pin) |= (1 << GPFPU); // Enable Pullup + GPC(pin) = (1 << GPCD) | (4 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(LOW) | WAKEUP_ENABLE(ENABLED) + } else { + GPF(pin) |= (1 << GPFPD); // Enable Pulldown + GPC(pin) = (1 << GPCD) | (5 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(HIGH) | WAKEUP_ENABLE(ENABLED) + } + } + } else if(pin == 16){ + GPF16 = GP16FFS(GPFFS_GPIO(pin));//Set mode to GPIO + GPC16 = 0; + if(mode == INPUT || mode == INPUT_PULLDOWN_16){ + if(mode == INPUT_PULLDOWN_16){ + GPF16 |= (1 << GP16FPD);//Enable Pulldown + } + GP16E &= ~1; + } else if(mode == OUTPUT){ + GP16E |= 1; + } + } +} + +extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) { + stopWaveform(pin); + if(pin < 16){ + if(val) GPOS = (1 << pin); + else GPOC = (1 << pin); + } else if(pin == 16){ + if(val) GP16O |= 1; + else GP16O &= ~1; + } +} + +extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) { + if(pin < 16){ + return GPIP(pin); + } else if(pin == 16){ + return GP16I & 0x01; + } + return 0; +} + +/* + GPIO INTERRUPTS +*/ + +typedef void (*voidFuncPtr)(void); +typedef void (*voidFuncPtrArg)(void*); + +typedef struct { + uint8_t mode; + voidFuncPtr fn; + void * arg; + bool functional; +} interrupt_handler_t; + +//duplicate from functionalInterrupt.h keep in sync +typedef struct InterruptInfo { + uint8_t pin; + uint8_t value; + uint32_t micro; +} InterruptInfo; + +typedef struct { + InterruptInfo* interruptInfo; + void* functionInfo; +} ArgStructure; + +static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, }; +static uint32_t interrupt_reg = 0; + +void ICACHE_RAM_ATTR interrupt_handler(void *arg, void *frame) +{ + (void) arg; + (void) frame; + uint32_t status = GPIE; + GPIEC = status;//clear them interrupts + uint32_t levels = GPI; + if(status == 0 || interrupt_reg == 0) return; + ETS_GPIO_INTR_DISABLE(); + int i = 0; + uint32_t changedbits = status & interrupt_reg; + while(changedbits){ + while(!(changedbits & (1 << i))) i++; + changedbits &= ~(1 << i); + interrupt_handler_t *handler = &interrupt_handlers[i]; + if (handler->fn && + (handler->mode == CHANGE || + (handler->mode & 1) == !!(levels & (1 << i)))) { + // to make ISR compatible to Arduino AVR model where interrupts are disabled + // we disable them before we call the client ISR + esp8266::InterruptLock irqLock; // stop other interrupts + if (handler->functional) + { + ArgStructure* localArg = (ArgStructure*)handler->arg; + if (localArg && localArg->interruptInfo) + { + localArg->interruptInfo->pin = i; + localArg->interruptInfo->value = __digitalRead(i); + localArg->interruptInfo->micro = micros(); + } + } + if (handler->arg) + { + ((voidFuncPtrArg)handler->fn)(handler->arg); + } + else + { + handler->fn(); + } + } + } + ETS_GPIO_INTR_ENABLE(); +} + +extern void cleanupFunctional(void* arg); + +static void set_interrupt_handlers(uint8_t pin, voidFuncPtr userFunc, void* arg, uint8_t mode, bool functional) +{ + interrupt_handler_t* handler = &interrupt_handlers[pin]; + handler->mode = mode; + handler->fn = userFunc; + if (handler->functional && handler->arg) // Clean when new attach without detach + { + cleanupFunctional(handler->arg); + } + handler->arg = arg; + handler->functional = functional; +} + +extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode, bool functional) +{ + // #5780 + // https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map + if ((uint32_t)userFunc >= 0x40200000) + { + // ISR not in IRAM + ::printf((PGM_P)F("ISR not in IRAM!\r\n")); + abort(); + } + + if(pin < 16) { + ETS_GPIO_INTR_DISABLE(); + set_interrupt_handlers(pin, (voidFuncPtr)userFunc, arg, mode, functional); + interrupt_reg |= (1 << pin); + GPC(pin) &= ~(0xF << GPCI);//INT mode disabled + GPIEC = (1 << pin); //Clear Interrupt for this pin + GPC(pin) |= ((mode & 0xF) << GPCI);//INT mode "mode" + ETS_GPIO_INTR_ATTACH(interrupt_handler, &interrupt_reg); + ETS_GPIO_INTR_ENABLE(); + } +} + +extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode) +{ + __attachInterruptFunctionalArg(pin, userFunc, arg, mode, false); +} + +extern void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin) { + if (pin < 16) + { + ETS_GPIO_INTR_DISABLE(); + GPC(pin) &= ~(0xF << GPCI);//INT mode disabled + GPIEC = (1 << pin); //Clear Interrupt for this pin + interrupt_reg &= ~(1 << pin); + set_interrupt_handlers(pin, nullptr, nullptr, 0, false); + if (interrupt_reg) + { + ETS_GPIO_INTR_ENABLE(); + } + } +} + +extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int mode) +{ + __attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, 0, mode, false); +} + +extern void __resetPins() { + for (int i = 0; i <= 16; ++i) { + if (!isFlashInterfacePin(i)) + pinMode(i, INPUT); + } +} + +extern void initPins() { + //Disable UART interrupts + system_set_os_print(0); + U0IE = 0; + U1IE = 0; + + resetPins(); +} + +extern void resetPins() __attribute__ ((weak, alias("__resetPins"))); +extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode"))); +extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite"))); +extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead"), nothrow)); +extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt"))); +extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void* arg, int mode) __attribute__((weak, alias("__attachInterruptArg"))); +extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt"))); + +}; diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp new file mode 100644 index 000000000..608e06bda --- /dev/null +++ b/tasmota/core_esp8266_wiring_pwm.cpp @@ -0,0 +1,79 @@ +/* + pwm.c - analogWrite implementation for esp8266 + + Use the shared TIMER1 utilities to generate PWM signals + + Original Copyright (c) 2015 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "core_esp8266_waveform.h" + +extern "C" { + +static uint32_t analogMap = 0; +static int32_t analogScale = PWMRANGE; +static uint16_t analogFreq = 1000; + +extern void __analogWriteRange(uint32_t range) { + if (range > 0) { + analogScale = range; + } +} + +extern void __analogWriteFreq(uint32_t freq) { + if (freq < 100) { + analogFreq = 100; + } else if (freq > 60000) { + analogFreq = 60000; + } else { + analogFreq = freq; + } +} + +extern void __analogWrite(uint8_t pin, int val) { + if (pin > 16) { + return; + } + uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq; + if (val < 0) { + val = 0; + } else if (val > analogScale) { + val = analogScale; + } + + if (analogMap & 1UL << pin) { + analogMap &= ~(1 << pin); + } + else { + pinMode(pin, OUTPUT); + } + uint32_t high = (analogPeriod * val) / analogScale; + uint32_t low = analogPeriod - high; + // 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) + int phaseReference = __builtin_ffs(analogMap) - 1; + if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) { + analogMap |= (1 << pin); + } +} + +extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); +extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq"))); +extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange"))); + +}; From 029a6d165e5868c743ccdea90c9bd316ab8c9d10 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 19:35:58 +0200 Subject: [PATCH 05/10] Add files via upload From ee2fea9e25e0a9b1d1e1c7c2d43a62ed58c3ecc8 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 19:39:14 +0200 Subject: [PATCH 06/10] Add files via upload --- tasmota/core_esp8266_waveform.h | 89 +++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tasmota/core_esp8266_waveform.h diff --git a/tasmota/core_esp8266_waveform.h b/tasmota/core_esp8266_waveform.h new file mode 100644 index 000000000..61cb99966 --- /dev/null +++ b/tasmota/core_esp8266_waveform.h @@ -0,0 +1,89 @@ +/* + esp8266_waveform - General purpose waveform generation and control, + supporting outputs on all pins in parallel. + + Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + Copyright (c) 2020 Dirk O. Kaar. + + 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 + set to 1-shot mode and is always loaded with the time until the next edge + of any live waveforms. + + Up to one waveform generator per pin supported. + + Each waveform generator is synchronized to the ESP clock cycle counter, not the + timer. This allows for removing interrupt jitter and delay as the counter + always increments once per 80MHz clock. Changes to a waveform are + contiguous and only take effect on the next waveform transition, + allowing for smooth transitions. + + This replaces older tone(), analogWrite(), and the Servo classes. + + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() + clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#ifndef __ESP8266_WAVEFORM_H +#define __ESP8266_WAVEFORM_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Start or change a waveform of the specified high and low times on specific pin. +// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next +// full period. +// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, +// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that. +// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio +// under load, for applications where frequency or duty cycle must not change, leave false. +// Returns true or false on success or failure. +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, + uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false); +// Start or change a waveform of the specified high and low CPU clock cycles on specific pin. +// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next +// full period. +// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, +// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that. +// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio +// under load, for applications where frequency or duty cycle must not change, leave false. +// Returns true or false on success or failure. +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, + uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false); +// Stop a waveform, if any, on the specified pin. +// Returns true or false on success or failure. +int stopWaveform(uint8_t pin); + +// Add a callback function to be called on *EVERY* timer1 trigger. The +// callback returns the number of microseconds until the next desired call. +// However, since it is called every timer1 interrupt, it may be called +// again before this period. It should therefore use the ESP Cycle Counter +// to determine whether or not to perform an operation. +// Pass in NULL to disable the callback and, if no other waveforms being +// generated, stop the timer as well. +// Make sure the CB function has the ICACHE_RAM_ATTR decorator. +void setTimer1Callback(uint32_t (*fn)()); + +#ifdef __cplusplus +} +#endif + +#endif From bcc9145a91c394ee09e33a0356a9514eb480e9ae Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 20:01:26 +0200 Subject: [PATCH 07/10] Update core_esp8266_waveform.h --- tasmota/core_esp8266_waveform.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tasmota/core_esp8266_waveform.h b/tasmota/core_esp8266_waveform.h index 61cb99966..ff5a0f56f 100644 --- a/tasmota/core_esp8266_waveform.h +++ b/tasmota/core_esp8266_waveform.h @@ -39,6 +39,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef ESP8266 + #include #ifndef __ESP8266_WAVEFORM_H @@ -87,3 +89,5 @@ void setTimer1Callback(uint32_t (*fn)()); #endif #endif + +#endif // ESP8266 From e893f8d97d0f459d24d153394277a018936f96e8 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 20:02:15 +0200 Subject: [PATCH 08/10] Update core_esp8266_waveform.cpp --- tasmota/core_esp8266_waveform.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tasmota/core_esp8266_waveform.cpp b/tasmota/core_esp8266_waveform.cpp index 952e1fd19..371e9e554 100644 --- a/tasmota/core_esp8266_waveform.cpp +++ b/tasmota/core_esp8266_waveform.cpp @@ -39,6 +39,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef ESP8266 + #include "core_esp8266_waveform.h" #include #include "ets_sys.h" @@ -434,3 +436,5 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Register access is fast and edge IRQ was configured before. T1L = nextEventCcys; } + +#endif // ESP8266 From 5322238d565028ec7f7f3a659c01100e6d646b63 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 20:02:55 +0200 Subject: [PATCH 09/10] Update core_esp8266_wiring_digital.cpp --- tasmota/core_esp8266_wiring_digital.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tasmota/core_esp8266_wiring_digital.cpp b/tasmota/core_esp8266_wiring_digital.cpp index 9c15703e0..982e1c6bf 100644 --- a/tasmota/core_esp8266_wiring_digital.cpp +++ b/tasmota/core_esp8266_wiring_digital.cpp @@ -18,6 +18,9 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef ESP8266 + #define ARDUINO_MAIN #include "wiring_private.h" #include "pins_arduino.h" @@ -262,3 +265,5 @@ extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void* arg, i extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt"))); }; + +#endif // ESP8266 From 526593d9c91193f38a34b1eaf863a8d569262a16 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Tue, 23 Jun 2020 20:03:41 +0200 Subject: [PATCH 10/10] Update core_esp8266_wiring_pwm.cpp --- tasmota/core_esp8266_wiring_pwm.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tasmota/core_esp8266_wiring_pwm.cpp b/tasmota/core_esp8266_wiring_pwm.cpp index 608e06bda..f74334e43 100644 --- a/tasmota/core_esp8266_wiring_pwm.cpp +++ b/tasmota/core_esp8266_wiring_pwm.cpp @@ -21,6 +21,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef ESP8266 + #include #include "core_esp8266_waveform.h" @@ -37,8 +39,8 @@ extern void __analogWriteRange(uint32_t range) { } extern void __analogWriteFreq(uint32_t freq) { - if (freq < 100) { - analogFreq = 100; + if (freq < 40) { + analogFreq = 40; } else if (freq > 60000) { analogFreq = 60000; } else { @@ -77,3 +79,5 @@ extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogW extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange"))); }; + +#endif // ESP8266