| /* |
| * Copyright (c) 2024 CogniPilot Foundation |
| * Copyright (c) 2024 NXP Semiconductors |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT futaba_sbus |
| |
| #include <zephyr/device.h> |
| #include <zephyr/input/input.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/time_units.h> |
| #include <zephyr/sys_clock.h> |
| #include <zephyr/drivers/uart.h> |
| |
| LOG_MODULE_REGISTER(futaba_sbus, CONFIG_INPUT_LOG_LEVEL); |
| |
| /* Driver config */ |
| struct sbus_input_channel { |
| uint32_t sbus_channel; |
| uint32_t type; |
| uint32_t zephyr_code; |
| }; |
| |
| const struct uart_config uart_cfg_sbus = { |
| .baudrate = 100000, |
| .parity = UART_CFG_PARITY_EVEN, |
| .stop_bits = UART_CFG_STOP_BITS_2, |
| .data_bits = UART_CFG_DATA_BITS_8, |
| .flow_ctrl = UART_CFG_FLOW_CTRL_NONE |
| }; |
| |
| struct input_sbus_config { |
| uint8_t num_channels; |
| const struct sbus_input_channel *channel_info; |
| const struct device *uart_dev; |
| uart_irq_callback_user_data_t cb; |
| }; |
| |
| #define SBUS_FRAME_LEN 25 |
| #define SBUS_HEADER 0x0F |
| #define SBUS_FOOTER 0x00 |
| |
| #define SBUS_SERVO_LEN 22 |
| #define SBUS_SERVO_CH_MASK 0x7FF |
| |
| #define SBUS_BYTE24_IDX 23 |
| #define SBUS_BYTE24_CH17 0x01 |
| #define SBUS_BYTE24_CH18 0x02 |
| #define SBUS_BYTE24_FRAME_LOST 0x04 |
| #define SBUS_BYTE24_FAILSAFE 0x08 |
| |
| #define SBUS_TRANSMISSION_TIME_MS 4 /* Max transmission of a single SBUS frame */ |
| #define SBUS_INTERFRAME_SPACING_MS 20 /* Max spacing between SBUS frames */ |
| #define SBUS_CHANNEL_COUNT 16 |
| |
| #define REPORT_FILTER CONFIG_INPUT_SBUS_REPORT_FILTER |
| #define CHANNEL_VALUE_ZERO CONFIG_INPUT_SBUS_CHANNEL_VALUE_ZERO |
| #define CHANNEL_VALUE_ONE CONFIG_INPUT_SBUS_CHANNEL_VALUE_ONE |
| |
| struct input_sbus_data { |
| struct k_thread thread; |
| struct k_sem report_lock; |
| |
| uint16_t xfer_bytes; |
| uint8_t rd_data[SBUS_FRAME_LEN]; |
| uint8_t sbus_frame[SBUS_FRAME_LEN]; |
| bool partial_sync; |
| bool in_sync; |
| uint32_t last_rx_time; |
| |
| uint16_t last_reported_value[SBUS_CHANNEL_COUNT]; |
| int8_t channel_mapping[SBUS_CHANNEL_COUNT]; |
| |
| K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_INPUT_SBUS_THREAD_STACK_SIZE); |
| }; |
| |
| static void input_sbus_report(const struct device *dev, unsigned int sbus_channel, |
| unsigned int value) |
| { |
| const struct input_sbus_config *const config = dev->config; |
| struct input_sbus_data *const data = dev->data; |
| |
| int channel = data->channel_mapping[sbus_channel]; |
| |
| /* Not Mapped */ |
| if (channel == -1) { |
| return; |
| } |
| |
| if (value >= (data->last_reported_value[channel] + REPORT_FILTER) || |
| value <= (data->last_reported_value[channel] - REPORT_FILTER)) { |
| switch (config->channel_info[channel].type) { |
| case INPUT_EV_ABS: |
| case INPUT_EV_MSC: |
| input_report(dev, config->channel_info[channel].type, |
| config->channel_info[channel].zephyr_code, value, false, |
| K_FOREVER); |
| break; |
| |
| default: |
| if (value > CHANNEL_VALUE_ONE) { |
| input_report_key(dev, config->channel_info[channel].zephyr_code, 1, |
| false, K_FOREVER); |
| } else if (value < CHANNEL_VALUE_ZERO) { |
| input_report_key(dev, config->channel_info[channel].zephyr_code, 0, |
| false, K_FOREVER); |
| } |
| } |
| data->last_reported_value[channel] = value; |
| } |
| } |
| |
| static void input_sbus_input_report_thread(const struct device *dev, void *dummy2, void *dummy3) |
| { |
| struct input_sbus_data *const data = dev->data; |
| |
| ARG_UNUSED(dummy2); |
| ARG_UNUSED(dummy3); |
| |
| uint8_t i, channel; |
| uint8_t *sbus_channel_data = &data->sbus_frame[1]; /* Omit header */ |
| uint16_t value; |
| int bits_read; |
| unsigned int key; |
| int ret; |
| bool connected_reported = false; |
| |
| while (true) { |
| if (!data->in_sync) { |
| k_sem_take(&data->report_lock, K_FOREVER); |
| if (data->in_sync) { |
| LOG_DBG("SBUS receiver connected"); |
| } else { |
| continue; |
| } |
| } else { |
| ret = k_sem_take(&data->report_lock, K_MSEC(SBUS_INTERFRAME_SPACING_MS)); |
| if (ret == -EBUSY) { |
| continue; |
| } else if (ret < 0 || !data->in_sync) { |
| /* We've lost sync with the UART receiver */ |
| key = irq_lock(); |
| |
| data->partial_sync = false; |
| data->in_sync = false; |
| data->xfer_bytes = 0; |
| irq_unlock(key); |
| |
| connected_reported = false; |
| LOG_DBG("SBUS receiver connection lost"); |
| |
| /* Report connection lost */ |
| continue; |
| } |
| } |
| |
| if (connected_reported && |
| data->sbus_frame[SBUS_BYTE24_IDX] & SBUS_BYTE24_FRAME_LOST) { |
| LOG_DBG("SBUS controller connection lost"); |
| connected_reported = false; |
| } else if (!connected_reported && |
| !(data->sbus_frame[SBUS_BYTE24_IDX] & SBUS_BYTE24_FRAME_LOST)) { |
| LOG_DBG("SBUS controller connected"); |
| connected_reported = true; |
| } |
| |
| /* Parse the data */ |
| channel = 0; |
| value = 0; |
| bits_read = 0; |
| |
| for (i = 0; i < SBUS_SERVO_LEN; i++) { |
| /* Read the next byte */ |
| unsigned char byte = sbus_channel_data[i]; |
| |
| /* Extract bits and construct the 11-bit value */ |
| value |= byte << bits_read; |
| bits_read += 8; |
| |
| /* Check if we've read enough bits to form a full 11-bit value */ |
| while (bits_read >= 11) { |
| input_sbus_report(dev, channel, value & SBUS_SERVO_CH_MASK); |
| |
| /* Shift right to prepare for the next 11 bits */ |
| value >>= 11; |
| bits_read -= 11; |
| channel++; |
| } |
| } |
| |
| #ifdef CONFIG_INPUT_SBUS_SEND_SYNC |
| input_report(dev, 0, 0, 0, true, K_FOREVER); |
| #endif |
| } |
| } |
| |
| static void sbus_resync(const struct device *uart_dev, struct input_sbus_data *const data) |
| { |
| uint8_t *rd_data = data->rd_data; |
| |
| if (data->partial_sync) { |
| data->xfer_bytes += uart_fifo_read(uart_dev, &rd_data[data->xfer_bytes], |
| SBUS_FRAME_LEN - data->xfer_bytes); |
| if (data->xfer_bytes == SBUS_FRAME_LEN) { |
| /* Transfer took longer then 4ms probably faulty */ |
| if (k_uptime_get_32() - data->last_rx_time > SBUS_TRANSMISSION_TIME_MS) { |
| data->xfer_bytes = 0; |
| data->partial_sync = false; |
| } else if (rd_data[0] == SBUS_HEADER && |
| rd_data[SBUS_FRAME_LEN - 1] == SBUS_FOOTER) { |
| data->in_sync = true; |
| } else { |
| /* Dummy read to clear fifo */ |
| uart_fifo_read(uart_dev, &rd_data[0], 1); |
| data->xfer_bytes = 0; |
| data->partial_sync = false; |
| } |
| } |
| } else { |
| if (uart_fifo_read(uart_dev, &rd_data[0], 1) == 1) { |
| if (rd_data[0] == SBUS_HEADER) { |
| data->partial_sync = true; |
| data->xfer_bytes = 1; |
| data->last_rx_time = k_uptime_get_32(); |
| } |
| } |
| } |
| } |
| |
| static void sbus_uart_isr(const struct device *uart_dev, void *user_data) |
| { |
| const struct device *dev = user_data; |
| struct input_sbus_data *const data = dev->data; |
| uint8_t *rd_data = data->rd_data; |
| |
| if (uart_dev == NULL) { |
| LOG_DBG("UART device is NULL"); |
| return; |
| } |
| |
| if (!uart_irq_update(uart_dev)) { |
| LOG_DBG("Unable to start processing interrupts"); |
| return; |
| } |
| |
| while (uart_irq_rx_ready(uart_dev) && data->xfer_bytes <= SBUS_FRAME_LEN) { |
| if (data->in_sync) { |
| if (data->xfer_bytes == 0) { |
| data->last_rx_time = k_uptime_get_32(); |
| } |
| data->xfer_bytes += uart_fifo_read(uart_dev, &rd_data[data->xfer_bytes], |
| SBUS_FRAME_LEN - data->xfer_bytes); |
| } else { |
| sbus_resync(uart_dev, data); |
| } |
| } |
| |
| if (data->in_sync && (k_uptime_get_32() - data->last_rx_time > |
| SBUS_INTERFRAME_SPACING_MS)) { |
| data->partial_sync = false; |
| data->in_sync = false; |
| data->xfer_bytes = 0; |
| k_sem_give(&data->report_lock); |
| } else if (data->in_sync && data->xfer_bytes == SBUS_FRAME_LEN) { |
| data->xfer_bytes = 0; |
| |
| if (rd_data[0] == SBUS_HEADER && rd_data[SBUS_FRAME_LEN - 1] == SBUS_FOOTER) { |
| memcpy(data->sbus_frame, rd_data, SBUS_FRAME_LEN); |
| k_sem_give(&data->report_lock); |
| } else { |
| data->partial_sync = false; |
| data->in_sync = false; |
| } |
| } |
| } |
| |
| /* |
| * @brief Initialize sbus driver |
| */ |
| static int input_sbus_init(const struct device *dev) |
| { |
| const struct input_sbus_config *const config = dev->config; |
| struct input_sbus_data *const data = dev->data; |
| int i, ret; |
| |
| uart_irq_rx_disable(config->uart_dev); |
| uart_irq_tx_disable(config->uart_dev); |
| |
| LOG_DBG("Initializing SBUS driver"); |
| |
| for (i = 0; i < SBUS_CHANNEL_COUNT; i++) { |
| data->last_reported_value[i] = 0; |
| data->channel_mapping[i] = -1; |
| } |
| |
| data->xfer_bytes = 0; |
| data->in_sync = false; |
| data->partial_sync = false; |
| data->last_rx_time = 0; |
| |
| for (i = 0; i < config->num_channels; i++) { |
| data->channel_mapping[config->channel_info[i].sbus_channel - 1] = i; |
| } |
| |
| ret = uart_configure(config->uart_dev, &uart_cfg_sbus); |
| if (ret < 0) { |
| LOG_ERR("Unable to configure UART port: %d", ret); |
| return ret; |
| } |
| |
| ret = uart_irq_callback_user_data_set(config->uart_dev, config->cb, (void *)dev); |
| if (ret < 0) { |
| if (ret == -ENOTSUP) { |
| LOG_ERR("Interrupt-driven UART API support not enabled"); |
| } else if (ret == -ENOSYS) { |
| LOG_ERR("UART device does not support interrupt-driven API"); |
| } else { |
| LOG_ERR("Error setting UART callback: %d", ret); |
| } |
| return ret; |
| } |
| |
| uart_irq_rx_enable(config->uart_dev); |
| |
| k_sem_init(&data->report_lock, 0, 1); |
| |
| k_thread_create(&data->thread, data->thread_stack, |
| K_KERNEL_STACK_SIZEOF(data->thread_stack), |
| (k_thread_entry_t)input_sbus_input_report_thread, (void *)dev, NULL, NULL, |
| CONFIG_INPUT_SBUS_THREAD_PRIORITY, 0, K_NO_WAIT); |
| |
| k_thread_name_set(&data->thread, dev->name); |
| |
| return ret; |
| } |
| |
| #define INPUT_CHANNEL_CHECK(input_channel_id) \ |
| BUILD_ASSERT(IN_RANGE(DT_PROP(input_channel_id, channel), 1, 16), \ |
| "invalid channel number"); \ |
| BUILD_ASSERT(DT_PROP(input_channel_id, type) == INPUT_EV_ABS || \ |
| DT_PROP(input_channel_id, type) == INPUT_EV_KEY || \ |
| DT_PROP(input_channel_id, type) == INPUT_EV_MSC, \ |
| "invalid channel type"); |
| |
| #define SBUS_INPUT_CHANNEL_INITIALIZER(input_channel_id) \ |
| { \ |
| .sbus_channel = DT_PROP(input_channel_id, channel), \ |
| .type = DT_PROP(input_channel_id, type), \ |
| .zephyr_code = DT_PROP(input_channel_id, zephyr_code), \ |
| }, |
| |
| #define INPUT_SBUS_INIT(n) \ |
| \ |
| static const struct sbus_input_channel input_##n[] = { \ |
| DT_INST_FOREACH_CHILD(n, SBUS_INPUT_CHANNEL_INITIALIZER) \ |
| }; \ |
| DT_INST_FOREACH_CHILD(n, INPUT_CHANNEL_CHECK) \ |
| \ |
| static struct input_sbus_data sbus_data_##n; \ |
| \ |
| static const struct input_sbus_config sbus_cfg_##n = { \ |
| .channel_info = input_##n, \ |
| .uart_dev = DEVICE_DT_GET(DT_INST_BUS(n)), \ |
| .num_channels = ARRAY_SIZE(input_##n), \ |
| .cb = sbus_uart_isr, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, input_sbus_init, NULL, &sbus_data_##n, &sbus_cfg_##n, \ |
| POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL); |
| |
| DT_INST_FOREACH_STATUS_OKAY(INPUT_SBUS_INIT) |