diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 0841866c9..90be957ef 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -105,3 +105,4 @@ Index | Define | Driver | Device | Address(es) | Description 68 | USE_HYT | xsns_97 | HYTxxx | 0x28 | Temperature and Humidity sensor 69 | USE_SGP40 | xsns_98 | SGP40 | 0x59 | Gas (TVOC) and air quality 70 | USE_LUXV30B | xsns_99 | LUXV30B | 0x4A | DFRobot SEN0390 V30B lux sensor + 71 | USE_QMC5883L | xsns_33 | QMC5883L | 0x0D | Magnetic Field Sensor diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index c303d49b3..380913bfb 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -233,6 +233,11 @@ #define D_JSON_SIGNALSTRENGTH "SignalStrength" #define D_JSON_CHIPTEMPERATURE "ChipTemperature" #define D_JSON_RAW "Raw" +#define D_JSON_MX "Compass X-Axis" +#define D_JSON_MY "Compass Y-Axis" +#define D_JSON_MZ "Compass Z-Axis" +#define D_JSON_HEADING "Compass Heading" +#define D_JSON_MAGNETICFLD "Magnetic Induction" #define D_RSLT_ENERGY "ENERGY" #define D_RSLT_HASS_STATE "HASS_STATE" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index e9a9f6609..7f07b5a08 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -564,6 +564,13 @@ #define D_GY_AXIS "Gyroskop Y-Achse" #define D_GZ_AXIS "Gyroskop Z-Achse" +// xsns_33_QMC5883L.ino +#define D_MX "Kompass X-Achse" +#define D_MY "Kompass Y-Achse" +#define D_MZ "Kompass Z-Achse" +#define D_HG "Kompass Richtung" +#define D_MAGNETICFLD "Magnet Feld Stärke" + // xsns_34_hx711.ino #define D_HX_CAL_REMOVE "Wägegut entfernen" #define D_HX_CAL_REFERENCE "Referenzgewicht auflegen" @@ -911,6 +918,7 @@ #define D_UNIT_MICROMETER "µm" #define D_UNIT_MICROSECOND "µs" #define D_UNIT_MICROSIEMENS_PER_CM "µS/cm" +#define D_UNIT_MICROTESLA "uT" #define D_UNIT_MILLIAMPERE "mA" #define D_UNIT_MILLILITERS "ml" #define D_UNIT_MILLIMETER "mm" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index 60b9e8942..90b9b6ade 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -564,6 +564,13 @@ #define D_GY_AXIS "Gyro Y-Axis" #define D_GZ_AXIS "Gyro Z-Axis" +// xsns_33_QMC5883L.ino +#define D_MX "Compass X-Axis" +#define D_MY "Compass Y-Axis" +#define D_MZ "Compass Z-Axis" +#define D_HG "Compass Heading" +#define D_MAGNETICFLD "Magnetic Field Strength" + // xsns_34_hx711.ino #define D_HX_CAL_REMOVE "Remove weight" #define D_HX_CAL_REFERENCE "Load reference weight" @@ -911,6 +918,7 @@ #define D_UNIT_MICROMETER "µm" #define D_UNIT_MICROSECOND "µs" #define D_UNIT_MICROSIEMENS_PER_CM "µS/cm" +#define D_UNIT_MICROTESLA "uT" #define D_UNIT_MILLIAMPERE "mA" #define D_UNIT_MILLILITERS "ml" #define D_UNIT_MILLIMETER "mm" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index bac7413c2..a31228cf0 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -715,6 +715,8 @@ // Reference: https://cdn-learn.adafruit.com/downloads/pdf/adafruit-led-backpack.pdf // #define SEVENSEG_ADDRESS1 0x70 // No longer used. Use MTX_ADDRESS1 - MTX_ADDRESS8 instead to specify I2C address of sevenseg displays // #define USE_DISPLAY_SH1106 // [DisplayModel 7] [I2cDriver6] Enable SH1106 Oled 128x64 display (I2C addresses 0x3C and 0x3D) + #define USE_ // have a compass sensor + #define USE_QMC5883L_Temp 22 // compass sensor temperatur are not calibrated (only relativ measurement) and need an absolute ground value in °C (see datasheet) #endif // USE_I2C diff --git a/tasmota/tasmota_xsns_sensor/xsns_33_qmc5883l.ino b/tasmota/tasmota_xsns_sensor/xsns_33_qmc5883l.ino new file mode 100644 index 000000000..be66649e3 --- /dev/null +++ b/tasmota/tasmota_xsns_sensor/xsns_33_qmc5883l.ino @@ -0,0 +1,394 @@ +/* + xsns_99_qmc5883l.ino - QMC5883L 3-Axis Digital Compass sensor support for Tasmota + + Copyright (C) 2022 Helge Scheunemann + + This program 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 3 of the License, or + (at your option) any later version. + + This program 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 this program. If not, see . + + DATASHEET + + The QMC5883L is a multi-chip three-axis magnetic sensor. This + surface -mount, small sized chip has integrated magnetic sensors with + signal condition ASIC, targeted for high precision applications such as + compassing, navigation and gaming in drone, robot, mobile and + personal hand-held devices. + The QMC5883L is based on our state-of-the-art, high resolution, + magneto-resistive technology licensed from Honeywell AMR technology. + Along with custom-designed 16-bit ADC ASIC, it offers the advantages of + low noise, high accuracy, low power consumption, offset cancellation and + temperature compensation. QMC5883L enables 1° to 2° compass + heading accuracy. The I²C serial bus allows for easy interface + + 9.1 Register Map + The table below provides a list of the 8-bit registers embedded in the device and their respective function and + addresses + + Table 13. Register Map + Addr. 7 6 5 4 3 2 1 0 Access + 00H Data Output X LSB Register XOUT[7:0] Read only + 01H Data Output X MSB Register XOUT[15:8] Read only + 02H Data Output Y LSB Register YOUT[7:0] Read only + 03H Data Output Y MSB Register YOUT[15:8] Read only + 04H Data Output Z LSB Register ZOUT[7:0] Read only + 05H Data Output Z MSB Register ZOUT[15:8] Read only + 06H DOR OVL DRDY Read only + 07H TOUT[7:0] Read only + 08H TOUT[15:8] Read only + 09H OSR[1:0] RNG[1:0] ODR[1:0] MODE[1:0] Read/Write + 0AH SOFT_RST ROL_P NT INT_E NB R/W, Read only on blanks + 0BH SET/RESET Period FBR [7:0] Read/Write + 0CH Reserved Read only + 0DH Reserved Read only + + 9.2 Register Definition + 9.2.1 Output Data Register + Registers 00H ~ 05H store the measurement data from each axis magnetic sensor in continuous-measurement. + In the continuous measurement mode, the output data is refreshed periodically based on the data update rate + ODR setup in control registers 1. The data stays the same, regardless of reading status through I2C, until new + data replaces them. Each axis has 16 bit data width in 2’s complement, i.e., MSB of 01H/03H/05H indicates the + sign of each axis. The output data of each channel saturates at -32768 and 32767. + + Table 14. Output Data Register + Addr. 7 6 5 4 3 2 1 0 + 00H Data Output X LSB Register XOUT[7:0] + 01H Data Output X MSB Register XOUT[15:8] + 02H Data Output Y LSB Register YOUT[7:0] + 03H Data Output Y MSB Register YOUT[15:8] + 04H Data Output Z LSB Register ZOUT[7:0] + 05H Data Output Z MSB Register ZOUT[15:8] + + 9.2.2 Status Register + There are two status registers located in address 06H and 0CH. + Register 06H has three bits indicating for status flags, the rest are reserved for factory use. The status registers + are read only bits. + + Table 15. Status Register 1 + Addr. 7 6 5 4 3 2 1 0 + 06H DOR OVL DRDY + + Data Ready Register (DRDY), it is set when all three axis data is ready, and loaded to the output data registers in + the continuous measurement mode. It is reset to “0” by reading any data register (00H~05H) through I2C + commends + DRDY: “0”: no new data, “1”: new data is ready + Overflow flag (OVL) is set to “1” if any data of three axis magnetic sensor channels is out of range. The output + data of each axis saturates at -32768 and 32767, if any of the axis exceeds this range, OVL flag is set to “1”. This + flag is reset to “0” if next measurement goes back to the range of (-32768, 32767), otherwise, it keeps as “1”. + OVL: “0”: normal, “1”: data overflow + Data Skip (DOR) bit is set to “1” if all the channels of output data registers are skipped in reading in the + continuous-measurement mode. It is reset to “0” by reading any data register (00H~05H) through I2C + DOR: “0”: normal, “1”: data skipped for reading + + 9.2.3 Temperature Data Registers + Registers 07H-08H store temperature sensor output data. 16 bits temperature sensor output is in 2’s complement. + Temperature sensor gain is factory-calibrated, but its offset has not been compensated, only relative temperature + value is accurate. The temperature coefficient is about 100 LSB/℃ + + Table 17. Temperature Sensor Output + Addr. 7 6 5 4 3 2 1 0 + 07H TOUT[7:0] + 08H TOUT[15:8] + + 9.2.4 Control Registers + Two 8-bits registers are used to control the device configurations. + Control register 1 is located in address 09H, it sets the operational modes (MODE). output data update rate + (ODR), magnetic field measurement range or sensitivity of the sensors (RNG) and over sampling rate (OSR). + Control register 2 is located in address 0AH. It controls Interrupt Pin enabling (INT_ENB), Point roll over function + enabling(POL_PNT) and soft reset (SOFT_RST). + Two bits of MODE registers can transfer mode of operations in the device, the two modes are Standby, and + Continuous measurements. The default mode after Power-on-Reset (POR) is standby. There is no any restriction + in the transferring between the modes. + Output data rate is controlled by ODR registers. Four data update frequencies can be selected: 10Hz, 50Hz, + 100Hz and 200Hz. For most of compassing applications, we recommend 10 Hz for low power consumption. For + gaming, the high update rate such as 100Hz or 200Hz can be used. + Field ranges of the magnetic sensor can be selected through the register RNG. The full scale field range is + determined by the application environments. For magnetic clear environment, low field range such as +/- 2gauss + can be used. The field range goes hand in hand with the sensitivity of the magnetic sensor. The lowest field range + has the highest sensitivity, therefore, higher resolution. + Over sample Rate (OSR) registers are used to control bandwidth of an internal digital filter. Larger OSR value + leads to smaller filter bandwidth, less in-band noise and higher power consumption. It could be used to reach a + good balance between noise and power. Four over sample ratio can be selected, 64, 128, 256 or 512. + + Table 18. Control Register 1 + Addr 7 6 5 4 3 2 1 0 + 09H OSR[1:0] RNG[1:0] ODR[1:0] MODE[1:0] + + Reg. Definition 00 01 10 11 + Mode Mode Control Standby Continuous Reserve Reserve + ODR Output Data Rate 10Hz 50Hz 100Hz 200Hz + RNG Full Scale 2G 8G Reserve Reserve + OSR Over Sample Ratio 512 256 128 64 + + Interrupt enabling is controlled by register INT_ENB in control register 2. Once the interrupt is enabled, it will flag + when new data is in Data Output Registers. + INT_ENB: “0”: enable interrupt PIN, “1”: disable interrupt PIN + Pointer roll-over function is controlled by ROL_PNT register. When the point roll-over function is enabled, the I2C + data pointer automatically rolls between 00H ~ 06H, if I2C read begins at any address among 00H~06H. + ROL_PNT: “0”: Normal, “1”: Enable pointer roll-over function + Soft Reset can be done by changing the register SOFT_RST to set. Soft reset can be invoked at any time of any + mode. For example, if soft reset occurs at the middle of continuous mode reading, QMC5883L immediately + switches to standby mode due to mode register is reset to “00” in default. + SOFT_RST: “0”: Normal“1”: Soft reset, restore default value of all registers. + Table 19. Control Register 2 + Addr. 7 6 5 4 3 2 1 0 + 0AH SOFT_RST ROL_PNT INT_ENB + + 9.2.5 SET/RESET Period Register + SET/RESET Period is controlled by FBR [7:0], it is recommended that the register 0BH is written by 0x01. + + Table 20. SET/RESET Period Register + Addr. 7 6 5 4 3 2 1 0 + 0BH SET/RESET Perio [7:0] + +*/ + +#ifdef USE_I2C +#ifdef USE_QMC5883L + +/*********************************************************************************************\ + * QMC5883L is 3-Axis Digital Compass sensor + * + * Source: Helge Scheunemann + * + * I2C Address: 0x0D +\*********************************************************************************************/ + +// Define driver ID +#define XSNS_33 33 +#define XI2C_71 71 // See I2CDEVICES.md + +/* The default I2C address of this chip */ +#define QMC5883L_ADDR 0x0D + +/* Register numbers */ +#define QMC5883L_X_LSB 0x00 +#define QMC5883L_X_MSB 0x01 +#define QMC5883L_Y_LSB 0x02 +#define QMC5883L_Y_MSB 0x03 +#define QMC5883L_Z_LSB 0x04 +#define QMC5883L_Z_MSB 0x05 +#define QMC5883L_STATUS 0x06 +#define QMC5883L_TEMP_LSB 0x07 +#define QMC5883L_TEMP_MSB 0x08 +#define QMC5883L_CONFIG 0x09 +#define QMC5883L_CONFIG2 0x0a +#define QMC5883L_RESET 0x0b +#define QMC5883L_RESERVED 0x0c +#define QMC5883L_CHIP_ID 0x0d + +/* Bit values for the STATUS register */ +#define QMC5883L_STATUS_DRDY 1 +#define QMC5883L_STATUS_OVL 2 +#define QMC5883L_STATUS_DOR 4 + +/* Oversampling values for the CONFIG register */ +#define QMC5883L_CONFIG_OS512 0b00000000 +#define QMC5883L_CONFIG_OS256 0b01000000 +#define QMC5883L_CONFIG_OS128 0b10000000 +#define QMC5883L_CONFIG_OS64 0b11000000 + +/* Range values for the CONFIG register */ +#define QMC5883L_CONFIG_2GAUSS 0b00000000 +#define QMC5883L_CONFIG_8GAUSS 0b00010000 + +/* Rate values for the CONFIG register */ +#define QMC5883L_CONFIG_10HZ 0b00000000 +#define QMC5883L_CONFIG_50HZ 0b00000100 +#define QMC5883L_CONFIG_100HZ 0b00001000 +#define QMC5883L_CONFIG_200HZ 0b00001100 + +/* Mode values for the CONFIG register */ +#define QMC5883L_CONFIG_STANDBY 0b00000000 +#define QMC5883L_CONFIG_CONT 0b00000001 + +/* Mode values for the CONFIG2 register */ +#define QMC5883L_CONFIG2_RESET 0b10000000 + +/* Apparently M_PI isn't available in all environments. */ +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 +#endif + +// data field +struct { +int16_t MX = 0, MY = 0, MZ = 0, HG = 0; +int16_t xhigh = 0, yhigh = 0, xlow = 0, ylow = 0; +int16_t temp = 0; +uint16_t scalar = 0; +uint8_t status; +bool ready = false; +uint8_t i2c_address = QMC5883L_ADDR; +} QMC5883L; + +void writeRegister(uint8_t reg, uint8_t val) +{ + Wire.beginTransmission(QMC5883L.i2c_address); // start talking + Wire.write(reg); + Wire.write(val); + Wire.endTransmission(); +} + +int readRegister(uint8_t reg, uint8_t count) +{ + Wire.beginTransmission(QMC5883L.i2c_address); + Wire.write(reg); + Wire.endTransmission(); + + Wire.requestFrom(QMC5883L.i2c_address, count); + int n = Wire.available(); + if (n != count) return 0; + return n; +} + +// Initialise the device +void QMC5883L_Init() +{ + if (!I2cSetDevice(QMC5883L.i2c_address)) { return; } + I2cSetActiveFound(QMC5883L.i2c_address, "QMC5883L"); + // reset QMC5883L + writeRegister(QMC5883L_CONFIG2,QMC5883L_CONFIG2_RESET); // Software Reset + writeRegister(QMC5883L_RESET, 0x01); + // write config + writeRegister(QMC5883L_CONFIG, QMC5883L_CONFIG_OS256 | QMC5883L_CONFIG_8GAUSS | QMC5883L_CONFIG_10HZ | QMC5883L_CONFIG_CONT); + QMC5883L.ready = true; +} + +//Read the magnetic data +void QMC5883L_read_data(void) +{ + if(!QMC5883L.ready) return; + + // check if chip is ready to provice data + if (!readRegister(QMC5883L_STATUS, 1)) return; // read error + if (!(Wire.read() & QMC5883L_STATUS_DRDY)) return; // chip not yet ready, next round try again + + // QMC5883 reading data + if (readRegister(QMC5883L_X_LSB, 6) != 6) return; // read error, select LSB register + QMC5883L.MX = Wire.read() | (Wire.read() << 8); + QMC5883L.MY = Wire.read() | (Wire.read() << 8); + QMC5883L.MZ = Wire.read() | (Wire.read() << 8); + + int16_t x = QMC5883L.MX; + int16_t y = QMC5883L.MY; + + // calculate azimut, heading + if(x < QMC5883L.xlow) QMC5883L.xlow = x; + if(x > QMC5883L.xhigh) QMC5883L.xhigh = x; + if(y < QMC5883L.ylow) QMC5883L.ylow = y; + if(y > QMC5883L.yhigh) QMC5883L.yhigh = y; + + /* Bail out if not enough data is available. */ + if( QMC5883L.xlow == QMC5883L.xhigh || QMC5883L.ylow == QMC5883L.yhigh ) return; + /* Recenter the measurement by subtracting the average */ + x -= (QMC5883L.xhigh + QMC5883L.xlow) / 2; + y -= (QMC5883L.yhigh + QMC5883L.ylow) / 2; + /* Rescale the measurement to the range observed. */ + float fx = (float) x / (QMC5883L.xhigh - QMC5883L.xlow); + float fy = (float) y / (QMC5883L.yhigh - QMC5883L.ylow); + + x = -atan2(fy, fx) * 180.0 / M_PI; + x += 180; // no negative numbers + QMC5883L.HG = x; + + // calculate scalar magnetic induction + QMC5883L.scalar = sqrt((QMC5883L.MX * QMC5883L.MX) + (QMC5883L.MY * QMC5883L.MY) + (QMC5883L.MZ * QMC5883L.MZ)); + + // get temperature + if (readRegister(QMC5883L_TEMP_LSB, 2) != 2) return; // read error + int16_t t = 0; + t = Wire.read() | (Wire.read() << 8); + QMC5883L.temp = (t / 100) + USE_QMC5883L_Temp; +} + +/*********************************************************************************************\ + * Presentation +\*********************************************************************************************/ + +#ifdef USE_WEBSERVER +const char HTTP_SNS_QMC5883L[] PROGMEM = + "{s}QMC5883L " D_MX "{m}%d " D_UNIT_MICROTESLA "{e}" // {s} = , {m} = , {e} = + "{s}QMC5883L " D_MY "{m}%d " D_UNIT_MICROTESLA "{e}" // {s} = , {m} = , {e} = + "{s}QMC5883L " D_MZ "{m}%d " D_UNIT_MICROTESLA "{e}" // {s} = , {m} = , {e} = + "{s}QMC5883L " D_MAGNETICFLD "{m}%d " D_UNIT_MICROTESLA "{e}" // {s} = , {m} = , {e} = + "{s}QMC5883L " D_HG "{m}%d " D_UNIT_DEGREE "{e}" // {s} = , {m} = , {e} = + "{s}QMC5883L " D_TEMPERATURE "{m}%d " D_UNIT_DEGREE D_UNIT_CELSIUS "{e}"; // {s} = , {m} = , {e} = + +const char HTTP_SNS_QMC5883L_ERROR[] PROGMEM = + "{s}QMC5883L {m} %s {e}"; +#endif + + +void QMC5883L_Show(uint8_t json) +{ + if (json) + { + if (!QMC5883L.ready) + { + AddLog(LOG_LEVEL_INFO, PSTR("QMC5883L: " D_ERROR " %x" ), QMC5883L.status); + return; + } + else + { + ResponseAppend_P(PSTR(",\"QMC5883L\":{\"" D_JSON_MX "\":%d,\"" D_JSON_MY "\":%d,\"" D_JSON_MZ "\":%d,\"" D_JSON_MAGNETICFLD "\":%u,\"" D_JSON_HEADING "\":%d,\"" D_JSON_TEMPERATURE "\":%d}"), QMC5883L.MX, QMC5883L.MY, QMC5883L.MZ, QMC5883L.scalar, QMC5883L.HG, QMC5883L.temp); + } + } +#ifdef USE_WEBSERVER + else + { + switch(QMC5883L.ready) + { + case true: + WSContentSend_PD(HTTP_SNS_QMC5883L, QMC5883L.MX, QMC5883L.MY, QMC5883L.MZ, QMC5883L.scalar, QMC5883L.HG, QMC5883L.temp); + break; + case false: + WSContentSend_PD(HTTP_SNS_QMC5883L_ERROR, D_START); + break; + default: + WSContentSend_PD(HTTP_SNS_QMC5883L_ERROR, D_ERROR); + } + } +#endif +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns99(byte function) +{ + if (!I2cEnabled(XI2C_69)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + QMC5883L_Init(); + } + else if (QMC5883L.ready) { + switch (function) { + case FUNC_JSON_APPEND: + QMC5883L_Show(1); + break; + case FUNC_EVERY_SECOND: + QMC5883L_read_data(); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + QMC5883L_Show(0); + break; +#endif // USE_WEBSERVER + } + } + return result; +} +#endif // USE_QMC5883L +#endif // USE_I2C \ No newline at end of file