/*
 * adc_global.c
 *
 *  Created on: Jan 12, 2021
 *      Author: David    Original work by Jose (PTDreamer), 2017
 */

#include "adc_global.h"
#include "buzzer.h"
#include "iron.h"
#include "tempsensors.h"
#include "voltagesensors.h"
#include "board.h"


volatile adc_measures_t ADC_measures[ADC_BFSIZ];
volatile ADC_Status_t ADC_Status;

volatile ADCDataTypeDef_t TIP = {
    adc_buffer: &ADC_measures[0].TIP
};

#ifdef USE_VIN
volatile ADCDataTypeDef_t VIN = {
    adc_buffer: &ADC_measures[0].VIN
};
#endif

#ifdef USE_NTC
volatile ADCDataTypeDef_t NTC = {
    adc_buffer: &ADC_measures[0].NTC
};
#endif

#ifdef USE_VREF
volatile ADCDataTypeDef_t VREF = {
    adc_buffer: &ADC_measures[0].VREF
};
#endif

static ADC_HandleTypeDef *adc_device;



uint8_t ADC_Cal(void){
  return HAL_ADCEx_Calibration_Start(adc_device);
}


void ADC_Init(ADC_HandleTypeDef *adc){
  adc_device=adc;
  ADC_ChannelConfTypeDef sConfig = {0};

  #ifdef STM32F072xB
    adc_device->Instance->CHSELR &= ~(0x7FFFF);                                             // Disable all regular channels
    sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  #endif

  #if defined STM32F101xB || defined STM32F102xB || defined STM32F103xB
    adc_device->Init.NbrOfConversion = ADC_Num;
  #endif

  adc_device->Init.ExternalTrigConv = ADC_SOFTWARE_START;                                   // Set software trigger
  if (HAL_ADC_Init(adc_device) != HAL_OK) { Error_Handler(); }

  sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5;                                         // More sampling time to compensate high input impedances

  #ifdef ADC_CH_1ST
    #if defined STM32F101xB || defined STM32F102xB || defined STM32F103xB
      sConfig.Rank = ADC_REGULAR_RANK_1;
    #endif
    sConfig.Channel = ADC_CH_1ST;
    if (HAL_ADC_ConfigChannel(adc_device, &sConfig) != HAL_OK){Error_Handler();}
  #endif

  #ifdef ADC_CH_2ND
    #if defined STM32F101xB || defined STM32F102xB || defined STM32F103xB
      sConfig.Rank = ADC_REGULAR_RANK_2;
    #endif
    sConfig.Channel = ADC_CH_2ND;
    if (HAL_ADC_ConfigChannel(adc_device, &sConfig) != HAL_OK){Error_Handler();}
  #endif

  #ifdef ADC_CH_3RD
    #if defined STM32F101xB || defined STM32F102xB || defined STM32F103xB
      sConfig.Rank = ADC_REGULAR_RANK_3;
    #endif
    sConfig.Channel = ADC_CH_3RD;
    if (HAL_ADC_ConfigChannel(adc_device, &sConfig) != HAL_OK){Error_Handler();}
  #endif

  #ifdef ADC_CH_4TH
  #if defined STM32F101xB || defined STM32F102xB || defined STM32F103xB
    sConfig.Rank = ADC_REGULAR_RANK_4;
  #endif
  sConfig.Channel = ADC_CH_4TH;
  if (HAL_ADC_ConfigChannel(adc_device, &sConfig) != HAL_OK){Error_Handler();}
  #endif

  if(ADC_Cal() != HAL_OK ){
    Error_Handler();
  }

  ADC_Status = ADC_Idle;
  buzzer_short_beep();
}

void ADC_Start_DMA(){

  if(ADC_Status!=ADC_Waiting){
    Error_Handler();
  }
  if( PWM_GPIO_Port->IDR & PWM_Pin ){                                                       // Check if PWM is active
    buzzer_long_beep();                                                                     // Generate warning with a beep
  }
  #ifdef DEBUG_PWM
  HAL_GPIO_WritePin(PWM_DBG_GPIO_Port, PWM_DBG_Pin,1);                                      // Toggle TEST
  #endif
  ADC_Status=ADC_Sampling;
  if(HAL_ADC_Start_DMA(adc_device, (uint32_t*)ADC_measures, sizeof(ADC_measures)/ sizeof(uint16_t) )!=HAL_OK){  // Start ADC conversion now
    Error_Handler();
  }
}


void ADC_Stop_DMA(void){
  HAL_ADC_Stop_DMA(adc_device);
}

/*
 * Some credits: https://kiritchatterjee.wordpress.com/2014/11/10/a-simple-digital-low-pass-filter-in-c/
 */
void DoAverage(volatile ADCDataTypeDef_t* InputData){
  volatile uint16_t *inputBuffer=InputData->adc_buffer;
  uint32_t adc_sum,avg_data;
  uint16_t max=0, min=0xffff;
  uint8_t shift;

  InputData->prev_avg=InputData->last_avg;
  InputData->prev_raw=InputData->last_raw;

  // Make the average of the ADC buffer
  adc_sum = 0;
  for(uint16_t x = 0; x < ADC_BFSIZ; x++) {
    adc_sum += *inputBuffer;
    if(*inputBuffer > max){
      max = *inputBuffer;
    }
    if(*inputBuffer < min){
      min = *inputBuffer;
    }
    inputBuffer += ADC_Num;
  }
  //Remove highest and lowest values
  adc_sum -=  (min + max);

  // Calculate average
  avg_data = adc_sum / (ADC_BFSIZ -2) ;
  InputData->last_raw = avg_data;

  if(systemSettings.Profile.filterFactor > 0) {                                             // Advanced filtering enabled?

    if(systemSettings.Profile.filterFactor>8){                                              // Limit coefficient, more than 8 will cause overflow
      systemSettings.Profile.filterFactor=8;
    }
    shift = systemSettings.Profile.filterFactor;                                            // Set EMA factor setting from system settings

    // Fixed point shift
    uint32_t RawData = avg_data << 12;

    // Compute EMA of input
#define LIMIT_FILTERING

#ifdef LIMIT_FILTERING
#define SMOOTH_START  50       // Start difference to apply partial filtering override
#define SMOOTH_END    150      // Max difference to completely override filter
#define SMOOTH_DIFF  (SMOOTH_END-SMOOTH_START)
#if defined DEBUG_PWM && SWO_PRINT
    extern bool dbg_newData;
#endif

    int32_t diff = (int32_t)avg_data - (int32_t)(InputData->EMA_of_Input>>12);              // Check difference between stored EMA and last average
    int32_t abs_diff=abs(diff);

    if(abs_diff>SMOOTH_END){                                                                // If huge (Filtering will delay too much the response)
      InputData->EMA_of_Input = RawData;                                                    // Reset filter
      #if defined DEBUG_PWM && SWO_PRINT
      dbg_newData=1;                                                                        // Enable flag to debug the data
      #endif
    }
    else if(abs_diff>SMOOTH_START){                                                         // If medium, smoothen the difference
      InputData->EMA_of_Input += ((diff*(abs_diff-SMOOTH_START))/SMOOTH_DIFF)<<12;
      #if defined DEBUG_PWM && SWO_PRINT
      //dbg_newData=1;                                                                      // Meh, just some noise, not important
      #endif
    }
    else{
      InputData->EMA_of_Input = ( ((InputData->EMA_of_Input << shift) - InputData->EMA_of_Input) + RawData +(1<<(shift-1)))>>shift;
    }
#else
    InputData->EMA_of_Input = ( ((InputData->EMA_of_Input << shift) - InputData->EMA_of_Input) + RawData +(1<<(shift-1)))>>shift;
#endif
    InputData->last_avg = InputData->EMA_of_Input>>12;
  }
  else {
    InputData->last_avg=avg_data;
  }
}

uint16_t ADC_to_mV (uint16_t adc){
  /*
   * Instead running ( ADC*(3300/4095) ),
   * We previously multiply (3300/4095)*2^20 = 845006
   * Then we can use the fast hardware multiplier and
   * divide just with bit rotation.
   *
   * So it becomes Vadc = (ADC * 845006) >>20
   * Max possible input = 20 bit number, more will cause overflow to the 32 bit variable
   * Calculated to use  12 bit max input from ADC (4095)
   * Much, much faster than floats!
   */

  return( ((uint32_t)845006*adc)>>20 );
}


// Don't call this function, only the ADC ISR should use it.
void handle_ADC_Data(void){
  DoAverage(&TIP);
  #ifdef USE_VREF
  DoAverage(&VREF);
  #endif
  #ifdef USE_NTC
  DoAverage(&NTC);
  #endif
  #ifdef USE_VIN
  DoAverage(&VIN);
  #endif
}


void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* _hadc){

#if defined DEBUG_PWM && SWO_PRINT
    extern bool dbg_newData;
    extern uint16_t dbg_prev_TIP_Raw, dbg_prev_TIP, dbg_prev_VIN, dbg_prev_PWR;
    extern int16_t dbg_prev_NTC;
    bool dbg_t=dbg_newData;
#endif

  if(_hadc == adc_device){
    if(ADC_Status!=ADC_Sampling){
      Error_Handler();
    }
    ADC_Stop_DMA();                                                                         // Reset the ADC
    ADC_Status = ADC_Idle;


    HAL_IWDG_Refresh(&hiwdg);                                                               // This is the main reset of the watchdog
                                                                                            // If anything critical stalls (PWM, ADC, hanlleIron) this won't be updated anymore
                                                                                            // causing a system reset

    if( PWM_GPIO_Port->IDR & PWM_Pin ){                                                     // If PWM is active
      buzzer_long_beep();                                                                   // Beep to warn the issue, skip adc
      TIP.last_avg = systemSettings.Profile.noIronValue - 1;                                // Set reading = max temperature to force PID to throttle down
      TIP.last_raw = systemSettings.Profile.noIronValue - 1;                                // But avoiding no iron error
    }
    else{
      handle_ADC_Data();                                                                    // Else, update data normally
    }

#if defined DEBUG_PWM && defined SWO_PRINT
    if(dbg_t!=dbg_newData){                                                                 // Save values before handleIron() updates them
      dbg_prev_TIP_Raw=last_TIP_Raw;                                                        // If filter was resetted, print values
      dbg_prev_TIP=last_TIP;
      dbg_prev_VIN=last_VIN;
      dbg_prev_NTC=last_NTC;
      dbg_prev_PWR=Iron.CurrentIronPower;
    }
#endif

    handleIron();                                                                           // Handle iron

    runAwayCheck();                                                                         // Check runaway condition

    #ifdef DEBUG_PWM
    HAL_GPIO_WritePin(PWM_DBG_GPIO_Port, PWM_DBG_Pin,0);                                    // Toggle TEST
    #endif
  }
}
