diff --git a/conf/modules/mag_lis3mdl.xml b/conf/modules/mag_lis3mdl.xml
new file mode 100644
index 00000000000..f663d19893f
--- /dev/null
+++ b/conf/modules/mag_lis3mdl.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+ ST LIS3MDL magnetometer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ifeq ($(MAG_LIS3MDL_I2C_DEV),)
+ $(error mag_lis3mdl module error: please configure MAG_LIS3MDL_I2C_DEV)
+ endif
+
+
+
+
+
+
diff --git a/sw/airborne/modules/sensors/mag_lis3mdl.c b/sw/airborne/modules/sensors/mag_lis3mdl.c
new file mode 100644
index 00000000000..4320592379c
--- /dev/null
+++ b/sw/airborne/modules/sensors/mag_lis3mdl.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 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/sensors/mag_lis3mdl.c
+ *
+ * Module wrapper for ST LIS3MDL magnetometers.
+ */
+
+#include "modules/sensors/mag_lis3mdl.h"
+#include "mcu_periph/uart.h"
+#include "pprzlink/messages.h"
+#include "subsystems/datalink/downlink.h"
+#include "generated/airframe.h"
+
+#ifndef LIS3MDL_CHAN_X
+#define LIS3MDL_CHAN_X 0
+#endif
+#ifndef LIS3MDL_CHAN_Y
+#define LIS3MDL_CHAN_Y 1
+#endif
+#ifndef LIS3MDL_CHAN_Z
+#define LIS3MDL_CHAN_Z 2
+#endif
+#ifndef LIS3MDL_CHAN_X_SIGN
+#define LIS3MDL_CHAN_X_SIGN +
+#endif
+#ifndef LIS3MDL_CHAN_Y_SIGN
+#define LIS3MDL_CHAN_Y_SIGN +
+#endif
+#ifndef LIS3MDL_CHAN_Z_SIGN
+#define LIS3MDL_CHAN_Z_SIGN +
+#endif
+
+#if MODULE_LIS3MDL_UPDATE_AHRS
+#include "subsystems/imu.h"
+#include "subsystems/abi.h"
+
+#if defined LIS3MDL_MAG_TO_IMU_PHI && defined LIS3MDL_MAG_TO_IMU_THETA && defined LIS3MDL_MAG_TO_IMU_PSI
+#define USE_MAG_TO_IMU 1
+static struct Int32RMat mag_to_imu; ///< rotation from mag to imu frame
+#else
+#define USE_MAG_TO_IMU 0
+#endif
+#endif
+
+struct Lis3mdl mag_lis3mdl;
+
+void mag_lis3mdl_module_init(void)
+{
+ lis3mdl_init(&mag_lis3mdl, &(MAG_LIS3MDL_I2C_DEV), LIS3MDL_ADDR1,
+ LIS3MDL_DATA_RATE_80_HZ,
+ LIS3MDL_SCALE_4_GAUSS,
+ LIS3MDL_MODE_CONTINUOUS,
+ LIS3MDL_PERFORMANCE_ULTRA_HIGH);
+
+#if MODULE_LIS3MDL_UPDATE_AHRS && USE_MAG_TO_IMU
+ struct Int32Eulers mag_to_imu_eulers = {
+ ANGLE_BFP_OF_REAL(LIS3MDL_MAG_TO_IMU_PHI),
+ ANGLE_BFP_OF_REAL(LIS3MDL_MAG_TO_IMU_THETA),
+ ANGLE_BFP_OF_REAL(LIS3MDL_MAG_TO_IMU_PSI)
+ };
+ int32_rmat_of_eulers(&mag_to_imu, &mag_to_imu_eulers);
+#endif
+}
+
+void mag_lis3mdl_module_periodic(void)
+{
+ lis3mdl_periodic(&mag_lis3mdl);
+}
+
+void mag_lis3mdl_module_event(void)
+{
+ lis3mdl_event(&mag_lis3mdl);
+
+ if (mag_lis3mdl.data_available) {
+#if MODULE_LIS3MDL_UPDATE_AHRS
+ // current timestamp
+ uint32_t now_ts = get_sys_time_usec();
+
+ // set channel order
+ struct Int32Vect3 mag = {
+ LIS3MDL_CHAN_X_SIGN(int32_t)(mag_lis3mdl.data.value[LIS3MDL_CHAN_X]),
+ LIS3MDL_CHAN_Y_SIGN(int32_t)(mag_lis3mdl.data.value[LIS3MDL_CHAN_Y]),
+ LIS3MDL_CHAN_Z_SIGN(int32_t)(mag_lis3mdl.data.value[LIS3MDL_CHAN_Z])
+ };
+ // only rotate if needed
+#if USE_MAG_TO_IMU
+ struct Int32Vect3 imu_mag;
+ // rotate data from mag frame to imu frame
+ int32_rmat_vmult(&imu_mag, &mag_to_imu, &mag);
+ // unscaled vector
+ VECT3_COPY(imu.mag_unscaled, imu_mag);
+#else
+ // unscaled vector
+ VECT3_COPY(imu.mag_unscaled, mag);
+#endif
+ // scale vector
+ imu_scale_mag(&imu);
+
+ AbiSendMsgIMU_MAG_INT32(MAG_LIS3MDL_SENDER_ID, now_ts, &imu.mag);
+#endif
+#if MODULE_LIS3MDL_SYNC_SEND
+ mag_lis3mdl_report();
+#endif
+#if MODULE_LIS3MDL_UPDATE_AHRS || MODULE_LIS3MDL_SYNC_SEND
+ mag_lis3mdl.data_available = false;
+#endif
+ }
+}
+
+void mag_lis3mdl_report(void)
+{
+ struct Int32Vect3 mag = {
+ LIS3MDL_CHAN_X_SIGN(int32_t)(mag_lis3mdl.data.value[LIS3MDL_CHAN_X]),
+ LIS3MDL_CHAN_Y_SIGN(int32_t)(mag_lis3mdl.data.value[LIS3MDL_CHAN_Y]),
+ LIS3MDL_CHAN_Z_SIGN(int32_t)(mag_lis3mdl.data.value[LIS3MDL_CHAN_Z])
+ };
+ DOWNLINK_SEND_IMU_MAG_RAW(DefaultChannel, DefaultDevice, &mag.x, &mag.y, &mag.z);
+}
diff --git a/sw/airborne/modules/sensors/mag_lis3mdl.h b/sw/airborne/modules/sensors/mag_lis3mdl.h
new file mode 100644
index 00000000000..1edf80e531d
--- /dev/null
+++ b/sw/airborne/modules/sensors/mag_lis3mdl.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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/sensors/mag_lis3mdl.h
+ *
+ * Module wrapper for ST LIS3MDL magnetometers.
+ */
+
+#ifndef MAG_LIS3MDL_H
+#define MAG_LIS3MDL_H
+
+#include "peripherals/lis3mdl.h"
+
+extern struct Lis3mdl mag_lis3mdl;
+
+extern void mag_lis3mdl_module_init(void);
+extern void mag_lis3mdl_module_periodic(void);
+extern void mag_lis3mdl_module_event(void);
+extern void mag_lis3mdl_report(void);
+
+#endif // MAG_LIS3MDL_H
diff --git a/sw/airborne/peripherals/lis3mdl.c b/sw/airborne/peripherals/lis3mdl.c
new file mode 100644
index 00000000000..4c5a344a6c6
--- /dev/null
+++ b/sw/airborne/peripherals/lis3mdl.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2019 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 peripherals/lis3mdl.c
+ *
+ * ST LIS3MDL 3-axis magnetometer driver interface (I2C).
+ */
+
+#include "peripherals/lis3mdl.h"
+
+#define LIS3MDL_ENABLE_AUTO_INC (0x1<<7)
+
+#define LIS3MDL_STATUS_ZYXOR 0b10000000
+#define LIS3MDL_STATUS_ZOR 0b01000000
+#define LIS3MDL_STATUS_YOR 0b00100000
+#define LIS3MDL_STATUS_XOR 0b00010000
+#define LIS3MDL_STATUS_ZYXDA 0b00001000
+#define LIS3MDL_STATUS_ZDA 0b00000100
+#define LIS3MDL_STATUS_YDA 0b00000010
+#define LIS3MDL_STATUS_XDA 0b00000001
+
+#define LIS3MDL_DEVICE_ID 0b00111101
+
+#define LIS3MDL_REG_WHO_AM_I 0x0F
+#define LIS3MDL_REG_CTL_1 0x20
+#define LIS3MDL_REG_CTL_2 0x21
+#define LIS3MDL_REG_CTL_3 0x22
+#define LIS3MDL_REG_CTL_4 0x23
+#define LIS3MDL_REG_STATUS 0x27
+#define LIS3MDL_REG_OUT_X_L 0x28
+#define LIS3MDL_REG_OUT_X_H 0x29
+#define LIS3MDL_REG_OUT_Y_L 0x2A
+#define LIS3MDL_REG_OUT_Y_H 0x2B
+#define LIS3MDL_REG_OUT_Z_L 0x2C
+#define LIS3MDL_REG_OUT_Z_H 0x2D
+#define LIS3MDL_REG_OUT_TEMP_L 0x2E
+#define LIS3MDL_REG_OUT_TEMP_H 0x2F
+
+#define LIS3MDL_REG_CTL_1_TEMP_EN 0b10000000
+#define LIS3MDL_REG_CTL_2_RESET 0b00000100
+
+void lis3mdl_init(struct Lis3mdl *mag, struct i2c_periph *i2c_p, uint8_t addr, uint8_t data_rate, uint8_t scale, uint8_t mode, uint8_t perf)
+{
+ /* set i2c_peripheral */
+ mag->i2c_p = i2c_p;
+ /* set i2c address */
+ mag->i2c_trans.slave_addr = addr;
+ mag->i2c_trans.status = I2CTransDone;
+
+ /* prepare config request */
+ mag->i2c_trans.buf[0] = LIS3MDL_REG_CTL_1 | LIS3MDL_ENABLE_AUTO_INC;
+ mag->i2c_trans.buf[1] = (data_rate << 2) | (perf << 5);
+ mag->i2c_trans.buf[2] = (scale << 5);
+ mag->i2c_trans.buf[3] = mode;
+ mag->i2c_trans.buf[4] = (perf << 2);
+ mag->i2c_trans.buf[5] = 0;
+
+ mag->initialized = false;
+ mag->status = LIS3MDL_CONF_UNINIT;
+ mag->data_available = false;
+}
+
+// Configure
+void lis3mdl_configure(struct Lis3mdl *mag)
+{
+ // Only configure when not busy
+ if (mag->i2c_trans.status != I2CTransSuccess && mag->i2c_trans.status != I2CTransFailed
+ && mag->i2c_trans.status != I2CTransDone) {
+ return;
+ }
+
+ // Only when succesfull continue with next
+ if (mag->i2c_trans.status == I2CTransSuccess) {
+ mag->status++;
+ }
+
+ mag->i2c_trans.status = I2CTransDone;
+ switch (mag->status) {
+
+ case LIS3MDL_CONF_UNINIT:
+ // send config request
+ i2c_transmit(mag->i2c_p, &(mag->i2c_trans), mag->i2c_trans.slave_addr, 6);
+ break;
+
+ case LIS3MDL_CONF_REG:
+ mag->status = LIS3MDL_STATUS_IDLE;
+ mag->initialized = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+void lis3mdl_read(struct Lis3mdl *mag)
+{
+ if (mag->status != LIS3MDL_STATUS_IDLE) {
+ return;
+ }
+
+ mag->i2c_trans.buf[0] = LIS3MDL_REG_STATUS | LIS3MDL_ENABLE_AUTO_INC;
+ i2c_transceive(mag->i2c_p, &(mag->i2c_trans), mag->i2c_trans.slave_addr, 1, 7);
+ mag->status = LIS3MDL_STATUS_MEAS;
+}
+
+// Get raw value
+#define Int16FromBuf(_buf,_idx) ((int16_t)(_buf[_idx] | (_buf[_idx+1] << 8)))
+
+void lis3mdl_event(struct Lis3mdl *mag)
+{
+ if (!mag->initialized) {
+ return;
+ }
+
+ switch (mag->status) {
+
+ case LIS3MDL_STATUS_MEAS:
+ if (mag->i2c_trans.status == I2CTransSuccess) {
+ // Read status and error bytes
+ const bool dr = mag->i2c_trans.buf[0] & LIS3MDL_STATUS_ZYXDA; // data ready
+ if (dr > 0) {
+ // Copy the data
+ mag->data.vect.x = Int16FromBuf(mag->i2c_trans.buf, 1);
+ mag->data.vect.y = Int16FromBuf(mag->i2c_trans.buf, 3);
+ mag->data.vect.z = Int16FromBuf(mag->i2c_trans.buf, 5);
+ mag->data_available = true;
+ }
+ // End reading, back to idle
+ mag->status = LIS3MDL_STATUS_IDLE;
+ }
+ break;
+
+ default:
+ if (mag->i2c_trans.status == I2CTransSuccess || mag->i2c_trans.status == I2CTransFailed) {
+ // Goto idle
+ mag->i2c_trans.status = I2CTransDone;
+ mag->status = LIS3MDL_STATUS_IDLE;
+ }
+ break;
+ }
+}
+
diff --git a/sw/airborne/peripherals/lis3mdl.h b/sw/airborne/peripherals/lis3mdl.h
new file mode 100644
index 00000000000..b3101469956
--- /dev/null
+++ b/sw/airborne/peripherals/lis3mdl.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 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 peripherals/lis3mdl.h
+ *
+ * ST LIS3MDL 3-axis magnetometer driver interface (I2C).
+ */
+
+#ifndef LIS3MDL_H
+#define LIS3MDL_H
+
+#include "std.h"
+#include "mcu_periph/i2c.h"
+#include "math/pprz_algebra_int.h"
+
+#define LIS3MDL_ADDR1 (0b0011110 << 1)
+#define LIS3MDL_ADDR2 (0b0011100 << 1)
+
+#define LIS3MDL_PERFORMANCE_LOW_POWER 0b00
+#define LIS3MDL_PERFORMANCE_MEDIUM 0b01
+#define LIS3MDL_PERFORMANCE_HIGH 0b10
+#define LIS3MDL_PERFORMANCE_ULTRA_HIGH 0b11
+
+#define LIS3MDL_DATA_RATE_0_625_HZ 0b000
+#define LIS3MDL_DATA_RATE_1_25_HZ 0b001
+#define LIS3MDL_DATA_RATE_2_5_HZ 0b010
+#define LIS3MDL_DATA_RATE_5_HZ 0b011
+#define LIS3MDL_DATA_RATE_10_HZ 0b100
+#define LIS3MDL_DATA_RATE_20_HZ 0b101
+#define LIS3MDL_DATA_RATE_40_HZ 0b110
+#define LIS3MDL_DATA_RATE_80_HZ 0b111
+
+#define LIS3MDL_MODE_CONTINUOUS 0b00
+#define LIS3MDL_MODE_SINGLE 0b01
+#define LIS3MDL_MODE_POWER_DOWN 0b11
+
+#define LIS3MDL_SCALE_4_GAUSS 0b00
+#define LIS3MDL_SCALE_8_GAUSS 0b01
+#define LIS3MDL_SCALE_12_GAUSS 0b10
+#define LIS3MDL_SCALE_16_GAUSS 0b11
+
+/** config status states */
+enum Lis3mdlStatus {
+ LIS3MDL_CONF_UNINIT,
+ LIS3MDL_CONF_REG,
+ LIS3MDL_STATUS_IDLE,
+ LIS3MDL_STATUS_MEAS
+};
+
+// main structure
+struct Lis3mdl {
+ struct i2c_periph *i2c_p; ///< peripheral used for communcation
+ struct i2c_transaction i2c_trans; ///< i2c transaction
+ bool initialized; ///< config done flag
+ enum Lis3mdlStatus status; ///< init status
+ volatile bool data_available; ///< data ready flag
+ union {
+ struct Int16Vect3 vect; ///< data vector in mag coordinate system
+ int16_t value[3]; ///< data values accessible by channel index
+ } data;
+};
+
+// Functions
+extern void lis3mdl_init(struct Lis3mdl *mag, struct i2c_periph *i2c_p, uint8_t addr, uint8_t data_rate, uint8_t scale, uint8_t mode, uint8_t perf);
+extern void lis3mdl_configure(struct Lis3mdl *mag);
+extern void lis3mdl_event(struct Lis3mdl *mag);
+extern void lis3mdl_read(struct Lis3mdl *mag);
+
+/// convenience function: read or start configuration if not already initialized
+static inline void lis3mdl_periodic(struct Lis3mdl *mag)
+{
+ if (mag->initialized) {
+ lis3mdl_read(mag);
+ } else {
+ lis3mdl_configure(mag);
+ }
+}
+
+#endif /* LIS3MDL_H */
diff --git a/sw/airborne/subsystems/abi_sender_ids.h b/sw/airborne/subsystems/abi_sender_ids.h
index 9720427f09f..beef38c2216 100644
--- a/sw/airborne/subsystems/abi_sender_ids.h
+++ b/sw/airborne/subsystems/abi_sender_ids.h
@@ -165,6 +165,10 @@
#define MAG_HMC58XX_SENDER_ID 2
#endif
+#ifndef MAG_LIS3MDL_SENDER_ID
+#define MAG_LIS3MDL_SENDER_ID 3
+#endif
+
#ifndef IMU_MAG_PITOT_ID
#define IMU_MAG_PITOT_ID 50
#endif