diff --git a/conf/airframes/ENAC/quadrotor/crazyflie_2.1.xml b/conf/airframes/ENAC/quadrotor/crazyflie_2.1.xml
index 65d2d68046..2d87f7d4a0 100644
--- a/conf/airframes/ENAC/quadrotor/crazyflie_2.1.xml
+++ b/conf/airframes/ENAC/quadrotor/crazyflie_2.1.xml
@@ -48,8 +48,14 @@
-
-
+
+
+
+
+
+
+
+
diff --git a/conf/modules/opticflow_pmw3901.xml b/conf/modules/opticflow_pmw3901.xml
new file mode 100644
index 0000000000..d91994a0ad
--- /dev/null
+++ b/conf/modules/opticflow_pmw3901.xml
@@ -0,0 +1,49 @@
+
+
+
+
+ Driver for PMW3901 optical flow sensor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ include $(CFG_SHARED)/spi_master.makefile
+
+
+
+
diff --git a/sw/airborne/arch/chibios/mcu_periph/spi_arch.c b/sw/airborne/arch/chibios/mcu_periph/spi_arch.c
index 27bdbfae14..b671a034bc 100644
--- a/sw/airborne/arch/chibios/mcu_periph/spi_arch.c
+++ b/sw/airborne/arch/chibios/mcu_periph/spi_arch.c
@@ -281,7 +281,10 @@ static void handle_spi_thd(struct spi_periph *p)
// Configure SPI bus with the current slave select pin
spiStart((SPIDriver *)p->reg_addr, &spi_cfg);
- spiSelect((SPIDriver *)p->reg_addr);
+ // Select the slave after reconfiguration of the peripheral
+ if (t->select == SPISelectUnselect || t->select == SPISelect) {
+ spiSelect((SPIDriver *)p->reg_addr);
+ }
// Run the callback after selecting the slave
// FIXME warning: done in spi thread
@@ -300,7 +303,9 @@ static void handle_spi_thd(struct spi_periph *p)
#endif
// Unselect the slave
- spiUnselect((SPIDriver *)p->reg_addr);
+ if (t->select == SPISelectUnselect || t->select == SPIUnselect) {
+ spiUnselect((SPIDriver *)p->reg_addr);
+ }
chSysLock();
// end of transaction, handle fifo
@@ -322,6 +327,7 @@ static void handle_spi_thd(struct spi_periph *p)
if (t->after_cb != 0) {
t->after_cb(t);
}
+
}
/**
diff --git a/sw/airborne/mcu_periph/spi.h b/sw/airborne/mcu_periph/spi.h
index 4e6305f449..abd3c7d02b 100644
--- a/sw/airborne/mcu_periph/spi.h
+++ b/sw/airborne/mcu_periph/spi.h
@@ -34,6 +34,12 @@
#include "std.h"
#include "mcu_periph/spi_arch.h"
+#include "mcu_periph/sys_time.h"
+
+#ifndef SPI_BLOCKING_TIMEOUT
+#define SPI_BLOCKING_TIMEOUT 1.f
+#endif
+
/**
* @addtogroup mcu_periph
@@ -270,6 +276,25 @@ extern void spi_init_slaves(void);
*/
extern bool spi_submit(struct spi_periph *p, struct spi_transaction *t);
+/** Perform a spi transaction (blocking).
+ * @param p spi peripheral to be used
+ * @param t spi transaction
+ * @return TRUE if transaction completed (success or failure)
+ */
+static inline bool spi_blocking_transceive(struct spi_periph *p, struct spi_transaction *t) {
+ if (!spi_submit(p, t)) {
+ return false;
+ }
+ // Wait for transaction to complete
+ float start_t = get_sys_time_float();
+ while (t->status == SPITransPending || t->status == SPITransRunning) {
+ if (get_sys_time_float() - start_t > SPI_BLOCKING_TIMEOUT) {
+ break;
+ }
+ }
+ return true;
+}
+
/** Select a slave.
* @param slave slave id
*/
diff --git a/sw/airborne/modules/sensors/opticflow_pmw3901.c b/sw/airborne/modules/sensors/opticflow_pmw3901.c
new file mode 100644
index 0000000000..28ee54d5a5
--- /dev/null
+++ b/sw/airborne/modules/sensors/opticflow_pmw3901.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) Tom van Dijk
+ *
+ * This file is part of paparazzi
+ *
+ * paparazzi 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, or (at your option)
+ * any later version.
+ *
+ * paparazzi 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 paparazzi; see the file COPYING. If not, see
+ * .
+ */
+/**
+ * @file "modules/sensors/opticflow_pmw3901.c"
+ * @author Tom van Dijk
+ * Driver for PMW3901 optical flow sensor
+ */
+
+#include "modules/sensors/opticflow_pmw3901.h"
+
+#include "subsystems/abi.h"
+#include "subsystems/datalink/downlink.h"
+#include "generated/modules.h"
+
+#include "state.h"
+
+#include "std.h"
+
+#include
+
+
+#ifndef OPTICFLOW_PMW3901_SENSOR_ANGLE
+#define OPTICFLOW_PMW3901_SENSOR_ANGLE 270 // [deg] Sensor rotation around body z axis (down). 0 rad = sensor x forward, y right.
+#endif
+
+#ifndef OPTICFLOW_PMW3901_SUBPIXEL_FACTOR
+#define OPTICFLOW_PMW3901_SUBPIXEL_FACTOR 100
+#endif
+
+#ifndef OPTICFLOW_PMW3901_STD_PX
+#define OPTICFLOW_PMW3901_STD_PX 50 // [px] standard deviation of flow measurement
+#endif
+
+#ifndef OPTICFLOW_PMW3901_AGL_ID
+#define OPTICFLOW_PMW3901_AGL_ID ABI_BROADCAST
+#endif
+PRINT_CONFIG_VAR(OPTICFLOW_PMW3901_AGL_ID)
+
+#ifndef OPTICFLOW_PMW3901_AGL_TIMEOUT_US
+#define OPTICFLOW_PMW3901_AGL_TIMEOUT_US 500000
+#endif
+
+
+struct opticflow_pmw3901_t of_pmw;
+
+abi_event agl_ev;
+static float agl_dist;
+static uint32_t agl_ts;
+
+
+static void agl_cb(uint8_t sender_id, uint32_t stamp, float distance) {
+ (void) sender_id;
+ agl_dist = distance;
+ agl_ts = stamp;
+}
+
+static bool agl_valid(uint32_t at_ts) {
+ return \
+ ((at_ts - agl_ts) < of_pmw.agl_timeout) &&
+ (agl_dist > 0.080);
+}
+
+
+static void opticflow_pmw3901_publish(int16_t delta_x, int16_t delta_y, uint32_t ts_usec) {
+ /* Prepare message variables */
+ // Time
+ static uint32_t prev_ts_usec = 0;
+ float dt = (ts_usec - prev_ts_usec) / 1.0e6;
+ if (prev_ts_usec == 0) {
+ dt = OPTICFLOW_PMW3901_PERIODIC_PERIOD;
+ }
+ prev_ts_usec = ts_usec;
+ // Sensor orientation
+ float c = cosf(RadOfDeg(of_pmw.sensor_angle));
+ float s = sinf(RadOfDeg(of_pmw.sensor_angle));
+ // Flow [px/s] (body-frame)
+ float flow_x = (c * delta_x - s * delta_y) / dt;
+ float flow_y = (s * delta_x + c * delta_y) / dt;
+ int16_t flow_x_subpix = (int16_t)(of_pmw.subpixel_factor * flow_x);
+ int16_t flow_y_subpix = (int16_t)(of_pmw.subpixel_factor * flow_y);
+ // Derotated flow [px/s] (body-frame)
+ struct FloatRates *rates = stateGetBodyRates_f();
+ float flow_dy_p = rates->p / of_pmw.pmw.rad_per_px;
+ float flow_dx_q = -rates->q / of_pmw.pmw.rad_per_px;
+ float flow_der_x = flow_x - flow_dx_q;
+ float flow_der_y = flow_y - flow_dy_p;
+ int16_t flow_der_x_subpix = (int16_t)(of_pmw.subpixel_factor * flow_der_x);
+ int16_t flow_der_y_subpix = (int16_t)(of_pmw.subpixel_factor * flow_der_y);
+ // Velocity
+ static float vel_x = 0; // static: keep last measurement for telemetry if agl not valid
+ static float vel_y = 0;
+ static float noise = 0;
+ if (agl_valid(ts_usec)) {
+ vel_x = -flow_der_x * of_pmw.pmw.rad_per_px * agl_dist;
+ vel_y = -flow_der_y * of_pmw.pmw.rad_per_px * agl_dist;
+ noise = of_pmw.std_px * of_pmw.pmw.rad_per_px * agl_dist;
+ }
+
+ /* Send ABI messages */
+ // Note: INS only subscribes to VELOCITY_ESTIMATE. OPTICAL_FLOW is only used
+ // for niche applications(?) and therefore only uses (sub)pixels without any
+ // camera intrinsics?? On the bright side, the sensor datasheet does not
+ // provide any intrinsics either.....
+ AbiSendMsgOPTICAL_FLOW(FLOW_OPTICFLOW_PMW3901_ID,
+ ts_usec, /* stamp [us] */
+ flow_x_subpix, /* flow_x [subpixels] */
+ flow_y_subpix, /* flow_y [subpixels] */
+ flow_der_x_subpix, /* flow_der_x [subpixels] */
+ flow_der_y_subpix, /* flow_der_y [subpixels] */
+ 0.f, /* quality [???] */
+ 0.f /* size_divergence [1/s] */
+ );
+ if (agl_valid(ts_usec)) {
+ AbiSendMsgVELOCITY_ESTIMATE(VEL_OPTICFLOW_PMW3901_ID,
+ ts_usec, /* stamp [us] */
+ vel_x, /* x [m/s] */
+ vel_y, /* y [m/s] */
+ 0.f, /* z [m/s] */
+ noise, /* noise_x [m/s] */
+ noise, /* noise_y [m/s] */
+ -1.f /* noise_z [disabled] */
+ );
+ }
+
+ /* Send telemetry */
+#if SENSOR_SYNC_SEND_OPTICFLOW_PMW3901
+ float dummy_f = 0.f;
+ uint16_t dummy_u16 = 0;
+ float fps = 1.f / dt;
+ DOWNLINK_SEND_OPTIC_FLOW_EST(DefaultChannel, DefaultDevice,
+ &fps, /* fps */
+ &dummy_u16, /* corner_cnt */
+ &dummy_u16, /* tracked_cnt */
+ &flow_x_subpix, /* flow_x */
+ &flow_y_subpix, /* flow_y */
+ &flow_der_x_subpix, /* flow_der_x */
+ &flow_der_y_subpix, /* flow_der_y */
+ &vel_x, /* vel_x */
+ &vel_y, /* vel_y */
+ &dummy_f, /* vel_z */
+ &dummy_f, /* div_size */
+ &dummy_f, /* surface_roughness */
+ &dummy_f /* divergence */
+ );
+#endif
+}
+
+
+void opticflow_pmw3901_init(void) {
+ pmw3901_init(&of_pmw.pmw, &OPTICFLOW_PMW3901_SPI_DEV, OPTICFLOW_PMW3901_SPI_SLAVE_IDX);
+ AbiBindMsgAGL(OPTICFLOW_PMW3901_AGL_ID, &agl_ev, agl_cb);
+ of_pmw.sensor_angle = OPTICFLOW_PMW3901_SENSOR_ANGLE;
+ of_pmw.subpixel_factor = OPTICFLOW_PMW3901_SUBPIXEL_FACTOR;
+ of_pmw.std_px = OPTICFLOW_PMW3901_STD_PX;
+ of_pmw.agl_timeout = OPTICFLOW_PMW3901_AGL_TIMEOUT_US;
+#ifdef OPTICFLOW_PMW3901_RAD_PER_PX
+ of_pmw.pmw.rad_per_px = OPTICFLOW_PMW3901_RAD_PER_PX;
+#endif
+}
+
+void opticflow_pmw3901_periodic(void) {
+ if (pmw3901_is_idle(&of_pmw.pmw)) {
+ pmw3901_start_read(&of_pmw.pmw);
+ }
+}
+
+void opticflow_pmw3901_event(void) {
+ pmw3901_event(&of_pmw.pmw);
+ if (pmw3901_data_available(&of_pmw.pmw)) {
+ int16_t delta_x, delta_y;
+ pmw3901_get_data(&of_pmw.pmw, &delta_x, &delta_y);
+ opticflow_pmw3901_publish(delta_x, delta_y, get_sys_time_usec());
+ }
+}
+
+
diff --git a/sw/airborne/modules/sensors/opticflow_pmw3901.h b/sw/airborne/modules/sensors/opticflow_pmw3901.h
new file mode 100644
index 0000000000..3412c36766
--- /dev/null
+++ b/sw/airborne/modules/sensors/opticflow_pmw3901.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) Tom van Dijk
+ *
+ * This file is part of paparazzi
+ *
+ * paparazzi 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, or (at your option)
+ * any later version.
+ *
+ * paparazzi 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 paparazzi; see the file COPYING. If not, see
+ * .
+ */
+/**
+ * @file "modules/sensors/opticflow_pmw3901.h"
+ * @author Tom van Dijk
+ * Driver for PMW3901 optical flow sensor
+ */
+
+#ifndef OPTICFLOW_PMW3901_H
+#define OPTICFLOW_PMW3901_H
+
+#include "peripherals/pmw3901.h"
+
+struct opticflow_pmw3901_t {
+ struct pmw3901_t pmw;
+ float sensor_angle; // [deg!]
+ int16_t subpixel_factor;
+ float std_px;
+ uint32_t agl_timeout;
+};
+extern struct opticflow_pmw3901_t of_pmw;
+
+extern void opticflow_pmw3901_init(void);
+extern void opticflow_pmw3901_periodic(void);
+extern void opticflow_pmw3901_event(void);
+
+#endif
+
diff --git a/sw/airborne/peripherals/pmw3901.c b/sw/airborne/peripherals/pmw3901.c
new file mode 100644
index 0000000000..4e2504feb8
--- /dev/null
+++ b/sw/airborne/peripherals/pmw3901.c
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) Tom van Dijk
+ *
+ * This file is part of paparazzi
+ *
+ * paparazzi 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, or (at your option)
+ * any later version.
+ *
+ * paparazzi 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 paparazzi; see the file COPYING. If not, see
+ * .
+ */
+/**
+ * @file "modules/peripherals/pmw3901.c"
+ * @author Tom van Dijk
+ * Low-level driver for PMW3901 optical flow sensor
+ *
+ * Code based on the PixArt reference firmware
+ * https://os.mbed.com/teams/PixArt/code/3901_referenceFirmware//file/10365086d44e/commHeaders/SPIcommFunctions.h/
+ *
+ */
+
+#include "pmw3901.h"
+
+#include "mcu_periph/sys_time.h"
+
+
+// Based on crazyflie-firmware
+#ifndef PMW3901_RAD_PER_PX
+#define PMW3901_RAD_PER_PX 0.002443389
+#endif
+
+// SPI divisor, to adjust the clock speed according to the PCLK
+// Don't exceed 2MHz
+#ifndef PMW3901_SPI_CDIV
+#define PMW3901_SPI_CDIV SPIDiv256
+#endif
+
+#define PMW3901_REG_MOTION 0x02
+#define PMW3901_REG_DELTA_X_L 0x03
+#define PMW3901_REG_DELTA_X_H 0x04
+#define PMW3901_REG_DELTA_Y_L 0x05
+#define PMW3901_REG_DELTA_Y_H 0x06
+
+
+// Non-blocking read function
+// returns true upon completion
+static bool readRegister_nonblocking(struct pmw3901_t *pmw, uint8_t addr, uint8_t *value) {
+ switch (pmw->readwrite_state) {
+ case 0:
+ if (get_sys_time_usec() < pmw->readwrite_timeout) return false;
+ pmw->trans.output_buf[0] = addr & 0x7F; // MSB 0 => read
+ pmw->trans.output_length = 1;
+ pmw->trans.input_length = 0;
+ pmw->trans.select = SPISelect;
+ spi_submit(pmw->periph, &pmw->trans);
+ pmw->readwrite_state++;
+ /* Falls through. */
+ case 1:
+ if (pmw->trans.status == SPITransPending || pmw->trans.status == SPITransRunning) return false;
+ // Write addr complete
+ pmw->readwrite_timeout = get_sys_time_usec() + 35;
+ pmw->readwrite_state++;
+ /* Falls through. */
+ case 2:
+ if (get_sys_time_usec() < pmw->readwrite_timeout) return false;
+ // Addr-read delay passed
+ pmw->trans.output_length = 0;
+ pmw->trans.input_length = 1;
+ pmw->trans.select = SPIUnselect;
+ spi_submit(pmw->periph, &pmw->trans);
+ pmw->readwrite_state++;
+ /* Falls through. */
+ case 3:
+ if (pmw->trans.status == SPITransPending || pmw->trans.status == SPITransRunning) return false;
+ // Read complete
+ pmw->trans.select = SPISelectUnselect;
+ *value = pmw->trans.input_buf[0];
+ pmw->readwrite_timeout = get_sys_time_usec() + 20;
+ pmw->readwrite_state = 0;
+ return true;
+ default: return false;
+ }
+}
+
+
+// Blocking read/write functions
+static uint8_t readRegister_blocking(struct pmw3901_t *pmw, uint8_t addr) {
+ pmw->trans.output_buf[0] = addr & 0x7F; // MSB 0 => read
+ pmw->trans.output_length = 1;
+ pmw->trans.input_length = 0;
+ pmw->trans.select = SPISelect;
+ spi_blocking_transceive(pmw->periph, &pmw->trans);
+ sys_time_usleep(35); // See ref firmware and datasheet
+ pmw->trans.output_length = 0;
+ pmw->trans.input_length = 1;
+ pmw->trans.select = SPIUnselect;
+ spi_blocking_transceive(pmw->periph, &pmw->trans);
+ pmw->trans.select = SPISelectUnselect;
+ return pmw->trans.input_buf[0];
+}
+
+static void writeRegister_blocking(struct pmw3901_t *pmw, uint8_t addr, uint8_t data) {
+ pmw->trans.output_buf[0] = addr | 0x80; // MSB 1 => write
+ pmw->trans.output_buf[1] = data;
+ pmw->trans.output_length = 2;
+ pmw->trans.input_length = 0;
+ spi_blocking_transceive(pmw->periph, &pmw->trans);
+}
+
+// For PixArt firmware compatibility:
+#define writeRegister(_addr, _data) writeRegister_blocking(pmw, (_addr), (_data))
+#define readRegister(_addr) readRegister_blocking(pmw, (_addr))
+#define wait_ms(_ms) sys_time_usleep((_ms) * 1000)
+
+
+static void initializeSensor(struct pmw3901_t *pmw) {
+ // Try to detect sensor before initializing
+ int tries = 0;
+ while (readRegister(0x00) != 0x49 && tries < 100) {
+ sys_time_usleep(50);
+ tries++;
+ }
+
+ // From reference firmware
+ writeRegister(0x7F, 0x00);
+ writeRegister(0x55, 0x01);
+ writeRegister(0x50, 0x07);
+ writeRegister(0x7F, 0x0E);
+ writeRegister(0x43, 0x10);
+
+ if (readRegister(0x67) & 0x40)
+ writeRegister(0x48, 0x04);
+
+ else
+ writeRegister(0x48, 0x02);
+
+ writeRegister(0x7F, 0x00);
+ writeRegister(0x51, 0x7B);
+ writeRegister(0x50, 0x00);
+ writeRegister(0x55, 0x00);
+ writeRegister(0x7F, 0x0E);
+
+ if (readRegister(0x73) == 0x00) {
+ writeRegister(0x7F, 0x00);
+ writeRegister(0x61, 0xAD);
+ writeRegister(0x51, 0x70);
+ writeRegister(0x7F, 0x0E);
+
+ if (readRegister(0x70) <= 28)
+ writeRegister(0x70, readRegister(0x70) + 14);
+
+ else
+ writeRegister(0x70, readRegister(0x70) + 11);
+
+ writeRegister(0x71, readRegister(0x71) * 45/100);
+ }
+
+ writeRegister(0x7F, 0x00);
+ writeRegister(0x61, 0xAD);
+ writeRegister(0x7F, 0x03);
+ writeRegister(0x40, 0x00);
+ writeRegister(0x7F, 0x05);
+ writeRegister(0x41, 0xB3);
+ writeRegister(0x43, 0xF1);
+ writeRegister(0x45, 0x14);
+ writeRegister(0x5B, 0x32);
+ writeRegister(0x5F, 0x34);
+ writeRegister(0x7B, 0x08);
+ writeRegister(0x7F, 0x06);
+ writeRegister(0x44, 0x1B);
+ writeRegister(0x40, 0xBF);
+ writeRegister(0x4E, 0x3F);
+ writeRegister(0x7F, 0x06);
+ writeRegister(0x44, 0x1B);
+ writeRegister(0x40, 0xBF);
+ writeRegister(0x4E, 0x3F);
+ writeRegister(0x7F, 0x08);
+ writeRegister(0x65, 0x20);
+ writeRegister(0x6A, 0x18);
+ writeRegister(0x7F, 0x09);
+ writeRegister(0x4F, 0xAF);
+ writeRegister(0x5F, 0x40);
+ writeRegister(0x48, 0x80);
+ writeRegister(0x49, 0x80);
+ writeRegister(0x57, 0x77);
+ writeRegister(0x60, 0x78);
+ writeRegister(0x61, 0x78);
+ writeRegister(0x62, 0x08);
+ writeRegister(0x63, 0x50);
+ writeRegister(0x7F, 0x0A);
+ writeRegister(0x45, 0x60);
+ writeRegister(0x7F, 0x00);
+ writeRegister(0x4D, 0x11);
+ writeRegister(0x55, 0x80);
+ writeRegister(0x74, 0x21);
+ writeRegister(0x75, 0x1F);
+ writeRegister(0x4A, 0x78);
+ writeRegister(0x4B, 0x78);
+ writeRegister(0x44, 0x08);
+ writeRegister(0x45, 0x50);
+ writeRegister(0x64, 0xFF);
+ writeRegister(0x65, 0x1F);
+ writeRegister(0x7F, 0x14);
+ writeRegister(0x65, 0x67);
+ writeRegister(0x66, 0x08);
+ writeRegister(0x63, 0x70);
+ writeRegister(0x7F, 0x15);
+ writeRegister(0x48, 0x48);
+ writeRegister(0x7F, 0x07);
+ writeRegister(0x41, 0x0D);
+ writeRegister(0x43, 0x14);
+ writeRegister(0x4B, 0x0E);
+ writeRegister(0x45, 0x0F);
+ writeRegister(0x44, 0x42);
+ writeRegister(0x4C, 0x80);
+ writeRegister(0x7F, 0x10);
+ writeRegister(0x5B, 0x02);
+ writeRegister(0x7F, 0x07);
+ writeRegister(0x40, 0x41);
+ writeRegister(0x70, 0x00);
+
+ wait_ms(10);
+
+ writeRegister(0x32, 0x44);
+ writeRegister(0x7F, 0x07);
+ writeRegister(0x40, 0x40);
+ writeRegister(0x7F, 0x06);
+ writeRegister(0x62, 0xF0);
+ writeRegister(0x63, 0x00);
+ writeRegister(0x7F, 0x0D);
+ writeRegister(0x48, 0xC0);
+ writeRegister(0x6F, 0xD5);
+ writeRegister(0x7F, 0x00);
+ writeRegister(0x5B, 0xA0);
+ writeRegister(0x4E, 0xA8);
+ writeRegister(0x5A, 0x50);
+ writeRegister(0x40, 0x80);
+}
+
+
+void pmw3901_init(struct pmw3901_t *pmw, struct spi_periph *periph, uint8_t slave_idx) {
+ // Set up SPI peripheral and transaction
+ pmw->periph = periph;
+ pmw->trans.input_buf = pmw->spi_input_buf;
+ pmw->trans.output_buf = pmw->spi_output_buf;
+ pmw->trans.slave_idx = slave_idx;
+ pmw->trans.select = SPISelectUnselect;
+ pmw->trans.cpol = SPICpolIdleLow;
+ pmw->trans.cpha = SPICphaEdge1;
+ pmw->trans.dss = SPIDss8bit;
+ pmw->trans.bitorder = SPIMSBFirst;
+ pmw->trans.cdiv = PMW3901_SPI_CDIV;
+ pmw->trans.before_cb = NULL;
+ pmw->trans.after_cb = NULL;
+ pmw->trans.status = SPITransDone;
+ // Initialize sensor registers
+ initializeSensor(pmw);
+ // Set up remaining fields
+ pmw->state = PMW3901_IDLE;
+ pmw->delta_x = 0;
+ pmw->delta_y = 0;
+ pmw->data_available = false;
+ pmw->rad_per_px = PMW3901_RAD_PER_PX;
+}
+
+void pmw3901_event(struct pmw3901_t *pmw) {
+ uint8_t temp;
+ switch (pmw->state) {
+ case PMW3901_IDLE:
+ /* Do nothing */
+ return;
+ case PMW3901_READ_MOTION:
+ if (!readRegister_nonblocking(pmw, PMW3901_REG_MOTION, &temp)) return;
+ if (!(temp & 0x80)) return;
+ pmw->delta_x = 0;
+ pmw->delta_y = 0;
+ pmw->state++;
+ /* Falls through. */
+ case PMW3901_READ_DELTAXLOW:
+ if (!readRegister_nonblocking(pmw, PMW3901_REG_DELTA_X_L, &temp)) return;
+ pmw->delta_x |= temp;
+ pmw->state++;
+ /* Falls through. */
+ case PMW3901_READ_DELTAXHIGH:
+ if (!readRegister_nonblocking(pmw, PMW3901_REG_DELTA_X_H, &temp)) return;
+ pmw->delta_x |= (temp << 8) & 0xFF00;
+ pmw->state++;
+ /* Falls through. */
+ case PMW3901_READ_DELTAYLOW:
+ if (!readRegister_nonblocking(pmw, PMW3901_REG_DELTA_Y_L, &temp)) return;
+ pmw->delta_y |= temp;
+ pmw->state++;
+ /* Falls through. */
+ case PMW3901_READ_DELTAYHIGH:
+ if (!readRegister_nonblocking(pmw, PMW3901_REG_DELTA_Y_H, &temp)) return;
+ pmw->delta_y |= (temp << 8) & 0xFF00;
+ pmw->data_available = true;
+ pmw->state = PMW3901_IDLE;
+ return;
+ default: return;
+ }
+}
+
+bool pmw3901_is_idle(struct pmw3901_t *pmw) {
+ return pmw->state == PMW3901_IDLE;
+}
+
+void pmw3901_start_read(struct pmw3901_t *pmw) {
+ if (pmw3901_is_idle(pmw)) {
+ pmw->state = PMW3901_READ_MOTION;
+ }
+}
+
+bool pmw3901_data_available(struct pmw3901_t *pmw) {
+ return pmw->data_available;
+}
+
+bool pmw3901_get_data(struct pmw3901_t *pmw, int16_t *delta_x, int16_t *delta_y) {
+ if (!pmw->data_available) return false;
+ *delta_x = pmw->delta_x;
+ *delta_y = pmw->delta_y;
+ pmw->data_available = false;
+ return true;
+}
diff --git a/sw/airborne/peripherals/pmw3901.h b/sw/airborne/peripherals/pmw3901.h
new file mode 100644
index 0000000000..1629c2c0f4
--- /dev/null
+++ b/sw/airborne/peripherals/pmw3901.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) Tom van Dijk
+ *
+ * This file is part of paparazzi
+ *
+ * paparazzi 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, or (at your option)
+ * any later version.
+ *
+ * paparazzi 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 paparazzi; see the file COPYING. If not, see
+ * .
+ */
+/**
+ * @file "modules/peripherals/pmw3901.h"
+ * @author Tom van Dijk
+ * Low-level driver for PMW3901 optical flow sensor
+ *
+ * Some notes about the sensor:
+ * - The sensor is extremely poorly documented:
+ * - There are no units or delta_t's specified for the delta_x and _y registers.
+ * - The datasheet does not specify whether delta_x and _y are rates [something/s]
+ * or pixel counts. In the latter case, the datasheet does not specify when
+ * delta_x and _y are reset to 0.
+ * - The squal quality(?) register is not described.
+ *
+ * Based on crazyflie-firmware::kalman_core.c::512
+ * - delta_x, _y are pixel counts, not rates.
+ * - crazyflie uses scaling factor of thetapix/Npix = 0,002443389 rad/px,
+ * corresponding to a focal length of ~409 px.
+ */
+
+#ifndef PMW3901_H_
+#define PMW3901_H_
+
+#include "mcu_periph/spi.h"
+
+#include
+#include
+
+
+#define SPI_BUFFER_SIZE 8
+
+
+enum pmw3901_state {
+ PMW3901_IDLE,
+ PMW3901_READ_MOTION,
+ PMW3901_READ_DELTAXLOW,
+ PMW3901_READ_DELTAXHIGH,
+ PMW3901_READ_DELTAYLOW,
+ PMW3901_READ_DELTAYHIGH,
+};
+
+struct pmw3901_t {
+ struct spi_periph *periph;
+ struct spi_transaction trans;
+ volatile uint8_t spi_input_buf[SPI_BUFFER_SIZE];
+ volatile uint8_t spi_output_buf[SPI_BUFFER_SIZE];
+ enum pmw3901_state state;
+ uint8_t readwrite_state;
+ uint32_t readwrite_timeout;
+ int16_t delta_x;
+ int16_t delta_y;
+ bool data_available;
+ float rad_per_px;
+};
+
+void pmw3901_init(struct pmw3901_t *pmw, struct spi_periph *periph, uint8_t slave_idx);
+
+void pmw3901_event(struct pmw3901_t *pmw);
+
+bool pmw3901_is_idle(struct pmw3901_t *pmw);
+void pmw3901_start_read(struct pmw3901_t *pmw);
+bool pmw3901_data_available(struct pmw3901_t *pmw);
+bool pmw3901_get_data(struct pmw3901_t *pmw, int16_t *delta_x, int16_t *delta_y);
+
+
+
+#endif // PMW3901_H_
diff --git a/sw/airborne/subsystems/abi_sender_ids.h b/sw/airborne/subsystems/abi_sender_ids.h
index efae34f12b..bf0e1c0c87 100644
--- a/sw/airborne/subsystems/abi_sender_ids.h
+++ b/sw/airborne/subsystems/abi_sender_ids.h
@@ -347,6 +347,10 @@
#define FLOW_OPTICFLOW_ID 1
#endif
+#ifndef FLOW_OPTICFLOW_PMW3901_ID
+#define FLOW_OPTICFLOW_PMW3901_ID 2
+#endif
+
/*
* IDs of VELOCITY estimates (message 12)
*/
@@ -366,6 +370,10 @@
#define VEL_STEREOCAM_ID 4
#endif
+#ifndef VEL_OPTICFLOW_PMW3901_ID
+#define VEL_OPTICFLOW_PMW3901_ID 5
+#endif
+
/*
* IDs of RSSI measurements (message 13)
*/