/***************************************
Project:     OpAmp Tester
File:        ProgPowerSupply (PPS)
Version:     3.0 - May 2026
Written by:  diy-electronic.ru
****************************************
Version history
  1.0 Mar 2025 Initial version
  2.1 Dec 2025 Changes related with
               calibration procedures
  3.0 May 2026 Minor fixes & improvements
****************************************/
#include <Adafruit_MCP4725.h>

// Two DACs in programmable power supply:
// with following I2C addresses:
//   0x60 (ADDR pin tied to GND) - in V+ channel
//   0x61 (ADDR pin tied to VCC) - in V- channel
#define DACPLUS_I2C_ADDR 0x61
#define DACMINUS_I2C_ADDR 0x60

Adafruit_MCP4725 dacPlus;
Adafruit_MCP4725 dacMinus;

//--------------------------------------
// Public functions for external calls |
//--------------------------------------

/************************************/
/* Programmable power supply init.  */
/* Initialize DACs in both channels */
/************************************/
void ppsInit() {
#ifdef __DEBUG__
  bool success = false;
  success = dacPlus.begin(DACPLUS_I2C_ADDR);
  Serial.print("PPS+: init ");
  succeeded_or_failed(success);
  success = dacMinus.begin(DACMINUS_I2C_ADDR);
  Serial.print("PPS-: init ");
  succeeded_or_failed(success);
#else
  dacPlus.begin(DACPLUS_I2C_ADDR);
  dacMinus.begin(DACMINUS_I2C_ADDR);
#endif
  ppsReset(true); // Set 0.0V in both channels and
                  // store zero values in DAC EEPROM
}

#ifdef __DEBUG__
void succeeded_or_failed(bool Yes) {
  if (Yes) {
    Serial.println("success.");
  }
  else {
    Serial.println("failure.");
  }
}
#endif

/**************************************/
/* Programmable power supply reset.   */
/* Set voltage in both channels to 0. */
/*   writeEEPROM - keep zero value in */
/*                 EEPROM if true     */
/**************************************/
void ppsReset(bool writeEEPROM) {
  ppsSetVoltage(channelPlus, 0.0, writeEEPROM);
  ppsSetVoltage(channelMinus, 0.0, writeEEPROM);
}

/******************************************/
/* Set output voltage of Programmable     */
/* power supply by voltageCode.           */
/*  Channel - either channelPlus or       */
/*            channelMinus                */
/*  voltageCode - 0...4095 for 12-bit DAC */
/*  Eol - print end-of-line to Serial     */
/*        monitor in debug mode           */
/*  writeEEPROM - keep value in internal  */
/*                non-volatile memory     */
/* Returns: true - success, false - error */
/******************************************/
bool ppsSetVoltageCode(ppsChannel Channel, uint16_t voltageCode, bool Eol, bool writeEEPROM) {
  bool success = false;
  switch (Channel) {
    case channelPlus:
      success = dacPlus.setVoltage(voltageCode, writeEEPROM);
      break;
    case channelMinus:
      success = dacMinus.setVoltage(voltageCode, writeEEPROM);
      break;
  }
#ifdef __DEBUG__
  Serial.print("PPS" + String((Channel == channelPlus) ? "+" : "-") + ":");
  if (success) {
    Serial.print(String(" [") + voltageCode + "] ");
    if (Eol) {
      Serial.println("");
    }
  }
  else {
    Serial.println(" error.");
  }
#endif
  return success;
}

/******************************************/
/*  Set output voltage of Programmable    */
/*  power supply by voltage.              */
/*  Channel     - either channelPlus      */
/*                or channelMinus         */
/*  voltage     - 0...13.5                */
/*  writeEEPROM - keep value in internal  */
/*                non-volatile memory     */
/******************************************/
void ppsSetVoltage(ppsChannel Channel, float voltage, bool writeEEPROM) {
#ifdef __DEBUG__
  bool success = ppsSetVoltageCode(Channel, ppsVoltageToCode(Channel, voltage), false, writeEEPROM);
  if (success) {
    Serial.print(voltage,1);
    Serial.println("V");
  }
#else
  ppsSetVoltageCode(Channel, ppsVoltageToCode(Channel, voltage), false, writeEEPROM);
#endif
}

/************************************************/
/* Conversion: voltage --> voltageCode:         */
/*   voltageCode = voltage * ppsGain + ppsZero  */
/* where:                                       */
/*  Channel - (enum) either channelPlus or      */
/*            channelMinus                      */
/*  voltage - (float) channel output in Volts   */
/*            0...13.50                         */
/* Returns DAC register value 0...4095          */
/************************************************/
uint16_t ppsVoltageToCode(ppsChannel Channel, float voltage) {
  float ppsZero = 0.0, ppsGain = 0.0;
  switch (Channel) {
    case channelPlus:
      ppsZero = ActiveSettings.ppsPlusZero;
      ppsGain = ActiveSettings.ppsPlusGain;
      break;
    case channelMinus:
      ppsZero = ActiveSettings.ppsMinusZero;
      ppsGain = ActiveSettings.ppsMinusGain;
      break;
  }
  return (uint16_t) (voltage * ppsGain + ppsZero + 0.5); // Add 0.5 to ensure correct 
                                                         // rounding of the value after 
                                                         // conversion to uint16_t.
}
