Files
zigbeelight/main/light_driver.c
2026-03-19 19:52:13 +01:00

105 lines
3.6 KiB
C

#include "light_driver.h"
#include <esp_zigbee_core.h>
#include "driver/ledc.h"
#include "esp_log.h"
#include "esp_err.h"
#include <stdbool.h>
#include <math.h>
#include <freertos/semphr.h>
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);
}