114 lines
4.0 KiB
C
114 lines
4.0 KiB
C
#include "light_driver.h"
|
|
|
|
#include "driver/ledc.h"
|
|
#include "esp_log.h"
|
|
#include "esp_err.h"
|
|
#include <math.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/semphr.h>
|
|
|
|
static const char* TAG = "light_driver";
|
|
|
|
/* tweakable stuff */
|
|
static constexpr uint8_t light_gpio = 2;
|
|
static constexpr uint16_t light_pwm_freq = 65000;
|
|
static constexpr ledc_timer_bit_t light_pwm_resolution = LEDC_TIMER_10_BIT;
|
|
static constexpr double light_lowest_duty_pct = 0.001; // lowest duty percentage where the LEDs still light up nice
|
|
static constexpr uint8_t light_identify_level_delta = 80;
|
|
|
|
/* no need to tweak this stuff */
|
|
static constexpr ledc_channel_t light_ledc_channel = LEDC_CHANNEL_0;
|
|
static constexpr ledc_mode_t light_ledc_speed_mode = LEDC_LOW_SPEED_MODE;
|
|
static constexpr uint32_t light_brightness_steps = (1 << light_pwm_resolution) - 1;
|
|
static SemaphoreHandle_t light_sem = nullptr;
|
|
|
|
/* global vars */
|
|
static uint8_t light_target_level = 0;
|
|
static uint16_t gamma_lut[ZIGBEE_LIGHT_LEVELS + 1];
|
|
|
|
static uint16_t light_driver_get_level() {
|
|
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() {
|
|
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));
|
|
}
|
|
} |