From c0beb621e26a5aea017fe20f2f501175a1e33474 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 7 Mar 2024 20:21:56 +0100 Subject: [PATCH] Better low brightness level PWM handling Additional string optimisation --- wled00/bus_manager.cpp | 77 +++++++++++++++++++++++++++--------------- wled00/bus_manager.h | 4 +-- wled00/cfg.cpp | 16 ++++++--- wled00/remote.cpp | 2 +- wled00/set.cpp | 8 ++--- wled00/xml.cpp | 8 ++--- 6 files changed, 70 insertions(+), 45 deletions(-) mode change 100755 => 100644 wled00/bus_manager.cpp diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp old mode 100755 new mode 100644 index d78ed60d0..3ac12c04e --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -376,14 +376,22 @@ BusPwm::BusPwm(BusConfig &bc) _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; #ifdef ESP8266 - analogWriteRange(255); //same range as one RGB channel + // duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth + if (_frequency > 1760) _depth = 8; + else if (_frequency > 880) _depth = 9; + else _depth = 10; // WLED_PWM_FREQ <= 880Hz + analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); #else _ledcStart = pinManager.allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels deallocatePins(); return; } - //_prevBri = _bri; + // duty cycle resolution (_depth) can be extracted from this formula: 80MHz > _frequency * 2^_depth + if (_frequency > 78124) _depth = 9; + else if (_frequency > 39062) _depth = 10; + else if (_frequency > 19531) _depth = 11; + else _depth = 12; // WLED_PWM_FREQ <= 19531Hz #endif for (unsigned i = 0; i < numPins; i++) { @@ -395,14 +403,6 @@ BusPwm::BusPwm(BusConfig &bc) #ifdef ESP8266 pinMode(_pins[i], OUTPUT); #else - switch (_frequency) { - case WLED_PWM_FREQ/3: _depth = 12; break; // 6510Hz - case WLED_PWM_FREQ/2: _depth = 11; break; // 9676Hz - default: - case WLED_PWM_FREQ: _depth = 10; break; // 19531Hz - case WLED_PWM_FREQ*4/3: _depth = 9; break; // 26041Hz - case WLED_PWM_FREQ*2: _depth = 8; break; // 39062Hz - } ledcSetup(_ledcStart + i, _frequency, _depth); ledcAttachPin(_pins[i], _ledcStart + i); #endif @@ -411,13 +411,6 @@ BusPwm::BusPwm(BusConfig &bc) _valid = true; } -void BusPwm::setBrightness(uint8_t bri) { - #ifdef ARDUINO_ARCH_ESP32 - //_prevBri = _bri; - #endif - Bus::setBrightness(bri); -} - void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); @@ -477,24 +470,52 @@ uint32_t BusPwm::getPixelColor(uint16_t pix) { return RGBW32(_data[0], _data[1], _data[2], _data[3]); } +#ifndef ESP8266 +static const uint16_t cieLUT[256] = { + 0, 2, 4, 5, 7, 9, 11, 13, 15, 16, + 18, 20, 22, 24, 26, 27, 29, 31, 33, 35, + 34, 36, 37, 39, 41, 43, 45, 47, 49, 52, + 54, 56, 59, 61, 64, 67, 69, 72, 75, 78, + 81, 84, 87, 90, 94, 97, 100, 104, 108, 111, + 115, 119, 123, 127, 131, 136, 140, 144, 149, 154, + 158, 163, 168, 173, 178, 183, 189, 194, 200, 205, + 211, 217, 223, 229, 235, 241, 247, 254, 261, 267, + 274, 281, 288, 295, 302, 310, 317, 325, 333, 341, + 349, 357, 365, 373, 382, 391, 399, 408, 417, 426, + 436, 445, 455, 464, 474, 484, 494, 505, 515, 526, + 536, 547, 558, 569, 580, 592, 603, 615, 627, 639, + 651, 663, 676, 689, 701, 714, 727, 741, 754, 768, + 781, 795, 809, 824, 838, 853, 867, 882, 897, 913, + 928, 943, 959, 975, 991, 1008, 1024, 1041, 1058, 1075, + 1092, 1109, 1127, 1144, 1162, 1180, 1199, 1217, 1236, 1255, + 1274, 1293, 1312, 1332, 1352, 1372, 1392, 1412, 1433, 1454, + 1475, 1496, 1517, 1539, 1561, 1583, 1605, 1628, 1650, 1673, + 1696, 1719, 1743, 1767, 1791, 1815, 1839, 1864, 1888, 1913, + 1939, 1964, 1990, 2016, 2042, 2068, 2095, 2121, 2148, 2176, + 2203, 2231, 2259, 2287, 2315, 2344, 2373, 2402, 2431, 2461, + 2491, 2521, 2551, 2581, 2612, 2643, 2675, 2706, 2738, 2770, + 2802, 2835, 2867, 2900, 2934, 2967, 3001, 3035, 3069, 3104, + 3138, 3174, 3209, 3244, 3280, 3316, 3353, 3389, 3426, 3463, + 3501, 3539, 3576, 3615, 3653, 3692, 3731, 3770, 3810, 3850, + 3890, 3930, 3971, 4012, 4053, 4095 +}; +#endif + void BusPwm::show() { if (!_valid) return; uint8_t numPins = NUM_PWM_PINS(_type); + unsigned maxBri = (1<<_depth) - 1; + #ifdef ESP8266 + unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri + 0.5f)); // using gamma 1.7 to extrapolate PWM duty cycle + #else + unsigned pwmBri = cieLUT[_bri] >> (12 - _depth); // use CIE LUT + #endif for (unsigned i = 0; i < numPins; i++) { + unsigned scaled = (_data[i] * pwmBri) / 255; + if (_reversed) scaled = maxBri - scaled; #ifdef ESP8266 - uint8_t scaled = (_data[i] * _bri) / 255; - if (_reversed) scaled = 255 - scaled; analogWrite(_pins[i], scaled); #else - unsigned scaled = ((_data[i] * _bri) << (_depth-8)) / 255; - if (_reversed) scaled = (1<<_depth) - 1 - scaled; - // TODO: implement ledcFade() if the brightness changed between calls - // bool ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms); - // if (_prevBri != _bri) { - // unsigned prevScaled = ((_data[i] * _prevBri) << (_depth-8)) / 255; - // ledcFade(_ledcStart + i, prevScaled, scaled, FRAMETIME-1); // frametime is a macro expanding to strip.getFrameTime() which is unwanted here - // _prevBri = _bri; - // } else ledcWrite(_ledcStart + i, scaled); #endif } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index fd7b647e1..0b791adf3 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -255,7 +255,6 @@ class BusPwm : public Bus { BusPwm(BusConfig &bc); ~BusPwm() { cleanup(); } - void setBrightness(uint8_t bri) override; void setPixelColor(uint16_t pix, uint32_t c) override; uint32_t getPixelColor(uint16_t pix) override; //does no index check uint8_t getPins(uint8_t* pinArray) override; @@ -268,9 +267,8 @@ class BusPwm : public Bus { uint8_t _pwmdata[5]; #ifdef ARDUINO_ARCH_ESP32 uint8_t _ledcStart; - uint8_t _depth; - //uint8_t _prevBri; #endif + uint8_t _depth; uint16_t _frequency; void deallocatePins(); diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index c528e0640..e51b666e4 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -180,7 +180,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; bool reversed = elm["rev"]; bool refresh = elm["ref"] | false; - uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully) + uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY; uint8_t maPerLed = elm[F("ledma")] | 55; uint16_t maMax = elm[F("maxpwr")] | (ablMilliampsMax * length) / total; // rough (incorrect?) per strip ABL calculation when no config exists @@ -627,6 +627,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { return (doc["sv"] | true); } + +static const char s_cfg_json[] PROGMEM = "/cfg.json"; + void deserializeConfigFromFS() { bool success = deserializeConfigSec(); if (!success) { //if file does not exist, try reading from EEPROM @@ -640,7 +643,7 @@ void deserializeConfigFromFS() { DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); - success = readObjectFromFile(PSTR("/cfg.json"), nullptr, pDoc); + success = readObjectFromFile(s_cfg_json, nullptr, pDoc); if (!success) { // if file does not exist, optionally try reading from EEPROM and then save defaults to FS releaseJSONBufferLock(); #ifdef WLED_ADD_EEPROM_SUPPORT @@ -1065,7 +1068,7 @@ void serializeConfig() { JsonObject usermods_settings = root.createNestedObject("um"); usermods.addToConfig(usermods_settings); - File f = WLED_FS.open(SET_F("/cfg.json"), "w"); + File f = WLED_FS.open(FPSTR(s_cfg_json), "w"); if (f) serializeJson(root, f); f.close(); releaseJSONBufferLock(); @@ -1073,13 +1076,16 @@ void serializeConfig() { doSerializeConfig = false; } + +static const char s_wsec_json[] PROGMEM = "/wsec.json"; + //settings in /wsec.json, not accessible via webserver, for passwords and tokens bool deserializeConfigSec() { DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); if (!requestJSONBufferLock(3)) return false; - bool success = readObjectFromFile(PSTR("/wsec.json"), nullptr, pDoc); + bool success = readObjectFromFile(s_wsec_json, nullptr, pDoc); if (!success) { releaseJSONBufferLock(); return false; @@ -1162,7 +1168,7 @@ void serializeConfigSec() { ota[F("lock-wifi")] = wifiLock; ota[F("aota")] = aOtaEnabled; - File f = WLED_FS.open(SET_F("/wsec.json"), "w"); + File f = WLED_FS.open(FPSTR(s_wsec_json), "w"); if (f) serializeJson(root, f); f.close(); releaseJSONBufferLock(); diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 95f817a36..49fbc4b02 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -127,7 +127,7 @@ static bool remoteJson(int button) JsonObject fdo = pDoc->as(); if (fdo.isNull()) { // the received button does not exist - //if (!WLED_FS.exists(SET_F("/remote.json"))) errorFlag = ERR_FS_RMLOAD; //warn if file itself doesn't exist + //if (!WLED_FS.exists(F("/remote.json"))) errorFlag = ERR_FS_RMLOAD; //warn if file itself doesn't exist releaseJSONBufferLock(); return parsed; } diff --git a/wled00/set.cpp b/wled00/set.cpp index 489204b06..52ec1ea43 100755 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -167,12 +167,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) uint16_t freq = request->arg(sp).toInt(); if (IS_PWM(type)) { switch (freq) { - case 0 : freq = WLED_PWM_FREQ/3; break; - case 1 : freq = WLED_PWM_FREQ/2; break; + case 0 : freq = WLED_PWM_FREQ/2; break; + case 1 : freq = WLED_PWM_FREQ*2/3; break; default: case 2 : freq = WLED_PWM_FREQ; break; - case 3 : freq = WLED_PWM_FREQ*4/3; break; - case 4 : freq = WLED_PWM_FREQ*2; break; + case 3 : freq = WLED_PWM_FREQ*2; break; + case 4 : freq = WLED_PWM_FREQ*4; break; } } else if (IS_DIGITAL(type) && IS_2PIN(type)) { switch (freq) { diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 7c25f05bd..65bcef997 100755 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -402,12 +402,12 @@ void getSettingsJS(byte subPage, char* dest) uint16_t speed = bus->getFrequency(); if (IS_PWM(bus->getType())) { switch (speed) { - case WLED_PWM_FREQ/3 : speed = 0; break; - case WLED_PWM_FREQ/2 : speed = 1; break; + case WLED_PWM_FREQ/2 : speed = 0; break; + case WLED_PWM_FREQ*2/3 : speed = 1; break; default: case WLED_PWM_FREQ : speed = 2; break; - case WLED_PWM_FREQ*4/3 : speed = 3; break; - case WLED_PWM_FREQ*2 : speed = 4; break; + case WLED_PWM_FREQ*2 : speed = 3; break; + case WLED_PWM_FREQ*4 : speed = 4; break; } } else if (IS_DIGITAL(bus->getType()) && IS_2PIN(bus->getType())) { switch (speed) {