aboutsummaryrefslogtreecommitdiffstats
path: root/usr/space_light/src/space_light.c
diff options
context:
space:
mode:
authorBernhard Guillon <Bernhard.Guillon@begu.org>2025-01-01 18:22:14 +0100
committerBernhard Guillon <Bernhard.Guillon@begu.org>2025-01-01 18:22:14 +0100
commitf70b052b789dbac68651591031aabc0b372db8ec (patch)
tree8d6fb9aa274200849251328d4aa1dbc71b06a014 /usr/space_light/src/space_light.c
parentb917dc4fa5a400141c75c81164f442dd43b23a28 (diff)
downloadwb3s-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/space_light/src/space_light.c')
-rw-r--r--usr/space_light/src/space_light.c322
1 files changed, 196 insertions, 126 deletions
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);
}