1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-15 17:43:51 +01:00

drivers: added support for the PA5100JE and PMW3901 optical flow sensor

This commit is contained in:
LeonardHerbst 2025-09-12 11:29:02 +02:00
parent a8f0171bc1
commit fb4e1838d7
16 changed files with 982 additions and 0 deletions

View File

@ -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"

134
drivers/include/paa5100je.h Normal file
View File

@ -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 sensors 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 <leonard.herbst@tu-dresden.de>
*/
#include <periph/gpio.h>
#include <periph/spi.h>
#include <errno.h>
#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 sensors 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
/** @} */

37
drivers/paa5100je/Kconfig Normal file
View File

@ -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

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,4 @@
FEATURES_REQUIRED += periph_spi
USEMODULE += ztimer
USEMODULE += ztimer_msec

View File

@ -0,0 +1,2 @@
USEMODULE_INCLUDES_paa5100je := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_paa5100je)

View File

@ -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 <leonard.herbst@tu-dresden.de>
*/
#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
/** @} */

View File

@ -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 <leonard.herbst@tu-dresden.de>
*/
#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
/** @} */

View File

@ -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 <leonard.herbst@tu-dresden.de>
*
* @}
*/
#include "paa5100je.h"
#include "paa5100je_constants.h"
#include "paa5100je_params.h"
#include "ztimer.h"
#include "macros/utils.h"
#include "log.h"
#include <stdio.h>
#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);
}

View File

@ -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 sensors distance from the ground.
*
* @author Leonard Herbst <leonard.herbst@tu-dresden.de>
*
* @}
*/
#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,
};

View File

@ -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 <leonard.herbst@tu-dresden.de>
*
* @}
*/
#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]);
}
}

View File

@ -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();

View File

@ -0,0 +1,6 @@
include ../Makefile.drivers_common
USEMODULE += paa5100je
USEMODULE += ztimer
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,3 @@
BOARD_INSUFFICIENT_MEMORY := \
atmega8 \
#

View File

@ -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).

View File

@ -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 <leonard.herbst@tu-dresden.de>
*
* @}
*/
#include <stdio.h>
#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;
}