diff --git a/lib/MycilaDimmer/MycilaDimmer.cpp b/lib/MycilaDimmer/MycilaDimmer.cpp index 6c71939..982ad00 100644 --- a/lib/MycilaDimmer/MycilaDimmer.cpp +++ b/lib/MycilaDimmer/MycilaDimmer.cpp @@ -30,13 +30,25 @@ extern Mycila::Logger logger; #define TAG "DIMMER" -#define MYCILA_DIMMER_RESOLUTION 12 // bits -#define MYCILA_DIMMER_MAX_DUTY 4095 // ((1 << MYCILA_DIMMER_RESOLUTION) - 1) - -#define TABLE_PHASE_LEN (80) -#define TABLE_PHASE_SCALE ((TABLE_PHASE_LEN - 1) * (1UL << (16 - MYCILA_DIMMER_RESOLUTION))) - -static const uint16_t TABLE_PHASE_DELAY[TABLE_PHASE_LEN] PROGMEM{0xefea, 0xdfd4, 0xd735, 0xd10d, 0xcc12, 0xc7cc, 0xc403, 0xc094, 0xbd6a, 0xba78, 0xb7b2, 0xb512, 0xb291, 0xb02b, 0xaddc, 0xaba2, 0xa97a, 0xa762, 0xa557, 0xa35a, 0xa167, 0x9f7f, 0x9da0, 0x9bc9, 0x99fa, 0x9831, 0x966e, 0x94b1, 0x92f9, 0x9145, 0x8f95, 0x8de8, 0x8c3e, 0x8a97, 0x88f2, 0x8750, 0x85ae, 0x840e, 0x826e, 0x80cf, 0x7f31, 0x7d92, 0x7bf2, 0x7a52, 0x78b0, 0x770e, 0x7569, 0x73c2, 0x7218, 0x706b, 0x6ebb, 0x6d07, 0x6b4f, 0x6992, 0x67cf, 0x6606, 0x6437, 0x6260, 0x6081, 0x5e99, 0x5ca6, 0x5aa9, 0x589e, 0x5686, 0x545e, 0x5224, 0x4fd5, 0x4d6f, 0x4aee, 0x484e, 0x4588, 0x4296, 0x3f6c, 0x3bfd, 0x3834, 0x33ee, 0x2ef3, 0x28cb, 0x202c, 0x1016}; +// Minimum delay to reach the voltage required for a gate current of 30mA. +// delay_us = asin((gate_resistor * gate_current) / grid_volt_max) / pi * period_us +// delay_us = asin((330 * 0.03) / 325) / pi * 10000 = 97us +#define PHASE_DELAY_MIN_US (100) + +#define TABLE_PHASE_LEN (80U) + +static const uint32_t DIMMER_MAX = (1 << MYCILA_DIMMER_RESOLUTION) - 1; +static const uint32_t TABLE_PHASE_SCALE = (TABLE_PHASE_LEN - 1U) * (1UL << (16 - MYCILA_DIMMER_RESOLUTION)); +static const uint16_t TABLE_PHASE_DELAY[TABLE_PHASE_LEN] PROGMEM = {0xefea, 0xdfd4, 0xd735, 0xd10d, 0xcc12, 0xc7cc, 0xc403, 0xc094, 0xbd6a, 0xba78, 0xb7b2, 0xb512, 0xb291, 0xb02b, 0xaddc, 0xaba2, 0xa97a, 0xa762, 0xa557, 0xa35a, 0xa167, 0x9f7f, 0x9da0, 0x9bc9, 0x99fa, 0x9831, 0x966e, 0x94b1, 0x92f9, 0x9145, 0x8f95, 0x8de8, 0x8c3e, 0x8a97, 0x88f2, 0x8750, 0x85ae, 0x840e, 0x826e, 0x80cf, 0x7f31, 0x7d92, 0x7bf2, 0x7a52, 0x78b0, 0x770e, 0x7569, 0x73c2, 0x7218, 0x706b, 0x6ebb, 0x6d07, 0x6b4f, 0x6992, 0x67cf, 0x6606, 0x6437, 0x6260, 0x6081, 0x5e99, 0x5ca6, 0x5aa9, 0x589e, 0x5686, 0x545e, 0x5224, 0x4fd5, 0x4d6f, 0x4aee, 0x484e, 0x4588, 0x4296, 0x3f6c, 0x3bfd, 0x3834, 0x33ee, 0x2ef3, 0x28cb, 0x202c, 0x1016}; + +static uint16_t _lookupPhaseDelay(uint32_t duty, uint16_t period) { + uint32_t slot = duty * TABLE_PHASE_SCALE + (TABLE_PHASE_SCALE >> 1); + uint16_t index = slot >> 16; + uint32_t a = TABLE_PHASE_DELAY[index]; + uint32_t b = TABLE_PHASE_DELAY[index + 1]; + uint32_t delay = a - (((a - b) * (slot & 0xffff)) >> 16); // interpolate a b + return (delay * period) >> 16; // scale to period +} void Mycila::Dimmer::begin(int8_t pin, uint32_t semiPeriod) { if (_enabled) @@ -63,49 +75,45 @@ void Mycila::Dimmer::begin(int8_t pin, uint32_t semiPeriod) { digitalWrite(_pin, LOW); _enabled = true; - setDutyCycle(_dutyCycleMin); + // restart with last saved value + setDutyCycle(_dutyCycle); } void Mycila::Dimmer::end() { if (!_enabled) return; - - LOGI(TAG, "Disable Dimmer on pin %" PRId8, _pin); _enabled = false; - _disable(); + LOGI(TAG, "Disable Dimmer on pin %" PRId8, _pin); + // keep last saved value, + // _dutyCycle = 0; + _delay = UINT32_MAX; + _semiPeriod = 0; + _dimmer->setDelay(_delay); + delete _dimmer; + _dimmer = nullptr; + digitalWrite(_pin, LOW); + _pin = GPIO_NUM_NC; } void Mycila::Dimmer::setDutyCycle(float newDutyCycle) { if (!_enabled) return; - if (_semiPeriod == 0) - return; - - // ensure newDuty is within bounds + // apply limit _dutyCycle = constrain(newDutyCycle, 0, _dutyCycleLimit); - if (_dutyCycle == 0) { - _dimmer->setDelay(_semiPeriod); - } else if (_dutyCycle >= 1) { - _dimmer->setDelay(0); + // duty remapping (equivalent to Shelly Dimmer remapping feature) + const float mappedDutyCycle = _dutyCycleMin + _dutyCycle * (_dutyCycleMax - _dutyCycleMin); + if (mappedDutyCycle == 0) { + _delay = UINT32_MAX; + } else if (mappedDutyCycle == 1) { + _delay = PHASE_DELAY_MIN_US; } else { - // duty remapping (equivalent to Shelly Dimmer remapping feature) - const float remappedDutyCycle = _dutyCycleMin + _dutyCycle * (_dutyCycleMax - _dutyCycleMin); - const uint16_t remappedDuty = remappedDutyCycle * MYCILA_DIMMER_MAX_DUTY; - // ESP_LOGD(TAG, "Dimmer %d duty cycle set to %f (remapped to %f). Duty: %d", _pin, _dutyCycle, remappedDutyCycle, remappedDuty); - - // map new level to firing delay (LUT + linear interpolation) - const uint32_t slot = remappedDuty * TABLE_PHASE_SCALE + (TABLE_PHASE_SCALE >> 1); - const uint16_t index = slot >> 16; - const uint32_t a = TABLE_PHASE_DELAY[index]; - const uint32_t b = TABLE_PHASE_DELAY[index + 1]; - const uint32_t delay = a - (((a - b) * (slot & 0xffff)) >> 16); - _delay = (delay * _semiPeriod) >> 16; - - _dimmer->setDelay(_delay); + _delay = _lookupPhaseDelay(mappedDutyCycle * DIMMER_MAX, _semiPeriod); } + + _dimmer->setDelay(_delay); } void Mycila::Dimmer::setDutyCycleLimit(float limit) { @@ -126,14 +134,3 @@ void Mycila::Dimmer::setDutyCycleMax(float max) { LOGD(TAG, "Set dimmer %" PRId8 " duty cycle max to %f", _pin, _dutyCycleMax); setDutyCycle(_dutyCycle); } - -void Mycila::Dimmer::_disable() { - if (_enabled) { - _dutyCycle = 0; - _delay = UINT32_MAX; - digitalWrite(_pin, LOW); - delete _dimmer; - _dimmer = nullptr; - _pin = GPIO_NUM_NC; - } -} diff --git a/lib/MycilaDimmer/MycilaDimmer.h b/lib/MycilaDimmer/MycilaDimmer.h index b919dcd..48d7b59 100644 --- a/lib/MycilaDimmer/MycilaDimmer.h +++ b/lib/MycilaDimmer/MycilaDimmer.h @@ -11,6 +11,13 @@ #include #endif +/** + * Optional resolution, 15bits max + */ +#ifndef MYCILA_DIMMER_RESOLUTION + #define MYCILA_DIMMER_RESOLUTION 12 +#endif + namespace Mycila { class Dimmer { public: @@ -44,6 +51,11 @@ namespace Mycila { */ gpio_num_t getPin() const { return _pin; } + /** + * @brief Get the semi-period of the dimmer in us + */ + uint32_t getSemiPeriod() const { return _semiPeriod; } + #ifdef MYCILA_JSON_SUPPORT /** * @brief Serialize Dimmer information to a JSON object @@ -123,7 +135,7 @@ namespace Mycila { /** * @brief Get the power duty cycle of the dimmer */ - float getDutyCycle() const { return _dutyCycle; } + float getDutyCycle() const { return _enabled ? _dutyCycle : 0; } /** * @brief Get the power duty cycle limit of the dimmer @@ -163,7 +175,6 @@ namespace Mycila { float _dutyCycleLimit = 1; float _dutyCycleMin = 0; float _dutyCycleMax = 1; - void _disable(); uint32_t _delay = UINT32_MAX; Thyristor* _dimmer = nullptr; };