/* xsns_30_mpr121.ino - MPR121 support for Tasmota Copyright (C) 2020 Rene 'Renne' Bartsch and Theo Arends 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 . */ /** * * @file xsns_30_mpr121.ino * * @package Tasmota * @subpackage Sensors * @name MPR121 * * @description Driver for up to 4x Freescale MPR121 Proximity Capacitive Touch Sensor Controllers (Only touch buttons). * * @author Rene 'Renne' Bartsch, B.Sc. Informatics, * @copyright Rene 'Renne' Bartsch 2018 * @date $Date$ * @version $Id$ * * @link https://github.com/arendst/Tasmota/wiki/MPR121 \endlink * @link https://www.sparkfun.com/datasheets/Components/MPR121.pdf \endlink * @link http://cache.freescale.com/files/sensors/doc/app_note/AN3891.pdf \endlink * @link http://cache.freescale.com/files/sensors/doc/app_note/AN3892.pdf \endlink * @link http://cache.freescale.com/files/sensors/doc/app_note/AN3893.pdf \endlink * @link http://cache.freescale.com/files/sensors/doc/app_note/AN3894.pdf \endlink * @link http://cache.freescale.com/files/sensors/doc/app_note/AN3895.pdf \endlink * * @license GNU GPL v.3 */ #ifdef USE_I2C #ifdef USE_MPR121 /** * @ingroup group1 * Assign Tasmota sensor model ID */ #define XSNS_30 30 #define XI2C_23 23 // See I2CDEVICES.md /** @defgroup group1 MPR121 * MPR121 preprocessor directives * @{ */ /** Electrodes Status Register. */ #define MPR121_ELEX_REG 0x00 /** ELEC0-11 Maximum Half Delta Rising Register. */ #define MPR121_MHDR_REG 0x2B /** ELEC0-11 Maximum Half Delta Rising Value. */ #define MPR121_MHDR_VAL 0x01 /** ELEC0-11 Noise Half Delta Falling Register. */ #define MPR121_NHDR_REG 0x2C /** ELEC0-11 Noise Half Delta Falling Value. */ #define MPR121_NHDR_VAL 0x01 /** ELEC0-11 Noise Count Limit Rising Register. */ #define MPR121_NCLR_REG 0x2D /** ELEC0-11 Noise Count Limit Rising Value. */ #define MPR121_NCLR_VAL 0x0E /** ELEC0-11 Maximum Half Delta Falling Register. */ #define MPR121_MHDF_REG 0x2F /** ELEC0-11 Maximum Half Delta Falling Value. */ #define MPR121_MHDF_VAL 0x01 /** ELEC0-11 Noise Half Delta Falling Register. */ #define MPR121_NHDF_REG 0x30 /** ELEC0-11 Noise Half Delta Falling Value. */ #define MPR121_NHDF_VAL 0x05 /** ELEC0-11 Noise Count Limit Falling Register. */ #define MPR121_NCLF_REG 0x31 /** ELEC0-11 Noise Count Limit Falling Value. */ #define MPR121_NCLF_VAL 0x01 /** Proximity Maximum Half Delta Rising Register. */ #define MPR121_MHDPROXR_REG 0x36 /** Proximity Maximum Half Delta Rising Value. */ #define MPR121_MHDPROXR_VAL 0x3F /** Proximity Noise Half Delta Rising Register. */ #define MPR121_NHDPROXR_REG 0x37 /** Proximity Noise Half Delta Rising Value. */ #define MPR121_NHDPROXR_VAL 0x5F /** Proximity Noise Count Limit Rising Register. */ #define MPR121_NCLPROXR_REG 0x38 /** Proximity Noise Count Limit Rising Value. */ #define MPR121_NCLPROXR_VAL 0x04 /** Proximity Filter Delay Count Limit Rising Register. */ #define MPR121_FDLPROXR_REG 0x39 /** Proximity Filter Delay Count Limit Rising Value. */ #define MPR121_FDLPROXR_VAL 0x00 /** Proximity Maximum Half Delta Falling Register. */ #define MPR121_MHDPROXF_REG 0x3A /** Proximity Maximum Half Delta Falling Value. */ #define MPR121_MHDPROXF_VAL 0x01 /** Proximity Noise Half Delta Falling Register. */ #define MPR121_NHDPROXF_REG 0x3B /** Proximity Noise Half Delta Falling Value. */ #define MPR121_NHDPROXF_VAL 0x01 /** Proximity Noise Count Limit Falling Register. */ #define MPR121_NCLPROXF_REG 0x3C /** Proximity Noise Count Limit Falling Value. */ #define MPR121_NCLPROXF_VAL 0x1F /** Proximity Filter Delay Count Limit Falling Register. */ #define MPR121_FDLPROXF_REG 0x3D /** Proximity Filter Delay Count Limit Falling Value. */ #define MPR121_FDLPROXF_VAL 0x04 /** First Electrode Touch Threshold Register. */ #define MPR121_E0TTH_REG 0x41 /** First Electrode Touch Threshold Value. */ #define MPR121_E0TTH_VAL 12 /** First Electrode Release Threshold Register. */ #define MPR121_E0RTH_REG 0x42 /** First Electrode Release Threshold Value. */ #define MPR121_E0RTH_VAL 6 /** Global CDC/CDT Configuration Register. */ #define MPR121_CDT_REG 0x5D /** Global CDC/CDT Configuration Value. */ #define MPR121_CDT_VAL 0x20 /** Enable electrodes Register. */ #define MPR121_ECR_REG 0x5E /** Enable electrodes Value. */ #define MPR121_ECR_VAL 0x8F // Start ELE0-11 with first 5 bits of baseline tracking //#define MPR121_ECR_VAL 0xBF // Start ELE0-11 + proximity with first 5 bits of baseline tracking /** Soft-reset Register. */ #define MPR121_SRST_REG 0x80 /** Soft-reset Value. */ #define MPR121_SRST_VAL 0x63 /** Get bit of variable 'current' of sensor at position. */ #define BITC(sensor, position) ((pS->current[sensor] >> position) & 1) /** Get bit of variable 'previous' of sensor at position. */ #define BITP(sensor, position) ((pS->previous[sensor] >> position) & 1) /**@}*/ /** * MPR121 sensors status and data struct. * * The struct mpr121 uses the indices of i2c_addr and id to link the specific sensors to an I2C address and a human-readable ID * and the indices of the arrays connected, running, current and previous to store sensor status and data of a specific sensor. * */ typedef struct mpr121 mpr121; struct mpr121 { const uint8_t i2c_addr[4] = { 0x5A, 0x5B, 0x5C, 0x5D }; /** I2C addresses of MPR121 controller */ const char id[4] = { 'A', 'B', 'C', 'D' }; /** Human-readable sensor IDs*/ bool connected[4] = { false, false, false, false }; /** Status if sensor is connected at I2C address */ bool running[4] = { false, false, false, false }; /** Running state of sensor */ uint16_t current[4] = { 0x0000, 0x0000, 0x0000, 0x0000 }; /** Current values in electrode register of sensor */ uint16_t previous[4] = { 0x0000, 0x0000, 0x0000, 0x0000 }; /** Current values in electrode register of sensor */ }; bool mpr21_found = false; /** * The function Mpr121Init() soft-resets, detects and configures up to 4x MPR121 sensors. * * @param struct *pS Struct with MPR121 status and data. * bool initial true - Initial call, false - next calls * @return void * @pre None. * @post None. * */ void Mpr121Init(struct mpr121 *pS, bool initial) { // Loop through I2C addresses for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) { if (initial && I2cActive(pS->i2c_addr[i])) { continue; } // Soft reset sensor and check if connected at I2C address pS->connected[i] = (I2cWrite8(pS->i2c_addr[i], MPR121_SRST_REG, MPR121_SRST_VAL) && (0x24 == I2cRead8(pS->i2c_addr[i], 0x5D))); if (pS->connected[i]) { // Log sensor found mpr21_found = true; char device_name[16]; snprintf_P(device_name, sizeof(device_name), PSTR("MPR121(%c)"), pS->id[i]); I2cSetActiveFound(pS->i2c_addr[i], device_name); // Set thresholds for registers 0x41 - 0x5A (ExTTH and ExRTH) for (uint32_t j = 0; j < 13; j++) { // Touch I2cWrite8(pS->i2c_addr[i], MPR121_E0TTH_REG + 2 * j, MPR121_E0TTH_VAL); // Release I2cWrite8(pS->i2c_addr[i], MPR121_E0RTH_REG + 2 * j, MPR121_E0RTH_VAL); } // ELEC0-11 Maximum Half Delta Rising I2cWrite8(pS->i2c_addr[i], MPR121_MHDR_REG, MPR121_MHDR_VAL); // ELEC0-11 Noise Half Delta Rising I2cWrite8(pS->i2c_addr[i], MPR121_NHDR_REG, MPR121_NHDR_VAL); // ELEC0-11 Noise Count Limit Rising I2cWrite8(pS->i2c_addr[i], MPR121_NCLR_REG, MPR121_NCLR_VAL); // ELEC0-11 Maximum Half Delta Falling I2cWrite8(pS->i2c_addr[i], MPR121_MHDF_REG, MPR121_MHDF_VAL); // ELEC0-11 Noise Half Delta Falling I2cWrite8(pS->i2c_addr[i], MPR121_NHDF_REG, MPR121_NHDF_VAL); // ELEC0-11 Noise Count Limit Falling I2cWrite8(pS->i2c_addr[i], MPR121_NCLF_REG, MPR121_NCLF_VAL); // Proximity Maximum Half Delta Rising I2cWrite8(pS->i2c_addr[i], MPR121_MHDPROXR_REG, MPR121_MHDPROXR_VAL); // Proximity Noise Half Delta Rising I2cWrite8(pS->i2c_addr[i], MPR121_NHDPROXR_REG, MPR121_NHDPROXR_VAL); // Proximity Noise Count Limit Rising I2cWrite8(pS->i2c_addr[i], MPR121_NCLPROXR_REG, MPR121_NCLPROXR_VAL); // Proximity Filter Delay Count Limit Rising I2cWrite8(pS->i2c_addr[i], MPR121_FDLPROXR_REG, MPR121_FDLPROXR_VAL); // Proximity Maximum Half Delta Falling I2cWrite8(pS->i2c_addr[i], MPR121_MHDPROXF_REG, MPR121_MHDPROXF_VAL); // Proximity Noise Half Delta Falling I2cWrite8(pS->i2c_addr[i], MPR121_NHDPROXF_REG, MPR121_NHDPROXF_VAL); // Proximity Noise Count Limit Falling I2cWrite8(pS->i2c_addr[i], MPR121_NCLPROXF_REG, MPR121_NCLPROXF_VAL); // Proximity Filter Delay Count Limit Falling I2cWrite8(pS->i2c_addr[i], MPR121_FDLPROXF_REG, MPR121_FDLPROXF_VAL); // Global CDC/CDT Configuration I2cWrite8(pS->i2c_addr[i], MPR121_CDT_REG, MPR121_CDT_VAL); // Enable sensor I2cWrite8(pS->i2c_addr[i], MPR121_ECR_REG, MPR121_ECR_VAL); // Check if sensor is running pS->running[i] = (0x00 != I2cRead8(pS->i2c_addr[i], MPR121_ECR_REG)); AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_I2C "MPR121%c: %sRunning"), pS->id[i], (pS->running[i]) ? "" : "NOT"); } else { // Make sure running is false pS->running[i] = false; } } // for-loop // Display no sensor found message if (!(pS->connected[0] || pS->connected[1] || pS->connected[2] || pS->connected[3])) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C "MPR121: No sensors found")); } } // void Mpr121Init(struct mpr121 *s) /** * Publishes the sensor information. * * The function Mpr121Show() reads sensor data, checks for over-current exceptions and * creates strings with button states for the web-interface and near real-time/ telemetriy MQTT. * * @param struct *pS Struct with MPR121 status and data. * @param byte function Tasmota function ID. * @return void * @pre Call Mpr121Init() once. * @post None. * */ void Mpr121Show(struct mpr121 *pS, uint8_t function) { // Loop through sensors for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) { // Check if sensor is connected if (pS->connected[i]) { // Read data if (!I2cValidRead16LE(&pS->current[i], pS->i2c_addr[i], MPR121_ELEX_REG)) { AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_I2C "MPR121%c: ERROR: Cannot read data!"), pS->id[i]); Mpr121Init(pS, false); return; } // Check if OVCF bit is set if (BITC(i, 15)) { // Clear OVCF bit I2cWrite8(pS->i2c_addr[i], MPR121_ELEX_REG, 0x00); AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_I2C "MPR121%c: ERROR: Excess current detected! Fix circuits if it happens repeatedly! Soft-resetting MPR121 ..."), pS->id[i]); Mpr121Init(pS, false); return; } } // Check if sensor is running if (pS->running[i]) { // Append sensor to JSON message string if (FUNC_JSON_APPEND == function) { ResponseAppend_P(PSTR(",\"MPR121%c\":{"), pS->id[i]); } // Loop through buttons for (uint32_t j = 0; j < 13; j++) { // Add sensor, button and state to MQTT JSON message string if ((FUNC_EVERY_50_MSECOND == function) && (BITC(i, j) != BITP(i, j))) { Response_P(PSTR("{\"MPR121%c\":{\"Button%i\":%i}}"), pS->id[i], j, BITC(i, j)); MqttPublishPrefixTopic_P(RESULT_OR_STAT, TasmotaGlobal.mqtt_data); } // Add buttons to web string #ifdef USE_WEBSERVER if (FUNC_WEB_SENSOR == function) { WSContentSend_PD(PSTR("{s}MPR121%c Button%d{m}%d{e}"), pS->id[i], j, BITC(i, j)); } #endif // USE_WEBSERVER // Append JSON message string if (FUNC_JSON_APPEND == function) { ResponseAppend_P(PSTR("%s\"Button%i\":%i"), (j > 0 ? "," : ""), j, BITC(i, j)); } } // for-loop j // Save sensor data pS->previous[i] = pS->current[i]; // Append JSON message string if (FUNC_JSON_APPEND == function) { ResponseJsonEnd(); } } // if->running } // for-loop i } // void Mpr121Show(uint8_t function) /*********************************************************************************************\ * Interface \*********************************************************************************************/ /** * The function Xsns30() interfaces Tasmota with the driver. * * It provides the function IDs * FUNC_INIT to initialize a driver, * FUNC_EVERY_50_MSECOND for near real-time operation, * FUNC_JSON_APPEND for telemetry data and * FUNC_WEB_SENSOR for displaying data in the Tasmota web-interface * * @param byte function Tasmota function ID. * @return bool ??? * @pre None. * @post None. * */ bool Xsns30(uint8_t function) { if (!I2cEnabled(XI2C_23)) { return false; } bool result = false; // Sensor state/data struct static struct mpr121 mpr121; if (FUNC_INIT == function) { // Initialize Sensors Mpr121Init(&mpr121, true); } else if (mpr21_found) { switch (function) { // Run ever 50 milliseconds (near real-time functions) case FUNC_EVERY_50_MSECOND: Mpr121Show(&mpr121, FUNC_EVERY_50_MSECOND); break; // Generate JSON telemetry string case FUNC_JSON_APPEND: Mpr121Show(&mpr121, FUNC_JSON_APPEND); break; #ifdef USE_WEBSERVER // Show sensor data on main web page case FUNC_WEB_SENSOR: Mpr121Show(&mpr121, FUNC_WEB_SENSOR); break; #endif // USE_WEBSERVER } } // Return bool result return result; } #endif // USE_MPR121 #endif // USE_I2C