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

Added PMW3320 driver #19543

Merged
merged 2 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion builddefs/common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ ifeq ($(strip $(MOUSEKEY_ENABLE)), yes)
SRC += $(QUANTUM_DIR)/mousekey.c
endif

VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick cirque_pinnacle_i2c cirque_pinnacle_spi paw3204 pmw3360 pmw3389 pimoroni_trackball custom
VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick cirque_pinnacle_i2c cirque_pinnacle_spi paw3204 pmw3320 pmw3360 pmw3389 pimoroni_trackball 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
18 changes: 18 additions & 0 deletions docs/feature_pointing_device.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,24 @@ The Pimoroni Trackball module is a I2C based breakout board with an RGB enable t
| `PIMORONI_TRACKBALL_DEBOUNCE_CYCLES` | (Optional) The number of scan cycles used for debouncing on the ball press. | `20` |
| `PIMORONI_TRACKBALL_ERROR_COUNT` | (Optional) Specifies the number of read/write errors until the sensor is disabled. | `10` |

### PMW3320 Sensor

To use the PMW3320 sensor, add this to your `rules.mk`

```make
POINTING_DEVICE_DRIVER = pmw3320
```

The PMW3320 sensor uses a serial type protocol for communication, and requires an additional light source (it could work without one, but expect it to be out of service early).

| Setting | Description | Default |
| ------------------- | ------------------------------------------------------------------- | -------------------------- |
| `PMW3320_SCLK_PIN` | (Required) The pin connected to the clock pin of the sensor. | `POINTING_DEVICE_SCLK_PIN` |
| `PMW3320_SDIO_PIN` | (Required) The pin connected to the data pin of the sensor. | `POINTING_DEVICE_SDIO_PIN` |
| `PMW3320_CS_PIN` | (Required) The pin connected to the cable select pin of the sensor. | `POINTING_DEVICE_CS_PIN` |

The CPI range is 500-3500, in increments of 250. Defaults to 1000 CPI.

### PMW 3360 and PMW 3389 Sensor

This drivers supports both the PMW 3360 and PMW 3389 sensor as well as multiple sensors of the same type _per_ controller, so 2 can be attached at the same side for split keyboards (or unsplit keyboards).
Expand Down
192 changes: 192 additions & 0 deletions drivers/sensors/pmw3320.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/* Copyright 2021 Colin Lam (Ploopy Corporation)
* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
* Copyright 2019 Sunjun Kim
* Copyright 2019 Hiroyuki Okada
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "pmw3320.h"
#include "wait.h"
#include "debug.h"
#include "gpio.h"

void pmw3320_init(void) {
// Initialize sensor serial pins.
setPinOutput(PMW3320_SCLK_PIN);
setPinOutput(PMW3320_SDIO_PIN);
setPinOutput(PMW3320_CS_PIN);

// reboot the sensor.
pmw3320_write_reg(REG_Power_Up_Reset, 0x5a);

// wait maximum time before sensor is ready.
// this ensures that the sensor is actually ready after reset.
wait_ms(55);

// read a burst from the sensor and then discard it.
// gets the sensor ready for write commands
// (for example, setting the dpi).
pmw3320_read_burst();

// Pretty sure that this shouldn't be in the driver.
// Probably device specific?
// Set rest mode to default
pmw3320_write_reg(REG_Rest_Mode_Status, 0x00);
// Set LED to be always on
pmw3320_write_reg(REG_Led_Control, 0x4);
// Disable rest mode
pmw3320_write_reg(REG_Performance, 0x80);
}

// Perform a synchronization with sensor.
// Just as with the serial protocol, this is used by the slave to send a
// synchronization signal to the master.
void pmw3320_sync(void) {
writePinLow(PMW3320_CS_PIN);
wait_us(1);
writePinHigh(PMW3320_CS_PIN);
}

void pmw3320_cs_select(void) {
writePinLow(PMW3320_CS_PIN);
}

void pmw3320_cs_deselect(void) {
writePinHigh(PMW3320_CS_PIN);
}

uint8_t pmw3320_serial_read(void) {
setPinInput(PMW3320_SDIO_PIN);
uint8_t byte = 0;

for (uint8_t i = 0; i < 8; ++i) {
writePinLow(PMW3320_SCLK_PIN);
wait_us(1);

byte = (byte << 1) | readPin(PMW3320_SDIO_PIN);

writePinHigh(PMW3320_SCLK_PIN);
wait_us(1);
}

return byte;
}

void pmw3320_serial_write(uint8_t data) {
setPinOutput(PMW3320_SDIO_PIN);

for (int8_t b = 7; b >= 0; b--) {
writePinLow(PMW3320_SCLK_PIN);

if (data & (1 << b))
writePinHigh(PMW3320_SDIO_PIN);
else
writePinLow(PMW3320_SDIO_PIN);

wait_us(2);

writePinHigh(PMW3320_SCLK_PIN);
}

// This was taken from ADNS5050 driver.
// There's no any info in PMW3320 datasheet about this...
// tSWR. See page 15 of the ADNS5050 spec sheet.
// Technically, this is only necessary if the next operation is an SDIO
// read. This is not guaranteed to be the case, but we're being lazy.
wait_us(4);

// Note that tSWW is never necessary. All write operations require at
// least 32us, which exceeds tSWW, so there's never a need to wait for it.
}

// Read a byte of data from a register on the sensor.
uint8_t pmw3320_read_reg(uint8_t reg_addr) {
pmw3320_cs_select();

pmw3320_serial_write(reg_addr);

uint8_t byte = pmw3320_serial_read();

// This was taken directly from ADNS5050 driver...
// tSRW & tSRR. See page 15 of the ADNS5050 spec sheet.
// Technically, this is only necessary if the next operation is an SDIO
// read or write. This is not guaranteed to be the case.
// Honestly, this wait could probably be removed.
wait_us(1);

pmw3320_cs_deselect();

return byte;
}

void pmw3320_write_reg(uint8_t reg_addr, uint8_t data) {
pmw3320_cs_select();
pmw3320_serial_write(0b10000000 | reg_addr);
pmw3320_serial_write(data);
pmw3320_cs_deselect();
}

report_pmw3320_t pmw3320_read_burst(void) {
pmw3320_cs_select();

report_pmw3320_t data;
data.dx = 0;
data.dy = 0;

pmw3320_serial_write(REG_Motion_Burst);

uint8_t x = pmw3320_serial_read();
uint8_t y = pmw3320_serial_read();

// Probably burst mode may include contents of delta_xy register,
// which contain HI parts of x/y deltas, but I had no luck finding it.
// Probably it's required to activate 12-bit mode to access this data.
// So we end burst mode early to not read unneeded information.
pmw3320_cs_deselect();

data.dx = convert_twoscomp(x);
data.dy = convert_twoscomp(y);

return data;
}

// Convert a two's complement byte from an unsigned data type into a signed
// data type.
int8_t convert_twoscomp(uint8_t data) {
if ((data & 0x80) == 0x80)
return -128 + (data & 0x7F);
else
return data;
}

uint16_t pmw3320_get_cpi(void) {
uint8_t cpival = pmw3320_read_reg(REG_Resolution);
// 0x1F is an inversion of 0x20 which is 0b100000
return (uint16_t)((cpival & 0x1F) * PMW3320_CPI_STEP);
}

void pmw3320_set_cpi(uint16_t cpi) {
uint8_t cpival = constrain((cpi / PMW3320_CPI_STEP) - 1U, 0, (PMW3320_CPI_MAX / PMW3320_CPI_STEP) - 1U);
// Fifth bit is probably a control bit.
// PMW3320 datasheet don't have any info on this, so this is a pure guess.
pmw3320_write_reg(REG_Resolution, 0x20 | cpival);
}

bool pmw3320_check_signature(void) {
uint8_t pid = pmw3320_read_reg(REG_Product_ID);
uint8_t pid2 = pmw3320_read_reg(REG_Inverse_Product_ID);

return (pid == 0x3b && pid2 == 0xc4);
}
119 changes: 119 additions & 0 deletions drivers/sensors/pmw3320.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/* Copyright 2021 Colin Lam (Ploopy Corporation)
* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
* Copyright 2019 Sunjun Kim
* Copyright 2019 Hiroyuki Okada
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

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

#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))

// Definitions for the PMW3320 serial line.
#ifndef PMW3320_SCLK_PIN
# ifdef POINTING_DEVICE_SCLK_PIN
# define PMW3320_SCLK_PIN POINTING_DEVICE_SCLK_PIN
# else
# error "No clock pin defined -- missing POINTING_DEVICE_SCLK_PIN or PMW3320_SCLK_PIN"
# endif
#endif

#ifndef PMW3320_SDIO_PIN
# ifdef POINTING_DEVICE_SDIO_PIN
# define PMW3320_SDIO_PIN POINTING_DEVICE_SDIO_PIN
# else
# error "No data pin defined -- missing POINTING_DEVICE_SDIO_PIN or PMW3320_SDIO_PIN"
# endif
#endif

#ifndef PMW3320_CS_PIN
# ifdef POINTING_DEVICE_CS_PIN
# define PMW3320_CS_PIN POINTING_DEVICE_CS_PIN
# else
# error "No chip select pin defined -- missing POINTING_DEVICE_CS_PIN or PMW3320_CS_PIN define"
# endif
#endif

typedef struct {
int8_t dx;
int8_t dy;
} report_pmw3320_t;

// A bunch of functions to implement the PMW3320-specific serial protocol.
// Mostly taken from ADNS5050 driver.
// Note that the "serial.h" driver is insufficient, because it does not
// manually manipulate a serial clock signal.
void pmw3320_init(void);
void pmw3320_sync(void);
uint8_t pmw3320_serial_read(void);
void pmw3320_serial_write(uint8_t data);
uint8_t pmw3320_read_reg(uint8_t reg_addr);
void pmw3320_write_reg(uint8_t reg_addr, uint8_t data);
report_pmw3320_t pmw3320_read_burst(void);
void pmw3320_set_cpi(uint16_t cpi);
uint16_t pmw3320_get_cpi(void);
int8_t convert_twoscomp(uint8_t data);
bool pmw3320_check_signature(void);

#if !defined(PMW3320_CPI)
# define PMW3320_CPI 1000
#endif

#define PMW3320_CPI_STEP 250
#define PMW3320_CPI_MIN 250
#define PMW3320_CPI_MAX 3500

// PMW3320 register addresses
// clang-format off
#define REG_Product_ID 0x00
#define REG_Revision_ID 0x01
#define REG_Motion 0x02
#define REG_Delta_X 0x03
#define REG_Delta_Y 0x04
#define REG_SQUAL 0x05
#define REG_Shutter_Upper 0x06
#define REG_Shutter_Lower 0x07
#define REG_Maximum_Pixel 0x08
#define REG_Pixel_Accum 0x09
#define REG_Minimum_Pixel 0x0a
#define REG_Pixel_Grab 0x0b
#define REG_Delta_XY 0x0c
#define REG_Resolution 0x0d
#define REG_Run_Downshift 0x0e
#define REG_Rest1_Period 0x0f
#define REG_Rest1_Downshift 0x10
#define REG_Rest2_Preiod 0x11
#define REG_Rest2_Downshift 0x12
#define REG_Rest3_Period 0x13
#define REG_Min_SQ_Run 0x17
#define REG_Axis_Control 0x1a
#define REG_Performance 0x22
#define REG_Low_Motion_Jitter 0x23
#define REG_Shutter_Max_HI 0x36
#define REG_Shutter_Max_LO 0x37
#define REG_Frame_Rate 0x39
#define REG_Power_Up_Reset 0x3a
#define REG_Shutdown 0x3b
#define REG_Inverse_Revision_ID 0x3f
#define REG_Led_Control 0x40
#define REG_Motion_Control 0x41
#define REG_Burst_Read_First 0x42
#define REG_Rest_Mode_Status 0x45
#define REG_Inverse_Product_ID 0x4f
#define REG_Motion_Burst 0x63
// clang-format on
3 changes: 3 additions & 0 deletions quantum/pointing_device/pointing_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#if defined(POINTING_DEVICE_DRIVER_adns5050)
# include "drivers/sensors/adns5050.h"
# define POINTING_DEVICE_MOTION_PIN_ACTIVE_LOW
#elif defined(POINTING_DEVICE_DRIVER_pmw3320)
# include "drivers/sensors/pmw3320.h"
# define POINTING_DEVICE_MOTION_PIN_ACTIVE_LOW
#elif defined(POINTING_DEVICE_DRIVER_adns9800)
# include "spi_master.h"
# include "drivers/sensors/adns9800.h"
Expand Down
22 changes: 22 additions & 0 deletions quantum/pointing_device/pointing_device_drivers.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,28 @@ const pointing_device_driver_t pointing_device_driver = {
};
// clang-format on

#elif defined(POINTING_DEVICE_DRIVER_pmw3320)
report_mouse_t pmw3320_get_report(report_mouse_t mouse_report) {
report_pmw3320_t data = pmw3320_read_burst();

if (data.dx != 0 || data.dy != 0) {
pd_dprintf("Raw ] X: %d, Y: %d\n", data.dx, data.dy);
mouse_report.x = (mouse_xy_report_t)data.dx;
mouse_report.y = (mouse_xy_report_t)data.dy;
}

return mouse_report;
}

// clang-format off
const pointing_device_driver_t pointing_device_driver = {
.init = pmw3320_init,
.get_report = pmw3320_get_report,
.set_cpi = pmw3320_set_cpi,
.get_cpi = pmw3320_get_cpi,
};
// clang-format on

#elif defined(POINTING_DEVICE_DRIVER_adns9800)

report_mouse_t adns9800_get_report_driver(report_mouse_t mouse_report) {
Expand Down