Skip to content

Commit

Permalink
feat(split): wired split over serial support (WIP 2024-01-07)
Browse files Browse the repository at this point in the history
  • Loading branch information
xudongzheng committed Jan 8, 2024
1 parent 4036f15 commit 173f656
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 9 deletions.
29 changes: 29 additions & 0 deletions app/include/zmk/split/serial/serial.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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_POLLING
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);
18 changes: 13 additions & 5 deletions app/src/split/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
9 changes: 5 additions & 4 deletions app/src/split/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -70,3 +70,4 @@ config ZMK_SPLIT_PERIPHERAL_HID_INDICATORS
endif

rsource "bluetooth/Kconfig"
rsource "serial/Kconfig"
8 changes: 8 additions & 0 deletions app/src/split/serial/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
21 changes: 21 additions & 0 deletions app/src/split/serial/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
if ZMK_SPLIT && ZMK_SPLIT_SERIAL

menu "Serial Transport"

config ZMK_SPLIT_SERIAL_UART
bool "Serial over UART"
default y

# TODO TODO TODO once on 3.5/3.6, can default to
# DT_HAS_RASPBERRYPI_PICO_UART_PIO_ENABLED and BOARD_NATIVE_SIM
config ZMK_SPLIT_SERIAL_UART_POLLING
bool "Serial over UART Polling API"
default n

config ZMK_SPLIT_SERIAL_CDC_ACM
bool "Serial over USB CDC ACM"
default n

endmenu

endif
59 changes: 59 additions & 0 deletions app/src/split/serial/central.c
Original file line number Diff line number Diff line change
@@ -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;
}
}
196 changes: 196 additions & 0 deletions app/src/split/serial/serial.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* 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_POLLING
.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_POLLING
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_POLLING

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() {
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_POLLING
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

// TODO TODO TODO iirc need to check return function in zephyr 3.5
uart_irq_callback_user_data_set(sd->dev, serial_callback, sd);
uart_irq_rx_enable(sd->dev);
}

return 0;
}

SYS_INIT(serial_init, APPLICATION, CONFIG_ZMK_SPLIT_INIT_PRIORITY);

0 comments on commit 173f656

Please sign in to comment.