背景
客制化键盘玩法很多,有很多原厂高度的GMK配色键帽,但那都是不透光的。如果想要RGB,那可以整双皮奶键帽,全键RGB。更进一步,可以整亚克力外壳、透明轴、透明键帽…
前不久买一把我觉得是RGB终点的套件,方块男孩的RK680,Gasket,110颗 RGB灯,QMK VIA,南向轴座…
但这就是RGB终点了吗?!
看着一机箱的RGB灯,和这把无法和他们愉快同步的键盘,我幡然大悟!
灯效同步才是RGB的终点,我要给我这把键盘自己加灯效!
好在方块男孩键盘的开源程度很不错,轻松要到了工程文件,装好qmk工具就可以在wsl编译。但是因为这个键盘尼玛的reset键要拆了键盘才能按,一直懒得弄。开发RGB同步的念头就像咒语一样在脑海中徘徊了几个月。直到昨天不想上班休了一天假,在家才坐下来仔细研究了一下RGB同步能否实现。
先上下最后同步的效果:
QMK VIA
qmk是开源键盘驱动Quantum Mechanical Keyboard的缩写,支持非常多的客制化键盘,支持自定义键位、自定义层、自带RGB灯效等功能,如果在2022年你的键盘还没有用上qmk,那么你可以考虑换一把了。
via是什么就比较难懂了,直观来看,只要支持via的键盘,就可以通过via的电脑程序或者webapp获得实时修改键位、调整RGB光效的能力,不用再像QMK每次改键都要编译一个新的固件出来刷机了。从底层来看,via是附加在qmk上的一层自定义的协议,通过UsbHID和电脑通信,定义的协议中实现了一些功能就有修改键位、修改RGB光效。
开启VIA需要在rule.mk中设置VIA_ENABLED=yes,当VIA开启的时候,RAWHID会自动开启,可以不需要额外设置RAW_ENABLE=yes。编译QMK固件的过程不再在这里赘述,需要确认编译好的固件可以正常使用VIA软件,这代表电脑-usbhid-via这条路就已经通了。
SignalRGB
SignalRGB是现在一款统一RGB软件,可以统一控制华硕Aurora、技嘉fusion、海盗船、雷蛇等很多厂商的RGB,他采取了反向工程的方法,破解出厂商RGB通信的协议,然后再用自己实现的服务端和RGB设备通信,起到控制RGB灯效的作用。在统一RGB领域中还有一款是OpenRGB,全开源软件,但是我觉得UI比较简陋,还是更喜欢用SignalRGB。
华硕、技嘉、海盗船这些厂商对于RGB通信协议都是支支吾吾的,从来没有一份公开的文档或者SDK,现在开源的SDK都是无数玩家开发者贡献出来的反向工程代码,OpenRGB就是这些代码的集大成者,SignalRGB这个软件也极大程度受益于OpenRGB提供的开源协议。诸如内存灯光,显卡灯光,可能是厂家通过向内存/PCIE设备一个指定的地址,按照指定的格式写RGB信息实现的控制方法,都是大佬们通过反向工程抓包解析出来的。有了OpenRGB和SignalRGB这样的平台铺路,我们一般人想写个插件支持下新的设备,或者添加一个自定义的光效,就变得非常简单了。
https://signalrgb.com/devices/
这是SignalRGB目前支持的设备列表,主流厂商并不是尽数支持的,可以进去看看你的设备支持不支持。很巧的是,我用的华硕主板+海盗船内存+技嘉显卡,SignalRGB都是支持的,所以它就成了我得最佳选择,赶紧抛弃掉这三个厂商的垃圾RGB控制软件。
我的设备列表,技嘉显卡送去修了所以这里没有
它支持的RGB效果很多,你也可以自己开发效果
每一个RGB效果都有速度、颜色等参数可以自定义
RGB控制采取类似桌面布局的方式,可以把设备放到对应的位置,大小方向都可以调整
对主板的每个argb插针都可以单独控制
总之这是一款特别棒的RGB控制软件,经过一段时间使用之后也对基于它二次开发有了兴趣。哦对了,他们自己也卖RGB灯条,牌子叫whirlwindfx,小贵,并且只寄美国和加拿大,淘宝没有。
我的键盘
先说下我使用的这把键盘
这把键盘是方块男孩的FK680Pro套件,用的V2版本的PCB,正面有70颗LED灯,背面有40颗LED灯,使用QMK固件支持VIA,单模USB接口。
这里用的键帽是AKKO透明键帽,轴是幻想轴。
这是把不错的入门QMK的键盘,套件价格只要不到三百元,键盘手感不错,RGB灯很多,硬件软件全开源,群里有图纸和代码,工程文件可以找淘宝客服要,很适合二次开发RGB灯效。
基于SignalRGB的二次开发
SignalRGB的很多设备是通过Javascript插件来支持的,对于尚不支持的设备,开发者鼓励自己开发Javascript脚本支持。
官方对于如何从头开始制作一个脚本有详细的教程,特别解释了如何去抓USB HID包,因为大部分RGB都是通过HID来控制的。
从0开始反向工程一个USBHID协议还是一个挺难的工程,我配置了半天wireshark,都没看到一个有效的hid包。不过好在我们今天做的QMK VIA键盘不需要我们反向工程。QMK VIA代码是开源的,并且SignalRGB的Javascript脚本也是开源的,只要参考官方的脚本和代码,我们就可以将自己的QMK键盘也接入SignalRGB的控制。
细读修改QMK VIA的RGB Matrix代码与USBHID代码,加入SignalRGB支持
前言,SignalRGB改造的协议、handler都是SignalRGB的开发者写好的,只是SignalRGB尚未有详细的文档教人如何去自己改造QMK固件,官方只发布了三个QMK键盘的支持:https://signalrgb.developerhub.io/qmk/supported-keyboards
可以预见官方肯定会加强QMK的支持,但目前我只能根据这已经放出的三把键盘,改造一下我得QMK固件,自己编译一个支持SignalRGB的固件,同时在写一个电脑端调用脚本。好在我有上面链接里的三把键盘可以参考。不幸的是官方是放出了编译好的固件,所以qmk固件中的修改,还要去官方的qmk仓库中找:signalrgb/qmk_firmware
RGB
QMK控制RGB等效有两种接口,一个叫rgblight,FLAG是RGBLIGHT_ENABLE
,这个比较旧了,效果也很简单;另一个是rgbmatrix,FLAG是RGB_MATRIX_ENABLE
,如其名字是矩阵化的RGB等效,这两个的区别有点像12v4pin的RGB和5v3pin的ARGB的效果区别,matrix的灯效更精细,可以per键控制。FK680键盘默认使用rgbmatrix控制rgb。
rgb_matrix中需要使用的函数是rgb_matrix_set_color
,可以设置一个灯泡的rgb值,灯泡从0开始顺序计数的,一般来说一行一行递增计数。rgb_matrix的动画效果存放在animation文件中,动画需要先在rgb_matrix_effects.inc
中定义,然后在定义的头文件里实现RGB效果。
rgb_matrix支持使用RGB灯条控制器,FK680pro自带的控制器是WS2812。
为了实现SignalRGB接入,需要在animation创建一个自定义的动画:SIGNALRGB
// animations/signalrgb_anim.h
RGB_MATRIX_EFFECT(SIGNALRGB)
#ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS
bool SIGNALRGB(effect_params_t* params)
{
RGB_MATRIX_USE_LIMITS(led_min, led_max);
return rgb_matrix_check_finished_leds(led_max);
}
#endif // RGB_MATRIX_CUSTOM_EFFECT_IMPLS
然后在头文件中加入SIGNALRGB
的动画定义
// animations/rgb_matrix_effects.inc
// Add your new core rgb matrix effect here, order determines enum order
#include "signalrgb_anim.h"
这个动画模式的目的是给后面从电脑控制RGB预留一个空白的RGB模式,这样电脑传过来的RGB信号不会和固件发出的RGB冲突。
VIA
VIA实现在quantum/via.c中,VIA的核心就是在 raw_hid_receive
函数中实现了一系列的USBHID命令,包括实时修改键位、设置RGB灯等等。
VIA的USBHID协议包含的命令可以看via.h头文件。从0x01
到0x13
是via原生定义的指令,0x21开始则是signalRGB自己新增加的命令,SignalRGB的电脑端就是通过这些命令和VIA键盘通信的。包括获得qmk版本、via版本、signalrgb指令版本、开启关闭signalrgb,最重要的id_signalrgb_stream_leds
才是设置LED颜色,控制RGB的函数。
enum via_command_id {
id_get_protocol_version = 0x01, // always 0x01
id_get_keyboard_value = 0x02,
id_set_keyboard_value = 0x03,
id_dynamic_keymap_get_keycode = 0x04,
id_dynamic_keymap_set_keycode = 0x05,
id_dynamic_keymap_reset = 0x06,
id_lighting_set_value = 0x07,
id_lighting_get_value = 0x08,
id_lighting_save = 0x09,
id_eeprom_reset = 0x0A,
id_bootloader_jump = 0x0B,
id_dynamic_keymap_macro_get_count = 0x0C,
id_dynamic_keymap_macro_get_buffer_size = 0x0D,
id_dynamic_keymap_macro_get_buffer = 0x0E,
id_dynamic_keymap_macro_set_buffer = 0x0F,
id_dynamic_keymap_macro_reset = 0x10,
id_dynamic_keymap_get_layer_count = 0x11,
id_dynamic_keymap_get_buffer = 0x12,
id_dynamic_keymap_set_buffer = 0x13,
id_unhandled = 0xFF,
id_signalrgb_qmk_version = 0x21,
id_signalrgb_protocol_version = 0x22,
id_signalrgb_unique_identifier = 0x23,
id_signalrgb_stream_leds = 0x24,
id_signalrgb_effect_enable = 0x25,
id_signalrgb_effect_disable = 0x26,
id_signalrgb_get_total_leds = 0x27,
id_signalrgb_get_firmware_type = 0x28,
};
VIA的raw_hid_receive
函数就是一个很简单的switch,传入的data是1字节uint8_t数组,第一个1字节data[0]是指令,之后的都是数据段,可以在各个指令里自己定义。这段SWITCH最后就是signalrgb相关的handler,signalrgb的开发者已经把他们实现好了,我们只需要拷贝一份。仓库:signalrgb/qmk_firmware
// VIA handles received HID messages first, and will route to
// raw_hid_receive_kb() for command IDs that are not handled here.
// This gives the keyboard code level the ability to handle the command
// specifically.
//
// raw_hid_send() is called at the end, with the same buffer, which was
// possibly modified with returned values.
void raw_hid_receive(uint8_t *data, uint8_t length) {
uint8_t *command_id = &(data[0]);
uint8_t *command_data = &(data[1]);
switch (*command_id) {
case id_get_protocol_version: {
command_data[0] = VIA_PROTOCOL_VERSION >> 8;
command_data[1] = VIA_PROTOCOL_VERSION & 0xFF;
break;
}
case id_get_keyboard_value: {
switch (command_data[0]) {
case id_uptime: {
uint32_t value = timer_read32();
command_data[1] = (value >> 24) & 0xFF;
command_data[2] = (value >> 16) & 0xFF;
command_data[3] = (value >> 8) & 0xFF;
command_data[4] = value & 0xFF;
break;
}
case id_layout_options: {
uint32_t value = via_get_layout_options();
command_data[1] = (value >> 24) & 0xFF;
command_data[2] = (value >> 16) & 0xFF;
command_data[3] = (value >> 8) & 0xFF;
command_data[4] = value & 0xFF;
break;
}
case id_switch_matrix_state: {
#if ((MATRIX_COLS / 8 + 1) * MATRIX_ROWS <= 28)
uint8_t i = 1;
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
matrix_row_t value = matrix_get_row(row);
# if (MATRIX_COLS > 24)
command_data[i++] = (value >> 24) & 0xFF;
# endif
# if (MATRIX_COLS > 16)
command_data[i++] = (value >> 16) & 0xFF;
# endif
# if (MATRIX_COLS > 8)
command_data[i++] = (value >> 8) & 0xFF;
# endif
command_data[i++] = value & 0xFF;
}
#endif
break;
}
default: {
raw_hid_receive_kb(data, length);
break;
}
}
break;
}
case id_set_keyboard_value: {
switch (command_data[0]) {
case id_layout_options: {
uint32_t value = ((uint32_t)command_data[1] << 24) | ((uint32_t)command_data[2] << 16) | ((uint32_t)command_data[3] << 8) | (uint32_t)command_data[4];
via_set_layout_options(value);
break;
}
default: {
raw_hid_receive_kb(data, length);
break;
}
}
break;
}
case id_dynamic_keymap_get_keycode: {
uint16_t keycode = dynamic_keymap_get_keycode(command_data[0], command_data[1], command_data[2]);
command_data[3] = keycode >> 8;
command_data[4] = keycode & 0xFF;
break;
}
case id_dynamic_keymap_set_keycode: {
dynamic_keymap_set_keycode(command_data[0], command_data[1], command_data[2], (command_data[3] << 8) | command_data[4]);
break;
}
case id_dynamic_keymap_reset: {
dynamic_keymap_reset();
break;
}
case id_lighting_set_value: {
#if defined(VIA_QMK_BACKLIGHT_ENABLE)
via_qmk_backlight_set_value(command_data);
#endif
#if defined(VIA_QMK_RGBLIGHT_ENABLE)
via_qmk_rgblight_set_value(command_data);
#endif
#if defined(VIA_QMK_RGB_MATRIX_ENABLE)
via_qmk_rgb_matrix_set_value(command_data);
#endif
#if defined(VIA_CUSTOM_LIGHTING_ENABLE)
raw_hid_receive_kb(data, length);
#endif
#if !defined(VIA_QMK_BACKLIGHT_ENABLE) && !defined(VIA_QMK_RGBLIGHT_ENABLE) && !defined(VIA_CUSTOM_LIGHTING_ENABLE) && !defined(VIA_QMK_RGB_MATRIX_ENABLE)
// Return the unhandled state
*command_id = id_unhandled;
#endif
break;
}
case id_lighting_get_value: {
#if defined(VIA_QMK_BACKLIGHT_ENABLE)
via_qmk_backlight_get_value(command_data);
#endif
#if defined(VIA_QMK_RGBLIGHT_ENABLE)
via_qmk_rgblight_get_value(command_data);
#endif
#if defined(VIA_QMK_RGB_MATRIX_ENABLE)
via_qmk_rgb_matrix_get_value(command_data);
#endif
#if defined(VIA_CUSTOM_LIGHTING_ENABLE)
raw_hid_receive_kb(data, length);
#endif
#if !defined(VIA_QMK_BACKLIGHT_ENABLE) && !defined(VIA_QMK_RGBLIGHT_ENABLE) && !defined(VIA_CUSTOM_LIGHTING_ENABLE) && !defined(VIA_QMK_RGB_MATRIX_ENABLE)
// Return the unhandled state
*command_id = id_unhandled;
#endif
break;
}
case id_lighting_save: {
#if defined(VIA_QMK_BACKLIGHT_ENABLE)
eeconfig_update_backlight_current();
#endif
#if defined(VIA_QMK_RGBLIGHT_ENABLE)
eeconfig_update_rgblight_current();
#endif
#if defined(VIA_QMK_RGB_MATRIX_ENABLE)
eeconfig_update_rgb_matrix();
#endif
#if defined(VIA_CUSTOM_LIGHTING_ENABLE)
raw_hid_receive_kb(data, length);
#endif
#if !defined(VIA_QMK_BACKLIGHT_ENABLE) && !defined(VIA_QMK_RGBLIGHT_ENABLE) && !defined(VIA_CUSTOM_LIGHTING_ENABLE) && !defined(VIA_QMK_RGB_MATRIX_ENABLE)
// Return the unhandled state
*command_id = id_unhandled;
#endif
break;
}
#ifdef VIA_EEPROM_ALLOW_RESET
case id_eeprom_reset: {
via_eeprom_set_valid(false);
eeconfig_init_via();
break;
}
#endif
case id_dynamic_keymap_macro_get_count: {
command_data[0] = dynamic_keymap_macro_get_count();
break;
}
case id_dynamic_keymap_macro_get_buffer_size: {
uint16_t size = dynamic_keymap_macro_get_buffer_size();
command_data[0] = size >> 8;
command_data[1] = size & 0xFF;
break;
}
case id_dynamic_keymap_macro_get_buffer: {
uint16_t offset = (command_data[0] << 8) | command_data[1];
uint16_t size = command_data[2]; // size <= 28
dynamic_keymap_macro_get_buffer(offset, size, &command_data[3]);
break;
}
case id_dynamic_keymap_macro_set_buffer: {
uint16_t offset = (command_data[0] << 8) | command_data[1];
uint16_t size = command_data[2]; // size <= 28
dynamic_keymap_macro_set_buffer(offset, size, &command_data[3]);
break;
}
case id_dynamic_keymap_macro_reset: {
dynamic_keymap_macro_reset();
break;
}
case id_dynamic_keymap_get_layer_count: {
command_data[0] = dynamic_keymap_get_layer_count();
break;
}
case id_dynamic_keymap_get_buffer: {
uint16_t offset = (command_data[0] << 8) | command_data[1];
uint16_t size = command_data[2]; // size <= 28
dynamic_keymap_get_buffer(offset, size, &command_data[3]);
break;
}
case id_dynamic_keymap_set_buffer: {
uint16_t offset = (command_data[0] << 8) | command_data[1];
uint16_t size = command_data[2]; // size <= 28
dynamic_keymap_set_buffer(offset, size, &command_data[3]);
break;
}
case id_signalrgb_qmk_version:
get_qmk_version();
break;
case id_signalrgb_protocol_version:
get_signalrgb_protocol_version();
break;
case id_signalrgb_unique_identifier:
get_unique_identifier();
break;
case id_signalrgb_stream_leds:
led_streaming(data);
break;
case id_signalrgb_effect_enable:
signalrgb_mode_enable();
break;
case id_signalrgb_effect_disable:
signalrgb_mode_disable();
break;
case id_signalrgb_get_total_leds:
get_total_leds();
break;
case id_signalrgb_get_firmware_type:
get_firmware_type();
break;
default: {
// The command ID is not known
// Return the unhandled state
*command_id = id_unhandled;
break;
}
}
SignalRGB实现好了的handler。我们也可以直接拷贝,其中最重要的ledstream指令,传入的是一个led index + led num + rgb list的结构,data[1]代表设置的第一个灯泡index,data[2]是接下来数据里包含的灯泡数量,data[2]之后是一组组长度为24(3字节)的 (R,G,B)数据。
uint8_t packet[32];
void get_qmk_version(void) //Grab the QMK Version
{
packet[0] = id_signalrgb_qmk_version;
packet[1] = QMK_VERSION_BYTE_1;
packet[2] = QMK_VERSION_BYTE_2;
packet[3] = QMK_VERSION_BYTE_3;
raw_hid_send(packet, 32);
}
void get_signalrgb_protocol_version(void) //Grab what version of the SignalRGB protocol a keyboard is running
{
packet[0] = id_signalrgb_protocol_version;
packet[1] = PROTOCOL_VERSION_BYTE_1;
packet[2] = PROTOCOL_VERSION_BYTE_2;
packet[3] = PROTOCOL_VERSION_BYTE_3;
raw_hid_send(packet, 32);
}
void get_unique_identifier(void) //Grab the unique identifier for each specific model of keyboard.
{
packet[0] = id_signalrgb_unique_identifier;
packet[1] = DEVICE_UNIQUE_IDENTIFIER_BYTE_1;
packet[2] = DEVICE_UNIQUE_IDENTIFIER_BYTE_2;
packet[3] = DEVICE_UNIQUE_IDENTIFIER_BYTE_3;
raw_hid_send(packet, 32);
}
void led_streaming(uint8_t *data) //Stream data from HID Packets to Keyboard.
{
uint8_t index = data[1];
uint8_t numberofleds = data[2];
if(numberofleds >= 10)
{
packet[1] = DEVICE_ERROR_LEDS;
raw_hid_send(packet,32);
return;
}
for (uint8_t i = 0; i < numberofleds; i++)
{
uint8_t offset = (i * 3) + 3;
uint8_t r = data[offset];
uint8_t g = data[offset + 1];
uint8_t b = data[offset + 2];
rgb_matrix_set_color(index + i, r, g, b);
}
}
void signalrgb_mode_enable(void)
{
rgb_matrix_mode_noeeprom(RGB_MATRIX_SIGNALRGB); //Set RGB Matrix to SignalRGB Compatible Mode
}
void signalrgb_mode_disable(void)
{
rgb_matrix_reload_from_eeprom(); //Reloading last effect from eeprom
}
void get_total_leds(void)//Grab total number of leds that a board has.
{
packet[0] = id_signalrgb_get_total_leds;
packet[1] = DRIVER_LED_TOTAL;
raw_hid_send(packet, 32);
}
void get_firmware_type(void) //Grab which fork of qmk a board is running.
{
packet[0] = id_signalrgb_get_firmware_type;
packet[1] = FIRMWARE_TYPE_BYTE;
raw_hid_send(packet, 32);
}
在这里补充下FK680Pro键盘的LED INDEX,在SignalRGB中定义RGB矩阵时会用到:
细读修改SignalRGB的设备接入脚本
这里我们先参考一下SignalRGB官方放出的KDBFans KDB67的QMK VIA支持脚本:
首先是头部,定义了一些脚本的基本信息,这些是提供给SignalRGB显示用的。
export function Name() { return "KBDFans KBD67"; }
export function VendorId() { return 0x4b42; }
export function ProductId() { return 0x1225; }
export function Publisher() { return "WhirlwindFX"; }
export function Size() { return [15, 5]; }
export function DefaultPosition(){return [10, 100]; }
export function DefaultScale(){return 8.0}
export function ControllableParameters() {
return [
{"property":"shutdownColor", "group":"lighting", "label":"Shutdown Color", "min":"0", "max":"360", "type":"color", "default":"009bde"},
{"property":"LightingMode", "group":"lighting", "label":"Lighting Mode", "type":"combobox", "values":["Canvas", "Forced"], "default":"Canvas"},
{"property":"forcedColor", "group":"lighting", "label":"Forced Color", "min":"0", "max":"360", "type":"color", "default":"009bde"},
];
}
接下来是正戏,在vKeys
中定义的键位(应该说LED顺序),在vKeyNames
中定义的每一个LED名字,在vKeyPositions
里定义的每一个LED的抽象位置。
vKey
s中的可以值可以对LED排序再做一次映射,比较绕,我就直接保持了从0开始递增到最后一个LED结束的规则,我有110颗LED,就是[0, … 109]
vKeyNames
只是debug用的名称显示,自己取个名字就行,但是注意这三个数组长度必须一样。
vKeyPosition
是每一个LED在SignalRGB的Layout中的抽象位置,键盘被划成5×15的区域(这个在第一段代码中第一),比如最左上的ESC位置就是[0,0],左下的Ctrl就是[0,4],这里比较坑的是:这里的顺序,ESC所在的LED灯的INDEX值是0。
const vKeys =
[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
58, 59, 60, 61, 62, 63, 64, 65, 66
];
const vKeyNames =
[
"Esc","1","2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "+", "Backspace","Home", //15
"Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\","Page Up", //15
"CapsLock", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'","Enter", "Page Down", //14
"Left Shift","Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Right Shift", "Up Arrow", "End", //14
"Left Ctrl", "Left Win", "Left Alt", "Space", "Right Alt", "Fn", "Left Arrow", "Down Arrow", "Right Arrow", //9
];
const vKeyPositions =
[
[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0], [12,0], [13,0], [14,0], //15
[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[11,1], [12,1], [13,1], [14,1], //15
[0,2],[1,2],[2,2],[3,2],[4,2],[5,2],[6,2],[7,2],[8,2],[9,2],[10,2],[11,2], [13,2], [14,2], //14
[0,3],[1,3],[2,3],[3,3],[4,3],[5,3],[6,3],[7,3],[8,3],[9,3],[10,3],[11,3], [13,3], [14,3], //14
[0,4],[1,4],[2,4], [6,4], [10,4],[11,4], [12,4], [13,4], [14,4] //9
];
但实际上在我的键盘上,Left Ctrl的LED INDEX才是0,所以这里要做顺序调整,也是我花了一些时间才搞出来的。之外我得空格下有3颗LED,所以空格中间也需要再加两颗LED。最后这是67键盘的键位,我是68键盘,最下层再多一个键。最最后我还有背面40颗灯珠,还得给后买后面的灯珠设置逻辑位置。这里直接公布答案了,照抄就行:
const vKeys =
[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 12
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 14
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, // 14
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, // 15
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, // 15
70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109 // 40
];
const vKeyNames =
[
"Left Ctrl", "Left Win", "Left Alt", "Space0", "Space1", "Space2", "Right Ctrl", "Fn", "Right Alt", "Left Arrow", "Down Arrow", "Right Arrow", //12
"Left Shift","Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Right Shift", "Up Arrow", "End", //14
"CapsLock", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'","Enter", "Page Down", //14
"Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\","Page Up", //15
"Esc","1","2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "+", "Backspace","Home", //15
"bk0", "bk1", "bk2", "bk3", "bk4", "bk5", "bk6", "bk7", "bk8", "bk9", "bk10", "bk11", "bk12", "bk13", "bk14", "bk15", "bk16", "bk17", "bk18", "bk19", "bk20",
"bk21", "bk22", "bk23", "bk24", "bk25", "bk26", "bk27", "bk28", "bk29", "bk30", "bk31", "bk32", "bk33", "bk34", "bk35", "bk36", "bk37", "bk38", "bk39"
];
const vKeyPositions =
[
[0,4],[1,4],[2,4], [4,4], [6,4], [8,4],[9,4],[10,4],[11,4], [12,4], [13,4], [14,4], //12
[0,3],[1,3],[2,3],[3,3],[4,3],[5,3],[6,3],[7,3],[8,3],[9,3],[10,3],[11,3], [13,3], [14,3], //14
[0,2],[1,2],[2,2],[3,2],[4,2],[5,2],[6,2],[7,2],[8,2],[9,2],[10,2],[11,2], [13,2], [14,2], //14
[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[11,1], [12,1], [13,1], [14,1], //15
[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0], [12,0], [13,0], [14,0], //15
[14,0],[14,1],[14,2],[14,3],[14,4],
[14,4],[13,4],[12,4],[11,4],[10,4],[9,4],[8,4],[7,4],[6,4],[5,4],[4,4],[3,4],[2,4],[1,4],[0,4],
[0,4],[0,3],[0,2],[0,1],[0,0],
[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0],[12,0],[13,0],[14,0],`-`
];
后面是从SignalRGB获取每一个逻辑位置RGB值得代码,和实际构造数据包,发送USBHID的代码,如果使用SignalRGB开发者设计好的协议的话这段不需要修改,当然你可以修改协议实现自己的RGB或者别的功能。
export function LedNames()
{
return vKeyNames;
}
export function LedPositions()
{
return vKeyPositions;
}
export function Initialize()
{
ClearReadBuffer();
versionVIA();
versionQMK();
versionSignalRGBProtocol();
uniqueIdentifier();
effectEnable();
}
export function Render()
{
sendColors();
}
export function Shutdown()
{
effectDisable();
}
function ClearReadBuffer(timeout = 10){
let count = 0;
let readCounts = [];
device.flush();
while(device.getLastReadSize() > 0){
device.read([0x00], 32, timeout);
count++;
readCounts.push(device.getLastReadSize());
}
//device.log(`Read Count {count}:{readCounts} Bytes`)
}
function versionVIA()
{
var packet = [];
packet[0] = 0x00;
packet[1] = 0x01;
device.write(packet, 32);
packet = device.read(packet,32);
let via_version = packet[3];
device.log("Via Protocol Version: " + via_version);
}
function versionQMK() //Check the version of QMK Firmware that the keyboard is running
{
var packet = [];
packet[0] = 0x00;
packet[1] = 0x21;
device.write(packet, 32);
packet = device.read(packet,32);
let QMKVersionByte1 = packet[2];
let QMKVersionByte2 = packet[3];
let QMKVersionByte3 = packet[4];
device.log("QMK Version: " + QMKVersionByte1 + "." + QMKVersionByte2 + "." + QMKVersionByte3);
device.pause(30);
}
function versionSignalRGBProtocol() //Grab the version of the SignalRGB Protocol the keyboard is running
{
var packet = [];
packet[0] = 0x00;
packet[1] = 0x22;
device.write(packet, 32);
packet = device.read(packet,32);
let ProtocolVersionByte1 = packet[2];
let ProtocolVersionByte2 = packet[3];
let ProtocolVersionByte3 = packet[4];
device.log("SignalRGB Protocol Version: " + ProtocolVersionByte1 + "." + ProtocolVersionByte2 + "." + ProtocolVersionByte3);
device.pause(30);
}
function uniqueIdentifier() //Grab the unique identifier for this keyboard model
{
var packet = [];
packet[0] = 0x00;
packet[1] = 0x23;
device.write(packet, 32);
packet = device.read(packet,32);
let UniqueIdentifierByte1 = packet[2];
let UniqueIdentifierByte2 = packet[3];
let UniqueIdentifierByte3 = packet[4];
device.log("Unique Device Identifier: " + UniqueIdentifierByte1 + UniqueIdentifierByte2 + UniqueIdentifierByte3);
device.pause(30);
}
function effectEnable() //Enable the SignalRGB Effect Mode
{
let packet = [];
packet[0] = 0x00;
packet[1] = 0x25;
device.write(packet,32);
device.pause(30);
}
function effectDisable() //Revert to Hardware Mode
{
let packet = [];
packet[0] = 0x00;
packet[1] = 0x26;
device.write(packet,32);
}
function grabColors(shutdown = false)
{
let rgbdata = [];
for(let iIdx = 0; iIdx < vKeys.length; iIdx++)
{
let iPxX = vKeyPositions[iIdx][0];
let iPxY = vKeyPositions[iIdx][1];
let color;
if(shutdown)
{
color = hexToRgb(shutdownColor);
}
else if (LightingMode === "Forced")
{
color = hexToRgb(forcedColor);
}
else
{
color = device.color(iPxX, iPxY);
}
let iLedIdx = vKeys[iIdx] * 3;
rgbdata[iLedIdx] = color[0];
rgbdata[iLedIdx+1] = color[1];
rgbdata[iLedIdx+2] = color[2];
}
return rgbdata;
}
function sendColors()
{
let rgbdata = grabColors();
for(var index = 0; index < 8; index++) //This will need rounded up to closest value for your board.
{
let packet = [];
let offset = index * 9;
packet[0] = 0x00;
packet[1] = 0x24;
packet[2] = offset;
packet[3] = 0x09;
packet = packet.concat(rgbdata.splice(0, 27));
device.write(packet, 33);
}
}
function hexToRgb(hex)
{
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
let colors = [];
colors[0] = parseInt(result[1], 16);
colors[1] = parseInt(result[2], 16);
colors[2] = parseInt(result[3], 16);
return colors;
}
export function Validate(endpoint)
{
return endpoint.interface === 1;
}
export function Image()
{
return "";
}
注意脚本中打印的device.log
需要在SignalRGB的设备管理里头看:
开发好的自定义脚本可以加上Image,放到C:\%USER%\Documents\WhirlwindFX\Plugins\
到此,只要刷上新的固件,在SignalRGB中导入你的脚本,就可以愉快的用SignalRGB控制你的键盘了
扩展:添加模式切换,大小写,饱和度亮度调整等
FK680键盘原来有个CapsLock灯的效果,这个是在qmk_firmware\keyboards\FK680ProV2\keymaps\via\keymap.c
中由键盘开发者实现的,代码逻辑如下:
这段代码同时实现了两个功能:如果上/下键盘RGB等效关闭则关灯;如果大写锁定打开,则打开index=26灯。从键盘LED INDEX可以看出26就是大小写键。
void rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) {
if (user_config.top_rgb_change)
{
for (size_t i = 0; i < 70; i++)
{
RGB_MATRIX_INDICATOR_SET_COLOR(i, 0, 0, 0);
}
}
if (host_keyboard_led_state().caps_lock) {
RGB_MATRIX_INDICATOR_SET_COLOR(26, 0, 255, 255); // assuming caps lock is at led #5
}
if (user_config.bottom_rgb_change)
{
for (size_t i = 70; i < 110; i++)
{
RGB_MATRIX_INDICATOR_SET_COLOR(i, 0, 0, 0);
}
}
}
在支持SignalRGB之后,SignalRGB会覆盖大小写灯,还原原来的功能只需要加一行continue:
void led_streaming(uint8_t *data) //Stream data from HID Packets to Keyboard.
{
uint8_t index = data[1];
uint8_t numberofleds = data[2];
if(numberofleds >= 10)
{
packet[1] = DEVICE_ERROR_LEDS;
raw_hid_send(packet,32);
return;
}
for (uint8_t i = 0; i < numberofleds; i++)
{
uint8_t offset = (i * 3) + 3;
uint8_t r = data[offset];
uint8_t g = data[offset + 1];
uint8_t b = data[offset + 2];
if (index + i == 26 && host_keyboard_led_state().caps_lock) continue;
rgb_matrix_set_color(index + i, r, g, b);
}
}
饱和度和亮度调整是在计算RGB是使用HSV格式,修改 S(atuation) 和 V(alue) 实现的,同时 H(ue) 也是可以修改的,不过 Hue对于有RGB颜色循环的等效无效。使用SignalRGB之后,键盘直接显示电脑端传过来的RGB值,这样很直接。但是我们也可以加上原来的饱和度S,亮度S调节。通过将RGB值先用rgb_to_hsv
转换成HSV值,在对S和V进行微调,然后再用hsv_to_rgb
转换回RGB格式,调用rgb_matrix_set_color
显示。这么做是可行的,可以通过键盘对电脑同步RGB灯效再做加强/减弱饱和度/亮度的调整。但是我也遇到了问题,rgb->hsv->rgb这样的转换造成了一些色彩的丢失,特别表现在亮度上,原来变化自然的明暗变得非常生硬,几乎没有了亮度变化,不知道是转换中不可避免的损失,还是我哪里写错了。
void led_streaming(uint8_t *data) //Stream data from HID Packets to Keyboard.
{
uint8_t index = data[1];
uint8_t numberofleds = data[2];
if(numberofleds >= 10)
{
packet[1] = DEVICE_ERROR_LEDS;
raw_hid_send(packet,32);
return;
}
for (uint8_t i = 0; i < numberofleds; i++)
{
uint8_t offset = (i * 3) + 3;
uint8_t r = data[offset];
uint8_t g = data[offset + 1];
uint8_t b = data[offset + 2];
if (index + i == 26 && host_keyboard_led_state().caps_lock) continue;
if (rgb_matrix_get_mode() != RGB_MATRIX_SIGNALRGB) {
if (user_config.top_rgb_signal == true && index + i < 70) {
rgb_matrix_set_color(index + i, r, g, b);
continue;
}
else if (user_config.bottom_rgb_signal == true && index + i >= 70) {
rgb_matrix_set_color(index + i, r, g, b);
continue;
}
else continue;
};
// rgb_matrix_set_color(index + i, r, g, b);
RGB rgb;
rgb.r = r;
rgb.g = g;
rgb.b = b;
HSV hsv = rgb_to_hsv(rgb);
hsv.s = hsv.s + 128 - rgb_matrix_config.hsv.s;
hsv.s = hsv.s > 255 ? 255 : hsv.s;
hsv.s = hsv.s < 0 ? 0 : hsv.s;
hsv.v = hsv.v + 128 - rgb_matrix_config.hsv.v;
hsv.v = hsv.v > 255 ? 255 : hsv.v;
hsv.v = hsv.v < 0 ? 0 : hsv.v;
rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(index + i, rgb.r, rgb.g, rgb.b);
}
}
这里用到的rgb_to_hsv
不是qmk代码中自带的,是我从stackoverflow随便复制的一个函数:
HSV rgb_to_hsv(RGB rgb)
{
HSV hsv;
uint8_t rgbMin, rgbMax;
rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b);
rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b);
hsv.v = rgbMax;
if (hsv.v == 0)
{
hsv.h = 0;
hsv.s = 0;
return hsv;
}
long delta = rgbMax - rgbMin;
hsv.s = 255 * delta / hsv.v;
if (hsv.s == 0)
{
hsv.h = 0;
return hsv;
}
if (rgbMax == rgb.r)
hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin);
else if (rgbMax == rgb.g)
hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin);
else
hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin);
return hsv;
}
扩展:自定义动画
最后一个bonus,我想实现的上层字母区LED和下层背面LED实现不同的动画:
在SignalRGB的led_streaming
函数中添加如下开关:
user_config.top_rgb_signal
为True
时上层RGB(前70个灯)一定输出;
user_config.bottom_rgb_signal
为True
时下层RGB(后40个灯)一定输出;
void led_streaming(uint8_t *data) //Stream data from HID Packets to Keyboard.
{
uint8_t index = data[1];
uint8_t numberofleds = data[2];
if(numberofleds >= 10)
{
packet[1] = DEVICE_ERROR_LEDS;
raw_hid_send(packet,32);
return;
}
for (uint8_t i = 0; i < numberofleds; i++)
{
uint8_t offset = (i * 3) + 3;
uint8_t r = data[offset];
uint8_t g = data[offset + 1];
uint8_t b = data[offset + 2];
if (index + i == 26 && host_keyboard_led_state().caps_lock) continue;
if (rgb_matrix_get_mode() != RGB_MATRIX_SIGNALRGB) {
if (user_config.top_rgb_signal == true && index + i < 70) {
rgb_matrix_set_color(index + i, r, g, b);
continue;
}
else if (user_config.bottom_rgb_signal == true && index + i >= 70) {
rgb_matrix_set_color(index + i, r, g, b);
continue;
}
else continue;
};
rgb_matrix_set_color(index + i, r, g, b);
}
}
user_config
定义如下,原来是在keymap.c
中定义的,挪到via.c
中
typedef union {
uint32_t raw;
struct {
bool top_rgb_change :1;
bool bottom_rgb_change :1;
bool top_rgb_signal :1;
bool bottom_rgb_signal :1;
};
} user_config_t;
user_config_t user_config;
最后为了SignalRGB显示在上层、原版动画显示在下层的效果,需要在开关打开时,原版的RGB不写入,防止冲突。在animation中定义自定义函数rgb_matrix_set_color_user_config
,让原版的animation设置RGB时都调用这个函数。
#pragma once
typedef HSV (*dx_dy_dist_f)(HSV hsv, int16_t dx, int16_t dy, uint8_t dist, uint8_t time);
void rgb_matrix_set_color_user_config(int index, uint8_t r, uint8_t g, uint8_t b) {
if (user_config.top_rgb_signal == false && index < 70) {
rgb_matrix_set_color(index, r, g, b);
return;
}
else if (user_config.bottom_rgb_signal == false && index >= 70) {
rgb_matrix_set_color(index, r, g, b);
return;
}
else return;
}
bool effect_runner_dx_dy_dist(effect_params_t* params, dx_dy_dist_f effect_func) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
uint8_t time = scale16by8(g_rgb_timer, rgb_matrix_config.speed / 2);
for (uint8_t i = led_min; i < led_max; i++) {
RGB_MATRIX_TEST_LED_FLAGS();
int16_t dx = g_led_config.point[i].x - k_rgb_matrix_center.x;
int16_t dy = g_led_config.point[i].y - k_rgb_matrix_center.y;
uint8_t dist = sqrt16(dx * dx + dy * dy);
RGB rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, dist, time));
rgb_matrix_set_color_user_config(i, rgb.r, rgb.g, rgb.b);
}
return rgb_matrix_check_finished_leds(led_max);
}
效果:
代码库:
https://github.com/azuse/qmk_firmware
21 thoughts to “任何QMK VIA键盘接入SignalRGB实现电脑端RGB控制”
Thank for your post.
But can you share source code of Blockboy FK640RGB? (It same your keyboard but 64 key and 88 led)
I can’t find because I don’t know Chinese and don’t have QQ account 🙁
I just comment but it not show, sorry if it duplicate
FK640 doesn’t have source code uploaded in QQ chat group, I tried to reach out to the seller on Taobao but they won’t give me because I didn’t purchase one.😣 They want a order number, IDK if you have.
大佬你好(*´▽`)ノノ,我有一个和你相同的设备,正在尝试烧录与你相同的固件,用于s rgb灯光管理,但是我存在一个非常愚蠢的问题是FK680pro这个键盘该如何进入固件烧录模式?期待你的解答
背面有个reset键,对角线短接两下。其实可以问客服(。PS,很多键盘pcb都在背面有reset键
感谢大佬的解答,最后确实是问了客服解决了这个问题,另外还想问下最后一段bonus中上下层分别控制在操作中如何实现,需要再改动一下固件吗
大佬我拉了你的库但是编译不了好像是缺少了 目录下的platforms/chibios/boards/STM32_F103_STM32DUINO/ld 路径里的uf2 id文件以及
ib/ChibiOS-Contrib/os/hal/boards/目录下的 STM相关文件。你能发我一份你本机能运行的完整源码到我的邮箱吗?客服和群里已经不提供源码了。拜托了你是我唯一的希望 哭哭
大佬可以分享FK750Pro固件或者有哪些地方需要修改吗
https://github.com/azuse/qmk_firmware
为什么我在msys编译固件时提示没有规则可制作目标“lib/chibios-contrib/os/hal/boards/GENER
IC_STM32_F103/board.mk(fk680和fk750都是这样,其他键盘没问题)
你从github clone下的chibios-contrib库里没有这个芯片的编译文件,https://github.com/azuse/qmk_firmware/releases/download/1.0/qmk_firmware.zip 从这下一下
用这个下的提示”git describe –abbrev=6 –dirty –always –tags”报128。。。。
刚刚把mcu_selection.mk里的GENERIC_STM32_F103改成STM32_F103_STM32DUINO解决了
您好 您这里有k750pro的源文件吗 我也是在群里也问不到了
没有诶 我只买了fk680,找淘宝客服要会给吗?以前挺喜欢方块的因为他们家开源很友好
⚠ “git describe –abbrev=6 –dirty –always –tags” returned error code 128
⚠ “git describe –abbrev=6 –dirty –always –tags” returned error code 128
⚠ “git describe –abbrev=6 –dirty –always –tags” returned error code 128
builddefs/build_keyboard.mk:49: *** 多个目标匹配。 停止。
不知道什么情况
问下那个Image()函数返回的是图片的base64编码?是jpg还是png转换的,有没有什么需要特别注意的地方?
jpg和png应该都行,html里的
你好,请问如果是分体键盘的话应该怎么让signalrgb控制副键盘呢?我这边试过很多写法,都只能控制usb连着的主键盘
我也没用过分体键盘,我猜是不是signalrgb还是通过hid和主键盘的芯片usb通信,主键盘上raw_hid_receive处理函数里,再调用复键盘通信,把RGB效果发送给复键盘?请问你研究出来了吗?
你好,感谢你的分享,可是大概是因为我的环境哪里有问题源码编译一直出错😭,你那边有打编译好的固件吗,本来的源码我这也没有😣
https://github.com/azuse/qmk_firmware/releases/tag/1.0