/*** 
 * @Author: mypx
 * @Email: mypx_coder@163.com
 * @Date: 2025-06-23 10:26:23
 * @LastEditors: mypx mypx_coder@163.com
 * @Description: AD7793 driver header
 */
#ifndef __AD7793_H__
#define __AD7793_H__
#include <stdbool.h>
#include <stdint.h>

#define USING_AUTO_READ_CONFIG_BITS 0 // gain will set auto, so read it from register dynamic.

/* Hardware interface abstraction - should be implemented for your platform */
typedef struct
{
    void (*spi_transfer)(uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len); /**< SPI transfer function */
    void (*spi_write)(uint8_t cmd, uint8_t *tx_buf, uint16_t len);        /**< SPI write function */
    void (*spi_read)(uint8_t cmd, uint8_t *rx_buf, uint16_t len);         /**< SPI read function */
    void (*gpio_set)(uint8_t pin, bool state);                            /**< Set CS state */
    bool (*gpio_get)(uint8_t pin);                                        /**< Get RDY/MISO state */
    void (*delay_ms)(uint16_t ms);                                        /**< Millisecond delay */
} ad7793_hw_if_t;

/* Register address definitions */
#define REG_COMMUNICATION 0x00 /**< Communication register */
#define REG_STATUS        0x00 /**< Status register (read mode) */
#define REG_MODE          0x01 /**< Mode register */
#define REG_CONFIG        0x02 /**< Configuration register */
#define REG_DATA          0x03 /**< Data register */
#define REG_ID            0x04 /**< ID register */
#define REG_IO            0x05 /**< IO register */
#define REG_OFFSET        0x06 /**< Offset register */
#define REG_FULLSCALE     0x07 /**< Full scale register */

/* Communication register bit definitions */
#define COMM_WEN   (1 << 7) /**< Write enable */
#define COMM_RW    (1 << 6) /**< Read/Write control */
#define COMM_RS2   (1 << 5) /**< Register select bit 2 */
#define COMM_RS1   (1 << 4) /**< Register select bit 1 */
#define COMM_RS0   (1 << 3) /**< Register select bit 0 */
#define COMM_CREAD (1 << 2) /**< Continuous read enable */

/* Mode register bit definitions */
#define MODE_MD2  (1 << 15) /**< Mode select bit 2 */
#define MODE_MD1  (1 << 14) /**< Mode select bit 1 */
#define MODE_MD0  (1 << 13) /**< Mode select bit 0 */
#define MODE_CLK1 (1 << 7)  /**< Clock select bit 1 */
#define MODE_CLK0 (1 << 6)  /**< Clock select bit 0 */
#define MODE_FS3  (1 << 3)  /**< Update rate select bit 3 */
#define MODE_FS2  (1 << 2)  /**< Update rate select bit 2 */
#define MODE_FS1  (1 << 1)  /**< Update rate select bit 1 */
#define MODE_FS0  (1 << 0)  /**< Update rate select bit 0 */

/* Configuration register bit definitions */
#define CONFIG_VBIAS1 (1 << 15) /**< Bias voltage bit 1 */
#define CONFIG_VBIAS0 (1 << 14) /**< Bias voltage bit 0 */
#define CONFIG_BO     (1 << 13) /**< Burnout current enable */
#define CONFIG_U_B    (1 << 12) /**< Unipolar/Bipolar mode */
#define CONFIG_BOOST  (1 << 11) /**< Bias boost enable */
#define CONFIG_G2     (1 << 10) /**< Gain select bit 2 */
#define CONFIG_G1     (1 << 9)  /**< Gain select bit 1 */
#define CONFIG_G0     (1 << 8)  /**< Gain select bit 0 */
#define CONFIG_REFSEL (1 << 7)  /**< Reference select */
#define CONFIG_BUF    (1 << 4)  /**< Buffer mode enable */
#define CONFIG_CH2    (1 << 2)  /**< Channel select bit 2 */
#define CONFIG_CH1    (1 << 1)  /**< Channel select bit 1 */
#define CONFIG_CH0    (1 << 0)  /**< Channel select bit 0 */

/* IO register bit definitions */
#define IO_IEXCDIR1 (1 << 3) /**< Excitation current direction bit 1 */
#define IO_IEXCDIR0 (1 << 2) /**< Excitation current direction bit 0 */
#define IO_IEXCEN1  (1 << 1) /**< Excitation current enable bit 1 */
#define IO_IEXCEN0  (1 << 0) /**< Excitation current enable bit 0 */

/* Working mode enumeration */
typedef enum
{
    AD7793_MODE_CONTINUOUS,    /**< Continuous conversion mode */
    AD7793_MODE_SINGLE,        /**< Single conversion mode */
    AD7793_MODE_IDLE,          /**< Idle mode */
    AD7793_MODE_POWER_DOWN,    /**< Power-down mode */
    AD7793_MODE_INTERNAL_ZERO, /**< Internal zero-scale calibration */
    AD7793_MODE_INTERNAL_FULL, /**< Internal full-scale calibration */
    AD7793_MODE_SYSTEM_ZERO,   /**< System zero-scale calibration */
    AD7793_MODE_SYSTEM_FULL    /**< System full-scale calibration */
} ad7793_mode_t;

/* Gain enumeration */
typedef enum
{
    AD7793_GAIN_1   = 1,  /**< Gain 1 */
    AD7793_GAIN_2   = 2,  /**< Gain 2 */
    AD7793_GAIN_4   = 4,  /**< Gain 4 */
    AD7793_GAIN_8   = 8,  /**< Gain 8 */
    AD7793_GAIN_16  = 16, /**< Gain 16 */
    AD7793_GAIN_32  = 32, /**< Gain 32 */
    AD7793_GAIN_64  = 64, /**< Gain 64 */
    AD7793_GAIN_128 = 128 /**< Gain 128 */
} ad7793_gain_t;

/* Channel enumeration */
typedef enum
{
    AD7793_CHANNEL_1,    /**< Channel 1: AIN1(+)-AIN1(-) */
    AD7793_CHANNEL_2,    /**< Channel 2: AIN2(+)-AIN2(-) */
    AD7793_CHANNEL_3,    /**< Channel 3: AIN3(+)-AIN3(-) */
    AD7793_CHANNEL_TEMP, /**< Temperature sensor channel */
    AD7793_CHANNEL_AVDD  /**< AVDD monitor channel */
} ad7793_channel_t;

/* Update rate enumeration */
typedef enum
{
    AD7793_RATE_4_17HZ = 0xF, /**< 4.17Hz */
    AD7793_RATE_8_33HZ = 0xE, /**< 8.33Hz */
    AD7793_RATE_16_7HZ = 0xA, /**< 16.7Hz */
    AD7793_RATE_33_2HZ = 0x7, /**< 33.2Hz */
    AD7793_RATE_62HZ   = 0x4, /**< 62Hz */
    AD7793_RATE_123HZ  = 0x3, /**< 123Hz */
    AD7793_RATE_242HZ  = 0x2, /**< 242Hz */
    AD7793_RATE_470HZ  = 0x1  /**< 470Hz */
} ad7793_rate_t;

/* Configuration structure for AD7793 */
typedef struct
{
    bool             use_internal_ref; /**< true: use internal reference, false: use external reference */
    float            external_ref;     /**< External reference voltage (V), used if use_internal_ref is false */
    ad7793_gain_t    gain;             /**< Gain setting */
    ad7793_channel_t channel;          /**< Channel selection */
    ad7793_rate_t    rate;             /**< Output data rate */
    bool             buffered;         /**< true: enable input buffer, false: disable buffer */
    bool             unipolar_mode;    /**< true: unipolar mode, false: bipolar mode */
    bool calibrate_system_zero;        /**< true: perform system zero calibration at startup, false: skip calibration */
} ad7793_config_t;

/* Default config as a compound literal so it can be used in expressions */
#ifndef AD7793_DEFAULT_CONFIG
#define AD7793_DEFAULT_CONFIG                                                                                \
    (ad7793_config_t)                                                                                        \
    {                                                                                                        \
        .use_internal_ref = true, .external_ref = 1.17f, .gain = AD7793_GAIN_1, .channel = AD7793_CHANNEL_1, \
        .rate = AD7793_RATE_8_33HZ, .buffered = false, .unipolar_mode = true, .calibrate_system_zero = false \
    }
#endif

/* Device structure */
typedef struct
{
    ad7793_hw_if_t  *hw_if;            /**< Hardware interface function pointers */
    uint8_t          cs_pin;           /**< Chip select pin */
    ad7793_mode_t    cur_mode;         /**< Current working mode */
    ad7793_gain_t    cur_gain;         /**< Current gain setting */
    ad7793_channel_t cur_channel;      /**< Current channel */
    ad7793_rate_t    cur_rate;         /**< Current update rate */
    float            external_ref;     /**< Set reference when use_internal_ref is false  */
    bool             is_ad7793;        /**< True if AD7793 (24-bit), false if AD7792 (16-bit) */
    bool             use_internal_ref; /**< True if using internal reference */
    bool             unipolar_mode;    /**< True if unipolar mode, false if bipolar mode */
    bool             buffered;         /**< True if buffer enable, false if buffer unused */
    bool             is_initialized;   /**< True if the device has been initialized */
    /* --- Runtime diagnostics --- */
    uint32_t err_count;      /**< Total STATUS.ERR (bit6) occurrences */
    uint32_t err_recoveries; /**< Number of automatic recovery attempts */
    uint32_t low_code_drops; /**< Dropped samples due to low code threshold */
    uint32_t last_status;    /**< Last read STATUS register */
} ad7793_dev_t;
#define AD7793_CHECK_INITIALIZED(dev)                  \
    do                                                 \
    {                                                  \
        if (!dev->is_initialized)                      \
        {                                              \
            DEBUG_PRINTF("Device not initialized!\n"); \
            return false;                              \
        }                                              \
    } while (0)

/*
 * AD7793 Driver Header
 *
 * Recommended Initialization Flow:
 *   1. Configure the ad7793_hw_if_t hardware interface struct, filling in SPI/GPIO/delay function pointers.
 *   2. Define the ad7793_config_t configuration struct, specifying reference source, gain, channel, rate, buffer, etc.
 *   3. Call ad7793_init(&dev, &hw_if, cs_pin, &cfg) to initialize the device.
 *   4. Before data acquisition, call ad7793_wait_ready to wait for data ready.
 *   5. Use ad7793_read_data to acquire data, and ad7793_convert_to_voltage to convert to voltage.
 *   6. If you need to switch channel/gain/reference, it is recommended to set idle mode first, then switch parameters, and finally restore continuous mode.
 *
 * Common Troubleshooting:
 *   - STATUS=0x48/0xC8: Usually caused by reference source mismatch, buffer/gain conflict, or IEXC not configured as required by hardware.
 *   - All readings zero or extremely large: Check reference voltage, SPI wiring, channel/gain configuration.
 *   - Register write failure: Check SPI speed, CS timing, hardware interface implementation.
 *   - Temperature jumps/spikes: Application layer should implement temperature jump/low code filtering.
 *   - Abnormal sampling rate: Confirm rate configuration matches master clock and SPI speed.
 *
 * See API comments for typical usage.
 */

/**
 * @brief Write data to a specified register of the AD7793 device.
 * @param dev Device structure pointer.
 * @param reg Register address to write to.
 * @param data Data buffer to be written.
 * @param len Data length to write (2/3 bytes).
 * @return Operation status. True if successful, false otherwise.
 * @detail This function constructs a communication command to write data to the specified register.
 *         It first clears the chip select pin, then performs an SPI transfer to send the command and data.
 *         Finally, it sets the chip select pin back to high.
 * @note Boundary: Returns false if dev is not initialized or parameters are invalid. Example: ad7793_write_reg(&dev, REG_MODE, 0x200A, 2);
 */
bool ad7793_write_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t data, uint16_t len);

/**
 * @brief Read data from a specified register of the AD7793 device.
 * @param dev Device structure pointer.
 * @param reg Register address to read from.
 * @param data Buffer to store the read data.
 * @param len Data length to read (2/3 bytes).
 * @return Operation status. True if successful, false otherwise.
 * @detail This function constructs a communication command to read data from the specified register.
 *         It first clears the chip select pin, then performs an SPI transfer to receive the data.
 *         Finally, it sets the chip select pin back to high.
 * @note Boundary: Returns false if dev is not initialized or parameters are invalid. Example: ad7793_read_reg(&dev, REG_CONFIG, &val, 2);
 */
bool ad7793_read_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t *data, uint16_t len);

/**
 * @brief Reset the AD7793 device.
 * @param dev Device structure pointer.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function resets the device by writing 32 ones to the device.
 *         It clears the chip select pin, sends the reset sequence via SPI, and then sets the chip select pin back to high.
 *         After that, it waits for 1 millisecond to ensure the reset is completed.
 * @note Example: ad7793_reset(&dev);
 */
bool ad7793_reset(ad7793_dev_t *dev);

/**
 * @brief Set the working mode of the AD7793 device.
 * @param dev Device structure pointer.
 * @param mode Mode enum value representing the desired working mode.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function reads the current mode register, clears the mode bits, and then sets the new mode according to the input.
 *         It updates the current mode in the device structure and writes the new mode to the mode register.
 * @note Example: ad7793_set_mode(&dev, AD7793_MODE_CONTINUOUS);
 */
bool ad7793_set_mode(ad7793_dev_t *dev, ad7793_mode_t mode);

/**
 * @brief Set the gain of the AD7793 device.
 * @param dev Device structure pointer.
 * @param gain Gain enum value representing the desired gain setting.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function reads the current configuration register, clears the gain bits, and then sets the new gain according to the input.
 *         It updates the current gain in the device structure and writes the new configuration to the configuration register.
 * @note Boundary: Returns false if BUF=0 and G>2. Example: ad7793_set_gain(&dev, AD7793_GAIN_4);
 */
bool ad7793_set_gain(ad7793_dev_t *dev, ad7793_gain_t gain);

/**
 * @brief Set the channel of the AD7793 device.
 * @param dev Device structure pointer.
 * @param channel Channel enum value representing the desired channel.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function reads the current configuration register, clears the channel bits, and then sets the new channel according to the input.
 *         It updates the current channel in the device structure and writes the new configuration to the configuration register.
 * @note Example: ad7793_set_channel(&dev, AD7793_CHANNEL_1);
 */
bool ad7793_set_channel(ad7793_dev_t *dev, ad7793_channel_t channel);

/**
 * @brief Set the update rate of the AD7793 device.
 * @param dev Device structure pointer.
 * @param rate Update rate enum value representing the desired update rate.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function reads the current mode register, clears the rate bits, and then sets the new rate according to the input.
 *         It updates the current update rate in the device structure and writes the new mode to the mode register.
 * @note Example: ad7793_set_rate(&dev, AD7793_RATE_8_33HZ);
 */
bool ad7793_set_rate(ad7793_dev_t *dev, ad7793_rate_t rate);

/**
 * @brief Set the reference source of the AD7793 device.
 * @param dev Device structure pointer.
 * @param use_internal true=internal reference, false=external reference.
 * @param external_ref External reference voltage (if use_internal is false).
 * @return Operation status. True if successful, false otherwise.
 * @detail This function reads the current configuration register and sets the reference select bit according to the input.
 *         It updates the reference source information in the device structure and writes the new configuration to the configuration register.
 * @note Example: ad7793_set_reference(&dev, false, 2.5f);
 */
bool ad7793_set_reference(ad7793_dev_t *dev, bool use_internal, float external_ref);

/**
 * @brief Wait for the AD7793 device to be ready for conversion.
 * @param dev Device structure pointer.
 * @param timeout_ms Timeout in milliseconds.
 * @return true=ready, false=timeout.
 * @detail This function continuously checks the GPIO pin status until the device is ready or the timeout occurs.
 *         It waits for 1 millisecond between each check.
 * @note Example: ad7793_wait_ready(&dev, 1000);
 */
bool ad7793_wait_ready(ad7793_dev_t *dev, uint16_t timeout_ms);

/**
 * @brief Read the conversion data from the AD7793 device.
 * @param dev Device structure pointer.
 * @param value adc value to store the read data (3 bytes for AD7793).
 * @return Operation status. True if successful, false otherwise.
 * @detail This function reads the conversion data from the data register.
 *         If the device is AD7793, it reads 3 bytes; if it is AD7792, it reads 2 bytes.
 * @note Example: ad7793_read_data(&dev, &val);
 */
bool ad7793_read_data(ad7793_dev_t *dev, uint32_t *value);

/**
 * @brief Convert the raw digital data read from AD7793 to the corresponding voltage value.
 * @param dev Pointer to the ad7793_dev_t instance.
 * @param raw_data The raw digital data (24-bit) read from AD7793.
 * @return The converted voltage value (unit: volts).
 * @detail This function converts the raw digital data to the voltage value according to the formula provided in the AD7793 manual.
 *         The formula is: V = (D * Vref) / (2^N * G)
 *         Where:
 *         V is the converted voltage value.
 *         D is the raw digital data.
 *         Vref is the reference voltage.
 *         N is the number of ADC bits (24 bits for AD7793).
 *         G is the gain setting.
 * @note Example: float v=ad7793_convert_to_voltage(&dev, code);
 */
float ad7793_convert_to_voltage(ad7793_dev_t *dev, uint32_t raw_data);

/**
 * @brief Convert the input voltage to the corresponding ADC code for AD7793.
 * @param dev Pointer to the AD7793 device structure.
 * @param voltage Input voltage value (V).
 * @return Corresponding ADC code value.
 * @note Example: uint32_t code=ad7793_convert_voltage_to_code(&dev, v);
 */
uint32_t ad7793_convert_voltage_to_code(ad7793_dev_t *dev, float voltage);

/**
 * @brief Configure the excitation current source of the AD7793 device.
 * @param dev Device structure pointer.
 * @param current Current value (10/210/1000uA).
 * @param dir Current direction (0=IOUT1/IOUT2, 1=swap).
 * @return Operation status. True if successful, false otherwise.
 * @detail This function configures the excitation current source by setting the current value and direction in the IO register.
 *         It then enables the current source and writes the configuration to the IO register.
 * @note Example: ad7793_config_iexc(&dev, 210, 0);
 */
bool ad7793_config_iexc(ad7793_dev_t *dev, uint16_t current, uint8_t dir);

/**
 * @brief Configure the AD7793 for unipolar or bipolar operation mode
 * @param dev Pointer to the AD7793 device structure
 * @param unipolar true = enable unipolar mode, false = enable bipolar mode
 * @return true if operation succeeded, false otherwise
 * @details 
 * This function sets the U/B (Unipolar/Bipolar) bit in the configuration register 
 * to determine the conversion mode:
 * - Unipolar mode (U/B=1): Converts 0V to VREF into 0x000000 to 0xFFFFFF
 * - Bipolar mode (U/B=0): Converts -VREF/2 to +VREF/2 into 0x000000 to 0xFFFFFF
 * 
 * Note: Changing this bit affects the interpretation of the ADC output code.
 * @note Example: ad7793_set_unipolar(&dev, true);
 */
bool ad7793_set_unipolar(ad7793_dev_t *dev, bool unipolar);

/**
 * @brief Configure the input buffer mode of AD7793
 * @param dev Pointer to the ad7793_dev_t instance
 * @param enable true=Enable input buffer, false=Disable input buffer
 * @return Operation status. true=Success, false=Failure
 * @detail This function configures the input buffer mode of AD7793.
 *         Enabling the buffer reduces input impedance requirements but increases power consumption.
 *         Disabling the buffer is suitable for high-impedance sources but requires attention to input signal range limitations.
 * @note Boundary: Returns false if G>2 and buffer is disabled. Example: ad7793_set_buffered(&dev, true);
 */
bool ad7793_set_buffered(ad7793_dev_t *dev, bool enable);

/**
 * @brief Initialize the AD7793 device.
 * @param dev Device structure pointer.
 * @param hw_if Hardware interface structure containing function pointers.
 * @param cs_pin Chip select pin.
 * @param cfg Configuration struct
 * @return Operation status. True if successful, false otherwise.
 * @detail This function initializes the AD7793 device by saving the hardware interface and chip select information.
 *         It resets the device, waits for the reset to complete, and then reads the ID register to confirm the device type.
 *         Finally, it initializes the device with default configuration settings.
 * @note Example: ad7793_init(&dev, &hw_if, cs_pin, &cfg);
 */
bool ad7793_init(ad7793_dev_t *dev, ad7793_hw_if_t *hw_if, uint8_t cs_pin, const ad7793_config_t *cfg);

/**
 * @brief Dump the contents of the AD7793 registers for debugging.
 * @param dev Device structure pointer.
 * @detail This function reads and prints the contents of the AD7793 registers.
 *         It can be used to verify the register settings and diagnose issues.
 */
void ad7793_dump_registers(ad7793_dev_t *dev);
void ad7793_dump_registers_mode(ad7793_dev_t *dev, int full);

#endif // __AD7793_H__
