#include "light_driver.h" #include #include "driver/ledc.h" #include "esp_log.h" #include "esp_err.h" #include #include #include static const char* TAG = "light_driver"; /* tweakable stuff */ static const uint8_t light_gpio = 15; static const uint16_t light_pwm_freq = 65000; static const ledc_timer_bit_t light_pwm_resolution = LEDC_TIMER_10_BIT; static const double light_lowest_duty_pct = 0.05; // lowest duty percentage where the LEDs still light up nice /* no need to tweak this stuff */ static const ledc_channel_t light_ledc_channel = LEDC_CHANNEL_0; static const ledc_mode_t light_ledc_speed_mode = LEDC_LOW_SPEED_MODE; static const uint32_t light_brightness_steps = (1 << light_pwm_resolution) - 1; SemaphoreHandle_t light_sem = NULL; /* global vars */ static uint8_t light_target_level = 0; static uint16_t gamma_lut[ZIGBEE_LIGHT_LEVELS + 1]; void light_driver_set_level(const uint8_t level) { xSemaphoreTake(light_sem, portMAX_DELAY); light_target_level = level; xSemaphoreGive(light_sem); } uint16_t get_level(void) { xSemaphoreTake(light_sem, portMAX_DELAY); const uint16_t lvl = light_target_level; xSemaphoreGive(light_sem); return MIN(254, lvl); } void light_driver_task(void *pvParameter) { const ledc_timer_config_t ledc_timer = { .speed_mode = light_ledc_speed_mode, .duty_resolution = light_pwm_resolution, .timer_num = LEDC_TIMER_2, .freq_hz = light_pwm_freq, .clk_cfg = LEDC_AUTO_CLK }; const ledc_channel_config_t ledc_channel = { .speed_mode = ledc_timer.speed_mode, .channel = light_ledc_channel, .timer_sel = ledc_timer.timer_num, .intr_type = LEDC_INTR_DISABLE, .gpio_num = light_gpio, .duty = 0, // Set duty to 0% .hpoint = (int)light_brightness_steps }; ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); static uint8_t prev_level = 0; // pre-compute gamma table for (uint8_t i = 0; i <= ZIGBEE_LIGHT_LEVELS; i++) { const double brightness_pct = (double)i / (double)ZIGBEE_LIGHT_LEVELS; const double gamma_corrected = pow(brightness_pct, 2.2); const double scaled = (double)light_brightness_steps * gamma_corrected * (1 - light_lowest_duty_pct); gamma_lut[i] = (uint16_t)scaled + (uint16_t)((double)light_brightness_steps * light_lowest_duty_pct) + 1; } gamma_lut[0] = 0; // 0 is always completely off if (gamma_lut[ZIGBEE_LIGHT_LEVELS] > light_brightness_steps) gamma_lut[ZIGBEE_LIGHT_LEVELS] = light_brightness_steps; ESP_LOGI(TAG, "pre-computed brightness lookup table"); // semaphore init light_sem = xSemaphoreCreateMutex(); xSemaphoreGive(light_sem); ESP_LOGI(TAG, "ledc driver initialized, f=%d Hz, %d step resolution", light_pwm_freq, light_brightness_steps); light_driver_set_level(0); while (true) { const uint16_t tgt = get_level(); if (tgt != prev_level) { const int diff = tgt - prev_level; const int abs_diff = abs(diff); const int step = MIN(abs_diff / 8 + 1, 20); if (diff > 0) prev_level += step; else prev_level -= step; ESP_LOGI(TAG, "setting light level to %d, tgt is %d, step of %d", prev_level, tgt, step); ledc_set_duty_and_update(light_ledc_speed_mode, light_ledc_channel, gamma_lut[prev_level], light_brightness_steps); } vTaskDelay(pdMS_TO_TICKS(20)); } } void light_driver_set_power(const bool power) { light_driver_set_level(power ? get_level() : 0); }