forked from zmkfirmware/zmk
-
Notifications
You must be signed in to change notification settings - Fork 0
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 2023-12-18)
- Loading branch information
1 parent
a593c72
commit b7d63fb
Showing
5 changed files
with
315 additions
and
1 deletion.
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
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,6 @@ | ||
# Copyright (c) 2023 The ZMK Contributors | ||
# SPDX-License-Identifier: MIT | ||
|
||
if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL) | ||
target_sources(app PRIVATE central.c) | ||
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,25 @@ | ||
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_UART_POLLING | ||
bool "Serial over UART Polling API" | ||
default n | ||
# TODO TODO TODO once on 3.5/3.6, replace with the following | ||
# default ZMK_SPLIT_SERIAL && (DT_HAS_RASPBERRYPI_PICO_UART_PIO_ENABLED || BOARD_NATIVE_SIM) | ||
|
||
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 | ||
|
||
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,274 @@ | ||
#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); | ||
|
||
#define SERIAL_MSG_PREFIX "UarT" | ||
|
||
// The serial protocol is defined for payloads of up to 254 bytes. TODO TODO | ||
// TODO len 255 designates another part to come. support for that can be | ||
// implemented later. | ||
#define SERIAL_BUF_SIZE 300 | ||
|
||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART_POLLING | ||
K_THREAD_STACK_DEFINE(uart_tx_wq_stack, 256); | ||
struct k_work_q uart_tx_wq; | ||
#endif | ||
|
||
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; | ||
|
||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART_POLLING | ||
bool poll; | ||
struct k_work tx_work; | ||
struct k_timer rx_timer; | ||
#endif | ||
}; | ||
|
||
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) | ||
|
||
#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 bool serial_callback_tx(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_callback_rx_internal(struct serial_device *sd) { | ||
// Continue processing data as long as the buffer exceeds the header length | ||
// (13 bytes). | ||
uint8_t data[512]; | ||
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(cmd, &data[13], len); | ||
} else { | ||
LOG_ERR("received UART message with invalid CRC32 checksum"); | ||
} | ||
} | ||
} | ||
|
||
static void serial_callback_rx(struct serial_device *sd) { | ||
uint8_t c; | ||
while (uart_fifo_read(sd->dev, &c, 1) == 1) { | ||
ring_buf_put(&sd->rx_rb, &c, 1); | ||
} | ||
serial_callback_rx_internal(sd); | ||
} | ||
|
||
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_callback_tx(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_callback_rx(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(&uart_tx_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)); | ||
} | ||
serial_callback_rx_internal(sd); | ||
} | ||
|
||
#endif | ||
|
||
static int serial_init() { | ||
#ifdef CONFIG_ZMK_SPLIT_SERIAL_UART_POLLING | ||
// TODO TODO TODO define kconfig for prio. nrf52 max 14. want this lower | ||
// than lowprio_wq since it can block. | ||
struct k_work_queue_config uart_tx_cfg = {.name = "uart_tx_wq"}; | ||
k_work_queue_start(&uart_tx_wq, uart_tx_wq_stack, K_THREAD_STACK_SIZEOF(uart_tx_wq_stack), 14, | ||
&uart_tx_cfg); | ||
#endif | ||
|
||
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); | ||
|
||
#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, 0); |