diff options
| author | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2025-01-01 18:22:14 +0100 |
|---|---|---|
| committer | Bernhard Guillon <Bernhard.Guillon@begu.org> | 2025-01-01 18:22:14 +0100 |
| commit | f70b052b789dbac68651591031aabc0b372db8ec (patch) | |
| tree | 8d6fb9aa274200849251328d4aa1dbc71b06a014 /usr | |
| parent | b917dc4fa5a400141c75c81164f442dd43b23a28 (diff) | |
| download | wb3s-ble-nebula-galaxy-f70b052b789dbac68651591031aabc0b372db8ec.tar.gz wb3s-ble-nebula-galaxy-f70b052b789dbac68651591031aabc0b372db8ec.zip | |
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.
Diffstat (limited to 'usr')
| -rw-r--r-- | usr/space_light/include/space_light.h | 5 | ||||
| -rw-r--r-- | usr/space_light/src/device.c | 44 | ||||
| -rw-r--r-- | usr/space_light/src/space_light.c | 322 |
3 files changed, 229 insertions, 142 deletions
diff --git a/usr/space_light/include/space_light.h b/usr/space_light/include/space_light.h index e5a604a..b5d1f9f 100644 --- a/usr/space_light/include/space_light.h +++ b/usr/space_light/include/space_light.h @@ -11,8 +11,9 @@ struct RGBCW { void init_led_thread(void *arg); -void setRGBCW(struct RGBCW *rgbcw); -void power_save_mode(); +void set_rgbcw(struct RGBCW *rgbcw); + +void set_timer(uint16_t seconds); // WILL BE REMOVED just for testing void setPWM(uint8_t *values, size_t len); diff --git a/usr/space_light/src/device.c b/usr/space_light/src/device.c index 30bcf9e..8b87879 100644 --- a/usr/space_light/src/device.c +++ b/usr/space_light/src/device.c @@ -1,3 +1,28 @@ +/* + * The BLE code and the stripped down version of the SDK was taken and modified + * from here + * https://www.elektroda.com/rtvforum/topic3989434.html#20742145 + * + * Copyright (C) 2023 jitsirakowsk (Alex) + * + * The original code was taken modified and used for this project by + * Copyright (C) 2025 Bernhard Guillon <Bernhard.Guillon@begu.org> + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <https://www.gnu.org/licenses/>. + * + * SPDX: GPL-3.0 + * + */ #include "ble.h" #include "ble_api.h" @@ -20,10 +45,6 @@ #include "mcu_ps_pub.h" #include "start_type_pub.h" -#include "FreeRTOS.h" -#include "task.h" -#include "timers.h" - #include "space_light.h" #define ATT_DB_LENGTH 5 @@ -73,10 +94,6 @@ bk_attm_desc_t btl_att_db[ATT_DB_LENGTH] = { beken_semaphore_t app_sema = NULL; beken_semaphore_t ext_init_sema = NULL; -TimerHandle_t xOneShotTimer; - -static void oneShotTimerCallback(TimerHandle_t xTimer) { power_save_mode(); } - static void ble_event_callback(ble_event_t event, void *param) { switch (event) { case BLE_STACK_OK: @@ -165,11 +182,14 @@ static void ble_event_callback(ble_event_t event, void *param) { static void ble_write_callback(write_req_t *write_req) { switch (write_req->value[0]) { case 0x1: { + if (write_req->len != 6) { + return; + } struct RGBCW rgbcw = { write_req->value[1], write_req->value[2], write_req->value[3], write_req->value[4], write_req->value[5], }; - setRGBCW(&rgbcw); + set_rgbcw(&rgbcw); break; } case 0x2: @@ -177,11 +197,7 @@ static void ble_write_callback(write_req_t *write_req) { return; } uint16_t seconds = write_req->value[1] << 8 | write_req->value[2]; - xOneShotTimer = xTimerCreate("OneShot", seconds * 1000, pdFALSE, 0, - oneShotTimerCallback); - if ((xOneShotTimer != NULL)) { - xTimerStart(xOneShotTimer, 0); - } + set_timer(seconds); break; case 0xFF: setPWM(&write_req->value[1], write_req->len - 1); 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 <Bernhard.Guillon@begu.org> + * + * 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 <math.h> #include <space_light.h> #include <stdint.h> +#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); } |
