From 5ae3efb8230ed8f4afe3e165325ffe37d8283523 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" <19971886+dok-net@users.noreply.github.com> Date: Sun, 15 Nov 2020 19:13:34 +0100 Subject: [PATCH 01/13] EspSoftwareSerial 6.10.0: override keyword for recent Print::availableForWrite() addition (#7710) * Strict C++ use of "override" update for Print::availableForWrite addition, and ESP32 GPIO fix. * EspSoftwareSerial: minor version update to 6.10.0 --- libraries/SoftwareSerial | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index 4c08ee8d2..6d520c259 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit 4c08ee8d2cb7b5b27eb4f86797694cbac94aa5c9 +Subproject commit 6d520c259cad4457ccdbee362c16f7fa3b504b06 From 8fe80f163063470bf2a3188cd827f74260f42283 Mon Sep 17 00:00:00 2001 From: Takayuki 'January June' Suwa Date: Mon, 16 Nov 2020 18:40:48 +0900 Subject: [PATCH 02/13] WString: Optimize a bit (#7553) * WString: Optimize a bit * move bodies of dtor, `init()` and `charAt()` to .h (implicitly inlined) * unify descriptions of the initialization into one: `init()` (literally), that is called from each ctors, `invalidate()` and `move()` * invert the SSO state logic in order to make init state zeroed (as a result, each inlined `init()` saves 1 insn) * detab and trim * remove `inline` from .h * cosmetics * optimize the non-SSO -> SSO transition part of `changeBuffer()` * remove duped body of `operator =(StringSumHelper &&rval)` * remove common subexpressions from `lastIndexOf()` and `substring()` * eliminate `strlen(buf)` after calling `sprintf(buf, ...)` that returns # of chars written * eliminate `len()` after calling `setLen(newlen)` * make ctor`(char c)` inlineable * optimize `setLen()` * replace constant-forwarding overload functions with default argument ones * optimize `concat(char c)` * * optimize `init()` more --- cores/esp8266/WString.cpp | 240 ++++++++++++++------------------------ cores/esp8266/WString.h | 218 +++++++++++++++++++--------------- 2 files changed, 212 insertions(+), 246 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 3dda69bda..8eaa88d91 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -55,14 +55,6 @@ String::String(StringSumHelper &&rval) noexcept { move(rval); } -String::String(char c) { - init(); - char buf[2]; - buf[0] = c; - buf[1] = 0; - *this = buf; -} - String::String(unsigned char value, unsigned char base) { init(); char buf[1 + 8 * sizeof(unsigned char)]; @@ -91,7 +83,7 @@ String::String(unsigned int value, unsigned char base) { String::String(long value, unsigned char base) { init(); char buf[2 + 8 * sizeof(long)]; - if (base==10) { + if (base == 10) { sprintf(buf, "%ld", value); } else { ltoa(value, buf, base); @@ -118,31 +110,21 @@ String::String(double value, unsigned char decimalPlaces) { *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); } -String::~String() { - invalidate(); -} - /*********************************************/ /* Memory Management */ /*********************************************/ -inline void String::init(void) { - setSSO(true); - setLen(0); - wbuffer()[0] = 0; -} - void String::invalidate(void) { - if(!isSSO() && wbuffer()) + if (!isSSO() && wbuffer()) free(wbuffer()); init(); } unsigned char String::reserve(unsigned int size) { - if(buffer() && capacity() >= size) + if (buffer() && capacity() >= size) return 1; - if(changeBuffer(size)) { - if(len() == 0) + if (changeBuffer(size)) { + if (len() == 0) wbuffer()[0] = 0; return 1; } @@ -157,35 +139,32 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) { uint16_t oldLen = len(); setSSO(true); setLen(oldLen); - return 1; } else { // if bufptr && !isSSO() // Using bufptr, need to shrink into sso.buff - char temp[sizeof(sso.buff)]; - memcpy(temp, buffer(), maxStrLen); - free(wbuffer()); + const char *temp = buffer(); uint16_t oldLen = len(); setSSO(true); setLen(oldLen); memcpy(wbuffer(), temp, maxStrLen); - return 1; + free((void *)temp); } + return 1; } // Fallthrough to normal allocator size_t newSize = (maxStrLen + 16) & (~0xf); // Make sure we can fit newsize in the buffer if (newSize > CAPACITY_MAX) { - return false; + return 0; } uint16_t oldLen = len(); - char *newbuffer = (char *) realloc(isSSO() ? nullptr : wbuffer(), newSize); + char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize); if (newbuffer) { size_t oldSize = capacity() + 1; // include NULL. if (isSSO()) { // Copy the SSO buffer into allocated space memmove_P(newbuffer, sso.buff, sizeof(sso.buff)); } - if (newSize > oldSize) - { + if (newSize > oldSize) { memset(newbuffer + oldSize, 0, newSize - oldSize); } setSSO(false); @@ -201,7 +180,7 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) { /* Copy and Move */ /*********************************************/ -String & String::copy(const char *cstr, unsigned int length) { +String &String::copy(const char *cstr, unsigned int length) { if (!reserve(length)) { invalidate(); return *this; @@ -211,7 +190,7 @@ String & String::copy(const char *cstr, unsigned int length) { return *this; } -String & String::copy(const __FlashStringHelper *pstr, unsigned int length) { +String &String::copy(const __FlashStringHelper *pstr, unsigned int length) { if (!reserve(length)) { invalidate(); return *this; @@ -227,44 +206,35 @@ void String::move(String &rhs) noexcept { rhs.init(); } -String & String::operator =(const String &rhs) { +String &String::operator =(const String &rhs) { if (this == &rhs) return *this; - if (rhs.buffer()) copy(rhs.buffer(), rhs.len()); else invalidate(); - return *this; } -String & String::operator =(String &&rval) noexcept { +String &String::operator =(String &&rval) noexcept { if (this != &rval) move(rval); return *this; } -String & String::operator =(StringSumHelper &&rval) noexcept { - if (this != &rval) - move(rval); - return *this; -} - -String & String::operator =(const char *cstr) { +String &String::operator =(const char *cstr) { if (cstr) copy(cstr, strlen(cstr)); else invalidate(); - return *this; } -String & String::operator = (const __FlashStringHelper *pstr) -{ - if (pstr) copy(pstr, strlen_P((PGM_P)pstr)); - else invalidate(); - +String &String::operator =(const __FlashStringHelper *pstr) { + if (pstr) + copy(pstr, strlen_P((PGM_P)pstr)); + else + invalidate(); return *this; } @@ -285,7 +255,7 @@ unsigned char String::concat(const String &s) { return 0; memmove_P(wbuffer() + len(), buffer(), len()); setLen(newlen); - wbuffer()[len()] = 0; + wbuffer()[newlen] = 0; return 1; } else { return concat(s.buffer(), s.len()); @@ -313,22 +283,17 @@ unsigned char String::concat(const char *cstr) { } unsigned char String::concat(char c) { - char buf[2]; - buf[0] = c; - buf[1] = 0; - return concat(buf, 1); + return concat(&c, 1); } unsigned char String::concat(unsigned char num) { char buf[1 + 3 * sizeof(unsigned char)]; - sprintf(buf, "%d", num); - return concat(buf, strlen(buf)); + return concat(buf, sprintf(buf, "%d", num)); } unsigned char String::concat(int num) { char buf[2 + 3 * sizeof(int)]; - sprintf(buf, "%d", num); - return concat(buf, strlen(buf)); + return concat(buf, sprintf(buf, "%d", num)); } unsigned char String::concat(unsigned int num) { @@ -339,8 +304,7 @@ unsigned char String::concat(unsigned int num) { unsigned char String::concat(long num) { char buf[2 + 3 * sizeof(long)]; - sprintf(buf, "%ld", num); - return concat(buf, strlen(buf)); + return concat(buf, sprintf(buf, "%ld", num)); } unsigned char String::concat(unsigned long num) { @@ -351,22 +315,25 @@ unsigned char String::concat(unsigned long num) { unsigned char String::concat(float num) { char buf[20]; - char* string = dtostrf(num, 4, 2, buf); + char *string = dtostrf(num, 4, 2, buf); return concat(string, strlen(string)); } unsigned char String::concat(double num) { char buf[20]; - char* string = dtostrf(num, 4, 2, buf); + char *string = dtostrf(num, 4, 2, buf); return concat(string, strlen(string)); } -unsigned char String::concat(const __FlashStringHelper * str) { - if (!str) return 0; +unsigned char String::concat(const __FlashStringHelper *str) { + if (!str) + return 0; int length = strlen_P((PGM_P)str); - if (length == 0) return 1; + if (length == 0) + return 1; unsigned int newlen = len() + length; - if (!reserve(newlen)) return 0; + if (!reserve(newlen)) + return 0; memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1); setLen(newlen); return 1; @@ -376,79 +343,78 @@ unsigned char String::concat(const __FlashStringHelper * str) { /* Concatenate */ /*********************************************/ -StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(rhs.buffer(), rhs.len())) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) { + StringSumHelper &a = const_cast(lhs); if (!cstr || !a.concat(cstr, strlen(cstr))) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, char c) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, char c) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(c)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, int num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, int num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, long num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, long num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, float num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, float num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, double num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, double num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs) -{ - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(rhs)) a.invalidate(); return a; @@ -459,11 +425,11 @@ StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHel /*********************************************/ int String::compareTo(const String &s) const { - if(!buffer() || !s.buffer()) { - if(s.buffer() && s.len() > 0) - return 0 - *(unsigned char *) s.buffer(); - if(buffer() && len() > 0) - return *(unsigned char *) buffer(); + if (!buffer() || !s.buffer()) { + if (s.buffer() && s.len() > 0) + return 0 - *(unsigned char *)s.buffer(); + if (buffer() && len() > 0) + return *(unsigned char *)buffer(); return 0; } return strcmp(buffer(), s.buffer()); @@ -521,7 +487,7 @@ unsigned char String::equalsConstantTime(const String &s2) const { //at this point lengths are the same if (len() == 0) return 1; - //at this point lenghts are the same and non-zero + //at this point lengths are the same and non-zero const char *p1 = buffer(); const char *p2 = s2.buffer(); unsigned int equalchars = 0; @@ -541,19 +507,19 @@ unsigned char String::equalsConstantTime(const String &s2) const { } unsigned char String::startsWith(const String &s2) const { - if(len() < s2.len()) + if (len() < s2.len()) return 0; return startsWith(s2, 0); } unsigned char String::startsWith(const String &s2, unsigned int offset) const { - if(offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer()) + if (offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer()) return 0; return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0; } unsigned char String::endsWith(const String &s2) const { - if(len() < s2.len() || !buffer() || !s2.buffer()) + if (len() < s2.len() || !buffer() || !s2.buffer()) return 0; return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0; } @@ -562,16 +528,12 @@ unsigned char String::endsWith(const String &s2) const { /* Character Access */ /*********************************************/ -char String::charAt(unsigned int loc) const { - return operator[](loc); -} - void String::setCharAt(unsigned int loc, char c) { if (loc < len()) wbuffer()[loc] = c; } -char & String::operator[](unsigned int index) { +char &String::operator[](unsigned int index) { static char dummy_writable_char; if (index >= len() || !buffer()) { dummy_writable_char = 0; @@ -596,7 +558,7 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind unsigned int n = bufsize - 1; if (n > len() - index) n = len() - index; - strncpy((char *) buf, buffer() + index, n); + strncpy((char *)buf, buffer() + index, n); buf[n] = 0; } @@ -604,31 +566,15 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind /* Search */ /*********************************************/ -int String::indexOf(char c) const { - return indexOf(c, 0); -} - int String::indexOf(char ch, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; - const char* temp = strchr(buffer() + fromIndex, ch); + const char *temp = strchr(buffer() + fromIndex, ch); if (temp == NULL) return -1; return temp - buffer(); } -int String::indexOf(const __FlashStringHelper *s2) const { - return indexOf(s2, 0); -} - -int String::indexOf(const __FlashStringHelper *s2, unsigned int fromIndex) const { - return indexOf((const char*) s2, fromIndex); -} - -int String::indexOf(const char *s2) const { - return indexOf(s2, 0); -} - int String::indexOf(const char *s2, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; @@ -638,28 +584,25 @@ int String::indexOf(const char *s2, unsigned int fromIndex) const { return found - buffer(); } -int String::indexOf(const String &s2) const { - return indexOf(s2, 0); -} - int String::indexOf(const String &s2, unsigned int fromIndex) const { return indexOf(s2.c_str(), fromIndex); } -int String::lastIndexOf(char theChar) const { - return lastIndexOf(theChar, len() - 1); +int String::lastIndexOf(char ch) const { + return lastIndexOf(ch, len() - 1); } int String::lastIndexOf(char ch, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; - char tempchar = buffer()[fromIndex + 1]; - wbuffer()[fromIndex + 1] = '\0'; - char* temp = strrchr(wbuffer(), ch); - wbuffer()[fromIndex + 1] = tempchar; + char *writeTo = wbuffer(); + char tempchar = writeTo[fromIndex + 1]; // save the replaced character + writeTo[fromIndex + 1] = '\0'; + char *temp = strrchr(writeTo, ch); + writeTo[fromIndex + 1] = tempchar; // restore character if (temp == NULL) return -1; - return temp - buffer(); + return temp - writeTo; } int String::lastIndexOf(const String &s2) const { @@ -672,11 +615,11 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const { if (fromIndex >= len()) fromIndex = len() - 1; int found = -1; - for (char *p = wbuffer(); p <= wbuffer() + fromIndex; p++) { + for (const char *p = buffer(); p <= buffer() + fromIndex; p++) { p = strstr(p, s2.buffer()); if (!p) break; - if ((unsigned int) (p - wbuffer()) <= fromIndex) + if ((unsigned int)(p - buffer()) <= fromIndex) found = p - buffer(); } return found; @@ -693,10 +636,11 @@ String String::substring(unsigned int left, unsigned int right) const { return out; if (right > len()) right = len(); - char temp = buffer()[right]; // save the replaced character - wbuffer()[right] = '\0'; - out = wbuffer() + left; // pointer arithmetic - wbuffer()[right] = temp; //restore character + char *writeTo = wbuffer(); + char tempchar = writeTo[right]; // save the replaced character + writeTo[right] = '\0'; + out = writeTo + left; // pointer arithmetic + writeTo[right] = tempchar; // restore character return out; } @@ -713,7 +657,7 @@ void String::replace(char find, char replace) { } } -void String::replace(const String& find, const String& replace) { +void String::replace(const String &find, const String &replace) { if (len() == 0 || find.len() == 0) return; int diff = replace.len() - find.len(); @@ -735,7 +679,7 @@ void String::replace(const String& find, const String& replace) { readFrom = foundAt + find.len(); setLen(len() + diff); } - memmove_P(writeTo, readFrom, strlen(readFrom)+1); + memmove_P(writeTo, readFrom, strlen(readFrom) + 1); } else { unsigned int size = len(); // compute size needed for result while ((foundAt = strstr(readFrom, find.buffer())) != NULL) { @@ -759,13 +703,6 @@ void String::replace(const String& find, const String& replace) { } } -void String::remove(unsigned int index) { - // Pass the biggest integer as the count. The remove method - // below will take care of truncating it at the end of the - // string. - remove(index, (unsigned int) -1); -} - void String::remove(unsigned int index, unsigned int count) { if (index >= len()) { return; @@ -828,11 +765,10 @@ long String::toInt(void) const { float String::toFloat(void) const { if (buffer()) return atof(buffer()); - return 0; + return 0.0F; } -double String::toDouble(void) const -{ +double String::toDouble(void) const { if (buffer()) return atof(buffer()); return 0.0; diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index 262cfda0a..dcb098238 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -53,7 +53,7 @@ class String { // if the initial value is null or invalid, or if memory allocation // fails, the string will be marked as invalid (i.e. "if (s)" will // be false). - String() { + String() __attribute__((always_inline)) { // See init() init(); } String(const char *cstr); @@ -61,7 +61,12 @@ class String { String(const __FlashStringHelper *str); String(String &&rval) noexcept; String(StringSumHelper &&rval) noexcept; - explicit String(char c); + explicit String(char c) { + sso.buff[0] = c; + sso.buff[1] = 0; + sso.len = 1; + sso.isHeap = 0; + } explicit String(unsigned char, unsigned char base = 10); explicit String(int, unsigned char base = 10); explicit String(unsigned int, unsigned char base = 10); @@ -69,35 +74,35 @@ class String { explicit String(unsigned long, unsigned char base = 10); explicit String(float, unsigned char decimalPlaces = 2); explicit String(double, unsigned char decimalPlaces = 2); - ~String(void); + ~String() { + invalidate(); + } // memory management // return true on success, false on failure (in which case, the string // is left unchanged). reserve(0), if successful, will validate an // invalid string (i.e., "if (s)" will be true afterwards) unsigned char reserve(unsigned int size); - inline unsigned int length(void) const { - if(buffer()) { - return len(); - } else { - return 0; - } + unsigned int length(void) const { + return buffer() ? len() : 0; } - inline void clear(void) { + void clear(void) { setLen(0); } - inline bool isEmpty(void) const { + bool isEmpty(void) const { return length() == 0; } // creates a copy of the assigned value. if the value is null or // invalid, or if the memory allocation fails, the string will be // marked as invalid ("if (s)" will be false). - String & operator =(const String &rhs); - String & operator =(const char *cstr); - String & operator = (const __FlashStringHelper *str); - String & operator =(String &&rval) noexcept; - String & operator =(StringSumHelper &&rval) noexcept; + String &operator =(const String &rhs); + String &operator =(const char *cstr); + String &operator =(const __FlashStringHelper *str); + String &operator =(String &&rval) noexcept; + String &operator =(StringSumHelper &&rval) noexcept { + return operator =((String &&)rval); + } // concatenate (works w/ built-in types) @@ -114,67 +119,67 @@ class String { unsigned char concat(unsigned long num); unsigned char concat(float num); unsigned char concat(double num); - unsigned char concat(const __FlashStringHelper * str); + unsigned char concat(const __FlashStringHelper *str); unsigned char concat(const char *cstr, unsigned int length); // if there's not enough memory for the concatenated value, the string // will be left unchanged (but this isn't signalled in any way) - String & operator +=(const String &rhs) { + String &operator +=(const String &rhs) { concat(rhs); - return (*this); + return *this; } - String & operator +=(const char *cstr) { + String &operator +=(const char *cstr) { concat(cstr); - return (*this); + return *this; } - String & operator +=(char c) { + String &operator +=(char c) { concat(c); - return (*this); + return *this; } - String & operator +=(unsigned char num) { + String &operator +=(unsigned char num) { concat(num); - return (*this); + return *this; } - String & operator +=(int num) { + String &operator +=(int num) { concat(num); - return (*this); + return *this; } - String & operator +=(unsigned int num) { + String &operator +=(unsigned int num) { concat(num); - return (*this); + return *this; } - String & operator +=(long num) { + String &operator +=(long num) { concat(num); - return (*this); + return *this; } - String & operator +=(unsigned long num) { + String &operator +=(unsigned long num) { concat(num); - return (*this); + return *this; } - String & operator +=(float num) { + String &operator +=(float num) { concat(num); - return (*this); + return *this; } - String & operator +=(double num) { + String &operator +=(double num) { concat(num); - return (*this); + return *this; } - String & operator += (const __FlashStringHelper *str){ + String &operator +=(const __FlashStringHelper *str) { concat(str); - return (*this); + return *this; } - friend StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs); - friend StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr); - friend StringSumHelper & operator +(const StringSumHelper &lhs, char c); - friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, int num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, long num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, float num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, double num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs); + friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs); + friend StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr); + friend StringSumHelper &operator +(const StringSumHelper &lhs, char c); + friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, int num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, long num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, float num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, double num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs); // comparison (only works w/ Strings and "strings") operator StringIfHelperType() const { @@ -202,45 +207,45 @@ class String { unsigned char equalsIgnoreCase(const String &s) const; unsigned char equalsConstantTime(const String &s) const; unsigned char startsWith(const String &prefix) const; - unsigned char startsWith(const char * prefix) const { + unsigned char startsWith(const char *prefix) const { return this->startsWith(String(prefix)); } - unsigned char startsWith(const __FlashStringHelper * prefix) const { + unsigned char startsWith(const __FlashStringHelper *prefix) const { return this->startsWith(String(prefix)); } unsigned char startsWith(const String &prefix, unsigned int offset) const; unsigned char endsWith(const String &suffix) const; - unsigned char endsWith(const char * suffix) const { + unsigned char endsWith(const char *suffix) const { return this->endsWith(String(suffix)); } - unsigned char endsWith(const __FlashStringHelper * suffix) const { + unsigned char endsWith(const __FlashStringHelper *suffix) const { return this->endsWith(String(suffix)); } // character access - char charAt(unsigned int index) const; + char charAt(unsigned int index) const { + return operator [](index); + } void setCharAt(unsigned int index, char c); char operator [](unsigned int index) const; - char& operator [](unsigned int index); + char &operator [](unsigned int index); void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const; void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const { getBytes((unsigned char *) buf, bufsize, index); } - const char* c_str() const { return buffer(); } - char* begin() { return wbuffer(); } - char* end() { return wbuffer() + length(); } - const char* begin() const { return c_str(); } - const char* end() const { return c_str() + length(); } + const char *c_str() const { return buffer(); } + char *begin() { return wbuffer(); } + char *end() { return wbuffer() + length(); } + const char *begin() const { return c_str(); } + const char *end() const { return c_str() + length(); } // search - int indexOf(char ch) const; - int indexOf(char ch, unsigned int fromIndex) const; - int indexOf(const char *str) const; - int indexOf(const char *str, unsigned int fromIndex) const; - int indexOf(const __FlashStringHelper *str) const; - int indexOf(const __FlashStringHelper *str, unsigned int fromIndex) const; - int indexOf(const String &str) const; - int indexOf(const String &str, unsigned int fromIndex) const; + int indexOf(char ch, unsigned int fromIndex = 0) const; + int indexOf(const char *str, unsigned int fromIndex = 0) const; + int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const { + return indexOf((const char*)str, fromIndex); + } + int indexOf(const String &str, unsigned int fromIndex = 0) const; int lastIndexOf(char ch) const; int lastIndexOf(char ch, unsigned int fromIndex) const; int lastIndexOf(const String &str) const; @@ -248,29 +253,29 @@ class String { String substring(unsigned int beginIndex) const { return substring(beginIndex, len()); } - ; String substring(unsigned int beginIndex, unsigned int endIndex) const; // modification void replace(char find, char replace); - void replace(const String& find, const String& replace); - void replace(const char * find, const String& replace) { + void replace(const String &find, const String &replace); + void replace(const char *find, const String &replace) { this->replace(String(find), replace); } - void replace(const __FlashStringHelper * find, const String& replace) { + void replace(const __FlashStringHelper *find, const String &replace) { this->replace(String(find), replace); } - void replace(const char * find, const char * replace) { + void replace(const char *find, const char *replace) { this->replace(String(find), String(replace)); } - void replace(const __FlashStringHelper * find, const char * replace) { + void replace(const __FlashStringHelper *find, const char *replace) { this->replace(String(find), String(replace)); } - void replace(const __FlashStringHelper * find, const __FlashStringHelper * replace) { + void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) { this->replace(String(find), String(replace)); } - void remove(unsigned int index); - void remove(unsigned int index, unsigned int count); + // Pass the biggest integer if the count is not specified. + // The remove method below will take care of truncating it at the end of the string. + void remove(unsigned int index, unsigned int count = (unsigned int)-1); void toLowerCase(void); void toUpperCase(void); void trim(void); @@ -278,11 +283,11 @@ class String { // parsing/conversion long toInt(void) const; float toFloat(void) const; - double toDouble(void) const; + double toDouble(void) const; protected: // Contains the string info when we're not in SSO mode - struct _ptr { + struct _ptr { char * buff; uint16_t cap; uint16_t len; @@ -291,8 +296,8 @@ class String { enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more struct _sso { char buff[SSOSIZE]; - unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields - unsigned char isSSO : 1; + unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields + unsigned char isHeap : 1; } __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues enum { CAPACITY_MAX = 65535 }; // If typeof(cap) changed from uint16_t, be sure to update this enum to the max value storable in the type union { @@ -300,25 +305,47 @@ class String { struct _sso sso; }; // Accessor functions - inline bool isSSO() const { return sso.isSSO; } - inline unsigned int len() const { return isSSO() ? sso.len : ptr.len; } - inline unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL - inline void setSSO(bool set) { sso.isSSO = set; } - inline void setLen(int len) { if (isSSO()) sso.len = len; else ptr.len = len; } - inline void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; } - inline void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; } + bool isSSO() const { return !sso.isHeap; } + unsigned int len() const { return isSSO() ? sso.len : ptr.len; } + unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL + void setSSO(bool set) { sso.isHeap = !set; } + void setLen(int len) { + if (isSSO()) { + setSSO(true); // Avoid emitting of bitwise EXTRACT-AND-OR ops (store-merging optimization) + sso.len = len; + } else + ptr.len = len; + } + void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; } + void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; } // Buffer accessor functions - inline const char *buffer() const { return (const char *)(isSSO() ? sso.buff : ptr.buff); } - inline char *wbuffer() const { return isSSO() ? const_cast(sso.buff) : ptr.buff; } // Writable version of buffer + const char *buffer() const { return wbuffer(); } + char *wbuffer() const { return isSSO() ? const_cast(sso.buff) : ptr.buff; } // Writable version of buffer protected: - void init(void); + void init(void) __attribute__((always_inline)) { + sso.buff[0] = 0; + sso.len = 0; + sso.isHeap = 0; + // Without the 6 statements shown below, GCC simply emits such as: "MOVI.N aX,0", "S8I aX,a2,0" and "S8I aX,a2,11" (8 bytes in total) + sso.buff[1] = 0; + sso.buff[2] = 0; + sso.buff[3] = 0; + sso.buff[8] = 0; + sso.buff[9] = 0; + sso.buff[10] = 0; + // With the above, thanks to store-merging, GCC can use the narrow form of 32-bit store insn ("S32I.N") and emits: + // "MOVI.N aX,0", "S32I.N aX,a2,0" and "S32I.N aX,a2,8" (6 bytes in total) + // (Literature: Xtensa(R) Instruction Set Reference Manual, "S8I - Store 8-bit" [p.504] and "S32I.N - Narrow Store 32-bit" [p.512]) + // Unfortunately, GCC seems not to re-evaluate the cost of inlining after the store-merging optimizer stage, + // `always_inline` attribute is necessary in order to keep inlining. + } void invalidate(void); unsigned char changeBuffer(unsigned int maxStrLen); // copy and move - String & copy(const char *cstr, unsigned int length); - String & copy(const __FlashStringHelper *pstr, unsigned int length); + String ©(const char *cstr, unsigned int length); + String ©(const __FlashStringHelper *pstr, unsigned int length); void move(String &rhs) noexcept; }; @@ -354,6 +381,9 @@ class StringSumHelper: public String { StringSumHelper(double num) : String(num) { } + StringSumHelper(const __FlashStringHelper *s) : + String(s) { + } }; extern const String emptyString; From 0e735e386dc17261ab230ad093f2d6d3ad1b6e9b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" <19971886+dok-net@users.noreply.github.com> Date: Thu, 19 Nov 2020 22:12:06 +0100 Subject: [PATCH 03/13] Waveform: fix significant jitter, that stresses servos and is clearly audible in Tone output (#7022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow 100% high or low periods. Let output remain at current level on stopping instead of always turning to low. * Fix serious jitter issues in previous versions. * Use ESP.getCycleCount() just like everyone else. * Highest timer rate at which this runs stable appears to be 2µs (500kHz). * Guard for zero period length undefined waveforms. Fix for zero duty or off cycles and expiring from them. * Cycle precision for expiry instead of special treatment for 0 value. * Give expiry proper precedence over updating a waveform * Important comment * Refactored, identical behavior. * Use plural for bit arrays. * Fix for completely duty or all off cycle period case. * Expiration is explicitly relative to service time. * Comment updated, here it's about cycles not usecs. * Revert misconception of how waveformToEnable/Disable communicates with the NMI handler. * Rewrite to keep phase in sync if period remains same during duty cycle change. Refactor identifies to distinguish CPU clock cycle from waveform cycle. * Rather iterate even if full-duty or no-duty cycle in period, than too many calculations in NMI handler. * Must fire timer early to reach waveform deadlines, otherwise under some load aggressive jitter occurs. * Schedule expiry explicitly, too. Needed to keep track of next timer ccy in each iteration, not just when changing level. * Quick change lets analogWrite keep phase for any duty cycle (including 0% and 100%). * Set duration to multiple of period, so tone stops on LOW pin output. * Improve phase timing * Eror causing next Timer IRQ to fail busy-to-off cycle transitions. * Regression fix, don't reset timer if pending shortly. * Rather reschedule ISR instead of busy looping during permitted maximum time. * Lead time improved for ISR * Reduce number of cycle calculations. * Reactive the gcc optimize pragmas. * Simplify calculation. * handles overshoot where an updated period is shorter than the previous duty cycle * Misleading code, there must ever be only one bit set at a time, start and stop block until the ISR has handled and reset the token. * Prevent missing a duty cycle unless it is overshot already. * Continuously remove distant pending waveform edges from the loop, continuously update now. * Replace volatile for one-way exchange into ISR with memory fence. * Remove redundant stack object. * Revert pending waveform removal from loop - corrupts continuous next event computation. * Reduce if/do ... while to while * Convert relative timings to absolute. * Relax waveform start to possibly cluster phases into same IRQ interval. * max 12us in ISR seems to work best for servo/fan/led/tone combo test. * Restructured code in ISR for expiration, this saves 36 byte IRAM, and improves PWM resolution. * Simplified overshot detection and 0% / 100% duty cycle. * Leave ISR early if rescheduling is more promising than busy-waiting until next edge. * Stabilized timings. * Prevent WDT under load. * Use clock cycle resolution instead of us for analogWrite. * Reduce idle calculations in ISR. * Optimize in-ISR time. * Support starting new waveform in phase with another running waveform. * Align phase for analogWrite PWMs. * Tune preshoot, add lost period fast forward. * Adapt phase sync code from analogWrite to Servo * Fix for going off 100% duty cycle period. * Eschew obfuscation. * Fixed logic for zero duty cycle. * Determine generator quantum during same IRQ - this is better than timer resolution, but non-zero. * Tune timings, fix write barriers and overshoot logic. * Migrate Tone to waveform with CPU cycle precision * Can do 60kHz PWM. * Recalibrated timings after performance optimizations. Initialize GPIO if needed. * Fix regression for waveform runtime. * Test cycle duration values for signed arithmetic safety. * Performance tuning. * Performance tweak, in-ISR quantum is now 1.12µs. * Round up duration instead of down - possibly to zero, which means forever. * Extend phase alignment with optional phase offset. * Slightly better in-ISR quantum approximation for steadier increments. * Waveform stopped by runtime limit in iSR doesn't deinit the timer, but stopWaveform refuses to do anything if the waveform was stopped by runtime, either. * Improved quantum correction code. * Fix broken multi-wave generation. * Aggregate GPIO output across inner loop. True phase sync, and now better performance. * IRQ latency can be reduced from 2 to 1 us now, no WDT etc. * Improved handling of complete idle cycle miss, progress directly into duty cycle. * Recalibrated after latest changes and reverts. * Overshoot compensation for duty cycle results in PWM milestone. * Adjustments to duty/idle cycle to mitigate effects of floating duty cycle logic. * Remove implicit condition from loop guard and fix timer restart duration * Host all static globals in an anonymous static struct. * Busy wait directly for next pending event and go to that pin. * Record nextEventCcy in waveform struct to save a few cycles. * Adapt duty cycle modification to only fix full duty and all idle cases. * Remember next pin to operate between IRQs. * Don't set pinMode each time on already running PWM or Tone. * Remove quantum, correct irq latency from testing,reuse isr timeout from master et al * Move updating "now" out of inner loop, prevents float between pins that are in phase lock. * Merge init loop with action loop again. * Adaptive PWM frequency and floating duty cycle. * Predictive static frequency scaling. * Dynamic frequency down-scaling * Frequency scaling is only for PWM-like applications, anything needing real time duty cycles or frequency must be able to fail on overload. * Conserve IRAM cache, resort to best effort. * Directly scale frequency for all duty/all idle waves to reasonable maximum, reduces thrashing. * Getting the math right beats permanently reducing PWM frequency. * Rename identifier to help think about the problem. * AutoPwm correction moved to correct location - after overshoot recalc - and allow limited duty floating * Finish overshoot math fixes. * First set pin mode, then digital write. * Simplify calculations, fix non-autoPwm for servo use, where exact duty is needed, idle is elastic. * Move wave initialization and modification outside the inner loop. * Some optimizing. * Updating "now" in the inner loop should lessen interference * Finally get rid of volatile and use atomic thread fence memory barriers, great for ISR performance. * Improved idle cycle overshoot mitigation. * Improved duty cycle overshoot mitigation. Case for investigation: 3% (shl 5) vs. 1.5% (shl 6), either less fuzz, but a few marked stray spots, or more fuzz, but no bumps in counter-PWM travel test. * Move startPin etc. into common static struct * Persist next event cycle across ISR invocations, like initPin was before. * Recalibrated DELTAIRQ and IRQLATENCY. Tested @ 3x 40kHz PWM + 440Hz Tone * CPU clock to Timer1 ccy correction must be dynamic even when BSP is compiled for fixed CPU clock. * Corrected use of Timer1 registers and add rationale to Timer1 use in comment. Recalibrate for improved frequence downscaling @ 80MHz and 160MHz. * Let duty cycle overshoot correction depend on relative impact compareed to both period and duty. * 80MHz/160MHz specific code can be compile-time selected in general, only NMI is affected by apparent CPU frequency scaling in SDK code. * Seems that removing the redudant resetting of edge interrupt mode shaves 0.5us off rearm latency. * Recalibrated delta irq ccys. * Off-by-one in 100% duty overshoot correction. * Simple register writes. * Memory fences checked and joining events into same loop iteration that are close to one another. * Shorten progression when going off 100% duty. * Code simplifications. * Dynamically map pins out from in-ISR handling based on next event timing. Major performance boost. * Reverting maximum IRQ period to 10ms. This sets the wave reprogramming rate to 100Hz max. * Revert recent change that is the most likely cause of reported PWM frequency drop regression. * Much simplified overshoot mitigation code. * Fixing overshoot mitigation, 3x 880Hz, 256 states now. * Increase resolution by keeping reference time moving forward earlier. * Mitigation logic for ESP8266 SDK boosting to 160MHz during some WiFi ops. * Event timestamps are all recorded for compile-time CPU frequency, the timer ticks conversion must be set at compile-time also. The SDK WiFi 160MHz boost mitigation temporarily handles the CPU clock running twice as fast. * Expired pins must not be checked for next event. * Recalibrate after latest changes. * Save a few bytes code. * Guards are in place, so xor rather than and bitwise not. * Reduce memory use. * SDK boost to 160MHz may last across multiple ISR invocations, therefore adjust target ccy instead of ccount. * Overshoot mitigation w/o PWM frequency change. * New PWM overshoot mitigation code keeps frequency. Averages duty between consecutive periods. * Small refactoring, remove code path that is never taken even at 3x25kHz/1023 PWM. * Don't ever skip off duty, no matter if late or infinitely short. * Shed speed-up code that didn't speed up things. * Must always recompute new waveform.nextEventCcy if there is any busy pin. * Break out of ISR if timespan to next event allows, instead of busy waiting and stealing CPU cycles from userland. * Minor code simplification. * Improve code efficiency. * Improved performance of loop. * Recalibrated. * No positive effect of lead time inclusion was found during testing, remove this code. Maximum period duration limit is implicit to timer, consider it documented constraint, don't runtime check in ISR. * Fix WDT when at 160MHz CPU clock the Timer1 is set below 1µs. * Consolidate 160MHz constexpr check, finish 1µs minimum for Timer1 fix. * Test for non-zero before subtract should improve performance. * Reviewers/tested noted they were seeing WDT, and this change appeared to fix that. * More expressive use of parentheses and alias CPU2X for reduced code size. * Bug fix: at 160MHz compiled, don't force minimum Timer1 latency to 2µs. * Alternate CPU frequency scaling mitigation. * Handle time-of-flight in the right spot. * Remove _toneMap from Tone.cpp Co-authored-by: david gauchard --- cores/esp8266/Tone.cpp | 9 +- cores/esp8266/core_esp8266_waveform.cpp | 522 +++++++++++++--------- cores/esp8266/core_esp8266_waveform.h | 23 +- cores/esp8266/core_esp8266_wiring_pwm.cpp | 24 +- libraries/Servo/src/Servo.cpp | 6 +- 5 files changed, 358 insertions(+), 226 deletions(-) diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index 064fdad5d..601b1df51 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -25,10 +25,6 @@ #include "core_esp8266_waveform.h" #include "user_interface.h" -// Which pins have a tone running on them? -static uint32_t _toneMap = 0; - - static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) { if (_pin > 16) { return; @@ -42,9 +38,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat duration = microsecondsToClockCycles(duration * 1000UL); duration += high + low - 1; duration -= duration % (high + low); - if (startWaveformClockCycles(_pin, high, low, duration)) { - _toneMap |= 1 << _pin; - } + startWaveformClockCycles(_pin, high, low, duration); } @@ -86,6 +80,5 @@ void noTone(uint8_t _pin) { return; } stopWaveform(_pin); - _toneMap &= ~(1 << _pin); digitalWrite(_pin, 0); } diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 597a8e88a..952e1fd19 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -3,6 +3,7 @@ supporting outputs on all pins in parallel. Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + Copyright (c) 2020 Dirk O. Kaar. The core idea is to have a programmable waveform generator with a unique high and low period (defined in microseconds or CPU clock cycles). TIMER1 is @@ -19,8 +20,8 @@ This replaces older tone(), analogWrite(), and the Servo classes. - Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() - clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() + clock cycle time, or an interval measured in clock cycles, but not TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz). This library is free software; you can redistribute it and/or @@ -38,275 +39,398 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "core_esp8266_waveform.h" #include #include "ets_sys.h" -#include "core_esp8266_waveform.h" +#include -extern "C" { +// Timer is 80MHz fixed. 160MHz CPU frequency need scaling. +constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160; +// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz +constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); +// Maximum servicing time for any single IRQ +constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); +// The latency between in-ISR rearming of the timer and the earliest firing +constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2); +// The SDK and hardware take some time to actually get to our NMI code +constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ? + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); -// Maximum delay between IRQs -#define MAXIRQUS (10000) - -// Set/clear GPIO 0-15 by bitmask -#define SetGPIO(a) do { GPOS = a; } while (0) -#define ClearGPIO(a) do { GPOC = a; } while (0) +// for INFINITE, the NMI proceeds on the waveform without expiry deadline. +// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. +// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. +// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. +enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3}; // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextServiceCycle; // ESP cycle timer when a transition required - 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 nextTimeLowCycles; // Copy over high->low to keep smooth waveform + uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count + uint32_t endDutyCcy; // ESP clock cycle when going from duty to off + int32_t dutyCcys; // Set next off cycle at low->high to maintain phase + int32_t adjDutyCcys; // Temporary correction for next period + int32_t periodCcys; // Set next phase cycle at low->high to maintain phase + uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count + WaveformMode mode; + int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin + bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings } Waveform; -static Waveform waveform[17]; // State of all possible pins -static volatile uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code -static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code +namespace { -// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine -static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin -static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation + static struct { + Waveform pins[17]; // State of all possible pins + uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code + uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code -static uint32_t (*timer1CB)() = NULL; + // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine + int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform + int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation + uint32_t(*timer1CB)() = nullptr; -// Non-speed critical bits -#pragma GCC optimize ("Os") + bool timer1Running = false; + + uint32_t nextEventCcy; + } waveform; -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 bool timerRunning = false; + +// Non-speed critical bits +#pragma GCC optimize ("Os") static void initTimer() { timer1_disable(); ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); - timerRunning = true; + waveform.timer1Running = true; + timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste } static void ICACHE_RAM_ATTR deinitTimer() { ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); timer1_disable(); timer1_isr_init(); - timerRunning = false; + waveform.timer1Running = false; } +extern "C" { + // Set a callback. Pass in NULL to stop it void setTimer1Callback(uint32_t (*fn)()) { - timer1CB = fn; - if (!timerRunning && fn) { + waveform.timer1CB = fn; + std::atomic_thread_fence(std::memory_order_acq_rel); + if (!waveform.timer1Running && fn) { initTimer(); - timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste - } else if (timerRunning && !fn && !waveformEnabled) { + } else if (waveform.timer1Running && !fn && !waveform.enabled) { deinitTimer(); } } +int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS, + uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { + return startWaveformClockCycles(pin, + microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS), + microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm); +} + // Start up a waveform on a pin, or change the current one. Will change to the new // waveform smoothly on next low->high transition. For immediate change, stopWaveform() // first, then it will immediately begin. -int 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)) { +int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, + uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) { + uint32_t periodCcys = highCcys + lowCcys; + if (periodCcys < MAXIRQTICKSCCYS) { + if (!highCcys) { + periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; + } + else if (!lowCcys) { + highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; + } + } + // sanity checks, including mixed signed/unsigned arithmetic safety + if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || + static_cast(periodCcys) <= 0 || + static_cast(highCcys) < 0 || static_cast(lowCcys) < 0) { return false; } - Waveform *wave = &waveform[pin]; - // Adjust to shave off some of the IRQ time, approximately - wave->nextTimeHighCycles = timeHighCycles; - wave->nextTimeLowCycles = timeLowCycles; - wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0; - if (runTimeCycles && !wave->expiryCycle) { - wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it - } + Waveform& wave = waveform.pins[pin]; + wave.dutyCcys = highCcys; + wave.adjDutyCcys = 0; + wave.periodCcys = periodCcys; + wave.autoPwm = autoPwm; - uint32_t mask = 1<nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1); - waveformToEnable |= mask; - if (!timerRunning) { - initTimer(); - timer1_write(microsecondsToClockCycles(10)); - } else { - // Ensure timely service.... - if (T1L > microsecondsToClockCycles(10)) { - timer1_write(microsecondsToClockCycles(10)); + std::atomic_thread_fence(std::memory_order_acquire); + const uint32_t pinBit = 1UL << pin; + if (!(waveform.enabled & pinBit)) { + // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR + wave.nextPeriodCcy = phaseOffsetCcys; + wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count + wave.mode = WaveformMode::INIT; + wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; + if (!wave.dutyCcys) { + // If initially at zero duty cycle, force GPIO off + if (pin == 16) { + GP16O = 0; + } + else { + GPOC = pinBit; } } - while (waveformToEnable) { - delay(0); // Wait for waveform to update + std::atomic_thread_fence(std::memory_order_release); + waveform.toSetBits = 1UL << pin; + std::atomic_thread_fence(std::memory_order_release); + if (!waveform.timer1Running) { + initTimer(); + } + else if (T1V > IRQLATENCYCCYS) { + // Must not interfere if Timer is due shortly + timer1_write(IRQLATENCYCCYS); } } - - return true; -} - -// Speed critical bits -#pragma GCC optimize ("O2") -// Normally would not want two copies like this, but due to different -// optimization levels the inline attribute gets lost if we try the -// other version. - -static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() { - uint32_t ccount; - __asm__ __volatile__("rsr %0,ccount":"=a"(ccount)); - return ccount; -} - -static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) { - if (a < b) { - return a; + else { + wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI + std::atomic_thread_fence(std::memory_order_release); + wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count + if (runTimeCcys) { + wave.mode = WaveformMode::UPDATEEXPIRY; + std::atomic_thread_fence(std::memory_order_release); + waveform.toSetBits = 1UL << pin; + } } - return b; + std::atomic_thread_fence(std::memory_order_acq_rel); + while (waveform.toSetBits) { + delay(0); // Wait for waveform to update + std::atomic_thread_fence(std::memory_order_acquire); + } + return true; } // Stops a waveform on a pin int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { // Can't possibly need to stop anything if there is no timer active - if (!timerRunning) { + if (!waveform.timer1Running) { return false; } // If user sends in a pin >16 but <32, this will always point to a 0 bit // If they send >=32, then the shift will result in 0 and it will also return false - if (waveformEnabled & (1UL << pin)) { - waveformToDisable = 1UL << pin; + std::atomic_thread_fence(std::memory_order_acquire); + const uint32_t pinBit = 1UL << pin; + if (waveform.enabled & pinBit) { + waveform.toDisableBits = 1UL << pin; + std::atomic_thread_fence(std::memory_order_release); // Must not interfere if Timer is due shortly - if (T1L > microsecondsToClockCycles(10)) { - timer1_write(microsecondsToClockCycles(10)); + if (T1V > IRQLATENCYCCYS) { + timer1_write(IRQLATENCYCCYS); } - while (waveformToDisable) { + while (waveform.toDisableBits) { /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ + std::atomic_thread_fence(std::memory_order_acquire); } } - if (!waveformEnabled && !timer1CB) { + if (!waveform.enabled && !waveform.timer1CB) { deinitTimer(); } return true; } -// The SDK and hardware take some time to actually get to our NMI code, so -// decrement the next IRQ's timer value by a bit so we can actually catch the -// real CPU cycle counter we want for the waveforms. -#if F_CPU == 80000000 - #define DELTAIRQ (microsecondsToClockCycles(3)) -#else - #define DELTAIRQ (microsecondsToClockCycles(2)) -#endif +}; +// Speed critical bits +#pragma GCC optimize ("O2") -static ICACHE_RAM_ATTR void timer1Interrupt() { - // Optimize the NMI inner loop by keeping track of the min and max GPIO that we - // are generating. In the common case (1 PWM) these may be the same pin and - // we can avoid looking at the other pins. - static int startPin = 0; - static int endPin = 0; - - uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS); - uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14); - - if (waveformToEnable || waveformToDisable) { - // Handle enable/disable requests from main app. - waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off - waveformState &= ~waveformToEnable; // And clear the state of any just started - waveformToEnable = 0; - waveformToDisable = 0; - // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) - startPin = __builtin_ffs(waveformEnabled) - 1; - // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) - endPin = 32 - __builtin_clz(waveformEnabled); +// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. +// Using constexpr makes sure that the CPU clock frequency is compile-time fixed. +static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) { + if (ISCPUFREQ160MHZ) { + return isCPU2X ? ccys : (ccys >> 1); } - - bool done = false; - if (waveformEnabled) { - do { - nextEventCycles = microsecondsToClockCycles(MAXIRQUS); - for (int i = startPin; i <= endPin; i++) { - uint32_t mask = 1<expiryCycle) { - int32_t expiryToGo = wave->expiryCycle - now; - if (expiryToGo < 0) { - // Done, remove! - waveformEnabled &= ~mask; - if (i == 16) { - GP16O &= ~1; - } else { - ClearGPIO(mask); - } - continue; - } - } - - // Check for toggles - int32_t cyclesToGo = wave->nextServiceCycle - now; - if (cyclesToGo < 0) { - waveformState ^= mask; - if (waveformState & mask) { - if (i == 16) { - GP16O |= 1; // GPIO16 write slow as it's RMW - } else { - SetGPIO(mask); - } - wave->nextServiceCycle = now + wave->nextTimeHighCycles; - nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles); - } else { - if (i == 16) { - GP16O &= ~1; // GPIO16 write slow as it's RMW - } else { - ClearGPIO(mask); - } - wave->nextServiceCycle = now + wave->nextTimeLowCycles; - nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles); - } - } else { - uint32_t deltaCycles = wave->nextServiceCycle - now; - nextEventCycles = min_u32(nextEventCycles, deltaCycles); - } - } - - // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur - uint32_t now = GetCycleCountIRQ(); - int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles); - int32_t cyclesLeftTimeout = timeoutCycle - now; - done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0); - } while (!done); - } // if (waveformEnabled) - - if (timer1CB) { - nextEventCycles = min_u32(nextEventCycles, timer1CB()); + else { + return isCPU2X ? (ccys << 1) : ccys; } - - if (nextEventCycles < microsecondsToClockCycles(10)) { - nextEventCycles = microsecondsToClockCycles(10); - } - nextEventCycles -= DELTAIRQ; - - // Do it here instead of global function to save time and because we know it's edge-IRQ -#if F_CPU == 160000000 - T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS -#else - T1L = nextEventCycles; // Already know we're in range by MAXIRQUS -#endif - TEIE |= TEIE1; // Edge int enable } -}; +static ICACHE_RAM_ATTR void timer1Interrupt() { + const uint32_t isrStartCcy = ESP.getCycleCount(); + int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; + const bool isCPU2X = CPU2X & 1; + if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { + // Handle enable/disable requests from main app. + waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off + // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) + waveform.toDisableBits = 0; + } + + if (waveform.toSetBits) { + const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1; + Waveform& wave = waveform.pins[toSetPin]; + switch (wave.mode) { + case WaveformMode::INIT: + waveform.states &= ~waveform.toSetBits; // Clear the state of any just started + if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { + wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; + } + else { + wave.nextPeriodCcy = waveform.nextEventCcy; + } + if (!wave.expiryCcy) { + wave.mode = WaveformMode::INFINITE; + break; + } + // fall through + case WaveformMode::UPDATEEXPIRY: + // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X); + wave.mode = WaveformMode::EXPIRES; + break; + default: + break; + } + waveform.toSetBits = 0; + } + + // Exit the loop if the next event, if any, is sufficiently distant. + const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; + uint32_t busyPins = waveform.enabled; + waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; + + uint32_t now = ESP.getCycleCount(); + uint32_t isrNextEventCcy = now; + while (busyPins) { + if (static_cast(isrNextEventCcy - now) > IRQLATENCYCCYS) { + waveform.nextEventCcy = isrNextEventCcy; + break; + } + isrNextEventCcy = waveform.nextEventCcy; + uint32_t loopPins = busyPins; + while (loopPins) { + const int pin = __builtin_ffsl(loopPins) - 1; + const uint32_t pinBit = 1UL << pin; + loopPins ^= pinBit; + + Waveform& wave = waveform.pins[pin]; + + if (clockDrift) { + wave.endDutyCcy += clockDrift; + wave.nextPeriodCcy += clockDrift; + wave.expiryCcy += clockDrift; + } + + uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; + if (WaveformMode::EXPIRES == wave.mode && + static_cast(waveNextEventCcy - wave.expiryCcy) >= 0 && + static_cast(now - wave.expiryCcy) >= 0) { + // Disable any waveforms that are done + waveform.enabled ^= pinBit; + busyPins ^= pinBit; + } + else { + const int32_t overshootCcys = now - waveNextEventCcy; + if (overshootCcys >= 0) { + const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X); + if (waveform.states & pinBit) { + // active configuration and forward are 100% duty + if (wave.periodCcys == wave.dutyCcys) { + wave.nextPeriodCcy += periodCcys; + wave.endDutyCcy = wave.nextPeriodCcy; + } + else { + if (wave.autoPwm) { + wave.adjDutyCcys += overshootCcys; + } + waveform.states ^= pinBit; + if (16 == pin) { + GP16O = 0; + } + else { + GPOC = pinBit; + } + } + waveNextEventCcy = wave.nextPeriodCcy; + } + else { + wave.nextPeriodCcy += periodCcys; + if (!wave.dutyCcys) { + wave.endDutyCcy = wave.nextPeriodCcy; + } + else { + int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X); + if (dutyCcys <= wave.adjDutyCcys) { + dutyCcys >>= 1; + wave.adjDutyCcys -= dutyCcys; + } + else if (wave.adjDutyCcys) { + dutyCcys -= wave.adjDutyCcys; + wave.adjDutyCcys = 0; + } + wave.endDutyCcy = now + dutyCcys; + if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) > 0) { + wave.endDutyCcy = wave.nextPeriodCcy; + } + waveform.states |= pinBit; + if (16 == pin) { + GP16O = 1; + } + else { + GPOS = pinBit; + } + } + waveNextEventCcy = wave.endDutyCcy; + } + + if (WaveformMode::EXPIRES == wave.mode && static_cast(waveNextEventCcy - wave.expiryCcy) > 0) { + waveNextEventCcy = wave.expiryCcy; + } + } + + if (static_cast(waveNextEventCcy - isrTimeoutCcy) >= 0) { + busyPins ^= pinBit; + if (static_cast(waveform.nextEventCcy - waveNextEventCcy) > 0) { + waveform.nextEventCcy = waveNextEventCcy; + } + } + else if (static_cast(isrNextEventCcy - waveNextEventCcy) > 0) { + isrNextEventCcy = waveNextEventCcy; + } + } + now = ESP.getCycleCount(); + } + clockDrift = 0; + } + + int32_t callbackCcys = 0; + if (waveform.timer1CB) { + callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB()), isCPU2X); + } + now = ESP.getCycleCount(); + int32_t nextEventCcys = waveform.nextEventCcy - now; + // Account for unknown duration of timer1CB(). + if (waveform.timer1CB && nextEventCcys > callbackCcys) { + waveform.nextEventCcy = now + callbackCcys; + nextEventCcys = callbackCcys; + } + + // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. + int32_t deltaIrqCcys = DELTAIRQCCYS; + int32_t irqLatencyCcys = IRQLATENCYCCYS; + if (isCPU2X) { + nextEventCcys >>= 1; + deltaIrqCcys >>= 1; + irqLatencyCcys >>= 1; + } + + // Firing timer too soon, the NMI occurs before ISR has returned. + if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) { + waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS; + nextEventCcys = irqLatencyCcys; + } + else { + nextEventCcys -= deltaIrqCcys; + } + + // Register access is fast and edge IRQ was configured before. + T1L = nextEventCcys; +} diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index e42a17f89..61cb99966 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -3,6 +3,7 @@ supporting outputs on all pins in parallel. Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + Copyright (c) 2020 Dirk O. Kaar. The core idea is to have a programmable waveform generator with a unique high and low period (defined in microseconds or CPU clock cycles). TIMER1 is @@ -19,7 +20,7 @@ This replaces older tone(), analogWrite(), and the Servo classes. - Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz). @@ -48,13 +49,25 @@ 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. +// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next +// full period. +// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, +// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that. +// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio +// under load, for applications where frequency or duty cycle must not change, leave false. // Returns true or false on success or failure. -int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, + uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false); // Start or change a waveform of the specified high and low CPU clock cycles on specific pin. -// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles. +// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next +// full period. +// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, +// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that. +// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio +// under load, for applications where frequency or duty cycle must not change, leave false. // Returns true or false on success or failure. -int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, + uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false); // Stop a waveform, if any, on the specified pin. // Returns true or false on success or failure. int stopWaveform(uint8_t pin); diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index b4991310d..6f655716d 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -45,8 +45,8 @@ extern void __analogWriteResolution(int res) { extern void __analogWriteFreq(uint32_t freq) { if (freq < 100) { analogFreq = 100; - } else if (freq > 40000) { - analogFreq = 40000; + } else if (freq > 60000) { + analogFreq = 60000; } else { analogFreq = freq; } @@ -63,22 +63,22 @@ extern void __analogWrite(uint8_t pin, int val) { val = analogScale; } + if (analogMap & 1UL << pin) { // Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/ // val: the duty cycle: between 0 (always off) and 255 (always on). // So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH) - analogMap &= ~(1 << pin); + analogMap &= ~(1 << pin); + } + else { + pinMode(pin, OUTPUT); + } uint32_t high = (analogPeriod * val) / analogScale; uint32_t low = analogPeriod - high; - pinMode(pin, OUTPUT); - if (low == 0) { - digitalWrite(pin, HIGH); - } else if (high == 0) { - digitalWrite(pin, LOW); - } else { - if (startWaveformClockCycles(pin, high, low, 0)) { - analogMap |= (1 << pin); - } + // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) + int phaseReference = __builtin_ffs(analogMap) - 1; + if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) { + analogMap |= (1 << pin); } } diff --git a/libraries/Servo/src/Servo.cpp b/libraries/Servo/src/Servo.cpp index aff9afebb..cacbbebba 100644 --- a/libraries/Servo/src/Servo.cpp +++ b/libraries/Servo/src/Servo.cpp @@ -69,8 +69,8 @@ uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs) uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs, int value) { if (!_attached) { - digitalWrite(pin, LOW); pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); _pin = pin; _attached = true; } @@ -115,7 +115,9 @@ void Servo::writeMicroseconds(int value) _valueUs = value; if (_attached) { _servoMap &= ~(1 << _pin); - if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0)) { + // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) + int phaseReference = __builtin_ffs(_servoMap) - 1; + if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0, phaseReference)) { _servoMap |= (1 << _pin); } } From eec4dc490b0eefc310a7ad45f321ae9baf9e1f6d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 19 Nov 2020 22:33:31 +0100 Subject: [PATCH 04/13] install a new waveform-flavouring Arduino IDE menu and a new PIO #define (#7712) --- boards.txt | 137 ++++++++++++++++++++++ cores/esp8266/core_esp8266_waveform.cpp | 4 + cores/esp8266/core_esp8266_waveform.h | 6 +- cores/esp8266/core_esp8266_wiring_pwm.cpp | 35 ++++-- libraries/Servo/src/Servo.cpp | 12 +- platform.txt | 4 +- tools/boards.txt.py | 16 ++- tools/platformio-build.py | 9 ++ 8 files changed, 205 insertions(+), 18 deletions(-) diff --git a/boards.txt b/boards.txt index afd4f3004..9c4af2f52 100644 --- a/boards.txt +++ b/boards.txt @@ -23,6 +23,7 @@ menu.stacksmash=Stack Protection menu.wipe=Erase Flash menu.sdk=Espressif FW menu.ssl=SSL Support +menu.waveform=Waveform Flavour ############################################################## generic.name=Generic ESP8266 Module @@ -63,6 +64,10 @@ generic.menu.ssl.all=All SSL ciphers (most compatible) generic.menu.ssl.all.build.sslflags= generic.menu.ssl.basic=Basic SSL ciphers (lower ROM use) generic.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +generic.menu.waveform.phase=Locked Phase +generic.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +generic.menu.waveform.pwm=Locked PWM +generic.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM generic.menu.ResetMethod.nodemcu=dtr (aka nodemcu) generic.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset generic.menu.ResetMethod.ck=no dtr (aka ck) @@ -532,6 +537,10 @@ esp8285.menu.ssl.all=All SSL ciphers (most compatible) esp8285.menu.ssl.all.build.sslflags= esp8285.menu.ssl.basic=Basic SSL ciphers (lower ROM use) esp8285.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +esp8285.menu.waveform.phase=Locked Phase +esp8285.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +esp8285.menu.waveform.pwm=Locked PWM +esp8285.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM esp8285.menu.ResetMethod.nodemcu=dtr (aka nodemcu) esp8285.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset esp8285.menu.ResetMethod.ck=no dtr (aka ck) @@ -871,6 +880,10 @@ gen4iod.menu.ssl.all=All SSL ciphers (most compatible) gen4iod.menu.ssl.all.build.sslflags= gen4iod.menu.ssl.basic=Basic SSL ciphers (lower ROM use) gen4iod.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +gen4iod.menu.waveform.phase=Locked Phase +gen4iod.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +gen4iod.menu.waveform.pwm=Locked PWM +gen4iod.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM gen4iod.upload.resetmethod=--before default_reset --after hard_reset gen4iod.menu.FlashMode.dout=DOUT (compatible) gen4iod.menu.FlashMode.dout.build.flash_mode=dout @@ -1125,6 +1138,10 @@ huzzah.menu.ssl.all=All SSL ciphers (most compatible) huzzah.menu.ssl.all.build.sslflags= huzzah.menu.ssl.basic=Basic SSL ciphers (lower ROM use) huzzah.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +huzzah.menu.waveform.phase=Locked Phase +huzzah.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +huzzah.menu.waveform.pwm=Locked PWM +huzzah.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM huzzah.upload.resetmethod=--before default_reset --after hard_reset huzzah.build.flash_mode=qio huzzah.build.flash_flags=-DFLASHMODE_QIO @@ -1312,6 +1329,10 @@ wifi_slot.menu.ssl.all=All SSL ciphers (most compatible) wifi_slot.menu.ssl.all.build.sslflags= wifi_slot.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifi_slot.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifi_slot.menu.waveform.phase=Locked Phase +wifi_slot.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +wifi_slot.menu.waveform.pwm=Locked PWM +wifi_slot.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM wifi_slot.upload.resetmethod=--before default_reset --after hard_reset wifi_slot.menu.FlashFreq.40=40MHz wifi_slot.menu.FlashFreq.40.build.flash_freq=40 @@ -1625,6 +1646,10 @@ arduino-esp8266.menu.ssl.all=All SSL ciphers (most compatible) arduino-esp8266.menu.ssl.all.build.sslflags= arduino-esp8266.menu.ssl.basic=Basic SSL ciphers (lower ROM use) arduino-esp8266.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +arduino-esp8266.menu.waveform.phase=Locked Phase +arduino-esp8266.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +arduino-esp8266.menu.waveform.pwm=Locked PWM +arduino-esp8266.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM arduino-esp8266.upload.resetmethod=--before no_reset --after soft_reset arduino-esp8266.build.flash_mode=qio arduino-esp8266.build.flash_flags=-DFLASHMODE_QIO @@ -1813,6 +1838,10 @@ espmxdevkit.menu.ssl.all=All SSL ciphers (most compatible) espmxdevkit.menu.ssl.all.build.sslflags= espmxdevkit.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espmxdevkit.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espmxdevkit.menu.waveform.phase=Locked Phase +espmxdevkit.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +espmxdevkit.menu.waveform.pwm=Locked PWM +espmxdevkit.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espmxdevkit.upload.resetmethod=--before default_reset --after hard_reset espmxdevkit.build.flash_mode=dout espmxdevkit.build.flash_flags=-DFLASHMODE_DOUT @@ -2041,6 +2070,10 @@ oak.menu.ssl.all=All SSL ciphers (most compatible) oak.menu.ssl.all.build.sslflags= oak.menu.ssl.basic=Basic SSL ciphers (lower ROM use) oak.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +oak.menu.waveform.phase=Locked Phase +oak.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +oak.menu.waveform.pwm=Locked PWM +oak.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM oak.upload.resetmethod=--before no_reset --after soft_reset oak.build.flash_mode=dio oak.build.flash_flags=-DFLASHMODE_DIO @@ -2237,6 +2270,10 @@ espduino.menu.ssl.all=All SSL ciphers (most compatible) espduino.menu.ssl.all.build.sslflags= espduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espduino.menu.waveform.phase=Locked Phase +espduino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +espduino.menu.waveform.pwm=Locked PWM +espduino.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espduino.build.flash_mode=dio espduino.build.flash_flags=-DFLASHMODE_DIO espduino.build.flash_freq=40 @@ -2423,6 +2460,10 @@ espectro.menu.ssl.all=All SSL ciphers (most compatible) espectro.menu.ssl.all.build.sslflags= espectro.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espectro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espectro.menu.waveform.phase=Locked Phase +espectro.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +espectro.menu.waveform.pwm=Locked PWM +espectro.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espectro.upload.resetmethod=--before default_reset --after hard_reset espectro.build.flash_mode=dio espectro.build.flash_flags=-DFLASHMODE_DIO @@ -2610,6 +2651,10 @@ espino.menu.ssl.all=All SSL ciphers (most compatible) espino.menu.ssl.all.build.sslflags= espino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espino.menu.waveform.phase=Locked Phase +espino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +espino.menu.waveform.pwm=Locked PWM +espino.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espino.menu.ResetMethod.nodemcu=dtr (aka nodemcu) espino.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset espino.menu.ResetMethod.ck=no dtr (aka ck) @@ -2800,6 +2845,10 @@ espresso_lite_v1.menu.ssl.all=All SSL ciphers (most compatible) espresso_lite_v1.menu.ssl.all.build.sslflags= espresso_lite_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espresso_lite_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espresso_lite_v1.menu.waveform.phase=Locked Phase +espresso_lite_v1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +espresso_lite_v1.menu.waveform.pwm=Locked PWM +espresso_lite_v1.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espresso_lite_v1.build.flash_mode=dio espresso_lite_v1.build.flash_flags=-DFLASHMODE_DIO espresso_lite_v1.build.flash_freq=40 @@ -2990,6 +3039,10 @@ espresso_lite_v2.menu.ssl.all=All SSL ciphers (most compatible) espresso_lite_v2.menu.ssl.all.build.sslflags= espresso_lite_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espresso_lite_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espresso_lite_v2.menu.waveform.phase=Locked Phase +espresso_lite_v2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +espresso_lite_v2.menu.waveform.pwm=Locked PWM +espresso_lite_v2.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espresso_lite_v2.build.flash_mode=dio espresso_lite_v2.build.flash_flags=-DFLASHMODE_DIO espresso_lite_v2.build.flash_freq=40 @@ -3190,6 +3243,10 @@ sonoff.menu.ssl.all=All SSL ciphers (most compatible) sonoff.menu.ssl.all.build.sslflags= sonoff.menu.ssl.basic=Basic SSL ciphers (lower ROM use) sonoff.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +sonoff.menu.waveform.phase=Locked Phase +sonoff.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +sonoff.menu.waveform.pwm=Locked PWM +sonoff.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM sonoff.upload.resetmethod=--before no_reset --after soft_reset sonoff.build.flash_mode=dout sonoff.build.flash_flags=-DFLASHMODE_DOUT @@ -3417,6 +3474,10 @@ inventone.menu.ssl.all=All SSL ciphers (most compatible) inventone.menu.ssl.all.build.sslflags= inventone.menu.ssl.basic=Basic SSL ciphers (lower ROM use) inventone.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +inventone.menu.waveform.phase=Locked Phase +inventone.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +inventone.menu.waveform.pwm=Locked PWM +inventone.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM inventone.upload.resetmethod=--before default_reset --after hard_reset inventone.build.flash_mode=dio inventone.build.flash_flags=-DFLASHMODE_DIO @@ -3604,6 +3665,10 @@ d1_mini.menu.ssl.all=All SSL ciphers (most compatible) d1_mini.menu.ssl.all.build.sslflags= d1_mini.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini.menu.waveform.phase=Locked Phase +d1_mini.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +d1_mini.menu.waveform.pwm=Locked PWM +d1_mini.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM d1_mini.upload.resetmethod=--before default_reset --after hard_reset d1_mini.build.flash_mode=dio d1_mini.build.flash_flags=-DFLASHMODE_DIO @@ -3791,6 +3856,10 @@ d1_mini_lite.menu.ssl.all=All SSL ciphers (most compatible) d1_mini_lite.menu.ssl.all.build.sslflags= d1_mini_lite.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini_lite.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini_lite.menu.waveform.phase=Locked Phase +d1_mini_lite.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +d1_mini_lite.menu.waveform.pwm=Locked PWM +d1_mini_lite.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM d1_mini_lite.upload.resetmethod=--before default_reset --after hard_reset d1_mini_lite.build.flash_mode=dout d1_mini_lite.build.flash_flags=-DFLASHMODE_DOUT @@ -4018,6 +4087,10 @@ d1_mini_pro.menu.ssl.all=All SSL ciphers (most compatible) d1_mini_pro.menu.ssl.all.build.sslflags= d1_mini_pro.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini_pro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini_pro.menu.waveform.phase=Locked Phase +d1_mini_pro.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +d1_mini_pro.menu.waveform.pwm=Locked PWM +d1_mini_pro.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM d1_mini_pro.upload.resetmethod=--before default_reset --after hard_reset d1_mini_pro.build.flash_mode=dio d1_mini_pro.build.flash_flags=-DFLASHMODE_DIO @@ -4188,6 +4261,10 @@ d1.menu.ssl.all=All SSL ciphers (most compatible) d1.menu.ssl.all.build.sslflags= d1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1.menu.waveform.phase=Locked Phase +d1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +d1.menu.waveform.pwm=Locked PWM +d1.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM d1.upload.resetmethod=--before default_reset --after hard_reset d1.build.flash_mode=dio d1.build.flash_flags=-DFLASHMODE_DIO @@ -4375,6 +4452,10 @@ nodemcu.menu.ssl.all=All SSL ciphers (most compatible) nodemcu.menu.ssl.all.build.sslflags= nodemcu.menu.ssl.basic=Basic SSL ciphers (lower ROM use) nodemcu.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +nodemcu.menu.waveform.phase=Locked Phase +nodemcu.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +nodemcu.menu.waveform.pwm=Locked PWM +nodemcu.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM nodemcu.upload.resetmethod=--before default_reset --after hard_reset nodemcu.build.flash_mode=qio nodemcu.build.flash_flags=-DFLASHMODE_QIO @@ -4562,6 +4643,10 @@ nodemcuv2.menu.ssl.all=All SSL ciphers (most compatible) nodemcuv2.menu.ssl.all.build.sslflags= nodemcuv2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) nodemcuv2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +nodemcuv2.menu.waveform.phase=Locked Phase +nodemcuv2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +nodemcuv2.menu.waveform.pwm=Locked PWM +nodemcuv2.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM nodemcuv2.upload.resetmethod=--before default_reset --after hard_reset nodemcuv2.build.flash_mode=dio nodemcuv2.build.flash_flags=-DFLASHMODE_DIO @@ -4753,6 +4838,10 @@ modwifi.menu.ssl.all=All SSL ciphers (most compatible) modwifi.menu.ssl.all.build.sslflags= modwifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use) modwifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +modwifi.menu.waveform.phase=Locked Phase +modwifi.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +modwifi.menu.waveform.pwm=Locked PWM +modwifi.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM modwifi.upload.resetmethod=--before no_reset --after soft_reset modwifi.build.flash_mode=qio modwifi.build.flash_flags=-DFLASHMODE_QIO @@ -4960,6 +5049,10 @@ phoenix_v1.menu.ssl.all=All SSL ciphers (most compatible) phoenix_v1.menu.ssl.all.build.sslflags= phoenix_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) phoenix_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +phoenix_v1.menu.waveform.phase=Locked Phase +phoenix_v1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +phoenix_v1.menu.waveform.pwm=Locked PWM +phoenix_v1.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM phoenix_v1.build.flash_mode=dio phoenix_v1.build.flash_flags=-DFLASHMODE_DIO phoenix_v1.build.flash_freq=40 @@ -5150,6 +5243,10 @@ phoenix_v2.menu.ssl.all=All SSL ciphers (most compatible) phoenix_v2.menu.ssl.all.build.sslflags= phoenix_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) phoenix_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +phoenix_v2.menu.waveform.phase=Locked Phase +phoenix_v2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +phoenix_v2.menu.waveform.pwm=Locked PWM +phoenix_v2.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM phoenix_v2.build.flash_mode=dio phoenix_v2.build.flash_flags=-DFLASHMODE_DIO phoenix_v2.build.flash_freq=40 @@ -5340,6 +5437,10 @@ eduinowifi.menu.ssl.all=All SSL ciphers (most compatible) eduinowifi.menu.ssl.all.build.sslflags= eduinowifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use) eduinowifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +eduinowifi.menu.waveform.phase=Locked Phase +eduinowifi.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +eduinowifi.menu.waveform.pwm=Locked PWM +eduinowifi.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM eduinowifi.upload.resetmethod=--before default_reset --after hard_reset eduinowifi.build.flash_mode=dio eduinowifi.build.flash_flags=-DFLASHMODE_DIO @@ -5527,6 +5628,10 @@ wiolink.menu.ssl.all=All SSL ciphers (most compatible) wiolink.menu.ssl.all.build.sslflags= wiolink.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wiolink.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wiolink.menu.waveform.phase=Locked Phase +wiolink.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +wiolink.menu.waveform.pwm=Locked PWM +wiolink.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM wiolink.upload.resetmethod=--before default_reset --after hard_reset wiolink.build.flash_mode=qio wiolink.build.flash_flags=-DFLASHMODE_QIO @@ -5714,6 +5819,10 @@ blynk.menu.ssl.all=All SSL ciphers (most compatible) blynk.menu.ssl.all.build.sslflags= blynk.menu.ssl.basic=Basic SSL ciphers (lower ROM use) blynk.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +blynk.menu.waveform.phase=Locked Phase +blynk.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +blynk.menu.waveform.pwm=Locked PWM +blynk.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM blynk.upload.resetmethod=--before default_reset --after hard_reset blynk.build.flash_mode=qio blynk.build.flash_flags=-DFLASHMODE_QIO @@ -5901,6 +6010,10 @@ thing.menu.ssl.all=All SSL ciphers (most compatible) thing.menu.ssl.all.build.sslflags= thing.menu.ssl.basic=Basic SSL ciphers (lower ROM use) thing.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +thing.menu.waveform.phase=Locked Phase +thing.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +thing.menu.waveform.pwm=Locked PWM +thing.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM thing.upload.resetmethod=--before no_reset --after soft_reset thing.build.flash_mode=qio thing.build.flash_flags=-DFLASHMODE_QIO @@ -6088,6 +6201,10 @@ thingdev.menu.ssl.all=All SSL ciphers (most compatible) thingdev.menu.ssl.all.build.sslflags= thingdev.menu.ssl.basic=Basic SSL ciphers (lower ROM use) thingdev.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +thingdev.menu.waveform.phase=Locked Phase +thingdev.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +thingdev.menu.waveform.pwm=Locked PWM +thingdev.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM thingdev.upload.resetmethod=--before default_reset --after hard_reset thingdev.build.flash_mode=dio thingdev.build.flash_flags=-DFLASHMODE_DIO @@ -6275,6 +6392,10 @@ esp210.menu.ssl.all=All SSL ciphers (most compatible) esp210.menu.ssl.all.build.sslflags= esp210.menu.ssl.basic=Basic SSL ciphers (lower ROM use) esp210.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +esp210.menu.waveform.phase=Locked Phase +esp210.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +esp210.menu.waveform.pwm=Locked PWM +esp210.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM esp210.upload.resetmethod=--before no_reset --after soft_reset esp210.build.flash_mode=qio esp210.build.flash_flags=-DFLASHMODE_QIO @@ -6462,6 +6583,10 @@ espinotee.menu.ssl.all=All SSL ciphers (most compatible) espinotee.menu.ssl.all.build.sslflags= espinotee.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espinotee.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espinotee.menu.waveform.phase=Locked Phase +espinotee.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +espinotee.menu.waveform.pwm=Locked PWM +espinotee.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espinotee.upload.resetmethod=--before default_reset --after hard_reset espinotee.build.flash_mode=qio espinotee.build.flash_flags=-DFLASHMODE_QIO @@ -6649,6 +6774,10 @@ wifiduino.menu.ssl.all=All SSL ciphers (most compatible) wifiduino.menu.ssl.all.build.sslflags= wifiduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifiduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifiduino.menu.waveform.phase=Locked Phase +wifiduino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +wifiduino.menu.waveform.pwm=Locked PWM +wifiduino.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM wifiduino.upload.resetmethod=--before default_reset --after hard_reset wifiduino.build.flash_mode=dio wifiduino.build.flash_flags=-DFLASHMODE_DIO @@ -6853,6 +6982,10 @@ wifinfo.menu.ssl.all=All SSL ciphers (most compatible) wifinfo.menu.ssl.all.build.sslflags= wifinfo.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifinfo.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifinfo.menu.waveform.phase=Locked Phase +wifinfo.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +wifinfo.menu.waveform.pwm=Locked PWM +wifinfo.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM wifinfo.upload.resetmethod=--before default_reset --after hard_reset wifinfo.build.flash_mode=qio wifinfo.build.flash_flags=-DFLASHMODE_QIO @@ -7087,6 +7220,10 @@ cw01.menu.ssl.all=All SSL ciphers (most compatible) cw01.menu.ssl.all.build.sslflags= cw01.menu.ssl.basic=Basic SSL ciphers (lower ROM use) cw01.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +cw01.menu.waveform.phase=Locked Phase +cw01.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE +cw01.menu.waveform.pwm=Locked PWM +cw01.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM cw01.upload.resetmethod=--before default_reset --after hard_reset cw01.menu.CrystalFreq.26=26 MHz cw01.menu.CrystalFreq.40=40 MHz diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp index 952e1fd19..4bd0f1c01 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform.cpp @@ -39,6 +39,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef WAVEFORM_LOCKED_PHASE + #include "core_esp8266_waveform.h" #include #include "ets_sys.h" @@ -434,3 +436,5 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { // Register access is fast and edge IRQ was configured before. T1L = nextEventCcys; } + +#endif // WAVEFORM_LOCKED_PHASE diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index 61cb99966..e24819a65 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -39,6 +39,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef WAVEFORM_LOCKED_PHASE + #include #ifndef __ESP8266_WAVEFORM_H @@ -86,4 +88,6 @@ void setTimer1Callback(uint32_t (*fn)()); } #endif -#endif +#endif // __ESP8266_WAVEFORM_H + +#endif // WAVEFORM_LOCKED_PHASE diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index 6f655716d..565cac7d8 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -26,22 +26,13 @@ extern "C" { -static uint32_t analogMap = 0; static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x + +#ifdef WAVEFORM_LOCKED_PHASE + +static uint32_t analogMap = 0; static uint16_t analogFreq = 1000; -extern void __analogWriteRange(uint32_t range) { - if ((range >= 15) && (range <= 65535)) { - analogScale = range; - } -} - -extern void __analogWriteResolution(int res) { - if ((res >= 4) && (res <= 16)) { - analogScale = (1 << res) - 1; - } -} - extern void __analogWriteFreq(uint32_t freq) { if (freq < 100) { analogFreq = 100; @@ -82,6 +73,24 @@ extern void __analogWrite(uint8_t pin, int val) { } } +#endif // WAVEFORM_LOCKED_PHASE + +#ifdef WAVEFORM_LOCKED_PWM + +#endif // WAVEFORM_LOCKED_PWM + +extern void __analogWriteRange(uint32_t range) { + if ((range >= 15) && (range <= 65535)) { + analogScale = range; + } +} + +extern void __analogWriteResolution(int res) { + if ((res >= 4) && (res <= 16)) { + analogScale = (1 << res) - 1; + } +} + 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"))); diff --git a/libraries/Servo/src/Servo.cpp b/libraries/Servo/src/Servo.cpp index cacbbebba..09d87c0f4 100644 --- a/libraries/Servo/src/Servo.cpp +++ b/libraries/Servo/src/Servo.cpp @@ -69,8 +69,13 @@ uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs) uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs, int value) { if (!_attached) { +#ifdef WAVEFORM_LOCKED_PHASE pinMode(pin, OUTPUT); digitalWrite(pin, LOW); +#else + digitalWrite(pin, LOW); + pinMode(pin, OUTPUT); +#endif _pin = pin; _attached = true; } @@ -115,9 +120,14 @@ void Servo::writeMicroseconds(int value) _valueUs = value; if (_attached) { _servoMap &= ~(1 << _pin); +#ifdef WAVEFORM_LOCKED_PHASE // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) int phaseReference = __builtin_ffs(_servoMap) - 1; - if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0, phaseReference)) { + if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0, phaseReference)) +#else + if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0)) +#endif + { _servoMap |= (1 << _pin); } } diff --git a/platform.txt b/platform.txt index 5e2c9be54..b3401cd0d 100644 --- a/platform.txt +++ b/platform.txt @@ -55,7 +55,7 @@ compiler.libc.path={runtime.platform.path}/tools/sdk/libc/xtensa-lx106-elf compiler.cpreprocessor.flags=-D__ets__ -DICACHE_FLASH -U__STRICT_ANSI__ "-I{compiler.sdk.path}/include" "-I{compiler.sdk.path}/{build.lwip_include}" "-I{compiler.libc.path}/include" "-I{build.path}/core" compiler.c.cmd=xtensa-lx106-elf-gcc -compiler.c.flags=-c {compiler.warning_flags} -std=gnu17 {build.stacksmash_flags} -Os -g -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -falign-functions=4 -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} +compiler.c.flags=-c {compiler.warning_flags} -std=gnu17 {build.stacksmash_flags} -Os -g -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -falign-functions=4 -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} {build.waveform} compiler.S.cmd=xtensa-lx106-elf-gcc compiler.S.flags=-c -g -x assembler-with-cpp -MMD -mlongcalls @@ -66,7 +66,7 @@ compiler.c.elf.cmd=xtensa-lx106-elf-gcc compiler.c.elf.libs=-lhal -lphy -lpp -lnet80211 {build.lwip_lib} -lwpa -lcrypto -lmain -lwps -lbearssl -lespnow -lsmartconfig -lairkiss -lwpa2 {build.stdcpp_lib} -lm -lc -lgcc compiler.cpp.cmd=xtensa-lx106-elf-g++ -compiler.cpp.flags=-c {compiler.warning_flags} {build.stacksmash_flags} -Os -g -mlongcalls -mtext-section-literals -fno-rtti -falign-functions=4 {build.stdcpp_level} -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} +compiler.cpp.flags=-c {compiler.warning_flags} {build.stacksmash_flags} -Os -g -mlongcalls -mtext-section-literals -fno-rtti -falign-functions=4 {build.stdcpp_level} -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} {build.waveform} compiler.as.cmd=xtensa-lx106-elf-as diff --git a/tools/boards.txt.py b/tools/boards.txt.py index 616275f67..816c9c00a 100755 --- a/tools/boards.txt.py +++ b/tools/boards.txt.py @@ -1466,6 +1466,18 @@ def led (name, default, ledList): ])) return { name: led } +################################################################ +# Waveform flavour + +def waveform (): + return { 'waveform': collections.OrderedDict([ + ('.menu.waveform.phase', 'Locked Phase'), + ('.menu.waveform.phase.build.waveform', '-DWAVEFORM_LOCKED_PHASE'), + ('.menu.waveform.pwm', 'Locked PWM'), + ('.menu.waveform.pwm.build.waveform', '-DWAVEFORM_LOCKED_PWM'), + ]) + } + ################################################################ # sdk selection @@ -1517,6 +1529,7 @@ def all_boards (): macros.update(led('led', led_default, range(0,led_max+1))) macros.update(led('led216', 2, { 16 })) macros.update(sdk()) + macros.update(waveform()) if boardfilteropt or excludeboards: print('#') @@ -1561,6 +1574,7 @@ def all_boards (): print('menu.wipe=Erase Flash') print('menu.sdk=Espressif FW') print('menu.ssl=SSL Support') + print('menu.waveform=Waveform Flavour') print('') missingboards = [] @@ -1581,7 +1595,7 @@ def all_boards (): print(id + optname + '=' + board['opts'][optname]) # macros - macrolist = [ 'defaults', 'cpufreq_menu', 'vtable_menu', 'exception_menu', 'stacksmash_menu', 'ssl_cipher_menu' ] + macrolist = [ 'defaults', 'cpufreq_menu', 'vtable_menu', 'exception_menu', 'stacksmash_menu', 'ssl_cipher_menu', 'waveform' ] if 'macro' in board: macrolist += board['macro'] macrolist += [ 'lwip', 'debug_menu', 'flash_erase_menu' ] diff --git a/tools/platformio-build.py b/tools/platformio-build.py index 9dfc9277d..c735247f3 100644 --- a/tools/platformio-build.py +++ b/tools/platformio-build.py @@ -237,6 +237,15 @@ else: LIBS=["lwip2-536-feat"] ) +# +# Waveform +# +if "PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PWM" in flatten_cppdefines: + env.Append(CPPDEFINES=[("WAVEFORM_LOCKED_PWM", 1)]) +# PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PHASE (defaults) +else: + env.Append(CPPDEFINES=[("WAVEFORM_LOCKED_PHASE", 1)]) + # # VTables # From 59315836f283cf8eb56f7fea7cdcf0f79b3799e9 Mon Sep 17 00:00:00 2001 From: david gauchard Date: Thu, 19 Nov 2020 23:01:45 +0100 Subject: [PATCH 05/13] schedule_recurrent_function_us should be in iram (#7713) --- cores/esp8266/Schedule.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 5ac887ce1..b1230ed67 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -102,6 +102,7 @@ bool schedule_function(const std::function& fn) return true; } +IRAM_ATTR // (not only) called from ISR bool schedule_recurrent_function_us(const std::function& fn, uint32_t repeat_us, const std::function& alarm) { From ccdde5f396d442f73dc101a1badeed3291f4652c Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 19 Nov 2020 20:47:05 -0800 Subject: [PATCH 06/13] Re-implement PWM generator logic (#7231) * Re-implement PWM generator logic Add special-purpose PWM logic to preserve alignment of PWM signals for things like RGB LEDs. Keep a sorted list of GPIO changes in memory. At time 0 of the PWM cycle, set all pins to high. As time progresses bring down the additional pins as their duty cycle runs out. This way all PWM signals are time aligned by construction. This also reduces the number of PWM interrupts by up to 50%. Before, both the rising and falling edge of a PWM pin required an interrupt (and could shift arround accordingly). Now, a single IRQ sets all PWM rising edges (so 1 no matter how many PWM pins) and individual interrupts generate the falling edges. The code favors duty cycle accuracy over PWM period accuracy (since PWM is simulating an analog voltage it's the %age of time high that's the critical factor in most apps, not the refresh rate). Measurements give it about 35% less total error over full range at 20khz than master. @me-no-dev used something very similar in the original PWM generator. * Adjust running PWM when analogWriteFreq changed Use fixed point math to adjust running PWM channels to the new frequency. * Also preserve phase of running tone/waveforms Copy over full high/low periods only on the falling edge of a cycle, ensuring phase alignment for Tone and Servo. * Clean up signed/unsigned mismatch, 160MHz operat'n * Turn off PWM on a Tone or digitalWrite Ensure both the general purpose waveform generator and the PWM generator are disabled on a pin used for Tone/digitalWrite. * Remove hump due to fixed IRQ delta A hump in the dueling PWMs was very prominent in prior pulls. The hump was caused by having a PWM falling edge just before the cycle restart, while having the other channel requesting a 1->0 transition just outside the busy-loop window of 10us. So it gets an IRQ for channel B 0->1, then waits 2..8us for the next PWM full cycle 0->1, and ends up returning from interrupt and not scheduling another IRQ for 10us...hence the horizontal leg of the bump... Reduce the minimum IRQ latency a little bit to minimize this effect. There will still be a (significantly smaller) hump when things cross, but it won't be anywhere near as bad or detectable. * Speed PWM generator by reordering data struct Breaking out bitfields required a load and an AND, slowing things down in the PWM loop. Convert the bitfield into two separate natural-sized arrays to reduce code size and increase accuracy. * Remove if() that could never evaluate TRUE * Add error feedback to waveform generation Apply an error term to generated waveform phase times to adjust for any other ongoing processes/waveforms. Take the actual edge generation times, subtract them from the desired, and add 1/4 of that (to dampen any potential oscillations) to the next similar phase of that waveform. Allows the waveform to seek its proper period and duty cycle without hardcoding any specific calibrations (which would change depending on the codepaths, compiler options, etc.) in the source. * Move _stopPWM and _removePWMEntry to IRAM Thanks to @dok-net for noticing these need to be in IRAM as they may be called by digitalWrites in an IRQ. * Avoid long wait times when PWM freq is low * Fix bug where tone/pwm could happen on same pin * Adjust for random 160MHZ operation The WiFi stack sometimes changes frequency behind our backs, so ESP's cycle counter does not count constant ticks. We can't know how long it's been at a different than expected frequency, so do the next best thing and make sure we adjust any ESP cycles we're waiting for by the current CPU speed. This can lead to a blip in the waveform for 1 period when the frequency toggles from normal, and when it toggles back, but it should remain for the intervening periods. Should avoid a lot of LED shimmering and servo errors during WiFi connection (and maybe transmission). * Clean up leftover debugs in ISR * Subtract constant-time overhead for PWM, add 60khz PWM has a constant minimum time between loops with a single pin, so pull that time out of the desired PWM period and shift the center of the PWM frequency closer to the desired without any dynamic feedback needed. Enable 60khz PWM, even though it's not terribly useful as it causes an IRQ every ~8us (and each IRQ is 2-3us). The core can still run w/o WDT, but it's performance is about 5x slower than unloaded. * Fix GPIO16 not toggling properly. * Remove constant offset to PWM period analogWrite doesn't know about the change in total PWM cycles, so it is possible for it to send in a value that's beyond the maximum adjusted PWM cycle count, royally messing up things. Remove the offset. Also, fix bug with timer callback functions potentially disabling the timer if PWM was still active. * Remove volatiles, replace with explicit membarrier Volatiles are expensive in flash/IRAM as well as in runtime because they introduce `memw` instructions everywhere their values are used. Remove the volatiles and manually mark handshake signals for re-read/flush to reduce code and runtime in the waveform generator/PWM. * Consolidate data into single structure Save IRAM and flash by using a class to hold waveform generator state. Allows for bast+offset addressing to be used in many cases, removing `l32r` and literals from the assembly code. * Factor out common timer shutdown code * Remove unneeded extra copy on PWM start * Factor out common edge work in waveform loop * Factor out waveform phase feedback loop math * Reduce PWM size by using 32b count, indexes Byte-wide operations require extra instructions, so make index and count a full 32-bits wide. * GP16O is a 1-bit register, just write to it Testing indicates that GP16O is just a simple 1-bit wide register in the RTC module. Instead of |= and &- (i.e. RmW), use direct assignment in PWM generator. * Increase PWM linearity in low/high regions By adjusting the PWM cycle slightly to account for the fixed time through the compute loop, increase the linear response near the min and max areas. * Remove redundant GetCycleCount (non-IRQ) * Factor out common timer setup operations * Fix clean-waveform transition, lock to tone faster New startWaveform waveforms were being copied over on the falling edge of the cycle, not the rising edge. Everything else is based on rising edge, so adjust accordingly. Also, feedback a larger % of the error term in standard waveform generation. Balances the speed at which it locks to tones under changing circumstances with it not going completely bonkers when a transient error occurs due to some other bit. * Reduce IRAM by pushing more work to _setPWM Simply mark pins as inactive, don't adjust the ordered list until the next _startPWM call (in IROM). * Fix typo in PWM pin 1->0 transition Actually check the pin mask is active before setting the PWM pin low. D'oh. * Combine cleanup and pin remove, save 50 bytes IROM The cleanup (where marked-off pins are removed from the PWM time map) and remove (where a chosen pin is taken out of the PWM map) do essentially the same processing. Combine them and save ~50 bytes of code and speed things up a tiny bit. * Remove unused analogMap, toneMap Save ~100 bytes of IROM by removing the tone/analog pin tracking from the interface functions. They were completely unused. * Save IRAM/heap by adjusting WVF update struct The waveform update structure included 2 32-bit quantities (so, used 8 * 17 = 136 bytes of RAM) for the next cycle of a waveform. Replace that with a single update register, in a posted fashion. The logic now sets the new state of a single waveform and returns immediately (so, no need to wait 1ms if you've got an existing waveform of 1khz). The waveform NMI will pick up the changed value on its next cycle. Reduces IRAM by 40 bytes, and heap by 144 bytes. * Don't duplicate PWM period calculation Let the waveform generator be the single source of truth for the PWM period in clock cycles. Reduces IRAM by 32 bytes and makes things generally saner. * Factor out common PWM update code Replace repeated PWM update logic with a subroutine, and move the PWMUpdate pointer into the state itself. Reduces IROM and IRAM, removes code duplication. Also remove single-use macros and ifdef configurable options as the IRAM and IROM impact of them are now not very large. * Fix regression when analogWrite done cold Lost an `initTimer()` call in a refactoring, resulting in the core hanging forever while waiting for the NMI which will never happen. Re-add as appropriate. * Save 16b of IRAM by not re-setting edge intr bit Per @dok-net, drop the rewrite of the edge trigger flag in the timer interrupt register. It's set on startup and never cleared, so this is redundant. Drops ~16 bytes of IRAM. * Allow on-the-fly PWM frequency changes When PWM is running and analogWriteFreq is called, re-calculate the entire set of PWM pins to the new frequency. Preserve the raw numerator/denominator in an unused bit of the waveform structure to avoid wasting memory. * Adjust for fixed overhead on PWM period Pulls the actual PWM period closer to the requested one with a simple, 0-overhead static adjustment. * Fix value reversal when analogWrite out of range Silly mistake, swapped high and low values when checking analogWrite for over/under values. Fixed * Don't optimize the satopWaveform call Save a few bytes of IRAM by not using -O2 on the stopWaveform call. It is not a speed-critical function. * Avoid side effects in addPWMtoList * Adjust PWM period as fcn of # of PWM pins Results in much closer PWM frequency range over any number of PWM pins, while taking 0 add'l overhead in IRAM or in the IRQ. * Fix occasional Tone artifacts When _setPWMFreq was called the initial PWM mask was not set to 0 leading to occasional issues where non-PWM pins would be set to 1 on the nextPWM cycle. Manifested itself as an overtone at the PWM frequency +/-. * Reduce CPU usage and enhance low range PWM output Borrow a trick from #7022 to exit the busy loop when the next event is too far out. Also reduce the IRQ delta subtraction because it was initially not NMI so there was much more variation than now. Keep the PWM state machine active at a higher prio than the standard tone generation when the next edge is very close (i.e. when we're at the max or min of the range and have 2 or more near edges). Adds a lot of resolution to the response at low and high ranges. Go from relative to absolute cycle counts in the main IRQ loop so that we don't mingle delta-cycles when the delta start was significantly different. * Update min IRQ time to remove humps in PWM linearity Keep PWM error <2.0% on entire range, from 0-100%, and remove the hump seen in testC by fixing the min IRQ delay setting. * Remove minor bump at high PWM frequencies The IRQ lead time was a tiny bit undersized, causing IRQs to come back too late for about .25us worth of PWM range. Adjust the constant accordingly --- boards.txt | 136 ++-- cores/esp8266/Tone.cpp | 7 + cores/esp8266/core_esp8266_waveform.h | 94 +-- ...rm.cpp => core_esp8266_waveform_phase.cpp} | 2 +- cores/esp8266/core_esp8266_waveform_phase.h | 93 +++ cores/esp8266/core_esp8266_waveform_pwm.cpp | 626 ++++++++++++++++++ cores/esp8266/core_esp8266_waveform_pwm.h | 87 +++ cores/esp8266/core_esp8266_wiring_digital.cpp | 5 +- cores/esp8266/core_esp8266_wiring_pwm.cpp | 34 +- libraries/Servo/src/Servo.cpp | 4 + libraries/SoftwareSerial | 2 +- tools/boards.txt.py | 4 +- tools/platformio-build.py | 4 +- 13 files changed, 929 insertions(+), 169 deletions(-) rename cores/esp8266/{core_esp8266_waveform.cpp => core_esp8266_waveform_phase.cpp} (99%) create mode 100644 cores/esp8266/core_esp8266_waveform_phase.h create mode 100644 cores/esp8266/core_esp8266_waveform_pwm.cpp create mode 100644 cores/esp8266/core_esp8266_waveform_pwm.h diff --git a/boards.txt b/boards.txt index 9c4af2f52..d32a47e85 100644 --- a/boards.txt +++ b/boards.txt @@ -64,10 +64,10 @@ generic.menu.ssl.all=All SSL ciphers (most compatible) generic.menu.ssl.all.build.sslflags= generic.menu.ssl.basic=Basic SSL ciphers (lower ROM use) generic.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +generic.menu.waveform.pwm=Locked PWM +generic.menu.waveform.pwm.build.waveform= generic.menu.waveform.phase=Locked Phase generic.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -generic.menu.waveform.pwm=Locked PWM -generic.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM generic.menu.ResetMethod.nodemcu=dtr (aka nodemcu) generic.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset generic.menu.ResetMethod.ck=no dtr (aka ck) @@ -537,10 +537,10 @@ esp8285.menu.ssl.all=All SSL ciphers (most compatible) esp8285.menu.ssl.all.build.sslflags= esp8285.menu.ssl.basic=Basic SSL ciphers (lower ROM use) esp8285.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +esp8285.menu.waveform.pwm=Locked PWM +esp8285.menu.waveform.pwm.build.waveform= esp8285.menu.waveform.phase=Locked Phase esp8285.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -esp8285.menu.waveform.pwm=Locked PWM -esp8285.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM esp8285.menu.ResetMethod.nodemcu=dtr (aka nodemcu) esp8285.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset esp8285.menu.ResetMethod.ck=no dtr (aka ck) @@ -880,10 +880,10 @@ gen4iod.menu.ssl.all=All SSL ciphers (most compatible) gen4iod.menu.ssl.all.build.sslflags= gen4iod.menu.ssl.basic=Basic SSL ciphers (lower ROM use) gen4iod.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +gen4iod.menu.waveform.pwm=Locked PWM +gen4iod.menu.waveform.pwm.build.waveform= gen4iod.menu.waveform.phase=Locked Phase gen4iod.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -gen4iod.menu.waveform.pwm=Locked PWM -gen4iod.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM gen4iod.upload.resetmethod=--before default_reset --after hard_reset gen4iod.menu.FlashMode.dout=DOUT (compatible) gen4iod.menu.FlashMode.dout.build.flash_mode=dout @@ -1138,10 +1138,10 @@ huzzah.menu.ssl.all=All SSL ciphers (most compatible) huzzah.menu.ssl.all.build.sslflags= huzzah.menu.ssl.basic=Basic SSL ciphers (lower ROM use) huzzah.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +huzzah.menu.waveform.pwm=Locked PWM +huzzah.menu.waveform.pwm.build.waveform= huzzah.menu.waveform.phase=Locked Phase huzzah.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -huzzah.menu.waveform.pwm=Locked PWM -huzzah.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM huzzah.upload.resetmethod=--before default_reset --after hard_reset huzzah.build.flash_mode=qio huzzah.build.flash_flags=-DFLASHMODE_QIO @@ -1329,10 +1329,10 @@ wifi_slot.menu.ssl.all=All SSL ciphers (most compatible) wifi_slot.menu.ssl.all.build.sslflags= wifi_slot.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifi_slot.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifi_slot.menu.waveform.pwm=Locked PWM +wifi_slot.menu.waveform.pwm.build.waveform= wifi_slot.menu.waveform.phase=Locked Phase wifi_slot.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -wifi_slot.menu.waveform.pwm=Locked PWM -wifi_slot.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM wifi_slot.upload.resetmethod=--before default_reset --after hard_reset wifi_slot.menu.FlashFreq.40=40MHz wifi_slot.menu.FlashFreq.40.build.flash_freq=40 @@ -1646,10 +1646,10 @@ arduino-esp8266.menu.ssl.all=All SSL ciphers (most compatible) arduino-esp8266.menu.ssl.all.build.sslflags= arduino-esp8266.menu.ssl.basic=Basic SSL ciphers (lower ROM use) arduino-esp8266.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +arduino-esp8266.menu.waveform.pwm=Locked PWM +arduino-esp8266.menu.waveform.pwm.build.waveform= arduino-esp8266.menu.waveform.phase=Locked Phase arduino-esp8266.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -arduino-esp8266.menu.waveform.pwm=Locked PWM -arduino-esp8266.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM arduino-esp8266.upload.resetmethod=--before no_reset --after soft_reset arduino-esp8266.build.flash_mode=qio arduino-esp8266.build.flash_flags=-DFLASHMODE_QIO @@ -1838,10 +1838,10 @@ espmxdevkit.menu.ssl.all=All SSL ciphers (most compatible) espmxdevkit.menu.ssl.all.build.sslflags= espmxdevkit.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espmxdevkit.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espmxdevkit.menu.waveform.pwm=Locked PWM +espmxdevkit.menu.waveform.pwm.build.waveform= espmxdevkit.menu.waveform.phase=Locked Phase espmxdevkit.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -espmxdevkit.menu.waveform.pwm=Locked PWM -espmxdevkit.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espmxdevkit.upload.resetmethod=--before default_reset --after hard_reset espmxdevkit.build.flash_mode=dout espmxdevkit.build.flash_flags=-DFLASHMODE_DOUT @@ -2070,10 +2070,10 @@ oak.menu.ssl.all=All SSL ciphers (most compatible) oak.menu.ssl.all.build.sslflags= oak.menu.ssl.basic=Basic SSL ciphers (lower ROM use) oak.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +oak.menu.waveform.pwm=Locked PWM +oak.menu.waveform.pwm.build.waveform= oak.menu.waveform.phase=Locked Phase oak.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -oak.menu.waveform.pwm=Locked PWM -oak.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM oak.upload.resetmethod=--before no_reset --after soft_reset oak.build.flash_mode=dio oak.build.flash_flags=-DFLASHMODE_DIO @@ -2270,10 +2270,10 @@ espduino.menu.ssl.all=All SSL ciphers (most compatible) espduino.menu.ssl.all.build.sslflags= espduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espduino.menu.waveform.pwm=Locked PWM +espduino.menu.waveform.pwm.build.waveform= espduino.menu.waveform.phase=Locked Phase espduino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -espduino.menu.waveform.pwm=Locked PWM -espduino.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espduino.build.flash_mode=dio espduino.build.flash_flags=-DFLASHMODE_DIO espduino.build.flash_freq=40 @@ -2460,10 +2460,10 @@ espectro.menu.ssl.all=All SSL ciphers (most compatible) espectro.menu.ssl.all.build.sslflags= espectro.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espectro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espectro.menu.waveform.pwm=Locked PWM +espectro.menu.waveform.pwm.build.waveform= espectro.menu.waveform.phase=Locked Phase espectro.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -espectro.menu.waveform.pwm=Locked PWM -espectro.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espectro.upload.resetmethod=--before default_reset --after hard_reset espectro.build.flash_mode=dio espectro.build.flash_flags=-DFLASHMODE_DIO @@ -2651,10 +2651,10 @@ espino.menu.ssl.all=All SSL ciphers (most compatible) espino.menu.ssl.all.build.sslflags= espino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espino.menu.waveform.pwm=Locked PWM +espino.menu.waveform.pwm.build.waveform= espino.menu.waveform.phase=Locked Phase espino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -espino.menu.waveform.pwm=Locked PWM -espino.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espino.menu.ResetMethod.nodemcu=dtr (aka nodemcu) espino.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset espino.menu.ResetMethod.ck=no dtr (aka ck) @@ -2845,10 +2845,10 @@ espresso_lite_v1.menu.ssl.all=All SSL ciphers (most compatible) espresso_lite_v1.menu.ssl.all.build.sslflags= espresso_lite_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espresso_lite_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espresso_lite_v1.menu.waveform.pwm=Locked PWM +espresso_lite_v1.menu.waveform.pwm.build.waveform= espresso_lite_v1.menu.waveform.phase=Locked Phase espresso_lite_v1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -espresso_lite_v1.menu.waveform.pwm=Locked PWM -espresso_lite_v1.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espresso_lite_v1.build.flash_mode=dio espresso_lite_v1.build.flash_flags=-DFLASHMODE_DIO espresso_lite_v1.build.flash_freq=40 @@ -3039,10 +3039,10 @@ espresso_lite_v2.menu.ssl.all=All SSL ciphers (most compatible) espresso_lite_v2.menu.ssl.all.build.sslflags= espresso_lite_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espresso_lite_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espresso_lite_v2.menu.waveform.pwm=Locked PWM +espresso_lite_v2.menu.waveform.pwm.build.waveform= espresso_lite_v2.menu.waveform.phase=Locked Phase espresso_lite_v2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -espresso_lite_v2.menu.waveform.pwm=Locked PWM -espresso_lite_v2.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espresso_lite_v2.build.flash_mode=dio espresso_lite_v2.build.flash_flags=-DFLASHMODE_DIO espresso_lite_v2.build.flash_freq=40 @@ -3243,10 +3243,10 @@ sonoff.menu.ssl.all=All SSL ciphers (most compatible) sonoff.menu.ssl.all.build.sslflags= sonoff.menu.ssl.basic=Basic SSL ciphers (lower ROM use) sonoff.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +sonoff.menu.waveform.pwm=Locked PWM +sonoff.menu.waveform.pwm.build.waveform= sonoff.menu.waveform.phase=Locked Phase sonoff.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -sonoff.menu.waveform.pwm=Locked PWM -sonoff.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM sonoff.upload.resetmethod=--before no_reset --after soft_reset sonoff.build.flash_mode=dout sonoff.build.flash_flags=-DFLASHMODE_DOUT @@ -3474,10 +3474,10 @@ inventone.menu.ssl.all=All SSL ciphers (most compatible) inventone.menu.ssl.all.build.sslflags= inventone.menu.ssl.basic=Basic SSL ciphers (lower ROM use) inventone.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +inventone.menu.waveform.pwm=Locked PWM +inventone.menu.waveform.pwm.build.waveform= inventone.menu.waveform.phase=Locked Phase inventone.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -inventone.menu.waveform.pwm=Locked PWM -inventone.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM inventone.upload.resetmethod=--before default_reset --after hard_reset inventone.build.flash_mode=dio inventone.build.flash_flags=-DFLASHMODE_DIO @@ -3665,10 +3665,10 @@ d1_mini.menu.ssl.all=All SSL ciphers (most compatible) d1_mini.menu.ssl.all.build.sslflags= d1_mini.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini.menu.waveform.pwm=Locked PWM +d1_mini.menu.waveform.pwm.build.waveform= d1_mini.menu.waveform.phase=Locked Phase d1_mini.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -d1_mini.menu.waveform.pwm=Locked PWM -d1_mini.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM d1_mini.upload.resetmethod=--before default_reset --after hard_reset d1_mini.build.flash_mode=dio d1_mini.build.flash_flags=-DFLASHMODE_DIO @@ -3856,10 +3856,10 @@ d1_mini_lite.menu.ssl.all=All SSL ciphers (most compatible) d1_mini_lite.menu.ssl.all.build.sslflags= d1_mini_lite.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini_lite.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini_lite.menu.waveform.pwm=Locked PWM +d1_mini_lite.menu.waveform.pwm.build.waveform= d1_mini_lite.menu.waveform.phase=Locked Phase d1_mini_lite.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -d1_mini_lite.menu.waveform.pwm=Locked PWM -d1_mini_lite.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM d1_mini_lite.upload.resetmethod=--before default_reset --after hard_reset d1_mini_lite.build.flash_mode=dout d1_mini_lite.build.flash_flags=-DFLASHMODE_DOUT @@ -4087,10 +4087,10 @@ d1_mini_pro.menu.ssl.all=All SSL ciphers (most compatible) d1_mini_pro.menu.ssl.all.build.sslflags= d1_mini_pro.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini_pro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini_pro.menu.waveform.pwm=Locked PWM +d1_mini_pro.menu.waveform.pwm.build.waveform= d1_mini_pro.menu.waveform.phase=Locked Phase d1_mini_pro.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -d1_mini_pro.menu.waveform.pwm=Locked PWM -d1_mini_pro.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM d1_mini_pro.upload.resetmethod=--before default_reset --after hard_reset d1_mini_pro.build.flash_mode=dio d1_mini_pro.build.flash_flags=-DFLASHMODE_DIO @@ -4261,10 +4261,10 @@ d1.menu.ssl.all=All SSL ciphers (most compatible) d1.menu.ssl.all.build.sslflags= d1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1.menu.waveform.pwm=Locked PWM +d1.menu.waveform.pwm.build.waveform= d1.menu.waveform.phase=Locked Phase d1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -d1.menu.waveform.pwm=Locked PWM -d1.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM d1.upload.resetmethod=--before default_reset --after hard_reset d1.build.flash_mode=dio d1.build.flash_flags=-DFLASHMODE_DIO @@ -4452,10 +4452,10 @@ nodemcu.menu.ssl.all=All SSL ciphers (most compatible) nodemcu.menu.ssl.all.build.sslflags= nodemcu.menu.ssl.basic=Basic SSL ciphers (lower ROM use) nodemcu.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +nodemcu.menu.waveform.pwm=Locked PWM +nodemcu.menu.waveform.pwm.build.waveform= nodemcu.menu.waveform.phase=Locked Phase nodemcu.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -nodemcu.menu.waveform.pwm=Locked PWM -nodemcu.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM nodemcu.upload.resetmethod=--before default_reset --after hard_reset nodemcu.build.flash_mode=qio nodemcu.build.flash_flags=-DFLASHMODE_QIO @@ -4643,10 +4643,10 @@ nodemcuv2.menu.ssl.all=All SSL ciphers (most compatible) nodemcuv2.menu.ssl.all.build.sslflags= nodemcuv2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) nodemcuv2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +nodemcuv2.menu.waveform.pwm=Locked PWM +nodemcuv2.menu.waveform.pwm.build.waveform= nodemcuv2.menu.waveform.phase=Locked Phase nodemcuv2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -nodemcuv2.menu.waveform.pwm=Locked PWM -nodemcuv2.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM nodemcuv2.upload.resetmethod=--before default_reset --after hard_reset nodemcuv2.build.flash_mode=dio nodemcuv2.build.flash_flags=-DFLASHMODE_DIO @@ -4838,10 +4838,10 @@ modwifi.menu.ssl.all=All SSL ciphers (most compatible) modwifi.menu.ssl.all.build.sslflags= modwifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use) modwifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +modwifi.menu.waveform.pwm=Locked PWM +modwifi.menu.waveform.pwm.build.waveform= modwifi.menu.waveform.phase=Locked Phase modwifi.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -modwifi.menu.waveform.pwm=Locked PWM -modwifi.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM modwifi.upload.resetmethod=--before no_reset --after soft_reset modwifi.build.flash_mode=qio modwifi.build.flash_flags=-DFLASHMODE_QIO @@ -5049,10 +5049,10 @@ phoenix_v1.menu.ssl.all=All SSL ciphers (most compatible) phoenix_v1.menu.ssl.all.build.sslflags= phoenix_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) phoenix_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +phoenix_v1.menu.waveform.pwm=Locked PWM +phoenix_v1.menu.waveform.pwm.build.waveform= phoenix_v1.menu.waveform.phase=Locked Phase phoenix_v1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -phoenix_v1.menu.waveform.pwm=Locked PWM -phoenix_v1.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM phoenix_v1.build.flash_mode=dio phoenix_v1.build.flash_flags=-DFLASHMODE_DIO phoenix_v1.build.flash_freq=40 @@ -5243,10 +5243,10 @@ phoenix_v2.menu.ssl.all=All SSL ciphers (most compatible) phoenix_v2.menu.ssl.all.build.sslflags= phoenix_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) phoenix_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +phoenix_v2.menu.waveform.pwm=Locked PWM +phoenix_v2.menu.waveform.pwm.build.waveform= phoenix_v2.menu.waveform.phase=Locked Phase phoenix_v2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -phoenix_v2.menu.waveform.pwm=Locked PWM -phoenix_v2.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM phoenix_v2.build.flash_mode=dio phoenix_v2.build.flash_flags=-DFLASHMODE_DIO phoenix_v2.build.flash_freq=40 @@ -5437,10 +5437,10 @@ eduinowifi.menu.ssl.all=All SSL ciphers (most compatible) eduinowifi.menu.ssl.all.build.sslflags= eduinowifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use) eduinowifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +eduinowifi.menu.waveform.pwm=Locked PWM +eduinowifi.menu.waveform.pwm.build.waveform= eduinowifi.menu.waveform.phase=Locked Phase eduinowifi.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -eduinowifi.menu.waveform.pwm=Locked PWM -eduinowifi.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM eduinowifi.upload.resetmethod=--before default_reset --after hard_reset eduinowifi.build.flash_mode=dio eduinowifi.build.flash_flags=-DFLASHMODE_DIO @@ -5628,10 +5628,10 @@ wiolink.menu.ssl.all=All SSL ciphers (most compatible) wiolink.menu.ssl.all.build.sslflags= wiolink.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wiolink.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wiolink.menu.waveform.pwm=Locked PWM +wiolink.menu.waveform.pwm.build.waveform= wiolink.menu.waveform.phase=Locked Phase wiolink.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -wiolink.menu.waveform.pwm=Locked PWM -wiolink.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM wiolink.upload.resetmethod=--before default_reset --after hard_reset wiolink.build.flash_mode=qio wiolink.build.flash_flags=-DFLASHMODE_QIO @@ -5819,10 +5819,10 @@ blynk.menu.ssl.all=All SSL ciphers (most compatible) blynk.menu.ssl.all.build.sslflags= blynk.menu.ssl.basic=Basic SSL ciphers (lower ROM use) blynk.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +blynk.menu.waveform.pwm=Locked PWM +blynk.menu.waveform.pwm.build.waveform= blynk.menu.waveform.phase=Locked Phase blynk.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -blynk.menu.waveform.pwm=Locked PWM -blynk.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM blynk.upload.resetmethod=--before default_reset --after hard_reset blynk.build.flash_mode=qio blynk.build.flash_flags=-DFLASHMODE_QIO @@ -6010,10 +6010,10 @@ thing.menu.ssl.all=All SSL ciphers (most compatible) thing.menu.ssl.all.build.sslflags= thing.menu.ssl.basic=Basic SSL ciphers (lower ROM use) thing.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +thing.menu.waveform.pwm=Locked PWM +thing.menu.waveform.pwm.build.waveform= thing.menu.waveform.phase=Locked Phase thing.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -thing.menu.waveform.pwm=Locked PWM -thing.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM thing.upload.resetmethod=--before no_reset --after soft_reset thing.build.flash_mode=qio thing.build.flash_flags=-DFLASHMODE_QIO @@ -6201,10 +6201,10 @@ thingdev.menu.ssl.all=All SSL ciphers (most compatible) thingdev.menu.ssl.all.build.sslflags= thingdev.menu.ssl.basic=Basic SSL ciphers (lower ROM use) thingdev.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +thingdev.menu.waveform.pwm=Locked PWM +thingdev.menu.waveform.pwm.build.waveform= thingdev.menu.waveform.phase=Locked Phase thingdev.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -thingdev.menu.waveform.pwm=Locked PWM -thingdev.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM thingdev.upload.resetmethod=--before default_reset --after hard_reset thingdev.build.flash_mode=dio thingdev.build.flash_flags=-DFLASHMODE_DIO @@ -6392,10 +6392,10 @@ esp210.menu.ssl.all=All SSL ciphers (most compatible) esp210.menu.ssl.all.build.sslflags= esp210.menu.ssl.basic=Basic SSL ciphers (lower ROM use) esp210.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +esp210.menu.waveform.pwm=Locked PWM +esp210.menu.waveform.pwm.build.waveform= esp210.menu.waveform.phase=Locked Phase esp210.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -esp210.menu.waveform.pwm=Locked PWM -esp210.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM esp210.upload.resetmethod=--before no_reset --after soft_reset esp210.build.flash_mode=qio esp210.build.flash_flags=-DFLASHMODE_QIO @@ -6583,10 +6583,10 @@ espinotee.menu.ssl.all=All SSL ciphers (most compatible) espinotee.menu.ssl.all.build.sslflags= espinotee.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espinotee.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espinotee.menu.waveform.pwm=Locked PWM +espinotee.menu.waveform.pwm.build.waveform= espinotee.menu.waveform.phase=Locked Phase espinotee.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -espinotee.menu.waveform.pwm=Locked PWM -espinotee.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM espinotee.upload.resetmethod=--before default_reset --after hard_reset espinotee.build.flash_mode=qio espinotee.build.flash_flags=-DFLASHMODE_QIO @@ -6774,10 +6774,10 @@ wifiduino.menu.ssl.all=All SSL ciphers (most compatible) wifiduino.menu.ssl.all.build.sslflags= wifiduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifiduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifiduino.menu.waveform.pwm=Locked PWM +wifiduino.menu.waveform.pwm.build.waveform= wifiduino.menu.waveform.phase=Locked Phase wifiduino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -wifiduino.menu.waveform.pwm=Locked PWM -wifiduino.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM wifiduino.upload.resetmethod=--before default_reset --after hard_reset wifiduino.build.flash_mode=dio wifiduino.build.flash_flags=-DFLASHMODE_DIO @@ -6982,10 +6982,10 @@ wifinfo.menu.ssl.all=All SSL ciphers (most compatible) wifinfo.menu.ssl.all.build.sslflags= wifinfo.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifinfo.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifinfo.menu.waveform.pwm=Locked PWM +wifinfo.menu.waveform.pwm.build.waveform= wifinfo.menu.waveform.phase=Locked Phase wifinfo.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -wifinfo.menu.waveform.pwm=Locked PWM -wifinfo.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM wifinfo.upload.resetmethod=--before default_reset --after hard_reset wifinfo.build.flash_mode=qio wifinfo.build.flash_flags=-DFLASHMODE_QIO @@ -7220,10 +7220,10 @@ cw01.menu.ssl.all=All SSL ciphers (most compatible) cw01.menu.ssl.all.build.sslflags= cw01.menu.ssl.basic=Basic SSL ciphers (lower ROM use) cw01.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +cw01.menu.waveform.pwm=Locked PWM +cw01.menu.waveform.pwm.build.waveform= cw01.menu.waveform.phase=Locked Phase cw01.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE -cw01.menu.waveform.pwm=Locked PWM -cw01.menu.waveform.pwm.build.waveform=-DWAVEFORM_LOCKED_PWM cw01.upload.resetmethod=--before default_reset --after hard_reset cw01.menu.CrystalFreq.26=26 MHz cw01.menu.CrystalFreq.40=40 MHz diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index 601b1df51..8deeb63d0 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -30,6 +30,13 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat return; } +#ifndef WAVEFORM_LOCKED_PHASE + // Stop any analogWrites (PWM) because they are a different generator + _stopPWM(_pin); +#endif + // If there's another Tone or startWaveform on this pin + // it will be changed on-the-fly (no need to stop it) + pinMode(_pin, OUTPUT); high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency, diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index e24819a65..4c5ce4fed 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -1,93 +1,7 @@ -/* - esp8266_waveform - General purpose waveform generation and control, - supporting outputs on all pins in parallel. - - Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. - Copyright (c) 2020 Dirk O. Kaar. - - The core idea is to have a programmable waveform generator with a unique - high and low period (defined in microseconds or CPU clock cycles). TIMER1 is - set to 1-shot mode and is always loaded with the time until the next edge - of any live waveforms. - - Up to one waveform generator per pin supported. - - Each waveform generator is synchronized to the ESP clock cycle counter, not the - timer. This allows for removing interrupt jitter and delay as the counter - always increments once per 80MHz clock. Changes to a waveform are - contiguous and only take effect on the next waveform transition, - allowing for smooth transitions. - - This replaces older tone(), analogWrite(), and the Servo classes. - - Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() - clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 - cycles (which may be 2 CPU clock cycles @ 160MHz). - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ +// Wrapper to include both versions of the waveform generator #ifdef WAVEFORM_LOCKED_PHASE - -#include - -#ifndef __ESP8266_WAVEFORM_H -#define __ESP8266_WAVEFORM_H - -#ifdef __cplusplus -extern "C" { + #include "core_esp8266_waveform_phase.h" +#else + #include "core_esp8266_waveform_pwm.h" #endif - -// Start or change a waveform of the specified high and low times on specific pin. -// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next -// full period. -// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, -// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that. -// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio -// under load, for applications where frequency or duty cycle must not change, leave false. -// Returns true or false on success or failure. -int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, - uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false); -// Start or change a waveform of the specified high and low CPU clock cycles on specific pin. -// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next -// full period. -// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, -// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that. -// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio -// under load, for applications where frequency or duty cycle must not change, leave false. -// Returns true or false on success or failure. -int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, - uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false); -// Stop a waveform, if any, on the specified pin. -// Returns true or false on success or failure. -int stopWaveform(uint8_t pin); - -// Add a callback function to be called on *EVERY* timer1 trigger. The -// callback returns the number of microseconds until the next desired call. -// However, since it is called every timer1 interrupt, it may be called -// again before this period. It should therefore use the ESP Cycle Counter -// to determine whether or not to perform an operation. -// Pass in NULL to disable the callback and, if no other waveforms being -// generated, stop the timer as well. -// Make sure the CB function has the ICACHE_RAM_ATTR decorator. -void setTimer1Callback(uint32_t (*fn)()); - -#ifdef __cplusplus -} -#endif - -#endif // __ESP8266_WAVEFORM_H - -#endif // WAVEFORM_LOCKED_PHASE diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform_phase.cpp similarity index 99% rename from cores/esp8266/core_esp8266_waveform.cpp rename to cores/esp8266/core_esp8266_waveform_phase.cpp index 4bd0f1c01..62e3ad3dd 100644 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ b/cores/esp8266/core_esp8266_waveform_phase.cpp @@ -41,7 +41,7 @@ #ifdef WAVEFORM_LOCKED_PHASE -#include "core_esp8266_waveform.h" +#include "core_esp8266_waveform_phase.h" #include #include "ets_sys.h" #include diff --git a/cores/esp8266/core_esp8266_waveform_phase.h b/cores/esp8266/core_esp8266_waveform_phase.h new file mode 100644 index 000000000..e24819a65 --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform_phase.h @@ -0,0 +1,93 @@ +/* + esp8266_waveform - General purpose waveform generation and control, + supporting outputs on all pins in parallel. + + Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + Copyright (c) 2020 Dirk O. Kaar. + + The core idea is to have a programmable waveform generator with a unique + high and low period (defined in microseconds or CPU clock cycles). TIMER1 is + set to 1-shot mode and is always loaded with the time until the next edge + of any live waveforms. + + Up to one waveform generator per pin supported. + + Each waveform generator is synchronized to the ESP clock cycle counter, not the + timer. This allows for removing interrupt jitter and delay as the counter + always increments once per 80MHz clock. Changes to a waveform are + contiguous and only take effect on the next waveform transition, + allowing for smooth transitions. + + This replaces older tone(), analogWrite(), and the Servo classes. + + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() + clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef WAVEFORM_LOCKED_PHASE + +#include + +#ifndef __ESP8266_WAVEFORM_H +#define __ESP8266_WAVEFORM_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Start or change a waveform of the specified high and low times on specific pin. +// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next +// full period. +// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, +// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that. +// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio +// under load, for applications where frequency or duty cycle must not change, leave false. +// Returns true or false on success or failure. +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, + uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false); +// Start or change a waveform of the specified high and low CPU clock cycles on specific pin. +// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next +// full period. +// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running, +// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that. +// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio +// under load, for applications where frequency or duty cycle must not change, leave false. +// Returns true or false on success or failure. +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, + uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false); +// Stop a waveform, if any, on the specified pin. +// Returns true or false on success or failure. +int stopWaveform(uint8_t pin); + +// Add a callback function to be called on *EVERY* timer1 trigger. The +// callback returns the number of microseconds until the next desired call. +// However, since it is called every timer1 interrupt, it may be called +// again before this period. It should therefore use the ESP Cycle Counter +// to determine whether or not to perform an operation. +// Pass in NULL to disable the callback and, if no other waveforms being +// generated, stop the timer as well. +// Make sure the CB function has the ICACHE_RAM_ATTR decorator. +void setTimer1Callback(uint32_t (*fn)()); + +#ifdef __cplusplus +} +#endif + +#endif // __ESP8266_WAVEFORM_H + +#endif // WAVEFORM_LOCKED_PHASE diff --git a/cores/esp8266/core_esp8266_waveform_pwm.cpp b/cores/esp8266/core_esp8266_waveform_pwm.cpp new file mode 100644 index 000000000..aea88e15b --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform_pwm.cpp @@ -0,0 +1,626 @@ +/* + esp8266_waveform - General purpose waveform generation and control, + supporting outputs on all pins in parallel. + + Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + + The core idea is to have a programmable waveform generator with a unique + high and low period (defined in microseconds or CPU clock cycles). TIMER1 + is set to 1-shot mode and is always loaded with the time until the next + edge of any live waveforms. + + Up to one waveform generator per pin supported. + + Each waveform generator is synchronized to the ESP clock cycle counter, not + the timer. This allows for removing interrupt jitter and delay as the + counter always increments once per 80MHz clock. Changes to a waveform are + contiguous and only take effect on the next waveform transition, + allowing for smooth transitions. + + This replaces older tone(), analogWrite(), and the Servo classes. + + Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() + clock cycle count, or an interval measured in CPU clock cycles, but not + TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef WAVEFORM_LOCKED_PHASE + +#include +#include "ets_sys.h" +#include "core_esp8266_waveform_pwm.h" +#include "user_interface.h" +extern "C" { + +// Maximum delay between IRQs +#define MAXIRQUS (10000) + +// Waveform generator can create tones, PWM, and servos +typedef struct { + uint32_t nextServiceCycle; // ESP cycle timer when a transition required + uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop + uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles) + uint32_t timeLowCycles; // + uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal + uint32_t desiredLowCycles; // + uint32_t lastEdge; // Cycle when this generator last changed +} Waveform; + +class WVFState { +public: + Waveform waveform[17]; // State of all possible pins + uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code + uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code + + // Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine + uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin + uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation + + uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI + uint32_t waveformNewHigh = 0; + uint32_t waveformNewLow = 0; + + uint32_t (*timer1CB)() = NULL; + + // Optimize the NMI inner loop by keeping track of the min and max GPIO that we + // are generating. In the common case (1 PWM) these may be the same pin and + // we can avoid looking at the other pins. + uint16_t startPin = 0; + uint16_t endPin = 0; +}; +static WVFState wvfState; + + +// Ensure everything is read/written to RAM +#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); } + +// Non-speed critical bits +#pragma GCC optimize ("Os") + +// Interrupt on/off control +static ICACHE_RAM_ATTR void timer1Interrupt(); +static bool timerRunning = false; + +static __attribute__((noinline)) void initTimer() { + if (!timerRunning) { + timer1_disable(); + ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); + ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); + timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); + timerRunning = true; + timer1_write(microsecondsToClockCycles(10)); + } +} + +static ICACHE_RAM_ATTR void forceTimerInterrupt() { + if (T1L > microsecondsToClockCycles(10)) { + T1L = microsecondsToClockCycles(10); + } +} + +// PWM implementation using special purpose state machine +// +// Keep an ordered list of pins with the delta in cycles between each +// element, with a terminal entry making up the remainder of the PWM +// period. With this method sum(all deltas) == PWM period clock cycles. +// +// At t=0 set all pins high and set the timeout for the 1st edge. +// On interrupt, if we're at the last element reset to t=0 state +// Otherwise, clear that pin down and set delay for next element +// and so forth. + +constexpr int maxPWMs = 8; + +// PWM machine state +typedef struct PWMState { + uint32_t mask; // Bitmask of active pins + uint32_t cnt; // How many entries + uint32_t idx; // Where the state machine is along the list + uint8_t pin[maxPWMs + 1]; + uint32_t delta[maxPWMs + 1]; + uint32_t nextServiceCycle; // Clock cycle for next step + struct PWMState *pwmUpdate; // Set by main code, cleared by ISR +} PWMState; + +static PWMState pwmState; +static uint32_t _pwmFreq = 1000; +static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq; + + +// If there are no more scheduled activities, shut down Timer 1. +// Otherwise, do nothing. +static ICACHE_RAM_ATTR void disableIdleTimer() { + if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { + ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); + timer1_disable(); + timer1_isr_init(); + timerRunning = false; + } +} + +// Notify the NMI that a new PWM state is available through the mailbox. +// Wait for mailbox to be emptied (either busy or delay() as needed) +static ICACHE_RAM_ATTR void _notifyPWM(PWMState *p, bool idle) { + p->pwmUpdate = nullptr; + pwmState.pwmUpdate = p; + MEMBARRIER(); + forceTimerInterrupt(); + while (pwmState.pwmUpdate) { + if (idle) { + delay(0); + } + MEMBARRIER(); + } +} + +static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range); + +// Called when analogWriteFreq() changed to update the PWM total period +void _setPWMFreq(uint32_t freq) { + _pwmFreq = freq; + + // Convert frequency into clock cycles + uint32_t cc = microsecondsToClockCycles(1000000UL) / freq; + + // Simple static adjustment to bring period closer to requested due to overhead + // Empirically determined as a constant PWM delay and a function of the number of PWMs +#if F_CPU == 80000000 + cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110; +#else + cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75; +#endif + + if (cc == _pwmPeriod) { + return; // No change + } + + _pwmPeriod = cc; + + if (pwmState.cnt) { + PWMState p; // The working copy since we can't edit the one in use + p.mask = 0; + p.cnt = 0; + for (uint32_t i = 0; i < pwmState.cnt; i++) { + auto pin = pwmState.pin[i]; + _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles); + } + // Update and wait for mailbox to be emptied + initTimer(); + _notifyPWM(&p, true); + disableIdleTimer(); + } +} + +// Helper routine to remove an entry from the state machine +// and clean up any marked-off entries +static void _cleanAndRemovePWM(PWMState *p, int pin) { + uint32_t leftover = 0; + uint32_t in, out; + for (in = 0, out = 0; in < p->cnt; in++) { + if ((p->pin[in] != pin) && (p->mask & (1<pin[in]))) { + p->pin[out] = p->pin[in]; + p->delta[out] = p->delta[in] + leftover; + leftover = 0; + out++; + } else { + leftover += p->delta[in]; + p->mask &= ~(1<pin[in]); + } + } + p->cnt = out; + // Final pin is never used: p->pin[out] = 0xff; + p->delta[out] = p->delta[in] + leftover; +} + + +// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%)) +ICACHE_RAM_ATTR bool _stopPWM(int pin) { + if (!((1<= _pwmPeriod) { + cc = _pwmPeriod - 1; + } + + if (p.cnt == 0) { + // Starting up from scratch, special case 1st element and PWM period + p.pin[0] = pin; + p.delta[0] = cc; + // Final pin is never used: p.pin[1] = 0xff; + p.delta[1] = _pwmPeriod - cc; + } else { + uint32_t ttl = 0; + uint32_t i; + // Skip along until we're at the spot to insert + for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) { + ttl += p.delta[i]; + } + // Shift everything out by one to make space for new edge + for (int32_t j = p.cnt; j >= (int)i; j--) { + p.pin[j + 1] = p.pin[j]; + p.delta[j + 1] = p.delta[j]; + } + int off = cc - ttl; // The delta from the last edge to the one we're inserting + p.pin[i] = pin; + p.delta[i] = off; // Add the delta to this new pin + p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant + } + p.cnt++; + p.mask |= 1<= maxPWMs) { + return false; // No space left + } + + // Sanity check for all-on/off + uint32_t cc = (_pwmPeriod * val) / range; + if ((cc == 0) || (cc >= _pwmPeriod)) { + digitalWrite(pin, cc ? HIGH : LOW); + return true; + } + + _addPWMtoList(p, pin, val, range); + + // Set mailbox and wait for ISR to copy it over + initTimer(); + _notifyPWM(&p, true); + disableIdleTimer(); + + // Potentially recalculate the PWM period if we've added another pin + _setPWMFreq(_pwmFreq); + + return true; +} + +// Start up a waveform on a pin, or change the current one. Will change to the new +// waveform smoothly on next low->high transition. For immediate change, stopWaveform() +// first, then it will immediately begin. +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) { + return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS)); +} + +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) { + if ((pin > 16) || isFlashInterfacePin(pin)) { + return false; + } + Waveform *wave = &wvfState.waveform[pin]; + wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0; + if (runTimeCycles && !wave->expiryCycle) { + wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it + } + + _stopPWM(pin); // Make sure there's no PWM live here + + uint32_t mask = 1<timeHighCycles = timeHighCycles; + wave->desiredHighCycles = timeHighCycles; + wave->timeLowCycles = timeLowCycles; + wave->desiredLowCycles = timeLowCycles; + wave->lastEdge = 0; + wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); + wvfState.waveformToEnable |= mask; + MEMBARRIER(); + initTimer(); + forceTimerInterrupt(); + while (wvfState.waveformToEnable) { + delay(0); // Wait for waveform to update + // No mem barrier here, the call to a global function implies global state updated + } + } + + return true; +} + + +// Set a callback. Pass in NULL to stop it +void setTimer1Callback(uint32_t (*fn)()) { + wvfState.timer1CB = fn; + if (fn) { + initTimer(); + forceTimerInterrupt(); + } + disableIdleTimer(); +} + +// Stops a waveform on a pin +int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) { + // Can't possibly need to stop anything if there is no timer active + if (!timerRunning) { + return false; + } + // If user sends in a pin >16 but <32, this will always point to a 0 bit + // If they send >=32, then the shift will result in 0 and it will also return false + uint32_t mask = 1<> 0) +#endif + +// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage +#define MINIRQTIME microsecondsToClockCycles(4) + +static ICACHE_RAM_ATTR void timer1Interrupt() { + // Flag if the core is at 160 MHz, for use by adjust() + bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false; + + uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS); + uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14); + + if (wvfState.waveformToEnable || wvfState.waveformToDisable) { + // Handle enable/disable requests from main app + wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off + wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started + wvfState.waveformToEnable = 0; + wvfState.waveformToDisable = 0; + // No mem barrier. Globals must be written to RAM on ISR exit. + // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) + wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; + // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) + wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); + } else if (!pwmState.cnt && pwmState.pwmUpdate) { + // Start up the PWM generator by copying from the mailbox + pwmState.cnt = 1; + pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 + pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! + // No need for mem barrier here. Global must be written by IRQ exit + } + + bool done = false; + if (wvfState.waveformEnabled || pwmState.cnt) { + do { + nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS); + + // PWM state machine implementation + if (pwmState.cnt) { + int32_t cyclesToGo; + do { + cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ(); + if (cyclesToGo < 0) { + if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new + if (pwmState.pwmUpdate) { + // Do the memory copy from temp to global and clear mailbox + pwmState = *(PWMState*)pwmState.pwmUpdate; + } + GPOS = pwmState.mask; // Set all active pins high + if (pwmState.mask & (1<<16)) { + GP16O = 1; + } + pwmState.idx = 0; + } else { + do { + // Drop the pin at this edge + if (pwmState.mask & (1<expiryCycle) { + int32_t expiryToGo = wave->expiryCycle - now; + if (expiryToGo < 0) { + // Done, remove! + if (i == 16) { + GP16O = 0; + } + GPOC = mask; + wvfState.waveformEnabled &= ~mask; + continue; + } + } + + // Check for toggles + int32_t cyclesToGo = wave->nextServiceCycle - now; + if (cyclesToGo < 0) { + uint32_t nextEdgeCycles; + uint32_t desired = 0; + uint32_t *timeToUpdate; + wvfState.waveformState ^= mask; + if (wvfState.waveformState & mask) { + if (i == 16) { + GP16O = 1; + } + GPOS = mask; + + if (wvfState.waveformToChange & mask) { + // Copy over next full-cycle timings + wave->timeHighCycles = wvfState.waveformNewHigh; + wave->desiredHighCycles = wvfState.waveformNewHigh; + wave->timeLowCycles = wvfState.waveformNewLow; + wave->desiredLowCycles = wvfState.waveformNewLow; + wave->lastEdge = 0; + wvfState.waveformToChange = 0; + } + if (wave->lastEdge) { + desired = wave->desiredLowCycles; + timeToUpdate = &wave->timeLowCycles; + } + nextEdgeCycles = wave->timeHighCycles; + } else { + if (i == 16) { + GP16O = 0; + } + GPOC = mask; + desired = wave->desiredHighCycles; + timeToUpdate = &wave->timeHighCycles; + nextEdgeCycles = wave->timeLowCycles; + } + if (desired) { + desired = adjust(desired); + int32_t err = desired - (now - wave->lastEdge); + if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal + err /= 2; + *timeToUpdate += err; + } + } + nextEdgeCycles = adjust(nextEdgeCycles); + wave->nextServiceCycle = now + nextEdgeCycles; + wave->lastEdge = now; + } + nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle); + } + + // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur + uint32_t now = GetCycleCountIRQ(); + int32_t cycleDeltaNextEvent = nextEventCycle - now; + int32_t cyclesLeftTimeout = timeoutCycle - now; + done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0); + } while (!done); + } // if (wvfState.waveformEnabled) + + if (wvfState.timer1CB) { + nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB()); + } + + int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ(); + + if (nextEventCycles < MINIRQTIME) { + nextEventCycles = MINIRQTIME; + } + nextEventCycles -= DELTAIRQ; + + // Do it here instead of global function to save time and because we know it's edge-IRQ + T1L = nextEventCycles >> (turbo ? 1 : 0); +} + +}; + +#endif diff --git a/cores/esp8266/core_esp8266_waveform_pwm.h b/cores/esp8266/core_esp8266_waveform_pwm.h new file mode 100644 index 000000000..8d60e4e7c --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform_pwm.h @@ -0,0 +1,87 @@ +/* + esp8266_waveform - General purpose waveform generation and control, + supporting outputs on all pins in parallel. + + Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + + The core idea is to have a programmable waveform generator with a unique + high and low period (defined in microseconds or CPU clock cycles). TIMER1 is + set to 1-shot mode and is always loaded with the time until the next edge + of any live waveforms. + + Up to one waveform generator per pin supported. + + Each waveform generator is synchronized to the ESP clock cycle counter, not the + timer. This allows for removing interrupt jitter and delay as the counter + always increments once per 80MHz clock. Changes to a waveform are + contiguous and only take effect on the next waveform transition, + allowing for smooth transitions. + + This replaces older tone(), analogWrite(), and the Servo classes. + + Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() + clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef WAVEFORM_LOCKED_PHASE + +#include + +#ifndef __ESP8266_WAVEFORM_H +#define __ESP8266_WAVEFORM_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Start or change a waveform of the specified high and low times on specific pin. +// If runtimeUS > 0 then automatically stop it after that many usecs. +// Returns true or false on success or failure. +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); +// Start or change a waveform of the specified high and low CPU clock cycles on specific pin. +// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles. +// Returns true or false on success or failure. +int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); +// 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)()); + + + +// Internal-only calls, not for applications +extern void _setPWMFreq(uint32_t freq); +extern bool _stopPWM(int pin); +extern bool _setPWM(int pin, uint32_t val, uint32_t range); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/cores/esp8266/core_esp8266_wiring_digital.cpp b/cores/esp8266/core_esp8266_wiring_digital.cpp index 9c15703e0..539f5448b 100644 --- a/cores/esp8266/core_esp8266_wiring_digital.cpp +++ b/cores/esp8266/core_esp8266_wiring_digital.cpp @@ -82,7 +82,10 @@ extern void __pinMode(uint8_t pin, uint8_t mode) { } extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) { - stopWaveform(pin); + stopWaveform(pin); // Disable any tone +#ifndef WAVEFORM_LOCKED_PHASE + _stopPWM(pin); // ...and any analogWrite +#endif if(pin < 16){ if(val) GPOS = (1 << pin); else GPOC = (1 << pin); diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index 565cac7d8..a456c965c 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -73,11 +73,39 @@ extern void __analogWrite(uint8_t pin, int val) { } } +#else // !WAVEFORM_LOCKED_PHASE + +extern void __analogWriteFreq(uint32_t freq) { + if (freq < 100) { + freq = 100; + } else if (freq > 60000) { + freq = 60000; + } else { + freq = freq; + } + _setPWMFreq(freq); +} + +extern void __analogWrite(uint8_t pin, int val) { + if (pin > 16) { + return; + } + + if (val < 0) { + val = 0; + } else if (val > analogScale) { + val = analogScale; + } + + // Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/ + // val: the duty cycle: between 0 (always off) and 255 (always on). + // So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH) + pinMode(pin, OUTPUT); + _setPWM(pin, val, analogScale); +} + #endif // WAVEFORM_LOCKED_PHASE -#ifdef WAVEFORM_LOCKED_PWM - -#endif // WAVEFORM_LOCKED_PWM extern void __analogWriteRange(uint32_t range) { if ((range >= 15) && (range <= 65535)) { diff --git a/libraries/Servo/src/Servo.cpp b/libraries/Servo/src/Servo.cpp index 09d87c0f4..d24ea3379 100644 --- a/libraries/Servo/src/Servo.cpp +++ b/libraries/Servo/src/Servo.cpp @@ -95,7 +95,11 @@ void Servo::detach() { if (_attached) { _servoMap &= ~(1 << _pin); +#ifdef WAVEFORM_LOCKED_PHASE startWaveform(_pin, 0, REFRESH_INTERVAL, 1); +#else + // TODO - timeHigh == 0 is illegal in _PWM code branch. Do nothing for now. +#endif delay(REFRESH_INTERVAL / 1000); // long enough to complete active period under all circumstances. stopWaveform(_pin); _attached = false; diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index 6d520c259..4c08ee8d2 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit 6d520c259cad4457ccdbee362c16f7fa3b504b06 +Subproject commit 4c08ee8d2cb7b5b27eb4f86797694cbac94aa5c9 diff --git a/tools/boards.txt.py b/tools/boards.txt.py index 816c9c00a..d0fcc4585 100755 --- a/tools/boards.txt.py +++ b/tools/boards.txt.py @@ -1471,10 +1471,10 @@ def led (name, default, ledList): def waveform (): return { 'waveform': collections.OrderedDict([ + ('.menu.waveform.pwm', 'Locked PWM'), + ('.menu.waveform.pwm.build.waveform', ''), ('.menu.waveform.phase', 'Locked Phase'), ('.menu.waveform.phase.build.waveform', '-DWAVEFORM_LOCKED_PHASE'), - ('.menu.waveform.pwm', 'Locked PWM'), - ('.menu.waveform.pwm.build.waveform', '-DWAVEFORM_LOCKED_PWM'), ]) } diff --git a/tools/platformio-build.py b/tools/platformio-build.py index c735247f3..6620b3740 100644 --- a/tools/platformio-build.py +++ b/tools/platformio-build.py @@ -242,9 +242,7 @@ else: # if "PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PWM" in flatten_cppdefines: env.Append(CPPDEFINES=[("WAVEFORM_LOCKED_PWM", 1)]) -# PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PHASE (defaults) -else: - env.Append(CPPDEFINES=[("WAVEFORM_LOCKED_PHASE", 1)]) +# PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PHASE will be used by default # # VTables From 4c7a20868aaedcca834a9c9726702be76a339e53 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 19 Nov 2020 21:53:02 -0800 Subject: [PATCH 07/13] Undo accidental loss of 6.10.0 SoftwareSerial (#7714) --- libraries/SoftwareSerial | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index 4c08ee8d2..6d520c259 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit 4c08ee8d2cb7b5b27eb4f86797694cbac94aa5c9 +Subproject commit 6d520c259cad4457ccdbee362c16f7fa3b504b06 From 27b54f57bdac416dd29ba23850442a2a560d1d5d Mon Sep 17 00:00:00 2001 From: david gauchard Date: Fri, 20 Nov 2020 21:35:41 +0100 Subject: [PATCH 08/13] CI waveform flavour select (#7715) * every other ci builder uses waveform phase lock * fix indentation * same defaults for PIO as in IDE * CI: force logging without error/warning * remove forced logging --- tests/common.sh | 8 +++++--- tools/build.py | 4 ++++ tools/platformio-build.py | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/common.sh b/tests/common.sh index 930071784..0c6ac9540 100755 --- a/tests/common.sh +++ b/tests/common.sh @@ -66,6 +66,8 @@ function build_sketches() local sketches=$(find $srcpath -name *.ino | sort) print_size_info >size.log export ARDUINO_IDE_PATH=$arduino + local pwm_phase="" + [ $(( $build_rem % 2 )) -eq 0 ] && pwm_phase="--waveform_phase" local testcnt=0 for sketch in $sketches; do testcnt=$(( ($testcnt + 1) % $build_mod )) @@ -104,7 +106,7 @@ function build_sketches() fi echo -e "\n ------------ Building $sketch ------------ \n"; # $arduino --verify $sketch; - if [ "$WINDOWS" == "1" ]; then + if [ "$WINDOWS" == "1" ]; then sketch=$(echo $sketch | sed 's/^\/c//') # MINGW will try to be helpful and silently convert args that look like paths to point to a spot inside the MinGW dir. This breaks everything. # http://www.mingw.org/wiki/Posix_path_conversion @@ -112,8 +114,8 @@ function build_sketches() export MSYS2_ARG_CONV_EXC="*" export MSYS_NO_PATHCONV=1 fi - echo "$build_cmd $sketch" - time ($build_cmd $sketch >build.log) + echo "$build_cmd $pwm_phase $sketch" + time ($build_cmd $pwm_phase $sketch >build.log) local result=$? if [ $result -ne 0 ]; then echo "Build failed ($1)" diff --git a/tools/build.py b/tools/build.py index efb1409ba..791ac30e2 100755 --- a/tools/build.py +++ b/tools/build.py @@ -69,6 +69,8 @@ def compile(tmp_dir, sketch, cache, tools_dir, hardware_dir, ide_path, f, args): 'ResetMethod=nodemcu'.format(**vars(args)) if args.debug_port and args.debug_level: fqbn += 'dbg={debug_port},lvl={debug_level}'.format(**vars(args)) + if args.waveform_phase: + fqbn += ',waveform=phase' cmd += [fqbn] cmd += ['-built-in-libraries', ide_path + '/libraries'] cmd += ['-ide-version=10607'] @@ -115,6 +117,8 @@ def parse_args(): type=int, choices=[40, 80]) parser.add_argument('--debug_port', help='Debug port', choices=['Serial', 'Serial1']) + parser.add_argument('--waveform_phase', action='store_true', + help='Select waveform locked on phase') parser.add_argument('--debug_level', help='Debug level') parser.add_argument('--build_cache', help='Build directory to cache core.a', default='') parser.add_argument('sketch_path', help='Sketch file path') diff --git a/tools/platformio-build.py b/tools/platformio-build.py index 6620b3740..38b8aa82d 100644 --- a/tools/platformio-build.py +++ b/tools/platformio-build.py @@ -240,9 +240,9 @@ else: # # Waveform # -if "PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PWM" in flatten_cppdefines: - env.Append(CPPDEFINES=[("WAVEFORM_LOCKED_PWM", 1)]) -# PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PHASE will be used by default +if "PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PHASE" in flatten_cppdefines: + env.Append(CPPDEFINES=[("WAVEFORM_LOCKED_PHASE", 1)]) +# PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PWM will be used by default # # VTables From 47a57e1ec690d0e4fea01a865bc2c5c8adb74b97 Mon Sep 17 00:00:00 2001 From: drderiv Date: Fri, 20 Nov 2020 15:23:43 -0800 Subject: [PATCH 09/13] Update to ESP8266HTTPClient.cpp for no Content-Length (#7691) Response bodies are ignored when _transferEncoding == HTTPC_TE_IDENTITY and there is no Content-Length header. The added code here fixes that issue. Add logic to writeToStreamDataBlock to only read what's available so as to avoid timeout, and adjust formatting. --- libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index 7ec3ab86f..37cdae0fc 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -725,7 +725,7 @@ int HTTPClient::writeToStream(Stream * stream) int ret = 0; if(_transferEncoding == HTTPC_TE_IDENTITY) { - if(len > 0) { + if(len > 0 || len == -1) { ret = writeToStreamDataBlock(stream, len); // have we an error? @@ -1184,6 +1184,12 @@ int HTTPClient::writeToStreamDataBlock(Stream * stream, int size) if(readBytes > buff_size) { readBytes = buff_size; } + + // len == -1 or len > what is available, read only what is available + int av = _client->available(); + if (readBytes < 0 || readBytes > av) { + readBytes = av; + } // read data int bytesRead = _client->readBytes(buff, readBytes); From f8115c32c937733c07232a33a8d6187af669d032 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 21 Nov 2020 18:25:51 +0100 Subject: [PATCH 10/13] =?UTF-8?q?Fix=20callback=20expected=20return=20to?= =?UTF-8?q?=20CPU=20cycles=20instead=20of=20=C2=B5s=20for=20PR=207022.=20F?= =?UTF-8?q?ix=20inline=20documentaton=20for=20both?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit waveform "flavors". --- cores/esp8266/core_esp8266_waveform_phase.cpp | 2 +- cores/esp8266/core_esp8266_waveform_phase.h | 2 +- cores/esp8266/core_esp8266_waveform_pwm.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_waveform_phase.cpp b/cores/esp8266/core_esp8266_waveform_phase.cpp index 62e3ad3dd..3d8eb1a89 100644 --- a/cores/esp8266/core_esp8266_waveform_phase.cpp +++ b/cores/esp8266/core_esp8266_waveform_phase.cpp @@ -405,7 +405,7 @@ static ICACHE_RAM_ATTR void timer1Interrupt() { int32_t callbackCcys = 0; if (waveform.timer1CB) { - callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB()), isCPU2X); + callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X); } now = ESP.getCycleCount(); int32_t nextEventCcys = waveform.nextEventCcy - now; diff --git a/cores/esp8266/core_esp8266_waveform_phase.h b/cores/esp8266/core_esp8266_waveform_phase.h index e24819a65..dff8fe502 100644 --- a/cores/esp8266/core_esp8266_waveform_phase.h +++ b/cores/esp8266/core_esp8266_waveform_phase.h @@ -75,7 +75,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLo 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. +// callback must return the number of CPU clock cycles 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. diff --git a/cores/esp8266/core_esp8266_waveform_pwm.h b/cores/esp8266/core_esp8266_waveform_pwm.h index 8d60e4e7c..3d66bc141 100644 --- a/cores/esp8266/core_esp8266_waveform_pwm.h +++ b/cores/esp8266/core_esp8266_waveform_pwm.h @@ -62,7 +62,7 @@ int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t time 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. +// callback must return the number of CPU clock cycles 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. From 8c7fd6aac12bad11d37c74b28f98b628e370bb32 Mon Sep 17 00:00:00 2001 From: Drzony Date: Wed, 25 Nov 2020 14:20:29 +0100 Subject: [PATCH 11/13] PROGMEM compatibility changes to String (#7724) Changed contructor to use strlen_P --- cores/esp8266/WString.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 8eaa88d91..07ab99b2c 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -32,7 +32,7 @@ String::String(const char *cstr) { init(); if (cstr) - copy(cstr, strlen(cstr)); + copy(cstr, strlen_P(cstr)); } String::String(const String &value) { From 04b0c270e4ee1cc21be3508622c6112aa85984b2 Mon Sep 17 00:00:00 2001 From: Drzony Date: Sun, 29 Nov 2020 02:09:25 +0100 Subject: [PATCH 12/13] Added GZipped OTA support in elf2bin and PlatformIO (#7727) --- tools/elf2bin.py | 29 +++++++++++++++++++++++++++++ tools/platformio-build.py | 7 ++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tools/elf2bin.py b/tools/elf2bin.py index ffc3a6208..231bd5e5d 100755 --- a/tools/elf2bin.py +++ b/tools/elf2bin.py @@ -141,6 +141,31 @@ def add_crc(out): with open(out, "wb") as binfile: binfile.write(raw) +def gzip_bin(mode, out): + import gzip + + firmware_path = out + gzip_path = firmware_path + '.gz' + orig_path = firmware_path + '.orig' + if os.path.exists(gzip_path): + os.remove(gzip_path) + print('GZipping firmware ' + firmware_path) + with open(firmware_path, 'rb') as firmware_file, \ + gzip.open(gzip_path, 'wb') as dest: + data = firmware_file.read() + dest.write(data) + orig_size = os.stat(firmware_path).st_size + gzip_size = os.stat(gzip_path).st_size + print("New FW size {:d} bytes vs old {:d} bytes".format( + gzip_size, orig_size)) + + if mode == "PIO": + if os.path.exists(orig_path): + os.remove(orig_path) + print('Moving original firmware to ' + orig_path) + os.rename(firmware_path, orig_path) + os.rename(gzip_path, firmware_path) + def main(): parser = argparse.ArgumentParser(description='Create a BIN file from eboot.elf and Arduino sketch.elf for upload by esptool.py') parser.add_argument('-e', '--eboot', action='store', required=True, help='Path to the Arduino eboot.elf bootloader') @@ -150,6 +175,7 @@ def main(): parser.add_argument('-s', '--flash_size', action='store', required=True, choices=['256K', '512K', '1M', '2M', '4M', '8M', '16M'], help='SPI flash size') parser.add_argument('-o', '--out', action='store', required=True, help='Output BIN filename') parser.add_argument('-p', '--path', action='store', required=True, help='Path to Xtensa toolchain binaries') + parser.add_argument('-g', '--gzip', choices=['PIO', 'Arduino'], help='PIO - generate gzipped BIN file, Arduino - generate BIN and BIN.gz') args = parser.parse_args() @@ -175,6 +201,9 @@ def main(): # Because the CRC includes both eboot and app, can only calculate it after the entire BIN generated add_crc(args.out) + if args.gzip: + gzip_bin(args.gzip, args.out) + return 0 diff --git a/tools/platformio-build.py b/tools/platformio-build.py index 38b8aa82d..0c893c2cc 100644 --- a/tools/platformio-build.py +++ b/tools/platformio-build.py @@ -45,10 +45,15 @@ Builder.match_splitext = scons_patched_match_splitext env = DefaultEnvironment() platform = env.PioPlatform() +board = env.BoardConfig() +gzip_fw = board.get("build.gzip_fw", False) +gzip_switch = [] FRAMEWORK_DIR = platform.get_package_dir("framework-arduinoespressif8266") assert isdir(FRAMEWORK_DIR) +if gzip_fw: + gzip_switch = ["--gzip", "PIO"] env.Append( ASFLAGS=["-x", "assembler-with-cpp"], @@ -145,7 +150,7 @@ env.Append( "--path", '"%s"' % join( platform.get_package_dir("toolchain-xtensa"), "bin"), "--out", "$TARGET" - ]), "Building $TARGET"), + ] + gzip_switch), "Building $TARGET"), suffix=".bin" ) ) From 92175d7090843745b024131fa959de0dbe8bd933 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sat, 28 Nov 2020 17:22:34 -0800 Subject: [PATCH 13/13] Rewrite multipart boundary detection (#7728) Use a simpler, cleaner implementation of multipart form detection as defined in https://tools.ietf.org/html/rfc7578 . Implements a simple state machine that detects the `\r\n--` stream in input a character at a time, instead of buffering and comparing in chunks which can miss things due to alignment issues and which also had a problem with replacing characters in a binary stream. Adjust the private _uploadReadByte function to return -1 on error (like a read()), and the main file upload handler to use that return value instead of duplicating logic. Fixes #7723 --- .../ESP8266WebServer/src/ESP8266WebServer.h | 2 +- libraries/ESP8266WebServer/src/Parsing-impl.h | 63 ++++++++----------- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 23b5b328a..d40313a59 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -246,7 +246,7 @@ protected: bool _parseForm(ClientType& client, const String& boundary, uint32_t len); bool _parseFormUploadAborted(); void _uploadWriteByte(uint8_t b); - uint8_t _uploadReadByte(ClientType& client); + int _uploadReadByte(ClientType& client); void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); bool _collectHeader(const char* headerName, const char* headerValue); diff --git a/libraries/ESP8266WebServer/src/Parsing-impl.h b/libraries/ESP8266WebServer/src/Parsing-impl.h index 33d3efd66..cab0c04f7 100644 --- a/libraries/ESP8266WebServer/src/Parsing-impl.h +++ b/libraries/ESP8266WebServer/src/Parsing-impl.h @@ -347,14 +347,14 @@ void ESP8266WebServerTemplate::_uploadWriteByte(uint8_t b){ } template -uint8_t ESP8266WebServerTemplate::_uploadReadByte(ClientType& client){ +int ESP8266WebServerTemplate::_uploadReadByte(ClientType& client){ int res = client.read(); if(res == -1){ while(!client.available() && client.connected()) yield(); res = client.read(); } - return (uint8_t)res; + return res; } @@ -444,45 +444,34 @@ bool ESP8266WebServerTemplate::_parseForm(ClientType& client, const _currentHandler->upload(*this, _currentUri, *_currentUpload); _currentUpload->status = UPLOAD_FILE_WRITE; - int bLen = boundary.length(); - uint8_t boundBuf[2 + bLen + 1]; // "--" + boundary + null terminator - boundBuf[2 + bLen] = '\0'; - uint8_t argByte; - bool first = true; - while (1) { - //attempt to fill up boundary buffer with length of boundary string - int i; - for (i = 0; i < 2 + bLen; i++) { - if (!client.connected()) return _parseFormUploadAborted(); - argByte = _uploadReadByte(client); - if (argByte == '\r') + int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */; + char fastBoundary[ fastBoundaryLen ]; + snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str()); + int boundaryPtr = 0; + while ( true ) { + int ret = _uploadReadByte(client); + if (ret < 0) { + // Unexpected, we should have had data available per above + return _parseFormUploadAborted(); + } + char in = (char) ret; + if (in == fastBoundary[ boundaryPtr ]) { + // The input matched the current expected character, advance and possibly exit this file + boundaryPtr++; + if (boundaryPtr == fastBoundaryLen - 1) { + // We read the whole boundary line, we're done here! break; - boundBuf[i] = argByte; - } - if ((strncmp((const char*)boundBuf, "--", 2) == 0) && (strcmp((const char*)(boundBuf + 2), boundary.c_str()) == 0)) - break; //found the boundary, done parsing this file - if (first) first = false; //only add newline characters after the first line - else { - _uploadWriteByte('\r'); - _uploadWriteByte('\n'); - } - // current line does not contain boundary, upload all bytes in boundary buffer - for (int j = 0; j < i; j++) - _uploadWriteByte(boundBuf[j]); - // the initial pass (filling up the boundary buffer) did not reach the end of the line. Upload the rest of the line now - if (i >= 2 + bLen) { - if (!client.connected()) return _parseFormUploadAborted(); - argByte = _uploadReadByte(client); - while (argByte != '\r') { - if (!client.connected()) return _parseFormUploadAborted(); - _uploadWriteByte(argByte); - argByte = _uploadReadByte(client); } + } else { + // The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start + for (int i = 0; i < boundaryPtr; i++) { + _uploadWriteByte( fastBoundary[ i ] ); + } + _uploadWriteByte( in ); + boundaryPtr = 0; } - if (!client.connected()) return _parseFormUploadAborted(); - _uploadReadByte(client); // '\n' } - //Found the boundary string, finish processing this file upload + // Found the boundary string, finish processing this file upload if (_currentHandler && _currentHandler->canUpload(_currentUri)) _currentHandler->upload(*this, _currentUri, *_currentUpload); _currentUpload->totalSize += _currentUpload->currentSize;