Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PWM enhancements (phase shifting and dithering) #4115

Closed
wants to merge 3 commits into from

Conversation

DedeHai
Copy link

@DedeHai DedeHai commented Aug 25, 2024

  • using dithering allows for longer pulse times if not FET driver is used. In current configuration, on the slowest speed setting the minimum on-time is 400ns, which is enough time to drive a FET directly from a GPIO. The down side of using dithering: the PWM frequency drops for the lowest duty-cycle values (minimum frequency is basefrequency/(1<<ditheringbits)
  • phase shifting is added to distribute the load better over one period. the algorithm used is very simple and does not perfectly distribute the signals in all cases but it is not too bad for a nice-to-have feature.
  • for CCT strips, phase is shifted 180° and a dead-time is added to support back-to-back configurations (using a H-bridge driver)

TODO:

  • dithering is only tested with 4bits, less may be possible
  • the way the frequency settings are used is just a POC
  • need a proper way to enable/disable phase shift as well as dithering

Using dithering allows for longer pulse times if not FET driver is used. In current configuration, on the slowest speed setting the minimum on-time is 400ns, which is enough time to drive a FET directly from a GPIO.
The down side of using dithering: the PWM frequency drops for the lowest duty-cycle values (minimum frequency is basefrequency/(1<<ditheringbits)
phase shifting is added to distribute the load better over one period. the algorithm used is very simple and does not perfectly distribute the signals in all cases but it is not too bad for a nice-to-have feature.
TODO:
-dithering is only tested with 4bits, less may be possible
-the way the frequency settings are used is just a POC
-need a proper way to enable/disable phase shift as well as dithering
Copy link
Collaborator

@blazoncek blazoncek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks quite complicated. 😄
I think you could forego phase shifting for anything but PWM CCT. And simplify that to always shift 180°.

wled00/bus_manager.cpp Outdated Show resolved Hide resolved
wled00/bus_manager.cpp Show resolved Hide resolved
wled00/bus_manager.cpp Show resolved Hide resolved
@DedeHai
Copy link
Author

DedeHai commented Aug 25, 2024

Looks quite complicated. 😄 I think you could forego phase shifting for anything but PWM CCT. And simplify that to always shift 180°.

your call, the code is already there :)
adding the phase shift is getting the timers in sync (3 lines of code) and calculating the offset (4 lines of code).
The CCT offset right now is calculated such that the pulses are best distributed (middle of off time of first pulse) i.e. 180°

wled00/bus_manager.cpp Outdated Show resolved Hide resolved
@blazoncek
Copy link
Collaborator

I've updated the PR to include changes in PinManager.
I do not understand why did you change PWM frequency logic in the constructor. There are odd and unexplainable values in the switch statement. Can you elaborate?

@DedeHai
Copy link
Author

DedeHai commented Aug 27, 2024

I dont fully get what you mean.
I left the original calculation as changing that somehow broke proper saving/loading cfg in my tests and used the saved frequency values to calculate new ones, as the comment states: just a POC.
Instead of calculating and saving the frequency, I would save the selection directly and calculate the frequency where it is used (in the show() function).
what odd and unexplainable values are you referring to?

Copy link
Collaborator

@blazoncek blazoncek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bc.frequency uses "slowest", "slow", "normal", "fast" and "fastest" monikers that relate to pre-determined WLED_PWM_FREQ frequencies. Why did you choose to change that logic/pre-determined values?
Is it necessary for dithering? If so there will need to be an explanation why WLED_PWM_FREQ is no longer respected as a "normal" frequency (it is twice that now).

I have not yet digested show() logic. But will do that ASAP.

default:
case WLED_PWM_FREQ : _frequency = WLED_PWM_FREQ*2; break; // fast, 40kHz, 8bit + 4bit dithering
case WLED_PWM_FREQ*2 : _frequency = WLED_PWM_FREQ*3; break; // ultra fast, 60kHz, 8bit + 4bit dithering
case WLED_PWM_FREQ*10/3 : _frequency = WLED_PWM_FREQ*4/3; _depth = 10; ditheringbits = 0; break; // no dithering, 26kHz, 10bit
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am referring to this line.

@DedeHai
Copy link
Author

DedeHai commented Aug 27, 2024

The frequencies chosen are preliminary and just a suggestion from my side.
When dithering (4bit) is used, the lowest 16 PWM values reduce the frequency as the minimum pulse width is set by the 8bit 'actual' PWM pulse. At the lowest value of 1, the frequency drops by a factor of 16 (I see minimal flickering in slowest mode on 60FPS phone camera as the light is really dim in that setting, I read that you need at least 30x the FPS rate of the camera in order to get it flicker free, not sure how accurate that is).

In my tests using a FET module (dual AOD514 from Ali) I found that a GPIO can not drive it any faster than a 400ns pulse, below that, the output would not switch on (still need to get some IRLZ44n to check how they perform).
So I chose frequencies that respect that minimum pulse (slowest and slow setting). In general, even when using a decent driver, pulses below 50ns are hard to do with power NFETs (unless you go for GaN or SiC technology but that is some advanced stuff).

These are the chosen settings:

  • Slow, 8bit with dithering, 10kHz (400ns minimum pulse time)
  • Medium, 7bit with 4bit dithering, 20kHz (400ns minimum pulse time)
  • Fast, 8bit with 4bit dithering, 40kHz (100ns minimum pulse time)
  • Ultra Fast, 8bit with 4bit dithering, 60kHz (65ns minimum pulse time)
  • Fixed Fast: 10bit NO dithering, 26kHz (40ns minimum pulse time)

the last one could also be dropped to 20kHz i.e. the WLED_PWM_FREQ (I just chose it to be different so I could use it to ID that mode as I had no other variable available).

I would also suggest a renaming of "slowest", "slow", "normal", "fast" and "fastest" but I have no good suggestions. Since I expect few users to read any documention on the new PWM settings, it should be clear what they are intended for.

I am open for suggestions on frequencies and bit depth / dithering bits (I assume also 2 and 3 bit dithering will work with some minor modifications).

@blazoncek
Copy link
Collaborator

When dithering (4bit) is used, the lowest 16 PWM values reduce the frequency as the minimum pulse width is set by the 8bit 'actual' PWM pulse. At the lowest value of 1, the frequency drops by a factor of 16

I am not sure I follow. Let me try to explain how I see "dithering" (that will extend pulse width fo our purposes).

Having 12 bit resolution means there are 4096 different pulse widths possible (minus 2 if we take 0 and 4095 out). Each pulse (regardless of its width) happens within a period of PWM frequency (i.e. 19531 Hz or 51.2 us). The shortest pulse will therefore be 12.5 ns.
Since not all FETs are made equal (some cannot handle such short widths/steep rises) we want to "extend" those widths without loosing "perceived" smoothness of brightness steps introduced by lower bit depth (8 bits will yield minimum pulse width of 200 ns). How can we achieve that? By using persistence of human eye and reducing the bit depth but alternating the least important bit in time. As the eye will average out those on/off alternation of the lowest bit, the perceived brightness will be less than the bit depth would allow.
All this happens regardless of the chosen PWM frequency and only depends on the bit depth and timed distribution of lowest bit alternations. The downside is that we need 16 cycles to reproduce those 16 lost possible values by using 4 bits less for depth.

Is my understanding so far correct? If yes, then I see no point in changing the frequency as we already "extended" pulse width to a reasonable width by reducing bit depth (if your FETs are still too slow, you can use slower frequencies from the chosen list). So it is just a matter of correctly alternating that lowest bit in time to achieve "perceived" resolution of 12 instead of 8 bits.

FYI I have 2 IRLZ44N on my desk, attached to 2 5V 3W analog LEDs WW & CW. I am using level shifter for driving FETs and they perform adequately at 12 bit at 19531 Hz. I can see brightness changes down to global brightness of 1.

@blazoncek
Copy link
Collaborator

As a solution for your other problem (UI option) I would go with "Use dithering" checkbox.
If enabled that would force 8 bit depth (regardless of the chosen frequency) but would use "dithered" output.

@DedeHai
Copy link
Author

DedeHai commented Aug 28, 2024

your understanding seems correct. let me put in other words:
the dithering is a temporal dithering, any 8bit value is extended to 12bits by dividing the 8bit pulse into 16 (the number of 'spaces' between two 8 bit values) time slots. if the 'missing' bits for example are '5' out of 16, 5 of the 16 pulses will be increased by one 8-bit minimum pulse length. This then his averages out to 5/16th of a pulse length hence preserving the full resolution (perceived).
you are right that we could keep the current frequency values, but I chose to change them such that the slowest settings result in a 400ns pulse (for the FETs I have at hand, even with a levelshifter the minimum pulse was 250ns, anything below that was just the same as 0, defeating the 12bit resolution which mostly helps in the lowest levels)
We need to keep in mind that to the average user 'dithering' means nothing. As I mentioned, my approacht would be to have 5 or 6 settings, that are like 'presets' for different setups, the users will choose what looks best for them (if they even care) for whatever setup they have.
My approach is not the best in versatility though.

@blazoncek
Copy link
Collaborator

We need to keep in mind that to the average user 'dithering' means nothing.

There is a simple answer for that: If your LEDs turn off at higher brightness levels (i.e. too soon) use dithering or try lower frequencies or both.

@blazoncek
Copy link
Collaborator

Temporal, that was the word I was searching for. 😄

@blazoncek
Copy link
Collaborator

but I chose to change them such that the slowest settings result in a 400ns pulse

The "slowest" frequency already is half of WLED_PWM_FREQ which is 9765 Hz that will produce 400 ns pulse with 8 bit resolution. Or does it not?

@DedeHai
Copy link
Author

DedeHai commented Aug 28, 2024

slowest is indeed the same. I just got some IRLZ44n in the mail today btw, will check how they perform.
I have no objections on keeping the frequencies and adding a dithering option, my point is just that we could pre-define useful settings and make it less complicated. The slow 10kHz setting with dithering and no driver is absolutely fine for lighting, just might not be for fast moving setups or video lighting with high frame-rates. Just remember: WS2812 do PWM at 400Hz.

@blazoncek
Copy link
Collaborator

As I've commented on Discord I finally understand how this should work. Let me explain.

We should talk about 2 different things: phase shifting and dithering.

Lets start with phase shifting.
Phase shifting is mandatory for reverse polarity CCT strips that need a H-bridge to work. For all other situations it may be useful but is not obligatory. It also gets complicated to calculate appropriate phase shifts the more GPIOs you have. So I would reserve phase shifting for CCT only strips and always do 180° shift. One prerequisite is that CCT blending has to be 0 as otherwise WW & CW signals may overlap which is forbidden for H-bridge. We can add a bit of dead time when individual channel brightness reaches 50% if we cannot properly sync timers.

Dithering.
After studying Espressif documentation there really is nothing to it beside the fact that it's hidden behind writing directly to LEDC registers. As such I think this should be an option in UI for user to select (instead of phase shift which shoul always be enabled for CCT). When selected the only difference to current implementation (using ledcWrite()) is to reduce the calculated _depth to 8 bits (fixed) and shift pwmBri by <<4 when not using dithering when writing to duty register.
This approach simplifies logic tremedously by only having 2 or perhaps 3 single statement ifs. Frequency should (and will) remain as is. The only exception may be redefinition of WLED_PWM_FREQ if the fastest frequency (10/3) cannot produce output at lowest brightness (too short pulse). Unfortunately I do not own a scope or logic analyzer so will depend on external verification.

I will update bus-config branch with the above some time later.

@blazoncek
Copy link
Collaborator

Closed in favor of #4126

@blazoncek blazoncek closed this Sep 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants