Skip to content

Commit

Permalink
Dimmer refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieucarbou committed Sep 21, 2024
1 parent 29e5ce2 commit 0488a09
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 46 deletions.
85 changes: 41 additions & 44 deletions lib/MycilaDimmer/MycilaDimmer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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;
}
}
15 changes: 13 additions & 2 deletions lib/MycilaDimmer/MycilaDimmer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
#include <ArduinoJson.h>
#endif

/**
* Optional resolution, 15bits max
*/
#ifndef MYCILA_DIMMER_RESOLUTION
#define MYCILA_DIMMER_RESOLUTION 12
#endif

namespace Mycila {
class Dimmer {
public:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
};
Expand Down

0 comments on commit 0488a09

Please sign in to comment.