From f70b052b789dbac68651591031aabc0b372db8ec Mon Sep 17 00:00:00 2001 From: Bernhard Guillon Date: Wed, 1 Jan 2025 18:22:14 +0100 Subject: space_light: finished the pwm stuff Using a logic analyzer and a multimeter to figguer out why the things did't do what I expected ^^ I wrote a long explanation on the set_rgbcw on how this space light is connected and how it is supposed to work. Also used a work arround for the PWM. --- usr/space_light/src/space_light.c | 322 +++++++++++++++++++++++--------------- 1 file changed, 196 insertions(+), 126 deletions(-) (limited to 'usr/space_light/src/space_light.c') diff --git a/usr/space_light/src/space_light.c b/usr/space_light/src/space_light.c index ebf0d26..eb82c5a 100644 --- a/usr/space_light/src/space_light.c +++ b/usr/space_light/src/space_light.c @@ -1,9 +1,38 @@ +/* + * 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 @@ -12,12 +41,169 @@ #define GREEN BK_PWM_4 #define BLUE BK_PWM_5 -static const float max_freq = 1000.0f; +TimerHandle_t xOneShotTimer; -// FIXME: consider creating a lookup table -static inline uint16_t get_led_frequency(const uint8_t value, - const float gamma) { - return (uint16_t)pow((float)value / 255.0f, gamma) * max_freq + 0.5; +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; //& 0x64 + uint16_t l_period = 0; + // 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) { power_save_mode(); } + +void set_timer(uint16_t seconds) { + // TODO: reset or disable timer! + xOneShotTimer = + xTimerCreate("OneShot", seconds * 1000, pdFALSE, 0, oneShotTimerCallback); + if ((xOneShotTimer != NULL)) { + xTimerStart(xOneShotTimer, 0); + } } // Complete hacky command which will be removed shortly @@ -28,12 +214,12 @@ void setPWM(uint8_t *values, size_t len) { switch (values[0]) { case 0x01: // initialzie { - if (len < 5) { + if (len < 6) { return; } uint8_t pwm = values[1]; uint16_t frequency = values[2] << 8 | values[3]; - uint8_t duty = values[4]; + uint16_t duty = values[4] << 8 | values[5]; bk_pwm_initialize(pwm, frequency, duty); break; } @@ -57,12 +243,12 @@ void setPWM(uint8_t *values, size_t len) { } case 0x04: // update param { - if (len < 5) { + if (len < 6) { return; } uint8_t pwm = values[1]; uint16_t frequency = values[2] << 8 | values[3]; - uint8_t duty = values[4]; + uint16_t duty = values[4] << 8 | values[5]; bk_pwm_update_param(pwm, frequency, duty); } default: @@ -70,123 +256,7 @@ void setPWM(uint8_t *values, size_t len) { } } -void setRGBCW(struct RGBCW *rgbcw) { - if (rgbcw->r) { - bk_pwm_initialize(RED, get_led_frequency(rgbcw->r, 2.8), 10); - bk_pwm_start(RED); - } else { - // FIXME: same as in the init ^^ should be stop - bk_pwm_initialize(RED, 100, 10); - bk_pwm_start(RED); - } - - if (rgbcw->g) { - bk_pwm_initialize(GREEN, get_led_frequency(rgbcw->g, 2.8), 10); - bk_pwm_start(GREEN); - } else { - // FIXME: same as in the init ^^ should be stop - bk_pwm_initialize(GREEN, 100, 10); - bk_pwm_start(GREEN); - } - - if (rgbcw->b) { - bk_pwm_initialize(BLUE, get_led_frequency(rgbcw->b, 2.8), 10); - bk_pwm_start(BLUE); - } else { - // FIXME: same as in the init ^^ should be stop - bk_pwm_initialize(BLUE, 100, 10); - bk_pwm_start(BLUE); - } - - if (rgbcw->c) { - uint16_t value = rgbcw->c; - if (value > 100) { - value = 100; - } - value = value * 10; - bk_pwm_initialize(COLD, value, 50); - bk_pwm_start(COLD); - } else { - bk_pwm_initialize(COLD, 0, 50); - bk_pwm_stop(COLD); - } - - if (rgbcw->w) { - uint16_t value = rgbcw->w; - if (value > 100) { - value = 100; - } - value = value * 10; - value = 1000 - value; // the motor is inverted 1000 is the slowest value - bk_pwm_initialize(WARM, value, 10); - bk_pwm_start(WARM); - } else { - bk_pwm_initialize(WARM, 0, 10); - bk_pwm_stop(WARM); - } -} - -// FIXME: this is where we want to shut down everything exept ble -void power_save_mode() { - // FIXME: this is as it is stupid and bad but it works this way.. - // TODO: figure out how to just disable pwm without setting defaults first ^^ - // TODO: update pwm also is not working like it should - bk_pwm_stop(RED); - bk_pwm_stop(GREEN); - bk_pwm_stop(BLUE); - bk_pwm_stop(COLD); - bk_pwm_stop(WARM); - - bk_pwm_initialize(RED, 100, 50); - bk_pwm_start(RED); - bk_pwm_initialize(GREEN, 100, 50); - bk_pwm_start(GREEN); - bk_pwm_initialize(BLUE, 100, 50); - bk_pwm_start(BLUE); - // FIXME: laser is still a puzzle to me - bk_pwm_initialize(COLD, 100, 50); - bk_pwm_start(COLD); - bk_pwm_initialize(WARM, 1000, 50); - bk_pwm_start(WARM); - - // FIXME: doing this will start all leds to full color ^^ - // TODO: figgure out how to reverse the level in the configuration of each PWM - // bk_pwm_stop(RED); - // bk_pwm_stop(GREEN); - // bk_pwm_stop(BLUE); - // bk_pwm_stop(COLD); - // bk_pwm_stop(WARM); -} - void init_led_thread(void *arg) { - // FIXME: this is as it is stupid and bad but it works this way.. - // TODO: figure out how to just disable pwm without setting defaults first ^^ - // TODO: update pwm also is not working like it should - bk_pwm_stop(RED); - bk_pwm_stop(GREEN); - bk_pwm_stop(BLUE); - bk_pwm_stop(COLD); - bk_pwm_stop(WARM); - - bk_pwm_initialize(RED, 100, 50); - bk_pwm_start(RED); - bk_pwm_initialize(GREEN, 100, 50); - bk_pwm_start(GREEN); - bk_pwm_initialize(BLUE, 100, 50); - bk_pwm_start(BLUE); - // FIXME: laser is still a puzzle to me - bk_pwm_initialize(COLD, 100, 50); - bk_pwm_start(COLD); - bk_pwm_initialize(WARM, 1000, 50); - bk_pwm_start(WARM); - - // FIXME: doing this will start all leds to full color ^^ - // TODO: figgure out how to reverse the level in the configuration of each PWM - // bk_pwm_stop(RED); - // bk_pwm_stop(GREEN); - // bk_pwm_stop(BLUE); - // bk_pwm_stop(COLD); - // bk_pwm_stop(WARM); - + power_save_mode(); rtos_delete_thread(NULL); } -- cgit v1.2.3