diff --git a/drivers/Kconfig b/drivers/Kconfig index a2f3ee7867..50d8763553 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -45,6 +45,7 @@ rsource "mag3110/Kconfig" rsource "matrix_keypad/Kconfig" rsource "mma8x5x/Kconfig" rsource "opt3001/Kconfig" +rsource "paa5100je/Kconfig" rsource "seesaw_soil/Kconfig" rsource "sht2x/Kconfig" rsource "sm_pwm_01c/Kconfig" diff --git a/drivers/include/paa5100je.h b/drivers/include/paa5100je.h new file mode 100644 index 0000000000..4a79a1fb90 --- /dev/null +++ b/drivers/include/paa5100je.h @@ -0,0 +1,134 @@ +/* + * SPDX-FileCopyrightText: 2025 TU Dresden + * SPDX-License-Identifier: LGPL-2.1-only + */ + +#pragma once + +/** + * @defgroup drivers_paa5100je PAA5100JE/PMW3901 Driver + * @ingroup drivers_sensors + * @brief Driver for the PAA5100JE/PMW3901 Optical Flow Sensor + * + * ## Description + * + * The PAA5100JE and PMW3901 sensors are accessed in the same way over SPI + * but require slightly different initialization code. + * Since the datasheets are not very detailed, I created this driver based on a reference + * implementation by Pimoroni, who designed a breakout board for the PMW3901. + * + * The motion data read represents the relative motion since the last readout. + * It depends on the surface, lighting conditions, and the sensor’s distance from the ground. + * + * The readings are scaled according to the configuration parameter `PAA5100JE_SCALE_DENOMINATOR`. + * The readings are multiplied by 100 and divided by the denominator. + * + * The scaling factor, the quality threshold, and the timeout can be configured via Kconfig. + * + * This driver provides @ref drivers_saul capabilities. + * + * Datasheets: + * * [PAA5100JE](https://cdn.shopify.com/s/files/1/0174/1800/files/PAA5100JE-Q-GDS-R1.00_25072018.pdf) + * * [PMW3901](https://wiki.bitcraze.io/_media/projects:crazyflie2:expansionboards:pot0189-pmw3901mb-txqt-ds-r1.00-200317_20170331160807_public.pdf) + * + * Reference Implementation: + * * [Pomoroni](https://github.com/pimoroni/pmw3901-python) + * + * @{ + * + * @file + * + * @author Leonard Herbst + */ + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Variant of the sensor + */ +typedef enum { + PAA5100JE = 0, + PMW3901 = 1, +} paa5100je_variant_t; + +/** + * @brief PAA5100JE LED brightness levels + */ +typedef enum { + PAA5100JE_LED_OFF = 0, + PAA5100JE_LED_MEDIUM = 1, + PAA5100JE_LED_MAX = 2, +} paa5100je_led_brightness_t; + +/** + * @brief Device initialization parameters + */ +typedef struct { + spi_t spi; /**< SPI bus used */ + spi_clk_t clk; /**< clock speed used on the selected SPI bus */ + gpio_t cs; /**< pin connected to the chip select line */ + paa5100je_variant_t var; /**< variant of the sensor used */ +} paa5100je_params_t; + +/** + * @brief Device descriptor for the driver + */ +typedef struct { + const paa5100je_params_t *params; /**< Device initialization parameters */ +} paa5100je_t; + +/** + * @brief Initialize the given device + * + * @param[in,out] dev Device descriptor of the driver + * @param[in] params Initialization parameters + * + * @retval 0 on success + * @retval -ENXIO invalid SPI device + * @retval -ENODEV invalid SPI CS pin/line or wrong device id or revision + */ +int paa5100je_init(paa5100je_t *dev, const paa5100je_params_t *params); + +/** + * @brief Reads the relative motition vector from the device using burst read. + * + * Reads twelve bytes from the burst register. + * Repeats the read when the data is not ready, the quality is too low + * or the shutter values is too high. + * The data being read from the sensor represents the relative motion since the last readout. + * It depends on the surface, lighting conditions, and the sensor’s distance from the ground. + * The quality threshold, the timeout, and a scaling factor can be configured via Kconfig. + * + * @param[in] dev device descriptor + * @param[out] x x component in millimeters + * @param[out] y y component in millimeters + * + * @retval 0 on success + * @retval -ETIME data was not ready in time, the quality was not good enough, + * or the shutter to high + */ +int paa5100je_get_motion_burst(const paa5100je_t *dev, int16_t *x, int16_t *y); + +/** + * @brief Sets the LED brightness level. + * + * @param[in] dev device descriptor + * @param[in] level brightness level + * + * @retval 0 on success + * @retval -EINVAL invalid brightness level + */ +int paa5100je_set_led_brightness(const paa5100je_t *dev, const paa5100je_led_brightness_t level); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/drivers/paa5100je/Kconfig b/drivers/paa5100je/Kconfig new file mode 100644 index 0000000000..f0eca6c2b3 --- /dev/null +++ b/drivers/paa5100je/Kconfig @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2025 TU Dresden +# SPDX-License-Identifier: LGPL-2.1-only + +config MODULE_PAA5100JE + bool "PAA5100JE" + depends on TEST_KCONFIG + depends on HAS_PERIPH_SPI + select MODULE_PERIPH_SPI + +menu "PAA5100JE driver" + depends on USEMODULE_PAA5100JE + +config PAA5100JE_QUALITY_THRESHOLD + hex "Quality Threshold" + range 0x00 0xff + default 0x19 + help + Defines a minimum quality threshold when reading from the burst register. + If the reported quality is below this threshold, + the driver retries until the threshold is met or the function times out. + +config PAA5100JE_TIMEOUT_MS + int "Timeout in Milliseconds" + default 1000 + help + Maximum time (in milliseconds) + the driver will attempt to read valid motion data before aborting. + +config PAA5100JE_SCALE_DENOMINATOR + int "Scale Denominator" + default 100 + help + Denominator used for scaling motion data. + The raw motion data read from the sensor is multiplied by 100 and + divided by this value to convert it into meaningful units. + +endmenu # PAA5100JE driver diff --git a/drivers/paa5100je/Makefile b/drivers/paa5100je/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/paa5100je/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/paa5100je/Makefile.dep b/drivers/paa5100je/Makefile.dep new file mode 100644 index 0000000000..b95f3dc904 --- /dev/null +++ b/drivers/paa5100je/Makefile.dep @@ -0,0 +1,4 @@ +FEATURES_REQUIRED += periph_spi + +USEMODULE += ztimer +USEMODULE += ztimer_msec \ No newline at end of file diff --git a/drivers/paa5100je/Makefile.include b/drivers/paa5100je/Makefile.include new file mode 100644 index 0000000000..3b5cb444c3 --- /dev/null +++ b/drivers/paa5100je/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_paa5100je := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_paa5100je) diff --git a/drivers/paa5100je/include/paa5100je_constants.h b/drivers/paa5100je/include/paa5100je_constants.h new file mode 100644 index 0000000000..e7f05b43c0 --- /dev/null +++ b/drivers/paa5100je/include/paa5100je_constants.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2025 TU Dresden + * SPDX-License-Identifier: LGPL-2.1-only + */ + +#pragma once + +/** + * @ingroup drivers_paa5100je + * + * @{ + * @file + * @brief Internal addresses, registers and constants + * + * @author Leonard Herbst + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Register definitions for the PAA5100JE amd PMW3901 optical flow sensors + * @{ + */ +/** @brief Product ID (reset value: 0x49, read-only) */ +#define REG_ID (0x00) + +/** @brief Product revision ID (reset value: 0x00, read-only) */ +#define REG_REV (0x01) + +/** @brief Motion data ready flag (bitfield, indicates new data availability, read/write) */ +#define REG_DATA_READY (0x02) + +/** @brief Motion burst register: block read of motion, delta X/Y, quality, shutter, read-only */ +#define REG_MOTION_BURST (0x16) + +/** @brief Write 0x5A to reset internal registers/state machine (soft reset), write-only */ +#define REG_POWER_UP_RESET (0x3A) + +/** @brief Sensor orientation/mirroring (read/write, flips axes depending on mounting) */ +#define REG_ORIENTATION (0x5B) + +/** @brief Inverse Product ID (reset value: 0xB6, bitwise NOT of REG_ID, read-only) */ +#define REG_INV_ID (0x5F) + +/** @brief Register for controlling the LED brightness (read/write) */ +#define REG_LED_BRIGHTNESS (0x6F) + +/** @brief Magic value to turn the led off */ +#define PAA5100JE_LED_OFF_VAL (0x00) +/** @brief Magic value to set the led to medium brightness */ +#define PAA5100JE_LED_MEDIUM_VAL (0x1C) +/** @brief Magic value to set the led to maximum brightness */ +#define PAA5100JE_LED_MAX_VAL (0xD5) + +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/drivers/paa5100je/include/paa5100je_params.h b/drivers/paa5100je/include/paa5100je_params.h new file mode 100644 index 0000000000..72f029f42c --- /dev/null +++ b/drivers/paa5100je/include/paa5100je_params.h @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2025 TU Dresden + * SPDX-License-Identifier: LGPL-2.1-only + */ + +#pragma once + +/** + * @ingroup drivers_paa5100je + * + * @{ + * @file + * @brief Default configuration + * + * @author Leonard Herbst + */ + +#include "board.h" +#include "paa5100je.h" +#include "paa5100je_constants.h" +#include "periph/spi.h" +#include "saul_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Default quality threshold for motion data readout. + */ +#ifndef CONFIG_PAA5100JE_QUALITY_THRESHOLD +# define CONFIG_PAA5100JE_QUALITY_THRESHOLD (0x19) +#endif + +/** + * @brief Default timeout for motion data readout in milliseconds. + */ +#ifndef CONFIG_PAA5100JE_TIMEOUT_MS +# define CONFIG_PAA5100JE_TIMEOUT_MS (1000U) +#endif + +/** + * @brief Default denominator for scaling the motion data. + */ +#ifndef CONFIG_PAA5100JE_SCALE_DENOMINATOR +# define CONFIG_PAA5100JE_SCALE_DENOMINATOR (100) +#endif + +/** + * @name Default configuration parameters for the PAA5100JE/PMW3901 driver. + * @{ + */ +#ifndef PAA5100JE_PARAM_SPI +# define PAA5100JE_PARAM_SPI (SPI_DEV(0)) /**< Default SPI device */ +#endif + +#ifndef PAA5100JE_PARAM_SPI_CLK +# define PAA5100JE_PARAM_SPI_CLK (SPI_CLK_400KHZ) /**< Default SPI speed */ +#endif + +#ifndef PAA5100JE_PARAM_CS +# define PAA5100JE_PARAM_CS (GPIO_PIN(1, 2)) /**< Default SPI chip select pin */ +#endif + +#ifndef PAA5100JE_PARAM_VAR +# define PAA5100JE_PARAM_VAR PAA5100JE /**< Default variant*/ +#endif + +/** + * @brief Default sensor parameters. + */ +#ifndef PAA5100JE_PARAMS +# define PAA5100JE_PARAMS { .spi = PAA5100JE_PARAM_SPI, \ + .clk = PAA5100JE_PARAM_SPI_CLK, \ + .cs = PAA5100JE_PARAM_CS, \ + .var = PAA5100JE_PARAM_VAR } +#endif + +/** + * @brief Default driver SAUL registry information + */ +#ifndef PAA5100JE_SAUL_INFO +# define PAA5100JE_SAUL_INFO { .name = "Flow Sensor (PAA5100JE/PMW3901)" } +#endif + +/**@}*/ + +/** + * @brief Default configuration parameters + */ +static const paa5100je_params_t paa5100je_params[] = +{ + PAA5100JE_PARAMS +}; + +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const saul_reg_info_t paa5100je_saul_info[] = +{ + PAA5100JE_SAUL_INFO +}; + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/drivers/paa5100je/paa5100je.c b/drivers/paa5100je/paa5100je.c new file mode 100644 index 0000000000..ddf8504138 --- /dev/null +++ b/drivers/paa5100je/paa5100je.c @@ -0,0 +1,430 @@ +/* + * SPDX-FileCopyrightText: 2025 TU Dresden + * SPDX-License-Identifier: LGPL-2.1-only + */ + +/** + * @ingroup drivers_paa5100je + * @{ + * + * @file + * @brief Device driver implementation for the PAA5100JE/PMW3901 optical flow sensor + * + * @author Leonard Herbst + * + * @} + */ + +#include "paa5100je.h" +#include "paa5100je_constants.h" +#include "paa5100je_params.h" +#include "ztimer.h" +#include "macros/utils.h" +#include "log.h" +#include + +#define PAA5100JE_CMD_READ (0b00000000) /**< Mask applied to a register address when reading */ +#define PAA5100JE_CMD_WRITE (0b10000000) /**< Mask applied to a register address when writing */ + +/* Prototypes of private functions */ +static uint8_t _read_reg(const paa5100je_t *dev, uint8_t reg); +static void _read_reg_burst(const paa5100je_t *dev, uint8_t reg, size_t num, uint8_t *buf); +static void _write_reg(const paa5100je_t *dev, uint8_t reg, uint8_t value); +static void _prop_init(const paa5100je_t *dev); +static void _init_paa5100je(const paa5100je_t *dev); +static void _init_pmw3901(const paa5100je_t *dev); + +/* Public API */ + +int paa5100je_init(paa5100je_t *dev, const paa5100je_params_t *params) +{ + assert(dev && params); + dev->params = params; + + int res = spi_init_cs(params->spi, params->cs); + if (res < 0) { + return -ENXIO; + } + _write_reg(dev, REG_POWER_UP_RESET, 0x5A); + + for (uint8_t offset = 0; offset < 5; offset++) { + (void) _read_reg(dev, REG_DATA_READY + offset); + } + + _prop_init(dev); + + uint8_t id = _read_reg(dev, REG_ID); + uint8_t inv_id = _read_reg(dev, REG_INV_ID); + uint8_t rev = _read_reg(dev, REG_REV); + if ((id ^ inv_id) != 0xFF || id != 0x49 || rev != 0x00) { + LOG_ERROR("[PAA5100JE] Wrong id, inverted id, or unknown revision.\n"); + return -ENODEV; + } + return 0; +} + +int paa5100je_get_motion_burst(const paa5100je_t *dev, int16_t *x, int16_t *y) +{ + uint8_t data[12]; + ztimer_now_t start = ztimer_now(ZTIMER_MSEC); + + do { + _read_reg_burst(dev, REG_MOTION_BURST, 12, data); + /* PixArt who designed the sensor is pretty secretive. The default threshold for the quality + * and for the shutter are taken from a reference implementation. */ + uint8_t data_ready = data[0]; + uint8_t quality = data[6]; + uint8_t shutter_upper = data[10]; + if ((data_ready & 0b10000000) + && quality >= CONFIG_PAA5100JE_QUALITY_THRESHOLD + && shutter_upper != 0x1F) { + *x = (int16_t)(data[3] << 8 | data[2]); + *y = (int16_t)(data[5] << 8 | data[4]); + /* Apply scaling factor */ + *x *= 100; + *x /= CONFIG_PAA5100JE_SCALE_DENOMINATOR; + *y *= 100; + *y /= CONFIG_PAA5100JE_SCALE_DENOMINATOR; + return 0; + } + ztimer_sleep(ZTIMER_MSEC, 1); + } while (CONFIG_PAA5100JE_TIMEOUT_MS + && ztimer_now(ZTIMER_MSEC) < start + CONFIG_PAA5100JE_TIMEOUT_MS); + return -ETIME; +} + +int paa5100je_set_led_brightness(const paa5100je_t *dev, const paa5100je_led_brightness_t level) +{ + assert(dev); + uint8_t reg_val; + + switch (level) { + case PAA5100JE_LED_OFF: + reg_val = PAA5100JE_LED_OFF_VAL; + break; + case PAA5100JE_LED_MEDIUM: + reg_val = PAA5100JE_LED_MEDIUM_VAL; + break; + case PAA5100JE_LED_MAX: + reg_val = PAA5100JE_LED_MAX_VAL; + break; + default: + return -EINVAL; + } + + _write_reg(dev, 0x7F, 0x14); + _write_reg(dev, REG_LED_BRIGHTNESS, reg_val); + _write_reg(dev, 0x7F, 0x00); + + return 0; +} + +/* Private API */ + +static uint8_t _read_reg(const paa5100je_t *dev, uint8_t reg) +{ + assert(dev); + assert(!(reg & 0x10000000)); + + uint8_t value; + + spi_acquire(dev->params->spi, dev->params->cs, SPI_MODE_3, dev->params->clk); + + value = spi_transfer_reg(dev->params->spi, dev->params->cs, reg | PAA5100JE_CMD_READ, 0); + + spi_release(dev->params->spi); + + return value; +} + +static void _read_reg_burst(const paa5100je_t *dev, uint8_t reg, size_t num, uint8_t *buf) +{ + assert(dev); + assert(!(reg & 0x10000000)); + spi_acquire(dev->params->spi, dev->params->cs, SPI_MODE_3, dev->params->clk); + spi_transfer_regs(dev->params->spi, dev->params->cs, reg | PAA5100JE_CMD_READ, NULL, buf, num); + spi_release(dev->params->spi); +} + +static void _write_reg(const paa5100je_t *dev, uint8_t reg, uint8_t value) +{ + assert(dev); + assert(!(reg & 0x10000000)); + + spi_acquire(dev->params->spi, dev->params->cs, SPI_MODE_3, dev->params->clk); + + (void) spi_transfer_reg(dev->params->spi, dev->params->cs, (reg | PAA5100JE_CMD_WRITE), value); + + spi_release(dev->params->spi); +} + +/** + * @brief Writes a set of magic values to the sensors registers. + * + * The datasheet does not explain this. + * These values and registers are taken from a reference implementation. + * + * @param[in] dev device descriptor + */ +static void _prop_init(const paa5100je_t *dev) +{ + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x55, 0x01); + _write_reg(dev, 0x50, 0x07); + _write_reg(dev, 0x7F, 0x0E); + _write_reg(dev, 0x43, 0x10); + + int tmp = _read_reg(dev, 0x67); + if (tmp & 0b10000000){ + _write_reg(dev, 0x48, 0x04); + } + else { + _write_reg(dev, 0x48, 0x02); + } + + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x51, 0x7B); + _write_reg(dev, 0x50, 0x00); + _write_reg(dev, 0x55, 0x00); + _write_reg(dev, 0x7F, 0x0E); + + tmp = _read_reg(dev, 0x73); + if (tmp == 0x00){ + int c1 = _read_reg(dev, 0x70); + int c2 = _read_reg(dev, 0x71); + if (c1 <= 28) { + c1 += 14; + } + if (c1 > 28) { + c1 += 11; + } + c1 = MAX(0, MIN(0x3F, c1)); + c2 = (c2 * 45); + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x61, 0xAD); + _write_reg(dev, 0x51, 0x70); + _write_reg(dev, 0x7F, 0x0E); + _write_reg(dev, 0x70, c1); + _write_reg(dev, 0x71, c2); + } + + if (dev->params->var == PAA5100JE) { + _init_paa5100je(dev); + } + else { + _init_pmw3901(dev); + } +} + +/** + * @brief Writes a set of PAA5100JE specific magic values to the sensors registers. + * + * @param[in] dev device descriptor + */ +static void _init_paa5100je(const paa5100je_t *dev) +{ + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x61, 0xAD); + + _write_reg(dev, 0x7F, 0x03); + _write_reg(dev, 0x40, 0x00); + + _write_reg(dev, 0x7F, 0x05); + _write_reg(dev, 0x41, 0xB3); + _write_reg(dev, 0x43, 0xF1); + _write_reg(dev, 0x45, 0x14); + + _write_reg(dev, 0x5F, 0x34); + _write_reg(dev, 0x7B, 0x08); + _write_reg(dev, 0x5E, 0x34); + _write_reg(dev, 0x5B, 0x11); + _write_reg(dev, 0x6D, 0x11); + _write_reg(dev, 0x45, 0x17); + _write_reg(dev, 0x70, 0xE5); + _write_reg(dev, 0x71, 0xE5); + + _write_reg(dev, 0x7F, 0x06); + _write_reg(dev, 0x44, 0x1B); + _write_reg(dev, 0x40, 0xBF); + _write_reg(dev, 0x4E, 0x3F); + + _write_reg(dev, 0x7F, 0x08); + _write_reg(dev, 0x66, 0x44); + _write_reg(dev, 0x65, 0x20); + _write_reg(dev, 0x6A, 0x3A); + _write_reg(dev, 0x61, 0x05); + _write_reg(dev, 0x62, 0x05); + + _write_reg(dev, 0x7F, 0x09); + _write_reg(dev, 0x4F, 0xAF); + _write_reg(dev, 0x5F, 0x40); + _write_reg(dev, 0x48, 0x80); + _write_reg(dev, 0x49, 0x80); + _write_reg(dev, 0x57, 0x77); + _write_reg(dev, 0x60, 0x78); + _write_reg(dev, 0x61, 0x78); + _write_reg(dev, 0x62, 0x08); + _write_reg(dev, 0x63, 0x50); + + _write_reg(dev, 0x7F, 0x0A); + _write_reg(dev, 0x45, 0x60); + + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x4D, 0x11); + _write_reg(dev, 0x55, 0x80); + _write_reg(dev, 0x74, 0x21); + _write_reg(dev, 0x75, 0x1F); + _write_reg(dev, 0x4A, 0x78); + _write_reg(dev, 0x4B, 0x78); + _write_reg(dev, 0x44, 0x08); + + _write_reg(dev, 0x45, 0x50); + _write_reg(dev, 0x64, 0xFF); + _write_reg(dev, 0x65, 0x1F); + + _write_reg(dev, 0x7F, 0x14); + _write_reg(dev, 0x65, 0x67); + _write_reg(dev, 0x66, 0x08); + _write_reg(dev, 0x63, 0x70); + _write_reg(dev, 0x6F, 0x1C); + + _write_reg(dev, 0x7F, 0x15); + _write_reg(dev, 0x48, 0x48); + + _write_reg(dev, 0x7F, 0x07); + _write_reg(dev, 0x41, 0x0D); + _write_reg(dev, 0x43, 0x14); + _write_reg(dev, 0x4B, 0x0E); + _write_reg(dev, 0x45, 0x0F); + _write_reg(dev, 0x44, 0x42); + _write_reg(dev, 0x4C, 0x80); + + _write_reg(dev, 0x7F, 0x10); + _write_reg(dev, 0x5B, 0x02); + + _write_reg(dev, 0x7F, 0x07); + _write_reg(dev, 0x40, 0x41); + + ztimer_sleep(ZTIMER_MSEC, 100); + + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x32, 0x00); + + _write_reg(dev, 0x7F, 0x07); + _write_reg(dev, 0x40, 0x40); + + _write_reg(dev, 0x7F, 0x06); + _write_reg(dev, 0x68, 0xF0); + _write_reg(dev, 0x69, 0x00); + + _write_reg(dev, 0x7F, 0x0D); + _write_reg(dev, 0x48, 0xC0); + _write_reg(dev, 0x6F, 0xD5); + + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x5B, 0xA0); + _write_reg(dev, 0x4E, 0xA8); + _write_reg(dev, 0x5A, 0x90); + _write_reg(dev, 0x40, 0x80); + _write_reg(dev, 0x73, 0x1F); + + ztimer_sleep(ZTIMER_MSEC, 100); + + _write_reg(dev, 0x73, 0x00); +} + +/** + * @brief Writes a set of PMW301 specific magic values to the sensors registers. + * + * @param[in] dev device descriptor + */ +static void _init_pmw3901(const paa5100je_t *dev) +{ + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x61, 0xAD); + _write_reg(dev, 0x7F, 0x03); + _write_reg(dev, 0x40, 0x00); + _write_reg(dev, 0x7F, 0x05); + + _write_reg(dev, 0x41, 0xB3); + _write_reg(dev, 0x43, 0xF1); + _write_reg(dev, 0x45, 0x14); + _write_reg(dev, 0x5B, 0x32); + _write_reg(dev, 0x5F, 0x34); + _write_reg(dev, 0x7B, 0x08); + _write_reg(dev, 0x7F, 0x06); + _write_reg(dev, 0x44, 0x1B); + _write_reg(dev, 0x40, 0xBF); + _write_reg(dev, 0x4E, 0x3F); + _write_reg(dev, 0x7F, 0x08); + _write_reg(dev, 0x65, 0x20); + _write_reg(dev, 0x6A, 0x18); + + _write_reg(dev, 0x7F, 0x09); + _write_reg(dev, 0x4F, 0xAF); + _write_reg(dev, 0x5F, 0x40); + _write_reg(dev, 0x48, 0x80); + _write_reg(dev, 0x49, 0x80); + + _write_reg(dev, 0x57, 0x77); + _write_reg(dev, 0x60, 0x78); + _write_reg(dev, 0x61, 0x78); + _write_reg(dev, 0x62, 0x08); + _write_reg(dev, 0x63, 0x50); + _write_reg(dev, 0x7F, 0x0A); + _write_reg(dev, 0x45, 0x60); + _write_reg(dev, 0x7F, 0x00); + _write_reg(dev, 0x4D, 0x11); + + _write_reg(dev, 0x55, 0x80); + _write_reg(dev, 0x74, 0x1F); + _write_reg(dev, 0x75, 0x1F); + _write_reg(dev, 0x4A, 0x78); + _write_reg(dev, 0x4B, 0x78); + + _write_reg(dev, 0x44, 0x08); + _write_reg(dev, 0x45, 0x50); + _write_reg(dev, 0x64, 0xFF); + _write_reg(dev, 0x65, 0x1F); + _write_reg(dev, 0x7F, 0x14); + _write_reg(dev, 0x65, 0x60); + _write_reg(dev, 0x66, 0x08); + _write_reg(dev, 0x63, 0x78); + _write_reg(dev, 0x7F, 0x15); + _write_reg(dev, 0x48, 0x58); + _write_reg(dev, 0x7F, 0x07); + _write_reg(dev, 0x41, 0x0D); + _write_reg(dev, 0x43, 0x14); + + _write_reg(dev, 0x4B, 0x0E); + _write_reg(dev, 0x45, 0x0F); + _write_reg(dev, 0x44, 0x42); + _write_reg(dev, 0x4C, 0x80); + _write_reg(dev, 0x7F, 0x10); + _write_reg(dev, 0x5B, 0x02); + _write_reg(dev, 0x7F, 0x07); + _write_reg(dev, 0x40, 0x41); + _write_reg(dev, 0x70, 0x00); + + ztimer_sleep(ZTIMER_MSEC, 100); + _write_reg(dev, 0x32, 0x44); + _write_reg(dev, 0x7F, 0x07); + _write_reg(dev, 0x40, 0x40); + _write_reg(dev, 0x7F, 0x06); + _write_reg(dev, 0x62, 0xF0); + _write_reg(dev, 0x63, 0x00); + _write_reg(dev, 0x7F, 0x0D); + _write_reg(dev, 0x48, 0xC0); + _write_reg(dev, 0x6F, 0xD5); + _write_reg(dev, 0x7F, 0x00); + + _write_reg(dev, 0x5B, 0xA0); + _write_reg(dev, 0x4E, 0xA8); + _write_reg(dev, 0x5A, 0x50); + _write_reg(dev, 0x40, 0x80); + + ztimer_sleep(ZTIMER_MSEC, 100); + _write_reg(dev, 0x7F, 0x14); + _write_reg(dev, 0x6F, 0x1C); + _write_reg(dev, 0x7F, 0x00); +} diff --git a/drivers/paa5100je/paa5100je_saul.c b/drivers/paa5100je/paa5100je_saul.c new file mode 100644 index 0000000000..8de0472f06 --- /dev/null +++ b/drivers/paa5100je/paa5100je_saul.c @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2025 TU Dresden + * SPDX-License-Identifier: LGPL-2.1-only + */ + +/** + * @ingroup drivers_paa5100je + * @{ + * + * @file + * @brief SAUL adaption for the PAA5100JE/PMW3901 optical flow sensor + * + * The data being read from the sensor represents the relative motion since the last readout. + * It depends on the surface, lighting conditions, and the sensor’s distance from the ground. + * + * @author Leonard Herbst + * + * @} + */ + +#include "saul.h" +#include "paa5100je.h" + +static int read_motion(const void *dev, phydat_t *res) +{ + int err = paa5100je_get_motion_burst((paa5100je_t *) dev, &res->val[0], &res->val[1]); + if (err) { + return -ECANCELED; + } + res->unit = UNIT_M; + res->scale = -3; + + return 2; +} + +const saul_driver_t paa5100je_saul_driver = { + .read = read_motion, + .write = saul_write_notsup, + .type = SAUL_SENSE_DISTANCE, +}; diff --git a/drivers/saul/init_devs/auto_init_paa5100je.c b/drivers/saul/init_devs/auto_init_paa5100je.c new file mode 100644 index 0000000000..0d155a1508 --- /dev/null +++ b/drivers/saul/init_devs/auto_init_paa5100je.c @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2025 TU Dresden + * SPDX-License-Identifier: LGPL-2.1-only + */ + +/** + * @ingroup drivers_paa5100je + * @{ + * + * @file + * @brief Auto initialization for PAA5100JE/PMW3901 Optical Flow Sensors + * + * @author Leonard Herbst + * + * @} + */ + +#include "assert.h" +#include "log.h" +#include "saul_reg.h" +#include "paa5100je.h" +#include "paa5100je_params.h" + +/** + * @brief Define the number of configured sensors + */ +#define PAA5100JE_NUM ARRAY_SIZE(paa5100je_params) + +/** + * @brief Allocate memory for the device descriptors + */ +static paa5100je_t paa5100je_devs[PAA5100JE_NUM]; + +/** + * @brief Allocate Memory for the SAUL registry entry + */ +static saul_reg_t saul_entries[PAA5100JE_NUM]; + +/** + * @brief Define the number of saul info + */ +#define PAA5100JE_INFO_NUM ARRAY_SIZE(paa5100je_saul_info) + +/** + * @name Import SAUL endpoint + * @{ + */ +extern const saul_driver_t paa5100je_saul_driver; + +void auto_init_paa5100je(void) +{ + assert(PAA5100JE_INFO_NUM == PAA5100JE_NUM); + + for (unsigned int i = 0; i < PAA5100JE_NUM; i++) { + LOG_DEBUG("[auto_init_saul] initializing paa5100je #%u\n", i); + + if (paa5100je_init(&paa5100je_devs[i], &paa5100je_params[i]) != 0) { + LOG_ERROR("[auto_init_saul] error initializing paa5100je #%u\n", i); + continue; + } + + saul_entries[i].dev = &(paa5100je_devs[i]); + saul_entries[i].name = paa5100je_saul_info[i].name; + saul_entries[i].driver = &paa5100je_saul_driver; + saul_reg_add(&saul_entries[i]); + } +} diff --git a/drivers/saul/init_devs/init.c b/drivers/saul/init_devs/init.c index c6cdad13db..9a1a660ee0 100644 --- a/drivers/saul/init_devs/init.c +++ b/drivers/saul/init_devs/init.c @@ -255,6 +255,10 @@ void saul_init_devs(void) extern void auto_init_opt3001(void); auto_init_opt3001(); } + if (IS_USED(MODULE_PAA5100JE)) { + extern void auto_init_paa5100je(void); + auto_init_paa5100je(); + } if (IS_USED(MODULE_PCA9685)) { extern void auto_init_pca9685(void); auto_init_pca9685(); diff --git a/tests/drivers/paa5100je/Makefile b/tests/drivers/paa5100je/Makefile new file mode 100644 index 0000000000..9b07eb437c --- /dev/null +++ b/tests/drivers/paa5100je/Makefile @@ -0,0 +1,6 @@ +include ../Makefile.drivers_common + +USEMODULE += paa5100je +USEMODULE += ztimer + +include $(RIOTBASE)/Makefile.include \ No newline at end of file diff --git a/tests/drivers/paa5100je/Makefile.ci b/tests/drivers/paa5100je/Makefile.ci new file mode 100644 index 0000000000..72db76ccb5 --- /dev/null +++ b/tests/drivers/paa5100je/Makefile.ci @@ -0,0 +1,3 @@ +BOARD_INSUFFICIENT_MEMORY := \ + atmega8 \ + # diff --git a/tests/drivers/paa5100je/README.md b/tests/drivers/paa5100je/README.md new file mode 100644 index 0000000000..71b66c963d --- /dev/null +++ b/tests/drivers/paa5100je/README.md @@ -0,0 +1,11 @@ +# About +This is a manual test application for the PAA5100JE/PMW3901 optical flow sensor +driver. It reads motion data from the sensor and prints it to the console. + +To run the test, connect the PAA5100JE/PMW3901 sensor to your MCU. +The sensor uses SPI for communication, so make sure to connect the SPI pins +correctly. You will also need to connect the chip select (CS) pin and power +the sensor with 3.3V to 5.0V and GND. + +Before running the test, you may need to adjust the pin configuration and select +the right version of the sensor (PAA5100JE or PMW3901). diff --git a/tests/drivers/paa5100je/main.c b/tests/drivers/paa5100je/main.c new file mode 100644 index 0000000000..ebbb3e0f84 --- /dev/null +++ b/tests/drivers/paa5100je/main.c @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2025 TU Dresden + * SPDX-License-Identifier: LGPL-2.1-only + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the PAA5100JE/PMW3901 optical flow sensor driver + * + * @author Leonard Herbst + * + * @} + */ + +#include + +#include "paa5100je.h" +#include "paa5100je_params.h" +#include "ztimer.h" + +static paa5100je_t dev; + +int main(void) +{ + puts("PAA5100JE/PMW3901 Optical Flow Sensor Test Application..."); + int ret = paa5100je_init(&dev, &paa5100je_params[0]); + if (ret == 0) { + puts("[OK]"); + } + else if (ret == -ENXIO) { + puts("Error: Invalid SPI device!"); + return -1; + } + else if (ret == -ENODEV) { + puts("Error: Invalid SPI CS pin/line or wrong device id or revision!"); + return -1; + } + else { + printf("Error: Unknown error during initialization! (%d)\n", ret); + return -1; + } + + puts("Setting LED brightness to maximum."); + if (paa5100je_set_led_brightness(&dev, PAA5100JE_LED_MAX) != 0) { + puts("Error: Could not set LED brightness!"); + return -1; + } + + puts("Printing sensor state every second."); + + int16_t x = 0; + int16_t y = 0; + int16_t delta_x = 0; + int16_t delta_y = 0; + + do { + ret = paa5100je_get_motion_burst(&dev, &delta_x, &delta_y); + if (ret == 0) { + x += delta_x; + y += delta_y; + printf("Relative Motion : x:%d, y:%d\n", delta_x, delta_y); + printf("Absolute Position: x=%d, y=%d\n", x, y); + } + ztimer_sleep(ZTIMER_MSEC, 250); + } while (1); + + return 0; +}