diff --git a/conf/modules/dw1000_arduino.xml b/conf/modules/dw1000_arduino.xml new file mode 100644 index 00000000000..be04aff3438 --- /dev/null +++ b/conf/modules/dw1000_arduino.xml @@ -0,0 +1,43 @@ + + + + + + Driver to get ranging data from Decawave DW1000 modules connected to Arduino + + Decawave DW1000 modules (http://www.decawave.com/products/dwm1000-module) are Ultra-Wide-Band devices that can be used for communication and ranging. + Especially, using 3 modules as anchors can provide data for a localization system based on trilateration. + The DW1000 is using a SPI connection, but an arduino-compatible board can be used with the library https://github.com/thotro/arduino-dw1000 to hyde the low level drivers and provide direct ranging informations. + + + +
+ + + + + + + + +
+
+
+ +
+ + + + + + + + + + + + + + +
+ diff --git a/sw/airborne/math/pprz_algebra_float.h b/sw/airborne/math/pprz_algebra_float.h index 76e425896a4..6b7c6fd66c7 100644 --- a/sw/airborne/math/pprz_algebra_float.h +++ b/sw/airborne/math/pprz_algebra_float.h @@ -583,6 +583,15 @@ static inline void float_vect_scale(float *a, const float s, const int n) for (i = 0; i < n; i++) { a[i] *= s; } } +/** a.b */ +static inline float float_vect_dot_product(const float *a, const float *b, const int n) +{ + int i; + float dot = 0.f; + for (i = 0; i < n; i++) { dot += a[i] * b[i]; } + return dot; +} + // // // Generic matrix algebra diff --git a/sw/airborne/modules/decawave/dw1000_arduino.c b/sw/airborne/modules/decawave/dw1000_arduino.c new file mode 100644 index 00000000000..c9d51dd7550 --- /dev/null +++ b/sw/airborne/modules/decawave/dw1000_arduino.c @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2017 Gautier Hattenberger + * + * 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/decawave/dw1000_arduino.c" + * @author Gautier Hattenberger + * Driver to get ranging data from Decawave DW1000 modules connected to Arduino + */ + +#include "modules/decawave/dw1000_arduino.h" + +#include "std.h" +#include "mcu_periph/uart.h" +#include "pprzlink/messages.h" +#include "subsystems/datalink/downlink.h" +#include "subsystems/abi.h" +#include "modules/decawave/trilateration.h" +#include "subsystems/gps.h" +#include "state.h" +#include "generated/flight_plan.h" +#include "generated/airframe.h" +#include +#include +#include + +/** Number of anchors + * + * using standard trilateration algorithm, only 3 anchors are required/supported + * at the moment. + * More advanced multilateration algorithms might allow more anchors in the future + */ +#ifndef DW1000_NB_ANCHORS +#define DW1000_NB_ANCHORS 3 +#endif + +/** default offset, applied to final result not to individual distances */ +#ifndef DW1000_OFFSET +#define DW1000_OFFSET { 0.f, 0.f, 0.f } +#endif + +/** default scale factor, applied to final result not to individual distances */ +#ifndef DW1000_SCALE +#define DW1000_SCALE { 1.f, 1.f, 1.f } +#endif + +/** default initial heading correction between anchors frame and global frame */ +#ifndef DW1000_INITIAL_HEADING +#define DW1000_INITIAL_HEADING 0.f +#endif + +/** default timeout (in ms) */ +#ifndef DW1000_TIMEOUT +#define DW1000_TIMEOUT 500 +#endif + +/** frame sync byte */ +#define DW_STX 0xFE + +/** Parsing states */ +#define DW_WAIT_STX 0 +#define DW_GET_DATA 1 +#define DW_GET_CK 2 +#define DW_NB_DATA 6 + +/** DW1000 positionning system structure */ +struct DW1000 { + uint8_t buf[DW_NB_DATA]; ///< incoming data buffer + uint8_t idx; ///< buffer index + uint8_t ck; ///< checksum + uint8_t state; ///< parser state + float initial_heading; ///< initial heading correction + struct Anchor anchors[DW1000_NB_ANCHORS]; ///buf); + if (dw->anchors[i].id == id) { + dw->anchors[i].distance = float_from_buf(dw->buf + 2); + dw->anchors[i].time = get_sys_time_float(); + dw->updated = true; + break; + } + } +} + +/** Data parsing function */ +static void dw1000_arduino_parse(struct DW1000 *dw, uint8_t c) +{ + switch (dw->state) { + + case DW_WAIT_STX: + /* Waiting Synchro */ + if (c == DW_STX) { + dw->idx = 0; + dw->ck = 0; + dw->state = DW_GET_DATA; + } + break; + + case DW_GET_DATA: + /* Read Bytes */ + dw->buf[dw->idx++] = c; + dw->ck += c; + if (dw->idx == DW_NB_DATA) { + dw->state = DW_GET_CK; + } + break; + + case DW_GET_CK: + /* Checksum */ + if (dw->ck == c) { + fill_anchor(dw); + } + dw->state = DW_WAIT_STX; + break; + } +} + +static void send_gps_dw1000_small(struct DW1000 *dw) +{ + // rotate and convert to cm integer + float x = dw->pos.x * cosf(dw->initial_heading) - dw->pos.y * sinf(dw->initial_heading); + float y = dw->pos.x * sinf(dw->initial_heading) + dw->pos.y * cosf(dw->initial_heading); + struct EnuCoor_i enu_pos; + enu_pos.x = (int32_t) (x * 100); + enu_pos.y = (int32_t) (y * 100); + enu_pos.z = (int32_t) (dw->pos.z * 100); + + // Convert the ENU coordinates to ECEF + ecef_of_enu_point_i(&(dw->gps_dw1000.ecef_pos), &(dw->ltp_def), &enu_pos); + SetBit(dw->gps_dw1000.valid_fields, GPS_VALID_POS_ECEF_BIT); + + lla_of_ecef_i(&(dw->gps_dw1000.lla_pos), &(dw->gps_dw1000.ecef_pos)); + SetBit(dw->gps_dw1000.valid_fields, GPS_VALID_POS_LLA_BIT); + + dw->gps_dw1000.hmsl = dw->ltp_def.hmsl + enu_pos.z * 10; + SetBit(dw->gps_dw1000.valid_fields, GPS_VALID_HMSL_BIT); + + dw->gps_dw1000.num_sv = 7; + dw->gps_dw1000.tow = get_sys_time_msec(); + dw->gps_dw1000.fix = GPS_FIX_3D; // set 3D fix to true + + // set gps msg time + dw->gps_dw1000.last_msg_ticks = sys_time.nb_sec_rem; + dw->gps_dw1000.last_msg_time = sys_time.nb_sec; + dw->gps_dw1000.last_3dfix_ticks = sys_time.nb_sec_rem; + dw->gps_dw1000.last_3dfix_time = sys_time.nb_sec; + + // publish new GPS data + uint32_t now_ts = get_sys_time_usec(); + AbiSendMsgGPS(GPS_DW1000_ID, now_ts, &(dw->gps_dw1000)); +} + +void dw1000_reset_heading_ref(void) +{ + // store current heading as ref and stop periodic call + dw1000.initial_heading = stateGetNedToBodyEulers_f()->psi; + dw1000_arduino_dw1000_reset_heading_ref_status = MODULES_STOP; +} + +/// init arrays from airframe file +static const uint16_t ids[] = DW1000_ANCHORS_IDS; +static const float pos_x[] = DW1000_ANCHORS_POS_X; +static const float pos_y[] = DW1000_ANCHORS_POS_Y; +static const float pos_z[] = DW1000_ANCHORS_POS_Z; +static const float offset[] = DW1000_OFFSET; +static const float scale[] = DW1000_SCALE; + +static void scale_position(struct DW1000 *dw) +{ + dw->pos.x = scale[0] * (dw->raw_pos.x - offset[0]); + dw->pos.y = scale[1] * (dw->raw_pos.y - offset[1]); + dw->pos.z = scale[2] * (dw->raw_pos.z - offset[2]); +} + +void dw1000_arduino_init() +{ + // init DW1000 structure + dw1000.idx = 0; + dw1000.ck = 0; + dw1000.state = DW_WAIT_STX; + dw1000.initial_heading = DW1000_INITIAL_HEADING; + dw1000.pos.x = 0.f; + dw1000.pos.y = 0.f; + dw1000.pos.z = 0.f; + dw1000.updated = false; + for (int i = 0; i < DW1000_NB_ANCHORS; i++) { + dw1000.anchors[i].distance = 0.f; + dw1000.anchors[i].time = 0.f; + dw1000.anchors[i].id = ids[i]; + dw1000.anchors[i].pos.x = pos_x[i]; + dw1000.anchors[i].pos.y = pos_y[i]; + dw1000.anchors[i].pos.z = pos_z[i]; + } + + // gps structure init + dw1000.gps_dw1000.fix = GPS_FIX_NONE; + dw1000.gps_dw1000.pdop = 0; + dw1000.gps_dw1000.sacc = 0; + dw1000.gps_dw1000.pacc = 0; + dw1000.gps_dw1000.cacc = 0; + dw1000.gps_dw1000.comp_id = GPS_DW1000_ID; + + struct LlaCoor_i llh_nav0; /* Height above the ellipsoid */ + llh_nav0.lat = NAV_LAT0; + llh_nav0.lon = NAV_LON0; + /* NAV_ALT0 = ground alt above msl, NAV_MSL0 = geoid-height (msl) over ellipsoid */ + llh_nav0.alt = NAV_ALT0 + NAV_MSL0; + ltp_def_from_lla_i(&dw1000.ltp_def, &llh_nav0); + + // init trilateration algorithm + trilateration_init(dw1000.anchors); + +} + +void dw1000_arduino_periodic() +{ + // Check for timeout + gps_periodic_check(&(dw1000.gps_dw1000)); +} + +void dw1000_arduino_report() +{ + float buf[9]; + buf[0] = dw1000.anchors[0].distance; + buf[1] = dw1000.anchors[1].distance; + buf[2] = dw1000.anchors[2].distance; + buf[3] = dw1000.raw_pos.x; + buf[4] = dw1000.raw_pos.y; + buf[5] = dw1000.raw_pos.z; + buf[6] = dw1000.pos.x; + buf[7] = dw1000.pos.y; + buf[8] = dw1000.pos.z; + DOWNLINK_SEND_PAYLOAD_FLOAT(DefaultChannel, DefaultDevice, 9, buf); +} + +/** check timeout for each anchor + * @return true if one has reach timeout + */ +static bool check_anchor_timeout(struct DW1000 *dw) +{ + const float now = get_sys_time_float(); + const float timeout = (float)DW1000_TIMEOUT / 1000.; + for (int i = 0; i < DW1000_NB_ANCHORS; i++) { + if (now - dw->anchors[i].time > timeout) { + return true; + } + } + return false; +} + +void dw1000_arduino_event() +{ + // Look for data on serial link and send to parser + while (uart_char_available(&DW1000_ARDUINO_DEV)) { + uint8_t ch = uart_getch(&DW1000_ARDUINO_DEV); + dw1000_arduino_parse(&dw1000, ch); + // process if new data + if (dw1000.updated) { + // if no timeout on anchors, run trilateration algorithm + if (check_anchor_timeout(&dw1000) == false && + trilateration_compute(dw1000.anchors, &dw1000.raw_pos) == 0) { + // apply scale and neutral corrections + scale_position(&dw1000); + // send fake GPS message for INS filters + send_gps_dw1000_small(&dw1000); + } + dw1000.updated = false; + } + } +} + + diff --git a/sw/airborne/modules/decawave/dw1000_arduino.h b/sw/airborne/modules/decawave/dw1000_arduino.h new file mode 100644 index 00000000000..fd2e6076e8f --- /dev/null +++ b/sw/airborne/modules/decawave/dw1000_arduino.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 Gautier Hattenberger + * + * 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/decawave/dw1000_arduino.h" + * @author Gautier Hattenberger + * Driver to get ranging data from Decawave DW1000 modules connected to Arduino + */ + +#ifndef DW1000_ARDUINO_H +#define DW1000_ARDUINO_H + +extern void dw1000_arduino_init(void); +extern void dw1000_arduino_periodic(void); +extern void dw1000_arduino_report(void); +extern void dw1000_arduino_event(void); + +/** Reset reference heading to current heading + * AHRS/INS should be aligned before calling this function + */ +extern void dw1000_reset_heading_ref(void); + +#endif + diff --git a/sw/airborne/modules/decawave/trilateration.c b/sw/airborne/modules/decawave/trilateration.c new file mode 100644 index 00000000000..c2a796333ea --- /dev/null +++ b/sw/airborne/modules/decawave/trilateration.c @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017 Gautier Hattenberger + * + * 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/decawave/trilateration.c" + * @author Gautier Hattenberger + * Trilateration algorithm + * https://en.wikipedia.org/wiki/Trilateration + */ + +#include "trilateration.h" +#include "math/pprz_algebra_float.h" + +// base original locations +static float P[3][3]; +// base unit vector on x, y and z axis +static float Ex[3], Ey[3], Ez[3]; +// base inter distances +static float D, I, J; + +bool init_failed; + +int trilateration_init(struct Anchor *anchors) +{ + float tmp[3], n; + init_failed = false; + + // store original points + for (int i = 0; i < 3; i++) { + P[i][0] = anchors[i].pos.x; + P[i][1] = anchors[i].pos.y; + P[i][2] = anchors[i].pos.z; + } + // Ex = (P1 - P0) / ||P1 - P0|| + float_vect_diff(tmp, P[1], P[0], 3); + n = float_vect_norm(tmp, 3); + if (n > 0.f) { + float_vect_sdiv(Ex, tmp, n, 3); + } else { + init_failed = true; + return -1; + } + // I = Ex . (P2 - P0) + float_vect_diff(tmp, P[2], P[0], 3); + I = float_vect_dot_product(Ex, tmp, 3); + // Ey = (P2 - P0 - I.Ex) / ||P2 - P0 - I.Ex|| + float_vect_smul(tmp, Ex, -I, 3); + float_vect_diff(tmp, tmp, P[0], 3); + float_vect_add(tmp, P[2], 3); + n = float_vect_norm(tmp, 3); + if (n > 0.f) { + float_vect_sdiv(Ey, tmp, n, 3); + } else { + init_failed = true; + return -1; + } + // Ez = Ex x Ey + Ez[0] = Ex[1]*Ey[2] - Ex[2]*Ey[1]; + Ez[1] = Ex[2]*Ey[0] - Ex[0]*Ey[2]; + Ez[2] = Ex[0]*Ey[1] - Ex[1]*Ey[0]; + // D = ||P1 - P0|| + float_vect_diff(tmp, P[1], P[0], 3); + D = float_vect_norm(tmp, 3); + // J = Ey . (P2 - P0) + float_vect_diff(tmp, P[2], P[0], 3); + J = float_vect_dot_product(Ey, tmp, 3); + + return 0; +} + +int trilateration_compute(struct Anchor *anchors, struct EnuCoor_f *pos) +{ + if (init_failed) { + return -1; + } + const float r02 = anchors[0].distance * anchors[0].distance; + const float r12 = anchors[1].distance * anchors[1].distance; + const float r22 = anchors[2].distance * anchors[2].distance; + const float d2 = D * D; + const float i2 = I * I; + const float j2 = J * J; + float tmp[3]; + tmp[0] = (r02 - r12 + d2) / (2.f * D); + tmp[1] = (r02 - r22 + i2 + j2) / (2.f * J) - ((I * tmp[0]) / J); + const float d0 = r02 - (tmp[0] * tmp[0]) - (tmp[1] * tmp[1]); + if (d0 < 0.f) { + // impossible solution + // might happen if position of the anchors are not correct + // or if reported distances are completely wrong + return -1; + } + tmp[2] = sqrtf(d0); // only keep positive value + // restore original frame + pos->x = P[0][0] + tmp[0] * Ex[0] + tmp[1] * Ey[0] + tmp[2] * Ez[0]; + pos->y = P[0][1] + tmp[0] * Ex[1] + tmp[1] * Ey[1] + tmp[2] * Ez[1]; + pos->z = P[0][2] + tmp[0] * Ex[2] + tmp[1] * Ey[2] + tmp[2] * Ez[2]; + return 0; +} + diff --git a/sw/airborne/modules/decawave/trilateration.h b/sw/airborne/modules/decawave/trilateration.h new file mode 100644 index 00000000000..c96a2861a54 --- /dev/null +++ b/sw/airborne/modules/decawave/trilateration.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 Gautier Hattenberger + * + * 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/decawave/trilateration.h" + * @author Gautier Hattenberger + * Trilateration algorithm + * https://en.wikipedia.org/wiki/Trilateration + */ + +#ifndef TRILATERATION_H +#define TRILATERATION_H + +#include "std.h" +#include "math/pprz_geodetic_float.h" + +/** Anchor structure */ +struct Anchor { + float distance; ///< last measured distance + float time; ///< time of the last received data + struct EnuCoor_f pos; ///< position of the anchor + uint16_t id; ///< anchor ID +}; + +/** Init internal trilateration structures + * + * @param[in] anchors array of anchors with their location + */ +extern int trilateration_init(struct Anchor *anchors); + +/** Compute trilateration based on the latest measurments + * + * @param[in] anchors array of anchors with updated distance measurements + * @param[out] pos computed position + * @return error status (0 for valid position) + */ +extern int trilateration_compute(struct Anchor *anchors, struct EnuCoor_f *pos); + +#endif + + diff --git a/sw/airborne/subsystems/abi_sender_ids.h b/sw/airborne/subsystems/abi_sender_ids.h index 7d57a15fb40..3954a90435b 100644 --- a/sw/airborne/subsystems/abi_sender_ids.h +++ b/sw/airborne/subsystems/abi_sender_ids.h @@ -194,6 +194,10 @@ #define GPS_IMCU_ID 14 #endif +#ifndef GPS_DW1000_ID +#define GPS_DW1000_ID 15 +#endif + /* * IDs of IMU sensors (accel, gyro) */