#include "light_driver.h" #include "driver/ledc.h" #include "esp_log.h" #include "esp_err.h" #include #include #include #include static const char* TAG = "light_driver"; /* tweakable stuff */ static const uint8_t light_gpio = 2; 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.001; // lowest duty percentage where the LEDs still light up nice static const uint8_t light_identify_level_delta = 80; /* 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]; uint16_t light_driver_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_set_level(const uint8_t level) { xSemaphoreTake(light_sem, portMAX_DELAY); light_target_level = level; xSemaphoreGive(light_sem); } void light_driver_set_power(const bool power) { light_driver_set_level(power ? light_driver_get_level() : 0); } void light_driver_identify(void) { const uint8_t level = light_driver_get_level(); if (level > 128) light_driver_set_level(level - light_identify_level_delta); else light_driver_set_level(level + light_identify_level_delta); vTaskDelay(pdMS_TO_TICKS(500)); light_driver_set_level(level); } 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_0, .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 = 0 }; 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) { // NOLINT ye its a loop const uint16_t tgt = light_driver_get_level(); if (tgt != prev_level) { const int diff = tgt - prev_level; const int abs_diff = abs(diff); int step = abs_diff / 16; if (step == 0) step = 1; if (diff > 0) prev_level += step; else prev_level -= step; ledc_set_duty(light_ledc_speed_mode, light_ledc_channel, gamma_lut[prev_level]); ledc_update_duty(light_ledc_speed_mode, light_ledc_channel); } vTaskDelay(pdMS_TO_TICKS(10)); } }