diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index d5fcbd11e3..0135464c81 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -292,6 +292,14 @@ ifneq (,$(filter hih6130,$(USEMODULE))) FEATURES_REQUIRED += periph_i2c endif +ifneq (,$(filter hmc5883l,$(USEMODULE))) + FEATURES_REQUIRED += periph_i2c +endif + +ifneq (,$(filter hmc5883l_int,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio_irq +endif + ifneq (,$(filter hts221,$(USEMODULE))) FEATURES_REQUIRED += periph_i2c endif diff --git a/drivers/Makefile.include b/drivers/Makefile.include index e23fede7f6..a2d442cae3 100644 --- a/drivers/Makefile.include +++ b/drivers/Makefile.include @@ -144,6 +144,10 @@ ifneq (,$(filter hdc1000,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/hdc1000/include endif +ifneq (,$(filter hmc5883l,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/drivers/hmc5883l/include +endif + ifneq (,$(filter hts221,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/hts221/include endif diff --git a/drivers/hmc5883l/Makefile b/drivers/hmc5883l/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/hmc5883l/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/hmc5883l/hmc5883l.c b/drivers/hmc5883l/hmc5883l.c new file mode 100644 index 0000000000..ff499c802d --- /dev/null +++ b/drivers/hmc5883l/hmc5883l.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_hmc5883l + * @brief Device driver for the Honeywell HMC5883L 3-axis digital compass + * @author Gunar Schorcht + * @file + * @{ + */ + +#include +#include + +#include "hmc5883l_regs.h" +#include "hmc5883l.h" + +#include "log.h" +#include "xtimer.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#if ENABLE_DEBUG + +#define DEBUG_DEV(f, d, ...) \ + DEBUG("[hmc5883l] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->dev, HMC5883L_I2C_ADDRESS, ## __VA_ARGS__); + +#else /* ENABLE_DEBUG */ + +#define DEBUG_DEV(f, d, ...) + +#endif /* ENABLE_DEBUG */ + +#define ERROR_DEV(f, d, ...) \ + LOG_ERROR("[hmc5883l] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->dev, HMC5883L_I2C_ADDRESS, ## __VA_ARGS__); + +#define EXEC_RET(f, r) \ + if ((r = f) != HMC5883L_OK) { \ + DEBUG("[hmc5883l] %s: error code %d\n", __func__, res); \ + return res; \ + } + +#define EXEC_RET_CODE(f, r, c) \ + if ((r = f) != HMC5883L_OK) { \ + DEBUG("[hmc5883l] %s: error code %d\n", __func__, res); \ + return c; \ + } + +/** Forward declaration of functions for internal use */ + +static int _is_available(const hmc5883l_t *dev); + +static int _reg_read(const hmc5883l_t *dev, uint8_t reg, uint8_t *data, uint16_t len); +static int _reg_write(const hmc5883l_t *dev, uint8_t reg, uint8_t data); + +int hmc5883l_init(hmc5883l_t *dev, const hmc5883l_params_t *params) +{ + int res = HMC5883L_OK; + + assert(dev != NULL); + assert(params != NULL); + DEBUG_DEV("params=%p", dev, params); + + /* init sensor data structure */ + dev->dev = params->dev; +#if MODULE_HMC5883L_INT + dev->int_pin = params->int_pin; +#endif + dev->op_mode = params->op_mode; + dev->gain = params->gain; + + /* check availability of the sensor */ + EXEC_RET(_is_available(dev), res) + + /* set configuration register A and B */ + EXEC_RET(_reg_write(dev, HMC5883L_REG_CFG_A, params->meas_avg | + params->meas_mode | + params->dor), res); + EXEC_RET(_reg_write(dev, HMC5883L_REG_CFG_B, params->gain), res); + + /* set operation mode */ + EXEC_RET(_reg_write(dev, HMC5883L_REG_MODE, params->op_mode), res); + + /* to set the LOCK flag, read the first data sample that is not valid */ + uint8_t data[6]; + EXEC_RET(_reg_read(dev, HMC5883L_REG_OUT_X_MSB, data, 6), res); + + return res; +} + +#ifdef MODULE_HMC5883L_INT + +int hmc5883l_init_int(hmc5883l_t *dev, hmc5883l_drdy_int_cb_t cb, void *arg) +{ + assert(dev != NULL); + assert(dev->int_pin != GPIO_UNDEF); + DEBUG_DEV("", dev); + + if (gpio_init_int(dev->int_pin, GPIO_IN, GPIO_FALLING, cb, arg)) { + return HMC5883L_ERROR_COMMON; + } + + return HMC5883L_OK; +} + +#endif /* MODULE_HMC5883L_INT */ + +int hmc5883l_data_ready(const hmc5883l_t *dev) +{ + assert(dev != NULL); + DEBUG_DEV("", dev); + + int res = HMC5883L_OK; + + uint8_t reg; + + EXEC_RET(_reg_read(dev, HMC5883L_REG_STATUS, ®, 1), res); + return (reg == HMC5883L_REG_STATUS_RDY) ? HMC5883L_OK : HMC5883L_ERROR_NO_DATA; +} + +/* + * Scale factors for conversion of raw sensor data to uGs for possible + * sensitivities according to the datasheet. + */ +static const uint16_t HMC5883L_RES[] = { + 730, /* uG/LSb for HMC5883L_GAIN_1370 with range +-0.88 Gs */ + 917, /* uG/LSb for HMC5883L_GAIN_1090 with range +-1.3 Gs */ + 1220, /* uG/LSb for HMC5883L_GAIN_820 with range +-1.9 Gs */ + 1515, /* uG/LSb for HMC5883L_GAIN_660 with range +-2.5 Gs */ + 2273, /* uG/LSb for HMC5883L_GAIN_440 with range +-4.0 Gs */ + 2564, /* uG/LSb for HMC5883L_GAIN_390 with range +-4.7 Gs */ + 3030, /* uG/LSb for HMC5883L_GAIN_330 with range +-5.6 Gs */ + 4348, /* uG/LSb for HMC5883L_GAIN_230 with range +-8.1 Gs */ +}; + +int hmc5883l_read(const hmc5883l_t *dev, hmc5883l_data_t *data) + +{ + assert(dev != NULL); + assert(data != NULL); + DEBUG_DEV("data=%p", dev, data); + + int res = HMC5883L_OK; + + hmc5883l_raw_data_t raw; + + EXEC_RET(hmc5883l_read_raw (dev, &raw), res); + + /* + * The range of raw data is -2048 ... -2047. That is, raw data multiplied + * by scale fit into 32 bit integer. + */ + data->x = ((int32_t)raw.x * HMC5883L_RES[dev->gain >> HMC5883L_REG_CFG_B_GN_S]) / 1000; + data->y = ((int32_t)raw.y * HMC5883L_RES[dev->gain >> HMC5883L_REG_CFG_B_GN_S]) / 1000; + data->z = ((int32_t)raw.z * HMC5883L_RES[dev->gain >> HMC5883L_REG_CFG_B_GN_S]) / 1000; + + return res; +} + +int hmc5883l_read_raw(const hmc5883l_t *dev, hmc5883l_raw_data_t *raw) +{ + assert(dev != NULL); + assert(raw != NULL); + DEBUG_DEV("raw=%p", dev, raw); + + int res = HMC5883L_OK; + + uint8_t data[6]; + + /* read raw data sample */ + EXEC_RET_CODE(_reg_read(dev, HMC5883L_REG_OUT_X_MSB, data, 6), + res, HMC5883L_ERROR_RAW_DATA); + + /* data MSB @ lower address */ + raw->x = (data[0] << 8) | data[1]; + raw->y = (data[4] << 8) | data[5]; + raw->z = (data[2] << 8) | data[3]; + + return res; +} + +int hmc5883l_power_down(hmc5883l_t *dev) +{ + assert(dev != NULL); + DEBUG_DEV("", dev); + + /* set operation mode to Idle mode with only 5 uA current */ + return _reg_write(dev, HMC5883L_REG_MODE, HMC5883L_OP_MODE_IDLE); +} + +int hmc5883l_power_up(hmc5883l_t *dev) +{ + assert(dev != NULL); + DEBUG_DEV("", dev); + + /* set operation mode to last operation mode */ + return _reg_write(dev, HMC5883L_REG_MODE, dev->op_mode); +} + +/** Functions for internal use only */ + +/** + * @brief Check the chip ID to test whether sensor is available + */ +static int _is_available(const hmc5883l_t *dev) +{ + DEBUG_DEV("", dev); + + int res = HMC5883L_OK; + + uint8_t id_c[] = HMC5883L_ID; + uint8_t id_r[HMC5883L_ID_LEN]; + + /* read the chip id from HMC5883L_REG_ID_X */ + EXEC_RET(_reg_read(dev, HMC5883L_REG_ID_A, id_r, HMC5883L_ID_LEN), res); + + if (memcmp(id_r, id_c, HMC5883L_ID_LEN)) { + DEBUG_DEV("sensor is not available, wrong id %02x%02x%02x, " + "should be %02x%02x%02x", + dev, id_r[0], id_r[1], id_r[2], id_c[0], id_c[1], id_c[2]); + return HMC5883L_ERROR_WRONG_ID; + } + + return res; +} + +static int _reg_read(const hmc5883l_t *dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + assert(dev != NULL); + assert(data != NULL); + assert(len != 0); + + DEBUG_DEV("read %d byte from sensor registers starting at addr 0x%02x", + dev, len, reg); + + if (i2c_acquire(dev->dev)) { + DEBUG_DEV("could not acquire I2C bus", dev); + return HMC5883L_ERROR_I2C; + } + + int res = i2c_read_regs(dev->dev, HMC5883L_I2C_ADDRESS, reg, data, len, 0); + i2c_release(dev->dev); + + if (res == 0) { + if (ENABLE_DEBUG) { + printf("[hmc5883l] %s i2c dev=%d addr=%02x: read following bytes: ", + __func__, dev->dev, HMC5883L_I2C_ADDRESS); + for (unsigned i = 0; i < len; i++) { + printf("%02x ", data[i]); + } + printf("\n"); + } + } + else { + DEBUG_DEV("could not read %d bytes from sensor registers " + "starting at addr %02x, reason %d (%s)", + dev, len, reg, res, strerror(res * -1)); + return HMC5883L_ERROR_I2C; + } + + return res; +} + +static int _reg_write(const hmc5883l_t *dev, uint8_t reg, uint8_t data) +{ + assert(dev != NULL); + + DEBUG_DEV("write register 0x%02x", dev, reg); + + if (ENABLE_DEBUG) { + printf("[hmc5883l] %s i2c dev=%d addr=%02x: write following bytes: ", + __func__, dev->dev, HMC5883L_I2C_ADDRESS); + printf("%02x ", data); + printf("\n"); + } + + if (i2c_acquire(dev->dev)) { + DEBUG_DEV("could not acquire I2C bus", dev); + return HMC5883L_ERROR_I2C; + } + + int res = i2c_write_regs(dev->dev, HMC5883L_I2C_ADDRESS, reg, &data, 1, 0); + i2c_release(dev->dev); + + if (res != 0) { + DEBUG_DEV("could not write to sensor registers " + "starting at addr 0x%02x, reason %d (%s)", + dev, reg, res, strerror(res * -1)); + return HMC5883L_ERROR_I2C; + } + + return res; +} diff --git a/drivers/hmc5883l/hmc5883l_saul.c b/drivers/hmc5883l/hmc5883l_saul.c new file mode 100644 index 0000000000..2cb5b94650 --- /dev/null +++ b/drivers/hmc5883l/hmc5883l_saul.c @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_hmc5883l + * @brief HMC5883L adaption to the RIOT actuator/sensor interface + * @author Gunar Schorcht + * @file + */ + +#include + +#include "saul.h" +#include "hmc5883l.h" + +static int read(const void *dev, phydat_t *res) +{ + hmc5883l_data_t data; + int ret = hmc5883l_read((const hmc5883l_t *)dev, &data); + if (ret < 0) { + return -ECANCELED; + } + res->val[0] = data.x; + res->val[1] = data.y; + res->val[2] = data.z; + res->unit = UNIT_GS; + res->scale = -3; + return 3; +} + +const saul_driver_t hmc5883l_saul_driver = { + .read = read, + .write = saul_notsup, + .type = SAUL_SENSE_MAG, +}; diff --git a/drivers/hmc5883l/include/hmc5883l_params.h b/drivers/hmc5883l/include/hmc5883l_params.h new file mode 100644 index 0000000000..49383baf5c --- /dev/null +++ b/drivers/hmc5883l/include/hmc5883l_params.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_hmc5883l + * @brief Default configuration for the Honeywell HMC5883L 3-axis digital compass + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef HMC5883L_PARAMS_H +#define HMC5883L_PARAMS_H + +#include "board.h" +#include "hmc5883l.h" +#include "saul_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters + * @{ + */ +#ifndef HMC5883L_PARAM_DEV +#define HMC5883L_PARAM_DEV I2C_DEV(0) +#endif +#ifndef HMC5883L_PARAM_DOR +#define HMC5883L_PARAM_DOR (HMC5883L_DOR_15) +#endif +#ifndef HMC5883L_PARAM_MEAS_MODE +#define HMC5883L_PARAM_MEAS_MODE (HMC5883L_MEAS_MODE_NORMAL) +#endif +#ifndef HMC5883L_PARAM_MEAS_AVG +#define HMC5883L_PARAM_MEAS_AVG (HMC5883L_MEAS_AVG_NONE) +#endif +#ifndef HMC5883L_PARAM_OP_MODE +#define HMC5883L_PARAM_OP_MODE (HMC5883L_OP_MODE_CONTINUOUS) +#endif +#ifndef HMC5883L_PARAM_GAIN +#define HMC5883L_PARAM_GAIN (HMC5883L_GAIN_1090) +#endif +#ifndef HMC5883L_PARAM_INT_PIN +#define HMC5883L_PARAM_INT_PIN (GPIO_UNDEF) +#endif + +#ifdef MODULE_HMC5883L_INT +#ifndef HMC5883L_PARAMS +#define HMC5883L_PARAMS { \ + .dev = HMC5883L_PARAM_DEV, \ + .dor = HMC5883L_PARAM_DOR, \ + .gain = HMC5883L_PARAM_GAIN, \ + .int_pin = HMC5883L_PARAM_INT_PIN, \ + .meas_mode = HMC5883L_PARAM_MEAS_MODE, \ + .meas_avg = HMC5883L_PARAM_MEAS_AVG, \ + .op_mode = HMC5883L_PARAM_OP_MODE, \ + } +#endif /* HMC5883L_PARAMS */ +#else /* MODULE_HMC5883L_INT */ +#define HMC5883L_PARAMS { \ + .dev = HMC5883L_PARAM_DEV, \ + .dor = HMC5883L_PARAM_DOR, \ + .gain = HMC5883L_PARAM_GAIN, \ + .meas_mode = HMC5883L_PARAM_MEAS_MODE, \ + .meas_avg = HMC5883L_PARAM_MEAS_AVG, \ + .op_mode = HMC5883L_PARAM_OP_MODE, \ + } +#endif /* MODULE_HMC5883L_INT */ + +#ifndef HMC5883L_SAUL_INFO +#define HMC5883L_SAUL_INFO { .name = "hmc5883l" } +#endif +/**@}*/ + +/** + * @brief Allocate some memory to store the actual configuration + */ +static const hmc5883l_params_t hmc5883l_params[] = +{ + HMC5883L_PARAMS +}; + +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const saul_reg_info_t hmc5883l_saul_info[] = +{ + HMC5883L_SAUL_INFO +}; + +#ifdef __cplusplus +} +#endif + +#endif /* HMC5883L_PARAMS_H */ +/** @} */ diff --git a/drivers/hmc5883l/include/hmc5883l_regs.h b/drivers/hmc5883l/include/hmc5883l_regs.h new file mode 100644 index 0000000000..70d4a3c3a3 --- /dev/null +++ b/drivers/hmc5883l/include/hmc5883l_regs.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_hmc5883l + * @brief Register definitions for the Honeywell HMC5883L 3-axis digital compass + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef HMC5883L_REGS_H +#define HMC5883L_REGS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** HMC5883L I2C address*/ +#define HMC5883L_I2C_ADDRESS (0x1e) + +/** HMC5883L chip id defined in Identification Registers A..C */ +#define HMC5883L_ID { 0x48, 0x34, 0x33 } + +/** HMC5883L chip id length */ +#define HMC5883L_ID_LEN (3) + +/** + * @name Register addresses + * @{ + */ +#define HMC5883L_REG_CFG_A (0x00) +#define HMC5883L_REG_CFG_B (0x01) +#define HMC5883L_REG_MODE (0x02) +#define HMC5883L_REG_OUT_X_MSB (0x03) +#define HMC5883L_REG_OUT_X_LSB (0x04) +#define HMC5883L_REG_OUT_Y_MSB (0x05) +#define HMC5883L_REG_OUT_Y_LSB (0x06) +#define HMC5883L_REG_OUT_Z_MSB (0x07) +#define HMC5883L_REG_OUT_Z_LSB (0x08) +#define HMC5883L_REG_STATUS (0x09) +#define HMC5883L_REG_ID_A (0x0a) +#define HMC5883L_REG_ID_B (0x0b) +#define HMC5883L_REG_ID_C (0x0c) +/** @} */ + +/** + * @name Register structure definitions + * @{ + */ +#define HMC5883L_REG_CFG_A_MA (0x60) /**< HMC5883L_REG_CFG_A<6:5> */ +#define HMC5883L_REG_CFG_A_DO (0x1c) /**< HMC5883L_REG_CFG_A<4:2> */ +#define HMC5883L_REG_CFG_A_MS (0x03) /**< HMC5883L_REG_CFG_A<1:0> */ + +#define HMC5883L_REG_CFG_B_GN (0xe0) /**< HMC5883L_REG_CFG_N<7:5> */ +#define HMC5883L_REG_CFG_B_GN_S (5) /**< HMC5883L_REG_CFG_N<7:5> shift */ + +#define HMC5883L_REG_MODE_HS (0x80) /**< HMC5883L_REG_MODE<7> */ +#define HMC5883L_REG_MODE_MD (0x03) /**< HMC5883L_REG_MODE<1:0> */ + +#define HMC5883L_REG_STATUS_LOCK (0x02) /**< HMC5883L_REG_STATUS<1> */ +#define HMC5883L_REG_STATUS_RDY (0x01) /**< HMC5883L_REG_STATUS<0> */ +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* HMC5883L_REGS_H */ diff --git a/drivers/include/hmc5883l.h b/drivers/include/hmc5883l.h new file mode 100644 index 0000000000..adf5c53172 --- /dev/null +++ b/drivers/include/hmc5883l.h @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_hmc5883l HMC5883L 3-axis digital compass + * @ingroup drivers_sensors + * @ingroup drivers_saul + * @brief Device driver for the Honeywell HMC5883L 3-axis digital compass + * + * The driver implements basic polling mode. The application can use + * different approaches to get new data, either + * + * - using the #hmc5883l_read function at a lower rate than the the DOR, or + * - using the data-ready interrupt (**DRDY**), see #hmc5883l_init_int. + * + * The data-ready interrupt (**DRDY**) is only be available when module + * `hmc5883l_int` is enabled. + * + * This driver provides @ref drivers_saul capabilities. + * + * @{ + * + * @author Gunar Schorcht + * @file + */ + +#ifndef HMC5883L_H +#define HMC5883L_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#include "periph/gpio.h" +#include "periph/i2c.h" + +/** Definition of error codes */ +typedef enum { + HMC5883L_OK = 0, /**< success */ + HMC5883L_ERROR_I2C = -1, /**< any I2C communication error */ + HMC5883L_ERROR_WRONG_ID = -2, /**< wrong id read */ + HMC5883L_ERROR_NO_DATA = -3, /**< no data are available */ + HMC5883L_ERROR_RAW_DATA = -4, /**< reading raw data failed */ + HMC5883L_ERROR_COMMON = -5, /**< common error */ +} hmc5883l_error_codes_t; + +/** + * @brief Data output rates (DOR) + * + * The values correspond to bits <4:2> of the HMC5883L_REG_CFG_A register. + */ +typedef enum { + HMC5883L_DOR_0_75 = 0x00, /**< 0.75 Hz */ + HMC5883L_DOR_1_5 = 0x04, /**< 1.5 Hz */ + HMC5883L_DOR_3 = 0x08, /**< 3 Hz */ + HMC5883L_DOR_7_5 = 0x0c, /**< 7.5 Hz */ + HMC5883L_DOR_15 = 0x10, /**< 15 Hz (default) */ + HMC5883L_DOR_30 = 0x14, /**< 30 Hz */ + HMC5883L_DOR_75 = 0x18, /**< 75 Hz */ +} hmc5883l_dor_t; + +/** + * @brief Measurement modes + * + * The values correspond to bits <1:0> of the HMC5883L_REG_CFG_A register. + */ +typedef enum { + HMC5883L_MEAS_MODE_NORMAL = 0x00, /**< Normal measurement config */ + HMC5883L_MEAS_MODE_BIAS_POS = 0x01, /**< Positive bias config for all axes */ + HMC5883L_MEAS_MODE_BIAS_NEG = 0x02, /**< Negative bias config for all axes */ +} hmc5883l_meas_mode_t; + +/** + * @brief Measurement avaraging (number of samples are averaged for output) + * + * The values correspond to bits <6:5> of the HMC5883L_REG_CFG_A register. + */ +typedef enum { + HMC5883L_MEAS_AVG_NONE = 0x00, /**< No averaging */ + HMC5883L_MEAS_AVG_2 = 0x20, /**< 2 samples are averaged */ + HMC5883L_MEAS_AVG_4 = 0x40, /**< 4 samples are averaged */ + HMC5883L_MEAS_AVG_8 = 0x60, /**< 8 samples are averaged */ +} hmc5883l_meas_avg_t; + +/** + * @brief Operation modes + * + * Values correspond to bits <1:0> of HMC5883L_REG_MODE register + */ +typedef enum { + HMC5883L_OP_MODE_CONTINUOUS = 0x00, /**< Continuous measurement */ + HMC5883L_OP_MODE_SINGLE = 0x01, /**< Single measurement */ + HMC5883L_OP_MODE_IDLE = 0x02, /**< Idle mode */ +} hmc5883l_op_mode_t; + +/** + * @brief Gain (determines the sensitivity and the range) + * + * The values correspond to bits <7:5> of the HMC5883L_REG_CFG_B_GN register. + */ +typedef enum { + HMC5883L_GAIN_1370 = 0x00, /**< Range +-0.88 Gs, Resolution 0.73 mGs/LSB */ + HMC5883L_GAIN_1090 = 0x20, /**< Range +-1.3 Gs, Resolution 0.92 mGs/LSB */ + HMC5883L_GAIN_820 = 0x40, /**< Range +-1.9 Gs, Resolution 1.22 mGs/LSB */ + HMC5883L_GAIN_660 = 0x60, /**< Range +-2.5 Gs, Resolution 1.52 mGs/LSB */ + HMC5883L_GAIN_440 = 0x80, /**< Range +-4.0 Gs, Resolution 2.27 mGs/LSB */ + HMC5883L_GAIN_390 = 0xa0, /**< Range +-4.7 Gs, Resolution 2.56 mGs/LSB */ + HMC5883L_GAIN_330 = 0xc0, /**< Range +-5.6 Gs, Resolution 3.03 mGs/LSB */ + HMC5883L_GAIN_230 = 0xe0, /**< Range +-8.1 Gs, Resolution 4.35 mGs/LSB */ +} hmc5883l_gain_t; + +/** + * @brief Magnetic field values in milli-Gauss (mGs) + */ +typedef struct { + int16_t x; /**< magnetic field x-axis */ + int16_t y; /**< magnetic field y-axis */ + int16_t z; /**< magnetic field y-axis */ +} hmc5883l_data_t; + +/** + * @brief Raw data set as two complements + */ +typedef struct { + int16_t x; /**< magnetic field x-axis as 16 bit two's complements */ + int16_t y; /**< magnetic field y-axis as 16 bit two's complements */ + int16_t z; /**< magnetic field z-axis as 16 bit two's complements */ +} hmc5883l_raw_data_t; + +#if MODULE_HMC5883L_INT || DOXYGEN + +/** + * @brief HMC5883L DRDY interrupt callback function type + * + * Function prototype for the function which is called on DRDY interrupt if + * the interrupt is activated by #hmc5883l_init_int and the interrupt pin is + * defined in device initialization parameters. + * + * @note The @p cb function is called in interrupt context. The application + * should do nothing time consuming and not directly access sensor data. + */ +typedef void (*hmc5883l_drdy_int_cb_t)(void *); + +#endif /* MODULE_HMC5883L_INT || DOXYGEN */ + +/** + * @brief HMC5883L device initialization parameters + */ +typedef struct { + unsigned dev; /**< I2C device */ +#if MODULE_HMC5883L_INT + gpio_t int_pin; /**< DRDY interrupt pin: if #GPIO_UNDEF, interrupts are not used */ +#endif + hmc5883l_meas_mode_t meas_mode; /**< Measurement mode (default #HMC5883L_MEAS_MODE_NORMAL) */ + hmc5883l_meas_avg_t meas_avg; /**< Measurement avaraging (default #HMC5883L_MEAS_AVG_NONE) */ + hmc5883l_dor_t dor; /**< Data output rate (default #HMC5883L_DOR_15) */ + hmc5883l_op_mode_t op_mode; /**< Operation mode (#HMC5883L_OP_MODE_CONTINUOUS) */ + hmc5883l_gain_t gain; /**< Gain (default #HMC5883L_GAIN_1090) */ +} hmc5883l_params_t; + +/** + * @brief HMC5883L sensor device data structure type + */ +typedef struct { + unsigned dev; /**< I2C device */ +#if MODULE_HMC5883L_INT + gpio_t int_pin; /**< DRDY interrupt pin: if #GPIO_UNDEF, interrupts are not used */ +#endif + hmc5883l_op_mode_t op_mode; /**< Operation mode (#HMC5883L_OP_MODE_CONTINUOUS) */ + hmc5883l_gain_t gain; /**< Gain (default #HMC5883L_GAIN_1090) */ +} hmc5883l_t; + +/** + * @brief Initialize the HMC5883L sensor device + * + * This function resets the sensor and initializes the sensor according to + * given initialization parameters. All registers are reset to default values. + * + * @param[in] dev device descriptor of HMC5883L sensor to be initialized + * @param[in] params HMC5883L initialization parameters + * + * @retval HMC5883L_OK on success + * @retval HMC5883L_ERROR_* a negative error code on error, + * see #hmc5883l_error_codes_t + */ +int hmc5883l_init(hmc5883l_t *dev, const hmc5883l_params_t *params); + +#if MODULE_HMC5883L_INT || DOXYGEN + +/** + * @brief Initialize and activate the DRDY interrupt of HMC5883L sensor device + * + * This function activates the DRDY interrupt and initializes the pin defined + * as the interrupt pin in the initialization parameters of the device. The + * @p cb parameter specifies the function, along with an optional argument + * @p arg, which is called when a DRDY interrupt is triggered. + * + * @warning The given callback function @p cb is executed in interrupt context. + * Make sure not to call any driver API function in that context. + * @note This function is only available when module `hmc5883l_int` is enabled. + * + * @param[in] dev device descriptor of HMC5883L sensor + * @param[in] cb function called when DRDY interrupt is triggered + * @param[in] arg argument for the callback function + * + * @retval HMC5883L_OK on success + * @retval HMC5883L_ERROR_* a negative error code on error, + * see #hmc5883l_error_codes_t + */ +int hmc5883l_init_int(hmc5883l_t *dev, hmc5883l_drdy_int_cb_t cb, void *arg); + +#endif /* MODULE_HMC5883L_INT || DOXYGEN */ + +/** + * @brief Data-ready status function + * + * The function checks the status register and returns + * + * @param[in] dev device descriptor of HMC5883L sensor + * + * @retval HMC5883L_OK new data available + * @retval HMC5883L_ERROR_NO_DATA no new data available + * @retval HMC5883L_ERROR_* negative error code, + * see #hmc5883l_error_codes_t + */ +int hmc5883l_data_ready(const hmc5883l_t *dev); + +/** + * @brief Read one sample of magnetic field values in milli-Gauss (mGs) + * + * Raw magnetometer data are read from the sensor and normalized them + * with respect to configured gain. Magnetic field values are given in + * milli-Gauss (mGs) to preserve full resolution: + * + * @param[in] dev device descriptor of HMC5883L sensor + * @param[out] data result vector in milli-Gauss (mGs) per axis + * + * @retval HMC5883L_OK on success + * @retval HMC5883L_ERROR_* a negative error code on error, + * see #hmc5883l_error_codes_t + */ +int hmc5883l_read(const hmc5883l_t *dev, hmc5883l_data_t *data); + +/** + * @brief Read one sample of raw sensor data as 16 bit two's complements + * + * @param[in] dev device descriptor of HMC5883L sensor + * @param raw raw data vector + * + * @retval HMC5883L_OK on success + * @retval HMC5883L_ERROR_* a negative error code on error, + * see #hmc5883l_error_codes_t + */ +int hmc5883l_read_raw(const hmc5883l_t *dev, hmc5883l_raw_data_t *raw); + +/** + * @brief Power down the sensor + * + * Changes the sensor operation mode to #HMC5883L_OP_MODE_IDLE in which + * almost all internal blocks are switched off. I2C interface is + * still active. The content of the configuration registers is preserved. + * + * @param[in] dev Device descriptor of HMC5883L device to read from + * + * @retval HMC5883L_OK on success + * @retval HMC5883L_ERROR_* negative error code, see #hmc5883l_error_codes_t + */ +int hmc5883l_power_down(hmc5883l_t *dev); + +/** + * @brief Power up the sensor + * + * Swichtes the sensor back into the last active operation mode. + * + * @param[in] dev Device descriptor of HMC5883L device to read from + * + * @retval HMC5883L_OK on success + * @retval HMC5883L_ERROR_* negative error code, see #hmc5883l_error_codes_t + */ +int hmc5883l_power_up(hmc5883l_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* HMC5883L_H */ +/** @} */ diff --git a/drivers/saul/init_devs/auto_init_hmc5883l.c b/drivers/saul/init_devs/auto_init_hmc5883l.c new file mode 100644 index 0000000000..fb5dd93614 --- /dev/null +++ b/drivers/saul/init_devs/auto_init_hmc5883l.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/* + * @ingroup sys_auto_init_saul + * @brief Auto initialization of HMC5883L 3-axis digital compass + * @author Gunar Schorcht + * @file + */ + +#ifdef MODULE_HMC5883L + +#include "assert.h" +#include "log.h" +#include "saul_reg.h" + +#include "hmc5883l.h" +#include "hmc5883l_params.h" + +/** + * @brief Define the number of configured sensors + */ +#define HMC5883L_NUM ARRAY_SIZE(hmc5883l_params) + +/** + * @brief Allocate memory for the device descriptors + */ +static hmc5883l_t hmc5883l_devs[HMC5883L_NUM]; + +/** + * @brief Memory for the SAUL registry entries + */ +static saul_reg_t saul_entries[HMC5883L_NUM]; + +/** + * @brief Define the number of saul info + */ +#define HMC5883L_INFO_NUM ARRAY_SIZE(hmc5883l_saul_info) + +/** + * @brief Reference the driver struct + */ +extern saul_driver_t hmc5883l_saul_driver; + + +void auto_init_hmc5883l(void) +{ + assert(HMC5883L_NUM == HMC5883L_INFO_NUM); + + for (unsigned int i = 0; i < HMC5883L_NUM; i++) { + LOG_DEBUG("[auto_init_saul] initializing hmc5883l #%u\n", i); + + if (hmc5883l_init(&hmc5883l_devs[i], &hmc5883l_params[i]) < 0) { + LOG_ERROR("[auto_init_saul] error initializing hmc5883l #%u\n", i); + continue; + } + + saul_entries[i].dev = &(hmc5883l_devs[i]); + saul_entries[i].name = hmc5883l_saul_info[i].name; + saul_entries[i].driver = &hmc5883l_saul_driver; + saul_reg_add(&(saul_entries[i])); + } +} + +#else +typedef int dont_be_pedantic; +#endif /* MODULE_HMC5883L */ diff --git a/drivers/saul/init_devs/init.c b/drivers/saul/init_devs/init.c index 9034d63a30..f328e34fcf 100644 --- a/drivers/saul/init_devs/init.c +++ b/drivers/saul/init_devs/init.c @@ -99,6 +99,10 @@ void saul_init_devs(void) extern void auto_init_grove_ledbar(void); auto_init_grove_ledbar(); } + if (IS_USED(MODULE_HMC5883L)) { + extern void auto_init_hmc5883l(void); + auto_init_hmc5883l(); + } if (IS_USED(MODULE_HDC1000)) { extern void auto_init_hdc1000(void); auto_init_hdc1000(); diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 0abd042378..bd899cf7ed 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -150,6 +150,9 @@ PSEUDOMODULES += cc1100 PSEUDOMODULES += cc1100e PSEUDOMODULES += cc1101 +# interrupt variant of the HMC5883L driver +PSEUDOMODULES += hmc5883l_int + # interrupt variant of the ITG320X driver as pseudo module PSEUDOMODULES += itg320x_int diff --git a/tests/driver_hmc5883l/Makefile b/tests/driver_hmc5883l/Makefile new file mode 100644 index 0000000000..37be2a03d7 --- /dev/null +++ b/tests/driver_hmc5883l/Makefile @@ -0,0 +1,6 @@ +include ../Makefile.tests_common + +USEMODULE += hmc5883l +USEMODULE += xtimer + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_hmc5883l/README.md b/tests/driver_hmc5883l/README.md new file mode 100644 index 0000000000..4695879511 --- /dev/null +++ b/tests/driver_hmc5883l/README.md @@ -0,0 +1,32 @@ +# About + +Test application for the HMC5883L 3-axis digital compass + +# Usage + +The test application demonstrates the use of the HMC5883L. It uses the +default configuration parameters. + +- Continuous measurement at a Data Output Rate (DOR) of 15 Hz +- Normal mode, no biasing +- Gain 1090 LSB/Gs +- No averaging of data samples + +The application can use different approaches to get new data: + +- using the #hmc5883l_read function at a lower rate than the the DOR +- using the data-ready interrupt (**DRDY**), see #hmc5883l_init_int. + +To use the data-ready interrupt (**DRDY**), the application has to enable +module `hmc5883l_int` and has to configure the GPIO to which the +interrupt signal is connected. This is done by overrding the default +configuration parameter `HMC5883L_PARAM_INT_PIN` if necessary, for example: + +``` +USEMODULE=hmc5883l_int CFLAGS='-DHMC5883L_PARAM_INT_PIN=GPIO_PIN\(0,12\)' \ +make flash -C tests/driver_hmc5883l BOARD=... +``` + +The heading angle is calculated for a magnetic declination in radians defined +by `HMC5883L_MAG_DECL`. This depends on the current location. The value for +the current location can be determined at http://www.magnetic-declination.com/. diff --git a/tests/driver_hmc5883l/main.c b/tests/driver_hmc5883l/main.c new file mode 100644 index 0000000000..c2cf7f9474 --- /dev/null +++ b/tests/driver_hmc5883l/main.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @brief Test application for the HMC5883L 3-axis digital compass + * @author Gunar Schorcht + * @file + * + * The test application demonstrates the use of the HMC5883L. It uses the + * default configuration parameters. + * + * - Continuous measurement at a Data Output Rate (DOR) of 15 Hz + * - Normal mode, no biasing + * - Gain 1090 LSB/Gs + * - No averaging of data samples + * + * The application can use different approaches to get new data: + * + * - using the #hmc5883l_read function at a lower rate than the the DOR + * - using the data-ready interrupt (**DRDY**), see #hmc5883l_init_int. + * + * To use the data-ready interrupt (**DRDY), the application has to enable + * module `hmc5883l_int` and has to configure the GPIO to which the + * interrupt signal is connected. This is done by overrding the default + * configuration parameter `HMC5883L_PARAM_INT_PIN` if necessary, for example: + * + * ``` + * USEMODULE=hmc5883l_int CFLAGS='-DHMC5883L_PARAM_INT_PIN=GPIO_PIN\(0,12\)' \ + * make flash -C tests/driver_hmc5883l BOARD=... + * ``` + * + * The heading angle is calculated for a magnetic declination in radians defined + * by `HMC5883L_MAG_DECL`. This depends on the current location. The value for + * the current location can be determined at http://www.magnetic-declination.com/. + */ + +#include +#include + +#include "thread.h" +#include "xtimer.h" + +#include "hmc5883l.h" +#include "hmc5883l_params.h" + +/* + * Magnetic declination in radians according to the real geo location, see: + * http://www.magnetic-declination.com/ + */ +#ifndef HMC5883L_MAG_DECL +#define HMC5883L_MAG_DECL (0.0573F) +#endif + +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + +#define HMC5883L_SLEEP (100 * US_PER_MS) + +kernel_pid_t p_main; + +#if MODULE_HMC5883L_INT +static void hmc5883l_isr_data_ready (void *arg) +{ + (void)arg; + /* send a message to trigger main thread to handle the interrupt */ + msg_t msg; + msg_send(&msg, p_main); +} +#endif + +int main(void) +{ + hmc5883l_t dev; + + p_main = sched_active_pid; + + puts("HMC5883L magnetometer driver test application\n"); + puts("Initializing HMC5883L sensor"); + + /* initialize the sensor with default configuration parameters */ + if (hmc5883l_init(&dev, &hmc5883l_params[0]) == HMC5883L_OK) { + puts("[OK]\n"); + } + else { + puts("[Failed]"); + return 1; + } + + #if MODULE_HMC5883L_INT + /* init INT2/DRDY signal pin and enable the interrupt */ + hmc5883l_init_int(&dev, hmc5883l_isr_data_ready, 0); + #endif /* MODULE_HMC5883L_INT */ + + while (1) { + #if MODULE_HMC5883L_INT + /* wait for data ready interrupt */ + msg_t msg; + msg_receive(&msg); + #else + /* wait longer than period of HMC5883L DOR */ + xtimer_usleep(HMC5883L_SLEEP); + #endif + + /* read data in any case */ + hmc5883l_data_t data; + if (hmc5883l_read(&dev, &data) == HMC5883L_OK) { + /* print xyz data */ + printf("magn [mGs] x = %" PRIi16 ", y = %" PRIi16 ", z = %" PRIi16 "\n", + data.x, data.y, data.z); + + /* compute and print heading for the given magnetic declination in rad */ + float head = atan2(data.y, data.x) + HMC5883L_MAG_DECL; + if (head < 0) { + head += 2 * M_PI; + } + if (head > 2 * M_PI) { + head -= 2 * M_PI; + } + unsigned deg = head * 180 / M_PI; + printf("head [deg] %u\n", deg); + } + } + + return 0; +}