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 94e40f1
Showing
5 changed files
with
249 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,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 |
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,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); |