Skip to content

Commit

Permalink
feat(split): wired split over serial support (WIP 2023-12-18)
Browse files Browse the repository at this point in the history
  • Loading branch information
xudongzheng committed Dec 18, 2023
1 parent a593c72 commit 94e40f1
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 1 deletion.
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()
5 changes: 5 additions & 0 deletions app/src/split/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ config ZMK_SPLIT_BLE
select BT_USER_PHY_UPDATE
select BT_AUTO_PHY_UPDATE

config ZMK_SPLIT_SERIAL
bool "Serial"
select RING_BUFFER

endchoice

config ZMK_SPLIT_PERIPHERAL_HID_INDICATORS
Expand All @@ -30,3 +34,4 @@ config ZMK_SPLIT_PERIPHERAL_HID_INDICATORS
endif

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

if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE central.c)
endif()
23 changes: 23 additions & 0 deletions app/src/split/serial/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
if ZMK_SPLIT && ZMK_SPLIT_SERIAL

menu "Serial Transport"

config ZMK_SPLIT_SERIAL_UART
bool "Serial over UART"
default ZMK_SPLIT_SERIAL

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

config ZMK_SPLIT_SERIAL_BITMAP
bool "Serial bitmap protocol"
default ZMK_SPLIT_SERIAL

config ZMK_SPLIT_SERIAL_CONVERTER
bool "Serial bitmap protocol"
default n

endmenu

endif
210 changes: 210 additions & 0 deletions app/src/split/serial/central.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/ring_buffer.h>

#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>

#include <zmk/events/position_state_changed.h>

// TODO TODO TODO
LOG_MODULE_REGISTER(slicemk);

// TODO TODO TODO kconfig? is there an existing one to use? allow different rx
// and tx sizes?

#define SERIAL_MSG_PREFIX "UarT"

// The serial protocol is defined for payloads of up to 127 bytes. 256 should be
// enough to ensure that bytes are not lost. TODO TODO TODO 254, make size 400
#define SERIAL_BUF_SIZE 256

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;
};

static struct serial_device serial_devs[] = {
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART
{.dev = DEVICE_DT_GET(DT_CHOSEN(zmk_split_uart))},
#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)

#define POSITION_STATE_DATA_LEN 16
static uint8_t position_state[POSITION_STATE_DATA_LEN];
static uint8_t changed_positions[POSITION_STATE_DATA_LEN];

K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed), 10, 4);

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));
}
}

// TODO TODO TODO this was copied from split bt. can go into
// app/src/split/split.c
K_WORK_DEFINE(peripheral_event_work, peripheral_event_work_callback);

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()};

k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT);
k_work_submit(&peripheral_event_work);
}
}
}
}

static void serial_handle(uint32_t cmd, uint8_t *data, uint8_t len) {
switch (cmd) {
// Handle bitmap split transformed (bst) version 0.
case 0x62737400:
serial_handle_bitmap(data, len);
break;

default:
LOG_ERR("Received unexpected UART command 0x%08x", cmd);
break;
}
}

static void serial_callback_rx(const struct device *dev, struct serial_device *ud) {
uint8_t c;
while (uart_fifo_read(dev, &c, 1) == 1) {
ring_buf_put(&ud->rx_rb, &c, 1);
}

// Continue processing data as long as the buffer exceeds the header length
// (13 bytes).
uint8_t data[SERIAL_BUF_SIZE];
while (ring_buf_peek(&ud->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(&ud->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(&ud->rx_rb) < total) {
return;
}

// Check message checksum and handle message.
uint32_t cmd, crc;
ring_buf_get(&ud->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(cmd, &data[13], len);
} else {
LOG_ERR("received UART message with invalid CRC32 checksum");
}
}
}

static bool serial_callback_tx(const struct device *dev, 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(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_callback(const struct device *dev, void *data) {
struct serial_device *sd = data;

// TODO TODO TODO lookup index in serial_devs array for slot ID?
if (uart_irq_update(sd->dev)) {
if (uart_irq_rx_ready(sd->dev)) {
serial_callback_rx(dev, sd);
}

if (uart_irq_tx_ready(sd->dev)) {
// If transmission complete, disable IRQ until next transmission.
bool complete = serial_callback_tx(dev, sd);
if (complete) {
uart_irq_tx_disable(sd->dev);
}
}
}
}

void serial_write(struct serial_device *sb, 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(&sb->tx_rb, header, sizeof(header));
ring_buf_put(&sb->tx_rb, data, len);
uart_irq_tx_enable(sb->dev);
}

static int serial_init() {
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);

// Enable RX callback. TODO TODO TODO polling for pio. also will need
// tx. take code from pmk native_sim.
uart_irq_callback_user_data_set(sd->dev, serial_callback, sd);
uart_irq_rx_enable(sd->dev);
}

return 0;
}

SYS_INIT(serial_init, APPLICATION, 0);

0 comments on commit 94e40f1

Please sign in to comment.