diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index 68f9a1dd08a..8be17d73707 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/drivers/sensors/spacemouse_module.c b/drivers/sensors/spacemouse_module.c new file mode 100644 index 00000000000..8722f941394 --- /dev/null +++ b/drivers/sensors/spacemouse_module.c @@ -0,0 +1,138 @@ +// 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" + +// REQUEST_DATA (the important part) +// Function: requests position data from the 3D-Sensor Command: 172 (0xAC) +// Returns: 16 bytes data +// Structure: B1 B2 ... B16 +// Byte 1: start-byte 0x96 (150 decimal); every data set starts with this byte Byte 2: high byte of X value +// Byte 3: low byte of X value +// Byte 4: high byte of Y value +// Byte 5: low byte of Y value +// Byte 6: high byte of Z value +// Byte 7: low byte of Z value +// Byte 8: high byte of A value (X rotation) Byte 9: low byte of A value (X rotation) Byte 10: high byte of B value (Y rotation) Byte 11: low byte of B value (Y rotation) Byte 12: high byte of C value (Z rotation) Byte 13: low byte of C value (Z rotation) Byte 14: high byte of Checksum +// Byte 15: low byte of Checksum +// Byte 16: end-byte 0x8D; every response ends with this byte +// +// X, Y, Z, A, B, C values and the Checksum are transmitted as unsigned 14-Bit values. This is due to the fact, that the MSB of payload data is always cleared (logic 0). +// Calculating a value: +// high byte (X) low byte (X) +// 14-bit value (unsigned) +// Xvalue = (high byte (X) * 128 + low byte (X)) - 8192 +// Transmitted Checksum: +// Checksumtrans = (high byte (Checksumtrans) * 128 + low byte (Checksumtrans)) +// Calculating the Checksum: +// Checksumcalc = (Byte1 + Byte2 + ... + Byte13) & 0x3FFF. +// By masking the Checksum with 0x3FFF (logic AND operation), the value is reduced to a 14-Bit value. +// The value range for X, Y, Z, A, B, C values is -350 up to +350. + +#define SPACEMOUSE_INPUT_OFFSET (8192) + +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, but device can be polled at 100/s or 10ms intervals + * Since 10ms is what pointing device polling defaults to, 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.a = (int16_t)((payload[6] << 7) + payload[7]) - SPACEMOUSE_INPUT_OFFSET; + data.b = (int16_t)((payload[8] << 7) + payload[9]) - SPACEMOUSE_INPUT_OFFSET; + data.c = (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 00000000000..f46125678d9 --- /dev/null +++ b/drivers/sensors/spacemouse_module.h @@ -0,0 +1,48 @@ +// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +// Datasheet UART settings specify: +// - 38400 baud +// - 8 data bits +// - 1 stop bit +// - no parity +// - 100/s data rate + +#ifndef SPACEMOUSE_BAUD_RATE +# define SPACEMOUSE_BAUD_RATE 38400 +#endif + +#define SPACEMOUSE_AXIS_COUNT 6 + +#define SPACEMOUSE_LENGTH_HEADER 1 +#define SPACEMOUSE_LENGTH_DATA (2 * SPACEMOUSE_AXIS_COUNT) +#define SPACEMOUSE_LENGTH_CHECKSUM 2 +#define SPACEMOUSE_LENGTH_FOOTER 1 +#define SPACEMOUSE_LENGTH_PACKET (SPACEMOUSE_LENGTH_HEADER + SPACEMOUSE_LENGTH_DATA + SPACEMOUSE_LENGTH_CHECKSUM + SPACEMOUSE_LENGTH_FOOTER) + +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, +}; + +typedef struct { + int16_t x; + int16_t y; + int16_t z; + int16_t a; + int16_t b; + int16_t c; +} 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 1cd4b0b5e60..c2523d1428b 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 bf131c6eda5..1164bfbcaab 100644 --- a/quantum/pointing_device/pointing_device_drivers.c +++ b/quantum/pointing_device/pointing_device_drivers.c @@ -492,6 +492,41 @@ 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_axises(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); +} + +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.a || data.b || data.c) { + pd_dprintf("Raw ] X: %d, Y: %d, Z: %d, A: %d, B: %d, C: %d\n", data.x, data.y, data.z, data.a, data.b, data.c); + } + spacemouse_module_handle_axises(&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) {