Skip to content

Commit f5fd591

Browse files
Allow specifying waveform generator in source code (#7800)
* Allow specifying waveform generator in source code Allows code to explicitly specify which waveform generator it wants, without needing to use one of the 100 IDE menus or adding a `-D` compile-time define. Uses weakrefs to allow for apps to call `enablePhaseLockedWaveform();` within their `setup()` (or anywhere, really) and have the phase locked versions override the default waveform generators automatically. For example: ```` void setup() { // Uncomment following line to use phase-locked waveform generator // enablePhaseLockedWaveform(); Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output analogWriteRange(1000); } void loop() { analogWrite(LED_BUILTIN, 100); delay(1000); // Wait for a second analogWrite(LED_BUILTIN, 900); delay(2000); // Wait for two seconds (to demonstrate the active low LED) } ```` Also adds an example showing it's use. Address @dok-net's comments and also remove the _weak/_bound version of startWaveform() since it's invariant of the actual waveform generator.
1 parent a4b6003 commit f5fd591

14 files changed

+283
-413
lines changed

boards.txt

Lines changed: 0 additions & 137 deletions
Large diffs are not rendered by default.

cores/esp8266/Tone.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat
3030
return;
3131
}
3232

33-
#ifndef WAVEFORM_LOCKED_PHASE
3433
// Stop any analogWrites (PWM) because they are a different generator
3534
_stopPWM(_pin);
36-
#endif
3735
// If there's another Tone or startWaveform on this pin
3836
// it will be changed on-the-fly (no need to stop it)
3937

cores/esp8266/core_esp8266_waveform.h

Lines changed: 126 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,128 @@
1-
// Wrapper to include both versions of the waveform generator
1+
/*
2+
esp8266_waveform - General purpose waveform generation and control,
3+
supporting outputs on all pins in parallel.
4+
5+
-- Default, PWM locked version --
6+
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
7+
8+
The core idea is to have a programmable waveform generator with a unique
9+
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
10+
set to 1-shot mode and is always loaded with the time until the next edge
11+
of any live waveforms.
12+
13+
Up to one waveform generator per pin supported.
14+
15+
Each waveform generator is synchronized to the ESP clock cycle counter, not the
16+
timer. This allows for removing interrupt jitter and delay as the counter
17+
always increments once per 80MHz clock. Changes to a waveform are
18+
contiguous and only take effect on the next waveform transition,
19+
allowing for smooth transitions.
20+
21+
This replaces older tone(), analogWrite(), and the Servo classes.
22+
23+
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
24+
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
25+
cycles (which may be 2 CPU clock cycles @ 160MHz).
26+
----------
27+
28+
-- Phase locked version --
29+
Copyright (c) 2020 Dirk O. Kaar.
30+
31+
The core idea is to have a programmable waveform generator with a unique
32+
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
33+
set to 1-shot mode and is always loaded with the time until the next edge
34+
of any live waveforms.
35+
36+
Up to one waveform generator per pin supported.
37+
38+
Each waveform generator is synchronized to the ESP clock cycle counter, not the
39+
timer. This allows for removing interrupt jitter and delay as the counter
40+
always increments once per 80MHz clock. Changes to a waveform are
41+
contiguous and only take effect on the next waveform transition,
42+
allowing for smooth transitions.
43+
44+
This replaces older tone(), analogWrite(), and the Servo classes.
45+
46+
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
47+
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
48+
cycles (which may be 2 CPU clock cycles @ 160MHz).
49+
----------
50+
51+
This library is free software; you can redistribute it and/or
52+
modify it under the terms of the GNU Lesser General Public
53+
License as published by the Free Software Foundation; either
54+
version 2.1 of the License, or (at your option) any later version.
55+
56+
This library is distributed in the hope that it will be useful,
57+
but WITHOUT ANY WARRANTY; without even the implied warranty of
58+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
59+
Lesser General Public License for more details.
60+
61+
You should have received a copy of the GNU Lesser General Public
62+
License along with this library; if not, write to the Free Software
63+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
64+
*/
65+
66+
#include <Arduino.h>
67+
68+
#ifndef __ESP8266_WAVEFORM_H
69+
#define __ESP8266_WAVEFORM_H
70+
71+
#ifdef __cplusplus
72+
extern "C" {
73+
#endif
74+
75+
// Call this function in your setup() to cause the phase locked version of the generator to
76+
// be linked in automatically. Otherwise, the default PWM locked version will be used.
77+
void enablePhaseLockedWaveform(void);
78+
79+
80+
// Start or change a waveform of the specified high and low times on specific pin.
81+
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
82+
// full period.
83+
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
84+
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
85+
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
86+
// under load, for applications where frequency or duty cycle must not change, leave false.
87+
// Returns true or false on success or failure.
88+
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS = 0,
89+
// Following parameters are ignored unless in PhaseLocked mode
90+
int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
91+
92+
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
93+
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
94+
// full period.
95+
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
96+
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
97+
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
98+
// under load, for applications where frequency or duty cycle must not change, leave false.
99+
// Returns true or false on success or failure.
100+
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys = 0,
101+
// Following parameters are ignored unless in PhaseLocked mode
102+
int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
103+
104+
// Stop a waveform, if any, on the specified pin.
105+
// Returns true or false on success or failure.
106+
int stopWaveform(uint8_t pin);
107+
108+
// Add a callback function to be called on *EVERY* timer1 trigger. The
109+
// callback must return the number of CPU clock cycles until the next desired call.
110+
// However, since it is called every timer1 interrupt, it may be called
111+
// again before this period. It should therefore use the ESP Cycle Counter
112+
// to determine whether or not to perform an operation.
113+
// Pass in NULL to disable the callback and, if no other waveforms being
114+
// generated, stop the timer as well.
115+
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
116+
void setTimer1Callback(uint32_t (*fn)());
117+
118+
119+
// Internal-only calls, not for applications
120+
extern void _setPWMFreq(uint32_t freq);
121+
extern bool _stopPWM(uint8_t pin);
122+
extern bool _setPWM(int pin, uint32_t val, uint32_t range);
123+
124+
#ifdef __cplusplus
125+
}
126+
#endif
2127

3-
#ifdef WAVEFORM_LOCKED_PHASE
4-
#include "core_esp8266_waveform_phase.h"
5-
#else
6-
#include "core_esp8266_waveform_pwm.h"
7128
#endif

cores/esp8266/core_esp8266_waveform_phase.cpp

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,26 @@
3939
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
4040
*/
4141

42-
#ifdef WAVEFORM_LOCKED_PHASE
43-
44-
#include "core_esp8266_waveform_phase.h"
42+
#include "core_esp8266_waveform.h"
4543
#include <Arduino.h>
44+
#include "debug.h"
4645
#include "ets_sys.h"
4746
#include <atomic>
4847

48+
49+
extern "C" void enablePhaseLockedWaveform (void)
50+
{
51+
// Does nothing, added to app to enable linking these versions
52+
// of the waveform functions instead of the default.
53+
DEBUGV("Enabling phase locked waveform generator\n");
54+
}
55+
56+
// No-op calls to override the PWM implementation
57+
extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; }
58+
extern "C" bool _stopPWM_weak(int pin) { (void) pin; return false; }
59+
extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; }
60+
61+
4962
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
5063
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
5164
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
@@ -122,7 +135,7 @@ static void ICACHE_RAM_ATTR deinitTimer() {
122135
extern "C" {
123136

124137
// Set a callback. Pass in NULL to stop it
125-
void setTimer1Callback(uint32_t (*fn)()) {
138+
void setTimer1Callback_weak(uint32_t (*fn)()) {
126139
waveform.timer1CB = fn;
127140
std::atomic_thread_fence(std::memory_order_acq_rel);
128141
if (!waveform.timer1Running && fn) {
@@ -132,17 +145,10 @@ void setTimer1Callback(uint32_t (*fn)()) {
132145
}
133146
}
134147

135-
int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS,
136-
uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
137-
return startWaveformClockCycles(pin,
138-
microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS),
139-
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
140-
}
141-
142148
// Start up a waveform on a pin, or change the current one. Will change to the new
143149
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
144150
// first, then it will immediately begin.
145-
int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
151+
int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
146152
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
147153
uint32_t periodCcys = highCcys + lowCcys;
148154
if (periodCcys < MAXIRQTICKSCCYS) {
@@ -212,7 +218,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
212218
}
213219

214220
// Stops a waveform on a pin
215-
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
221+
ICACHE_RAM_ATTR int stopWaveform_weak(uint8_t pin) {
216222
// Can't possibly need to stop anything if there is no timer active
217223
if (!waveform.timer1Running) {
218224
return false;
@@ -436,5 +442,3 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
436442
// Register access is fast and edge IRQ was configured before.
437443
T1L = nextEventCcys;
438444
}
439-
440-
#endif // WAVEFORM_LOCKED_PHASE

cores/esp8266/core_esp8266_waveform_phase.h

Lines changed: 0 additions & 93 deletions
This file was deleted.

0 commit comments

Comments
 (0)