Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split serial #1954

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/src/split/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@

if (CONFIG_ZMK_SPLIT_BLE)
add_subdirectory(bluetooth)
endif()
endif()

if (CONFIG_ZMK_SPLIT_SERIAL)
add_subdirectory(serial)
endif()
4 changes: 4 additions & 0 deletions app/src/split/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ config ZMK_SPLIT_BLE
select BT_USER_PHY_UPDATE
select BT_AUTO_PHY_UPDATE

config ZMK_SPLIT_SERIAL
bool "Serial"

endchoice

#ZMK_SPLIT
endif

rsource "bluetooth/Kconfig"
rsource "serial/Kconfig"
13 changes: 13 additions & 0 deletions app/src/split/serial/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
target_link_libraries(app PRIVATE
COBS
)

if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE central.c)
target_sources(app PRIVATE interrupt.c)
else ()
target_sources(app PRIVATE peripheral.c)
target_sources(app PRIVATE polling.c)
endif()
44 changes: 44 additions & 0 deletions app/src/split/serial/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT

if ZMK_SPLIT && ZMK_SPLIT_SERIAL

menu "Serial Transport"

if ZMK_SPLIT_ROLE_CENTRAL

config ZMK_SPLIT_SERIAL_CENTRAL_PRIORITY
int "Serial split peripheral workqueue thread priority"
default 5

config ZMK_SPLIT_SERIAL_CENTRAL_POSITION_QUEUE_SIZE
int "Max number of key position state events to queue when received from peripherals"
default 5

config ZMK_SPLIT_SERIAL_CENTRAL_RUN_STACK_SIZE
int "Serial split central write thread stack size"
default 512

endif # ZMK_SPLIT_ROLE_CENTRAL

if !ZMK_SPLIT_ROLE_CENTRAL

config ZMK_SPLIT_SERIAL_PERIPHERAL_STACK_SIZE
int "Serial split peripheral notify thread stack size"
default 650

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_ROLE_CENTRAL
endif

endmenu

#ZMK_SPLIT_SERIAL
endif
138 changes: 138 additions & 0 deletions app/src/split/serial/central.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <cobs.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/crc.h>
#include <zmk/event_manager.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/events/sensor_event.h>
#include <zmk/sensors.h>

#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#include "private.h"

/* Let's provide enough space for multiple messages to reduce the risk of
* having to drop new ones. */
RING_BUF_DECLARE(zmk_split_serial_rx_ringbuf, MAX_MESSAGE_LEN * 2);

static void on_rx_done(struct net_buf_simple *const buf) {
static uint8_t positions[POSITION_STATE_DATA_LEN];
const int64_t timestamp = k_uptime_get();

if (buf->len < 3) {
LOG_ERR("Message is smaller than it's header");
return;
}

const uint16_t crc_received = net_buf_simple_remove_le16(buf);
const uint16_t crc_calculated = crc16_ccitt(0, buf->data, buf->len);
if (crc_received != crc_calculated) {
LOG_ERR("Invalid checksum. received=%04X calculated=%04x", crc_received, crc_calculated);
return;
}

const uint8_t event_type = net_buf_simple_pull_u8(buf);
switch (event_type) {
case SPLIT_EVENT_POSITION: {
const uint8_t *const new_positions = buf->data;
const size_t new_positions_len = buf->len;

if (new_positions_len > ARRAY_SIZE(positions)) {
LOG_ERR("Got %zu positions but we only support %zu", new_positions_len,
ARRAY_SIZE(positions));
return;
}

for (size_t positions_index = 0; positions_index < new_positions_len;
positions_index += 1) {

const uint8_t state = new_positions[positions_index];
const uint8_t changed = state ^ positions[positions_index];
positions[positions_index] = state;

for (size_t bit_index = 0; bit_index < 8; bit_index += 1) {
if (changed & BIT(bit_index)) {
const struct zmk_position_state_changed ev = {
.source = 0,
.position = positions_index * 8 + bit_index,
.state = state & BIT(bit_index),
.timestamp = timestamp,
};
LOG_DBG("Trigger key position state change for %d", ev.position);

ZMK_EVENT_RAISE(new_zmk_position_state_changed(ev));
}
}
}
break;
}
default:
LOG_ERR("Unsupported event type: %02X", event_type);
break;
}
}

static void rx_work_handler(struct k_work *work) {
ARG_UNUSED(work);

NET_BUF_SIMPLE_DEFINE_STATIC(rx_buf, MAX_MESSAGE_LEN);
static struct cobs_decode cobs_decode;

for (;;) {
uint8_t encoded_byte;
const size_t num_read =
ring_buf_get(&zmk_split_serial_rx_ringbuf, &encoded_byte, sizeof(encoded_byte));
if (num_read == 0) {
/* No data, we're done here. */
return;
}
__ASSERT_NO_MSG(num_read == 1);

uint8_t decoded_byte = 0x00;
bool decoded_byte_available = false;
enum cobs_decode_result decode_result =
cobs_decode_stream(&cobs_decode, encoded_byte, &decoded_byte, &decoded_byte_available);

if (decoded_byte_available) {
if (net_buf_simple_tailroom(&rx_buf) == 0) {
LOG_DBG("message is too big");
net_buf_simple_reset(&rx_buf);
cobs_decode_reset(&cobs_decode);
continue;
}

net_buf_simple_add_u8(&rx_buf, decoded_byte);
}

switch (decode_result) {
case COBS_DECODE_RESULT_CONSUMED:
break;

case COBS_DECODE_RESULT_FINISHED: {
cobs_decode_reset(&cobs_decode);
on_rx_done(&rx_buf);
net_buf_simple_reset(&rx_buf);
break;
}

case COBS_DECODE_RESULT_UNEXPECTED_ZERO:
LOG_DBG("unexpected zero in COBS data");
net_buf_simple_reset(&rx_buf);
cobs_decode_reset(&cobs_decode);
break;

case COBS_DECODE_RESULT_ERROR:
LOG_DBG("COBS error");
net_buf_simple_reset(&rx_buf);
cobs_decode_reset(&cobs_decode);
break;
}
}
}
K_WORK_DEFINE(zmk_split_serial_rx_work, rx_work_handler);
128 changes: 128 additions & 0 deletions app/src/split/serial/interrupt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/ring_buffer.h>

#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#include "private.h"

static const struct device *const uart_dev = DEVICE_DT_GET(DT_CHOSEN(zmk_split_serial));

static void clear_fifo(const struct device *const dev) {
uint8_t c;
while (uart_fifo_read(dev, &c, 1) > 0) {
}
}

/**
* Read from the FIFO until it returns a size of 0.
*
* This is a workaround because some drivers read max 1 character per call.
*/
static int uart_fifo_read_all(const struct device *dev, uint8_t *rx_data, int size) {
int ret;
int num_read = 0;

while (size > 0) {
ret = uart_fifo_read(dev, rx_data, size);
if (ret < 0) {
LOG_ERR("Failed to read fifo: %d", ret);
return ret;
}
if (ret == 0) {
break;
}

__ASSERT_NO_MSG(num_read <= size);

rx_data += ret;
size -= ret;
num_read += ret;
}

return num_read;
}

static void irq_rx_callback(const struct device *const dev) {
int ret;
bool had_data = false;

for (;;) {
uint8_t *data = NULL;
const uint32_t max_size =
ring_buf_put_claim(&zmk_split_serial_rx_ringbuf, &data, UINT32_MAX);
if (max_size == 0) {
clear_fifo(uart_dev);
break;
}
const int max_size_int = MIN(max_size, INT_MAX);

uint32_t num_read;
ret = uart_fifo_read_all(dev, data, max_size_int);
if (ret < 0) {
LOG_ERR("Failed to read fifo: %d", ret);
num_read = 0;
} else {
num_read = ret;
had_data = true;
}

ret = ring_buf_put_finish(&zmk_split_serial_rx_ringbuf, num_read);
__ASSERT_NO_MSG(ret == 0);

if (num_read < max_size_int) {
break;
}

/* There's may still be data in the FIFO, if:
* - The ring buffer didn't return it's full capacity because
* it's about to wrap. Another attempt will return the rest.
* - In between claim and finish, data was read from the ring
* buffer so another attempt will return more data.
* - The ring buffer is still full. Another attempt will stop
* the loop.
*/
}

if (had_data) {
k_work_submit(&zmk_split_serial_rx_work);
}
}

static void irq_callback(const struct device *const dev, void *const user_data) {
ARG_UNUSED(dev);

if (!uart_irq_update(dev)) {
return;
}

if (uart_irq_rx_ready(dev)) {
irq_rx_callback(dev);
}
}

static int init(const struct device *dev) {
ARG_UNUSED(dev);

if (!device_is_ready(uart_dev)) {
LOG_ERR("split uart device is not ready");
return -EAGAIN;
}

uart_irq_rx_disable(uart_dev);
uart_irq_tx_disable(uart_dev);
clear_fifo(uart_dev);

uart_irq_callback_user_data_set(uart_dev, irq_callback, NULL);
uart_irq_rx_enable(uart_dev);

return 0;
}
SYS_INIT(init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);