How to tweak the brightness => delay (linearised or not)
mathieucarbou opened this issue · 8 comments
I would like to tweak how the delay is generated based on the level value (0-255).
I saw the polynomial equation but fail to understand where it comes from and how to tweak it.
I did a sample of all possible values with the current algorithm:
// newLevel: | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15| 16| 17| 18| 19| 20| 21| 22| 23| 24| 25| 26| 27| 28| 29| 30| 31| 32| 33| 34| 35| 36| 37| 38| 39| 40| 41| 42| 43| 44| 45| 46| 47| 48| 49| 50| 51| 52| 53| 54| 55| 56| 57| 58| 59| 60| 61| 62| 63| 64| 65| 66| 67| 68| 69| 70| 71| 72| 73| 74| 75| 76| 77| 78| 79| 80| 81| 82| 83| 84| 85| 86| 87| 88| 89| 90| 91| 92| 93| 94| 95| 96| 97| 98| 99| 100| 101| 102| 103| 104| 105| 106| 107| 108| 109| 110| 111| 112| 113| 114| 115| 116| 117| 118| 119| 120| 121| 122| 123| 124| 125| 126| 127| 128| 129| 130| 131| 132| 133| 134| 135| 136| 137| 138| 139| 140| 141| 142| 143| 144| 145| 146| 147| 148| 149| 150| 151| 152| 153| 154| 155| 156| 157| 158| 159| 160| 161| 162| 163| 164| 165| 166| 167| 168| 169| 170| 171| 172| 173| 174| 175| 176| 177| 178| 179| 180| 181| 182| 183| 184| 185| 186| 187| 188| 189| 190| 191| 192| 193| 194| 195| 196| 197| 198| 199| 200| 201| 202| 203| 204| 205| 206| 207| 208| 209| 210| 211| 212| 213| 214| 215| 216| 217| 218| 219| 220| 221| 222| 223| 224| 225| 226| 227| 228| 229| 230| 231| 232| 233| 234| 235| 236| 237| 238| 239| 240| 241| 242| 243| 244| 245| 246| 247| 248| 249| 250| 251| 252| 253| 254| 255|
// newDelay1: |10000| 9961| 9922| 9883| 9844| 9804| 9765| 9726| 9687| 9648| 9608| 9569| 9530| 9491| 9451| 9412| 9373| 9334| 9295| 9255| 9216| 9177| 9138| 9099| 9059| 9020| 8981| 8942| 8902| 8863| 8824| 8785| 8746| 8706| 8667| 8628| 8589| 8550| 8510| 8471| 8432| 8393| 8353| 8314| 8275| 8236| 8197| 8157| 8118| 8079| 8040| 8000| 7961| 7922| 7883| 7844| 7804| 7765| 7726| 7687| 7648| 7608| 7569| 7530| 7491| 7451| 7412| 7373| 7334| 7295| 7255| 7216| 7177| 7138| 7099| 7059| 7020| 6981| 6942| 6902| 6863| 6824| 6785| 6746| 6706| 6667| 6628| 6589| 6550| 6510| 6471| 6432| 6393| 6353| 6314| 6275| 6236| 6197| 6157| 6118| 6079| 6040| 6000| 5961| 5922| 5883| 5844| 5804| 5765| 5726| 5687| 5648| 5608| 5569| 5530| 5491| 5451| 5412| 5373| 5334| 5295| 5255| 5216| 5177| 5138| 5099| 5059| 5020| 4981| 4942| 4902| 4863| 4824| 4785| 4746| 4706| 4667| 4628| 4589| 4550| 4510| 4471| 4432| 4393| 4353| 4314| 4275| 4236| 4197| 4157| 4118| 4079| 4040| 4000| 3961| 3922| 3883| 3844| 3804| 3765| 3726| 3687| 3648| 3608| 3569| 3530| 3491| 3451| 3412| 3373| 3334| 3295| 3255| 3216| 3177| 3138| 3099| 3059| 3020| 2981| 2942| 2902| 2863| 2824| 2785| 2746| 2706| 2667| 2628| 2589| 2550| 2510| 2471| 2432| 2393| 2353| 2314| 2275| 2236| 2197| 2157| 2118| 2079| 2040| 2000| 1961| 1922| 1883| 1844| 1804| 1765| 1726| 1687| 1648| 1608| 1569| 1530| 1491| 1451| 1412| 1373| 1334| 1295| 1255| 1216| 1177| 1138| 1099| 1059| 1020| 981| 942| 902| 863| 824| 785| 746| 706| 667| 628| 589| 550| 510| 471| 432| 393| 353| 314| 275| 236| 197| 157| 118| 79| 40| 0|
// newDelay2: | 9984| 9837| 9695| 9557| 9425| 9297| 9173| 9054| 8939| 8827| 8720| 8617| 8517| 8421| 8329| 8240| 8154| 8071| 7992| 7915| 7841| 7770| 7702| 7636| 7573| 7512| 7454| 7397| 7343| 7291| 7241| 7193| 7147| 7102| 7060| 7018| 6979| 6941| 6904| 6869| 6834| 6802| 6770| 6739| 6710| 6681| 6654| 6627| 6601| 6576| 6552| 6529| 6506| 6484| 6462| 6441| 6420| 6400| 6380| 6361| 6342| 6323| 6305| 6287| 6269| 6251| 6234| 6216| 6199| 6182| 6165| 6148| 6131| 6114| 6097| 6080| 6063| 6046| 6029| 6012| 5995| 5977| 5960| 5942| 5925| 5907| 5889| 5870| 5852| 5834| 5815| 5796| 5777| 5758| 5738| 5719| 5699| 5679| 5659| 5639| 5618|
with the normal dimmer, here are the matching brightness values to set to be at a apecific power level (in terms of watts):
// newDelay1:
// 25% power: 73 (29%)
// 50% power: 106 (42%)
// 75% power: 146 (57%)
same for the linearised version:
// newDelay2:
// 25% power: 33 (13%)
// 50% power: 95 (37%)
// 75% power: 163 (64%)
I would like to tweak (maybe much more the second version) in order to better match the power output to the resistive load.
This would be equivalent to have a function which, depending on the value, output the right voltage value maybe.
(reopening)
These are the lines I would like to understand how to adjust them:
double tempBrightness;
if (Thyristor::getFrequency() == 50) {
tempBrightness = -1.5034e-10 * pow(bri, 5) + 9.5843e-08 * pow(bri, 4)
- 2.2953e-05 * pow(bri, 3) + 0.0025471 * pow(bri, 2) - 0.14965 * bri + 9.9846;
} else if (Thyristor::getFrequency() == 60) {
tempBrightness = -1.2528e-10 * pow(bri, 5) + 7.9866e-08 * pow(bri, 4)
- 1.9126e-05 * pow(bri, 3) + 0.0021225 * pow(bri, 2) - 0.12471 * bri + 8.3201;
Hi Mathieu, very briefly, I had estimated them using Octave and they approximate the inverse of formula to compute energy of a sine wave. The idea was to use a polynomial formula instead of the common lookup table to save memory on Arduino Uno trading off computational time... I'm not sure if this is still meaningful with modern MCU.
Can you tell which sensor you use to measure the output power? At the time, I had used a commercial a smart plug, and the result seemed reasonable, however I'm not sure about the reliability of that device...
You are touching all the points I would have to improve... Unfortunately, I have few issues to solve outside here, but I guarantee I will come back on all of these.
Hello,
So I am measuring the output power with a PZEM004Tv3, multimeter (RMS support) and oscilloscope (Owen HD).
I've also drawn a comparison of 3 implementations here:
https://docs.google.com/spreadsheets/d/1dCpAydu3WHekbXEdUZt53acCa6aSjtCBxW6lvv89Ku0/edit#gid=0
- V1 is from a LUT from StackOverflow (angles for power 0-100)
- V2 is Dimmable Light normal version
- V3 is the linearised one, which I am using
V3 is really the best that is closest to reality, but it could be improved:
- at 50% or less, I have 5V more on the multimeter than when computing the RMS voltage based on the delay.
- at 75%, this is only 3V
- after, the multimeter shows exactly the same Vrms as the one computed based.
For the LUT, I have converted all the values to a LUT table. I am only using levels from 0-100, not 0-255 to have less data and to save memory I am using PROGMEM.
I have 4 LUT per version:
- delay for 50Hz
- delay for 60Hz
- Vrms factor for 50Hz
- Vrms factor for 60Hz
=> I am multiplying this factor by the input measured voltage to get the dimmed voltage (Vrms)
So basically, the idea would be to shift the curve a little for the smaller values. I really don't know how to achieve that.
For example, at 25% on the slider, I have:
"angle_deg": 113,
"angle_rad": 1.975119352,
"delay": 6287,
"level": 25,
"vrms_factor": 0.506137013,
"voltage_in": 208.6000061,
"voltage_out": 105.5801849,
But to be closer to reality (111V), I should have instead the values that are shown at around 28%:
"angle_deg": 110,
"angle_rad": 1.931451201,
"delay": 6148,
"level": 28,
"vrms_factor": 0.529244006,
"voltage_in": 209.5,
"voltage_out": 110.8766174,
Note: this is normal that my grid voltage is low: the EV car is charging ;-)
Here are some data:
level | Output Vrms calculated based on input voltage | Output Vrms on multimeter | diff |
---|---|---|---|
2% | 10V | 5V | -5V |
4% | 24.5V | 29V | -5V |
16% | 90.5V | 94.5V | - 5V |
25% | 111V | 115V | - 4V |
35% | 126V | 130V | - 4V |
50% | 152.5V | 156.5V | - 4V |
60% | 169.5V | 172.5V | - 3V |
70% | 183.5V | 185.5V | - 2V |
80% | 195V | 195V | 0V |
90% | 207V | 207V | 0V |
update:
I did another test today, more targetted at some values, but this time using the exact brigthness values you have.
BRI 3 4 10 40 128 220 250
V 0 -5 -3 -2.5 -3 0 -1
BRI is the value tested (0-255)
V is the difference : Vrms calculated - multimeter (the multimeter always show a higher value)
I've chose these value based on this graph:
- https://www.desmos.com/calculator/llwqitrjck
- https://docs.google.com/spreadsheets/d/1dCpAydu3WHekbXEdUZt53acCa6aSjtCBxW6lvv89Ku0/edit#gid=0
V1 is calculating an angle with the formula:
return acos(2 * level / 255.0 - 1);
So I think a quick and dirty adjustment would be to compute the Vrms from a level value that is shifted, but for example +3 for lower values, then +2, then +1, then 0.
Example: if I want the vrms of level=4 (out of 255), then I should get level - 7.
Not sure the algo can be changed because if the algo changes, than consequently the delay changes, and the vrms follows.
So it is like I would need a different algo to grab the right vrms.
Here is the algo I used to test and apply the shift.
Shifting by +2 works well for 40 and 128. But not after.
I need to fund a function which is applying a shift decreasingly, and not linearly.
+3 from 4 to 10, +2 from 11 to 175 (?), etc...
uint16_t Mycila::Dimmer::_lookupFiringDelay(uint8_t level, float frequency) {
if (level == 0)
return 500000 / frequency; // semi-period in microseconds
if (level == MYCILA_DIMMER_MAX_LEVEL)
return 0;
// // https://github.com/fabianoriccardi/dimmable-light/blob/main/src/dimmable_light_linearized.h
if (frequency == 60)
return -1.2528e-7 * pow(level, 5) + 7.9866e-5 * pow(level, 4) - 1.9126e-2 * pow(level, 3) + 2.1225 * powf(level, 2) - 124.71 * level + 8320.1;
else
return -1.5034e-7 * pow(level, 5) + 9.5843e-5 * pow(level, 4) - 2.2953e-2 * pow(level, 3) + 2.5471 * pow(level, 2) - 149.65 * level + 9984.6;
}
float Mycila::Dimmer::_lookupVrmsFactor(uint8_t level, float frequency) {
if (level == 0)
return 0;
if (level > MYCILA_DIMMER_MAX_LEVEL - 4)
return 1;
level += 2;
const uint16_t delay = _lookupFiringDelay(level, frequency);
const float angle = Mycila::Dimmer::_delayToPhaseAngle(delay, frequency);
return Mycila::Dimmer::_vrmsFactor(angle);
}
These are the textbook functions to convert a power ratio to a phase delay ratio:
def phase2duty(phase):
"""
Phase delay ratio [0,1] to power ratio [0,1]
Sine square CDF (Cumulative Distribution Function)
"""
return sin(2 * pi * phase) / (2 * pi) - phase + 1
def duty2phase(duty):
"""
Power delay ratio [0,1] to phase ratio [0,1]
Invert Sine square CDF, Bisection method
"""
bounds = [0, 1]
for _ in range(32):
phase = (bounds[0] + bounds[1]) / 2
bounds[duty > phase2duty(phase)] = phase
return phase
The duty2phase
should only be used to build a lookup table since it's quite expensive.
The source of your issue could be due to the polynomial error since it's not constant and it can be quite high:
This d2p
function is much more accurate:
phase = (powf(1 - duty, 0.31338) - powf(duty, 0.31338) + 1) / 2
Of course the accuracy is as good as the grid remains a perfect sine wave.
thanks a lot!