Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Pointing] Add uart based SpaceMouse Module support #22519

Draft
wants to merge 10 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[Pointing] Add space mouse module uart support
  • Loading branch information
drashna committed Jun 10, 2024
commit 300fb2bc1df14ad1a7df840353eba1292a475dbc
4 changes: 3 additions & 1 deletion builddefs/common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
138 changes: 138 additions & 0 deletions drivers/sensors/spacemouse_module.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// 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
daskygit marked this conversation as resolved.
Show resolved Hide resolved
* 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.
drashna marked this conversation as resolved.
Show resolved Hide resolved
*
* @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;
}
48 changes: 48 additions & 0 deletions drivers/sensors/spacemouse_module.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2023 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <stdint.h>
#include <stdbool.h>

// 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);
2 changes: 2 additions & 0 deletions quantum/pointing_device/pointing_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# 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);
Expand Down
35 changes: 35 additions & 0 deletions quantum/pointing_device/pointing_device_drivers.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +529 to +530
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to have some form of software scaling here.

};
// 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) {
Expand Down