feat(split): wired split over serial RX support (WIP 2024-05-26)
This commit is contained in:
parent
5f0c8af83e
commit
233b63d637
7 changed files with 332 additions and 9 deletions
28
app/include/zmk/split/serial/serial.h
Normal file
28
app/include/zmk/split/serial/serial.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2023 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <zephyr/sys/ring_buffer.h>
|
||||
|
||||
// The serial protocol is defined for payloads of up to 254 bytes. This should
|
||||
// be large enough to ensure that one message can be fully buffered.
|
||||
#define SERIAL_BUF_SIZE 300
|
||||
|
||||
struct serial_device {
|
||||
const struct device *dev;
|
||||
uint8_t rx_buf[SERIAL_BUF_SIZE], tx_buf[SERIAL_BUF_SIZE];
|
||||
struct ring_buf rx_rb, tx_rb;
|
||||
struct k_work rx_work;
|
||||
|
||||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART_POLL
|
||||
bool poll;
|
||||
struct k_work tx_work;
|
||||
struct k_timer rx_timer;
|
||||
#endif
|
||||
};
|
||||
|
||||
void serial_handle_rx(uint32_t cmd, uint8_t *data, uint8_t len);
|
|
@ -1,15 +1,23 @@
|
|||
# Copyright (c) 2022 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if (CONFIG_ZMK_SPLIT_BLE AND (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL))
|
||||
target_sources(app PRIVATE listener.c)
|
||||
target_sources(app PRIVATE service.c)
|
||||
if (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
if (CONFIG_ZMK_SPLIT_BLE OR CONFIG_ZMK_SPLIT_SERIAL)
|
||||
target_sources(app PRIVATE listener.c)
|
||||
target_sources(app PRIVATE service.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (CONFIG_ZMK_SPLIT_BLE AND CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
target_sources(app PRIVATE central.c)
|
||||
if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
if (CONFIG_ZMK_SPLIT_BLE OR CONFIG_ZMK_SPLIT_SERIAL)
|
||||
target_sources(app PRIVATE central.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (CONFIG_ZMK_SPLIT_BLE)
|
||||
add_subdirectory(bluetooth)
|
||||
endif()
|
||||
|
||||
if (CONFIG_ZMK_SPLIT_SERIAL)
|
||||
add_subdirectory(serial)
|
||||
endif()
|
||||
|
|
|
@ -49,16 +49,16 @@ config ZMK_SPLIT_PERIPHERAL_POSITION_QUEUE_SIZE
|
|||
|
||||
endif #!ZMK_SPLIT_ROLE_CENTRAL
|
||||
|
||||
choice ZMK_SPLIT_TRANSPORT
|
||||
prompt "Split transport"
|
||||
|
||||
config ZMK_SPLIT_BLE
|
||||
bool "BLE"
|
||||
default ZMK_SPLIT && ZMK_BLE
|
||||
depends on ZMK_BLE
|
||||
select BT_USER_PHY_UPDATE
|
||||
select BT_AUTO_PHY_UPDATE
|
||||
|
||||
endchoice
|
||||
config ZMK_SPLIT_SERIAL
|
||||
bool "Serial"
|
||||
select RING_BUFFER
|
||||
|
||||
config ZMK_SPLIT_PERIPHERAL_HID_INDICATORS
|
||||
bool "Peripheral HID Indicators"
|
||||
|
@ -70,3 +70,4 @@ config ZMK_SPLIT_PERIPHERAL_HID_INDICATORS
|
|||
endif
|
||||
|
||||
rsource "bluetooth/Kconfig"
|
||||
rsource "serial/Kconfig"
|
||||
|
|
8
app/src/split/serial/CMakeLists.txt
Normal file
8
app/src/split/serial/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (c) 2023 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
|
||||
target_sources(app PRIVATE central.c)
|
||||
endif()
|
||||
|
||||
target_sources(app PRIVATE serial.c)
|
20
app/src/split/serial/Kconfig
Normal file
20
app/src/split/serial/Kconfig
Normal file
|
@ -0,0 +1,20 @@
|
|||
if ZMK_SPLIT && ZMK_SPLIT_SERIAL
|
||||
|
||||
menu "Serial Transport"
|
||||
|
||||
config ZMK_SPLIT_SERIAL_UART
|
||||
bool "Serial over UART"
|
||||
select CRC
|
||||
default y
|
||||
|
||||
config ZMK_SPLIT_SERIAL_UART_POLL
|
||||
bool "Serial over UART Polling API"
|
||||
default DT_HAS_RASPBERRYPI_PICO_UART_PIO_ENABLED || BOARD_NATIVE_SIM
|
||||
|
||||
config ZMK_SPLIT_SERIAL_CDC_ACM
|
||||
bool "Serial over USB CDC ACM"
|
||||
default n
|
||||
|
||||
endmenu
|
||||
|
||||
endif
|
59
app/src/split/serial/central.c
Normal file
59
app/src/split/serial/central.c
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2023 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include <zmk/split/central.h>
|
||||
#include <zmk/split/serial/serial.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
|
||||
// TODO TODO TODO
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_DECLARE(slicemk);
|
||||
|
||||
#define POSITION_STATE_DATA_LEN 16
|
||||
static uint8_t position_state[POSITION_STATE_DATA_LEN];
|
||||
static uint8_t changed_positions[POSITION_STATE_DATA_LEN];
|
||||
|
||||
static void serial_handle_bitmap(uint8_t *data, uint8_t len) {
|
||||
for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) {
|
||||
changed_positions[i] = ((uint8_t *)data)[i] ^ position_state[i];
|
||||
position_state[i] = ((uint8_t *)data)[i];
|
||||
LOG_DBG("TODO TODO TODO data: %d", position_state[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < POSITION_STATE_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);
|
||||
|
||||
// TODO TODO TODO does zero make sense? check ble central. what
|
||||
// slot is central itself?
|
||||
int slot = 0;
|
||||
|
||||
struct zmk_position_state_changed ev = {.source = slot,
|
||||
.position = position,
|
||||
.state = pressed,
|
||||
.timestamp = k_uptime_get()};
|
||||
zmk_position_state_change_handle(&ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void serial_handle_rx(uint32_t cmd, uint8_t *data, uint8_t len) {
|
||||
switch (cmd) {
|
||||
// Handle split bitmap transformed (sbt) version 0.
|
||||
case 0x73627400:
|
||||
serial_handle_bitmap(data, len);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERR("Received unexpected UART command 0x%08x", cmd);
|
||||
break;
|
||||
}
|
||||
}
|
199
app/src/split/serial/serial.c
Normal file
199
app/src/split/serial/serial.c
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (c) 2023 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/crc.h>
|
||||
|
||||
#include <zmk/split/serial/serial.h>
|
||||
|
||||
// TODO TODO TODO
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(slicemk);
|
||||
|
||||
#define SERIAL_MSG_PREFIX "UarT"
|
||||
|
||||
K_THREAD_STACK_DEFINE(serial_wq_stack, 1024);
|
||||
static struct k_work_q serial_wq;
|
||||
|
||||
static struct serial_device serial_devs[] = {
|
||||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART
|
||||
{
|
||||
.dev = DEVICE_DT_GET(DT_CHOSEN(zmk_split_uart)),
|
||||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART_POLL
|
||||
.poll = true,
|
||||
#endif
|
||||
},
|
||||
#endif
|
||||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_CDC_ACM
|
||||
{
|
||||
.dev = DEVICE_DT_GET(DT_CHOSEN(zmk_split_cdc_acm)),
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
#define CONFIG_ZMK_SPLIT_SERIAL_COUNT ARRAY_SIZE(serial_devs)
|
||||
|
||||
static bool serial_tx_callback(struct serial_device *ud) {
|
||||
// Read data from buffer. Stop transmitting if buffer is empty.
|
||||
uint8_t data[32];
|
||||
int len = ring_buf_peek(&ud->tx_rb, data, sizeof(data));
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write data to UART and remove number of bytes written from buffer.
|
||||
int ret = uart_fifo_fill(ud->dev, data, len);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("failed to fill UART FIFO (err %d)", ret);
|
||||
return true;
|
||||
}
|
||||
ring_buf_get(&ud->tx_rb, data, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void serial_rx_work_handler(struct k_work *work) {
|
||||
struct serial_device *sd = CONTAINER_OF(work, struct serial_device, rx_work);
|
||||
|
||||
// Continue processing data as long as the buffer exceeds the header length
|
||||
// (13 bytes).
|
||||
uint8_t data[280];
|
||||
while (ring_buf_peek(&sd->rx_rb, data, 13) >= 13) {
|
||||
// Discard single byte if prefix does not match.
|
||||
if (memcmp(data, SERIAL_MSG_PREFIX, strlen(SERIAL_MSG_PREFIX))) {
|
||||
uint8_t discard;
|
||||
ring_buf_get(&sd->rx_rb, &discard, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop processing if message body is not completely buffered.
|
||||
int len = data[12];
|
||||
int total = len + 13;
|
||||
if (ring_buf_size_get(&sd->rx_rb) < total) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check message checksum and handle message.
|
||||
uint32_t cmd, crc;
|
||||
ring_buf_get(&sd->rx_rb, data, total);
|
||||
memcpy(&cmd, &data[4], sizeof(cmd));
|
||||
memcpy(&crc, &data[8], sizeof(crc));
|
||||
if (crc == crc32_ieee(&data[13], len)) {
|
||||
serial_handle_rx(cmd, &data[13], len);
|
||||
} else {
|
||||
LOG_ERR("received UART message with invalid CRC32 checksum");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void serial_rx_callback(struct serial_device *sd) {
|
||||
uint8_t c;
|
||||
while (uart_fifo_read(sd->dev, &c, 1) == 1) {
|
||||
ring_buf_put(&sd->rx_rb, &c, 1);
|
||||
}
|
||||
k_work_submit_to_queue(&serial_wq, &sd->rx_work);
|
||||
}
|
||||
|
||||
static void serial_callback(const struct device *dev, void *data) {
|
||||
if (uart_irq_update(dev)) {
|
||||
struct serial_device *ud = data;
|
||||
|
||||
if (uart_irq_tx_ready(dev)) {
|
||||
// If transmission complete, disable IRQ until next transmission.
|
||||
bool complete = serial_tx_callback(ud);
|
||||
if (complete) {
|
||||
uart_irq_tx_disable(dev);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO TODO TODO lookup index in serial_devs array for slot ID?
|
||||
if (uart_irq_rx_ready(dev)) {
|
||||
serial_rx_callback(ud);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void serial_write(struct serial_device *sd, uint32_t cmd, uint8_t *data, uint8_t len) {
|
||||
// TODO TODO TODO use buf with size SERIAL_BUF_SIZE. do single
|
||||
// ring_buf_put() to avoid potential race
|
||||
uint8_t header[13] = SERIAL_MSG_PREFIX;
|
||||
memcpy(&header[4], &cmd, sizeof(cmd));
|
||||
uint32_t crc = crc32_ieee(data, len);
|
||||
memcpy(&header[8], &crc, sizeof(crc));
|
||||
header[12] = len;
|
||||
ring_buf_put(&sd->tx_rb, header, sizeof(header));
|
||||
ring_buf_put(&sd->tx_rb, data, len);
|
||||
|
||||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART_POLL
|
||||
if (sd->poll) {
|
||||
k_work_submit_to_queue(&serial_wq, &sd->tx_work);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
uart_irq_tx_enable(sd->dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART_POLL
|
||||
|
||||
static void serial_tx_work_handler(struct k_work *work) {
|
||||
struct serial_device *sd = CONTAINER_OF(work, struct serial_device, tx_work);
|
||||
uint8_t c;
|
||||
while (ring_buf_get(&sd->tx_rb, &c, sizeof(c))) {
|
||||
uart_poll_out(sd->dev, c);
|
||||
}
|
||||
}
|
||||
|
||||
static void serial_rx_timer_handler(struct k_timer *timer) {
|
||||
struct serial_device *sd = CONTAINER_OF(timer, struct serial_device, rx_timer);
|
||||
uint8_t c;
|
||||
while (uart_poll_in(sd->dev, &c) == 0) {
|
||||
ring_buf_put(&sd->rx_rb, &c, sizeof(c));
|
||||
}
|
||||
k_work_submit_to_queue(&serial_wq, &sd->rx_work);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int serial_init(void) {
|
||||
struct k_work_queue_config uart_tx_cfg = {.name = "serial_wq"};
|
||||
k_work_queue_start(&serial_wq, serial_wq_stack, K_THREAD_STACK_SIZEOF(serial_wq_stack), 14,
|
||||
&uart_tx_cfg);
|
||||
|
||||
for (int i = 0; i < CONFIG_ZMK_SPLIT_SERIAL_COUNT; i++) {
|
||||
struct serial_device *sd = &serial_devs[i];
|
||||
if (!device_is_ready(sd->dev)) {
|
||||
LOG_ERR("failed to get serial device %s", sd->dev->name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize ring buffer.
|
||||
ring_buf_init(&sd->rx_rb, sizeof(sd->rx_buf), sd->rx_buf);
|
||||
ring_buf_init(&sd->tx_rb, sizeof(sd->tx_buf), sd->tx_buf);
|
||||
|
||||
k_work_init(&sd->rx_work, serial_rx_work_handler);
|
||||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART_POLL
|
||||
if (sd->poll) {
|
||||
k_timer_init(&sd->rx_timer, serial_rx_timer_handler, NULL);
|
||||
k_timer_start(&sd->rx_timer, K_NO_WAIT, K_TICKS(1));
|
||||
k_work_init(&sd->tx_work, serial_tx_work_handler);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
int err = uart_irq_callback_user_data_set(sd->dev, serial_callback, sd);
|
||||
if (err) {
|
||||
LOG_ERR("failed to set callback for %s (err %d)", sd->dev->name, err);
|
||||
return err;
|
||||
}
|
||||
uart_irq_rx_enable(sd->dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(serial_init, APPLICATION, CONFIG_ZMK_SPLIT_INIT_PRIORITY);
|
Loading…
Add table
Reference in a new issue