diff options
Diffstat (limited to 'quantum/visualizer')
| -rw-r--r-- | quantum/visualizer/led_test.c | 170 | ||||
| -rw-r--r-- | quantum/visualizer/led_test.h | 41 | ||||
| -rw-r--r-- | quantum/visualizer/visualizer.c | 104 | ||||
| -rw-r--r-- | quantum/visualizer/visualizer.h | 24 | ||||
| -rw-r--r-- | quantum/visualizer/visualizer.mk | 28 | 
5 files changed, 342 insertions, 25 deletions
| diff --git a/quantum/visualizer/led_test.c b/quantum/visualizer/led_test.c new file mode 100644 index 0000000000..c2ea30b55b --- /dev/null +++ b/quantum/visualizer/led_test.c @@ -0,0 +1,170 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Fred Sundvik + +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 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. +*/ +#include "led_test.h" +#include "gfx.h" +#include "math.h" + +#define CROSSFADE_TIME 1000 +#define GRADIENT_TIME 3000 + +keyframe_animation_t led_test_animation = { +    .num_frames = 14, +    .loop = true, +    .frame_lengths = { +        gfxMillisecondsToTicks(1000), // fade in +        gfxMillisecondsToTicks(1000), // no op (leds on) +        gfxMillisecondsToTicks(1000), // fade out +        gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade +        gfxMillisecondsToTicks(GRADIENT_TIME), // left to rigt (outside in) +        gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade +        gfxMillisecondsToTicks(GRADIENT_TIME), // top_to_bottom +        0,           // mirror leds +        gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade +        gfxMillisecondsToTicks(GRADIENT_TIME), // left_to_right (mirrored, so inside out) +        gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade +        gfxMillisecondsToTicks(GRADIENT_TIME), // top_to_bottom +        0,           // normal leds +        gfxMillisecondsToTicks(CROSSFADE_TIME), // crossfade + +    }, +    .frame_functions = { +        keyframe_fade_in_all_leds, +        keyframe_no_operation, +        keyframe_fade_out_all_leds, +        keyframe_led_crossfade, +        keyframe_led_left_to_right_gradient, +        keyframe_led_crossfade, +        keyframe_led_top_to_bottom_gradient, +        keyframe_mirror_led_orientation, +        keyframe_led_crossfade, +        keyframe_led_left_to_right_gradient, +        keyframe_led_crossfade, +        keyframe_led_top_to_bottom_gradient, +        keyframe_normal_led_orientation, +        keyframe_led_crossfade, +    }, +}; + +static uint8_t fade_led_color(keyframe_animation_t* animation, int from, int to) { +    int frame_length = animation->frame_lengths[animation->current_frame]; +    int current_pos = frame_length - animation->time_left_in_frame; +    int delta = to - from; +    int luma = (delta * current_pos) / frame_length; +    luma += from; +    return luma; +} + +static void keyframe_fade_all_leds_from_to(keyframe_animation_t* animation, uint8_t from, uint8_t to) { +    uint8_t luma = fade_led_color(animation, from, to); +    color_t color = LUMA2COLOR(luma); +    gdispGClear(LED_DISPLAY, color); +} + +// TODO: Should be customizable per keyboard +#define NUM_ROWS 7 +#define NUM_COLS 7 + +static uint8_t crossfade_start_frame[NUM_ROWS][NUM_COLS]; +static uint8_t crossfade_end_frame[NUM_ROWS][NUM_COLS]; + +static uint8_t compute_gradient_color(float t, float index, float num) { +    const float two_pi = 2.0f * PI; +    float normalized_index = (1.0f - index / (num - 1)) * two_pi; +    float x = t * two_pi + normalized_index; +    float v = 0.5 * (cosf(x) + 1.0f); +    return (uint8_t)(255.0f * v); +} + +bool keyframe_fade_in_all_leds(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    keyframe_fade_all_leds_from_to(animation, 0, 255); +    return true; +} + +bool keyframe_fade_out_all_leds(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    keyframe_fade_all_leds_from_to(animation, 255, 0); +    return true; +} + +bool keyframe_led_left_to_right_gradient(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    float frame_length = animation->frame_lengths[animation->current_frame]; +    float current_pos = frame_length - animation->time_left_in_frame; +    float t = current_pos / frame_length; +    for (int i=0; i< NUM_COLS; i++) { +        uint8_t color = compute_gradient_color(t, i, NUM_COLS); +        gdispGDrawLine(LED_DISPLAY, i, 0, i, NUM_ROWS - 1, LUMA2COLOR(color)); +    } +    return true; +} + +bool keyframe_led_top_to_bottom_gradient(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    float frame_length = animation->frame_lengths[animation->current_frame]; +    float current_pos = frame_length - animation->time_left_in_frame; +    float t = current_pos / frame_length; +    for (int i=0; i< NUM_ROWS; i++) { +        uint8_t color = compute_gradient_color(t, i, NUM_ROWS); +        gdispGDrawLine(LED_DISPLAY, 0, i, NUM_COLS - 1, i, LUMA2COLOR(color)); +    } +    return true; +} + +static void copy_current_led_state(uint8_t* dest) { +    for (int i=0;i<NUM_ROWS;i++) { +        for (int j=0;j<NUM_COLS;j++) { +            dest[i*NUM_COLS + j] = gdispGGetPixelColor(LED_DISPLAY, j, i); +        } +    } +} +bool keyframe_led_crossfade(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    if (animation->first_update_of_frame) { +        copy_current_led_state(&crossfade_start_frame[0][0]); +        run_next_keyframe(animation, state); +        copy_current_led_state(&crossfade_end_frame[0][0]); +    } +    for (int i=0;i<NUM_ROWS;i++) { +        for (int j=0;j<NUM_COLS;j++) { +            color_t color  = LUMA2COLOR(fade_led_color(animation, crossfade_start_frame[i][j], crossfade_end_frame[i][j])); +            gdispGDrawPixel(LED_DISPLAY, j, i, color); +        } +    } +    return true; +} + +bool keyframe_mirror_led_orientation(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    (void)animation; +    gdispGSetOrientation(LED_DISPLAY, GDISP_ROTATE_180); +    return false; +} + +bool keyframe_normal_led_orientation(keyframe_animation_t* animation, visualizer_state_t* state) { +    (void)state; +    (void)animation; +    gdispGSetOrientation(LED_DISPLAY, GDISP_ROTATE_0); +    return false; +} diff --git a/quantum/visualizer/led_test.h b/quantum/visualizer/led_test.h new file mode 100644 index 0000000000..5e2325753e --- /dev/null +++ b/quantum/visualizer/led_test.h @@ -0,0 +1,41 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Fred Sundvik + +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 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. +*/ + +#ifndef TMK_VISUALIZER_LED_TEST_H_ +#define TMK_VISUALIZER_LED_TEST_H_ + +#include "visualizer.h" + +bool keyframe_fade_in_all_leds(keyframe_animation_t* animation, visualizer_state_t* state); +bool keyframe_fade_out_all_leds(keyframe_animation_t* animation, visualizer_state_t* state); +bool keyframe_led_left_to_right_gradient(keyframe_animation_t* animation, visualizer_state_t* state); +bool keyframe_led_top_to_bottom_gradient(keyframe_animation_t* animation, visualizer_state_t* state); +bool keyframe_led_crossfade(keyframe_animation_t* animation, visualizer_state_t* state); +bool keyframe_mirror_led_orientation(keyframe_animation_t* animation, visualizer_state_t* state); +bool keyframe_normal_led_orientation(keyframe_animation_t* animation, visualizer_state_t* state); + +extern keyframe_animation_t led_test_animation; + + +#endif /* TMK_VISUALIZER_LED_TEST_H_ */ diff --git a/quantum/visualizer/visualizer.c b/quantum/visualizer/visualizer.c index 605be30594..c240734055 100644 --- a/quantum/visualizer/visualizer.c +++ b/quantum/visualizer/visualizer.c @@ -23,9 +23,11 @@ SOFTWARE.  */  #include "visualizer.h" -#include "ch.h"  #include "config.h"  #include <string.h> +#ifdef PROTOCOL_CHIBIOS +#include "ch.h" +#endif  #ifdef LCD_ENABLE  #include "gfx.h" @@ -68,7 +70,6 @@ static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboa          status1->suspended == status2->suspended;  } -static event_source_t layer_changed_event;  static bool visualizer_enabled = false;  #define MAX_SIMULTANEOUS_ANIMATIONS 4 @@ -83,6 +84,18 @@ static remote_object_t* remote_objects[] = {  #endif +GDisplay* LCD_DISPLAY = 0; +GDisplay* LED_DISPLAY = 0; + +__attribute__((weak)) +GDisplay* get_lcd_display(void) { +    return gdispGetDisplay(0); +} + +__attribute__((weak)) +GDisplay* get_led_display(void) { +    return gdispGetDisplay(1); +}  void start_keyframe_animation(keyframe_animation_t* animation) {      animation->current_frame = -1; @@ -106,6 +119,8 @@ void stop_keyframe_animation(keyframe_animation_t* animation) {      animation->current_frame = animation->num_frames;      animation->time_left_in_frame = 0;      animation->need_update = true; +    animation->first_update_of_frame = false; +    animation->last_update_of_frame = false;      for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {          if (animations[i] == animation) {              animations[i] = NULL; @@ -120,12 +135,15 @@ void stop_all_keyframe_animations(void) {              animations[i]->current_frame = animations[i]->num_frames;              animations[i]->time_left_in_frame = 0;              animations[i]->need_update = true; +            animations[i]->first_update_of_frame = false; +            animations[i]->last_update_of_frame = false;              animations[i] = NULL;          }      }  } -static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systime_t delta, systime_t* sleep_time) { +static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systemticks_t delta, systemticks_t* sleep_time) { +    // TODO: Clean up this messy code      dprintf("Animation frame%d, left %d, delta %d\n", animation->current_frame,              animation->time_left_in_frame, delta);      if (animation->current_frame == animation->num_frames) { @@ -136,16 +154,20 @@ static bool update_keyframe_animation(keyframe_animation_t* animation, visualize         animation->current_frame = 0;         animation->time_left_in_frame = animation->frame_lengths[0];         animation->need_update = true; +       animation->first_update_of_frame = true;      } else {          animation->time_left_in_frame -= delta;          while (animation->time_left_in_frame <= 0) {              int left = animation->time_left_in_frame;              if (animation->need_update) {                  animation->time_left_in_frame = 0; +                animation->last_update_of_frame = true;                  (*animation->frame_functions[animation->current_frame])(animation, state); +                animation->last_update_of_frame = false;              }              animation->current_frame++;              animation->need_update = true; +            animation->first_update_of_frame = true;              if (animation->current_frame == animation->num_frames) {                  if (animation->loop) {                      animation->current_frame = 0; @@ -162,16 +184,32 @@ static bool update_keyframe_animation(keyframe_animation_t* animation, visualize      }      if (animation->need_update) {          animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state); +        animation->first_update_of_frame = false;      } -    int wanted_sleep = animation->need_update ? 10 : animation->time_left_in_frame; -    if ((unsigned)wanted_sleep < *sleep_time) { +    systemticks_t wanted_sleep = animation->need_update ? gfxMillisecondsToTicks(10) : (unsigned)animation->time_left_in_frame; +    if (wanted_sleep < *sleep_time) {          *sleep_time = wanted_sleep;      }      return true;  } +void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state) { +    int next_frame = animation->current_frame + 1; +    if (next_frame == animation->num_frames) { +        next_frame = 0; +    } +    keyframe_animation_t temp_animation = *animation; +    temp_animation.current_frame = next_frame; +    temp_animation.time_left_in_frame = animation->frame_lengths[next_frame]; +    temp_animation.first_update_of_frame = true; +    temp_animation.last_update_of_frame = false; +    temp_animation.need_update  = false; +    visualizer_state_t temp_state = *state; +    (*temp_animation.frame_functions[next_frame])(&temp_animation, &temp_state); +} +  bool keyframe_no_operation(keyframe_animation_t* animation, visualizer_state_t* state) {      (void)animation;      (void)state; @@ -303,12 +341,13 @@ bool enable_visualization(keyframe_animation_t* animation, visualizer_state_t* s  }  // TODO: Optimize the stack size, this is probably way too big -static THD_WORKING_AREA(visualizerThreadStack, 1024); -static THD_FUNCTION(visualizerThread, arg) { +static DECLARE_THREAD_STACK(visualizerThreadStack, 1024); +static DECLARE_THREAD_FUNCTION(visualizerThread, arg) {      (void)arg; -    event_listener_t event_listener; -    chEvtRegister(&layer_changed_event, &event_listener, 0); +    GListener event_listener; +    geventListenerInit(&event_listener); +    geventAttachSource(&event_listener, (GSourceHandle)¤t_status, 0);      visualizer_keyboard_status_t initial_status = {          .default_layer = 0xFFFFFFFF, @@ -335,12 +374,12 @@ static THD_FUNCTION(visualizerThread, arg) {              LCD_INT(state.current_lcd_color));  #endif -    systime_t sleep_time = TIME_INFINITE; -    systime_t current_time = chVTGetSystemTimeX(); +    systemticks_t sleep_time = TIME_INFINITE; +    systemticks_t current_time = gfxSystemTicks();      while(true) { -        systime_t new_time = chVTGetSystemTimeX(); -        systime_t delta = new_time - current_time; +        systemticks_t new_time = gfxSystemTicks(); +        systemticks_t delta = new_time - current_time;          current_time = new_time;          bool enabled = visualizer_enabled;          if (!same_status(&state.status, ¤t_status)) { @@ -373,6 +412,13 @@ static THD_FUNCTION(visualizerThread, arg) {                  update_keyframe_animation(animations[i], &state, delta, &sleep_time);              }          } +#ifdef LED_ENABLE +        gdispGFlush(LED_DISPLAY); +#endif + +#ifdef EMULATOR +        draw_emulator(); +#endif          // The animation can enable the visualizer          // And we might need to update the state when that happens          // so don't sleep @@ -380,7 +426,7 @@ static THD_FUNCTION(visualizerThread, arg) {              sleep_time = 0;          } -        systime_t after_update = chVTGetSystemTimeX(); +        systemticks_t after_update = gfxSystemTicks();          unsigned update_delta = after_update - current_time;          if (sleep_time != TIME_INFINITE) {              if (sleep_time > update_delta) { @@ -391,12 +437,24 @@ static THD_FUNCTION(visualizerThread, arg) {              }          }          dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time); -        chEvtWaitOneTimeout(EVENT_MASK(0), sleep_time); +#ifdef PROTOCOL_CHIBIOS +        // The gEventWait function really takes milliseconds, even if the documentation says ticks. +        // Unfortunately there's no generic ugfx conversion from system time to milliseconds, +        // so let's do it in a platform dependent way. + +        // On windows the system ticks is the same as milliseconds anyway +        if (sleep_time != TIME_INFINITE) { +            sleep_time = ST2MS(sleep_time); +        } +#endif +        geventEventWait(&event_listener, sleep_time);      }  #ifdef LCD_ENABLE      gdispCloseFont(state.font_fixed5x8);      gdispCloseFont(state.font_dejavusansbold12);  #endif + +    return 0;  }  void visualizer_init(void) { @@ -411,16 +469,26 @@ void visualizer_init(void) {  #ifdef USE_SERIAL_LINK      add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) );  #endif + +#ifdef LCD_ENABLE +    LCD_DISPLAY = get_lcd_display(); +#endif +#ifdef LED_ENABLE +    LED_DISPLAY = get_led_display(); +#endif +      // We are using a low priority thread, the idea is to have it run only      // when the main thread is sleeping during the matrix scanning -    chEvtObjectInit(&layer_changed_event); -    (void)chThdCreateStatic(visualizerThreadStack, sizeof(visualizerThreadStack), +    gfxThreadCreate(visualizerThreadStack, sizeof(visualizerThreadStack),                                VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL);  }  void update_status(bool changed) {      if (changed) { -        chEvtBroadcast(&layer_changed_event); +        GSourceListener* listener = geventGetSourceListener((GSourceHandle)¤t_status, NULL); +        if (listener) { +            geventSendEvent(listener); +        }      }  #ifdef USE_SERIAL_LINK      static systime_t last_update = 0; diff --git a/quantum/visualizer/visualizer.h b/quantum/visualizer/visualizer.h index 22798cda6b..45cfa9aa97 100644 --- a/quantum/visualizer/visualizer.h +++ b/quantum/visualizer/visualizer.h @@ -45,8 +45,18 @@ void visualizer_suspend(void);  // This should be called when the keyboard wakes up from suspend state  void visualizer_resume(void); -// If you need support for more than 8 keyframes per animation, you can change this -#define MAX_VISUALIZER_KEY_FRAMES 8 +// These functions are week, so they can be overridden by the keyboard +// if needed +GDisplay* get_lcd_display(void); +GDisplay* get_led_display(void); + +// For emulator builds, this function need to be implemented +#ifdef EMULATOR +void draw_emulator(void); +#endif + +// If you need support for more than 16 keyframes per animation, you can change this +#define MAX_VISUALIZER_KEY_FRAMES 16  struct keyframe_animation_t; @@ -95,12 +105,20 @@ typedef struct keyframe_animation_t {      // keyframe update functions      int current_frame;      int time_left_in_frame; +    bool first_update_of_frame; +    bool last_update_of_frame;      bool need_update;  } keyframe_animation_t; +extern GDisplay* LCD_DISPLAY; +extern GDisplay* LED_DISPLAY; +  void start_keyframe_animation(keyframe_animation_t* animation);  void stop_keyframe_animation(keyframe_animation_t* animation); +// This runs the next keyframe, but does not update the animation state +// Useful for crossfades for example +void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state);  // Some predefined keyframe functions that can be used by the user code  // Does nothing, useful for adding delays @@ -121,7 +139,7 @@ bool keyframe_enable_lcd_and_backlight(keyframe_animation_t* animation, visualiz  // directly from the initalize_user_visualizer function (the animation can be null)  bool enable_visualization(keyframe_animation_t* animation, visualizer_state_t* state); -// These two functions have to be implemented by the user +// These functions have to be implemented by the user  void initialize_user_visualizer(visualizer_state_t* state);  void update_user_visualizer_state(visualizer_state_t* state);  void user_visualizer_suspend(visualizer_state_t* state); diff --git a/quantum/visualizer/visualizer.mk b/quantum/visualizer/visualizer.mk index 13c5d31587..56525ffd93 100644 --- a/quantum/visualizer/visualizer.mk +++ b/quantum/visualizer/visualizer.mk @@ -21,21 +21,41 @@  # SOFTWARE.  GFXLIB = $(VISUALIZER_DIR)/ugfx +SRC += $(VISUALIZER_DIR)/visualizer.c +UINCDIR += $(GFXINC) $(VISUALIZER_DIR) +  ifdef LCD_ENABLE -include $(GFXLIB)/gfx.mk  UDEFS += -DLCD_ENABLE  ULIBS += -lm +USE_UGFX = yes  endif -SRC += $(GFXSRC) $(VISUALIZER_DIR)/visualizer.c -UINCDIR += $(GFXINC) $(VISUALIZER_DIR)  ifdef LCD_BACKLIGHT_ENABLE  SRC += $(VISUALIZER_DIR)/lcd_backlight.c +ifndef EMULATOR  SRC += lcd_backlight_hal.c +endif  UDEFS += -DLCD_BACKLIGHT_ENABLE  endif +ifdef LED_ENABLE +SRC += $(VISUALIZER_DIR)/led_test.c +UDEFS += -DLED_ENABLE +USE_UGFX = yes +endif + +ifdef USE_UGFX +include $(GFXLIB)/gfx.mk +SRC += $(GFXSRC) +UDEFS += $(patsubst %,-D%,$(patsubst -D%,%,$(GFXDEFS))) +ULIBS += $(patsubst %,-l%,$(patsubst -l%,%,$(GFXLIBS))) +endif +  ifndef VISUALIZER_USER  VISUALIZER_USER = visualizer_user.c  endif -SRC += $(VISUALIZER_USER)
\ No newline at end of file +SRC += $(VISUALIZER_USER) + +ifdef EMULATOR +UINCDIR += $(TMK_DIR)/common +endif
\ No newline at end of file | 
