Add wired split support using UART communication

This commit is contained in:
Megamind 2022-02-03 22:57:02 +08:00
parent 8e91e5ada1
commit e92a98625a
12 changed files with 458 additions and 21 deletions

View file

@ -45,7 +45,7 @@ target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed
target_sources_ifdef(CONFIG_USB app PRIVATE src/events/usb_conn_state_changed.c) target_sources_ifdef(CONFIG_USB app PRIVATE src/events/usb_conn_state_changed.c)
target_sources(app PRIVATE src/behaviors/behavior_reset.c) target_sources(app PRIVATE src/behaviors/behavior_reset.c)
target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c) target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c)
if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL OR CONFIG_ZMK_SPLIT_SERIAL_ROLE_CENTRAL)
target_sources(app PRIVATE src/behaviors/behavior_key_press.c) target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c) target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c)
target_sources(app PRIVATE src/behaviors/behavior_sticky_key.c) target_sources(app PRIVATE src/behaviors/behavior_sticky_key.c)
@ -75,6 +75,13 @@ endif()
if (CONFIG_ZMK_SPLIT_BLE AND CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) if (CONFIG_ZMK_SPLIT_BLE AND CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/split/bluetooth/central.c) target_sources(app PRIVATE src/split/bluetooth/central.c)
endif() endif()
if (CONFIG_ZMK_SPLIT_SERIAL AND (NOT CONFIG_ZMK_SPLIT_SERIAL_ROLE_CENTRAL))
target_sources(app PRIVATE src/split_listener.c)
target_sources(app PRIVATE src/split/serial/service.c)
endif()
if (CONFIG_ZMK_SPLIT_SERIAL AND CONFIG_ZMK_SPLIT_SERIAL_ROLE_CENTRAL)
target_sources(app PRIVATE src/split/serial/central.c)
endif()
target_sources_ifdef(CONFIG_USB app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_USB app PRIVATE src/usb.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/hog.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/hog.c)
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c)

View file

@ -155,10 +155,14 @@ config ZMK_SPLIT
if ZMK_SPLIT if ZMK_SPLIT
choice ZMK_SPLIT_TYPE
prompt "ZMK split type"
if ZMK_BLE
menuconfig ZMK_SPLIT_BLE menuconfig ZMK_SPLIT_BLE
bool "Split keyboard support via BLE transport" bool "Split keyboard support via BLE transport"
depends on ZMK_BLE depends on ZMK_BLE
default y
select BT_USER_PHY_UPDATE select BT_USER_PHY_UPDATE
if ZMK_SPLIT_BLE if ZMK_SPLIT_BLE
@ -199,9 +203,6 @@ config ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE
int "Max number of key position state events to queue to send to the central" int "Max number of key position state events to queue to send to the central"
default 10 default 10
config ZMK_USB
default n
config BT_MAX_PAIRED config BT_MAX_PAIRED
default 1 default 1
@ -217,6 +218,56 @@ endif
#ZMK_SPLIT_BLE #ZMK_SPLIT_BLE
endif endif
# ZMK_BLE
endif
if ZMK_USB
menuconfig ZMK_SPLIT_SERIAL
bool "Split keyboard support via Serial transport"
depends on ZMK_USB
if ZMK_SPLIT_SERIAL
choice ZMK_SPLIT_SERIAL_ROLE
prompt "ZMK split serial role"
menuconfig ZMK_SPLIT_SERIAL_ROLE_CENTRAL
bool "Central"
menuconfig ZMK_SPLIT_SERIAL_ROLE_PERIPHERAL
bool "Peripheral"
if ZMK_SPLIT_SERIAL_ROLE_PERIPHERAL
config ZMK_SPLIT_SERIAL_PERIPHERAL_STACK_SIZE
int "Serial split peripheral notify thread stack size"
default 512
config ZMK_SPLIT_SERIAL_PERIPHERAL_PRIORITY
int "Serial split peripheral notify thread priority"
default 5
config ZMK_SPLIT_SERIAL_PERIPHERAL_POSITION_QUEUE_SIZE
int "Max number of key position state events to queue to send to the central"
default 10
# ZMK_SPLIT_SERIAL_ROLE_PERIPHERAL
endif
# ZMK_SPLIT_SERIAL_ROLE
endchoice
# ZMK_SPLIT_SERIAL
endif
# ZMK_USB
endif
# ZMK_SPLIT_TYPE
endchoice
#ZMK_SPLIT #ZMK_SPLIT
endif endif

View file

@ -1,19 +1,41 @@
# Copyright (c) 2021 The ZMK Contributors # Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
if SHIELD_A_DUX_LEFT || SHIELD_A_DUX_RIGHT
if ZMK_BLE || ZMK_USB
config ZMK_SPLIT
default y
endif
if SHIELD_A_DUX_LEFT if SHIELD_A_DUX_LEFT
config ZMK_KEYBOARD_NAME config ZMK_KEYBOARD_NAME
default "A. Dux" default "A. Dux"
if ZMK_BLE
config ZMK_SPLIT_BLE_ROLE_CENTRAL config ZMK_SPLIT_BLE_ROLE_CENTRAL
default y default y
endif endif
if SHIELD_A_DUX_LEFT || SHIELD_A_DUX_RIGHT if ZMK_USB
choice ZMK_SPLIT_SERIAL_ROLE
default ZMK_SPLIT_SERIAL_ROLE_CENTRAL
endchoice
endif
config ZMK_SPLIT # SHIELD_A_DUX_LEFT
default y endif
if SHIELD_A_DUX_RIGHT
if ZMK_USB
choice ZMK_SPLIT_SERIAL_ROLE
default ZMK_SPLIT_SERIAL_ROLE_PERIPHERAL
endchoice
endif
# SHIELD_A_DUX_RIGHT
endif
endif endif

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
/ {
chosen {
zmk,split-serial = &usart1;
};
};
&usart1 {
status = "okay";
};
&kscan0 {
input-gpios =
<&pro_micro_d 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_a 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_a 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, /* Changed since swapped pins */
<&pro_micro_a 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_a 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, /* TODO: Use 0 if Console is needed */
<&pro_micro_d 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>,
<&pro_micro_d 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
;
};

View file

@ -19,6 +19,3 @@ struct zmk_split_run_behavior_payload {
struct zmk_split_run_behavior_data data; struct zmk_split_run_behavior_data data;
char behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN]; char behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN];
} __packed; } __packed;
int zmk_split_bt_position_pressed(uint8_t position);
int zmk_split_bt_position_released(uint8_t position);

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/types.h>
#define SPLIT_DATA_LEN 16
#define SPLIT_TYPE_KEYPOSITION 0
typedef struct _split_data_t {
uint16_t type;
uint8_t data[SPLIT_DATA_LEN];
uint16_t crc;
} split_data_t;
int zmk_split_position_pressed(uint8_t position);
int zmk_split_position_released(uint8_t position);

View file

@ -19,6 +19,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/behavior.h> #include <zmk/behavior.h>
#include <zmk/matrix.h> #include <zmk/matrix.h>
#include <zmk/split/bluetooth/uuid.h> #include <zmk/split/bluetooth/uuid.h>
#include <zmk/split/common.h>
#include <zmk/split/bluetooth/service.h> #include <zmk/split/bluetooth/service.h>
#define POS_STATE_LEN 16 #define POS_STATE_LEN 16
@ -141,12 +142,12 @@ int send_position_state() {
return 0; return 0;
} }
int zmk_split_bt_position_pressed(uint8_t position) { int zmk_split_position_pressed(uint8_t position) {
WRITE_BIT(position_state[position / 8], position % 8, true); WRITE_BIT(position_state[position / 8], position % 8, true);
return send_position_state(); return send_position_state();
} }
int zmk_split_bt_position_released(uint8_t position) { int zmk_split_position_released(uint8_t position) {
WRITE_BIT(position_state[position / 8], position % 8, false); WRITE_BIT(position_state[position / 8], position % 8, false);
return send_position_state(); return send_position_state();
} }

View file

@ -0,0 +1,190 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zmk/split/common.h>
#include <init.h>
#include <sys/crc.h>
#include <device.h>
#include <drivers/uart.h>
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/matrix.h>
#if DT_HAS_CHOSEN(zmk_split_serial)
#define UART_NODE1 DT_CHOSEN(zmk_split_serial)
const struct device *serial_dev = DEVICE_DT_GET(UART_NODE1);
static int uart_ready = 0;
#define CONFIG_ZMK_SPLIT_SERIAL_CENTRAL_POSITION_QUEUE_SIZE 5
#define CONFIG_SERIAL_THREAD_STACK_SIZE 128
#define CONFIG_SERIAL_THREAD_PRIORITY 5
static void split_serial_receive_thread(void *unused, void *unused1, void *unused2);
K_MEM_SLAB_DEFINE(split_memory_slab, sizeof(split_data_t), \
CONFIG_ZMK_SPLIT_SERIAL_CENTRAL_POSITION_QUEUE_SIZE, 4);
K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed), \
CONFIG_ZMK_SPLIT_SERIAL_CENTRAL_POSITION_QUEUE_SIZE, 4);
K_THREAD_DEFINE(split_central, CONFIG_SERIAL_THREAD_STACK_SIZE,
split_serial_receive_thread, NULL, NULL, NULL,
K_PRIO_PREEMPT(CONFIG_SERIAL_THREAD_PRIORITY), 0, 0);
static void peripheral_event_work_callback(struct k_work *work) {
struct zmk_position_state_changed ev;
while (k_msgq_get(&peripheral_event_msgq, &ev, K_NO_WAIT) == 0) {
LOG_DBG("Trigger key position state change for %d", ev.position);
ZMK_EVENT_RAISE(new_zmk_position_state_changed(ev));
}
}
K_WORK_DEFINE(peripheral_event_work, peripheral_event_work_callback);
static uint8_t split_central_notify_func(const void *data, uint16_t length) {
static uint8_t position_state[SPLIT_DATA_LEN];
uint8_t changed_positions[SPLIT_DATA_LEN];
const split_data_t *split_data = data;
uint16_t crc;
LOG_DBG("[NOTIFICATION] data %p type:%u CRC:%u", data, split_data->type, split_data->crc);
crc = crc16_ansi(split_data->data, sizeof(split_data->data));
if (crc != split_data->crc) {
LOG_WRN("CRC mismatch (%x:%x), skipping data", crc, split_data->crc);
return 0;
}
for (int i = 0; i < SPLIT_DATA_LEN; i++) {
changed_positions[i] = split_data->data[i] ^ position_state[i];
position_state[i] = split_data->data[i];
}
for (int i = 0; i < SPLIT_DATA_LEN; i++) {
for (int j = 0; j < 8; j++) {
if (changed_positions[i] & BIT(j)) {
uint32_t position = (i * 8) + j;
bool pressed = position_state[i] & BIT(j);
struct zmk_position_state_changed ev = {
.position = position, .state = pressed, .timestamp = k_uptime_get()};
if (position > ZMK_KEYMAP_LEN) {
LOG_WRN("Invalid position: %u", position);
continue;
}
k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
k_work_submit(&peripheral_event_work);
}
}
}
return 0;
}
static char *alloc_position_state_buffer() {
char *block_ptr = NULL;
if (k_mem_slab_alloc(&split_memory_slab, (void **)&block_ptr, K_NO_WAIT) == 0) {
memset(block_ptr, 0, SPLIT_DATA_LEN);
} else {
LOG_WRN("Memory allocation time-out");
}
return block_ptr;
}
static void free_position_state_buffer(char *block_ptr) {
k_mem_slab_free(&split_memory_slab, (void **)&block_ptr);
}
static void uart_callback(const struct device *dev, struct uart_event *evt, void *user_data) {
char *buf = NULL;
switch (evt->type) {
case UART_RX_STOPPED:
LOG_DBG("UART device:%s rx stopped", serial_dev->name);
break;
case UART_RX_BUF_REQUEST:
LOG_DBG("UART device:%s rx extra buf req", serial_dev->name);
buf = alloc_position_state_buffer();
if (NULL != buf) {
int ret = uart_rx_buf_rsp(serial_dev, buf, sizeof(split_data_t));
if (0 != ret) {
LOG_WRN("UART device:%s rx extra buf req add failed: %d", serial_dev->name, ret);
free_position_state_buffer(buf);
}
}
break;
case UART_RX_RDY:
LOG_DBG("UART device:%s rx buf ready", serial_dev->name);
break;
case UART_RX_BUF_RELEASED:
LOG_DBG("UART device:%s rx buf released", serial_dev->name);
split_central_notify_func(evt->data.rx_buf.buf, sizeof(split_data_t));
free_position_state_buffer(evt->data.rx_buf.buf);
break;
default:
LOG_DBG("UART device:%s unhandled event: %u", serial_dev->name, evt->type);
break;
};
return;
}
static void split_serial_receive_thread(void *unused, void *unused1, void *unused2) {
if (!device_is_ready(serial_dev)) {
LOG_WRN("UART device:%s not ready", serial_dev->name);
return;
}
int ret = uart_callback_set(serial_dev, uart_callback, NULL);
if (ret == -ENOTSUP || ret == -ENOSYS) {
LOG_WRN("UART device:%s ASYNC not supported", serial_dev->name);
return;
}
uart_ready = 1;
LOG_DBG("UART device:%s ready", serial_dev->name);
while(true) {
char *buf = alloc_position_state_buffer();
if (NULL == buf) {
k_msleep(100);
continue;
}
int ret = uart_rx_enable(serial_dev, buf, sizeof(split_data_t), SYS_FOREVER_MS);
if (ret == -ENOTSUP) {
LOG_WRN("UART device:%s not supporting DMA", serial_dev->name);
free_position_state_buffer(buf);
return;
}
if (ret != 0 && ret != -EBUSY) {
LOG_WRN("UART device:%s RX error:%d", serial_dev->name, ret);
free_position_state_buffer(buf);
continue;
}
};
}
#endif /* DT_HAS_CHOSEN(zmk_matrix_transform) */

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zmk/split/common.h>
#include <sys/util.h>
#include <sys/crc.h>
#include <init.h>
#include <device.h>
#include <drivers/uart.h>
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/matrix.h>
#if DT_HAS_CHOSEN(zmk_split_serial)
#define UART_NODE1 DT_CHOSEN(zmk_split_serial)
const struct device *serial_dev = DEVICE_DT_GET(UART_NODE1);
static int uart_ready = 0;
static uint8_t position_state[SPLIT_DATA_LEN];
K_THREAD_STACK_DEFINE(service_q_stack, CONFIG_ZMK_SPLIT_SERIAL_PERIPHERAL_STACK_SIZE);
struct k_work_q service_work_q;
K_MSGQ_DEFINE(position_state_msgq, sizeof(char[SPLIT_DATA_LEN]),
CONFIG_ZMK_SPLIT_SERIAL_PERIPHERAL_POSITION_QUEUE_SIZE, 4);
void send_data_via_uart(const struct device *dev, char *data, size_t len) {
if (!uart_ready) {
return;
}
for (int i=0; i < len; i++) {
uart_poll_out(serial_dev, data[i]);
}
}
void send_position_state_callback(struct k_work *work) {
split_data_t split_data = {.type = SPLIT_TYPE_KEYPOSITION};
while (k_msgq_get(&position_state_msgq, &split_data.data, K_NO_WAIT) == 0) {
split_data.crc = crc16_ansi(split_data.data, sizeof(split_data.data));
send_data_via_uart(serial_dev, (void *)&split_data, sizeof(split_data));
}
};
K_WORK_DEFINE(service_position_notify_work, send_position_state_callback);
int send_position_state() {
int err = k_msgq_put(&position_state_msgq, position_state, K_MSEC(100));
if (err) {
switch (err) {
case -EAGAIN: {
LOG_WRN("Position state message queue full, popping first message and queueing again");
uint8_t discarded_state[SPLIT_DATA_LEN];
k_msgq_get(&position_state_msgq, &discarded_state, K_NO_WAIT);
return send_position_state();
}
default:
LOG_WRN("Failed to queue position state to send (%d)", err);
return err;
}
}
k_work_submit_to_queue(&service_work_q, &service_position_notify_work);
return 0;
}
int zmk_split_position_pressed(uint8_t position) {
WRITE_BIT(position_state[position / 8], position % 8, true);
return send_position_state();
}
int zmk_split_position_released(uint8_t position) {
WRITE_BIT(position_state[position / 8], position % 8, false);
return send_position_state();
}
int service_init(const struct device *_arg) {
if (!device_is_ready(serial_dev)) {
LOG_WRN("UART device:%s not ready", serial_dev->name);
return 1;
}
uart_ready = 1;
LOG_INF("UART device:%s ready", serial_dev->name);
k_work_q_start(&service_work_q, service_q_stack, K_THREAD_STACK_SIZEOF(service_q_stack),
CONFIG_ZMK_SPLIT_SERIAL_PERIPHERAL_PRIORITY);
return 0;
}
SYS_INIT(service_init, APPLICATION, CONFIG_ZMK_USB_INIT_PRIORITY);
#endif /* DT_HAS_CHOSEN(zmk_matrix_transform) */

View file

@ -8,7 +8,7 @@
#include <power/reboot.h> #include <power/reboot.h>
#include <logging/log.h> #include <logging/log.h>
#include <zmk/split/bluetooth/service.h> #include <zmk/split/common.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
@ -22,9 +22,9 @@ int split_listener(const zmk_event_t *eh) {
const struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh); const struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh);
if (ev != NULL) { if (ev != NULL) {
if (ev->state) { if (ev->state) {
return zmk_split_bt_position_pressed(ev->position); return zmk_split_position_pressed(ev->position);
} else { } else {
return zmk_split_bt_position_released(ev->position); return zmk_split_position_released(ev->position);
} }
} }
return ZMK_EV_EVENT_BUBBLE; return ZMK_EV_EVENT_BUBBLE;

View file

@ -4,12 +4,14 @@ manifest:
url-base: https://github.com/zephyrproject-rtos url-base: https://github.com/zephyrproject-rtos
- name: zmkfirmware - name: zmkfirmware
url-base: https://github.com/zmkfirmware url-base: https://github.com/zmkfirmware
- name: megamind4089
url-base: https://github.com/megamind4089
- name: microsoft - name: microsoft
url-base: https://github.com/microsoft url-base: https://github.com/microsoft
projects: projects:
- name: zephyr - name: zephyr
remote: zmkfirmware remote: megamind4089
revision: v2.5.0+zmk-fixes revision: v2.5.0+zmk-fixes-stm32f4
clone-depth: 1 clone-depth: 1
import: import:
# TODO: Rename once upstream offers option like `exclude` or `denylist` # TODO: Rename once upstream offers option like `exclude` or `denylist`