/* * Copyright (c) 2025 Bernhard Guillon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the " Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * SPDX: MIT */ #include "comm.h" #include "comm_task.h" #include #include #include #include "FreeRTOS.h" #include "task.h" #include "timers.h" #include "BkDriverPwm.h" #define WARM BK_PWM_0 #define COLD BK_PWM_2 #define RED BK_PWM_3 #define GREEN BK_PWM_4 #define BLUE BK_PWM_5 TimerHandle_t xOneShotTimer = NULL; void power_save_mode() { // Turn your lights down low // And pull your window curtain (yeah) // Couldn't resitst ^^ bk_pwm_initialize(RED, 26000, 26000); bk_pwm_start(RED); bk_pwm_initialize(GREEN, 26000, 26000); bk_pwm_start(GREEN); bk_pwm_initialize(BLUE, 26000, 26000); bk_pwm_start(BLUE); bk_pwm_initialize(COLD, 26000, 26000); bk_pwm_start(COLD); bk_pwm_initialize(WARM, 26000, 0); bk_pwm_start(WARM); } /* * As the following took me longer then expected * I will keep this note for the future me or the * interested reader. * * The red green and blue LED are each connected to a * OCI OC7140 LED PWM dimmer chip. * * 5678 * |||| * [7140] 2 PWM dimmer input 4 LED output * |||| * 1234 * * This chip is driven by the (RED|GREEN|BLUE) PWM signal. * The recomanded frequency for the dimmer is within 1000KHz * where low -> LED off -> high -> LED on. * * I debuged the PWM and dimmer behavior with my logic analyzer * changing the frequency comparator of one PWM channel * sometimes messes up with the other channels. I gave * up on when exactly this happens. But the good news is * changing the duty comparotor on the other hand seems to * be isolated to the PWM channel :) * * Make sure to set the initial and start the pwm in a seperate * init step. * * Afterwards only use the update function and never try to turn * the PWM chip off and change the GPIO input source and try to * set it manually and reapply the PWM later. As this also messes * the other channels. As we don't know how to enter to a propper * powersave mode with the control pin as wakeup source anywas * the BLE will probalby drain the most power ^^. After figguring * out how to proper shut down the BLE we can use the control button * to call init and "power_save_mode". * * Therefore to keep things simple we set the PWM frequency to * 1000Khz and use the duty cycle to generate a positive perioid of * 0us to 1000us. The chips interface takes a number between 0 and * 1000 anyways for the "end_value" which they otherwise call duty * cycle between 0-100 in 0.1% steps. This is perfect for us to * finaly being able to fully controll the LEDs as setting this to 0 * realy gives us a low signal without any highs. As low and hight * are inverted we nedd to apply 1000-positive_period. * * We now take the RGB values which are between 0..255 and multiply * them by 4 and finaly have the RGB spectrum with full on and off * * The only thing left is that we need to apply gamma correction * to match the human eye. * * We leave it to the client application to create a HUE Ring * to nicely controll our device. * * We also controll the motor and the laser with this interface * as it seems to be the most common way to use RGBCW to do this. * Most applications use values from 0..100 instead of 0..255 for * CW our application uses cool (C) for the laser and warm (W) * for the motor. * * For the motor we need to generate a positive_period from * 0..1000. The motor start to drive smothly at 200 and seems * to be linear. Therefore we need to use the range between * 200.1000 with 0 being 0 and 200 being 1. As the output * is inverted we also need to apply 1000-positive_period. * * For the laser we need to generate a positive_period from * 0..1000. The laser starts to shine at 200 and seems * to be linear. Therefore we need to use the range between * 200.1000 with 0 being 0 and 200 being 1. * */ void set_rgbcw(struct RGBCW *rgbcw) { const float clk_26M = 26000000.0f; const float perioid_us = 1000.0f; const float pwm_period = clk_26M / perioid_us; // RGB control const float red_gamma_max = 1000.0f; // 100% const float green_gamma_max = 700.0f; // 70% const float blue_gamma_max = 750.0f; // 75% const float gamma = 2.2f; float r_val_gamma = pow((float)rgbcw->r / 255.0f, gamma) * red_gamma_max + 0.5f; float r_val_inv = 1000.f - r_val_gamma; float r_period = r_val_inv / 1000.0f * pwm_period; bk_pwm_update_param(RED, (uint16_t)pwm_period, (uint16_t)r_period); float g_val_gamma = pow((float)rgbcw->g / 255.0f, gamma) * green_gamma_max + 0.5f; float g_val_inv = 1000.f - g_val_gamma; float g_period = g_val_inv / 1000.0f * pwm_period; bk_pwm_update_param(GREEN, (uint16_t)pwm_period, (uint16_t)g_period); float b_val_gamma = pow((float)rgbcw->b / 255.0f, gamma) * blue_gamma_max + 0.5f; float b_val_inv = 1000.f - b_val_gamma; float b_period = b_val_inv / 1000.0f * pwm_period; bk_pwm_update_param(BLUE, pwm_period, b_period); // Laser control uint16_t laser_power = rgbcw->c; uint16_t l_period = pwm_period; // disable laser // FIMXE: use bitmaks but the compiler should guess this right if (laser_power > 100) { laser_power = 100; // Make sure 100 is the upper as we want to use percentage } if (laser_power != 0) { uint16_t one_percent = 800 / 100; uint16_t lower_limit = 200; l_period = laser_power * one_percent + lower_limit; l_period = 1000.f - l_period; // we need to invert it l_period = (float)l_period / 1000.0f * pwm_period; } bk_pwm_update_param(COLD, pwm_period, l_period); // Motor control uint16_t motor_speed = rgbcw->w; //& 0x64 uint16_t m_period = 0; // FIMXE: use bitmaks but the compiler should guess this right if (motor_speed > 100) { motor_speed = 100; // Make sure 100 is the upper as we want to use percentage } if (motor_speed != 0) { uint16_t one_percent = 800 / 100; uint16_t lower_limit = 200; m_period = motor_speed * one_percent + lower_limit; m_period = (float)m_period / 1000.0f * pwm_period; } bk_pwm_update_param(WARM, pwm_period, m_period); } static void oneShotTimerCallback(TimerHandle_t xTimer) { bk_printf("TIMER CALLBACK called\r\n"); power_save_mode(); } void set_timer(uint32_t seconds) { bk_printf("seconds %u\r\n", seconds); if (xOneShotTimer != NULL) { bk_printf("Delete timer because we want to reconfigure it\r\n"); xTimerDelete(xOneShotTimer, 0); xOneShotTimer = NULL; } if (seconds == 0) { return; } uint32_t ticks = pdMS_TO_TICKS(seconds * 1000); bk_printf("timer to ticks: %u\r\n", ticks); xOneShotTimer = xTimerCreate("OneShotTimer", ticks, pdFALSE, 0, oneShotTimerCallback); if (xOneShotTimer != NULL) { xTimerStart(xOneShotTimer, 0); } } void init_led_thread(void *arg) { power_save_mode(); rtos_delete_thread(NULL); }