mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-13 13:01:55 +03:00
Support multiple tone(), analogWrite(), and Servo (#4640)
Remove and rewrite all the parts of the core/libraries using TIMER1 and consolidate into a single, shared waveform generation interrupt structure. Tone, analogWrite(), Servo all now just call into this shared resource to perform their tasks so are all compatible and can be used simultaneously. This setup enables multiple tones, analogWrites, servos, and stepper motors to be controlled with reasonable accuracy. It uses both TIMER1 and the internal ESP cycle counter to handle timing of waveform edges. TIMER1 is used in non-reload mode and only edges cause interrupts. The interrupt is started and stopped as required, minimizing overhead when these features are not being used. A generic "startWaveform(pin, high-US, low-US, runtime-US)" and "stopWaveform(pin)" allow for further types of interfaces. Minimum high or low period is ~1 us. Add a tone(float) method, useful when working with lower frequencies. Fixes #4321. Fixes 4349.
This commit is contained in:
committed by
GitHub
parent
ea4720b03e
commit
ebda795f34
@ -279,6 +279,7 @@ unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout = 100000
|
||||
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L);
|
||||
|
||||
void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0);
|
||||
void tone(uint8_t _pin, double frequency, unsigned long duration = 0);
|
||||
void noTone(uint8_t _pin);
|
||||
|
||||
// WMath prototypes
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
A Tone Generator Library for the ESP8266
|
||||
|
||||
Copyright (c) 2016 Ben Pirt. All rights reserved.
|
||||
Original Copyright (c) 2016 Ben Pirt. 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
|
||||
@ -22,115 +22,59 @@
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "pins_arduino.h"
|
||||
#include "core_esp8266_waveform.h"
|
||||
|
||||
#define AVAILABLE_TONE_PINS 1
|
||||
const uint8_t tone_timers[] = { 1 };
|
||||
static uint8_t tone_pins[AVAILABLE_TONE_PINS] = { 255, };
|
||||
static long toggle_counts[AVAILABLE_TONE_PINS] = { 0, };
|
||||
#define T1INDEX 0
|
||||
// Which pins have a tone running on them?
|
||||
static uint32_t _toneMap = 0;
|
||||
|
||||
void t1IntHandler();
|
||||
|
||||
static int8_t toneBegin(uint8_t _pin) {
|
||||
int8_t _index = -1;
|
||||
|
||||
// if we're already using the pin, reuse it.
|
||||
for (int i = 0; i < AVAILABLE_TONE_PINS; i++) {
|
||||
if (tone_pins[i] == _pin) {
|
||||
return i;
|
||||
}
|
||||
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, unsigned long duration) {
|
||||
if (_pin > 16) {
|
||||
return;
|
||||
}
|
||||
|
||||
// search for an unused timer.
|
||||
for (int i = 0; i < AVAILABLE_TONE_PINS; i++) {
|
||||
if (tone_pins[i] == 255) {
|
||||
tone_pins[i] = _pin;
|
||||
_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pinMode(_pin, OUTPUT);
|
||||
|
||||
return _index;
|
||||
high = std::max(high, (uint32_t)100);
|
||||
low = std::max(low, (uint32_t)100);
|
||||
|
||||
if (startWaveform(_pin, high, low, (uint32_t) duration * 1000)) {
|
||||
_toneMap |= 1 << _pin;
|
||||
}
|
||||
}
|
||||
|
||||
// frequency (in hertz) and duration (in milliseconds).
|
||||
|
||||
void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) {
|
||||
int8_t _index;
|
||||
|
||||
_index = toneBegin(_pin);
|
||||
|
||||
if (_index >= 0) {
|
||||
// Set the pinMode as OUTPUT
|
||||
pinMode(_pin, OUTPUT);
|
||||
|
||||
// Alternate handling of zero freqency to avoid divide by zero errors
|
||||
if (frequency == 0)
|
||||
{
|
||||
noTone(_pin);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the toggle count
|
||||
if (duration > 0) {
|
||||
toggle_counts[_index] = 2 * frequency * duration / 1000;
|
||||
} else {
|
||||
toggle_counts[_index] = -1;
|
||||
}
|
||||
|
||||
// set up the interrupt frequency
|
||||
switch (tone_timers[_index]) {
|
||||
case 0:
|
||||
// Not currently supported
|
||||
break;
|
||||
|
||||
case 1:
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
timer1_attachInterrupt(t1IntHandler);
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
|
||||
timer1_write((clockCyclesPerMicrosecond() * 500000) / frequency);
|
||||
break;
|
||||
}
|
||||
if (frequency == 0) {
|
||||
noTone(_pin);
|
||||
} else {
|
||||
uint32_t period = 1000000L / frequency;
|
||||
uint32_t high = period / 2;
|
||||
uint32_t low = period - high;
|
||||
_startTone(_pin, high, low, duration);
|
||||
}
|
||||
}
|
||||
|
||||
void disableTimer(uint8_t _index) {
|
||||
tone_pins[_index] = 255;
|
||||
|
||||
switch (tone_timers[_index]) {
|
||||
case 0:
|
||||
// Not currently supported
|
||||
break;
|
||||
|
||||
case 1:
|
||||
timer1_disable();
|
||||
break;
|
||||
// Separate tone(float) to hopefully not pull in floating point libs unless
|
||||
// it's called with a float.
|
||||
void tone(uint8_t _pin, double frequency, unsigned long duration) {
|
||||
if (frequency < 1.0) { // FP means no exact comparisons
|
||||
noTone(_pin);
|
||||
} else {
|
||||
double period = 1000000.0 / frequency;
|
||||
uint32_t high = (uint32_t)((period / 2.0) + 0.5);
|
||||
uint32_t low = (uint32_t)(period + 0.5) - high;
|
||||
_startTone(_pin, high, low, duration);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void noTone(uint8_t _pin) {
|
||||
for (int i = 0; i < AVAILABLE_TONE_PINS; i++) {
|
||||
if (tone_pins[i] == _pin) {
|
||||
tone_pins[i] = 255;
|
||||
disableTimer(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
digitalWrite(_pin, LOW);
|
||||
}
|
||||
|
||||
ICACHE_RAM_ATTR void t1IntHandler() {
|
||||
if (toggle_counts[T1INDEX] != 0){
|
||||
// toggle the pin
|
||||
digitalWrite(tone_pins[T1INDEX], toggle_counts[T1INDEX] % 2);
|
||||
toggle_counts[T1INDEX]--;
|
||||
// handle the case of indefinite duration
|
||||
if (toggle_counts[T1INDEX] < -2){
|
||||
toggle_counts[T1INDEX] = -1;
|
||||
}
|
||||
}else{
|
||||
disableTimer(T1INDEX);
|
||||
digitalWrite(tone_pins[T1INDEX], LOW);
|
||||
if (_pin > 16) {
|
||||
return;
|
||||
}
|
||||
stopWaveform(_pin);
|
||||
_toneMap &= ~(1 << _pin);
|
||||
digitalWrite(_pin, 0);
|
||||
}
|
||||
|
0
cores/esp8266/base64.cpp
Executable file → Normal file
0
cores/esp8266/base64.cpp
Executable file → Normal file
0
cores/esp8266/base64.h
Executable file → Normal file
0
cores/esp8266/base64.h
Executable file → Normal file
304
cores/esp8266/core_esp8266_waveform.c
Normal file
304
cores/esp8266/core_esp8266_waveform.c
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
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). 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 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.getCycleTime()
|
||||
cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 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 <Arduino.h>
|
||||
#include "core_esp8266_waveform.h"
|
||||
|
||||
// Need speed, not size, here
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
// Map the IRQ stuff to standard terminology
|
||||
#define cli() ets_intr_lock()
|
||||
#define sei() ets_intr_unlock()
|
||||
|
||||
// Maximum delay between IRQs
|
||||
#define MAXIRQUS (10000)
|
||||
|
||||
// If the cycles from now to an event are below this value, perform it anyway since IRQs take longer than this
|
||||
#define CYCLES_FLUFF (100)
|
||||
|
||||
// Macro to get count of predefined array elements
|
||||
#define countof(a) ((size_t)(sizeof(a)/sizeof(a[0])))
|
||||
|
||||
// Set/clear *any* GPIO
|
||||
#define SetGPIOPin(a) do { if (a < 16) { GPOS |= (1<<a); } else { GP16O |= 1; } } while (0)
|
||||
#define ClearGPIOPin(a) do { if (a < 16) { GPOC |= (1<<a); } else { GP16O &= ~1; } } while (0)
|
||||
// Set/clear GPIO 0-15
|
||||
#define SetGPIO(a) do { GPOS = a; } while (0)
|
||||
#define ClearGPIO(a) do { GPOC = a; } while (0)
|
||||
|
||||
// Waveform generator can create tones, PWM, and servos
|
||||
typedef struct {
|
||||
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
||||
uint32_t timeLeftCycles; // For time-limited waveform, how many ESP cycles left
|
||||
uint16_t gpioMask; // Mask instead of value to speed IRQ loop
|
||||
uint16_t gpio16Mask; // Mask instead of value to speed IRQ loop
|
||||
unsigned state : 1; // Current state of this pin
|
||||
unsigned nextTimeHighCycles : 31; // Copy over low->high to keep smooth waveform
|
||||
unsigned enabled : 1; // Is this GPIO generating a waveform?
|
||||
unsigned nextTimeLowCycles : 31; // Copy over high->low to keep smooth waveform
|
||||
} Waveform;
|
||||
|
||||
// These can be accessed in interrupts, so ensure to bracket access with SEI/CLI
|
||||
static Waveform waveform[] = {
|
||||
{0, 0, 1<<0, 0, 0, 0, 0, 0}, // GPIO0
|
||||
{0, 0, 1<<1, 0, 0, 0, 0, 0}, // GPIO1
|
||||
{0, 0, 1<<2, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1<<3, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1<<4, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1<<5, 0, 0, 0, 0, 0},
|
||||
// GPIOS 6-11 not allowed, used for flash
|
||||
{0, 0, 1<<12, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1<<13, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1<<14, 0, 0, 0, 0, 0},
|
||||
{0, 0, 1<<15, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 1, 0, 0, 0, 0} // GPIO16
|
||||
};
|
||||
|
||||
static uint32_t (*timer1CB)() = NULL;;
|
||||
|
||||
|
||||
// Helper functions
|
||||
static inline ICACHE_RAM_ATTR uint32_t MicrosecondsToCycles(uint32_t microseconds) {
|
||||
return clockCyclesPerMicrosecond() * microseconds;
|
||||
}
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) {
|
||||
if (a < b) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t min_s32(int32_t a, int32_t b) {
|
||||
if (a < b) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static inline ICACHE_RAM_ATTR void ReloadTimer(uint32_t a) {
|
||||
// Below a threshold you actually miss the edge IRQ, so ensure enough time
|
||||
if (a > 32) {
|
||||
timer1_write(a);
|
||||
} else {
|
||||
timer1_write(32);
|
||||
}
|
||||
}
|
||||
|
||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() {
|
||||
uint32_t ccount;
|
||||
__asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount));
|
||||
return ccount;
|
||||
}
|
||||
|
||||
// Interrupt on/off control
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||
static uint8_t timerRunning = false;
|
||||
static uint32_t lastCycleCount = 0; // Last ESP cycle counter on running the interrupt routine
|
||||
|
||||
static void initTimer() {
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
timer1_attachInterrupt(timer1Interrupt);
|
||||
lastCycleCount = GetCycleCount();
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||
timerRunning = true;
|
||||
}
|
||||
|
||||
static void deinitTimer() {
|
||||
timer1_attachInterrupt(NULL);
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
timerRunning = false;
|
||||
}
|
||||
|
||||
// Set a callback. Pass in NULL to stop it
|
||||
void setTimer1Callback(uint32_t (*fn)()) {
|
||||
timer1CB = fn;
|
||||
if (!timerRunning && fn) {
|
||||
initTimer();
|
||||
} else if (timerRunning && !fn) {
|
||||
int cnt = 0;
|
||||
for (size_t i = 0; i < countof(waveform); i++) {
|
||||
cnt += waveform[i].enabled ? 1 : 0;
|
||||
}
|
||||
if (!cnt) {
|
||||
deinitTimer();
|
||||
}
|
||||
}
|
||||
ReloadTimer(MicrosecondsToCycles(1)); // Cause an interrupt post-haste
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Waveform *wave = NULL;
|
||||
for (size_t i = 0; i < countof(waveform); i++) {
|
||||
if (((pin == 16) && waveform[i].gpio16Mask==1) || ((pin != 16) && (waveform[i].gpioMask == 1<<pin))) {
|
||||
wave = (Waveform*) & (waveform[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!wave) {
|
||||
return false;
|
||||
}
|
||||
wave->nextTimeHighCycles = MicrosecondsToCycles(timeHighUS) - 70; // Take out some time for IRQ codepath
|
||||
wave->nextTimeLowCycles = MicrosecondsToCycles(timeLowUS) - 70; // Take out some time for IRQ codepath
|
||||
wave->timeLeftCycles = MicrosecondsToCycles(runTimeUS);
|
||||
if (!wave->enabled) {
|
||||
wave->state = 0;
|
||||
// Actually set the pin high or low in the IRQ service to guarantee times
|
||||
wave->nextServiceCycle = GetCycleCount() + MicrosecondsToCycles(1);
|
||||
wave->enabled = 1;
|
||||
if (!timerRunning) {
|
||||
initTimer();
|
||||
}
|
||||
ReloadTimer(MicrosecondsToCycles(1)); // Cause an interrupt post-haste
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stops a waveform on a pin
|
||||
int stopWaveform(uint8_t pin) {
|
||||
for (size_t i = 0; i < countof(waveform); i++) {
|
||||
if (((pin == 16) && waveform[i].gpio16Mask) || ((pin != 16) && (waveform[i].gpioMask == 1<<pin))) {
|
||||
waveform[i].enabled = 0;
|
||||
int cnt = timer1CB?1:0;
|
||||
for (size_t i = 0; i < countof(waveform); i++) {
|
||||
cnt += waveform[i].enabled ? 1 : 0;
|
||||
}
|
||||
if (!cnt) {
|
||||
deinitTimer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
cli();
|
||||
return false;
|
||||
}
|
||||
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||
uint32_t nextEventCycles;
|
||||
#if F_CPU == 160000000
|
||||
uint8_t cnt = 20;
|
||||
#else
|
||||
uint8_t cnt = 10;
|
||||
#endif
|
||||
|
||||
do {
|
||||
nextEventCycles = MicrosecondsToCycles(MAXIRQUS);
|
||||
for (size_t i = 0; i < countof(waveform); i++) {
|
||||
Waveform *wave = &waveform[i];
|
||||
uint32_t now;
|
||||
|
||||
// If it's not on, ignore!
|
||||
if (!wave->enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for toggles
|
||||
now = GetCycleCount();
|
||||
if (now >= wave->nextServiceCycle) {
|
||||
wave->state = !wave->state;
|
||||
if (wave->state) {
|
||||
SetGPIO(wave->gpioMask);
|
||||
if (wave->gpio16Mask) {
|
||||
GP16O |= wave->gpio16Mask; // GPIO16 write slow as it's RMW
|
||||
}
|
||||
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
|
||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
|
||||
} else {
|
||||
ClearGPIO(wave->gpioMask);
|
||||
if (wave->gpio16Mask) {
|
||||
GP16O &= ~wave->gpio16Mask;
|
||||
}
|
||||
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
|
||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
|
||||
}
|
||||
} else {
|
||||
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
||||
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
|
||||
}
|
||||
}
|
||||
} while (--cnt && (nextEventCycles < MicrosecondsToCycles(4)));
|
||||
|
||||
uint32_t curCycleCount = GetCycleCount();
|
||||
uint32_t deltaCycles = curCycleCount - lastCycleCount;
|
||||
lastCycleCount = curCycleCount;
|
||||
|
||||
// Check for timed-out waveforms out of the high-frequency toggle loop
|
||||
for (size_t i = 0; i < countof(waveform); i++) {
|
||||
Waveform *wave = &waveform[i];
|
||||
if (wave->timeLeftCycles) {
|
||||
// Check for unsigned underflow with new > old
|
||||
if (deltaCycles >= wave->timeLeftCycles) {
|
||||
// Done, remove!
|
||||
wave->enabled = false;
|
||||
ClearGPIO(wave->gpioMask);
|
||||
GP16O &= ~wave->gpio16Mask;
|
||||
} else {
|
||||
uint32_t newTimeLeftCycles = wave->timeLeftCycles - deltaCycles;
|
||||
wave->timeLeftCycles = newTimeLeftCycles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (timer1CB) {
|
||||
nextEventCycles = min_u32(nextEventCycles, timer1CB());
|
||||
}
|
||||
|
||||
#if F_CPU == 160000000
|
||||
if (nextEventCycles <= 5 * MicrosecondsToCycles(1)) {
|
||||
nextEventCycles = MicrosecondsToCycles(1) / 2;
|
||||
} else {
|
||||
nextEventCycles -= 5 * MicrosecondsToCycles(1);
|
||||
}
|
||||
nextEventCycles = nextEventCycles >> 1;
|
||||
#else
|
||||
if (nextEventCycles <= 6 * MicrosecondsToCycles(1)) {
|
||||
nextEventCycles = MicrosecondsToCycles(1) / 2;
|
||||
} else {
|
||||
nextEventCycles -= 6 * MicrosecondsToCycles(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
ReloadTimer(nextEventCycles);
|
||||
}
|
71
cores/esp8266/core_esp8266_waveform.h
Normal file
71
cores/esp8266/core_esp8266_waveform.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
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). 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 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.getCycleTime()
|
||||
cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 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 <Arduino.h>
|
||||
|
||||
#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.
|
||||
// Returns true or false on success or failure.
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
|
||||
// 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
|
@ -24,13 +24,12 @@
|
||||
#include "c_types.h"
|
||||
#include "eagle_soc.h"
|
||||
#include "ets_sys.h"
|
||||
|
||||
extern void pwm_stop_pin(uint8_t pin);
|
||||
#include "core_esp8266_waveform.h"
|
||||
|
||||
uint8_t esp8266_gpioToFn[16] = {0x34, 0x18, 0x38, 0x14, 0x3C, 0x40, 0x1C, 0x20, 0x24, 0x28, 0x2C, 0x30, 0x04, 0x08, 0x0C, 0x10};
|
||||
|
||||
extern void __pinMode(uint8_t pin, uint8_t mode) {
|
||||
pwm_stop_pin(pin);
|
||||
stopWaveform(pin);
|
||||
if(pin < 16){
|
||||
if(mode == SPECIAL){
|
||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
||||
@ -80,7 +79,7 @@ extern void __pinMode(uint8_t pin, uint8_t mode) {
|
||||
}
|
||||
|
||||
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
||||
pwm_stop_pin(pin);
|
||||
stopWaveform(pin);
|
||||
if(pin < 16){
|
||||
if(val) GPOS = (1 << pin);
|
||||
else GPOC = (1 << pin);
|
||||
@ -91,7 +90,7 @@ extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
||||
}
|
||||
|
||||
extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) {
|
||||
pwm_stop_pin(pin);
|
||||
stopWaveform(pin);
|
||||
if(pin < 16){
|
||||
return GPIP(pin);
|
||||
} else if(pin == 16){
|
||||
|
@ -1,7 +1,9 @@
|
||||
/*
|
||||
pwm.c - analogWrite implementation for esp8266
|
||||
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
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
|
||||
@ -18,204 +20,58 @@
|
||||
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 "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
|
||||
#include <Arduino.h>
|
||||
#include "core_esp8266_waveform.h"
|
||||
|
||||
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 uint32_t analogMap = 0;
|
||||
static int32_t analogScale = 255;
|
||||
static uint16_t analogFreq = 1000;
|
||||
|
||||
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;
|
||||
extern void __analogWriteRange(uint32_t range) {
|
||||
if (range > 0) {
|
||||
analogScale = range;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
extern void __analogWriteFreq(uint32_t freq) {
|
||||
if (freq < 100) {
|
||||
analogFreq = 100;
|
||||
} else if (freq > 40000) {
|
||||
analogFreq = 40000;
|
||||
} else {
|
||||
analogFreq = freq;
|
||||
}
|
||||
}
|
||||
|
||||
void prep_pwm_steps()
|
||||
{
|
||||
if(pwm_mask == 0) {
|
||||
return;
|
||||
}
|
||||
extern void __analogWrite(uint8_t pin, int val) {
|
||||
if (pin >= 16) {
|
||||
return;
|
||||
}
|
||||
uint32_t analogPeriod = 1000000L / analogFreq;
|
||||
if (val < 0) {
|
||||
val = 0;
|
||||
} else if (val > analogScale) {
|
||||
val = analogScale;
|
||||
}
|
||||
|
||||
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;
|
||||
analogMap &= ~(1 << pin);
|
||||
uint32_t high = (analogPeriod * val) / analogScale;
|
||||
uint32_t low = analogPeriod - high;
|
||||
if (low == 0) {
|
||||
stopWaveform(pin);
|
||||
digitalWrite(pin, HIGH);
|
||||
} else if (high == 0) {
|
||||
stopWaveform(pin);
|
||||
digitalWrite(pin, LOW);
|
||||
} else {
|
||||
if (startWaveform(pin, high, low, 0)) {
|
||||
analogMap |= (1 << pin);
|
||||
}
|
||||
|
||||
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() //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()
|
||||
{
|
||||
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((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")));
|
||||
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")));
|
||||
|
Reference in New Issue
Block a user