-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
Thanks to @DedeHai & @zalatnaicsongor
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
#include <IPAddress.h> | ||
#ifdef ARDUINO_ARCH_ESP32 | ||
#include "driver/ledc.h" | ||
#include "soc/ledc_struct.h" | ||
#endif | ||
#include "const.h" | ||
#include "pin_manager.h" | ||
|
@@ -392,7 +393,7 @@ void BusDigital::cleanup(void) { | |
#define MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM | ||
#else | ||
// ESP32: 20 bit (but in reality we would never go beyond 16 bit as the frequency would be to low) | ||
#define MAX_BIT_WIDTH 20 | ||
#define MAX_BIT_WIDTH 14 | ||
#endif | ||
#endif | ||
|
||
|
@@ -413,11 +414,13 @@ BusPwm::BusPwm(BusConfig &bc) | |
analogWriteRange((1<<_depth)-1); | ||
analogWriteFreq(_frequency); | ||
#else | ||
// for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer | ||
_ledcStart = pinManager.allocateLedc(numPins); | ||
if (_ledcStart == 255) { //no more free LEDC channels | ||
pinManager.deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); | ||
return; | ||
} | ||
if (_needsRefresh) _depth = 8; // fixed 8 bit depth with 4 bit dithering (ESP8266 has no hardware to support dithering) | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
blazoncek
Author
Collaborator
|
||
#endif | ||
|
||
for (unsigned i = 0; i < numPins; i++) { | ||
|
@@ -501,38 +504,62 @@ uint32_t BusPwm::getPixelColor(uint16_t pix) const { | |
void BusPwm::show() { | ||
if (!_valid) return; | ||
const unsigned numPins = getPins(); | ||
const unsigned maxBri = (1<<_depth); | ||
|
||
// use CIE brightness formula | ||
unsigned pwmBri = (unsigned)_bri * 100; | ||
if (pwmBri < 2040) | ||
pwmBri = ((pwmBri << _depth) + 115043) / 230087; //adding '0.5' before division for correct rounding | ||
else { | ||
const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8) | ||
|
||
// use CIE brightness formula to fit (or approximate linearity of) human eye perceived brightness | ||
// the formula is based on 12 bit resolution as there is no need for greater precision | ||
unsigned pwmBri = (unsigned)_bri * 100; // enlarge to use integer math for linear response | ||
if (pwmBri < 2040) { | ||
// linear response for values [0-20] | ||
pwmBri = ((pwmBri << 12) + 115043) / 230087; //adding '0.5' before division for correct rounding | ||
} else { | ||
// cubic response for values [21-255] | ||
pwmBri += 4080; | ||
float temp = (float)pwmBri / 29580.0f; | ||
temp = temp * temp * temp * maxBri; | ||
temp = temp * temp * temp * 4095.0f; | ||
This comment has been minimized.
Sorry, something went wrong.
DedeHai
|
||
pwmBri = (unsigned)temp; | ||
} | ||
// pwmBri is in range [0-4095] | ||
|
||
// determine phase shift | ||
[[maybe_unused]] unsigned phaseOffset = maxBri / numPins; // (maxBri is at _depth resolution) | ||
This comment has been minimized.
Sorry, something went wrong.
DedeHai
|
||
// we will be phase shifting every channel by fixed amount (i times /2 or /3 or /4 or /5) | ||
// phase shifting is only mandatory when using H-bridge to drive reverse-polarity PWM CCT (2 wire) LED type (with 180° phase) | ||
// CCT additive blending must be 0 (WW & CW must not overlap) in such case | ||
// for all other cases it will just try to "spread" the load on PSU | ||
|
||
for (unsigned i = 0; i < numPins; i++) { | ||
unsigned scaled = (_data[i] * pwmBri) / 255; | ||
if (_reversed) scaled = maxBri - scaled; | ||
// adjust "scaled" value (to fit resolution bounds) | ||
if (_depth < 12 && !_needsRefresh) scaled >>= 12 - _depth; // normalize scaled value (if not using dithering) | ||
else if (_depth > 12) scaled <<= _depth - 12; // scale to _depth if using >12 bit | ||
if (_reversed) scaled = maxBri - scaled; | ||
|
||
#ifdef ESP8266 | ||
analogWrite(_pins[i], scaled); | ||
#else | ||
unsigned channel = _ledcStart + i; | ||
// determine phase shift POC for PWM CCT (credit @dedehai) | ||
// phase shifting (180°) is only available for PWM CCT LED type if _needsRefresh is true (UI hack) | ||
// and CCT blending is 0 (WW & CW must not overlap) | ||
// this will allow using H-bridge to drive reverse-polarity CCT LED strip (2 wires) | ||
// NOTE/TODO: if this has no side effects we may forego UI hack and the need for _needsRefresh | ||
// we may even use phase shift to evenly distribute power across different pins | ||
if (_type == TYPE_ANALOG_2CH && _needsRefresh && Bus::getCCTBlend() == 0) { // hacked to determine if phase shifted PWM is requested | ||
unsigned maxDuty = (maxBri / numPins); // numPins is 2 | ||
if (scaled >= maxDuty) scaled = maxDuty - 1; // safety check & add dead time of 1 pulse when brightness is at 50% | ||
ledc_set_duty_and_update((ledc_mode_t)(channel / 8), (ledc_channel_t)(channel % 8), scaled, maxDuty*i); | ||
} else | ||
ledcWrite(channel, scaled); | ||
if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) { | ||
// pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer | ||
unsigned briLimit = phaseOffset << (_needsRefresh*4); // expand limit if using dithering (_depth==8, scaled is at 12 bit) | ||
if (scaled >= briLimit) scaled = briLimit - 1; // safety check & 1 pulse dead time when brightness is at 50% | ||
This comment has been minimized.
Sorry, something went wrong.
DedeHai
|
||
} | ||
unsigned gr = channel/8; // high/low speed group | ||
unsigned ch = channel%8; // group channel | ||
if (_needsRefresh) { | ||
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) | ||
// https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) | ||
// directly write to LEDC struct as there is no HAL exposed function for dithering | ||
// duty has 20 bit resolution with 4 fractional bits (24 bits in total) | ||
// _depth is 8 bit in this case (and maxBri==256), scaled is still at 12 bit | ||
LEDC.channel_group[gr].channel[ch].duty.duty = scaled; // write full 12 bit value (4 dithering bits) | ||
LEDC.channel_group[gr].channel[ch].hpoint.hpoint = phaseOffset*i; // phaseOffset is at _depth resolution (8 bit) | ||
ledc_update_duty((ledc_mode_t)gr, (ledc_channel_t)ch); | ||
} else { | ||
// scaled will be [0-((1<<_depth)-1)] and hpoint evenly distributed | ||
ledc_set_duty_and_update((ledc_mode_t)gr, (ledc_channel_t)ch, scaled, phaseOffset*i); | ||
This comment has been minimized.
Sorry, something went wrong.
DedeHai
|
||
//ledcWrite(channel, scaled); | ||
} | ||
#endif | ||
} | ||
} | ||
|
2 comments
on commit 0d035a0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a working version now with everything fixed (at least the basic stuff works, did not test all possible configurations).
Do you want me to make a PR with all fixes for review?
comments/changes:
- adding actual 180° phase shift requires some pre-calculations like in my original code. it can be done but it is not really necessary. currently the two CCT signals are cascaded, on RGB or RGBW it is somwhat random due to unsynced timers.
- dead-time calculation was incorrect, especially with dithering (there are 4 pulses of 8bit resolution required to avoid overlap)
- maxBri calculation was wrong with dithering (resulting in bogus values when inverting the signal)
- increased 'pwmBri' max value to 4096 (ensures full on, with 4095 there is a single pulse where signal goes low)
- phase offset calculation fixed
- CCT with zero blend now also goes to 100% (like with normal CCT strips), also see inline comment with screenshots
- removed the (non working) setting of dutycycle without dithering, now dutycycle is always set accessing the ledc struct directly
bugs that remain:
- when saving the settings, the output jumps to (almost ) full brightness every time, even though in the web UI I set a low brightness before
- the bug I once mentioned is still there: during transitions, the PWM value is not calculated correctly. If I change brightness from 5 to 1 using transition, this is what I get in
BusPwm::show()
:
Normal mode, no dithering (12bit) *:
duty = 9
duty = 6
duty = 4
duty = 3
duty = 1 (this would be correct for brightness = 1)
duty = 2 (this is the final value that is set)
This behaviour can be clearly seen in the light as well (and the jump happens in all transitions, not only the lowest values)
*with 'duty' I am referring to the 'scaled' value that is written to ledc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two bugs you describe were present before these changes so they must come from elsewhere.
Let me push the changes I did yesterday and then open a PR.
how is the actual pwm bit depth calculated? if that is still done the same way it was before this will not work I i think. i.e. if ledc setup is set to anything besides 12 bits (you set the bits written to ledc fixed to 12 bits below if I understand correctly)