Managing the luminosity
For a first try, I used the out of the box standard PWM provided by Arduino libraries through the analogWrite() function.
Unfortunately, this is not satisfactory:
- The analogWrite() function accept an input range of 0..255. This is not enough to provide a smooth change in luminosity. When incrementing the duty cycle, the steps are visible to the eyes, especially for the low values, we can clearly see each increment of the luminosity.
- The output frequency is around 500 Hz, which was too low for my electronic, and yield to an audible unpleasant noise (again, at list with my components, this may not be true with something else).
So I changed the code to use 16 bits timer for the PWM.
Full 16 bits was not required though, so the range is actually limited to 0..4095, which is enough to provide a smooth change between light steps. It has also the advantage of enabling a higher output frequency (if 16 bits used and no pre-divider, 16 MHz / 65 536 equals 244 Hz, too low! … 16 MHz / 4 096 is approx 4 900 Hz).
With 0..4095 range to control luminosity level is now perfect. Having that, it is obvious that we cannot use just 0..100 steps for ramp up / ramp down: we would go back to the same problem!
After some trials, the good range is using 0..1023 for stepping from 0 to 100 % light (or 100 % to 0).
However, the next test with this new setup was not very good too. The problem now is that a ramp up from 0 to full light is not perceived as increasing linearly. We have the impression that the full luminosity is obtained very quickly and then the luminosity doesn’t seem to increase.
This is a known problem due to the fact that the human eye doesn’t respond linearly to the luminance, but follows more on logarithmic law.
After googling, a little bit, I found an interesting post that gives an algorithm to compute the response curve.
I used this algorithm to generate a 1024 entries lookup table where each index is the linear luminosity level we want to achieve, and the content for each index is the actual PWM value we will send to LEDs (0..4095).
And with that, the result is perfect: the ramp up / ramp down are really smooth without any glitch.
Managing the ramps
The next difficulty was then to generate the ramp up and ramp down values for dimming up and down.
This problem is much like drawing a line on a 2D area: X is the time (0..1023) and Y the luminosity.
The dimmer accepts command telling “go to LLL % luminosity in SSS.S seconds”. So when receiving such commands, the program have to determine what would be each Y for a simulated progression of X representing the requested duration.
Going to the actual implementation, this is not a series of (X, Y) points that are prepared for output, but more a state machine that is updating the Y value every 50 ms during the ramp process. This way of doing allows to implement other tasks in parallel, such as reading serial line to get command from host and taking care of the bi-stable Relay.
For the ramp, there is no floating point computing using multiplication and division.
Instead, for each X increment, the fractional part of Y is held in a ‘quotient’ variable. All parameters to know what to do every 50 ms are prepared at the beginning of the ramp (for instance, slope sign, how much should we add / subtract at each loop, how many loops will the ramp last, etc…). See the comments in the code that explain that process in detail.
At each time the Y value is computed, the function that set the output PWM is called.
Exact code is not detailed in this article, if you are interested, have a look in the code which has a lot of comments.
Host Interface
As said in introduction, the dimmer is driven by the host HTPC by sending command over serial line.
The interpreter is very basic and accepts two types of commands, one for dimming and one for the Relay:
- ++Dc-vvv-ttt.t D stands for ‘Dim’, c is the channel, vvv the dim value and ttt.t the time (accepting tenth of seconds)
- ++Rc-v R stands for ‘Relay’, c is the channel and v the realy state (0 / 1 for On / off)
- ‘++’ is a re-sync pattern (as used in the old days by modems).
- Currently, only one channel is implemented, and c must be equal to 0.
Miscellaneous
The code that must be managed with strict timings is synchronized by a flag that is true every 50 ms. This encompass the ramp management and the relay coils activation (using one loop, the coil is activated for 50 ms).
In the main loop, outside this 50 ms paced code, we have the management of serial line data reception and command decoding If a dimming command is received, then all the parameters used to generate the luminosity ramp are prepared at that time.
Code
Bellow is the full code (also available as a zip file):
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 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
#include <avr/pgmspace.h> #define RELAY_CMD_MAX_LEN 20 #define PWM0_PIN 9 #define REL_COIL_OFF 7 #define REL_COIL_ON 8 #define PWMValue OCR1A const int PWMMax = 4095; // http://forum.arduino.cc/index.php/topic,130736.0.html // 50 ms tick management State Machine states #define DIM_IDLE 0 #define DIM_DIMMING 1 #define DIM_ENDSWEEP 2 #define COIL_INACTIVE 0 #define COIL_ACTIVATE 1 #define COIL_ACTIVE 2 unsigned long previousMillis = 0; unsigned long currentMillis; bool tick50ms; // Dim management variables unsigned short int targetDim, currentDim, duration; // Range 0..1023 for Dim* short int delta; unsigned short int calc, result, err, n_x_delta, deltaDivN, result_x_deltaDivN, quotient; unsigned short int N, step; bool highSlope, rising; bool pwmDisabled; // Relay management variables byte relayVal; // Serial Commands and Statte Machine management variables byte commandByte; byte serialCmdBuff[RELAY_CMD_MAX_LEN]; byte serialCmdBuffPtr; byte dimState, coilState; // Changing linearly PWM doesn't yield to a good visual effect, because eye doesn't react linearly. // So the table bellow is a lookoup table that translates linear input rampup into something growing logarithmically. // To let the table be not too big, input is restricted to [0..1023] whereas output range is [0..4095]. // Algorithm used to generate the table was found here: http://jared.geek.nz/2013/feb/linear-led-pwm // Settings was: INPUT_SIZE = 1023 OUTPUT_SIZE = 4095 const uint16_t cie[1024] PROGMEM = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 28, 29, 29, 30, 30, 31, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 54, 54, 55, 55, 56, 57, 57, 58, 58, 59, 60, 60, 61, 61, 62, 63, 63, 64, 65, 65, 66, 67, 67, 68, 69, 69, 70, 71, 71, 72, 73, 73, 74, 75, 76, 76, 77, 78, 79, 79, 80, 81, 82, 82, 83, 84, 85, 85, 86, 87, 88, 89, 89, 90, 91, 92, 93, 94, 94, 95, 96, 97, 98, 99, 99, 100, 101, 102, 103, 104, 105, 106, 107, 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, 141, 142, 143, 144, 145, 146, 147, 148, 150, 151, 152, 153, 154, 155, 156, 158, 159, 160, 161, 162, 164, 165, 166, 167, 168, 170, 171, 172, 173, 175, 176, 177, 179, 180, 181, 182, 184, 185, 186, 188, 189, 190, 192, 193, 194, 196, 197, 198, 200, 201, 203, 204, 205, 207, 208, 210, 211, 213, 214, 215, 217, 218, 220, 221, 223, 224, 226, 227, 229, 230, 232, 233, 235, 236, 238, 240, 241, 243, 244, 246, 247, 249, 251, 252, 254, 256, 257, 259, 260, 262, 264, 265, 267, 269, 270, 272, 274, 276, 277, 279, 281, 282, 284, 286, 288, 289, 291, 293, 295, 297, 298, 300, 302, 304, 306, 308, 309, 311, 313, 315, 317, 319, 321, 323, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 375, 377, 379, 381, 383, 385, 387, 389, 392, 394, 396, 398, 400, 403, 405, 407, 409, 411, 414, 416, 418, 420, 423, 425, 427, 430, 432, 434, 437, 439, 441, 444, 446, 448, 451, 453, 455, 458, 460, 463, 465, 468, 470, 472, 475, 477, 480, 482, 485, 487, 490, 492, 495, 497, 500, 502, 505, 508, 510, 513, 515, 518, 521, 523, 526, 528, 531, 534, 536, 539, 542, 544, 547, 550, 553, 555, 558, 561, 564, 566, 569, 572, 575, 577, 580, 583, 586, 589, 592, 594, 597, 600, 603, 606, 609, 612, 615, 618, 621, 624, 626, 629, 632, 635, 638, 641, 644, 647, 650, 654, 657, 660, 663, 666, 669, 672, 675, 678, 681, 684, 688, 691, 694, 697, 700, 703, 707, 710, 713, 716, 720, 723, 726, 729, 733, 736, 739, 743, 746, 749, 753, 756, 759, 763, 766, 769, 773, 776, 780, 783, 787, 790, 793, 797, 800, 804, 807, 811, 814, 818, 821, 825, 829, 832, 836, 839, 843, 847, 850, 854, 857, 861, 865, 868, 872, 876, 880, 883, 887, 891, 895, 898, 902, 906, 910, 913, 917, 921, 925, 929, 933, 936, 940, 944, 948, 952, 956, 960, 964, 968, 972, 976, 980, 984, 988, 992, 996, 1000, 1004, 1008, 1012, 1016, 1020, 1024, 1028, 1032, 1037, 1041, 1045, 1049, 1053, 1057, 1062, 1066, 1070, 1074, 1079, 1083, 1087, 1091, 1096, 1100, 1104, 1109, 1113, 1117, 1122, 1126, 1130, 1135, 1139, 1144, 1148, 1153, 1157, 1161, 1166, 1170, 1175, 1179, 1184, 1188, 1193, 1198, 1202, 1207, 1211, 1216, 1221, 1225, 1230, 1234, 1239, 1244, 1248, 1253, 1258, 1263, 1267, 1272, 1277, 1282, 1286, 1291, 1296, 1301, 1306, 1310, 1315, 1320, 1325, 1330, 1335, 1340, 1345, 1350, 1354, 1359, 1364, 1369, 1374, 1379, 1384, 1389, 1394, 1400, 1405, 1410, 1415, 1420, 1425, 1430, 1435, 1440, 1446, 1451, 1456, 1461, 1466, 1472, 1477, 1482, 1487, 1493, 1498, 1503, 1509, 1514, 1519, 1525, 1530, 1535, 1541, 1546, 1551, 1557, 1562, 1568, 1573, 1579, 1584, 1590, 1595, 1601, 1606, 1612, 1617, 1623, 1629, 1634, 1640, 1645, 1651, 1657, 1662, 1668, 1674, 1680, 1685, 1691, 1697, 1702, 1708, 1714, 1720, 1726, 1731, 1737, 1743, 1749, 1755, 1761, 1767, 1773, 1779, 1785, 1790, 1796, 1802, 1808, 1814, 1820, 1826, 1833, 1839, 1845, 1851, 1857, 1863, 1869, 1875, 1881, 1888, 1894, 1900, 1906, 1912, 1919, 1925, 1931, 1937, 1944, 1950, 1956, 1963, 1969, 1975, 1982, 1988, 1995, 2001, 2007, 2014, 2020, 2027, 2033, 2040, 2046, 2053, 2059, 2066, 2072, 2079, 2086, 2092, 2099, 2106, 2112, 2119, 2125, 2132, 2139, 2146, 2152, 2159, 2166, 2173, 2179, 2186, 2193, 2200, 2207, 2214, 2220, 2227, 2234, 2241, 2248, 2255, 2262, 2269, 2276, 2283, 2290, 2297, 2304, 2311, 2318, 2325, 2332, 2340, 2347, 2354, 2361, 2368, 2375, 2383, 2390, 2397, 2404, 2412, 2419, 2426, 2433, 2441, 2448, 2455, 2463, 2470, 2478, 2485, 2492, 2500, 2507, 2515, 2522, 2530, 2537, 2545, 2552, 2560, 2568, 2575, 2583, 2590, 2598, 2606, 2613, 2621, 2629, 2636, 2644, 2652, 2660, 2667, 2675, 2683, 2691, 2699, 2707, 2714, 2722, 2730, 2738, 2746, 2754, 2762, 2770, 2778, 2786, 2794, 2802, 2810, 2818, 2826, 2834, 2842, 2850, 2859, 2867, 2875, 2883, 2891, 2899, 2908, 2916, 2924, 2932, 2941, 2949, 2957, 2966, 2974, 2982, 2991, 2999, 3008, 3016, 3025, 3033, 3042, 3050, 3059, 3067, 3076, 3084, 3093, 3101, 3110, 3119, 3127, 3136, 3145, 3153, 3162, 3171, 3179, 3188, 3197, 3206, 3215, 3223, 3232, 3241, 3250, 3259, 3268, 3277, 3286, 3294, 3303, 3312, 3321, 3330, 3339, 3348, 3358, 3367, 3376, 3385, 3394, 3403, 3412, 3421, 3431, 3440, 3449, 3458, 3468, 3477, 3486, 3495, 3505, 3514, 3523, 3533, 3542, 3552, 3561, 3570, 3580, 3589, 3599, 3608, 3618, 3627, 3637, 3647, 3656, 3666, 3675, 3685, 3695, 3704, 3714, 3724, 3734, 3743, 3753, 3763, 3773, 3782, 3792, 3802, 3812, 3822, 3832, 3842, 3852, 3861, 3871, 3881, 3891, 3901, 3911, 3922, 3932, 3942, 3952, 3962, 3972, 3982, 3992, 4003, 4013, 4023, 4033, 4043, 4054, 4064, 4074, 4085, 4095, }; // Read a number of exactly n digits (must have 0 leading, eg 1 with 3 digits is 001). // Reading is done into serialCmdBuff buffer, and the current read position is held globally into serialCmdBuffPtr. // Return -1 in case of error. // If fraction is true, there is a one digit fractionnnal part, and the result is fixed point decimal 10 times the value: // eg 12.3 with 3 digits will be 012.3 and will return 123. int readNumber(byte nbDigits, bool fraction) { int value = 0; for (byte i = 0; i < nbDigits; i++) { if (serialCmdBuff[serialCmdBuffPtr] >= '0' && serialCmdBuff[serialCmdBuffPtr] <= '9') { value = value * 10 + serialCmdBuff[serialCmdBuffPtr++] - '0'; } else return -1; } if (fraction) { if (serialCmdBuff[serialCmdBuffPtr++] != '.') return -1; if (serialCmdBuff[serialCmdBuffPtr] >= '0' && serialCmdBuff[serialCmdBuffPtr] <= '9') value = value * 10 + serialCmdBuff[serialCmdBuffPtr++] - '0'; else return -1; } return value; } void setDim(short int dimVal) { // Due to AT328 timer design, when wanting to set 0 for PWM, there will remain a residual pulse. // This is noticed in the data sheet : The extreme values for the OCR1x Register represents special cases when // generating a PWM waveform output in the fast PWM mode. If the OCR1x is set equal to BOTTOM (0x0000) the // output will be a narrow spike for each TOP+1 timer clock cycle. // So this function correct that bug by disabling the PWM mode when wanting 0. if (dimVal != 0) { if (pwmDisabled) { // We where formerly in PWM disabled mode, re-enabling TCCR1A = (1 << COM1A1) | (1 << WGM11); pwmDisabled = false; } PWMValue = pgm_read_word(&cie[dimVal]); } else { // Changing to constant output if not already done if (!pwmDisabled) { TCCR1A = (1 << WGM11); // When disabled, pin will output the initialized digital '0' // Remembering that change pwmDisabled = true; } } } void setup() { Serial.begin(115200); pinMode(9,OUTPUT); digitalWrite(9, LOW); // For when PWM is disabled pinMode(REL_COIL_OFF,OUTPUT); pinMode(REL_COIL_ON,OUTPUT); dimState = DIM_IDLE; coilState = COIL_INACTIVE; pwmDisabled = true; tick50ms = false; currentDim = 0; targetDim = 0; serialCmdBuffPtr = 0; // Timer initialization for PWM. 8 bit Timer found not enought accurate for smooth dimming change, so chosed // a 16 bits timer used in 12 bits mode (max counter set to 4095). This gives approximately a 4000 Hz PWM (250 µS). // Instructions found here: http://forum.arduino.cc/index.php/topic,130736.0.html TCCR1A = (1 << WGM11); TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10); // Mode 14 Fast PWM/ (TOP = ICR1), pre-scale = 1 ICR1 = PWMMax; // TOP value for 12-bit PWM // COM1A1:0 Compare Output Mode for Fast PWM = // Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at BOTTOM (non-inverting mode) ==> 10 // TCCR1A: Mode 14 (Fast PWM & TOP = ICR1) ==> WGM1[0..3] = 1110 // CS12:0: Clock Select = No prescaling ==> 001 // Initial PWM output to full off. Finally not needed due to chnage made that set PWM disabled (see setDim) PWMValue = 0; // Force a switch relay off (this is the reset state) relayVal = 0; // 0 = Off, not 0 = On coilState = COIL_ACTIVATE; Serial.println("Ready"); } void loop() { int cmdValue1, cmdValue2; // Check if a new 50ms tic elapsed if (((currentMillis = millis()) - previousMillis) > 50) { previousMillis = currentMillis; tick50ms = true; } if (Serial.available() > 0) { commandByte = Serial.read(); if (commandByte == 0xd) // Ignore CR (are sent if Dos echo cmd used) return; // Simply proceed to next loop immediately in this case. // Discard all byte != '+' at begining of command line if (serialCmdBuffPtr == 0) { if (commandByte == '+') serialCmdBuff[serialCmdBuffPtr++] = commandByte; } else { // Where are in the middle... if (commandByte != 0xa) { // Not end of command line, push received char in buff if (serialCmdBuffPtr != RELAY_CMD_MAX_LEN) // Take care of line length serialCmdBuff[serialCmdBuffPtr++] = commandByte; else serialCmdBuffPtr = 0; // Reset command line buffer } } } else commandByte = 0; // To signal 'no command received' & continue processing // commandByte == LF means we just received a command // Commandsd are of the form ++Dc-vvv-ttt.t // D stands for 'Dim' c is the channel, vvv the dim value and ttt.t the ramp duration // Example: ++D0-050-001.0 if (commandByte == 0xa) { byte channel; // Check command validity, silently ignore bad command line if (serialCmdBuff[1] == '+') { serialCmdBuffPtr = 3; switch (serialCmdBuff[2]) { case 'D': if (((channel = readNumber(1, false)) != -1) && serialCmdBuff[serialCmdBuffPtr++] == '-' && ((cmdValue1 = readNumber(3, false)) != -1) && serialCmdBuff[serialCmdBuffPtr++] == '-' && ((cmdValue2 = readNumber(3, true)) != -1)) { targetDim = cmdValue1; duration = cmdValue2; Serial.print("DIM: "); Serial.print(targetDim, DEC); // Adjust so that 0..100 % ==> 0..1023 PWM targetDim = ((long)targetDim * (long)1023) / (long)100; Serial.print(" (PWM: "); Serial.print(targetDim, DEC); Serial.println(")"); // Prepare slope generation delta = targetDim - currentDim; if (delta >= 0) rising = true; else { rising = false; delta = -delta; } if (duration != 0) { N = duration * 2; // Hard coded because time slice is 50 ms if (delta <= N) { highSlope = false; } else { highSlope = true; n_x_delta = 0; deltaDivN = delta / N; result_x_deltaDivN = N * deltaDivN; quotient = 0; } // Initialize for first step result = 0; err = 0; calc = 0; // Useless for highSlope... step = 0; dimState = DIM_DIMMING; } else { // Force an immediate transition to target value (will issue targetDim) without slope calculation dimState = DIM_ENDSWEEP; } } break; case 'R': if (((channel = readNumber(1, false)) != -1) && serialCmdBuff[serialCmdBuffPtr++] == '-' && ((cmdValue1 = readNumber(1, false)) != -1)) { relayVal = cmdValue1; // 0 = Off, not 0 = On coilState = COIL_ACTIVATE; } Serial.print("REL: "); Serial.print(relayVal, DEC); break; } serialCmdBuffPtr = 0; // Command buffer processed, reset pointer } } if (tick50ms) { tick50ms = false; // Dim management /* * We have to generate a slope from 'currentDim' to 'targetDim' in steps of constant period 50 ms. * Nb of steps = duration / step_period = N * Let n = current step at a given time * Let delta be target - start * * Absolute formula is: * result = start + n x (delta / N) * * We want to avoid using multiplication & division at each step * -> will use Euclidean Division method (a = b x q + r) Dividant = Divider x Quotient + remainder * All computation will be done in the X>0 & Y>0 quadrant, yielding to an increasing slope. * Final result is managed by the 'rising' boolean telling if actual slope should rise or fall. * Depending on the slope (higher or lower than 45°) result will evolve in increments higher or * lower than 1, leading to two different methods. * Slope being higher or lower than 45° is held in 'highSlope' boolean. * * The algorithm will concentrate on each step increment, wich is N / delta. * * a) Low Slope (delta <= N) ==> increment < 1 * calc represent "bq" and err "r" * at each step we add delta + former err * if we are "above the line", e.g calculatedvalue is above N * result is incremented * and new err = the above calculated value - N * otherwise * the new err is the above calculated value - N * * b) High Slope (delta > N) ==> increment >= 1 * We compute once the minimal integre value that will be added each time (equal the integral part, * delta / N), called deltaDivN * at each step we have n x delta = N x result, * as we are interested only by the integral part of the result, we can say: * n x delta = N x INT(result) + N x FRAC(result) * n x delta = N x quotient + remainder * n x delta will be calculated by incrementing n_x_delta by delta * quotient will be calculated by incrementing it by deltaDivN * N x quotient is approximated (in calc) by incrementing it by result_x_deltaDivN * if gap between N x quotient and n x delta is higher than N, quotient is adjusted by + 1 */ switch (dimState) { // In the course of dimming case DIM_DIMMING: if (step < N) { if (!highSlope) { calc = err + delta; if (calc >= N) { result++; err = calc - N; if (rising) // Reflecting result taking quadrant into account currentDim++; else currentDim--; } else { err = calc; } } else { quotient += deltaDivN; // First aproximation of quotient n_x_delta += delta; // Update reference if (rising) // Reflecting approximated quotient taking quadrant into account currentDim += deltaDivN; else currentDim -= deltaDivN; calc += result_x_deltaDivN; // First aproximation of N x quotient if (calc + N <= n_x_delta) { // Adjusting (by max one quotient) quotient++; calc += N; if (rising) // Adjust finale result too currentDim++; else currentDim--; } err = n_x_delta - calc; } setDim(currentDim); step++; } else // Went through all steps dimState = DIM_ENDSWEEP; break; // Writing targed dim value one tick after last change at end of ramp just in case of rounding errors // Also used - immediately - if duration is 0 case DIM_ENDSWEEP: setDim(targetDim); dimState = DIM_IDLE; Serial.println("PWM Done"); break; } // Relay management switch (coilState) { // Be carefull, coil activation time is dependant on loop sync (50 ms) case COIL_ACTIVATE: if (relayVal != 0) digitalWrite(REL_COIL_ON, HIGH); else digitalWrite(REL_COIL_OFF, HIGH); coilState = COIL_ACTIVE; break; case COIL_ACTIVE: if (relayVal != 0) digitalWrite(REL_COIL_ON, LOW); else digitalWrite(REL_COIL_OFF, LOW); coilState = COIL_INACTIVE; break; } } } |
Be First to Comment