fabianoriccardi / dimmable-light

Arduino library to manage dimmers compatible with AVR, ESP8266, ESP32, SAMD, and RP2040 platforms.
GNU Lesser General Public License v2.1
97 stars 29 forks source link

How to tweak the brightness => delay (linearised or not) #58

Closed mathieucarbou closed 5 months ago

mathieucarbou commented 9 months ago

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.

mathieucarbou commented 9 months ago

(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;
fabianoriccardi commented 9 months ago

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.

mathieucarbou commented 9 months ago

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

V3 is really the best that is closest to reality, but it could be improved:

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:

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 ;-)

mathieucarbou commented 9 months ago

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
mathieucarbou commented 9 months ago

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:

V1 is calculating an angle with the formula:

 return acos(2 * level / 255.0 - 1);

From: // https://electronics.stackexchange.com/questions/414370/calculate-angle-for-triac-phase-control/414399#414399

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.

mathieucarbou commented 9 months ago

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);
}
florentbr commented 8 months ago

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:

image

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.

mathieucarbou commented 8 months ago

thanks a lot!