/*
 * @Author: mypx
 * @Email: mypx_coder@163.com
 * @Date: 2025-06-23 10:26:12
 * @LastEditors: mypx mypx_coder@163.com
 * @Description: AD7793 driver implementation
 */
#include "ad7793.h"
#include <stdbool.h>
#include <stddef.h>

#ifdef DEBUG
#include <rtthread.h>
#define DEBUG_PRINTF(...)        \
    do                           \
    {                            \
        rt_kprintf("[AD7793] "); \
        rt_kprintf(__VA_ARGS__); \
    } while (0)
#else
#define DEBUG_PRINTF(...) \
    do                    \
    {                     \
    } while (0)
#endif
/**
 * @brief Send SPI command and receive data.
 * @param dev Pointer to ad7793_dev_t instance.
 * @param cmd Command byte to send.
 * @param tx_buf Transmit buffer.
 * @param rx_buf Receive buffer.
 * @param len Data length to transfer.
 * @return Operation status. True if successful, false otherwise.
 */
static bool ad7793_spi_transfer(ad7793_dev_t *dev, uint8_t cmd, uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint8_t tx[32] = {0};
    uint8_t rx[32] = {0};

    tx[0] = cmd;
    if (tx_buf)
    {
        for (uint16_t i = 0; i < len; i++)
        {
            tx[i + 1] = tx_buf[i];
        }
    }
    dev->hw_if->spi_transfer(tx, rx, len + 1);

    if (rx_buf)
    {
        for (uint16_t i = 0; i < len; i++)
        {
            rx_buf[i] = rx[i + 1];
        }
    }

    return true;
}

/* Forward declaration for diagnostic dump (used early) */
void ad7793_dump_registers(ad7793_dev_t *dev);

/**
 * @brief Write communication register.
 * @param dev Pointer to ad7793_dev_t instance.
 * @param cmd Command byte to write to the communication register.
 * @return Operation status. True if successful, false otherwise.
 */
bool ad7793_write_communication(ad7793_dev_t *dev, uint8_t cmd)
{
    AD7793_CHECK_INITIALIZED(dev);
    dev->hw_if->gpio_set(dev->cs_pin, false);
    ad7793_spi_transfer(dev, cmd, NULL, NULL, 0);
    dev->hw_if->gpio_set(dev->cs_pin, true);
    return true;
}

/**
 * @brief Write data to a specified register.
 * @param dev Pointer to ad7793_dev_t instance.
 * @param reg Register address to write to.
 * @param data Data buffer to be written.
 * @param len Data length (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.
 */
bool ad7793_write_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t data, uint16_t len)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint8_t cmd    = 0;
    uint8_t tmp[4] = {0};

    /* Build communication command: WEN=0, R/W=0(write), register address */
    cmd  = (reg & 0x07) << 3;
    cmd &= ~COMM_RW;
    cmd &= ~COMM_WEN;

    if (len == 1)
    {
        tmp[0] = data >> 0;
    }
    else if (len == 2)
    {
        tmp[0] = data >> 8;
        tmp[1] = data >> 0;
    }
    else if (len == 3)
    {
        tmp[0] = data >> 16;
        tmp[1] = data >> 8;
        tmp[2] = data >> 0;
    }
    dev->hw_if->gpio_set(dev->cs_pin, false);
    dev->hw_if->spi_write(cmd, tmp, len);
    //ad7793_spi_transfer(dev, cmd, data, NULL, len);
    dev->hw_if->gpio_set(dev->cs_pin, true);

    /* Verify write by reading back the register up to a few retries */
    for (int attempt = 0; attempt < 3; attempt++)
    {
        uint32_t read_back = 0;
        dev->hw_if->delay_ms(2);
        if (!ad7793_read_reg(dev, reg, &read_back, len))
        {
            DEBUG_PRINTF("Readback failed for reg 0x%02X on attempt %d\n", reg, attempt);
            continue;
        }
        /* mask read_back to len bytes */
        uint32_t mask = (len == 3) ? 0xFFFFFFu : ((len == 2) ? 0xFFFFu : 0xFFu);
        if ((read_back & mask) == (data & mask))
        {
            return true; /* success */
        }
        DEBUG_PRINTF("Write verify mismatch reg 0x%02X: wrote 0x%0*X read 0x%0*X (attempt %d)\n", reg, len * 2,
                     data & mask, len * 2, read_back & mask, attempt + 1);
    }

    DEBUG_PRINTF("Write to reg 0x%02X failed after retries\n", reg);
    return false;
}

/**
 * @brief Read data from a specified register.
 * @param dev Pointer to ad7793_dev_t instance.
 * @param reg Register address to read from.
 * @param data Buffer to store the read data.
 * @param len Data length (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.
 */
bool ad7793_read_reg(ad7793_dev_t *dev, uint8_t reg, uint32_t *data, uint16_t len)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint8_t cmd    = 0;
    uint8_t tmp[4] = {0};

    /* Build communication command: WEN=0, R/W=1(read), register address */
    cmd  = (reg & 0x07) << 3;
    cmd |= COMM_RW;   // 0: Write operation; 1: Read operation
    cmd &= ~COMM_WEN; // write enable bit should be 0 for read operation

    dev->hw_if->gpio_set(dev->cs_pin, false);
    dev->hw_if->spi_read(cmd, tmp, len);
    //ad7793_spi_transfer(dev, cmd, NULL, data, len);
    //dev->hw_if->delay_ms(1);
    dev->hw_if->gpio_set(dev->cs_pin, true);
    if (len == 1)
    {
        *data = tmp[0] << 0;
    }
    else if (len == 2)
    {
        *data = tmp[0] << 8 | tmp[1] << 0;
    }
    else if (len == 3)
    {
        *data = tmp[0] << 16 | tmp[1] << 8 | tmp[2] << 0;
    }

    return true;
}

/**
 * @brief Reset the device.
 * @param dev Pointer to ad7793_dev_t instance.
 * @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.
 */
bool ad7793_reset(ad7793_dev_t *dev)
{
    AD7793_CHECK_INITIALIZED(dev);
    dev->hw_if->gpio_set(dev->cs_pin, false);

    uint8_t reset_seq[4] = {0xFF, 0xFF, 0xFF, 0xFF};
    uint8_t tmp[4];
    dev->hw_if->spi_transfer(reset_seq, tmp, 4);

    dev->hw_if->gpio_set(dev->cs_pin, true);
    dev->hw_if->delay_ms(1); /* Wait for reset to complete */

    return true;
}

/**
 * @brief Set the working mode of the device.
 * @param dev Pointer to ad7793_dev_t instance.
 * @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.
 */
bool ad7793_set_mode(ad7793_dev_t *dev, ad7793_mode_t mode)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint32_t mode_reg = 0;
    ad7793_read_reg(dev, REG_MODE, &mode_reg, 2);
    DEBUG_PRINTF("read current mode: 0x%04X\n", mode_reg);
    /* Clear mode bits */
    mode_reg &= ~(MODE_MD2 | MODE_MD1 | MODE_MD0);

    /* Set new mode */
    switch (mode)
    {
    case AD7793_MODE_CONTINUOUS:
        break; /* Default mode, MD2-MD0=000 */
    case AD7793_MODE_SINGLE:
        mode_reg |= MODE_MD0;
        break;
    case AD7793_MODE_IDLE:
        mode_reg |= MODE_MD1;
        break;
    case AD7793_MODE_POWER_DOWN:
        mode_reg |= MODE_MD1 | MODE_MD0;
        break;
    case AD7793_MODE_INTERNAL_ZERO:
        mode_reg |= MODE_MD2;
        break;
    case AD7793_MODE_INTERNAL_FULL:
        mode_reg |= MODE_MD2 | MODE_MD0;
        break;
    case AD7793_MODE_SYSTEM_ZERO:
        mode_reg |= MODE_MD2 | MODE_MD1;
        break;
    case AD7793_MODE_SYSTEM_FULL:
        mode_reg |= MODE_MD2 | MODE_MD1 | MODE_MD0;
        break;
    }
    DEBUG_PRINTF("[mode-w]set mode: 0x%04X\n", mode_reg);
    dev->cur_mode = mode;

    return ad7793_write_reg(dev, REG_MODE, mode_reg, 2);
}

/**
 * @brief Set the gain of the device.
 * @param dev Pointer to ad7793_dev_t instance.
 * @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.
 */
bool ad7793_set_gain(ad7793_dev_t *dev, ad7793_gain_t gain)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint32_t config_reg = 0;
    ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2);

    // 缓冲/增益策略规范化：BUF=0且G>2时禁止配置
    bool buf_enabled = (config_reg & CONFIG_BUF) ? true : false;
    if (!buf_enabled && gain > 2)
    {
        DEBUG_PRINTF("[ERR] Attempt to set gain >2 while buffer disabled!\n");
        return false;
    }

    /* Clear gain bits (G2/G1/G0) */
    config_reg &= ~(CONFIG_G2 | CONFIG_G1 | CONFIG_G0);

    /* Set new gain (PDF->table 17) */
    switch (gain)
    {
    case AD7793_GAIN_1: // 000
        break;
    case AD7793_GAIN_2: // 001
        config_reg |= CONFIG_G0;
        break;
    case AD7793_GAIN_4: // 010
        config_reg |= CONFIG_G1;
        break;
    case AD7793_GAIN_8: // 011
        config_reg |= CONFIG_G1 | CONFIG_G0;
        break;
    case AD7793_GAIN_16: // 100
        config_reg |= CONFIG_G2;
        break;
    case AD7793_GAIN_32: // 101
        config_reg |= CONFIG_G2 | CONFIG_G0;
        break;
    case AD7793_GAIN_64: // 110
        config_reg |= CONFIG_G2 | CONFIG_G1;
        break;
    case AD7793_GAIN_128: // 111
        config_reg |= CONFIG_G2 | CONFIG_G1 | CONFIG_G0;
        break;
    default:
        DEBUG_PRINTF("Invalid gain value: %d\n", gain);
        return false; // unavailable gain
    }
    DEBUG_PRINTF("[config-w]set gain: 0x%04X\n", config_reg);
    dev->cur_gain = gain;
    return ad7793_write_reg(dev, REG_CONFIG, config_reg, 2);
}

/**
 * @brief Set the channel of the device.
 * @param dev Pointer to ad7793_dev_t instance.
 * @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.
 */
bool ad7793_set_channel(ad7793_dev_t *dev, ad7793_channel_t channel)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint32_t config_reg = 0;
    ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2);

    /* Clear channel bits */
    config_reg &= ~(CONFIG_CH2 | CONFIG_CH1 | CONFIG_CH0);

    /* Set new channel */
    switch (channel)
    {
    case AD7793_CHANNEL_1:
        break; /* CH2-CH0=000 */
    case AD7793_CHANNEL_2:
        config_reg |= CONFIG_CH0;
        break;
    case AD7793_CHANNEL_3:
        config_reg |= CONFIG_CH1;
        break;
    case AD7793_CHANNEL_TEMP:
        config_reg |= CONFIG_CH1 | CONFIG_CH0;
        break;
    case AD7793_CHANNEL_AVDD:
        config_reg |= CONFIG_CH2 | CONFIG_CH1 | CONFIG_CH0;
        break;
    }

    dev->cur_channel = channel;

    DEBUG_PRINTF("[config-w]set channel: 0x%04X\n", config_reg);
    return ad7793_write_reg(dev, REG_CONFIG, config_reg, 2);
}

/**
 * @brief Set the update rate of the device.
 * @param dev Pointer to ad7793_dev_t instance.
 * @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.
 */
bool ad7793_set_rate(ad7793_dev_t *dev, ad7793_rate_t rate)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint32_t mode_reg = 0;
    ad7793_read_reg(dev, REG_MODE, &mode_reg, 2);
    /* Clear rate bits */
    mode_reg &= ~(MODE_FS3 | MODE_FS2 | MODE_FS1 | MODE_FS0);

    /* Set new rate */
    mode_reg |= (uint16_t)rate;

    dev->cur_rate = rate;
    DEBUG_PRINTF("[mode]set rate: 0x%04X\n", mode_reg);
    return ad7793_write_reg(dev, REG_MODE, mode_reg, 2);
}

/**
 * @brief Set the reference source of the device.
 * @param dev Pointer to ad7793_dev_t instance.
 * @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.
 */
bool ad7793_set_reference(ad7793_dev_t *dev, bool use_internal, float external_ref)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint32_t config_reg = 0;
    ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2);

    /* Set reference select bit */
    if (use_internal)
    {
        dev->use_internal_ref  = true;
        config_reg            |= CONFIG_REFSEL;
    }
    else
    {
        dev->external_ref      = external_ref;
        dev->use_internal_ref  = false; /* reflect external reference selection */
        config_reg            &= ~CONFIG_REFSEL;
    }
    DEBUG_PRINTF("[config-w]set reference: 0x%04X\n", config_reg);
    return ad7793_write_reg(dev, REG_CONFIG, config_reg, 2);
}

/**
 * @brief Wait for the conversion to be ready.
 * @param dev Pointer to ad7793_dev_t instance.
 * @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.
 */
bool ad7793_wait_ready(ad7793_dev_t *dev, uint16_t timeout_ms)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint16_t timeout = 0;
    uint32_t status  = 0;

    while (timeout < timeout_ms)
    {
        ad7793_read_reg(dev, REG_STATUS, &status, 1);
        dev->last_status = status;
        if (status & 0x40) // check if the device is in error state
        {
            dev->err_count++;
            if ((dev->err_count % 128u) == 1u) /* 抽样打印，避免刷屏 */
            {
                DEBUG_PRINTF("Device in error state (status=0x%02X) - continue polling\n", status);
                ad7793_dump_registers(dev);
            }
        }
        // check RDY bit（bit7）is zero or not
        if ((status & 0x80) == 0)
        {
            return true; // data is rdy
        }

        dev->hw_if->delay_ms(1);
        timeout++;
    }

    DEBUG_PRINTF("Timeout waiting for data ready\n");
    return false; // timeout
}

/* Diagnostic helper: dump key registers */
void ad7793_dump_registers(ad7793_dev_t *dev)
{
    ad7793_dump_registers_mode(dev, 1);
}

// mode: 0=简略, 1=全量
typedef enum
{
    AD7793_DUMP_SIMPLE = 0,
    AD7793_DUMP_FULL   = 1
} ad7793_dump_mode_t;
void ad7793_dump_registers_mode(ad7793_dev_t *dev, int full)
{
    if (!dev || !dev->hw_if)
        return;
    uint32_t reg = 0;
    ad7793_read_reg(dev, REG_STATUS, &reg, 1);
    DEBUG_PRINTF("DUMP STATUS: 0x%02X\n", (unsigned)reg);
    ad7793_read_reg(dev, REG_MODE, &reg, 2);
    DEBUG_PRINTF("DUMP MODE:   0x%04X\n", (unsigned)reg);
    ad7793_read_reg(dev, REG_CONFIG, &reg, 2);
    DEBUG_PRINTF("DUMP CONFIG: 0x%04X\n", (unsigned)reg);
    if (full)
    {
        ad7793_read_reg(dev, REG_IO, &reg, 1);
        DEBUG_PRINTF("DUMP IO:     0x%02X\n", (unsigned)reg);
        ad7793_read_reg(dev, REG_ID, &reg, 1);
        DEBUG_PRINTF("DUMP ID:     0x%02X\n", (unsigned)reg);
        ad7793_read_reg(dev, REG_OFFSET, &reg, 3);
        DEBUG_PRINTF("DUMP OFFSET: 0x%06X\n", (unsigned)reg);
        ad7793_read_reg(dev, REG_FULLSCALE, &reg, 3);
        DEBUG_PRINTF("DUMP FULLSC: 0x%06X\n", (unsigned)reg);
    }
    // 关键信息摘要
    DEBUG_PRINTF("SUMMARY: mode=%d gain=%d ch=%d ref=%s buf=%d uni=%d\n", dev->cur_mode, dev->cur_gain,
                 dev->cur_channel, dev->use_internal_ref ? "int" : "ext", dev->buffered, dev->unipolar_mode);
}

/**
 * @brief Read the conversion data from the device.
 * @param dev Pointer to ad7793_dev_t instance.
 * @param value Data buffer (3 bytes for AD7793) to store the read data.
 * @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.
 */
bool ad7793_read_data(ad7793_dev_t *dev, uint32_t *value)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint32_t data   = 0;
    bool     status = false;
    if (dev->is_ad7793)
    {
        /* AD7793: 24-bit data, read 3 bytes */
        status = ad7793_read_reg(dev, REG_DATA, &data, 3);
    }
    else
    {
        /* AD7792: 16-bit data, read 2 bytes */
        status = ad7793_read_reg(dev, REG_DATA, &data, 2);
    }
    if (status)
    {
        *value = data;
    }
    return status;
}

/**
 * @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.
 */
float ad7793_convert_to_voltage(ad7793_dev_t *dev, uint32_t adc_code)
{
    AD7793_CHECK_INITIALIZED(dev);
#if (USING_AUTO_READ_CONFIG_BITS == 1)
    // Determine the reference voltage: 1.17V for internal, or user-defined external VREF
    // Read configuration and mode registers from the AD7793 device
    uint32_t config_reg = 0;
    ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2);
    DEBUG_PRINTF("read config: 0x%04X\n", config_reg);

    float vref = (config_reg & CONFIG_REFSEL) ? 1.17f : dev->external_ref;
    // Extract gain bits (G2, G1, G0) from config register and calculate gain = 2^gain_bits
    uint8_t gain_bits  = 0;
    gain_bits         |= (config_reg & CONFIG_G2) ? 0x04 : 0x00;
    gain_bits         |= (config_reg & CONFIG_G1) ? 0x02 : 0x00;
    gain_bits         |= (config_reg & CONFIG_G0) ? 0x01 : 0x00;
    uint8_t gain       = 1 << gain_bits; // Gain = 2^gain_bits (e.g., 1, 2, ..., 128)

    // Determine bipolar mode: if CONFIG_U_B bit is 0, then bipolar is enabled (Offset Binary)
    bool bipolar = ((config_reg & CONFIG_U_B) == 0);

    // Determine if input buffer is enabled (bit CONFIG_BUF)
    bool buffer_enabled = (config_reg & CONFIG_BUF) ? true : false;
#else
    float   vref           = dev->use_internal_ref ? 1.17f : dev->external_ref;
    uint8_t gain           = dev->cur_gain;
    bool    bipolar        = !dev->unipolar_mode;
    bool    buffer_enabled = dev->buffered;
#endif
    // Set resolution: 24-bit for AD7793, 16-bit for AD7792
    uint8_t resolution_bits = dev->is_ad7793 ? 24 : 16;
    // Validate parameters:
    if (vref <= 0.0f)
        return 0.0f; // Invalid reference voltage
    if (!(gain == 1 || gain == 2 || gain == 4 || gain == 8 || gain == 16 || gain == 32 || gain == 64 || gain == 128))
        return 0.0f; // Invalid gain
    if (!(resolution_bits == 16 || resolution_bits == 24))
        return 0.0f; // Invalid resolution
    if (!buffer_enabled && gain > 2)
    {
        DEBUG_PRINTF("Warning: buffer disabled while gain > 2 — result may be invalid\n");
        /* Do not outright fail here; allow conversion but warn the caller. */
    }

    // Check that the ADC code is within valid range
    uint32_t max_code = (resolution_bits == 24) ? 0xFFFFFFu : 0xFFFFu;
    if (adc_code > max_code)
        return 0.0f; // ADC code out of range

    // Begin voltage calculation
    float voltage = 0.0f;

    if (!bipolar)
    {
        // Unipolar mode (Straight Binary)
        // Code range: 0x000000 to 0xFFFFFF
        // Input range: 0V to +VREF / gain
        // Voltage = (ADC_Code / 2^N) * (VREF / Gain)
        uint32_t full_range = (uint32_t)1 << resolution_bits;
        voltage             = (float)((double)adc_code * vref / (gain * (double)full_range));
    }
    else
    {
        // Bipolar mode (Offset Binary)
        // Code range: 0x000000 to 0xFFFFFF
        // Midpoint: 0x800000 (represents 0V)
        // Voltage = ((ADC_Code / 2^(N-1)) - 1) * (VREF / Gain)
        // This maps code range [0x000000, 0xFFFFFF] to voltage range [-VREF/Gain, +VREF/Gain]
        double half_range = (double)((uint32_t)1 << (resolution_bits - 1));
        double code_norm  = (double)adc_code / half_range;
        voltage           = (float)((code_norm - 1.0) * (vref / gain));
    }

    return voltage;
}

/**
 * @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.
 */
uint32_t ad7793_convert_voltage_to_code(ad7793_dev_t *dev, float voltage)
{
    AD7793_CHECK_INITIALIZED(dev);
#if (USING_AUTO_READ_CONFIG_BITS == 1)
    // Read configuration and mode registers from the AD7793 device
    uint32_t config_reg = 0;
    ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2);
    DEBUG_PRINTF("read config: 0x%04X\n", config_reg);

    float vref = (config_reg & CONFIG_REFSEL) ? 1.17f : dev->external_ref;
    // Extract gain bits (G2, G1, G0) from config register and calculate gain = 2^gain_bits
    uint8_t gain_bits  = 0;
    gain_bits         |= (config_reg & CONFIG_G2) ? 0x04 : 0x00;
    gain_bits         |= (config_reg & CONFIG_G1) ? 0x02 : 0x00;
    gain_bits         |= (config_reg & CONFIG_G0) ? 0x01 : 0x00;
    uint8_t gain       = 1 << gain_bits; // Gain = 2^gain_bits (e.g., 1, 2, ..., 128)

    // Determine bipolar mode: if CONFIG_U_B bit is 0, then bipolar is enabled (Offset Binary)
    bool bipolar = ((config_reg & CONFIG_U_B) == 0);

    // Determine if input buffer is enabled (CONFIG_BUF bit)
    bool buffer_enabled = (config_reg & CONFIG_BUF) ? true : false;
#else
    float   vref           = dev->use_internal_ref ? 1.17f : dev->external_ref;
    uint8_t gain           = dev->cur_gain;
    bool    bipolar        = !dev->unipolar_mode;
    bool    buffer_enabled = dev->buffered;
#endif

    // Set resolution: 24-bit for AD7793, 16-bit for AD7792
    uint8_t resolution_bits = dev->is_ad7793 ? 24 : 16;
    // Parameter validation:
    if (vref <= 0.0f)
        return 0; // Invalid reference voltage
    if (!(gain == 1 || gain == 2 || gain == 4 || gain == 8 || gain == 16 || gain == 32 || gain == 64 || gain == 128))
        return 0; // Invalid gain
    if (!(resolution_bits == 16 || resolution_bits == 24))
        return 0; // Invalid resolution
    if (!buffer_enabled && gain > 2)
    {
        DEBUG_PRINTF("Warning: buffer disabled while gain > 2 — code may be invalid\n");
        /* Allow calculation to proceed but warn */
    }

    // Calculate maximum ADC code value
    uint32_t max_code = (resolution_bits == 24) ? 0xFFFFFFu : 0xFFFFu;
    uint32_t adc_code = 0;

    // Voltage to ADC code conversion
    if (!bipolar)
    {
        // Unipolar mode (straight binary)
        // Voltage range: 0V to +VREF / gain
        // ADC code = (voltage * 2^N) / (VREF / gain)
        double full_scale  = (double)vref / gain;
        double code_double = (double)voltage * ((double)max_code + 1) / full_scale;

        // Clamp to valid range
        if (code_double < 0.0)
            adc_code = 0;
        else if (code_double >= (double)max_code)
            adc_code = max_code;
        else
            adc_code = (uint32_t)(code_double + 0.5); // Round to nearest
    }
    else
    {
        // Bipolar mode (offset binary)
        // Voltage range: -VREF/gain to +VREF/gain
        // ADC code = ((voltage / (VREF / gain)) + 1) * 2^(N-1)
        double half_scale         = (double)vref / gain;
        double normalized_voltage = (double)voltage / half_scale;
        double code_double        = (normalized_voltage + 1.0) * ((double)max_code + 1) / 2.0;

        // Clamp to valid range
        if (code_double < 0.0)
            adc_code = 0;
        else if (code_double >= (double)max_code)
            adc_code = max_code;
        else
            adc_code = (uint32_t)(code_double + 0.5); // Round to nearest
    }

    return adc_code;
}

/**
 * @brief Perform internal zero-scale calibration.
 * @param dev Pointer to ad7793_dev_t instance.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function sets the device to internal zero-scale calibration mode, waits for 10 milliseconds for the calibration to complete,
 *         and then sets the device back to continuous conversion mode.
 */
bool ad7793_calibrate_internal_zero(ad7793_dev_t *dev)
{
    AD7793_CHECK_INITIALIZED(dev);
    ad7793_set_mode(dev, AD7793_MODE_INTERNAL_ZERO);
    if (!ad7793_wait_ready(dev, 500)) /* Wait for calibration to complete */
        return false;
    return ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS);
}

/**
 * @brief Perform internal full-scale calibration.
 * @param dev Pointer to ad7793_dev_t instance.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function sets the device to internal full-scale calibration mode, waits for 10 milliseconds for the calibration to complete,
 *         and then sets the device back to continuous conversion mode.
 */
bool ad7793_calibrate_internal_full(ad7793_dev_t *dev)
{
    AD7793_CHECK_INITIALIZED(dev);
    ad7793_set_mode(dev, AD7793_MODE_INTERNAL_FULL);
    /* Wait until RDY clears (calibration completed) or timeout */
    if (!ad7793_wait_ready(dev, 1000))
    {
        DEBUG_PRINTF("Internal full-scale calibration timeout\n");
        return false;
    }
    return ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS);
}

/**
 * @brief Perform system zero-scale calibration.
 * @param dev Pointer to ad7793_dev_t instance.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function sets the device to system zero-scale calibration mode, waits for 10 milliseconds for the calibration to complete,
 *         and then sets the device back to continuous conversion mode.
 */
bool ad7793_calibrate_system_zero(ad7793_dev_t *dev)
{
    AD7793_CHECK_INITIALIZED(dev);
    ad7793_set_mode(dev, AD7793_MODE_SYSTEM_ZERO);
    if (!ad7793_wait_ready(dev, 1000))
    {
        DEBUG_PRINTF("System zero calibration timeout\n");
        return false;
    }
    return ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS);
}

/**
 * @brief Perform system full-scale calibration.
 * @param dev Pointer to ad7793_dev_t instance.
 * @return Operation status. True if successful, false otherwise.
 * @detail This function sets the device to system full-scale calibration mode, waits for 10 milliseconds for the calibration to complete,
 *         and then sets the device back to continuous conversion mode.
 */
bool ad7793_calibrate_system_full(ad7793_dev_t *dev)
{
    AD7793_CHECK_INITIALIZED(dev);
    ad7793_set_mode(dev, AD7793_MODE_SYSTEM_FULL);
    if (!ad7793_wait_ready(dev, 1000))
    {
        DEBUG_PRINTF("System full calibration timeout\n");
        return false;
    }
    return ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS);
}

/**
 * @brief Configure the excitation current source.
 * @param dev Pointer to ad7793_dev_t instance.
 * @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.
 */
bool ad7793_config_iexc(ad7793_dev_t *dev, uint16_t current, uint8_t dir)
{
    AD7793_CHECK_INITIALIZED(dev);
    /* 根据数据手册：IO 寄存器位: [3:2]=IEXCDIR1:0, [1:0]=IEXCEN1:0
       IEXCEN 00=关, 01=10uA, 10=210uA, 11=1mA (常见映射, 若芯片版本不同需核对手册) */
    uint8_t en_bits;
    switch (current)
    {
    case 10:
        en_bits = 0x01;
        break;
    case 210:
        en_bits = 0x02;
        break;
    case 1000:
        en_bits = 0x03;
        break;
    default:
        return false;                     /* unsupported */
    }
    uint8_t dir_bits = (dir & 0x03) << 2; /* 方向/复用: 00=IOUT1->AIN1+, 01=IOUT1&2? 需依据原理图调整 */
    uint8_t io_val   = (uint8_t)(dir_bits | en_bits);
    DEBUG_PRINTF("[io-w] set IEXC current=%u uA dir=0x%X val=0x%02X\n", (unsigned)current, (unsigned)dir, io_val);
    return ad7793_write_reg(dev, REG_IO, io_val, 1);
}

/**
 * @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.
 */
bool ad7793_set_unipolar(ad7793_dev_t *dev, bool unipolar)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint32_t config_reg = 0;

    // Read current configuration register
    ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2);

    // Update the U/B bit (bit 8 in the config register)
    if (unipolar)
    {
        config_reg |= CONFIG_U_B; // Set U/B bit for unipolar mode
    }
    else
    {
        config_reg &= ~CONFIG_U_B; // Clear U/B bit for bipolar mode
    }

    // Save the current mode setting
    dev->unipolar_mode = unipolar;
    DEBUG_PRINTF("[config-w]Unipolar mode set to 0x%04x\n", config_reg);
    // Write updated configuration back to the device
    return ad7793_write_reg(dev, REG_CONFIG, config_reg, 2);
}

/**
 * @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.
 */
bool ad7793_set_buffered(ad7793_dev_t *dev, bool enable)
{
    AD7793_CHECK_INITIALIZED(dev);
    uint32_t config_reg = 0;

    // Read current configuration register value
    ad7793_read_reg(dev, REG_CONFIG, &config_reg, 2);

    // 缓冲/增益策略规范化：若当前增益>2且要关闭缓冲，禁止
    uint8_t gain = dev->cur_gain;
    if (!enable && gain > 2)
    {
        DEBUG_PRINTF("[ERR] Attempt to disable buffer while gain >2!\n");
        return false;
    }

    // Update BUF bit (bit 7 of the configuration register)
    if (enable)
    {
        config_reg |= CONFIG_BUF; // Enable input buffer
    }
    else
    {
        config_reg &= ~CONFIG_BUF; // Disable input buffer
    }

    // Save current buffer mode state
    dev->buffered = enable;
    DEBUG_PRINTF("[config-w]set buffered %04x\n", config_reg);

    // Write updated configuration register
    return ad7793_write_reg(dev, REG_CONFIG, config_reg, 2);
}

/**
 * @brief Initialize the device.
 * @param dev Pointer to ad7793_dev_t instance.
 * @param hw_if Hardware interface structure containing function pointers.
 * @param cs_pin Chip select pin.
 * @param rdy_pin Ready pin
 * @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.
 */
bool ad7793_init(ad7793_dev_t *dev, ad7793_hw_if_t *hw_if, uint8_t cs_pin, const ad7793_config_t *cfg)
{
    if (!dev || !hw_if)
    {
        DEBUG_PRINTF("NULL param in init\n");
        return false;
    }

    dev->hw_if          = hw_if;
    dev->cs_pin         = cs_pin;
    dev->is_initialized = true;
    dev->is_ad7793      = true; /* assume, verify after reset */
    dev->err_count = dev->err_recoveries = dev->low_code_drops = 0;
    dev->last_status                                           = 0;

    /* Pick configuration (simple & predictable) */
    ad7793_config_t local_cfg = cfg ? *cfg : AD7793_DEFAULT_CONFIG;

    /* Reset & small settle */
    ad7793_reset(dev);
    dev->hw_if->delay_ms(2);

    /* Read ID */
    uint32_t id = 0;
    ad7793_read_reg(dev, REG_ID, &id, 1);
    dev->is_ad7793 = ((id & 0x0F) == 0x0B);
    DEBUG_PRINTF("ID: 0x%02X (%s)\n", id, dev->is_ad7793 ? "AD7793" : "Unknown");

    /* Put into continuous mode first */
    if (!ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS))
        return false;

    /* Apply reference before gain (gain affects allowed input span vs ref) */
    if (!ad7793_set_reference(dev, local_cfg.use_internal_ref, local_cfg.external_ref))
        return false;
    if (!ad7793_set_gain(dev, local_cfg.gain))
        return false;
    if (!ad7793_set_channel(dev, local_cfg.channel))
        return false;
    if (!ad7793_set_rate(dev, local_cfg.rate))
        return false;
    if (!ad7793_set_unipolar(dev, local_cfg.unipolar_mode))
        return false;
    if (!ad7793_set_buffered(dev, local_cfg.buffered))
        return false;

    /* Optional calibration */
    if (local_cfg.calibrate_system_zero)
    {
        if (!ad7793_calibrate_system_zero(dev))
            DEBUG_PRINTF("System zero calibration skipped/failed\n");
    }

    /* Quick sanity read (no verbose decode) */
    uint32_t cfg_rd = 0;
    ad7793_read_reg(dev, REG_CONFIG, &cfg_rd, 2);
    DEBUG_PRINTF("CONFIG=0x%04X\n", cfg_rd);
    /* 配置激励电流(可根据实际传感器回路调整) */
    (void)ad7793_config_iexc(dev, 210, 0);
    /* 初次 dump offset/fullscale 辅助调试 */
    ad7793_dump_registers(dev);
    return true;
}

/* Attempt a soft recovery when persistent ERR occurs.
 * Strategy: reset, force internal reference, re-apply basic config, optional system zero cal. */
bool ad7793_recover(ad7793_dev_t *dev, const ad7793_config_t *base_cfg)
{
    if (!dev || !dev->is_initialized)
        return false;
    ad7793_config_t cfg  = base_cfg ? *base_cfg : AD7793_DEFAULT_CONFIG;
    cfg.use_internal_ref = true; /* fallback always internal */
    if (!ad7793_reset(dev))
        return false;
    dev->hw_if->delay_ms(2);
    /* re-id */
    uint32_t id = 0;
    ad7793_read_reg(dev, REG_ID, &id, 1);
    dev->is_ad7793 = ((id & 0x0F) == 0x0B);
    if (!ad7793_set_mode(dev, AD7793_MODE_CONTINUOUS))
        return false;
    if (!ad7793_set_reference(dev, true, 0.0f))
        return false;
    if (!ad7793_set_gain(dev, cfg.gain))
        return false;
    if (!ad7793_set_channel(dev, cfg.channel))
        return false;
    if (!ad7793_set_rate(dev, cfg.rate))
        return false;
    if (!ad7793_set_unipolar(dev, cfg.unipolar_mode))
        return false;
#ifndef AD7793_DEFAULT_CONFIG
#define AD7793_DEFAULT_CONFIG                                                                                \
    {                                                                                                        \
        .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
    if (!ad7793_set_buffered(dev, cfg.buffered))
        return false;
    if (cfg.calibrate_system_zero)
        (void)ad7793_calibrate_system_zero(dev);
    dev->err_recoveries++;
    DEBUG_PRINTF("Recovery done (recoveries=%lu)\n", (unsigned long)dev->err_recoveries);
    return true;
}


