From 539c5089187c3438f4d616ada7804bf9991909c8 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Thu, 27 Jan 2022 21:30:05 +0100 Subject: [PATCH] Increase PWM channels to 16 (Esp32 only) --- CHANGELOG.md | 1 + .../Arduino_ST7789.cpp | 16 +- .../src/renderer.cpp | 4 +- .../Display_Renderer-gemu-1.0/src/renderer.h | 2 +- .../ILI9341-gemu-1.0/ILI9341_2.cpp | 14 +- lib/lib_display/UDisplay/uDisplay.cpp | 21 +- lib/lib_display/UDisplay/uDisplay.h | 7 +- .../lib_mail/src/wcs/esp8266/ESP_Mail_WCS.cpp | 4 +- .../src/WiFiClientSecureLightBearSSL.cpp | 2 +- .../ESP32-to-ESP8266-compat/src/c_types.h | 6 - .../src/eboot_command.h | 3 - .../src/esp8266toEsp32.cpp | 152 +++++----- .../src/esp8266toEsp32.h | 35 +-- .../ESP32-to-ESP8266-compat/src/osapi.h | 8 - tasmota/settings.h | 7 +- tasmota/settings.ino | 5 +- tasmota/support_command.ino | 50 ---- tasmota/support_pwm.ino | 273 ++++++++++++++++++ tasmota/support_tasmota.ino | 46 +-- tasmota/tasmota.h | 16 +- tasmota/tasmota.ino | 9 +- tasmota/tasmota_template.h | 4 +- tasmota/xdrv_04_light.ino | 23 +- tasmota/xdrv_13_display.ino | 4 +- tasmota/xdrv_24_buzzer.ino | 9 +- tasmota/xdrv_52_3_berry_webclient.ino | 14 + tasmota/xdrv_81_esp32_webcam.ino | 17 +- 27 files changed, 464 insertions(+), 288 deletions(-) delete mode 100644 lib/libesp32/ESP32-to-ESP8266-compat/src/c_types.h delete mode 100644 lib/libesp32/ESP32-to-ESP8266-compat/src/eboot_command.h delete mode 100644 lib/libesp32/ESP32-to-ESP8266-compat/src/osapi.h create mode 100644 tasmota/support_pwm.ino diff --git a/CHANGELOG.md b/CHANGELOG.md index d245af292..0ca625dce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file. - Command ``SspmScan`` to rescan Sonoff SPM modbus - Support for MQ analog sensor for air quality by Francesco Adriani (#14581) - Command ``SetOption134 1`` to disable PWM auto-phasing for lights by default (new behavior) (#14590) +- Increase PWM channels to 16 (Esp32 only) ### Changed - BME68x-Sensor-API library from v3.5.9 to v4.4.7 diff --git a/lib/lib_display/Arduino_ST7789-gemu-1.0/Arduino_ST7789.cpp b/lib/lib_display/Arduino_ST7789-gemu-1.0/Arduino_ST7789.cpp index 7347fa74f..b51339b32 100644 --- a/lib/lib_display/Arduino_ST7789-gemu-1.0/Arduino_ST7789.cpp +++ b/lib/lib_display/Arduino_ST7789-gemu-1.0/Arduino_ST7789.cpp @@ -18,6 +18,7 @@ const uint16_t ST7789_colors[]={ST7789_BLACK,ST7789_WHITE,ST7789_RED,ST7789_GREE ST7789_LIGHTGREY,ST7789_DARKGREY,ST7789_ORANGE,ST7789_GREENYELLOW,ST7789_PINK}; #ifdef ESP32 +#include "esp8266toEsp32.h" #define ST7789_DIMMER #endif @@ -258,11 +259,9 @@ void Arduino_ST7789::commonInit(const uint8_t *cmdList) { } if (_bp>=0) { -#define ESP32_PWM_CHANNEL 1 +// #define ESP32_PWM_CHANNEL 1 #ifdef ST7789_DIMMER - ledcSetup(ESP32_PWM_CHANNEL,4000,8); - ledcAttachPin(_bp,ESP32_PWM_CHANNEL); - ledcWrite(ESP32_PWM_CHANNEL,128); + analogWrite(_bp, 128); #else pinMode(_bp, OUTPUT); #endif @@ -575,7 +574,8 @@ void Arduino_ST7789::DisplayOnff(int8_t on) { writecommand(ST7789_DISPON); //Display on if (_bp>=0) { #ifdef ST7789_DIMMER - ledcWrite(ESP32_PWM_CHANNEL,dimmer); + analogWrite(_bp, dimmer); + // ledcWrite(ESP32_PWM_CHANNEL,dimmer); #else digitalWrite(_bp,HIGH); #endif @@ -584,7 +584,8 @@ void Arduino_ST7789::DisplayOnff(int8_t on) { writecommand(ST7789_DISPOFF); if (_bp>=0) { #ifdef ST7789_DIMMER - ledcWrite(ESP32_PWM_CHANNEL,0); + analogWrite(_bp, 0); + // ledcWrite(ESP32_PWM_CHANNEL,0); #else digitalWrite(_bp,LOW); #endif @@ -598,7 +599,8 @@ void Arduino_ST7789::dim(uint8_t dim) { if (dimmer>15) dimmer=15; dimmer=((float)dimmer/15.0)*255.0; #ifdef ESP32 - ledcWrite(ESP32_PWM_CHANNEL,dimmer); + analogWrite(_bp, dimmer); + // ledcWrite(ESP32_PWM_CHANNEL,dimmer); #endif } diff --git a/lib/lib_display/Display_Renderer-gemu-1.0/src/renderer.cpp b/lib/lib_display/Display_Renderer-gemu-1.0/src/renderer.cpp index e09ab71a8..d71a77368 100644 --- a/lib/lib_display/Display_Renderer-gemu-1.0/src/renderer.cpp +++ b/lib/lib_display/Display_Renderer-gemu-1.0/src/renderer.cpp @@ -59,10 +59,10 @@ uint16_t Renderer::GetColorFromIndex(uint8_t index) { void Renderer::dim(uint8_t contrast) { uint8_t contrast8 = ((uint32_t)contrast * 255) / 15; - dim8(contrast8, contrast8); + dim10(contrast8, contrast8 * 4); } -void Renderer::dim8(uint8_t contrast, uint8_t contrast_gamma) { +void Renderer::dim10(uint8_t contrast, uint16_t contrast_gamma) { } diff --git a/lib/lib_display/Display_Renderer-gemu-1.0/src/renderer.h b/lib/lib_display/Display_Renderer-gemu-1.0/src/renderer.h index 3e3304fbb..925b0ad7d 100644 --- a/lib/lib_display/Display_Renderer-gemu-1.0/src/renderer.h +++ b/lib/lib_display/Display_Renderer-gemu-1.0/src/renderer.h @@ -72,7 +72,7 @@ public: virtual void Begin(int16_t p1,int16_t p2,int16_t p3); virtual void Updateframe(); virtual void dim(uint8_t contrast); // input has range 0..15 - virtual void dim8(uint8_t contrast, uint8_t contrast_gamma); // input has range 0..255, second arg has gamma correction for PWM + virtual void dim10(uint8_t contrast, uint16_t contrast_gamma); // input has range 0..255, second arg has gamma correction for PWM with 10 bits resolution virtual void pushColors(uint16_t *data, uint16_t len, boolean first); virtual void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); virtual void invertDisplay(boolean i); diff --git a/lib/lib_display/ILI9341-gemu-1.0/ILI9341_2.cpp b/lib/lib_display/ILI9341-gemu-1.0/ILI9341_2.cpp index 4459eb957..2da8d4091 100644 --- a/lib/lib_display/ILI9341-gemu-1.0/ILI9341_2.cpp +++ b/lib/lib_display/ILI9341-gemu-1.0/ILI9341_2.cpp @@ -53,6 +53,7 @@ // ESP32 uses 2. SPI BUS, ESP8266 uses software spi #ifdef ESP32 +#include "esp8266toEsp32.h" #undef ILI9341_2_DIMMER #define ILI9341_2_DIMMER #undef ESP32_PWM_CHANNEL @@ -248,9 +249,7 @@ void ILI9341_2::init(uint16_t width, uint16_t height) { if (_bp >= 0) { #ifdef ILI9341_2_DIMMER - ledcSetup(ESP32_PWM_CHANNEL, 4000, 8); - ledcAttachPin(_bp, ESP32_PWM_CHANNEL); - ledcWrite(ESP32_PWM_CHANNEL, 128); + analogWrite(_bp, 511); #else pinMode(_bp, OUTPUT); #endif @@ -544,7 +543,8 @@ void ILI9341_2::DisplayOnff(int8_t on) { SPI_END_TRANSACTION(); if (_bp >= 0) { #ifdef ILI9341_2_DIMMER - ledcWrite(ESP32_PWM_CHANNEL, dimmer); + analogWrite(_bp, dimmer * 4); + // ledcWrite(ESP32_PWM_CHANNEL, dimmer); #else digitalWrite(_bp, HIGH); #endif @@ -557,7 +557,8 @@ void ILI9341_2::DisplayOnff(int8_t on) { SPI_END_TRANSACTION(); if (_bp >= 0) { #ifdef ILI9341_2_DIMMER - ledcWrite(ESP32_PWM_CHANNEL, 0); + analogWrite(_bp, 0); + // ledcWrite(ESP32_PWM_CHANNEL, 0); #else digitalWrite(_bp, LOW); #endif @@ -604,7 +605,8 @@ void ILI9341_2::dim(uint8_t dim) { dimmer=((float)dimmer/15.0)*255.0; #ifdef ESP32 if (_bp>=0) { - ledcWrite(ESP32_PWM_CHANNEL,dimmer); + analogWrite(_bp, dimmer * 4); + // ledcWrite(ESP32_PWM_CHANNEL,dimmer); } else { if (_hwspi>=2) { //ili9342_dimm(dim); diff --git a/lib/lib_display/UDisplay/uDisplay.cpp b/lib/lib_display/UDisplay/uDisplay.cpp index f6ac1a20b..4581cee9c 100755 --- a/lib/lib_display/UDisplay/uDisplay.cpp +++ b/lib/lib_display/UDisplay/uDisplay.cpp @@ -20,6 +20,10 @@ #include #include "uDisplay.h" +#ifdef ESP32 +#include "esp8266toEsp32.h" +#endif + // #define UDSP_DEBUG const uint16_t udisp_colors[]={UDISP_BLACK,UDISP_WHITE,UDISP_RED,UDISP_GREEN,UDISP_BLUE,UDISP_CYAN,UDISP_MAGENTA,\ @@ -428,9 +432,7 @@ Renderer *uDisplay::Init(void) { if (bpanel >= 0) { #ifdef ESP32 - ledcSetup(ESP32_PWM_CHANNEL, 977, 8); // use 10 bits resolution like in Light - ledcAttachPin(bpanel, ESP32_PWM_CHANNEL); - ledcWrite(ESP32_PWM_CHANNEL, 8); // 38/255 correspond roughly to 50% visual brighness (with Gamma) + analogWrite(bpanel, 32); #else pinMode(bpanel, OUTPUT); digitalWrite(bpanel, HIGH); @@ -1353,7 +1355,8 @@ void uDisplay::DisplayOnff(int8_t on) { if (dsp_on != 0xff) spi_command_one(dsp_on); if (bpanel >= 0) { #ifdef ESP32 - ledcWrite(ESP32_PWM_CHANNEL, dimmer8_gamma); + analogWrite(bpanel, dimmer10_gamma); + // ledcWrite(ESP32_PWM_CHANNEL, dimmer8_gamma); #else digitalWrite(bpanel, HIGH); #endif @@ -1363,7 +1366,8 @@ void uDisplay::DisplayOnff(int8_t on) { if (dsp_off != 0xff) spi_command_one(dsp_off); if (bpanel >= 0) { #ifdef ESP32 - ledcWrite(ESP32_PWM_CHANNEL, 0); + analogWrite(bpanel, 0); + // ledcWrite(ESP32_PWM_CHANNEL, 0); #else digitalWrite(bpanel, LOW); #endif @@ -1402,16 +1406,17 @@ void udisp_dimm(uint8_t dim); // } // dim is 0..255 -void uDisplay::dim8(uint8_t dim, uint8_t dim_gamma) { // dimmer with 8 bits resolution, 0..255. Gamma correction must be done by caller +void uDisplay::dim10(uint8_t dim, uint16_t dim_gamma) { // dimmer with 8 bits resolution, 0..255. Gamma correction must be done by caller dimmer8 = dim; - dimmer8_gamma = dim_gamma; + dimmer10_gamma = dim_gamma; if (ep_mode) { return; } #ifdef ESP32 // TODO should we also add a ESP8266 version for bpanel? if (bpanel >= 0) { // is the BaclPanel GPIO configured - ledcWrite(ESP32_PWM_CHANNEL, dimmer8_gamma); + analogWrite(bpanel, dimmer10_gamma); + // ledcWrite(ESP32_PWM_CHANNEL, dimmer8_gamma); } else if (dim_cbp) { dim_cbp(dim); } diff --git a/lib/lib_display/UDisplay/uDisplay.h b/lib/lib_display/UDisplay/uDisplay.h index 3dbc5aed7..b5eae65c5 100755 --- a/lib/lib_display/UDisplay/uDisplay.h +++ b/lib/lib_display/UDisplay/uDisplay.h @@ -74,9 +74,6 @@ enum uColorType { uCOLOR_BW, uCOLOR_COLOR }; #define SPI_DC_LOW if (spi_dc >= 0) GPIO_CLR_SLOW(spi_dc); #define SPI_DC_HIGH if (spi_dc >= 0) GPIO_SET_SLOW(spi_dc); - -#define ESP32_PWM_CHANNEL 1 - #define LUTMAXSIZE 64 class uDisplay : public Renderer { @@ -93,7 +90,7 @@ class uDisplay : public Renderer { uint16_t bgcol(void); int8_t color_type(void); // void dim(uint8_t dim); // original version with 4 bits resolution 0..15 - virtual void dim8(uint8_t dim, uint8_t dim_gamma); // dimmer with 8 bits resolution, 0..255. Gamma correction must be done by caller + virtual void dim10(uint8_t dim, uint16_t dim_gamma); // dimmer with 8 bits resolution, 0..255. Gamma correction must be done by caller with 10 bits resolution uint16_t GetColorFromIndex(uint8_t index); void setRotation(uint8_t m); void fillScreen(uint16_t color); @@ -186,7 +183,7 @@ class uDisplay : public Renderer { int8_t bpanel; // backbanel GPIO, -1 if none int8_t spi_miso; uint8_t dimmer8; // 8 bits resolution, 0..255 - uint8_t dimmer8_gamma; // 8 bits resolution, 0..255, gamma corrected + uint16_t dimmer10_gamma; // 10 bits resolution, 0..1023, gamma corrected SPIClass *uspi; uint8_t sspi; SPISettings spiSettings; diff --git a/lib/lib_div/lib_mail/src/wcs/esp8266/ESP_Mail_WCS.cpp b/lib/lib_div/lib_mail/src/wcs/esp8266/ESP_Mail_WCS.cpp index f24adaef3..d419a9249 100755 --- a/lib/lib_div/lib_mail/src/wcs/esp8266/ESP_Mail_WCS.cpp +++ b/lib/lib_div/lib_mail/src/wcs/esp8266/ESP_Mail_WCS.cpp @@ -50,7 +50,9 @@ extern "C" #include #include #include -#include +#ifdef ESP8266 + #include +#endif #include #if !CORE_MOCK diff --git a/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp b/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp index d714c4bd0..4028c4c5e 100755 --- a/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp +++ b/lib/lib_ssl/tls_mini/src/WiFiClientSecureLightBearSSL.cpp @@ -43,8 +43,8 @@ #include "lwip/netif.h" #ifdef ESP8266 #include + #include "c_types.h" #endif -#include "c_types.h" #include #undef DEBUG_TLS diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/c_types.h b/lib/libesp32/ESP32-to-ESP8266-compat/src/c_types.h deleted file mode 100644 index 22f551391..000000000 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/c_types.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -/**/ -#include -#ifndef ICACHE_FLASH_ATTR -#define ICACHE_FLASH_ATTR -#endif diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/eboot_command.h b/lib/libesp32/ESP32-to-ESP8266-compat/src/eboot_command.h deleted file mode 100644 index 992d014ea..000000000 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/eboot_command.h +++ /dev/null @@ -1,3 +0,0 @@ -// -// Compat with ESP32 -// diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp index fbead560a..69b0295ec 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.cpp @@ -13,58 +13,64 @@ along with this program. If not, see . */ -// + +#ifdef ESP32 + #include "Arduino.h" -//#include "lwip/apps/sntp.h" -#include - -// See libraries\ESP32\examples\ResetReason.ino -#if ESP_IDF_VERSION_MAJOR > 3 // IDF 4+ - #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 - #include "esp32/rom/rtc.h" - #elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 - #include "esp32s2/rom/rtc.h" - #elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 - #include "esp32c3/rom/rtc.h" - #else - #error Target CONFIG_IDF_TARGET is not supported - #endif -#else // ESP32 Before IDF 4.0 - #include "rom/rtc.h" -#endif - -#include #include "esp8266toEsp32.h" +#include "driver/ledc.h" + +// Tasmota Logging +extern void AddLog(uint32_t loglevel, PGM_P formatP, ...); +enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE}; // ESP Stuff +// replicated from `tasmota.h` +#if defined(CONFIG_IDF_TARGET_ESP32) + const uint8_t MAX_PWMS = 16; // ESP32: 16 ledc PWM channels in total - TODO for now +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + const uint8_t MAX_PWMS = 8; // ESP32S2: 8 ledc PWM channels in total +#elif defined(CONFIG_IDF_TARGET_ESP32C3) + const uint8_t MAX_PWMS = 6; // ESP32C3: 6 ledc PWM channels in total +#else + const uint8_t MAX_PWMS = 5; // Unknown - revert to 5 PWM max +#endif + +// channel mapping +static uint8_t pwm_channel[MAX_PWMS]; +static uint32_t pwm_frequency = 977; // Default 977Hz +static uint8_t pwm_bit_num = 10; // Default 1023 +static bool pwm_impl_inited = false; // trigger initialization + /*********************************************************************************************\ * ESP32 analogWrite emulation support \*********************************************************************************************/ -#if CONFIG_IDF_TARGET_ESP32C3 - uint8_t _pwm_channel[PWM_SUPPORTED_CHANNELS] = { 99, 99, 99, 99, 99, 99 }; - uint32_t _pwm_frequency = 977; // Default 977Hz - uint8_t _pwm_bit_num = 10; // Default 1023 -#else // other ESP32 - uint8_t _pwm_channel[PWM_SUPPORTED_CHANNELS] = { 99, 99, 99, 99, 99, 99, 99, 99 }; - uint32_t _pwm_frequency = 977; // Default 977Hz - uint8_t _pwm_bit_num = 10; // Default 1023 -#endif // CONFIG_IDF_TARGET_ESP32C3 vs ESP32 +void _analogInit(void) { + if (pwm_impl_inited) return; + // set all channels to unaffected (255) + for (uint32_t i = 0; i < MAX_PWMS; i++) { + pwm_channel[i] = 255; + } + pwm_impl_inited = true; +} -uint32_t _analog_pin2chan(uint32_t pin) { - for (uint32_t channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) { - if ((_pwm_channel[channel] < 99) && (_pwm_channel[channel] == pin)) { +int32_t _analog_pin2chan(uint32_t pin) { // returns -1 if uallocated + _analogInit(); // make sure the mapping array is initialized + for (uint32_t channel = 0; channel < MAX_PWMS; channel++) { + if ((pwm_channel[channel] < 255) && (pwm_channel[channel] == pin)) { return channel; } } - return 0; + return -1; } void _analogWriteFreqRange(void) { - for (uint32_t channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) { - if (_pwm_channel[channel] < 99) { - ledcSetup(channel + PWM_CHANNEL_OFFSET, _pwm_frequency, _pwm_bit_num); + _analogInit(); // make sure the mapping array is initialized + for (uint32_t channel = 0; channel < MAX_PWMS; channel++) { + if (pwm_channel[channel] < 255) { + ledcSetup(channel, pwm_frequency, pwm_bit_num); } } } @@ -80,50 +86,43 @@ uint32_t _analogGetResolution(uint32_t x) { } void analogWriteRange(uint32_t range) { - _pwm_bit_num = _analogGetResolution(range); + pwm_bit_num = _analogGetResolution(range); _analogWriteFreqRange(); } void analogWriteFreq(uint32_t freq) { - _pwm_frequency = freq; + pwm_frequency = freq; _analogWriteFreqRange(); } -bool analogAttach(uint32_t pin) { +int32_t analogAttach(uint32_t pin) { // returns ledc channel used, or -1 if failed + _analogInit(); // make sure the mapping array is initialized // Find if pin is already attached - uint32_t channel; - for (channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) { - if (_pwm_channel[channel] == pin) { - // Already attached - // Serial.printf("PWM: Already attached pin %d to channel %d\n", pin, channel); - return true; - } - } + int32_t channel = _analog_pin2chan(pin); + if (channel >= 0) { return channel; } // Find an empty channel - for (channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) { - if (99 == _pwm_channel[channel]) { - _pwm_channel[channel] = pin; - ledcAttachPin(pin, channel + PWM_CHANNEL_OFFSET); - ledcSetup(channel + PWM_CHANNEL_OFFSET, _pwm_frequency, _pwm_bit_num); + for (channel = 0; channel < MAX_PWMS; channel++) { + if (255 == pwm_channel[channel]) { + pwm_channel[channel] = pin; + ledcAttachPin(pin, channel); + ledcSetup(channel, pwm_frequency, pwm_bit_num); // Serial.printf("PWM: New attach pin %d to channel %d\n", pin, channel); - return true; + return channel; } } // No more channels available - return false; + AddLog(LOG_LEVEL_INFO, "PWM: no more PWM (ledc) channel for GPIO %i", pin); + return -1; } // void analogWrite(uint8_t pin, int val); -extern "C" void __wrap__Z11analogWritehi(uint8_t pin, int val) -{ - uint32_t channel = _analog_pin2chan(pin); - if ( val >> (_pwm_bit_num-1) ) ++val; - ledcWrite(channel + PWM_CHANNEL_OFFSET, val); - // Serial.printf("write %d - %d\n",channel,val); +extern "C" void __wrap__Z11analogWritehi(uint8_t pin, int val) { + analogWritePhase(pin, val, 0); // if unspecified, use phase = 0 } + /* - The primary goal of this library is to add phase control to PWM ledc + The primary goal of this function is to add phase control to PWM ledc functions. Phase control allows to stress less the power supply of LED lights. @@ -142,35 +141,28 @@ extern "C" void __wrap__Z11analogWritehi(uint8_t pin, int val) implementation changes. */ -#include "driver/ledc.h" - -#ifdef SOC_LEDC_SUPPORT_HS_MODE -#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1) -#else -#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM) -#endif - // exported from Arduno Core -extern uint8_t channels_resolution[LEDC_CHANNELS]; +extern uint8_t channels_resolution[MAX_PWMS]; void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase) { - uint32_t chan = _analog_pin2chan(pin) + PWM_CHANNEL_OFFSET; - if (duty >> (_pwm_bit_num-1) ) ++duty; - - if(chan >= LEDC_CHANNELS){ - return; + int32_t chan = _analog_pin2chan(pin); + if (chan < 0) { // not yet allocated, try to allocate + chan = analogAttach(pin); + if (chan < 0) { return; } // failed } + + if (duty >> (pwm_bit_num-1) ) ++duty; // input is 0..1023 but PWM takes 0..1024 - so we skip at mid-range. It creates a small non-linearity + if (phase >> (pwm_bit_num-1) ) ++phase; + uint8_t group=(chan/8), channel=(chan%8); //Fixing if all bits in resolution is set = LEDC FULL ON uint32_t max_duty = (1 << channels_resolution[chan]) - 1; - phase = phase % max_duty; - - if(duty == max_duty){ // no sure whether this is needed anymore TODO - duty = max_duty + 1; - } + phase = phase & max_duty; ledc_set_duty_with_hpoint((ledc_mode_t)group, (ledc_channel_t)channel, duty, phase); ledc_update_duty((ledc_mode_t)group, (ledc_channel_t)channel); } + +#endif // ESP32 diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h index adda5f2eb..b32e16bcf 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/esp8266toEsp32.h @@ -13,39 +13,21 @@ along with this program. If not, see . */ -#pragma once +#ifndef __ESP8266TOESP32_H__ +#define __ESP8266TOESP32_H__ + #ifdef ESP32 -// my debug Stuff -#define Serial_Debug1(p) Serial.printf p -#define Serial_DebugX(p) // // basics // -// dummy defines -//#define SPIFFS_END (SPI_FLASH_SEC_SIZE * 200) -//#define SETTINGS_LOCATION SPIFFS_END - #include -#if CONFIG_IDF_TARGET_ESP32C3 - #define PWM_SUPPORTED_CHANNELS 6 - #define PWM_CHANNEL_OFFSET 1 // Webcam uses channel 0, so we offset standard PWM -#else // other ESP32 - #define PWM_SUPPORTED_CHANNELS 8 - #define PWM_CHANNEL_OFFSET 2 // Webcam uses channel 0, so we offset standard PWM -#endif // CONFIG_IDF_TARGET_ESP32C3 vs ESP32 -extern uint8_t _pwm_channel[PWM_SUPPORTED_CHANNELS]; -extern uint32_t _pwm_frequency; -extern uint8_t _pwm_bit_num; - -void _analogWriteFreqRange(void); // input range is in full range, ledc needs bits -uint32_t _analogGetResolution(uint32_t x); void analogWriteRange(uint32_t range); void analogWriteFreq(uint32_t freq); -bool analogAttach(uint32_t pin); +int32_t analogAttach(uint32_t pin); // returns the ledc channel, or -1 if failed. This is implicitly called by analogWrite if the channel was not already allocated void analogWrite(uint8_t pin, int val); // Extended version that also allows to change phase @@ -56,8 +38,6 @@ extern void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase = 0); #define INPUT_PULLDOWN_16 INPUT_PULLUP -typedef double real64_t; - // // Time and Timer // @@ -71,7 +51,6 @@ typedef double real64_t; // Serial minimal type to hold the config typedef int SerConfu8; typedef int SerialConfig; -//#define analogWrite(a, b) // // UDP @@ -79,9 +58,6 @@ typedef int SerialConfig; //#define PortUdp_writestr(log_data) PortUdp.write((const uint8_t *)(log_data), strlen(log_data)) #define PortUdp_write(log_data, n) PortUdp.write((const uint8_t *)(log_data), n) -// -#define wifi_forceSleepBegin() - #undef LWIP_IPV6 #define REASON_DEFAULT_RST 0 // "Power on" normal startup by power on @@ -106,4 +82,5 @@ typedef int SerialConfig; #define STATION_IF 0 -#endif +#endif // ESP32 +#endif // __ESP8266TOESP32_H__ diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/osapi.h b/lib/libesp32/ESP32-to-ESP8266-compat/src/osapi.h deleted file mode 100644 index 947de57cc..000000000 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/osapi.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -/**/ -#include -/* -#ifndef ICACHE_FLASH_ATTR -#define ICACHE_FLASH_ATTR -#endif -*/ diff --git a/tasmota/settings.h b/tasmota/settings.h index c4be5aaee..8c2a25e41 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -474,7 +474,7 @@ typedef struct { TimeRule tflag[2]; // 2E2 uint16_t pwm_frequency; // 2E6 power_t power; // 2E8 - uint16_t pwm_value[MAX_PWMS]; // 2EC + uint16_t pwm_value[MAX_PWMS_LEGACY];// 2EC int16_t altitude; // 2F6 uint16_t tele_period; // 2F8 uint8_t display_rotate; // 2FA @@ -579,7 +579,7 @@ typedef struct { uint8_t ex_my_adc0; // 495 Free since 9.0.0.1 uint16_t light_pixels; // 496 - uint8_t light_color[5]; // 498 + uint8_t light_color[LST_MAX]; // 498 LST_MAX = 5 uint8_t light_correction; // 49D uint8_t light_dimmer; // 49E uint8_t rule_enabled; // 49F @@ -614,7 +614,8 @@ typedef struct { uint32_t ipv4_rgx_address; // 558 uint32_t ipv4_rgx_subnetmask; // 55C - uint8_t free_560[92]; // 560 + uint16_t pwm_value_ext[16-5]; // 560 Extension to pwm_value to store up to 16 PWM for ESP32. This array stores values 5..15 + uint8_t free_560[70]; // 576 SysMBitfield1 flag2; // 5BC uint32_t pulse_counter[MAX_COUNTERS]; // 5C0 diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 774b5dadf..a0cc34555 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -184,10 +184,11 @@ bool RtcRebootValid(void) { extern "C" { #include "spi_flash.h" } -#include "eboot_command.h" #ifdef ESP8266 +#include "eboot_command.h" + extern "C" uint32_t _FS_start; // 1M = 0x402fb000, 2M = 0x40300000, 4M = 0x40300000 const uint32_t FLASH_FS_START = (((uint32_t)&_FS_start - 0x40200000) / SPI_FLASH_SEC_SIZE); uint32_t SETTINGS_LOCATION = FLASH_FS_START -1; // 0xFA, 0x0FF or 0x0FF @@ -1094,7 +1095,7 @@ void SettingsDefaultSet2(void) { Settings->pwm_frequency = PWM_FREQ; Settings->pwm_range = PWM_RANGE; - for (uint32_t i = 0; i < MAX_PWMS; i++) { + for (uint32_t i = 0; i < LST_MAX; i++) { Settings->light_color[i] = DEFAULT_LIGHT_COMPONENT; // Settings->pwm_value[i] = 0; } diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index c93cae16b..7f191d7b9 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -1523,56 +1523,6 @@ void CmndTemplate(void) if (!error) { TemplateJson(); } } -void CmndPwm(void) -{ - if (TasmotaGlobal.pwm_present && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PWMS)) { - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= Settings->pwm_range) && PinUsed(GPIO_PWM1, XdrvMailbox.index -1)) { - Settings->pwm_value[XdrvMailbox.index -1] = XdrvMailbox.payload; - analogWrite(Pin(GPIO_PWM1, XdrvMailbox.index -1), bitRead(TasmotaGlobal.pwm_inverted, XdrvMailbox.index -1) ? Settings->pwm_range - XdrvMailbox.payload : XdrvMailbox.payload); - } - Response_P(PSTR("{")); - MqttShowPWMState(); // Render the PWM status to MQTT - ResponseJsonEnd(); - } -} - -void CmndPwmfrequency(void) -{ - if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= PWM_MIN) && (XdrvMailbox.payload <= PWM_MAX))) { - Settings->pwm_frequency = (1 == XdrvMailbox.payload) ? PWM_FREQ : XdrvMailbox.payload; - analogWriteFreq(Settings->pwm_frequency); // Default is 1000 (core_esp8266_wiring_pwm.c) -#ifdef USE_LIGHT - LightReapplyColor(); - LightAnimate(); -#endif // USE_LIGHT - } - ResponseCmndNumber(Settings->pwm_frequency); -} - -void CmndPwmrange(void) { - // Support only 8 (=255), 9 (=511) and 10 (=1023) bits resolution - if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload > 254) && (XdrvMailbox.payload < 1024))) { - uint32_t pwm_range = XdrvMailbox.payload; - uint32_t pwm_resolution = 0; - while (pwm_range) { - pwm_resolution++; - pwm_range >>= 1; - } - pwm_range = (1 << pwm_resolution) - 1; - uint32_t old_pwm_range = Settings->pwm_range; - Settings->pwm_range = (1 == XdrvMailbox.payload) ? PWM_RANGE : pwm_range; - for (uint32_t i = 0; i < MAX_PWMS; i++) { - if (Settings->pwm_value[i] > Settings->pwm_range) { - Settings->pwm_value[i] = Settings->pwm_range; - } - } - if (Settings->pwm_range != old_pwm_range) { // On ESP32 this prevents loss of duty state - analogWriteRange(Settings->pwm_range); // Default is 1023 (Arduino.h) - } - } - ResponseCmndNumber(Settings->pwm_range); -} - void CmndButtonDebounce(void) { if ((XdrvMailbox.payload > 39) && (XdrvMailbox.payload < 1001)) { diff --git a/tasmota/support_pwm.ino b/tasmota/support_pwm.ino new file mode 100644 index 000000000..82e4bdd7e --- /dev/null +++ b/tasmota/support_pwm.ino @@ -0,0 +1,273 @@ +/* + support_pwm.ino - command support for Tasmota + + Copyright (C) 2021 Theo Arends & Stephan Hadinger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +/***********************************************************************\ + * PWM Control for ESP32 +\***********************************************************************/ +#ifdef ESP32 + +// All changes in PWM have been applied, rearm all change indicators +void PwmRearmChanges(void) { + for (uint32_t i = 0; i < MAX_PWMS; i++) { + // Init expected changes + TasmotaGlobal.pwm_value[i] = -1; // no change wanted + TasmotaGlobal.pwm_phase[i] = -1; // no change wanted + } +} + +// Load PWM values from settings and intiliaze values +// void PwmLoadFromSettings(void) { +// for (uint32_t i = 0; i < MAX_PWMS; i++) { +// if (i < MAX_PWMS_LEGACY) { +// TasmotaGlobal.pwm_cur_value[i] = Settings->pwm_value[i]; // retrieve in Legacy pool for 0..4 +// } else { +// TasmotaGlobal.pwm_cur_value[i] = Settings->pwm_value_ext[i - MAX_PWMS_LEGACY]; // retrieve in Legacy pool for 5..15 +// } +// TasmotaGlobal.pwm_cur_phase[i] = 0; // no phase shift for now, will be recomputed at first push to GPIOs +// } +// PwmRearmChanges(); // reset expected changes +// } + +// Copy current values to Settings +void PwmSaveToSettings(void) { + for (uint32_t i = 0; i < MAX_PWMS; i++) { + if (i < MAX_PWMS_LEGACY) { + Settings->pwm_value[i] = TasmotaGlobal.pwm_cur_value[i]; // store in Legacy pool for 0..4 + } else { + Settings->pwm_value_ext[i - MAX_PWMS_LEGACY] = TasmotaGlobal.pwm_cur_value[i]; // retrieve in Legacy pool for 5..15 + } + } +} + +/***********************************************************************\ + * PWM Control for ESP32 +\***********************************************************************/ +// Apply PWM expected values to actual GPIO PWM +// As input, `TasmotaGlobal.pwm_value[]` and `TasmotaGlobal.pwm_phase[]` contain the new expected values +// or `-1` if no change. +// Auto-phasing is recomputed, and changes are applied to GPIO if there is a physical GPIO configured and an actual change needed +// +void PwmApplyGPIO(void) { + uint32_t pwm_phase_accumulator = 0; // dephase each PWM channel with the value of the previous + + for (uint32_t i = 0; i < MAX_PWMS; i++) { + // compute `pwm_val`, the virtual value of PWM (not taking into account inverted) + uint32_t pwm_val = TasmotaGlobal.pwm_cur_value[i]; // logical value of PWM, 0..1023 + if (TasmotaGlobal.pwm_value[i] >= 0) { pwm_val = TasmotaGlobal.pwm_value[i]; } // new value explicitly specified + if (pwm_val > Settings->pwm_range) { pwm_val = Settings->pwm_range; } // prevent overflow + + // gpio_val : actual value of GPIO, taking into account inversion + uint32_t gpio_val = bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - pwm_val : pwm_val; + + // compute phase + uint32_t pwm_phase = TasmotaGlobal.pwm_cur_phase[i]; // pwm_phase is the logical phase of the active pulse, ignoring inverted + uint32_t gpio_phase = pwm_phase; // gpio is the physical phase taking into account inverted + if (TasmotaGlobal.pwm_phase[i] >= 0) { + pwm_phase = TasmotaGlobal.pwm_phase[i]; // if explicit set explicitly, + } else if (Settings->flag5.pwm_force_same_phase) { + pwm_phase = 0; // if auto-phase is off + } else { + // compute auto-phase + pwm_phase = pwm_phase_accumulator; + uint32_t pwm_phase_invert = bitRead(TasmotaGlobal.pwm_inverted, i) ? pwm_val : 0; // move phase if inverted + gpio_phase = (pwm_phase + pwm_phase_invert) & Settings->pwm_range; + // accumulate phase for next GPIO + pwm_phase_accumulator = (pwm_phase + pwm_val) & Settings->pwm_range; + } + + // apply new values to GPIO if GPIO is set + if (PinUsed(GPIO_PWM1, i)) { + if ((pwm_val != TasmotaGlobal.pwm_cur_value[i]) || (pwm_phase != TasmotaGlobal.pwm_cur_phase[i])) { + // GPIO has PWM and there is a chnage to apply, apply it + analogWritePhase(Pin(GPIO_PWM1, i), gpio_val, gpio_phase); + } + } + + // set new current values + TasmotaGlobal.pwm_cur_value[i] = pwm_val; + TasmotaGlobal.pwm_cur_phase[i] = pwm_phase; + } + // AddLog(LOG_LEVEL_INFO, "PWM: Val=%03X-%03X-%03X-%03X-%03X Phase=%03X-%03X-%03X-%03X-%03X Range=%03X", + // TasmotaGlobal.pwm_cur_value[0], TasmotaGlobal.pwm_cur_value[1], TasmotaGlobal.pwm_cur_value[2], TasmotaGlobal.pwm_cur_value[3], + // TasmotaGlobal.pwm_cur_value[4], + // TasmotaGlobal.pwm_cur_phase[0], TasmotaGlobal.pwm_cur_phase[1], TasmotaGlobal.pwm_cur_phase[2], TasmotaGlobal.pwm_cur_phase[3], + // TasmotaGlobal.pwm_cur_phase[4], + // Settings->pwm_range + // ); + PwmSaveToSettings(); // copy to Settings + PwmRearmChanges(); // reset expected changes +} + +void CmndPwm(void) +{ + if (TasmotaGlobal.pwm_present && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PWMS)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= Settings->pwm_range) && PinUsed(GPIO_PWM1, XdrvMailbox.index -1)) { + TasmotaGlobal.pwm_value[XdrvMailbox.index - 1] = XdrvMailbox.payload; + PwmApplyGPIO(); + } + Response_P(PSTR("{")); + MqttShowPWMState(); // Render the PWM status to MQTT + ResponseJsonEnd(); + } +} + +void GpioInitPwm(void) { + PwmRearmChanges(); + + for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only + if (PinUsed(GPIO_PWM1, i)) { + analogAttach(Pin(GPIO_PWM1, i)); + if (i < TasmotaGlobal.light_type) { + // force PWM GPIOs to black + TasmotaGlobal.pwm_value[i] = 0; + } else { + TasmotaGlobal.pwm_present = true; + if (i < MAX_PWMS_LEGACY) { + TasmotaGlobal.pwm_value[i] = Settings->pwm_value[i]; + } else { + TasmotaGlobal.pwm_value[i] = Settings->pwm_value_ext[i - MAX_PWMS_LEGACY]; + } + } + } + } + PwmApplyGPIO(); // apply all changes +} + +/********************************************************************************************/ + +void ResetPwm(void) +{ + for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only + TasmotaGlobal.pwm_value[i] = 0; + } + PwmApplyGPIO(); +} + +#else // now for ESP8266 + +void PwmRearmChanges(void) {} + +void CmndPwm(void) +{ + if (TasmotaGlobal.pwm_present && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PWMS)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= Settings->pwm_range) && PinUsed(GPIO_PWM1, XdrvMailbox.index -1)) { + uint32_t pwm_index = XdrvMailbox.index - 1; + if (pwm_index < MAX_PWMS_LEGACY) { // write in the appropriate settings pool + Settings->pwm_value[pwm_index] = XdrvMailbox.payload; + } else { + Settings->pwm_value_ext[pwm_index - MAX_PWMS_LEGACY] = XdrvMailbox.payload; + } + analogWrite(Pin(GPIO_PWM1, pwm_index), bitRead(TasmotaGlobal.pwm_inverted, pwm_index) ? Settings->pwm_range - XdrvMailbox.payload : XdrvMailbox.payload); + } + Response_P(PSTR("{")); + MqttShowPWMState(); // Render the PWM status to MQTT + ResponseJsonEnd(); + } +} + +void GpioInitPwm(void) { + for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only + if (PinUsed(GPIO_PWM1, i)) { + pinMode(Pin(GPIO_PWM1, i), OUTPUT); + if (i < TasmotaGlobal.light_type) { + // force PWM GPIOs to low or high mode if belongs to the light (always <5), see #7165 + analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range : 0); + } else { + TasmotaGlobal.pwm_present = true; + if (i < MAX_PWMS_LEGACY) { + analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - Settings->pwm_value[i] : Settings->pwm_value[i]); + } else { + analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - Settings->pwm_value_ext[i] : Settings->pwm_value_ext[i]); + } + } + } + } +} + +/********************************************************************************************/ + +void ResetPwm(void) +{ + for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only + if (PinUsed(GPIO_PWM1, i)) { + analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range : 0); +// analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - Settings->pwm_value[i] : Settings->pwm_value[i]); + } + } +} + +#endif // ESP8266 + +void CmndPwmfrequency(void) +{ + if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= PWM_MIN) && (XdrvMailbox.payload <= PWM_MAX))) { + Settings->pwm_frequency = (1 == XdrvMailbox.payload) ? PWM_FREQ : XdrvMailbox.payload; + analogWriteFreq(Settings->pwm_frequency); // Default is 1000 (core_esp8266_wiring_pwm.c) +#ifdef USE_LIGHT + LightReapplyColor(); + LightAnimate(); +#endif // USE_LIGHT + } + ResponseCmndNumber(Settings->pwm_frequency); +} + +void CmndPwmrange(void) { + // Support only 8 (=255), 9 (=511) and 10 (=1023) bits resolution + if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload > 254) && (XdrvMailbox.payload < 1024))) { + uint32_t pwm_range = XdrvMailbox.payload; + uint32_t pwm_resolution = 0; + while (pwm_range) { + pwm_resolution++; + pwm_range >>= 1; + } + pwm_range = (1 << pwm_resolution) - 1; + uint32_t old_pwm_range = Settings->pwm_range; + Settings->pwm_range = (1 == XdrvMailbox.payload) ? PWM_RANGE : pwm_range; + for (uint32_t i = 0; i < MAX_PWMS; i++) { + if (i < MAX_PWMS_LEGACY) { + if (Settings->pwm_value[i] > Settings->pwm_range) { + Settings->pwm_value[i] = Settings->pwm_range; + } + } else { + if (Settings->pwm_value_ext[i - MAX_PWMS_LEGACY] > Settings->pwm_range) { + Settings->pwm_value_ext[i - MAX_PWMS_LEGACY] = Settings->pwm_range; + } + } + } + if (Settings->pwm_range != old_pwm_range) { // On ESP32 this prevents loss of duty state + analogWriteRange(Settings->pwm_range); // Default is 1023 (Arduino.h) + } + } + ResponseCmndNumber(Settings->pwm_range); +} + +void MqttShowPWMState(void) +{ + ResponseAppend_P(PSTR("\"" D_CMND_PWM "\":{")); + bool first = true; + for (uint32_t i = 0; i < MAX_PWMS; i++) { // TODO + if (PinUsed(GPIO_PWM1, i)) { + uint32_t pwm_val = (i < MAX_PWMS_LEGACY) ? Settings->pwm_value[i] : Settings->pwm_value_ext[i - MAX_PWMS_LEGACY]; + ResponseAppend_P(PSTR("%s\"" D_CMND_PWM "%d\":%d"), first ? "" : ",", i+1, pwm_val); + first = false; + } + } + ResponseJsonEnd(); +} diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index 5cfc2ad4f..78553c201 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -444,7 +444,7 @@ void SetLedPowerIdx(uint32_t led, uint32_t state) pwm = changeUIntScale((uint16_t)(state ? Settings->ledpwm_on : Settings->ledpwm_off), 0, 255, 0, Settings->pwm_range); // linear #endif //USE_LIGHT #ifdef ESP32 - if (analogAttach(Pin(GPIO_LED1, led))) + if (analogAttach(Pin(GPIO_LED1, led)) >= 0) #endif analogWrite(Pin(GPIO_LED1, led), bitRead(TasmotaGlobal.led_inverted, led) ? Settings->pwm_range - pwm : pwm); } else { @@ -735,19 +735,6 @@ void StopAllPowerBlink(void) } } -void MqttShowPWMState(void) -{ - ResponseAppend_P(PSTR("\"" D_CMND_PWM "\":{")); - bool first = true; - for (uint32_t i = 0; i < MAX_PWMS; i++) { - if (PinUsed(GPIO_PWM1, i)) { - ResponseAppend_P(PSTR("%s\"" D_CMND_PWM "%d\":%d"), first ? "" : ",", i+1, Settings->pwm_value[i]); - first = false; - } - } - ResponseJsonEnd(); -} - void MqttShowState(void) { char stemp1[TOPSZ]; @@ -1670,17 +1657,6 @@ void SerialInput(void) } } -/********************************************************************************************/ - -void ResetPwm(void) -{ - for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only - if (PinUsed(GPIO_PWM1, i)) { - analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range : 0); -// analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - Settings->pwm_value[i] : Settings->pwm_value[i]); - } - } -} /********************************************************************************************/ @@ -1825,7 +1801,7 @@ void GpioInit(void) mpin -= (AGPIO(GPIO_HEARTBEAT_INV) - AGPIO(GPIO_HEARTBEAT)); } else if ((mpin >= AGPIO(GPIO_PWM1_INV)) && (mpin < (AGPIO(GPIO_PWM1_INV) + MAX_PWMS))) { - bitSet(TasmotaGlobal.pwm_inverted, mpin - AGPIO(GPIO_PWM1_INV)); + bitSet(TasmotaGlobal.pwm_inverted, mpin - AGPIO(GPIO_PWM1_INV)); // PWMi are later converted to PMW, but marked as inverted in TasmotaGlobal.pwm_inverted mpin -= (AGPIO(GPIO_PWM1_INV) - AGPIO(GPIO_PWM1)); } else if (XdrvCall(FUNC_PIN_STATE)) { @@ -2002,23 +1978,7 @@ void GpioInit(void) #endif // USE_SONOFF_SC #endif // ESP8266 - for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only - if (PinUsed(GPIO_PWM1, i)) { -#ifdef ESP8266 - pinMode(Pin(GPIO_PWM1, i), OUTPUT); -#endif // ESP8266 -#ifdef ESP32 - analogAttach(Pin(GPIO_PWM1, i)); -#endif // ESP32 - if (TasmotaGlobal.light_type) { - // force PWM GPIOs to low or high mode, see #7165 - analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range : 0); - } else { - TasmotaGlobal.pwm_present = true; - analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - Settings->pwm_value[i] : Settings->pwm_value[i]); - } - } - } + GpioInitPwm(); for (uint32_t i = 0; i < MAX_RELAYS; i++) { if (PinUsed(GPIO_REL1, i)) { diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 150c6c729..3784e21d5 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -60,7 +60,21 @@ const uint8_t MAX_KEYS = 8; // Max number of keys or buttons (up const uint8_t MAX_INTERLOCKS_SET = 14; // Max number of interlock groups (MAX_RELAYS / 2) const uint8_t MAX_SWITCHES_SET = 28; // Max number of switches const uint8_t MAX_LEDS = 4; // Max number of leds -const uint8_t MAX_PWMS = 5; // Max number of PWM channels +const uint8_t MAX_PWMS_LEGACY = 5; // Max number of PWM channels in first settings block - Legacy limit for ESP8266, but extended for ESP32 (see below) +#ifdef ESP32 + // Max number of PWM channels (total including extended) - ESP32 only + #if defined(CONFIG_IDF_TARGET_ESP32) + const uint8_t MAX_PWMS = 16; // ESP32: 16 ledc PWM channels in total - TODO for now + #elif defined(CONFIG_IDF_TARGET_ESP32S2) + const uint8_t MAX_PWMS = 8; // ESP32S2: 8 ledc PWM channels in total + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + const uint8_t MAX_PWMS = 6; // ESP32C3: 6 ledc PWM channels in total + #else + const uint8_t MAX_PWMS = 5; // Unknown - revert to 5 PWM max + #endif +#else + const uint8_t MAX_PWMS = 5; // (not used on ESP8266) +#endif const uint8_t MAX_COUNTERS = 4; // Max number of counter sensors const uint8_t MAX_TIMERS = 16; // Max number of Timers const uint8_t MAX_PULSETIMERS = 8; // Max number of supported pulse timers diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index a208a44bc..d46e3cf4e 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -154,6 +154,13 @@ struct TasmotaGlobal_t { bool enable_logging; // Enable logging StateBitfield global_state; // Global states (currently Wifi and Mqtt) (8 bits) + uint16_t pwm_inverted; // PWM inverted flag (1 = inverted) - extended to 16 bits for ESP32 +#ifdef ESP32 + int16_t pwm_cur_value[MAX_PWMS]; // Current effective values of PWMs as applied to GPIOs + int16_t pwm_cur_phase[MAX_PWMS]; // Current phase values of PWMs as applied to GPIOs + int16_t pwm_value[MAX_PWMS]; // Wanted values of PWMs after update - -1 means no change + int16_t pwm_phase[MAX_PWMS]; // Wanted phase of PWMs after update - -1 means no change +#endif // ESP32 uint8_t init_state; // Tasmota init state uint8_t heartbeat_inverted; // Heartbeat pulse inverted flag uint8_t spi_enabled; // SPI configured @@ -172,7 +179,7 @@ struct TasmotaGlobal_t { uint8_t led_inverted; // LED inverted flag (1 = (0 = On, 1 = Off)) uint8_t led_power; // LED power state uint8_t ledlnk_inverted; // Link LED inverted flag (1 = (0 = On, 1 = Off)) - uint8_t pwm_inverted; // PWM inverted flag (1 = inverted) + // uint8_t pwm_inverted; // PWM inverted flag (1 = inverted) -- TODO uint8_t energy_driver; // Energy monitor configured uint8_t light_driver; // Light module configured uint8_t light_type; // Light types diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h index 86efb5c41..5ccb040d3 100644 --- a/tasmota/tasmota_template.h +++ b/tasmota/tasmota_template.h @@ -448,8 +448,8 @@ const uint16_t kGpioNiceList[] PROGMEM = { AGPIO(GPIO_CNTR1) + MAX_COUNTERS, // Counters AGPIO(GPIO_CNTR1_NP) + MAX_COUNTERS, #endif - AGPIO(GPIO_PWM1) + MAX_PWMS, // RGB Red or C Cold White - AGPIO(GPIO_PWM1_INV) + MAX_PWMS, + AGPIO(GPIO_PWM1) + MAX_PWMS, // RGB Red or C Cold White + AGPIO(GPIO_PWM1_INV) + MAX_PWMS, // or extended PWM for ESP32 #ifdef USE_BUZZER AGPIO(GPIO_BUZZER), // Buzzer AGPIO(GPIO_BUZZER_INV), // Inverted buzzer diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index 09181bfbb..f7448dc77 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -227,7 +227,7 @@ struct LIGHT { uint8_t old_power = 1; uint8_t wakeup_active = 0; // 0=inctive, 1=on-going, 2=about to start, 3=will be triggered next cycle uint8_t fixed_color_index = 1; - uint8_t pwm_offset = 0; // Offset in color buffer + uint8_t pwm_offset = 0; // Offset in color buffer, used by sm16716 to drive itself RGB, and PWM for CCT (value is 0 or 3) uint8_t max_scheme = LS_MAX -1; uint32_t wakeup_start_time = 0; @@ -1030,7 +1030,7 @@ bool LightModuleInit(void) TasmotaGlobal.light_type = LT_BASIC; // Use basic PWM control if SetOption15 = 0 if (Settings->flag.pwm_control) { // SetOption15 - Switch between commands PWM or COLOR/DIMMER/CT/CHANNEL - for (uint32_t i = 0; i < MAX_PWMS; i++) { + for (uint32_t i = 0; i < LST_MAX; i++) { if (PinUsed(GPIO_PWM1, i)) { TasmotaGlobal.light_type++; } // Use Dimmer/Color control for all PWM as SetOption15 = 1 } } @@ -1164,9 +1164,7 @@ void LightInit(void) #ifdef ESP8266 pinMode(Pin(GPIO_PWM1, i), OUTPUT); #endif // ESP8266 -#ifdef ESP32 - analogAttach(Pin(GPIO_PWM1, i)); -#endif // ESP32 + // For ESP32, the PWM are already attached by GpioInit() - GpioInitPwm() } } if (PinUsed(GPIO_ARIRFRCV)) { @@ -2087,10 +2085,6 @@ void LightApplyPower(uint8_t new_color[LST_MAX], power_t power) { void LightSetOutputs(const uint16_t *cur_col_10) { // now apply the actual PWM values, adjusted and remapped 10-bits range if (TasmotaGlobal.light_type < LT_PWM6) { // only for direct PWM lights, not for Tuya, Armtronix... -#ifdef ESP32 - uint32_t pwm_phase = 0; // dephase each PWM channel with the value of the previous - uint32_t pwm_modulus = (1 << _pwm_bit_num) - 1; // 1023 -#endif // ESP32 #ifdef USE_PWM_DIMMER uint16_t max_col = 0; @@ -2116,17 +2110,18 @@ void LightSetOutputs(const uint16_t *cur_col_10) { cur_col = cur_col > 0 ? changeUIntScale(cur_col, 0, Settings->pwm_range, Light.pwm_min, Light.pwm_max) : 0; // shrink to the range of pwm_min..pwm_max } if (!Settings->flag4.zerocross_dimmer) { - uint32_t pwm_val = bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - cur_col : cur_col; #ifdef ESP32 - uint32_t pwm_phase_invert = bitRead(TasmotaGlobal.pwm_inverted, i) ? cur_col : 0; // move phase if inverted - analogWritePhase(Pin(GPIO_PWM1, i), pwm_val, Settings->flag5.pwm_force_same_phase ? 0 : (pwm_phase + pwm_phase_invert) & pwm_modulus); - pwm_phase = (pwm_phase + cur_col) & pwm_modulus; + TasmotaGlobal.pwm_value[i] = cur_col; // mark the new expected value #else // ESP32 - analogWrite(Pin(GPIO_PWM1, i), pwm_val); + analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - cur_col : cur_col); #endif // ESP32 } } } +#ifdef ESP32 + PwmApplyGPIO(); +#endif // ESP32 + #ifdef USE_PWM_DIMMER // Animate brightness LEDs to follow PWM dimmer brightness if (PWM_DIMMER == TasmotaGlobal.module_type) PWMDimmerSetBrightnessLeds(change10to8(max_col)); diff --git a/tasmota/xdrv_13_display.ino b/tasmota/xdrv_13_display.ino index ffded1fde..2b60c7e4d 100755 --- a/tasmota/xdrv_13_display.ino +++ b/tasmota/xdrv_13_display.ino @@ -1989,7 +1989,7 @@ void CmndDisplayMode(void) { // Apply the current display dimmer void ApplyDisplayDimmer(void) { uint8_t dimmer8 = changeUIntScale(GetDisplayDimmer(), 0, 100, 0, 255); - uint8_t dimmer8_gamma = ledGamma(dimmer8); + uint16_t dimmer10_gamma = ledGamma10(dimmer8); if (dimmer8 && !(disp_power)) { ExecuteCommandPower(disp_device, POWER_ON, SRC_DISPLAY); } @@ -1997,7 +1997,7 @@ void ApplyDisplayDimmer(void) { ExecuteCommandPower(disp_device, POWER_OFF, SRC_DISPLAY); } if (renderer) { - renderer->dim8(dimmer8, dimmer8_gamma); // provide 8 bits and gamma corrected dimmer in 8 bits + renderer->dim10(dimmer8, dimmer10_gamma); // provide 8 bits and gamma corrected dimmer in 8 bits #ifdef USE_BERRY // still call Berry virtual display in case it is not managed entirely by renderer Xdsp18(FUNC_DISPLAY_DIM); diff --git a/tasmota/xdrv_24_buzzer.ino b/tasmota/xdrv_24_buzzer.ino index d7b8ef0f3..3921589f3 100644 --- a/tasmota/xdrv_24_buzzer.ino +++ b/tasmota/xdrv_24_buzzer.ino @@ -50,12 +50,9 @@ void BuzzerSet(uint32_t state) { if (Settings->flag4.buzzer_freq_mode) { // SetOption111 - Enable frequency output mode for buzzer static uint8_t last_state = 0; if (last_state != state) { -#ifdef ESP32 - if (analogAttach(Pin(GPIO_BUZZER))) -#endif // ESP32 - // Set 50% duty cycle for frequency output - // Set 0% (or 100% for inverted PWM) duty cycle which turns off frequency output either way - analogWrite(Pin(GPIO_BUZZER), (state) ? Settings->pwm_range / 2 : 0); // set duty cycle for frequency output + // Set 50% duty cycle for frequency output + // Set 0% (or 100% for inverted PWM) duty cycle which turns off frequency output either way + analogWrite(Pin(GPIO_BUZZER), (state) ? Settings->pwm_range / 2 : 0); // set duty cycle for frequency output last_state = state; } } else { diff --git a/tasmota/xdrv_52_3_berry_webclient.ino b/tasmota/xdrv_52_3_berry_webclient.ino index 625c54622..71b0aaab4 100644 --- a/tasmota/xdrv_52_3_berry_webclient.ino +++ b/tasmota/xdrv_52_3_berry_webclient.ino @@ -325,10 +325,17 @@ extern "C" { int32_t wc_tcp_read(struct bvm *vm); int32_t wc_tcp_read(struct bvm *vm) { WiFiClient * tcp = wc_getwificlient(vm); + int32_t max_read = -1; // by default read as much as we can + if (be_top(vm) >= 2 && be_isint(vm, 2)) { + max_read = be_toint(vm, 2); + } int32_t btr = tcp->available(); if (btr <= 0) { be_pushstring(vm, ""); } else { + if ((max_read >= 0) && (btr > max_read)) { + btr = max_read; + } char * buf = (char*) be_pushbuffer(vm, btr); int32_t btr2 = tcp->read((uint8_t*) buf, btr); be_pushnstring(vm, buf, btr2); @@ -340,10 +347,17 @@ extern "C" { int32_t wc_tcp_readbytes(struct bvm *vm); int32_t wc_tcp_readbytes(struct bvm *vm) { WiFiClient * tcp = wc_getwificlient(vm); + int32_t max_read = -1; // by default read as much as we can + if (be_top(vm) >= 2 && be_isint(vm, 2)) { + max_read = be_toint(vm, 2); + } int32_t btr = tcp->available(); if (btr <= 0) { be_pushbytes(vm, nullptr, 0); } else { + if ((max_read >= 0) && (btr > max_read)) { + btr = max_read; + } uint8_t * buf = (uint8_t*) be_pushbuffer(vm, btr); int32_t btr2 = tcp->read(buf, btr); be_pushbytes(vm, buf, btr2); diff --git a/tasmota/xdrv_81_esp32_webcam.ino b/tasmota/xdrv_81_esp32_webcam.ino index b89bc7930..b89bde902 100644 --- a/tasmota/xdrv_81_esp32_webcam.ino +++ b/tasmota/xdrv_81_esp32_webcam.ino @@ -219,12 +219,6 @@ uint32_t WcSetup(int32_t fsiz) { //esp_log_level_set("*", ESP_LOG_VERBOSE); camera_config_t config; - config.ledc_channel = LEDC_CHANNEL_0; - config.ledc_timer = LEDC_TIMER_0; - config.xclk_freq_hz = 20000000; - config.pixel_format = PIXFORMAT_JPEG; -// config.pixel_format = PIXFORMAT_GRAYSCALE; -// config.pixel_format = PIXFORMAT_RGB565; if (WcPinUsed()) { config.pin_d0 = Pin(GPIO_WEBCAM_DATA); // Y2_GPIO_NUM; @@ -265,8 +259,17 @@ uint32_t WcSetup(int32_t fsiz) { config.pin_reset = RESET_GPIO_NUM; AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Default template")); } + + int32_t ledc_channel = analogAttach(config.pin_xclk); + if (ledc_channel < 0) { + AddLog(LOG_LEVEL_ERROR, "CAM: cannot allocated ledc cahnnel, remove a PWM GPIO"); + } + config.ledc_channel = (ledc_channel_t) ledc_channel; + AddLog(LOG_LEVEL_DEBUG_MORE, "CAM: XCLK on GPIO %i using ledc channel %i", config.pin_xclk, config.ledc_channel); + config.ledc_timer = LEDC_TIMER_0; + config.xclk_freq_hz = 20000000; + config.pixel_format = PIXFORMAT_JPEG; - //ESP.getPsramSize() //esp_log_level_set("*", ESP_LOG_INFO);