mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-18 16:26:31 +00:00
Merge pull request #8246 from s-hadinger/pwm_7231
Change PWM implementation to Arduino #7231
This commit is contained in:
commit
f72be91f98
@ -6,6 +6,7 @@
|
|||||||
- Add config version tag
|
- Add config version tag
|
||||||
- Add command ``SetOption73 1`` for button decoupling and send multi-press and hold MQTT messages by Federico Leoni (#8235)
|
- Add command ``SetOption73 1`` for button decoupling and send multi-press and hold MQTT messages by Federico Leoni (#8235)
|
||||||
- Add command ``SO`` as shortcut for command ``SetOption``
|
- Add command ``SO`` as shortcut for command ``SetOption``
|
||||||
|
- Change PWM implementation to Arduino #7231
|
||||||
|
|
||||||
### 8.2.0.3 20200329
|
### 8.2.0.3 20200329
|
||||||
|
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
timer.c - Timer1 library 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
|
|
||||||
|
|
||||||
// Use PWM from core 2.4.0 as all versions below 2.5.0-beta3 produce LED flickering when settings are saved to flash
|
|
||||||
#include <core_version.h>
|
|
||||||
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
|
|
||||||
#warning **** Tasmota is using v2.4.0 timer.c as planned ****
|
|
||||||
|
|
||||||
#include "wiring_private.h"
|
|
||||||
#include "pins_arduino.h"
|
|
||||||
|
|
||||||
#include "c_types.h"
|
|
||||||
#include "ets_sys.h"
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------ -
|
|
||||||
// timer 1
|
|
||||||
|
|
||||||
static volatile timercallback timer1_user_cb = NULL;
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_isr_handler(void *para){
|
|
||||||
(void) para;
|
|
||||||
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
|
|
||||||
T1I = 0;
|
|
||||||
if (timer1_user_cb) {
|
|
||||||
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
|
||||||
// we disable them before we call the client ISR
|
|
||||||
uint32_t savedPS = xt_rsil(15); // stop other interrupts
|
|
||||||
timer1_user_cb();
|
|
||||||
xt_wsr_ps(savedPS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_isr_init(void){
|
|
||||||
ETS_FRC_TIMER1_INTR_ATTACH(timer1_isr_handler, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void timer1_attachInterrupt(timercallback userFunc) {
|
|
||||||
timer1_user_cb = userFunc;
|
|
||||||
ETS_FRC1_INTR_ENABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_detachInterrupt(void) {
|
|
||||||
timer1_user_cb = 0;
|
|
||||||
TEIE &= ~TEIE1;//edge int disable
|
|
||||||
ETS_FRC1_INTR_DISABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
void timer1_enable(uint8_t divider, uint8_t int_type, uint8_t reload){
|
|
||||||
T1C = (1 << TCTE) | ((divider & 3) << TCPD) | ((int_type & 1) << TCIT) | ((reload & 1) << TCAR);
|
|
||||||
T1I = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_write(uint32_t ticks){
|
|
||||||
T1L = ((ticks)& 0x7FFFFF);
|
|
||||||
if ((T1C & (1 << TCIT)) == 0) TEIE |= TEIE1;//edge int enable
|
|
||||||
}
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_disable(void){
|
|
||||||
T1C = 0;
|
|
||||||
T1I = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
|
||||||
// timer 0
|
|
||||||
|
|
||||||
static volatile timercallback timer0_user_cb = NULL;
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer0_isr_handler(void* para){
|
|
||||||
(void) para;
|
|
||||||
if (timer0_user_cb) {
|
|
||||||
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
|
||||||
// we disable them before we call the client ISR
|
|
||||||
uint32_t savedPS = xt_rsil(15); // stop other interrupts
|
|
||||||
timer0_user_cb();
|
|
||||||
xt_wsr_ps(savedPS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void timer0_isr_init(void){
|
|
||||||
ETS_CCOMPARE0_INTR_ATTACH(timer0_isr_handler, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void timer0_attachInterrupt(timercallback userFunc) {
|
|
||||||
timer0_user_cb = userFunc;
|
|
||||||
ETS_CCOMPARE0_ENABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer0_detachInterrupt(void) {
|
|
||||||
timer0_user_cb = NULL;
|
|
||||||
ETS_CCOMPARE0_DISABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // ARDUINO_ESP8266_RELEASE
|
|
||||||
|
|
||||||
#endif // ESP8266
|
|
@ -5,13 +5,13 @@
|
|||||||
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). TIMER1 is set to 1-shot
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||||
mode and is always loaded with the time until the next edge of any live
|
set to 1-shot mode and is always loaded with the time until the next edge
|
||||||
waveforms.
|
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 cycle counter, not the
|
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
|
timer. This allows for removing interrupt jitter and delay as the counter
|
||||||
always increments once per 80MHz clock. Changes to a waveform are
|
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,
|
||||||
@ -19,8 +19,9 @@
|
|||||||
|
|
||||||
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.getCycleTime()
|
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||||
cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 160MHz).
|
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
|
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
|
||||||
@ -39,17 +40,18 @@
|
|||||||
|
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
|
|
||||||
#include <core_version.h>
|
|
||||||
#if defined(ARDUINO_ESP8266_RELEASE_2_6_1) || defined(ARDUINO_ESP8266_RELEASE_2_6_2) || defined(ARDUINO_ESP8266_RELEASE_2_6_3) || !defined(ARDUINO_ESP8266_RELEASE)
|
|
||||||
#warning **** Tasmota is using a patched PWM Arduino version as planned ****
|
|
||||||
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "ets_sys.h"
|
#include "ets_sys.h"
|
||||||
#include "core_esp8266_waveform.h"
|
#include "core_esp8266_waveform.h"
|
||||||
|
#include "user_interface.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
// Internal-only calls, not for applications
|
||||||
|
extern void _setPWMPeriodCC(uint32_t cc);
|
||||||
|
extern bool _stopPWM(int pin);
|
||||||
|
extern bool _setPWM(int pin, uint32_t cc);
|
||||||
|
extern int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
|
||||||
|
|
||||||
// Maximum delay between IRQs
|
// Maximum delay between IRQs
|
||||||
#define MAXIRQUS (10000)
|
#define MAXIRQUS (10000)
|
||||||
|
|
||||||
@ -61,8 +63,10 @@ extern "C" {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
||||||
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
||||||
uint32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform
|
uint32_t timeHighCycles; // Currently running waveform period
|
||||||
uint32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform
|
uint32_t timeLowCycles; //
|
||||||
|
uint32_t gotoTimeHighCycles; // Copied over on the next period to preserve phase
|
||||||
|
uint32_t gotoTimeLowCycles; //
|
||||||
} Waveform;
|
} Waveform;
|
||||||
|
|
||||||
static Waveform waveform[17]; // State of all possible pins
|
static Waveform waveform[17]; // State of all possible pins
|
||||||
@ -73,8 +77,11 @@ static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated
|
|||||||
static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
|
static volatile 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
|
static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
|
||||||
|
|
||||||
static uint32_t (*timer1CB)() = NULL;
|
volatile int32_t waveformToChange = -1;
|
||||||
|
volatile uint32_t waveformNewHigh = 0;
|
||||||
|
volatile uint32_t waveformNewLow = 0;
|
||||||
|
|
||||||
|
static uint32_t (*timer1CB)() = NULL;
|
||||||
|
|
||||||
// Non-speed critical bits
|
// Non-speed critical bits
|
||||||
#pragma GCC optimize ("Os")
|
#pragma GCC optimize ("Os")
|
||||||
@ -115,25 +122,187 @@ void setTimer1Callback(uint32_t (*fn)()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 edge definition
|
||||||
|
typedef struct {
|
||||||
|
unsigned int pin : 8;
|
||||||
|
unsigned int delta : 24;
|
||||||
|
} PWMEntry;
|
||||||
|
|
||||||
|
// PWM machine state
|
||||||
|
typedef struct {
|
||||||
|
uint32_t mask; // Bitmask of active pins
|
||||||
|
uint8_t cnt; // How many entries
|
||||||
|
uint8_t idx; // Where the state machine is along the list
|
||||||
|
PWMEntry edge[maxPWMs + 1]; // Include space for terminal element
|
||||||
|
uint32_t nextServiceCycle; // Clock cycle for next step
|
||||||
|
} PWMState;
|
||||||
|
|
||||||
|
static PWMState pwmState;
|
||||||
|
static volatile PWMState *pwmUpdate = nullptr; // Set by main code, cleared by ISR
|
||||||
|
static uint32_t pwmPeriod = (1000000L * system_get_cpu_freq()) / 1000;
|
||||||
|
|
||||||
|
// Called when analogWriteFreq() changed to update the PWM total period
|
||||||
|
void _setPWMPeriodCC(uint32_t cc) {
|
||||||
|
if (cc == pwmPeriod) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pwmState.cnt) {
|
||||||
|
// Adjust any running ones to the best of our abilities by scaling them
|
||||||
|
// Used FP math for speed and code size
|
||||||
|
uint64_t oldCC64p0 = ((uint64_t)pwmPeriod);
|
||||||
|
uint64_t newCC64p16 = ((uint64_t)cc) << 16;
|
||||||
|
uint64_t ratio64p16 = (newCC64p16 / oldCC64p0);
|
||||||
|
PWMState p; // The working copy since we can't edit the one in use
|
||||||
|
p = pwmState;
|
||||||
|
uint32_t ttl = 0;
|
||||||
|
for (auto i = 0; i < p.cnt; i++) {
|
||||||
|
uint64_t val64p16 = ((uint64_t)p.edge[i].delta) << 16;
|
||||||
|
uint64_t newVal64p32 = val64p16 * ratio64p16;
|
||||||
|
p.edge[i].delta = newVal64p32 >> 32;
|
||||||
|
ttl += p.edge[i].delta;
|
||||||
|
}
|
||||||
|
p.edge[p.cnt].delta = cc - ttl; // Final cleanup exactly cc total cycles
|
||||||
|
// Update and wait for mailbox to be emptied
|
||||||
|
pwmUpdate = &p;
|
||||||
|
while (pwmUpdate) {
|
||||||
|
delay(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pwmPeriod = cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper routine to remove an entry from the state machine
|
||||||
|
static void _removePWMEntry(int pin, PWMState *p) {
|
||||||
|
if (!((1<<pin) & p->mask)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
p->edge[i+1].delta += delta;
|
||||||
|
// Move everything back one and clean up
|
||||||
|
for (i++; i <= p->cnt; i++) {
|
||||||
|
p->edge[i-1] = p->edge[i];
|
||||||
|
}
|
||||||
|
p->mask &= ~(1<<pin);
|
||||||
|
p->cnt--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by analogWrite(0/100%) to disable PWM on a specific pin
|
||||||
|
bool _stopPWM(int pin) {
|
||||||
|
if (!((1<<pin) & pwmState.mask)) {
|
||||||
|
return false; // Pin not actually active
|
||||||
|
}
|
||||||
|
|
||||||
|
PWMState p; // The working copy since we can't edit the one in use
|
||||||
|
p = pwmState;
|
||||||
|
_removePWMEntry(pin, &p);
|
||||||
|
// Update and wait for mailbox to be emptied
|
||||||
|
pwmUpdate = &p;
|
||||||
|
while (pwmUpdate) {
|
||||||
|
delay(0);
|
||||||
|
}
|
||||||
|
// Possibly shut doen the timer completely if we're done
|
||||||
|
if (!waveformEnabled && !pwmState.cnt && !timer1CB) {
|
||||||
|
deinitTimer();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
|
||||||
|
bool _setPWM(int pin, uint32_t cc) {
|
||||||
|
PWMState p; // Working copy
|
||||||
|
p = pwmState;
|
||||||
|
// Get rid of any entries for this pin
|
||||||
|
_removePWMEntry(pin, &p);
|
||||||
|
// And add it to the list, in order
|
||||||
|
if (p.cnt >= maxPWMs) {
|
||||||
|
return false; // No space left
|
||||||
|
} else if (p.cnt == 0) {
|
||||||
|
// Starting up from scratch, special case 1st element and PWM period
|
||||||
|
p.edge[0].pin = pin;
|
||||||
|
p.edge[0].delta = cc;
|
||||||
|
p.edge[1].pin = 0xff;
|
||||||
|
p.edge[1].delta = pwmPeriod - cc;
|
||||||
|
p.cnt = 1;
|
||||||
|
p.mask = 1<<pin;
|
||||||
|
} 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.edge[i].delta < cc); i++) {
|
||||||
|
ttl += p.edge[i].delta;
|
||||||
|
}
|
||||||
|
// 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]));
|
||||||
|
int off = cc - ttl; // The delta from the last edge to the one we're inserting
|
||||||
|
p.edge[i].pin = pin;
|
||||||
|
p.edge[i].delta = 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.cnt++;
|
||||||
|
p.mask |= 1<<pin;
|
||||||
|
}
|
||||||
|
// Set mailbox and wait for ISR to copy it over
|
||||||
|
pwmUpdate = &p;
|
||||||
|
if (!timerRunning) {
|
||||||
|
initTimer();
|
||||||
|
timer1_write(microsecondsToClockCycles(10));
|
||||||
|
}
|
||||||
|
while (pwmUpdate) { delay(0); }
|
||||||
|
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.
|
||||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
|
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)) {
|
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Waveform *wave = &waveform[pin];
|
Waveform *wave = &waveform[pin];
|
||||||
// Adjust to shave off some of the IRQ time, approximately
|
wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0;
|
||||||
wave->nextTimeHighCycles = microsecondsToClockCycles(timeHighUS);
|
if (runTimeCycles && !wave->expiryCycle) {
|
||||||
wave->nextTimeLowCycles = microsecondsToClockCycles(timeLowUS);
|
|
||||||
wave->expiryCycle = runTimeUS ? GetCycleCount() + microsecondsToClockCycles(runTimeUS) : 0;
|
|
||||||
if (runTimeUS && !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
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t mask = 1<<pin;
|
uint32_t mask = 1<<pin;
|
||||||
if (!(waveformEnabled & mask)) {
|
if (waveformEnabled & mask) {
|
||||||
// Actually set the pin high or low in the IRQ service to guarantee times
|
waveformNewHigh = timeHighCycles;
|
||||||
|
waveformNewLow = timeLowCycles;
|
||||||
|
waveformToChange = pin;
|
||||||
|
while (waveformToChange >= 0) {
|
||||||
|
delay(0); // Wait for waveform to update
|
||||||
|
}
|
||||||
|
} else { // if (!(waveformEnabled & mask)) {
|
||||||
|
wave->timeHighCycles = timeHighCycles;
|
||||||
|
wave->timeLowCycles = timeLowCycles;
|
||||||
|
wave->gotoTimeHighCycles = wave->timeHighCycles;
|
||||||
|
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 = GetCycleCount() + microsecondsToClockCycles(1);
|
||||||
waveformToEnable |= mask;
|
waveformToEnable |= mask;
|
||||||
if (!timerRunning) {
|
if (!timerRunning) {
|
||||||
@ -172,13 +341,6 @@ static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline ICACHE_RAM_ATTR int32_t max_32(int32_t a, int32_t b) {
|
|
||||||
if (a < b) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stops a waveform on a pin
|
// Stops a waveform on a pin
|
||||||
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
|
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
|
||||||
// Can't possibly need to stop anything if there is no timer active
|
// Can't possibly need to stop anything if there is no timer active
|
||||||
@ -199,7 +361,7 @@ int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
|
|||||||
while (waveformToDisable) {
|
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 && !timer1CB) {
|
if (!waveformEnabled && !pwmState.cnt && !timer1CB) {
|
||||||
deinitTimer();
|
deinitTimer();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -226,7 +388,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
|||||||
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
||||||
|
|
||||||
if (waveformToEnable || waveformToDisable) {
|
if (waveformToEnable || 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
|
waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off
|
||||||
waveformState &= ~waveformToEnable; // And clear the state of any just started
|
waveformState &= ~waveformToEnable; // And clear the state of any just started
|
||||||
waveformToEnable = 0;
|
waveformToEnable = 0;
|
||||||
@ -235,12 +397,60 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
|||||||
startPin = __builtin_ffs(waveformEnabled) - 1;
|
startPin = __builtin_ffs(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);
|
endPin = 32 - __builtin_clz(waveformEnabled);
|
||||||
|
} else if (!pwmState.cnt && pwmUpdate) {
|
||||||
|
// Start up the PWM generator by copying from the mailbox
|
||||||
|
pwmState = *(PWMState*)pwmUpdate;
|
||||||
|
pwmUpdate = nullptr;
|
||||||
|
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
|
||||||
|
pwmState.idx = pwmState.cnt; // Cause it to start at t=0
|
||||||
|
} else if (waveformToChange >=0) {
|
||||||
|
waveform[waveformToChange].gotoTimeHighCycles = waveformNewHigh;
|
||||||
|
waveform[waveformToChange].gotoTimeLowCycles = waveformNewLow;
|
||||||
|
waveformToChange = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool done = false;
|
bool done = false;
|
||||||
if (waveformEnabled) {
|
if (waveformEnabled || pwmState.cnt) {
|
||||||
do {
|
do {
|
||||||
nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
||||||
|
|
||||||
|
// PWM state machine implementation
|
||||||
|
if (pwmState.cnt) {
|
||||||
|
uint32_t now = GetCycleCountIRQ();
|
||||||
|
int32_t cyclesToGo = pwmState.nextServiceCycle - now;
|
||||||
|
if (cyclesToGo <= 10) {
|
||||||
|
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
|
||||||
|
if (pwmUpdate) {
|
||||||
|
// Do the memory copy from temp to global and clear mailbox
|
||||||
|
pwmState = *(PWMState*)pwmUpdate;
|
||||||
|
pwmUpdate = nullptr;
|
||||||
|
}
|
||||||
|
GPOS = pwmState.mask; // Set all active pins high
|
||||||
|
// GPIO16 isn't the same as the others
|
||||||
|
if (pwmState.mask & 0x100) {
|
||||||
|
GP16O |= 1;
|
||||||
|
}
|
||||||
|
pwmState.idx = 0;
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
// Drop the pin at this edge
|
||||||
|
GPOC = 1<<pwmState.edge[pwmState.idx].pin;
|
||||||
|
// GPIO16 still needs manual work
|
||||||
|
if (pwmState.edge[pwmState.idx].pin == 16) {
|
||||||
|
GP16O &= ~1;
|
||||||
|
}
|
||||||
|
pwmState.idx++;
|
||||||
|
// Any other pins at this same PWM value will have delta==0, drop them too.
|
||||||
|
} while (pwmState.edge[pwmState.idx].delta == 0);
|
||||||
|
}
|
||||||
|
// Preserve duty cycle over PWM period by using now+xxx instead of += delta
|
||||||
|
pwmState.nextServiceCycle = now + pwmState.edge[pwmState.idx].delta;
|
||||||
|
cyclesToGo = pwmState.nextServiceCycle - now;
|
||||||
|
if (cyclesToGo<0) cyclesToGo=0;
|
||||||
|
}
|
||||||
|
nextEventCycles = min_u32(nextEventCycles, cyclesToGo);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = startPin; i <= endPin; i++) {
|
for (int i = startPin; i <= endPin; i++) {
|
||||||
uint32_t mask = 1<<i;
|
uint32_t mask = 1<<i;
|
||||||
|
|
||||||
@ -270,17 +480,6 @@ 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) {
|
||||||
// See #7057
|
|
||||||
// The following is a no-op unless we have overshot by an entire waveform cycle.
|
|
||||||
// As modulus is an expensive operation, this code is removed for now:
|
|
||||||
// cyclesToGo = -((-cyclesToGo) % (wave->nextTimeHighCycles + wave->nextTimeLowCycles));
|
|
||||||
//
|
|
||||||
// Alternative version with lower CPU impact:
|
|
||||||
// while (-cyclesToGo > wave->nextTimeHighCycles + wave->nextTimeLowCycles) { cyclesToGo += wave->nextTimeHighCycles + wave->nextTimeLowCycles); }
|
|
||||||
//
|
|
||||||
// detect interrupt storm, for example during wifi connection.
|
|
||||||
// if we overshoot the cycle by more than 25%, we forget phase and keep PWM duration
|
|
||||||
int32_t overshoot = (-cyclesToGo) > ((wave->nextTimeHighCycles + wave->nextTimeLowCycles) >> 2);
|
|
||||||
waveformState ^= mask;
|
waveformState ^= mask;
|
||||||
if (waveformState & mask) {
|
if (waveformState & mask) {
|
||||||
if (i == 16) {
|
if (i == 16) {
|
||||||
@ -288,26 +487,19 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
|||||||
} else {
|
} else {
|
||||||
SetGPIO(mask);
|
SetGPIO(mask);
|
||||||
}
|
}
|
||||||
if (overshoot) {
|
wave->nextServiceCycle = now + wave->timeHighCycles;
|
||||||
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
|
nextEventCycles = min_u32(nextEventCycles, wave->timeHighCycles);
|
||||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
|
|
||||||
} else {
|
|
||||||
wave->nextServiceCycle += wave->nextTimeHighCycles;
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, max_32(wave->nextTimeHighCycles + cyclesToGo, microsecondsToClockCycles(1)));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
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 {
|
||||||
ClearGPIO(mask);
|
ClearGPIO(mask);
|
||||||
}
|
}
|
||||||
if (overshoot) {
|
wave->nextServiceCycle = now + wave->timeLowCycles;
|
||||||
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
|
nextEventCycles = min_u32(nextEventCycles, wave->timeLowCycles);
|
||||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
|
// Copy over next full-cycle timings
|
||||||
} else {
|
wave->timeHighCycles = wave->gotoTimeHighCycles;
|
||||||
wave->nextServiceCycle += wave->nextTimeLowCycles;
|
wave->timeLowCycles = wave->gotoTimeLowCycles;
|
||||||
nextEventCycles = min_u32(nextEventCycles, max_32(wave->nextTimeLowCycles + cyclesToGo, microsecondsToClockCycles(1)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
||||||
@ -327,8 +519,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
|||||||
nextEventCycles = min_u32(nextEventCycles, timer1CB());
|
nextEventCycles = min_u32(nextEventCycles, timer1CB());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextEventCycles < microsecondsToClockCycles(10)) {
|
if (nextEventCycles < microsecondsToClockCycles(5)) {
|
||||||
nextEventCycles = microsecondsToClockCycles(10);
|
nextEventCycles = microsecondsToClockCycles(5);
|
||||||
}
|
}
|
||||||
nextEventCycles -= DELTAIRQ;
|
nextEventCycles -= DELTAIRQ;
|
||||||
|
|
||||||
@ -343,6 +535,4 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ARDUINO_ESP8266_RELEASE
|
|
||||||
|
|
||||||
#endif // ESP8266
|
#endif // ESP8266
|
@ -21,24 +21,27 @@
|
|||||||
|
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
|
|
||||||
// Use PWM from core 2.4.0 as all versions below 2.5.0-beta3 produce LED flickering when settings are saved to flash
|
|
||||||
#include <core_version.h>
|
|
||||||
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
|
|
||||||
#warning **** Tasmota is using v2.4.0 wiring_digital.c as planned ****
|
|
||||||
|
|
||||||
#define ARDUINO_MAIN
|
#define ARDUINO_MAIN
|
||||||
#include "wiring_private.h"
|
#include "wiring_private.h"
|
||||||
#include "pins_arduino.h"
|
#include "pins_arduino.h"
|
||||||
#include "c_types.h"
|
#include "c_types.h"
|
||||||
#include "eagle_soc.h"
|
#include "eagle_soc.h"
|
||||||
#include "ets_sys.h"
|
#include "ets_sys.h"
|
||||||
|
#include "user_interface.h"
|
||||||
|
#include "core_esp8266_waveform.h"
|
||||||
|
#include "interrupts.h"
|
||||||
|
|
||||||
extern void pwm_stop_pin(uint8_t pin);
|
extern "C" {
|
||||||
|
|
||||||
uint8_t esp8266_gpioToFn[16] = {0x34, 0x18, 0x38, 0x14, 0x3C, 0x40, 0x1C, 0x20, 0x24, 0x28, 0x2C, 0x30, 0x04, 0x08, 0x0C, 0x10};
|
// Internal-only calls, not for applications
|
||||||
|
extern void _setPWMPeriodCC(uint32_t cc);
|
||||||
|
extern bool _stopPWM(int pin);
|
||||||
|
extern bool _setPWM(int pin, uint32_t cc);
|
||||||
|
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) {
|
extern void __pinMode(uint8_t pin, uint8_t mode) {
|
||||||
pwm_stop_pin(pin);
|
|
||||||
if(pin < 16){
|
if(pin < 16){
|
||||||
if(mode == SPECIAL){
|
if(mode == SPECIAL){
|
||||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
||||||
@ -88,7 +91,8 @@ extern void __pinMode(uint8_t pin, uint8_t mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
||||||
pwm_stop_pin(pin);
|
stopWaveform(pin); // Disable any tone
|
||||||
|
_stopPWM(pin); // ...and any analogWrite
|
||||||
if(pin < 16){
|
if(pin < 16){
|
||||||
if(val) GPOS = (1 << pin);
|
if(val) GPOS = (1 << pin);
|
||||||
else GPOC = (1 << pin);
|
else GPOC = (1 << pin);
|
||||||
@ -99,7 +103,6 @@ extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) {
|
extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) {
|
||||||
pwm_stop_pin(pin);
|
|
||||||
if(pin < 16){
|
if(pin < 16){
|
||||||
return GPIP(pin);
|
return GPIP(pin);
|
||||||
} else if(pin == 16){
|
} else if(pin == 16){
|
||||||
@ -117,16 +120,28 @@ typedef void (*voidFuncPtrArg)(void*);
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t mode;
|
uint8_t mode;
|
||||||
void (*fn)(void);
|
voidFuncPtr fn;
|
||||||
void * arg;
|
void * arg;
|
||||||
|
bool functional;
|
||||||
} interrupt_handler_t;
|
} interrupt_handler_t;
|
||||||
|
|
||||||
|
//duplicate from functionalInterrupt.h keep in sync
|
||||||
|
typedef struct InterruptInfo {
|
||||||
|
uint8_t pin;
|
||||||
|
uint8_t value;
|
||||||
|
uint32_t micro;
|
||||||
|
} InterruptInfo;
|
||||||
|
|
||||||
static interrupt_handler_t interrupt_handlers[16];
|
typedef struct {
|
||||||
|
InterruptInfo* interruptInfo;
|
||||||
|
void* functionInfo;
|
||||||
|
} ArgStructure;
|
||||||
|
|
||||||
|
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
|
||||||
static uint32_t interrupt_reg = 0;
|
static uint32_t interrupt_reg = 0;
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR interrupt_handler(void *arg) {
|
void ICACHE_RAM_ATTR interrupt_handler(void*)
|
||||||
(void) arg;
|
{
|
||||||
uint32_t status = GPIE;
|
uint32_t status = GPIE;
|
||||||
GPIEC = status;//clear them interrupts
|
GPIEC = status;//clear them interrupts
|
||||||
uint32_t levels = GPI;
|
uint32_t levels = GPI;
|
||||||
@ -143,7 +158,17 @@ void ICACHE_RAM_ATTR interrupt_handler(void *arg) {
|
|||||||
(handler->mode & 1) == !!(levels & (1 << i)))) {
|
(handler->mode & 1) == !!(levels & (1 << i)))) {
|
||||||
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
||||||
// we disable them before we call the client ISR
|
// we disable them before we call the client ISR
|
||||||
uint32_t savedPS = xt_rsil(15); // stop other interrupts
|
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)
|
if (handler->arg)
|
||||||
{
|
{
|
||||||
((voidFuncPtrArg)handler->fn)(handler->arg);
|
((voidFuncPtrArg)handler->fn)(handler->arg);
|
||||||
@ -152,72 +177,98 @@ void ICACHE_RAM_ATTR interrupt_handler(void *arg) {
|
|||||||
{
|
{
|
||||||
handler->fn();
|
handler->fn();
|
||||||
}
|
}
|
||||||
xt_wsr_ps(savedPS);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ETS_GPIO_INTR_ENABLE();
|
ETS_GPIO_INTR_ENABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void ICACHE_RAM_ATTR __attachInterruptArg(uint8_t pin, voidFuncPtr userFunc, void *arg, int mode) {
|
extern void cleanupFunctional(void* arg);
|
||||||
if(pin < 16) {
|
|
||||||
ETS_GPIO_INTR_DISABLE();
|
static void set_interrupt_handlers(uint8_t pin, voidFuncPtr userFunc, void* arg, uint8_t mode, bool functional)
|
||||||
|
{
|
||||||
interrupt_handler_t* handler = &interrupt_handlers[pin];
|
interrupt_handler_t* handler = &interrupt_handlers[pin];
|
||||||
handler->mode = mode;
|
handler->mode = mode;
|
||||||
handler->fn = userFunc;
|
handler->fn = userFunc;
|
||||||
|
if (handler->functional && handler->arg) // Clean when new attach without detach
|
||||||
|
{
|
||||||
|
cleanupFunctional(handler->arg);
|
||||||
|
}
|
||||||
handler->arg = 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);
|
interrupt_reg |= (1 << pin);
|
||||||
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
|
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
|
||||||
GPIEC = (1 << pin); //Clear Interrupt for this pin
|
GPIEC = (1 << pin); //Clear Interrupt for this pin
|
||||||
GPC(pin) |= ((mode & 0xF) << GPCI);//INT mode "mode"
|
GPC(pin) |= ((mode & 0xF) << GPCI);//INT mode "mode"
|
||||||
|
ETS_GPIO_INTR_ATTACH(interrupt_handler, &interrupt_reg);
|
||||||
ETS_GPIO_INTR_ENABLE();
|
ETS_GPIO_INTR_ENABLE();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void ICACHE_RAM_ATTR __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int mode )
|
extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode)
|
||||||
{
|
{
|
||||||
__attachInterruptArg (pin, userFunc, 0, mode);
|
__attachInterruptFunctionalArg(pin, userFunc, arg, mode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin) {
|
extern void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin) {
|
||||||
if(pin < 16) {
|
if (pin < 16)
|
||||||
|
{
|
||||||
ETS_GPIO_INTR_DISABLE();
|
ETS_GPIO_INTR_DISABLE();
|
||||||
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
|
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
|
||||||
GPIEC = (1 << pin); //Clear Interrupt for this pin
|
GPIEC = (1 << pin); //Clear Interrupt for this pin
|
||||||
interrupt_reg &= ~(1 << pin);
|
interrupt_reg &= ~(1 << pin);
|
||||||
interrupt_handler_t *handler = &interrupt_handlers[pin];
|
set_interrupt_handlers(pin, nullptr, nullptr, 0, false);
|
||||||
handler->mode = 0;
|
if (interrupt_reg)
|
||||||
handler->fn = 0;
|
{
|
||||||
handler->arg = 0;
|
|
||||||
ETS_GPIO_INTR_ENABLE();
|
ETS_GPIO_INTR_ENABLE();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void initPins(void) {
|
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
|
//Disable UART interrupts
|
||||||
system_set_os_print(0);
|
system_set_os_print(0);
|
||||||
U0IE = 0;
|
U0IE = 0;
|
||||||
U1IE = 0;
|
U1IE = 0;
|
||||||
|
|
||||||
/*
|
resetPins();
|
||||||
for (int i = 0; i <= 5; ++i) {
|
|
||||||
pinMode(i, INPUT);
|
|
||||||
}
|
|
||||||
// pins 6-11 are used for the SPI flash interface
|
|
||||||
for (int i = 12; i <= 16; ++i) {
|
|
||||||
pinMode(i, INPUT);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
ETS_GPIO_INTR_ATTACH(interrupt_handler, &interrupt_reg);
|
|
||||||
ETS_GPIO_INTR_ENABLE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern void resetPins() __attribute__ ((weak, alias("__resetPins")));
|
||||||
extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
|
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 void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite")));
|
||||||
extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead")));
|
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 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")));
|
extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt")));
|
||||||
|
|
||||||
#endif // ARDUINO_ESP8266_RELEASE
|
};
|
||||||
|
|
||||||
#endif // ESP8266
|
#endif // ESP8266
|
@ -1,238 +0,0 @@
|
|||||||
/*
|
|
||||||
pwm.c - analogWrite 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
|
|
||||||
|
|
||||||
// Use PWM from core 2.4.0 as all versions below 2.5.0-beta3 produce LED flickering when settings are saved to flash
|
|
||||||
#include <core_version.h>
|
|
||||||
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
|
|
||||||
#warning **** Tasmota is using v2.4.0 wiring_pwm.c as planned ****
|
|
||||||
|
|
||||||
#include "wiring_private.h"
|
|
||||||
#include "pins_arduino.h"
|
|
||||||
#include "c_types.h"
|
|
||||||
#include "eagle_soc.h"
|
|
||||||
#include "ets_sys.h"
|
|
||||||
|
|
||||||
#ifndef F_CPU
|
|
||||||
#define F_CPU 800000000L
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct pwm_isr_table {
|
|
||||||
uint8_t len;
|
|
||||||
uint16_t steps[17];
|
|
||||||
uint32_t masks[17];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pwm_isr_data {
|
|
||||||
struct pwm_isr_table tables[2];
|
|
||||||
uint8_t active;//0 or 1, which table is active in ISR
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct pwm_isr_data _pwm_isr_data;
|
|
||||||
|
|
||||||
uint32_t pwm_mask = 0;
|
|
||||||
uint16_t pwm_values[17] = {0,};
|
|
||||||
uint32_t pwm_freq = 1000;
|
|
||||||
uint32_t pwm_range = PWMRANGE;
|
|
||||||
|
|
||||||
uint8_t pwm_steps_changed = 0;
|
|
||||||
uint32_t pwm_multiplier = 0;
|
|
||||||
|
|
||||||
int pwm_sort_array(uint16_t a[], uint16_t al)
|
|
||||||
{
|
|
||||||
uint16_t i, j;
|
|
||||||
for (i = 1; i < al; i++) {
|
|
||||||
uint16_t tmp = a[i];
|
|
||||||
for (j = i; j >= 1 && tmp < a[j-1]; j--) {
|
|
||||||
a[j] = a[j-1];
|
|
||||||
}
|
|
||||||
a[j] = tmp;
|
|
||||||
}
|
|
||||||
int bl = 1;
|
|
||||||
for(i = 1; i < al; i++) {
|
|
||||||
if(a[i] != a[i-1]) {
|
|
||||||
a[bl++] = a[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bl;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t pwm_get_mask(uint16_t value)
|
|
||||||
{
|
|
||||||
uint32_t mask = 0;
|
|
||||||
int i;
|
|
||||||
for(i=0; i<17; i++) {
|
|
||||||
if((pwm_mask & (1 << i)) != 0 && pwm_values[i] == value) {
|
|
||||||
mask |= (1 << i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
void prep_pwm_steps(void)
|
|
||||||
{
|
|
||||||
if(pwm_mask == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pwm_temp_steps_len = 0;
|
|
||||||
uint16_t pwm_temp_steps[17];
|
|
||||||
uint32_t pwm_temp_masks[17];
|
|
||||||
uint32_t range = pwm_range;
|
|
||||||
|
|
||||||
if((F_CPU / ESP8266_CLOCK) == 1) {
|
|
||||||
range /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i;
|
|
||||||
for(i=0; i<17; i++) {
|
|
||||||
if((pwm_mask & (1 << i)) != 0 && pwm_values[i] != 0) {
|
|
||||||
pwm_temp_steps[pwm_temp_steps_len++] = pwm_values[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pwm_temp_steps[pwm_temp_steps_len++] = range;
|
|
||||||
pwm_temp_steps_len = pwm_sort_array(pwm_temp_steps, pwm_temp_steps_len) - 1;
|
|
||||||
for(i=0; i<pwm_temp_steps_len; i++) {
|
|
||||||
pwm_temp_masks[i] = pwm_get_mask(pwm_temp_steps[i]);
|
|
||||||
}
|
|
||||||
for(i=pwm_temp_steps_len; i>0; i--) {
|
|
||||||
pwm_temp_steps[i] = pwm_temp_steps[i] - pwm_temp_steps[i-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
pwm_steps_changed = 0;
|
|
||||||
struct pwm_isr_table *table = &(_pwm_isr_data.tables[!_pwm_isr_data.active]);
|
|
||||||
table->len = pwm_temp_steps_len;
|
|
||||||
ets_memcpy(table->steps, pwm_temp_steps, (pwm_temp_steps_len + 1) * 2);
|
|
||||||
ets_memcpy(table->masks, pwm_temp_masks, pwm_temp_steps_len * 4);
|
|
||||||
pwm_multiplier = ESP8266_CLOCK/(range * pwm_freq);
|
|
||||||
pwm_steps_changed = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR pwm_timer_isr(void) //103-138
|
|
||||||
{
|
|
||||||
struct pwm_isr_table *table = &(_pwm_isr_data.tables[_pwm_isr_data.active]);
|
|
||||||
static uint8_t current_step = 0;
|
|
||||||
TEIE &= ~TEIE1;//14
|
|
||||||
T1I = 0;//9
|
|
||||||
if(current_step < table->len) { //20/21
|
|
||||||
uint32_t mask = table->masks[current_step] & pwm_mask;
|
|
||||||
if(mask & 0xFFFF) {
|
|
||||||
GPOC = mask & 0xFFFF; //15/21
|
|
||||||
}
|
|
||||||
if(mask & 0x10000) {
|
|
||||||
GP16O = 0; //6/13
|
|
||||||
}
|
|
||||||
current_step++;//1
|
|
||||||
} else {
|
|
||||||
current_step = 0;//1
|
|
||||||
if(pwm_mask == 0) { //12
|
|
||||||
table->len = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(pwm_mask & 0xFFFF) {
|
|
||||||
GPOS = pwm_mask & 0xFFFF; //11
|
|
||||||
}
|
|
||||||
if(pwm_mask & 0x10000) {
|
|
||||||
GP16O = 1; //5/13
|
|
||||||
}
|
|
||||||
if(pwm_steps_changed) { //12/21
|
|
||||||
_pwm_isr_data.active = !_pwm_isr_data.active;
|
|
||||||
table = &(_pwm_isr_data.tables[_pwm_isr_data.active]);
|
|
||||||
pwm_steps_changed = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
T1L = (table->steps[current_step] * pwm_multiplier);//23
|
|
||||||
TEIE |= TEIE1;//13
|
|
||||||
}
|
|
||||||
|
|
||||||
void pwm_start_timer(void)
|
|
||||||
{
|
|
||||||
timer1_disable();
|
|
||||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_timer_isr);
|
|
||||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
|
||||||
timer1_write(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR pwm_stop_pin(uint8_t pin)
|
|
||||||
{
|
|
||||||
if(pwm_mask){
|
|
||||||
pwm_mask &= ~(1 << pin);
|
|
||||||
if(pwm_mask == 0) {
|
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
|
||||||
timer1_disable();
|
|
||||||
timer1_isr_init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __analogWrite(uint8_t pin, int value)
|
|
||||||
{
|
|
||||||
bool start_timer = false;
|
|
||||||
if(value == 0) {
|
|
||||||
digitalWrite(pin, LOW);
|
|
||||||
prep_pwm_steps();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(value == pwm_range) {
|
|
||||||
digitalWrite(pin, HIGH);
|
|
||||||
prep_pwm_steps();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if((pwm_mask & (1 << pin)) == 0) {
|
|
||||||
if(pwm_mask == 0) {
|
|
||||||
memset(&_pwm_isr_data, 0, sizeof(_pwm_isr_data));
|
|
||||||
start_timer = true;
|
|
||||||
}
|
|
||||||
pinMode(pin, OUTPUT);
|
|
||||||
digitalWrite(pin, LOW);
|
|
||||||
pwm_mask |= (1 << pin);
|
|
||||||
}
|
|
||||||
if((F_CPU / ESP8266_CLOCK) == 1) {
|
|
||||||
value = (value+1) / 2;
|
|
||||||
}
|
|
||||||
pwm_values[pin] = value % (pwm_range + 1);
|
|
||||||
prep_pwm_steps();
|
|
||||||
if(start_timer) {
|
|
||||||
pwm_start_timer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __analogWriteFreq(uint32_t freq)
|
|
||||||
{
|
|
||||||
pwm_freq = freq;
|
|
||||||
prep_pwm_steps();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __analogWriteRange(uint32_t range)
|
|
||||||
{
|
|
||||||
pwm_range = range;
|
|
||||||
prep_pwm_steps();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 // ARDUINO_ESP8266_RELEASE
|
|
||||||
|
|
||||||
#endif // ESP8266
|
|
94
tasmota/core_esp8266_wiring_pwm.cpp
Normal file
94
tasmota/core_esp8266_wiring_pwm.cpp
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
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 <Arduino.h>
|
||||||
|
#include "core_esp8266_waveform.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Internal-only calls, not for applications
|
||||||
|
extern void _setPWMPeriodCC(uint32_t cc);
|
||||||
|
extern bool _stopPWM(int pin);
|
||||||
|
extern bool _setPWM(int pin, uint32_t cc);
|
||||||
|
|
||||||
|
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 > 40000) {
|
||||||
|
analogFreq = 40000;
|
||||||
|
} else {
|
||||||
|
analogFreq = freq;
|
||||||
|
}
|
||||||
|
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq;
|
||||||
|
_setPWMPeriodCC(analogPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __analogWrite(uint8_t pin, int val) {
|
||||||
|
if (pin > 16) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq;
|
||||||
|
_setPWMPeriodCC(analogPeriod);
|
||||||
|
if (val < 0) {
|
||||||
|
val = 0;
|
||||||
|
} else if (val > analogScale) {
|
||||||
|
val = analogScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
analogMap &= ~(1 << pin);
|
||||||
|
uint32_t high = (analogPeriod * val) / analogScale;
|
||||||
|
uint32_t low = analogPeriod - high;
|
||||||
|
pinMode(pin, OUTPUT);
|
||||||
|
if (low == 0) {
|
||||||
|
_stopPWM(pin);
|
||||||
|
digitalWrite(pin, HIGH);
|
||||||
|
} else if (high == 0) {
|
||||||
|
_stopPWM(pin);
|
||||||
|
digitalWrite(pin, LOW);
|
||||||
|
} else {
|
||||||
|
_setPWM(pin, high);
|
||||||
|
analogMap |= (1 << pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));
|
||||||
|
extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq")));
|
||||||
|
extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange")));
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ESP8266
|
Loading…
x
Reference in New Issue
Block a user