From bf92d0ef70b7f7d7d91e87b968852575a30dd6d3 Mon Sep 17 00:00:00 2001 From: Drashna Jael're Date: Sat, 6 Jan 2024 23:42:18 -0800 Subject: [PATCH] [MERGE] Add UART based SpaceMouse Module support (22519) --- builddefs/common_features.mk | 4 +- docs/features/pointing_device.md | 28 ++++ drivers/sensors/spacemouse_module.c | 133 ++++++++++++++++++ drivers/sensors/spacemouse_module.h | 20 +++ quantum/pointing_device/pointing_device.h | 2 + .../pointing_device/pointing_device_drivers.c | 38 +++++ 6 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 drivers/sensors/spacemouse_module.c create mode 100644 drivers/sensors/spacemouse_module.h diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index 68f9a1dd08..8be17d7370 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -120,7 +120,7 @@ ifeq ($(strip $(MOUSEKEY_ENABLE)), yes) MOUSE_ENABLE := yes endif -VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick azoteq_iqs5xx cirque_pinnacle_i2c cirque_pinnacle_spi paw3204 pmw3320 pmw3360 pmw3389 pimoroni_trackball custom +VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick azoteq_iqs5xx cirque_pinnacle_i2c cirque_pinnacle_spi paw3204 pmw3320 pmw3360 pmw3389 pimoroni_trackball spacemouse_module custom ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes) ifeq ($(filter $(POINTING_DEVICE_DRIVER),$(VALID_POINTING_DEVICE_DRIVER_TYPES)),) $(call CATASTROPHIC_ERROR,Invalid POINTING_DEVICE_DRIVER,POINTING_DEVICE_DRIVER="$(POINTING_DEVICE_DRIVER)" is not a valid pointing device type) @@ -154,6 +154,8 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes) SRC += $(QUANTUM_DIR)/pointing_device/pointing_device_gestures.c else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), pimoroni_trackball) I2C_DRIVER_REQUIRED = yes + else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), spacemouse_module) + UART_DRIVER_REQUIRED = yes else ifneq ($(filter $(strip $(POINTING_DEVICE_DRIVER)),pmw3360 pmw3389),) SPI_DRIVER_REQUIRED = yes SRC += drivers/sensors/pmw33xx_common.c diff --git a/docs/features/pointing_device.md b/docs/features/pointing_device.md index a6bf521a18..d429504b99 100644 --- a/docs/features/pointing_device.md +++ b/docs/features/pointing_device.md @@ -368,6 +368,34 @@ report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) { ``` +### SpaceMouse Module (UART) + +To use the SpaceMouse module to control the pointer, add this to your `rules.mk` + +```make +POINTING_DEVICE_DRIVER = spacemouse_module +``` + +The SpaceMouse Module is a UART driven sensor, with 6 axes of motion. + +| Setting (`config.h`) | Description | Default | +| ---------------------------- | ------------------------------------------------------------------------------------------- | ------------- | +| `SPACEMOUSE_USE_TILT_AXIS` | Uses the tilt axes for movement rather than the shift axes. | _not_defined_ | + + +By default, not all of the axes are utilized. If you would like to use more of them, you can do so by using this custom function, which translates the data from the SpaceMouse Module to the pointing device report. + +```c +void spacemouse_module_handle_axes(spacemouse_data_t *spacemouse_data, report_mouse_t* mouse_report) { + mouse_report->x = CONSTRAIN_HID_XY(spacemouse_data->x); + mouse_report->y = CONSTRAIN_HID_XY(spacemouse_data->y); + mouse_report->h = CONSTRAIN_HID(spacemouse_data->b); + mouse_report->v = CONSTRAIN_HID(spacemouse_data->c); + + mouse_report->buttons = pointing_device_handle_buttons(mouse_report->buttons, (space_mouse_data->z < -10), KC_BTN1); +} +``` + ### Custom Driver If you have a sensor type that isn't supported above, a custom option is available by adding the following to your `rules.mk` diff --git a/drivers/sensors/spacemouse_module.c b/drivers/sensors/spacemouse_module.c new file mode 100644 index 0000000000..13e1e2f02f --- /dev/null +++ b/drivers/sensors/spacemouse_module.c @@ -0,0 +1,133 @@ +// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "spacemouse_module.h" +#include "pointing_device_internal.h" +#include "uart.h" + +// datasheet available at https://3dconnexion.com/cn/wp-content/uploads/2020/02/HW-Spec-3DX-700039_Rev001_serial.pdf + +/* Datasheet UART settings specify: + * baud rate: 38400 + * data bits: 8 + * parity: none + * stop bits: 1 + * data rate: max 100/s + */ + +#define SPACEMOUSE_BAUD_RATE 38400 +#define SPACEMOUSE_AXIS_COUNT 6 +#define SPACEMOUSE_LENGTH_DATA (2 * SPACEMOUSE_AXIS_COUNT) +#define SPACEMOUSE_INPUT_OFFSET (8192) + +enum spacemouse_commands { + SPACEMOUSE_CMD_REQUEST_DATA = 0xAC, + SPACEMOUSE_CMD_SET_ZERO_POSITION = 0xAD, + SPACEMOUSE_CMD_AUTO_DATA_ON = 0xAE, + SPACEMOUSE_CMD_AUTO_DATA_OFF = 0xAF, + SPACEMOUSE_CMD_END = 0x8D, + SPACEMOUSE_DATA_REQUEST_START = 0x96, +}; + +bool spacemouse_send_command(uint8_t cmd) { + uart_write(cmd); + uint8_t buf[2]; + uart_receive(buf, 2); + return (buf[0] == cmd && buf[1] == SPACEMOUSE_CMD_END); +} + +/** + * @brief Set the zero position of the module + * + * @return true command ran successfully + * @return false command failed + */ +bool spacemouse_cmd_set_zero_position(void) { + return spacemouse_send_command(SPACEMOUSE_CMD_SET_ZERO_POSITION); +} + +/** + * @brief Starts automatic transmission of data, at 30ms invervals + * Automatic data transmission happens at 30 ms intervals, we don't need need the stream command, + * but it is here for completeness, in case somebody wants to implement it elsewhere. + * + * @return true command ran successfully + * @return false command failed + */ +bool spacemouse_cmd_enable_stream(void) { + return spacemouse_send_command(SPACEMOUSE_CMD_AUTO_DATA_ON); +} + +/** + * @brief Stops automatic transmission of data, at 30ms invervals + * + * @return true command ran successfully + * @return false command failed + */ +bool spacemouse_cmd_disable_stream(void) { + return spacemouse_send_command(SPACEMOUSE_CMD_AUTO_DATA_OFF); +} + +/** + * @brief Initialize UART connection and send command to zero out starting position. + * + * @return true + * @return false + */ +bool spacemouse_init(void) { + uart_init(SPACEMOUSE_BAUD_RATE); + // position is zeroed out during device start, but re-zero it out to ensure that the + // device is present and working properly. + return spacemouse_cmd_set_zero_position(); +} + +spacemouse_data_t spacemouse_get_data(void) { + spacemouse_data_t data = {0}; + uint8_t retry_attempts = 0, index = 0, payload[SPACEMOUSE_LENGTH_DATA + SPACEMOUSE_LENGTH_CHECKSUM] = {0}; + uint16_t checksum = 0, checksum_received = 0; + bool has_started = false; + uart_write(SPACEMOUSE_CMD_REQUEST_DATA); + while (retry_attempts <= 15) { + uint8_t buf = uart_read(); + if (buf == SPACEMOUSE_DATA_REQUEST_START) { + has_started = true; + checksum = buf; + retry_attempts = 0; + continue; + } else if (has_started) { + if (buf == SPACEMOUSE_CMD_END) { + break; + } else { + if (index >= SPACEMOUSE_LENGTH_DATA) { + if (index == SPACEMOUSE_LENGTH_DATA) { + checksum_received = buf << 7; + } else { + checksum_received += buf; + } + } else { + payload[index] = buf; + checksum += buf; + } + index++; + } + } + retry_attempts++; + }; + + checksum &= 0x3FFF; + + if (has_started) { + if (checksum_received == checksum) { + data.x = (int16_t)((payload[0] << 7) + payload[1]) - SPACEMOUSE_INPUT_OFFSET; + data.z = (int16_t)((payload[2] << 7) + payload[3]) - SPACEMOUSE_INPUT_OFFSET; + data.y = (int16_t)((payload[4] << 7) + payload[5]) - SPACEMOUSE_INPUT_OFFSET; + data.tilt_y = (int16_t)((payload[6] << 7) + payload[7]) - SPACEMOUSE_INPUT_OFFSET; + data.twist = (int16_t)((payload[8] << 7) + payload[9]) - SPACEMOUSE_INPUT_OFFSET; + data.tilt_x = (int16_t)((payload[10] << 7) + payload[11]) - SPACEMOUSE_INPUT_OFFSET; + } else { + pd_dprintf("Space Mouse Checksum error: 0x%04x != 0x%04x \n", checksum_received, checksum); + } + } + + return data; +} diff --git a/drivers/sensors/spacemouse_module.h b/drivers/sensors/spacemouse_module.h new file mode 100644 index 0000000000..c99cba5cd0 --- /dev/null +++ b/drivers/sensors/spacemouse_module.h @@ -0,0 +1,20 @@ +// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +typedef struct { + int16_t x; + int16_t y; + int16_t z; + int16_t twist; + int16_t tilt_x; + int16_t tilt_y; +} spacemouse_data_t; + +bool spacemouse_send_command(uint8_t cmd); +bool spacemouse_init(void); +spacemouse_data_t spacemouse_get_data(void); diff --git a/quantum/pointing_device/pointing_device.h b/quantum/pointing_device/pointing_device.h index 1cd4b0b5e6..c2523d1428 100644 --- a/quantum/pointing_device/pointing_device.h +++ b/quantum/pointing_device/pointing_device.h @@ -67,6 +67,8 @@ along with this program. If not, see . # include "spi_master.h" # include "drivers/sensors/pmw33xx_common.h" # define POINTING_DEVICE_MOTION_PIN_ACTIVE_LOW +#elif defined(POINTING_DEVICE_DRIVER_spacemouse_module) +# include "drivers/sensors/spacemouse_module.h" #else void pointing_device_driver_init(void); report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report); diff --git a/quantum/pointing_device/pointing_device_drivers.c b/quantum/pointing_device/pointing_device_drivers.c index bf131c6eda..992a1336e6 100644 --- a/quantum/pointing_device/pointing_device_drivers.c +++ b/quantum/pointing_device/pointing_device_drivers.c @@ -492,6 +492,44 @@ const pointing_device_driver_t pointing_device_driver = { }; // clang-format on +#elif defined(POINTING_DEVICE_DRIVER_spacemouse_module) + +static bool spacemouse_present = false; + +__attribute__((weak)) void spacemouse_module_handle_axes(spacemouse_data_t* spacemouse_data, report_mouse_t* mouse_report) { +# ifdef SPACEMOUSE_USE_TILT_AXIS + mouse_report->x = CONSTRAIN_HID_XY(spacemouse_data->tilt_x); + mouse_report->y = CONSTRAIN_HID_XY(spacemouse_data->tilt_y); +# else + mouse_report->x = CONSTRAIN_HID_XY(spacemouse_data->x); + mouse_report->y = CONSTRAIN_HID_XY(spacemouse_data->y); +# endif +} + +static report_mouse_t spacemouse_get_report(report_mouse_t mouse_report) { + if (spacemouse_present) { + spacemouse_data_t data = spacemouse_get_data(); + + if (data.x || data.y || data.z || data.twist || data.tilt_x || data.tilt_y) { + pd_dprintf("Raw ] X: %d, Y: %d, Z: %d, twist: %d, tilt X: %d, tilt Y: %d\n", data.x, data.y, data.z, data.twist, data.tilt_x, data.tilt_y); + } + spacemouse_module_handle_axes(&data, &mouse_report); + } + return mouse_report; +} + +static void init(void) { + spacemouse_present = spacemouse_init(); +} + +// clang-format off +const pointing_device_driver_t pointing_device_driver = { + .init = init, + .get_report = spacemouse_get_report, + .set_cpi = NULL, + .get_cpi = NULL +}; +// clang-format on #else __attribute__((weak)) void pointing_device_driver_init(void) {} __attribute__((weak)) report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) {