// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright (c) 2026 Ashwin Whitchurch, Protocentral Electronics

//////////////////////////////////////////////////////////////////////////////////////////
//
//  Arduino library for the ProtoCentral FDC2214 capacitive sensor breakout board
//
//  Copyright (c) 2026 Ashwin Whitchurch, Protocentral Electronics
//  Email: info@protocentral.com
//
//  The FDC2214 is a 4-channel, 28-bit capacitance-to-digital converter from
//  Texas Instruments. It measures capacitance by sensing the resonant
//  frequency of an external LC tank attached to each channel.
//
//  This software is licensed under the MIT License (http://opensource.org/licenses/MIT).
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
//  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
//  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
//  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
//  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//  For information on how to use, visit:
//  https://github.com/Protocentral/protocentral_fdc2214_arduino
//
/////////////////////////////////////////////////////////////////////////////////////////

#ifndef _PROTOCENTRAL_FDC2214_H_
#define _PROTOCENTRAL_FDC2214_H_

#include <Arduino.h>
#include <Wire.h>

// =============================================================================
// I2C addresses
// =============================================================================
#define FDC2214_I2C_ADDR_0          (0x2A)  // ADDR pin tied to GND
#define FDC2214_I2C_ADDR_1          (0x2B)  // ADDR pin tied to VDD (ProtoCentral board default)

// =============================================================================
// Register map
// =============================================================================
#define FDC2214_REG_DATA_MSB_CH0    (0x00)
#define FDC2214_REG_DATA_LSB_CH0    (0x01)
#define FDC2214_REG_DATA_MSB_CH1    (0x02)
#define FDC2214_REG_DATA_LSB_CH1    (0x03)
#define FDC2214_REG_DATA_MSB_CH2    (0x04)
#define FDC2214_REG_DATA_LSB_CH2    (0x05)
#define FDC2214_REG_DATA_MSB_CH3    (0x06)
#define FDC2214_REG_DATA_LSB_CH3    (0x07)

#define FDC2214_REG_RCOUNT_CH0      (0x08)
#define FDC2214_REG_RCOUNT_CH1      (0x09)
#define FDC2214_REG_RCOUNT_CH2      (0x0A)
#define FDC2214_REG_RCOUNT_CH3      (0x0B)

#define FDC2214_REG_OFFSET_CH0      (0x0C)
#define FDC2214_REG_OFFSET_CH1      (0x0D)
#define FDC2214_REG_OFFSET_CH2      (0x0E)
#define FDC2214_REG_OFFSET_CH3      (0x0F)

#define FDC2214_REG_SETTLECOUNT_CH0 (0x10)
#define FDC2214_REG_SETTLECOUNT_CH1 (0x11)
#define FDC2214_REG_SETTLECOUNT_CH2 (0x12)
#define FDC2214_REG_SETTLECOUNT_CH3 (0x13)

#define FDC2214_REG_CLOCK_DIV_CH0   (0x14)
#define FDC2214_REG_CLOCK_DIV_CH1   (0x15)
#define FDC2214_REG_CLOCK_DIV_CH2   (0x16)
#define FDC2214_REG_CLOCK_DIV_CH3   (0x17)

#define FDC2214_REG_STATUS          (0x18)
#define FDC2214_REG_ERROR_CONFIG    (0x19)
#define FDC2214_REG_CONFIG          (0x1A)
#define FDC2214_REG_MUX_CONFIG      (0x1B)
#define FDC2214_REG_RESET_DEV       (0x1C)
#define FDC2214_REG_DRIVE_CURRENT_CH0 (0x1E)
#define FDC2214_REG_DRIVE_CURRENT_CH1 (0x1F)
#define FDC2214_REG_DRIVE_CURRENT_CH2 (0x20)
#define FDC2214_REG_DRIVE_CURRENT_CH3 (0x21)

#define FDC2214_REG_MANUFACTURER_ID (0x7E)
#define FDC2214_REG_DEVICE_ID       (0x7F)

#define FDC2214_MANUFACTURER_ID     (0x5449)  // "TI"
#define FDC2214_DEVICE_ID           (0x3055)  // FDC2214

// =============================================================================
// STATUS register bit masks
// =============================================================================
#define FDC2214_STATUS_DRDY         (1 << 6)   // Data ready (any channel)
#define FDC2214_STATUS_CH0_UNREAD   (1 << 3)
#define FDC2214_STATUS_CH1_UNREAD   (1 << 2)
#define FDC2214_STATUS_CH2_UNREAD   (1 << 1)
#define FDC2214_STATUS_CH3_UNREAD   (1 << 0)

// =============================================================================
// DATA_MSB upper-nibble error flags
// =============================================================================
#define FDC2214_DATA_ERR_AW         (1 << 15)  // amplitude warning
#define FDC2214_DATA_ERR_WD         (1 << 14)  // watchdog timeout
#define FDC2214_DATA_ERR_AH         (1 << 13)  // amplitude high
#define FDC2214_DATA_ERR_AL         (1 << 12)  // amplitude low
#define FDC2214_DATA_MASK_28BIT     (0x0FFFFFFFUL)

// =============================================================================
// MUX_CONFIG fields
// =============================================================================
#define FDC2214_AUTOSCAN_DISABLED   (0x0000)
#define FDC2214_AUTOSCAN_ENABLED    (0x8000)

#define FDC2214_RR_SEQ_CH0_CH1      (0x0000)
#define FDC2214_RR_SEQ_CH0_CH1_CH2  (0x2000)
#define FDC2214_RR_SEQ_ALL          (0x4000)

// MUX_CONFIG reserved bits required by datasheet
#define FDC2214_MUX_RESERVED        (0x0208)

#define FDC2214_DEGLITCH_1MHZ       (0x0001)
#define FDC2214_DEGLITCH_3_3MHZ     (0x0004)
#define FDC2214_DEGLITCH_10MHZ      (0x0005)
#define FDC2214_DEGLITCH_33MHZ      (0x0007)

// =============================================================================
// CONFIG register fields
// =============================================================================
#define FDC2214_CONFIG_ACTIVE_CH0   (0x0000)
#define FDC2214_CONFIG_ACTIVE_CH1   (0x4000)
#define FDC2214_CONFIG_ACTIVE_CH2   (0x8000)
#define FDC2214_CONFIG_ACTIVE_CH3   (0xC000)

#define FDC2214_CONFIG_SLEEP_EN     (0x2000)  // bit 13
#define FDC2214_CONFIG_SENSOR_ACT_LP (0x0800) // bit 11 - low-power sensor activation
#define FDC2214_CONFIG_REF_CLK_EXT  (0x0400)  // bit 10
#define FDC2214_CONFIG_INTB_DIS     (0x0100)  // bit 8
#define FDC2214_CONFIG_HIGH_CURRENT (0x0080)  // bit 7

// CONFIG reserved bits the datasheet requires to be set (bit 12 + bit 0).
#define FDC2214_CONFIG_RESERVED     (0x1001)

// =============================================================================
// CLOCK_DIVIDERS fields
// =============================================================================
#define FDC2214_FIN_SEL_LOW_FREQ    (0x1000)  // sensor < 8.75 MHz, divider = 1
#define FDC2214_FIN_SEL_HIGH_FREQ   (0x2000)  // sensor 0.01 - 10 MHz, divider = 2

// =============================================================================
// Internal reference oscillator (Hz)
// =============================================================================
#define FDC2214_INTERNAL_OSC_HZ     (43400000.0f)

// =============================================================================
// Channel enumeration
// =============================================================================
typedef enum {
    FDC2214_CH0 = 0,
    FDC2214_CH1 = 1,
    FDC2214_CH2 = 2,
    FDC2214_CH3 = 3
} fdc2214_channel_t;


/**
 * @brief Driver class for the Texas Instruments FDC2214 capacitance-to-digital converter.
 *
 * Typical usage:
 * @code
 *   FDC2214 fdc;
 *   Wire.begin();
 *   if (!fdc.begin()) while (1);
 *   fdc.configureDefaults();
 *   fdc.setAutoscan(true, FDC2214_RR_SEQ_ALL);
 *   fdc.start();
 *   uint32_t raw = fdc.readRawChannel(FDC2214_CH0);
 *   float    f   = fdc.readFrequencyHz(FDC2214_CH0);
 *   float    cpf = fdc.readCapacitancePf(FDC2214_CH0, 18.0f);  // 18 uH tank inductor
 * @endcode
 */
class FDC2214 {
public:
    FDC2214(uint8_t address = FDC2214_I2C_ADDR_1, TwoWire* wire = &Wire);

    // ------------------------------------------------------------- lifecycle
    bool      begin();
    bool      isConnected();
    void      reset();
    uint16_t  getDeviceID();
    uint16_t  getManufacturerID();

    // -------------------------------------------------------- channel config
    void      setReferenceCount(fdc2214_channel_t ch, uint16_t rcount);
    void      setSettleCount(fdc2214_channel_t ch, uint16_t scount);
    void      setOffset(fdc2214_channel_t ch, uint16_t offset);
    void      setClockDividers(fdc2214_channel_t ch, uint16_t fin_sel, uint16_t fref_div);
    void      setDriveCurrent(fdc2214_channel_t ch, uint8_t idrive_5bit);

    // ------------------------------------------------------ device-wide config
    void      setActiveChannel(fdc2214_channel_t ch);
    void      setAutoscan(bool enable, uint16_t rr_sequence = FDC2214_RR_SEQ_ALL);
    void      setDeglitch(uint16_t deglitch_field);
    void      setReferenceClockSource(bool external);
    void      setHighCurrentDrive(bool enable);
    void      setSleep(bool sleep);

    // -------------------------- one-shot defaults that work for capacitive sensing
    bool      configureDefaults();

    // Apply the configured CONFIG word and start conversions (clears SLEEP_MODE_EN).
    void      start();
    void      stop();

    // ------------------------------------------------------------ data access
    bool      isDataReady(fdc2214_channel_t ch);
    uint32_t  readRawChannel(fdc2214_channel_t ch);
    float     readFrequencyHz(fdc2214_channel_t ch);
    float     readCapacitancePf(fdc2214_channel_t ch, float inductance_uH);

    // ----------------------------------------------------------- status / raw
    uint16_t  readStatus();
    uint16_t  readErrorConfig();
    uint16_t  readRegister(uint8_t reg);
    void      writeRegister(uint8_t reg, uint16_t value);

    // ------------------------------------------------------- reference clock
    // Override the assumed reference frequency (e.g. when feeding an external clock).
    void      setReferenceClockHz(float hz);
    float     getReferenceClockHz() const { return _f_ref_hz; }

private:
    uint8_t   _addr;
    TwoWire*  _wire;
    float     _f_ref_hz;

    // Cached per-channel CLOCK_DIVIDERS so frequency conversion knows CH_FIN_DIVIDER.
    uint16_t  _clock_div[4];

    // Cached CONFIG and MUX_CONFIG so individual setters can update single fields
    // without losing the rest of the word.
    uint16_t  _config;
    uint16_t  _mux_config;

    void      writeConfig();
    void      writeMuxConfig();
};

#endif // _PROTOCENTRAL_FDC2214_H_
