summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Collyer <scollyer@google.com>2017-06-26 14:22:19 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-07-15 11:39:01 -0700
commitfebb4381c1b8d2730e13d8e8605e178da4413919 (patch)
treed93edeeea01f9f970193f8a6ef0fd72661ff0268
parente99d9795d563f063a9dde1017c55d061affccae9 (diff)
downloadchrome-ec-febb4381c1b8d2730e13d8e8605e178da4413919.tar.gz
eve: Changes to incoporate gradients to LED transitions
Current LED color changes are abrupt. This CL adds a new function which steps from one color (some RGB combo) to another gradually. When the new or old color is white, then precomputed steps computed from HSI space are used. Other transisions involving either just red/green use linear steps computed dynamially based on the total change. This CL also creates a new task for the LED code so that the color transitions won't hold off other hook tasks for too long a period of time. Changed the LED tick time to be 500 msec as it's no longer tied to the hook tick. BUG=b:35584895 BRANCH=eve TEST=Using EC console command 'battfake', changed reported battery charge level and verified that the LED patterns still work as expected. In addition, had debug code which printed to the console what the R, G, B duty cycle levels were for each transition and verified the expected values were achieved. Visually compared version with transitions to the previous version which changed abruptly. Change-Id: I4e7c19556db9ed6037f2afa05cf348d19aeb18bb Signed-off-by: Scott Collyer <scollyer@google.com> Reviewed-on: https://chromium-review.googlesource.com/562543 Commit-Ready: Scott Collyer <scollyer@chromium.org> Tested-by: Scott Collyer <scollyer@chromium.org> Reviewed-by: Shawn N <shawnn@chromium.org>
-rw-r--r--board/eve/ec.tasklist1
-rw-r--r--board/eve/led.c277
2 files changed, 237 insertions, 41 deletions
diff --git a/board/eve/ec.tasklist b/board/eve/ec.tasklist
index 16f7ec7ec5..a3dd5b05fd 100644
--- a/board/eve/ec.tasklist
+++ b/board/eve/ec.tasklist
@@ -22,6 +22,7 @@
#define CONFIG_TASK_LIST \
TASK_ALWAYS(HOOKS, hook_task, NULL, LARGER_TASK_STACK_SIZE) \
+ TASK_ALWAYS(LED, led_task, NULL, TASK_STACK_SIZE) \
TASK_ALWAYS(CHG_RAMP, chg_ramp_task, NULL, TASK_STACK_SIZE) \
TASK_ALWAYS(USB_CHG, usb_charger_task, NULL, TASK_STACK_SIZE) \
TASK_ALWAYS(CHARGER, charger_task, NULL, LARGER_TASK_STACK_SIZE) \
diff --git a/board/eve/led.c b/board/eve/led.c
index 1295dc8cc9..9574ff1922 100644
--- a/board/eve/led.c
+++ b/board/eve/led.c
@@ -14,21 +14,27 @@
#include "hooks.h"
#include "led_common.h"
#include "pwm.h"
+#include "math_util.h"
#include "registers.h"
+#include "task.h"
#include "util.h"
#define CPRINTF(format, args...) cprintf(CC_PWM, format, ## args)
#define CPRINTS(format, args...) cprints(CC_PWM, format, ## args)
-#define LED_TICKS_PER_BEAT 2
+#define LED_TICK_TIME (500 * MSEC)
+#define LED_TICKS_PER_BEAT 1
#define NUM_PHASE 2
#define DOUBLE_TAP_TICK_LEN (LED_TICKS_PER_BEAT * 8)
+#define LED_FRAC_BITS 4
+#define LED_STEP_MSEC 45
static int led_debug;
static int double_tap;
static int double_tap_tick_count;
static int led_pattern;
static int led_ticks;
+static enum led_color led_current_color;
const enum ec_led_id supported_led_ids[] = {
EC_LED_ID_LEFT_LED, EC_LED_ID_RIGHT_LED};
@@ -106,8 +112,8 @@ static const uint8_t color_brightness[LED_COLOR_COUNT][PWM_CHAN_PER_LED] = {
[LED_GREEN] = {0, 80, 0},
[LED_BLUE] = {0, 0, 80},
[LED_WHITE] = {100, 100, 100},
- [LED_RED_2_3] = {53, 0, 0},
- [LED_RED_1_3] = {27, 0, 0},
+ [LED_RED_2_3] = {40, 0, 0},
+ [LED_RED_1_3] = {20, 0, 0},
};
/*
@@ -136,13 +142,29 @@ static const struct range_map pattern_tbl[] = {
{100, SOLID_GREEN},
};
+enum led_state_change {
+ LED_STATE_INTENSITY_DOWN,
+ LED_STATE_INTENSITY_UP,
+ LED_STATE_DONE,
+};
+
+/*
+ * The PWM % on levels to transition from intensity 0 (black) to intensity 1.0
+ * (white) in the HSI color space converted back to RGB space (0 - 255) and
+ * converted to a % for PWM. This table is used for Red <--> White and Green
+ * <--> Transitions. In HSI space white = (0, 0, 1), red = (0, .5, .33), green =
+ * (120, .5, .33). For the transitions of interest only S and I are changed and
+ * they are changed linearly in HSI space.
+ */
+static const uint8_t trans_steps[] = {0, 4, 9, 16, 24, 33, 44, 56, 69, 84, 100};
+
/**
* Set LED color
*
- * @param color Enumerated color value
+ * @param pwm Pointer to 3 element RGB color level (0 -> 100)
* @param side Left LED, Right LED, or both LEDs
*/
-static void set_color(enum led_color color, enum led_side side)
+static void set_color(const uint8_t *pwm, enum led_side side)
{
int i;
static uint8_t saved_duty[LED_BOTH][PWM_CHAN_PER_LED];
@@ -150,12 +172,10 @@ static void set_color(enum led_color color, enum led_side side)
/* Set color for left LED */
if (side == LED_LEFT || side == LED_BOTH) {
for (i = 0; i < PWM_CHAN_PER_LED; i++) {
- if (saved_duty[LED_LEFT][i] !=
- color_brightness[color][i]) {
+ if (saved_duty[LED_LEFT][i] != pwm[i]) {
pwm_set_duty(PWM_CH_LED_L_RED + i,
- 100 - color_brightness[color][i]);
- saved_duty[LED_LEFT][i] =
- color_brightness[color][i];
+ 100 - pwm[i]);
+ saved_duty[LED_LEFT][i] = pwm[i];
}
}
}
@@ -163,12 +183,10 @@ static void set_color(enum led_color color, enum led_side side)
/* Set color for right LED */
if (side == LED_RIGHT || side == LED_BOTH) {
for (i = 0; i < PWM_CHAN_PER_LED; i++) {
- if (saved_duty[LED_RIGHT][i] !=
- color_brightness[color][i]) {
+ if (saved_duty[LED_RIGHT][i] != pwm[i]) {
pwm_set_duty(PWM_CH_LED_R_RED + i,
- 100 - color_brightness[color][i]);
- saved_duty[LED_RIGHT][i] =
- color_brightness[color][i];
+ 100 - pwm[i]);
+ saved_duty[LED_RIGHT][i] = pwm[i];
}
}
}
@@ -213,6 +231,164 @@ void led_register_double_tap(void)
double_tap = 1;
}
+static void led_change_color(int old_idx, int new_idx, enum led_side side)
+{
+ int i;
+ int step;
+ int state;
+ uint8_t rgb_current[3];
+ const uint8_t *rgb_target;
+ uint8_t trans[ARRAY_SIZE(trans_steps)];
+ int increase = 0;
+
+ /*
+ * Using the color indices, poplulate the current and target R, G, B
+ * arrays. The arrays are indexed R = 0, G = 1, B = 2. If the target of
+ * any of the 3 is greater than the current, then this color change is
+ * an increase in intensity. Otherwise, it's a decrease.
+ */
+ rgb_target = color_brightness[new_idx];
+ for (i = 0; i < PWM_CHAN_PER_LED; i++) {
+ rgb_current[i] = color_brightness[old_idx][i];
+ if (rgb_current[i] < rgb_target[i]) {
+ /* increase in color */
+ increase = 1;
+ }
+ }
+ /* Check to see if increasing or decreasing color */
+ if (increase) {
+ state = LED_STATE_INTENSITY_UP;
+ /* First entry of transition table == current level */
+ step = 1;
+ } else {
+ /* Last entry of transition table == current level */
+ step = ARRAY_SIZE(trans_steps) - 2;
+ state = LED_STATE_INTENSITY_DOWN;
+ }
+
+ /*
+ * Populate transition table based on the number of R, G, B components
+ * changing. If only 1 componenet is changing, then can just do linear
+ * steps over the range. If more than 1 component is changing, then
+ * this is a white <--> color transition and will use
+ * the precomputed steps which are derived by converting to HSI space
+ * and then linearly transitioning S and I to go from the starting color
+ * to white and vice versa.
+ */
+ if (old_idx == LED_WHITE || new_idx == LED_WHITE) {
+ for (i = 0; i < ARRAY_SIZE(trans_steps); i++)
+ trans[i] = trans_steps[i];
+ } else {
+ int delta_per_step;
+ int step_value;
+ int start_lvl;
+ int total_change;
+ /* Assume that the R component (index = 0) is changing */
+ int rgb_index = 0;
+
+ /*
+ * Since the new or old color is not white, then this change
+ * must involve only either red or green. There are no red <-->
+ * green transitions. So only 1 color is being changed in this
+ * case. Assume it's red (index = 0), but check if it's green
+ * (index = 1).
+ */
+
+ if (old_idx == LED_GREEN || new_idx == LED_GREEN)
+ rgb_index = 1;
+
+ /*
+ * Determine the total change assuming current level is higher
+ * than target level. The transitions steps are always ordered
+ * lower to higher. The starting index is adjusted if intensity
+ * is decreasing.
+ */
+ start_lvl = rgb_target[rgb_index];
+
+ if (state == LED_STATE_INTENSITY_UP)
+ /*
+ * Increasing in intensity, current level or R/G is
+ * the starting level.
+ */
+ start_lvl = rgb_current[rgb_index];
+
+ /*
+ * Compute change per step using fractional bits. The step
+ * change accumulates fractional bits and is truncated after
+ * rounding before being added to the starting value.
+ */
+ total_change = ABS(rgb_current[rgb_index] -
+ rgb_target[rgb_index]);
+ delta_per_step = (total_change << LED_FRAC_BITS)
+ / (ARRAY_SIZE(trans_steps) - 1);
+ step_value = 0;
+ for (i = 0; i < ARRAY_SIZE(trans_steps); i++) {
+ trans[i] = start_lvl +
+ ((step_value +
+ (1 << (LED_FRAC_BITS - 1)))
+ >> LED_FRAC_BITS);
+ step_value += delta_per_step;
+ }
+ }
+
+ /* Will loop here until the color change is complete. */
+ while (state != LED_STATE_DONE) {
+ int change = 0;
+
+ if (state == LED_STATE_INTENSITY_DOWN) {
+ /*
+ * Colors are going from higher to lower level. If the
+ * current level of R, G, or B is higher than both
+ * the next step in the transition table and and the
+ * target level, then move to the larger of the two. The
+ * MAX is used to make sure that it doens't drop below
+ * the target level.
+ */
+ for (i = 0; i < PWM_CHAN_PER_LED; i++) {
+ if ((rgb_current[i] > rgb_target[i]) &&
+ (rgb_current[i] >= trans[step])) {
+ rgb_current[i] = MAX(trans[step],
+ rgb_target[i]);
+ change = 1;
+ }
+ }
+ /*
+ * If nothing changed this iteration, or if lowest table
+ * entry has been used, then the change is complete.
+ */
+ if (!change || --step < 0)
+ state = LED_STATE_DONE;
+
+ } else if (state == LED_STATE_INTENSITY_UP) {
+ /*
+ * Colors are going from lower to higher level. If the
+ * current level of R, G, B is lower than both the
+ * target level and the transition table entry for a
+ * given color, then move up to the MIN of next
+ * transition step and target level.
+ */
+ for (i = 0; i < PWM_CHAN_PER_LED; i++) {
+ if ((rgb_current[i] < rgb_target[i]) &&
+ (rgb_current[i] <= trans[step])) {
+ rgb_current[i] = MIN(trans[step],
+ rgb_target[i]);
+ change = 1;
+ }
+ }
+ /*
+ * If nothing changed this iteration, or if highest
+ * table entry has been used, then the change is
+ * complete.
+ */
+ if (!change || ++step >= ARRAY_SIZE(trans_steps))
+ state = LED_STATE_DONE;
+ }
+ /* Apply current R, G, B levels */
+ set_color(rgb_current, side);
+ msleep(LED_STEP_MSEC);
+ }
+}
+
static void led_manage_pattern(int side)
{
int color;
@@ -222,8 +398,13 @@ static void led_manage_pattern(int side)
phase = led_ticks < LED_TICKS_PER_BEAT * pattern[led_pattern].len[0] ?
0 : 1;
color = pattern[led_pattern].color[phase];
+ /* If color is changing, then manage the transition */
+ if (led_current_color != color) {
+ led_change_color(led_current_color, color, side);
+ led_current_color = color;
+ }
/* Set color for the current phase */
- set_color(color, side);
+ set_color(color_brightness[color], side);
/*
* Update led_ticks. If the len field is 0, then the pattern
@@ -313,7 +494,7 @@ static void eve_led_set_power_battery(void)
* on the side with the charger is turned on.
*/
if (side != LED_BOTH)
- set_color(LED_OFF, side ^ 1);
+ set_color(color_brightness[LED_OFF], side ^ 1);
/* Update LED pattern */
led_manage_pattern(side);
}
@@ -334,7 +515,7 @@ static void led_init(void)
pwm_enable(PWM_CH_LED_R_GREEN, 1);
pwm_enable(PWM_CH_LED_R_BLUE, 1);
- set_color(LED_OFF, LED_BOTH);
+ set_color(color_brightness[LED_OFF], LED_BOTH);
led_pattern = OFF;
led_ticks = 0;
double_tap_tick_count = 0;
@@ -342,21 +523,30 @@ static void led_init(void)
/* After pwm_pin_init() */
DECLARE_HOOK(HOOK_INIT, led_init, HOOK_PRIO_DEFAULT);
-/**
- * Called by hook task every 250 ms
- */
-static void led_tick(void)
+void led_task(void)
{
- if (led_debug == 1)
- return;
+ uint32_t start_time;
+ uint32_t task_duration;
+
+ while (1) {
- if (led_auto_control_is_enabled(EC_LED_ID_LEFT_LED) &&
- led_auto_control_is_enabled(EC_LED_ID_RIGHT_LED)) {
- eve_led_set_power_battery();
- return;
+ start_time = get_time().le.lo;
+
+ if (led_auto_control_is_enabled(EC_LED_ID_LEFT_LED) &&
+ led_auto_control_is_enabled(EC_LED_ID_RIGHT_LED) &&
+ led_debug != 1) {
+ eve_led_set_power_battery();
+ }
+ /* Compute time for this iteration */
+ task_duration = get_time().le.lo - start_time;
+ /*
+ * Compute wait time required to for next desired LED tick. If
+ * the duration exceeds the tick time, then don't sleep.
+ */
+ if (task_duration < LED_TICK_TIME)
+ usleep(LED_TICK_TIME - task_duration);
}
}
-DECLARE_HOOK(HOOK_TICK, led_tick, HOOK_PRIO_DEFAULT);
/******************************************************************/
/* Console commands */
@@ -364,6 +554,7 @@ static int command_led(int argc, char **argv)
{
int side = LED_BOTH;
char *e;
+ enum led_color color;
if (argc > 1) {
if (argc > 2) {
@@ -377,19 +568,23 @@ static int command_led(int argc, char **argv)
if (!strcasecmp(argv[1], "debug")) {
led_debug ^= 1;
CPRINTF("led_debug = %d\n", led_debug);
- } else if (!strcasecmp(argv[1], "off")) {
- set_color(LED_OFF, side);
- } else if (!strcasecmp(argv[1], "red")) {
- set_color(LED_RED, side);
- } else if (!strcasecmp(argv[1], "green")) {
- set_color(LED_GREEN, side);
- } else if (!strcasecmp(argv[1], "blue")) {
- set_color(LED_BLUE, side);
- } else if (!strcasecmp(argv[1], "white")) {
- set_color(LED_WHITE, side);
- } else {
- return EC_ERROR_PARAM1;
+ return EC_SUCCESS;
}
+
+ if (!strcasecmp(argv[1], "off"))
+ color = LED_OFF;
+ else if (!strcasecmp(argv[1], "red"))
+ color = LED_RED;
+ else if (!strcasecmp(argv[1], "green"))
+ color = LED_GREEN;
+ else if (!strcasecmp(argv[1], "blue"))
+ color = LED_BLUE;
+ else if (!strcasecmp(argv[1], "white"))
+ color = LED_WHITE;
+ else
+ return EC_ERROR_PARAM1;
+
+ set_color(color_brightness[color], side);
}
return EC_SUCCESS;
}