RGB Matrix support for Massdrop CTRL/ALT (#5328)

* port Massdrop CTRL/ALT to use RGB Matrix

Co-authored-by: Matt Schneeberger <helluvamatt@gmail.com>

* Massdrop lighting support working

This commit is to get the Massdrop lighting code working again through use of the compilation define USE_MASSDROP_CONFIGURATOR added to a keymap's rules.mk.
Added keymaps for both CTRL and ALT named default_md and mac_md. These should be used if the Massdrop style lighting is desired.

* Updating config based on testing results with patrickmt & compile errors

* Updates for PR5328

For CTRL and ALT:
Moved location of new RGB Matrix macros from config_led.h to config.h.
Added RGB_MATRIX_LED_FLUSH_LIMIT (time between flushes) to config.h for correct LED driver update timing.
Re-added missing breathing code for when Massdrop configurator mode is defined.

* remove prilik keymap form PR
pull/5613/head
Daniel Prilik 6 years ago committed by Drashna Jaelre
parent 6acfceaeb4
commit 5f8fdefe3a
No known key found for this signature in database
GPG Key ID: 4C4221222CD5F9F0

@ -142,11 +142,28 @@ void rgb_matrix_update_pwm_buffers(void) {
} }
void rgb_matrix_set_color( int index, uint8_t red, uint8_t green, uint8_t blue ) { void rgb_matrix_set_color( int index, uint8_t red, uint8_t green, uint8_t blue ) {
#ifdef RGB_MATRIX_EXTRA_TOG
const bool is_key = g_rgb_leds[index].matrix_co.raw != 0xff;
if (
(rgb_matrix_config.enable == RGB_ZONE_KEYS && !is_key) ||
(rgb_matrix_config.enable == RGB_ZONE_UNDER && is_key)
) {
rgb_matrix_driver.set_color(index, 0, 0, 0);
return;
}
#endif
rgb_matrix_driver.set_color(index, red, green, blue); rgb_matrix_driver.set_color(index, red, green, blue);
} }
void rgb_matrix_set_color_all( uint8_t red, uint8_t green, uint8_t blue ) { void rgb_matrix_set_color_all( uint8_t red, uint8_t green, uint8_t blue ) {
#ifdef RGB_MATRIX_EXTRA_TOG
for (int i = 0; i < DRIVER_LED_TOTAL; i++) {
rgb_matrix_set_color(i, red, green, blue);
}
#else
rgb_matrix_driver.set_color_all(red, green, blue); rgb_matrix_driver.set_color_all(red, green, blue);
#endif
} }
bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) { bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) {
@ -494,7 +511,7 @@ void rgb_matrix_set_suspend_state(bool state) {
} }
void rgb_matrix_toggle(void) { void rgb_matrix_toggle(void) {
rgb_matrix_config.enable ^= 1; rgb_matrix_config.enable++;
if (!rgb_matrix_config.enable) { if (!rgb_matrix_config.enable) {
rgb_task_state = STARTING; rgb_task_state = STARTING;
} }

@ -73,11 +73,18 @@ typedef struct PACKED {
uint8_t modifier:1; uint8_t modifier:1;
} rgb_led; } rgb_led;
typedef enum {
RGB_ZONE_OFF = 0,
RGB_ZONE_ALL,
RGB_ZONE_KEYS,
RGB_ZONE_UNDER,
} rgb_zone_t;
typedef union { typedef union {
uint32_t raw; uint32_t raw;
struct PACKED { struct PACKED {
bool enable :1; uint8_t enable :2;
uint8_t mode :7; uint8_t mode :6;
uint8_t hue :8; uint8_t hue :8;
uint8_t sat :8; uint8_t sat :8;
uint8_t val :8; uint8_t val :8;

@ -35,7 +35,9 @@ void suspend_power_down_kb(void) {
*/ */
void suspend_power_down(void) void suspend_power_down(void)
{ {
#ifdef RGB_MATRIX_ENABLE
I2C3733_Control_Set(0); //Disable LED driver I2C3733_Control_Set(0); //Disable LED driver
#endif
suspend_power_down_kb(); suspend_power_down_kb();
} }
@ -75,10 +77,9 @@ void suspend_wakeup_init_kb(void) {
* FIXME: needs doc * FIXME: needs doc
*/ */
void suspend_wakeup_init(void) { void suspend_wakeup_init(void) {
/* If LEDs are set to enabled, enable the hardware */ #ifdef RGB_MATRIX_ENABLE
if (led_enabled) {
I2C3733_Control_Set(1); I2C3733_Control_Set(1);
} #endif
suspend_wakeup_init_kb(); suspend_wakeup_init_kb();
} }

@ -4,7 +4,10 @@ SRC += $(ARM_ATSAM_DIR)/adc.c
SRC += $(ARM_ATSAM_DIR)/clks.c SRC += $(ARM_ATSAM_DIR)/clks.c
SRC += $(ARM_ATSAM_DIR)/d51_util.c SRC += $(ARM_ATSAM_DIR)/d51_util.c
SRC += $(ARM_ATSAM_DIR)/i2c_master.c SRC += $(ARM_ATSAM_DIR)/i2c_master.c
SRC += $(ARM_ATSAM_DIR)/led_matrix.c ifeq ($(RGB_MATRIX_ENABLE),custom)
SRC += $(ARM_ATSAM_DIR)/led_matrix_programs.c
SRC += $(ARM_ATSAM_DIR)/led_matrix.c
endif
SRC += $(ARM_ATSAM_DIR)/main_arm_atsam.c SRC += $(ARM_ATSAM_DIR)/main_arm_atsam.c
SRC += $(ARM_ATSAM_DIR)/spi.c SRC += $(ARM_ATSAM_DIR)/spi.c
SRC += $(ARM_ATSAM_DIR)/startup.c SRC += $(ARM_ATSAM_DIR)/startup.c

@ -34,7 +34,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef MD_BOOTLOADER #ifndef MD_BOOTLOADER
#include "main_arm_atsam.h" #include "main_arm_atsam.h"
#ifdef RGB_MATRIX_ENABLE
#include "led_matrix.h" #include "led_matrix.h"
#include "rgb_matrix.h"
#endif
#include "issi3733_driver.h" #include "issi3733_driver.h"
#include "./usb/compiler.h" #include "./usb/compiler.h"
#include "./usb/udc.h" #include "./usb/udc.h"

@ -17,7 +17,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "arm_atsam_protocol.h" #include "arm_atsam_protocol.h"
#ifndef MD_BOOTLOADER #if !defined(MD_BOOTLOADER) && defined(RGB_MATRIX_ENABLE)
#include <string.h> #include <string.h>
@ -37,7 +37,7 @@ static uint8_t dma_sendbuf[I2C_DMA_MAX_SEND]; //Data being written to I2C
volatile uint8_t i2c_led_q_running; volatile uint8_t i2c_led_q_running;
#endif //MD_BOOTLOADER #endif // !defined(MD_BOOTLOADER) && defined(RGB_MATRIX_ENABLE)
void i2c0_init(void) void i2c0_init(void)
{ {
@ -112,7 +112,7 @@ void i2c0_stop(void)
} }
} }
#ifndef MD_BOOTLOADER #if !defined(MD_BOOTLOADER) && defined(RGB_MATRIX_ENABLE)
void i2c1_init(void) void i2c1_init(void)
{ {
DBGC(DC_I2C1_INIT_BEGIN); DBGC(DC_I2C1_INIT_BEGIN);
@ -583,4 +583,4 @@ uint8_t i2c_led_q_run(void)
return 1; return 1;
} }
#endif //MD_BOOTLOADER #endif // !defined(MD_BOOTLOADER) && defined(RGB_MATRIX_ENABLE)

@ -17,9 +17,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "arm_atsam_protocol.h" #include "arm_atsam_protocol.h"
#include "tmk_core/common/led.h" #include "tmk_core/common/led.h"
#include "rgb_matrix.h"
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#ifdef USE_MASSDROP_CONFIGURATOR
__attribute__((weak))
led_instruction_t led_instructions[] = { { .end = 1 } };
static void led_matrix_massdrop_config_override(int i);
#endif // USE_MASSDROP_CONFIGURATOR
extern rgb_config_t rgb_matrix_config;
extern rgb_counters_t g_rgb_counters;
void SERCOM1_0_Handler( void ) void SERCOM1_0_Handler( void )
{ {
if (SERCOM1->I2CM.INTFLAG.bit.ERROR) if (SERCOM1->I2CM.INTFLAG.bit.ERROR)
@ -51,14 +61,17 @@ void DMAC_0_Handler( void )
issi3733_driver_t issidrv[ISSI3733_DRIVER_COUNT]; issi3733_driver_t issidrv[ISSI3733_DRIVER_COUNT];
issi3733_led_t led_map[ISSI3733_LED_COUNT+1] = ISSI3733_LED_MAP; issi3733_led_t led_map[ISSI3733_LED_COUNT] = ISSI3733_LED_MAP;
issi3733_led_t *lede = led_map + ISSI3733_LED_COUNT; //End pointer of mapping RGB led_buffer[ISSI3733_LED_COUNT];
uint8_t gcr_desired; uint8_t gcr_desired;
uint8_t gcr_breathe;
uint8_t gcr_use;
uint8_t gcr_actual; uint8_t gcr_actual;
uint8_t gcr_actual_last; uint8_t gcr_actual_last;
#ifdef USE_MASSDROP_CONFIGURATOR
uint8_t gcr_breathe;
float breathe_mult;
float pomod;
#endif
#define ACT_GCR_NONE 0 #define ACT_GCR_NONE 0
#define ACT_GCR_INC 1 #define ACT_GCR_INC 1
@ -73,11 +86,14 @@ static uint8_t v_5v_cat_hit;
void gcr_compute(void) void gcr_compute(void)
{ {
uint8_t action = ACT_GCR_NONE; uint8_t action = ACT_GCR_NONE;
uint8_t gcr_use = gcr_desired;
#ifdef USE_MASSDROP_CONFIGURATOR
if (led_animation_breathing) if (led_animation_breathing)
{
gcr_use = gcr_breathe; gcr_use = gcr_breathe;
else }
gcr_use = gcr_desired; #endif
//If the 5v takes a catastrophic hit, disable the LED drivers briefly, assert auto gcr mode, min gcr and let the auto take over //If the 5v takes a catastrophic hit, disable the LED drivers briefly, assert auto gcr mode, min gcr and let the auto take over
if (v_5v < V5_CAT) if (v_5v < V5_CAT)
@ -151,6 +167,7 @@ void gcr_compute(void)
gcr_actual -= LED_GCR_STEP_AUTO; gcr_actual -= LED_GCR_STEP_AUTO;
gcr_min_counter = 0; gcr_min_counter = 0;
#ifdef USE_MASSDROP_CONFIGURATOR
//If breathe mode is active, the top end can fluctuate if the host can not supply enough current //If breathe mode is active, the top end can fluctuate if the host can not supply enough current
//So set the breathe GCR to where it becomes stable //So set the breathe GCR to where it becomes stable
if (led_animation_breathing == 1) if (led_animation_breathing == 1)
@ -160,12 +177,11 @@ void gcr_compute(void)
// and the same would happen maybe one or two more times. Therefore I'm favoring // and the same would happen maybe one or two more times. Therefore I'm favoring
// powering through one full breathe and letting gcr settle completely // powering through one full breathe and letting gcr settle completely
} }
#endif
} }
} }
} }
led_disp_t disp;
void issi3733_prepare_arrays(void) void issi3733_prepare_arrays(void)
{ {
memset(issidrv,0,sizeof(issi3733_driver_t) * ISSI3733_DRIVER_COUNT); memset(issidrv,0,sizeof(issi3733_driver_t) * ISSI3733_DRIVER_COUNT);
@ -178,111 +194,93 @@ void issi3733_prepare_arrays(void)
issidrv[i].addr = addrs[i]; issidrv[i].addr = addrs[i];
} }
issi3733_led_t *cur = led_map; for (uint8_t i = 0; i < ISSI3733_LED_COUNT; i++)
while (cur < lede)
{ {
//BYTE: 1 + (SW-1)*16 + (CS-1) //BYTE: 1 + (SW-1)*16 + (CS-1)
cur->rgb.g = issidrv[cur->adr.drv-1].pwm + 1 + ((cur->adr.swg-1)*16 + (cur->adr.cs-1)); led_map[i].rgb.g = issidrv[led_map[i].adr.drv-1].pwm + 1 + ((led_map[i].adr.swg-1)*16 + (led_map[i].adr.cs-1));
cur->rgb.r = issidrv[cur->adr.drv-1].pwm + 1 + ((cur->adr.swr-1)*16 + (cur->adr.cs-1)); led_map[i].rgb.r = issidrv[led_map[i].adr.drv-1].pwm + 1 + ((led_map[i].adr.swr-1)*16 + (led_map[i].adr.cs-1));
cur->rgb.b = issidrv[cur->adr.drv-1].pwm + 1 + ((cur->adr.swb-1)*16 + (cur->adr.cs-1)); led_map[i].rgb.b = issidrv[led_map[i].adr.drv-1].pwm + 1 + ((led_map[i].adr.swb-1)*16 + (led_map[i].adr.cs-1));
//BYTE: 1 + (SW-1)*2 + (CS-1)/8 //BYTE: 1 + (SW-1)*2 + (CS-1)/8
//BIT: (CS-1)%8 //BIT: (CS-1)%8
*(issidrv[cur->adr.drv-1].onoff + 1 + (cur->adr.swg-1)*2+(cur->adr.cs-1)/8) |= (1<<((cur->adr.cs-1)%8)); *(issidrv[led_map[i].adr.drv-1].onoff + 1 + (led_map[i].adr.swg-1)*2+(led_map[i].adr.cs-1)/8) |= (1<<((led_map[i].adr.cs-1)%8));
*(issidrv[cur->adr.drv-1].onoff + 1 + (cur->adr.swr-1)*2+(cur->adr.cs-1)/8) |= (1<<((cur->adr.cs-1)%8)); *(issidrv[led_map[i].adr.drv-1].onoff + 1 + (led_map[i].adr.swr-1)*2+(led_map[i].adr.cs-1)/8) |= (1<<((led_map[i].adr.cs-1)%8));
*(issidrv[cur->adr.drv-1].onoff + 1 + (cur->adr.swb-1)*2+(cur->adr.cs-1)/8) |= (1<<((cur->adr.cs-1)%8)); *(issidrv[led_map[i].adr.drv-1].onoff + 1 + (led_map[i].adr.swb-1)*2+(led_map[i].adr.cs-1)/8) |= (1<<((led_map[i].adr.cs-1)%8));
cur++;
} }
} }
void disp_calc_extents(void) void led_matrix_prepare(void)
{ {
issi3733_led_t *cur = led_map; for (uint8_t i = 0; i < ISSI3733_LED_COUNT; i++)
disp.left = 1e10;
disp.right = -1e10;
disp.top = -1e10;
disp.bottom = 1e10;
while (cur < lede)
{ {
if (cur->x < disp.left) disp.left = cur->x; *led_map[i].rgb.r = 0;
if (cur->x > disp.right) disp.right = cur->x; *led_map[i].rgb.g = 0;
if (cur->y < disp.bottom) disp.bottom = cur->y; *led_map[i].rgb.b = 0;
if (cur->y > disp.top) disp.top = cur->y;
cur++;
} }
disp.width = disp.right - disp.left;
disp.height = disp.top - disp.bottom;
disp.max_distance = sqrtf(powf(disp.width, 2) + powf(disp.height, 2));
} }
void disp_pixel_setup(void) void led_set_one(int i, uint8_t r, uint8_t g, uint8_t b)
{ {
issi3733_led_t *cur = led_map; if (i < ISSI3733_LED_COUNT)
while (cur < lede)
{ {
cur->px = (cur->x - disp.left) / disp.width * 100; #ifdef USE_MASSDROP_CONFIGURATOR
cur->py = (cur->y - disp.bottom) / disp.height * 100; led_matrix_massdrop_config_override(i);
*cur->rgb.r = 0; #else
*cur->rgb.g = 0; led_buffer[i].r = r;
*cur->rgb.b = 0; led_buffer[i].g = g;
led_buffer[i].b = b;
cur++; #endif
} }
} }
void led_matrix_prepare(void) void led_set_all(uint8_t r, uint8_t g, uint8_t b)
{ {
disp_calc_extents(); for (uint8_t i = 0; i < ISSI3733_LED_COUNT; i++)
disp_pixel_setup(); {
led_set_one(i, r, g, b);
}
} }
uint8_t led_enabled; void init(void)
float led_animation_speed; {
uint8_t led_animation_direction; DBGC(DC_LED_MATRIX_INIT_BEGIN);
uint8_t led_animation_orientation;
uint8_t led_animation_breathing; issi3733_prepare_arrays();
uint8_t led_animation_breathe_cur;
uint8_t breathe_step; led_matrix_prepare();
uint8_t breathe_dir;
uint8_t led_animation_circular; gcr_min_counter = 0;
uint64_t led_next_run; v_5v_cat_hit = 0;
uint8_t led_animation_id; DBGC(DC_LED_MATRIX_INIT_COMPLETE);
uint8_t led_lighting_mode; }
issi3733_led_t *led_cur;
uint8_t led_per_run = 15;
float breathe_mult;
__attribute__ ((weak)) void flush(void)
void led_matrix_run(void)
{ {
float ro; #ifdef USE_MASSDROP_CONFIGURATOR
float go; if (!led_enabled) { return; } //Prevent calculations and I2C traffic if LED drivers are not enabled
float bo; #else
float po; if (!sr_exp_data.bit.SDB_N) { return; } //Prevent calculations and I2C traffic if LED drivers are not enabled
#endif
uint8_t led_this_run = 0; // Wait for previous transfer to complete
led_setup_t *f = (led_setup_t*)led_setups[led_animation_id]; while (i2c_led_q_running) {}
if (led_cur == 0) //Denotes start of new processing cycle in the case of chunked processing // Copy buffer to live DMA region
for (uint8_t i = 0; i < ISSI3733_LED_COUNT; i++)
{ {
led_cur = led_map; *led_map[i].rgb.r = led_buffer[i].r;
*led_map[i].rgb.g = led_buffer[i].g;
disp.frame += 1; *led_map[i].rgb.b = led_buffer[i].b;
}
#ifdef USE_MASSDROP_CONFIGURATOR
breathe_mult = 1; breathe_mult = 1;
if (led_animation_breathing) if (led_animation_breathing)
{ {
led_animation_breathe_cur += breathe_step * breathe_dir; //+60us 119 LED
led_animation_breathe_cur += BREATHE_STEP * breathe_dir;
if (led_animation_breathe_cur >= BREATHE_MAX_STEP) if (led_animation_breathe_cur >= BREATHE_MAX_STEP)
breathe_dir = -1; breathe_dir = -1;
@ -294,77 +292,107 @@ void led_matrix_run(void)
if (breathe_mult > 1) breathe_mult = 1; if (breathe_mult > 1) breathe_mult = 1;
else if (breathe_mult < 0) breathe_mult = 0; else if (breathe_mult < 0) breathe_mult = 0;
} }
}
uint8_t fcur = 0; //This should only be performed once per frame
uint8_t fmax = 0; pomod = (float)((g_rgb_counters.tick / 10) % (uint32_t)(1000.0f / led_animation_speed)) / 10.0f * led_animation_speed;
pomod *= 100.0f;
pomod = (uint32_t)pomod % 10000;
pomod /= 100.0f;
#endif // USE_MASSDROP_CONFIGURATOR
uint8_t drvid;
//Frames setup //NOTE: GCR does not need to be timed with LED processing, but there is really no harm
while (f[fcur].end != 1) if (gcr_actual != gcr_actual_last)
{ {
fcur++; //Count frames for (drvid=0;drvid<ISSI3733_DRIVER_COUNT;drvid++)
I2C_LED_Q_GCR(drvid); //Queue data
gcr_actual_last = gcr_actual;
} }
fmax = fcur; //Store total frames count for (drvid=0;drvid<ISSI3733_DRIVER_COUNT;drvid++)
I2C_LED_Q_PWM(drvid); //Queue data
while (led_cur < lede && led_this_run < led_per_run) i2c_led_q_run();
{ }
ro = 0;
go = 0;
bo = 0;
if (led_lighting_mode == LED_MODE_KEYS_ONLY && led_cur->scan == 255) void led_matrix_indicators(void)
{ {
//Do not act on this LED uint8_t kbled = keyboard_leds();
} if (kbled && rgb_matrix_config.enable)
else if (led_lighting_mode == LED_MODE_NON_KEYS_ONLY && led_cur->scan != 255)
{
//Do not act on this LED
}
else if (led_lighting_mode == LED_MODE_INDICATORS_ONLY)
{
//Do not act on this LED (Only show indicators)
}
else
{ {
//Act on LED for (uint8_t i = 0; i < ISSI3733_LED_COUNT; i++)
for (fcur = 0; fcur < fmax; fcur++)
{ {
if (
if (led_animation_circular) { #if USB_LED_NUM_LOCK_SCANCODE != 255
po = sqrtf((powf(fabsf((disp.width / 2) - (led_cur->x - disp.left)), 2) + powf(fabsf((disp.height / 2) - (led_cur->y - disp.bottom)), 2))) / disp.max_distance * 100; (led_map[i].scan == USB_LED_NUM_LOCK_SCANCODE && (kbled & (1<<USB_LED_NUM_LOCK))) ||
} #endif //NUM LOCK
else { #if USB_LED_CAPS_LOCK_SCANCODE != 255
if (led_animation_orientation) (led_map[i].scan == USB_LED_CAPS_LOCK_SCANCODE && (kbled & (1<<USB_LED_CAPS_LOCK))) ||
#endif //CAPS LOCK
#if USB_LED_SCROLL_LOCK_SCANCODE != 255
(led_map[i].scan == USB_LED_SCROLL_LOCK_SCANCODE && (kbled & (1<<USB_LED_SCROLL_LOCK))) ||
#endif //SCROLL LOCK
#if USB_LED_COMPOSE_SCANCODE != 255
(led_map[i].scan == USB_LED_COMPOSE_SCANCODE && (kbled & (1<<USB_LED_COMPOSE))) ||
#endif //COMPOSE
#if USB_LED_KANA_SCANCODE != 255
(led_map[i].scan == USB_LED_KANA_SCANCODE && (kbled & (1<<USB_LED_KANA))) ||
#endif //KANA
(0))
{ {
po = led_cur->py; led_buffer[i].r = 255 - led_buffer[i].r;
led_buffer[i].g = 255 - led_buffer[i].g;
led_buffer[i].b = 255 - led_buffer[i].b;
} }
else
{
po = led_cur->px;
} }
} }
float pomod; }
pomod = (float)(disp.frame % (uint32_t)(1000.0f / led_animation_speed)) / 10.0f * led_animation_speed;
//Add in any moving effects const rgb_matrix_driver_t rgb_matrix_driver = {
if ((!led_animation_direction && f[fcur].ef & EF_SCR_R) || (led_animation_direction && (f[fcur].ef & EF_SCR_L))) .init = init,
.flush = flush,
.set_color = led_set_one,
.set_color_all = led_set_all
};
/*==============================================================================
= Legacy Lighting Support =
==============================================================================*/
#ifdef USE_MASSDROP_CONFIGURATOR
// Ported from Massdrop QMK Github Repo
// TODO?: wire these up to keymap.c
uint8_t led_animation_orientation = 0;
uint8_t led_animation_direction = 0;
uint8_t led_animation_breathing = 0;
uint8_t led_animation_id = 0;
float led_animation_speed = 4.0f;
uint8_t led_lighting_mode = LED_MODE_NORMAL;
uint8_t led_enabled = 1;
uint8_t led_animation_breathe_cur = BREATHE_MIN_STEP;
uint8_t breathe_dir = 1;
static void led_run_pattern(led_setup_t *f, float* ro, float* go, float* bo, float pos) {
float po;
while (f->end != 1)
{ {
pomod *= 100.0f; po = pos; //Reset po for new frame
pomod = (uint32_t)pomod % 10000;
pomod /= 100.0f;
//Add in any moving effects
if ((!led_animation_direction && f->ef & EF_SCR_R) || (led_animation_direction && (f->ef & EF_SCR_L)))
{
po -= pomod; po -= pomod;
if (po > 100) po -= 100; if (po > 100) po -= 100;
else if (po < 0) po += 100; else if (po < 0) po += 100;
} }
else if ((!led_animation_direction && f[fcur].ef & EF_SCR_L) || (led_animation_direction && (f[fcur].ef & EF_SCR_R))) else if ((!led_animation_direction && f->ef & EF_SCR_L) || (led_animation_direction && (f->ef & EF_SCR_R)))
{ {
pomod *= 100.0f;
pomod = (uint32_t)pomod % 10000;
pomod /= 100.0f;
po += pomod; po += pomod;
if (po > 100) po -= 100; if (po > 100) po -= 100;
@ -372,167 +400,103 @@ void led_matrix_run(void)
} }
//Check if LED's po is in current frame //Check if LED's po is in current frame
if (po < f[fcur].hs) continue; if (po < f->hs) { f++; continue; }
if (po > f[fcur].he) continue; if (po > f->he) { f++; continue; }
//note: < 0 or > 100 continue //note: < 0 or > 100 continue
//Calculate the po within the start-stop percentage for color blending //Calculate the po within the start-stop percentage for color blending
po = (po - f[fcur].hs) / (f[fcur].he - f[fcur].hs); po = (po - f->hs) / (f->he - f->hs);
//Add in any color effects //Add in any color effects
if (f[fcur].ef & EF_OVER) if (f->ef & EF_OVER)
{ {
ro = (po * (f[fcur].re - f[fcur].rs)) + f[fcur].rs;// + 0.5; *ro = (po * (f->re - f->rs)) + f->rs;// + 0.5;
go = (po * (f[fcur].ge - f[fcur].gs)) + f[fcur].gs;// + 0.5; *go = (po * (f->ge - f->gs)) + f->gs;// + 0.5;
bo = (po * (f[fcur].be - f[fcur].bs)) + f[fcur].bs;// + 0.5; *bo = (po * (f->be - f->bs)) + f->bs;// + 0.5;
} }
else if (f[fcur].ef & EF_SUBTRACT) else if (f->ef & EF_SUBTRACT)
{ {
ro -= (po * (f[fcur].re - f[fcur].rs)) + f[fcur].rs;// + 0.5; *ro -= (po * (f->re - f->rs)) + f->rs;// + 0.5;
go -= (po * (f[fcur].ge - f[fcur].gs)) + f[fcur].gs;// + 0.5; *go -= (po * (f->ge - f->gs)) + f->gs;// + 0.5;
bo -= (po * (f[fcur].be - f[fcur].bs)) + f[fcur].bs;// + 0.5; *bo -= (po * (f->be - f->bs)) + f->bs;// + 0.5;
} }
else else
{ {
ro += (po * (f[fcur].re - f[fcur].rs)) + f[fcur].rs;// + 0.5; *ro += (po * (f->re - f->rs)) + f->rs;// + 0.5;
go += (po * (f[fcur].ge - f[fcur].gs)) + f[fcur].gs;// + 0.5; *go += (po * (f->ge - f->gs)) + f->gs;// + 0.5;
bo += (po * (f[fcur].be - f[fcur].bs)) + f[fcur].bs;// + 0.5; *bo += (po * (f->be - f->bs)) + f->bs;// + 0.5;
}
}
} }
//Clamp values 0-255 f++;
if (ro > 255) ro = 255; else if (ro < 0) ro = 0;
if (go > 255) go = 255; else if (go < 0) go = 0;
if (bo > 255) bo = 255; else if (bo < 0) bo = 0;
if (led_animation_breathing)
{
ro *= breathe_mult;
go *= breathe_mult;
bo *= breathe_mult;
}
*led_cur->rgb.r = (uint8_t)ro;
*led_cur->rgb.g = (uint8_t)go;
*led_cur->rgb.b = (uint8_t)bo;
#ifdef USB_LED_INDICATOR_ENABLE
if (keyboard_leds())
{
uint8_t kbled = keyboard_leds();
if (
#if USB_LED_NUM_LOCK_SCANCODE != 255
(led_cur->scan == USB_LED_NUM_LOCK_SCANCODE && kbled & (1<<USB_LED_NUM_LOCK)) ||
#endif //NUM LOCK
#if USB_LED_CAPS_LOCK_SCANCODE != 255
(led_cur->scan == USB_LED_CAPS_LOCK_SCANCODE && kbled & (1<<USB_LED_CAPS_LOCK)) ||
#endif //CAPS LOCK
#if USB_LED_SCROLL_LOCK_SCANCODE != 255
(led_cur->scan == USB_LED_SCROLL_LOCK_SCANCODE && kbled & (1<<USB_LED_SCROLL_LOCK)) ||
#endif //SCROLL LOCK
#if USB_LED_COMPOSE_SCANCODE != 255
(led_cur->scan == USB_LED_COMPOSE_SCANCODE && kbled & (1<<USB_LED_COMPOSE)) ||
#endif //COMPOSE
#if USB_LED_KANA_SCANCODE != 255
(led_cur->scan == USB_LED_KANA_SCANCODE && kbled & (1<<USB_LED_KANA)) ||
#endif //KANA
(0))
{
if (*led_cur->rgb.r > 127) *led_cur->rgb.r = 0;
else *led_cur->rgb.r = 255;
if (*led_cur->rgb.g > 127) *led_cur->rgb.g = 0;
else *led_cur->rgb.g = 255;
if (*led_cur->rgb.b > 127) *led_cur->rgb.b = 0;
else *led_cur->rgb.b = 255;
}
}
#endif //USB_LED_INDICATOR_ENABLE
led_cur++;
led_this_run++;
} }
} }
uint8_t led_matrix_init(void) static void led_matrix_massdrop_config_override(int i)
{ {
DBGC(DC_LED_MATRIX_INIT_BEGIN); float ro = 0;
float go = 0;
issi3733_prepare_arrays(); float bo = 0;
led_matrix_prepare();
disp.frame = 0;
led_next_run = 0;
led_enabled = 1;
led_animation_id = 0;
led_lighting_mode = LED_MODE_NORMAL;
led_animation_speed = 4.0f;
led_animation_direction = 0;
led_animation_orientation = 0;
led_animation_breathing = 0;
led_animation_breathe_cur = BREATHE_MIN_STEP;
breathe_step = 1;
breathe_dir = 1;
led_animation_circular = 0;
gcr_min_counter = 0;
v_5v_cat_hit = 0;
//Run led matrix code once for initial LED coloring
led_cur = 0;
rgb_matrix_init_user();
led_matrix_run();
DBGC(DC_LED_MATRIX_INIT_COMPLETE);
return 0;
}
__attribute__ ((weak)) float po = (led_animation_orientation)
void rgb_matrix_init_user(void) { ? (float)g_rgb_leds[i].point.y / 64.f * 100
: (float)g_rgb_leds[i].point.x / 224.f * 100;
} uint8_t highest_active_layer = biton32(layer_state);
#define LED_UPDATE_RATE 10 //ms
//led data processing can take time, so process data in chunks to free up the processor if (led_lighting_mode == LED_MODE_KEYS_ONLY && g_rgb_leds[i].matrix_co.raw == 0xff) {
//this is done through led_cur and lede //Do not act on this LED
void led_matrix_task(void) } else if (led_lighting_mode == LED_MODE_NON_KEYS_ONLY && g_rgb_leds[i].matrix_co.raw != 0xff) {
{ //Do not act on this LED
if (led_enabled) } else if (led_lighting_mode == LED_MODE_INDICATORS_ONLY) {
{ //Do not act on this LED (Only show indicators)
//If an update may run and frame processing has completed } else {
if (timer_read64() >= led_next_run && led_cur == lede) led_instruction_t* led_cur_instruction = led_instructions;
{ while (!led_cur_instruction->end) {
uint8_t drvid; // Check if this applies to current layer
if ((led_cur_instruction->flags & LED_FLAG_MATCH_LAYER) &&
(led_cur_instruction->layer != highest_active_layer)) {
goto next_iter;
}
led_next_run = timer_read64() + LED_UPDATE_RATE; //Set next frame update time // Check if this applies to current index
if (led_cur_instruction->flags & LED_FLAG_MATCH_ID) {
uint8_t modid = i / 32; //Calculate which id# contains the led bit
uint32_t modidbit = 1 << (i % 32); //Calculate the bit within the id#
uint32_t *bitfield = &led_cur_instruction->id0 + modid; //Add modid as offset to id0 address. *bitfield is now idX of the led id
if (~(*bitfield) & modidbit) { //Check if led bit is not set in idX
goto next_iter;
}
}
//NOTE: GCR does not need to be timed with LED processing, but there is really no harm if (led_cur_instruction->flags & LED_FLAG_USE_RGB) {
if (gcr_actual != gcr_actual_last) ro = led_cur_instruction->r;
{ go = led_cur_instruction->g;
for (drvid=0;drvid<ISSI3733_DRIVER_COUNT;drvid++) bo = led_cur_instruction->b;
I2C_LED_Q_GCR(drvid); //Queue data } else if (led_cur_instruction->flags & LED_FLAG_USE_PATTERN) {
gcr_actual_last = gcr_actual; led_run_pattern(led_setups[led_cur_instruction->pattern_id], &ro, &go, &bo, po);
} else if (led_cur_instruction->flags & LED_FLAG_USE_ROTATE_PATTERN) {
led_run_pattern(led_setups[led_animation_id], &ro, &go, &bo, po);
} }
for (drvid=0;drvid<ISSI3733_DRIVER_COUNT;drvid++) next_iter:
I2C_LED_Q_PWM(drvid); //Queue data led_cur_instruction++;
}
i2c_led_q_run(); if (ro > 255) ro = 255; else if (ro < 0) ro = 0;
if (go > 255) go = 255; else if (go < 0) go = 0;
if (bo > 255) bo = 255; else if (bo < 0) bo = 0;
led_cur = 0; //Signal next frame calculations may begin if (led_animation_breathing)
{
ro *= breathe_mult;
go *= breathe_mult;
bo *= breathe_mult;
} }
} }
//Process more data if not finished led_buffer[i].r = (uint8_t)ro;
if (led_cur != lede) led_buffer[i].g = (uint8_t)go;
{ led_buffer[i].b = (uint8_t)bo;
//DBG_1_OFF; //debug profiling
led_matrix_run();
//DBG_1_ON; //debug profiling
}
} }
#endif // USE_MASSDROP_CONFIGURATOR

@ -18,6 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef _LED_MATRIX_H_ #ifndef _LED_MATRIX_H_
#define _LED_MATRIX_H_ #define _LED_MATRIX_H_
#include "quantum.h"
//From keyboard //From keyboard
#include "config_led.h" #include "config_led.h"
@ -75,25 +77,20 @@ typedef struct issi3733_led_s {
uint8_t scan; //Key scan code from wiring (set 0xFF if no key) uint8_t scan; //Key scan code from wiring (set 0xFF if no key)
} issi3733_led_t; } issi3733_led_t;
typedef struct led_disp_s { extern issi3733_driver_t issidrv[ISSI3733_DRIVER_COUNT];
uint64_t frame;
float left;
float right;
float top;
float bottom;
float width;
float height;
float max_distance;
} led_disp_t;
uint8_t led_matrix_init(void); extern uint8_t gcr_desired;
void rgb_matrix_init_user(void); extern uint8_t gcr_breathe;
extern uint8_t gcr_actual;
extern uint8_t gcr_actual_last;
#define LED_MODE_NORMAL 0 //Must be 0 void gcr_compute(void);
#define LED_MODE_KEYS_ONLY 1
#define LED_MODE_NON_KEYS_ONLY 2 void led_matrix_indicators(void);
#define LED_MODE_INDICATORS_ONLY 3
#define LED_MODE_MAX_INDEX LED_MODE_INDICATORS_ONLY //Must be highest value /*------------------------- Legacy Lighting Support ------------------------*/
#ifdef USE_MASSDROP_CONFIGURATOR
#define EF_NONE 0x00000000 //No effect #define EF_NONE 0x00000000 //No effect
#define EF_OVER 0x00000001 //Overwrite any previous color information with new #define EF_OVER 0x00000001 //Overwrite any previous color information with new
@ -114,33 +111,48 @@ typedef struct led_setup_s {
uint8_t end; //Set to signal end of the setup uint8_t end; //Set to signal end of the setup
} led_setup_t; } led_setup_t;
extern issi3733_driver_t issidrv[ISSI3733_DRIVER_COUNT]; extern const uint8_t led_setups_count;
extern void *led_setups[];
extern uint8_t gcr_desired; //LED Extra Instructions
extern uint8_t gcr_breathe; #define LED_FLAG_NULL 0x00 //Matching and coloring not used (default)
extern uint8_t gcr_actual; #define LED_FLAG_MATCH_ID 0x01 //Match on the ID of the LED (set id#'s to desired bit pattern, first LED is id 1)
extern uint8_t gcr_actual_last; #define LED_FLAG_MATCH_LAYER 0x02 //Match on the current active layer (set layer to desired match layer)
#define LED_FLAG_USE_RGB 0x10 //Use a specific RGB value (set r, g, b to desired output color values)
#define LED_FLAG_USE_PATTERN 0x20 //Use a specific pattern ID (set pattern_id to desired output pattern)
#define LED_FLAG_USE_ROTATE_PATTERN 0x40 //Use pattern the user has cycled to manually
typedef struct led_instruction_s {
uint16_t flags; // Bitfield for LED instructions
uint32_t id0; // Bitwise id, IDs 0-31
uint32_t id1; // Bitwise id, IDs 32-63
uint32_t id2; // Bitwise id, IDs 64-95
uint32_t id3; // Bitwise id, IDs 96-127
uint8_t layer;
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t pattern_id;
uint8_t end;
} led_instruction_t;
extern led_instruction_t led_instructions[];
extern uint8_t led_animation_breathing;
extern uint8_t led_animation_id; extern uint8_t led_animation_id;
extern uint8_t led_enabled;
extern float led_animation_speed; extern float led_animation_speed;
extern uint8_t led_lighting_mode; extern uint8_t led_lighting_mode;
extern uint8_t led_animation_direction; extern uint8_t led_enabled;
extern uint8_t led_animation_orientation;
extern uint8_t led_animation_breathing;
extern uint8_t led_animation_breathe_cur; extern uint8_t led_animation_breathe_cur;
extern uint8_t led_animation_direction;
extern uint8_t breathe_dir; extern uint8_t breathe_dir;
extern uint8_t led_animation_circular;
extern const uint8_t led_setups_count;
extern void *led_setups[]; #define LED_MODE_NORMAL 0 //Must be 0
#define LED_MODE_KEYS_ONLY 1
extern issi3733_led_t *led_cur; #define LED_MODE_NON_KEYS_ONLY 2
extern issi3733_led_t *lede; #define LED_MODE_INDICATORS_ONLY 3
#define LED_MODE_MAX_INDEX LED_MODE_INDICATORS_ONLY //Must be highest value
void led_matrix_run(void);
void led_matrix_task(void);
void gcr_compute(void); #endif // USE_MASSDROP_CONFIGURATOR
#endif //_LED_MATRIX_H_ #endif //_LED_MATRIX_H_

@ -0,0 +1,123 @@
/*
Copyright 2018 Massdrop Inc.
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, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_MASSDROP_CONFIGURATOR
#include "led_matrix.h"
//Teal <-> Salmon
led_setup_t leds_teal_salmon[] = {
{ .hs = 0, .he = 33, .rs = 24, .re = 24, .gs = 215, .ge = 215, .bs = 204, .be = 204, .ef = EF_NONE },
{ .hs = 33, .he = 66, .rs = 24, .re = 255, .gs = 215, .ge = 114, .bs = 204, .be = 118, .ef = EF_NONE },
{ .hs = 66, .he = 100, .rs = 255, .re = 255, .gs = 114, .ge = 114, .bs = 118, .be = 118, .ef = EF_NONE },
{ .end = 1 },
};
//Yellow
led_setup_t leds_yellow[] = {
{ .hs = 0, .he = 100, .rs = 255, .re = 255, .gs = 255, .ge = 255, .bs = 0, .be = 0, .ef = EF_NONE },
{ .end = 1 },
};
//Off
led_setup_t leds_off[] = {
{ .hs = 0, .he = 100, .rs = 0, .re = 0, .gs = 0, .ge = 0, .bs = 0, .be = 0, .ef = EF_NONE },
{ .end = 1 },
};
//Red
led_setup_t leds_red[] = {
{ .hs = 0, .he = 100, .rs = 255, .re = 255, .gs = 0, .ge = 0, .bs = 0, .be = 0, .ef = EF_NONE },
{ .end = 1 },
};
//Green
led_setup_t leds_green[] = {
{ .hs = 0, .he = 100, .rs = 0, .re = 0, .gs = 255, .ge = 255, .bs = 0, .be = 0, .ef = EF_NONE },
{ .end = 1 },
};
//Blue
led_setup_t leds_blue[] = {
{ .hs = 0, .he = 100, .rs = 0, .re = 0, .gs = 0, .ge = 0, .bs = 255, .be = 255, .ef = EF_NONE },
{ .end = 1 },
};
//White
led_setup_t leds_white[] = {
{ .hs = 0, .he = 100, .rs = 255, .re = 255, .gs = 255, .ge = 255, .bs = 255, .be = 255, .ef = EF_NONE },
{ .end = 1 },
};
//White with moving red stripe
led_setup_t leds_white_with_red_stripe[] = {
{ .hs = 0, .he = 100, .rs = 255, .re = 255, .gs = 255, .ge = 255, .bs = 255, .be = 255, .ef = EF_NONE },
{ .hs = 0, .he = 15, .rs = 0, .re = 0, .gs = 0, .ge = 255, .bs = 0, .be = 255, .ef = EF_SCR_R | EF_SUBTRACT },
{ .hs = 15, .he = 30, .rs = 0, .re = 0, .gs = 255, .ge = 0, .bs = 255, .be = 0, .ef = EF_SCR_R | EF_SUBTRACT },
{ .end = 1 },
};
//Black with moving red stripe
led_setup_t leds_black_with_red_stripe[] = {
{ .hs = 0, .he = 15, .rs = 0, .re = 255, .gs = 0, .ge = 0, .bs = 0, .be = 0, .ef = EF_SCR_R },
{ .hs = 15, .he = 30, .rs = 255, .re = 0, .gs = 0, .ge = 0, .bs = 0, .be = 0, .ef = EF_SCR_R },
{ .end = 1 },
};
//Rainbow no scrolling
led_setup_t leds_rainbow_ns[] = {
{ .hs = 0, .he = 16.67, .rs = 255, .re = 255, .gs = 0, .ge = 255, .bs = 0, .be = 0, .ef = EF_OVER },
{ .hs = 16.67, .he = 33.33, .rs = 255, .re = 0, .gs = 255, .ge = 255, .bs = 0, .be = 0, .ef = EF_OVER },
{ .hs = 33.33, .he = 50, .rs = 0, .re = 0, .gs = 255, .ge = 255, .bs = 0, .be = 255, .ef = EF_OVER },
{ .hs = 50, .he = 66.67, .rs = 0, .re = 0, .gs = 255, .ge = 0, .bs = 255, .be = 255, .ef = EF_OVER },
{ .hs = 66.67, .he = 83.33, .rs = 0, .re = 255, .gs = 0, .ge = 0, .bs = 255, .be = 255, .ef = EF_OVER },
{ .hs = 83.33, .he = 100, .rs = 255, .re = 255, .gs = 0, .ge = 0, .bs = 255, .be = 0, .ef = EF_OVER },
{ .end = 1 },
};
//Rainbow scrolling
led_setup_t leds_rainbow_s[] = {
{ .hs = 0, .he = 16.67, .rs = 255, .re = 255, .gs = 0, .ge = 255, .bs = 0, .be = 0, .ef = EF_OVER | EF_SCR_R },
{ .hs = 16.67, .he = 33.33, .rs = 255, .re = 0, .gs = 255, .ge = 255, .bs = 0, .be = 0, .ef = EF_OVER | EF_SCR_R },
{ .hs = 33.33, .he = 50, .rs = 0, .re = 0, .gs = 255, .ge = 255, .bs = 0, .be = 255, .ef = EF_OVER | EF_SCR_R },
{ .hs = 50, .he = 66.67, .rs = 0, .re = 0, .gs = 255, .ge = 0, .bs = 255, .be = 255, .ef = EF_OVER | EF_SCR_R },
{ .hs = 66.67, .he = 83.33, .rs = 0, .re = 255, .gs = 0, .ge = 0, .bs = 255, .be = 255, .ef = EF_OVER | EF_SCR_R },
{ .hs = 83.33, .he = 100, .rs = 255, .re = 255, .gs = 0, .ge = 0, .bs = 255, .be = 0, .ef = EF_OVER | EF_SCR_R },
{ .end = 1 },
};
//Add new LED animations here using one from above as example
//The last entry must be { .end = 1 }
//Add the new animation name to the list below following its format
void *led_setups[] = {
leds_rainbow_s,
leds_rainbow_ns,
leds_teal_salmon,
leds_yellow,
leds_red,
leds_green,
leds_blue,
leds_white,
leds_white_with_red_stripe,
leds_black_with_red_stripe,
leds_off
};
const uint8_t led_setups_count = sizeof(led_setups) / sizeof(led_setups[0]);
#endif

@ -203,13 +203,6 @@ void main_subtask_usb_state(void)
} }
} }
void main_subtask_led(void)
{
if (g_usb_state != USB_FSMSTATUS_FSMSTATE_ON_Val) return; //Only run LED tasks if USB is operating
led_matrix_task();
}
void main_subtask_power_check(void) void main_subtask_power_check(void)
{ {
static uint64_t next_5v_checkup = 0; static uint64_t next_5v_checkup = 0;
@ -221,7 +214,9 @@ void main_subtask_power_check(void)
v_5v = adc_get(ADC_5V); v_5v = adc_get(ADC_5V);
v_5v_avg = 0.9 * v_5v_avg + 0.1 * v_5v; v_5v_avg = 0.9 * v_5v_avg + 0.1 * v_5v;
#ifdef RGB_MATRIX_ENABLE
gcr_compute(); gcr_compute();
#endif
} }
} }
@ -240,7 +235,6 @@ void main_subtask_usb_extra_device(void)
void main_subtasks(void) void main_subtasks(void)
{ {
main_subtask_usb_state(); main_subtask_usb_state();
main_subtask_led();
main_subtask_power_check(); main_subtask_power_check();
main_subtask_usb_extra_device(); main_subtask_usb_extra_device();
} }
@ -263,7 +257,9 @@ int main(void)
SR_EXP_Init(); SR_EXP_Init();
#ifdef RGB_MATRIX_ENABLE
i2c1_init(); i2c1_init();
#endif // RGB_MATRIX_ENABLE
matrix_init(); matrix_init();
@ -281,8 +277,7 @@ int main(void)
DBG_LED_OFF; DBG_LED_OFF;
led_matrix_init(); #ifdef RGB_MATRIX_ENABLE
while (I2C3733_Init_Control() != 1) {} while (I2C3733_Init_Control() != 1) {}
while (I2C3733_Init_Drivers() != 1) {} while (I2C3733_Init_Drivers() != 1) {}
@ -292,6 +287,7 @@ int main(void)
for (uint8_t drvid = 0; drvid < ISSI3733_DRIVER_COUNT; drvid++) for (uint8_t drvid = 0; drvid < ISSI3733_DRIVER_COUNT; drvid++)
I2C_LED_Q_ONOFF(drvid); //Queue data I2C_LED_Q_ONOFF(drvid); //Queue data
#endif // RGB_MATRIX_ENABLE
keyboard_setup(); keyboard_setup();

@ -366,7 +366,9 @@ void USB_ExtraSetState(uint8_t state)
else if (usb_extra_state == USB_EXTRA_STATE_DISABLED) else if (usb_extra_state == USB_EXTRA_STATE_DISABLED)
{ {
CDC_print("USB: Extra disabled\r\n"); CDC_print("USB: Extra disabled\r\n");
#ifdef USE_MASSDROP_CONFIGURATOR
if (led_animation_breathing) gcr_breathe = gcr_desired; if (led_animation_breathing) gcr_breathe = gcr_desired;
#endif
} }
else if (usb_extra_state == USB_EXTRA_STATE_DISABLED_UNTIL_REPLUG) CDC_print("USB: Extra disabled until replug\r\n"); else if (usb_extra_state == USB_EXTRA_STATE_DISABLED_UNTIL_REPLUG) CDC_print("USB: Extra disabled until replug\r\n");
else CDC_print("USB: Extra state unknown\r\n"); else CDC_print("USB: Extra state unknown\r\n");

Loading…
Cancel
Save