-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(split): wired split over serial support (WIP 2024-01-07)
- Loading branch information
1 parent
a52ce6a
commit 3492ae3
Showing
7 changed files
with
331 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |