view Discovery/Src/data_central.c @ 945:aad1a6b9aaec Evo_2_23

Added slow exit graph to t3 view: In the first implementation slow exit was only available in T7 view. To enable it in T3 view the common parts have been extracted into a separate function which is shared between T7 and T3. Only the drawing specific parts remain in the T7 / T3 files.
author Ideenmodellierer
date Thu, 19 Dec 2024 22:16:36 +0100
parents 44599695df41
children c6b858f2e025
line wrap: on
line source

/**
  ******************************************************************************
	* @copyright heinrichs weikamp
  * @file   		data_central.c
  * @author 		heinrichs weikamp gmbh
  * @date   		10-November-2014
  * @version		V1.0.2
  * @since			10-Nov-2014
  * @brief			All the data EXCEPT
  *							 - settings (settings.c)
	*									feste Werte, die nur an der Oberfl�che ge�ndert werden
	*							 - dataIn and dataOut (data_exchange.h and data_exchange_main.c)
	*									Austausch mit Small CPU
	* @bug
	* @warning
  @verbatim
  ==============================================================================
              ##### SDiveState Real and Sim #####
  ==============================================================================
  [..] SDiveSettings
				copy of parts of Settings that are necessary during the dive
				and could be modified during the dive without post dive changes.

  [..] SLifeData
				written in DataEX_copy_to_LifeData();
				block 1 "lifedata" set by SmallCPU in stateReal
				block 2 "actualGas" set by main CPU from user input and send to Small CPU
				block 3 "calculated data" set by main CPU based on "lifedata"

  [..] SVpm

	[..] SEvents

  [..] SDecoinfo

  [..] mode
				set by SmallCPU in stateReal, can be surface, dive, ...

  [..] data_old__lost_connection_to_slave
				set by DataEX_copy_to_LifeData();

  ==============================================================================
              ##### SDiveState Deco #####
  ==============================================================================
  [..] kjbkldafj�lasdfjasdf

  ==============================================================================
              ##### decoLock #####
  ==============================================================================
  [..] The handler that synchronizes the data between IRQ copy and main deco loop


	 @endverbatim
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; COPYRIGHT(c) 2015 heinrichs weikamp</center></h2>
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include <math.h>
#include "data_central.h"
#include "calc_crush.h"
#include "decom.h"
#include "stm32f4xx_hal.h"
#include "settings.h"
#include "data_exchange_main.h"
#include "ostc.h" // for button adjust on hw testboard 1
#include "tCCR.h"
#include "crcmodel.h"
#include "configuration.h"
#include "tHome.h"
#include "t3.h"

static SDiveState stateReal = { 0 };
SDiveState stateSim = { 0 };
SDiveState stateDeco = { 0 };

static SDevice stateDevice =
{
	/* max is 0x7FFFFFFF, min is 0x80000000 but also defined in stdint.h :-) */

	/* count, use 0 */
	.batteryChargeCompleteCycles.value_int32 = 0,
	.batteryChargeCycles.value_int32 = 0,
	.diveCycles.value_int32 = 0,
	.hoursOfOperation.value_int32 = 0,

	/* max values, use min. */
	.temperatureMaximum.value_int32 = INT32_MIN,
	.depthMaximum.value_int32 = INT32_MIN,

	/* min values, use max. */
	.temperatureMinimum.value_int32 = INT32_MAX,
	.voltageMinimum.value_int32 = INT32_MAX,
};

static SVpmRepetitiveData stateVPM =
{
	.repetitive_variables_not_valid = 1,
	.is_data_from_RTE_CPU = 0,
};

const SDiveState *stateUsed = &stateReal;
SDiveState *stateUsedWrite = &stateReal;


#define COMPASS_FRACTION		(4.0f)		/* delay till value changes to new actual */

static float compass_compensated = 0;

void set_stateUsedToReal(void)
{
	stateUsed = stateUsedWrite = &stateReal;
}

void set_stateUsedToSim(void)
{
	stateUsed = stateUsedWrite = &stateSim;
}

_Bool is_stateUsedSetToSim(void)
{
	return stateUsed == &stateSim;
}

const SDiveState * stateRealGetPointer(void)
{
	return &stateReal;
}

SDiveState * stateRealGetPointerWrite(void)
{
	return &stateReal;
}


const SDiveState * stateSimGetPointer(void)
{
	return &stateSim;
}


SDiveState * stateSimGetPointerWrite(void)
{
	return &stateSim;
}


const SDevice * stateDeviceGetPointer(void)
{
	return &stateDevice;
}


SDevice * stateDeviceGetPointerWrite(void)
{
	return &stateDevice;
}


const SVpmRepetitiveData * stateVpmRepetitiveDataGetPointer(void)
{
	return &stateVPM;
}


SVpmRepetitiveData * stateVpmRepetitiveDataGetPointerWrite(void)
{
	return &stateVPM;
}


uint32_t time_elapsed_ms(uint32_t ticksstart,uint32_t ticksnow)
{
	if(ticksstart <= ticksnow)
		return ticksnow - ticksstart;
	else
		return 0xFFFFFFFF - ticksstart + ticksnow;
}


uint8_t decoLock = DECO_CALC_undefined;

static int descent_rate_meter_per_min  = 20;
static int max_depth = 70;
static int bottom_time = 10;

_Bool vpm_crush(SDiveState* pDiveState);
void setSimulationValues(int _ascent_rate_meter_per_min, int _descent_rate_meter_per_min, int _max_depth, int _bottom_time )
{
    descent_rate_meter_per_min = _descent_rate_meter_per_min;
    max_depth = _max_depth;
    bottom_time = _bottom_time;
}

int current_second(void) {

    return HAL_GetTick() / 1000;
}

#define OXY_ONE_SIXTIETH_PART 			0.0166667f

uint8_t calc_MOD(uint8_t gasId)
{
	int16_t oxygen, maxppO2, result;
	SSettings *pSettings;

	pSettings = settingsGetPointer();

	oxygen = (int16_t)(pSettings->gas[gasId].oxygen_percentage);

	if(pSettings->gas[gasId].note.ub.deco > 0)
		maxppO2 =(int16_t)(pSettings->ppO2_max_deco);
	else
		maxppO2 =(int16_t)(pSettings->ppO2_max_std);

	result = 10 *  maxppO2;
	result /= oxygen;
	result -= 10;

	if(result < 0)
		return 0;

	if(result > 255)
		return 255;

	return result;
}

float get_ambiant_pressure_simulation(long dive_time_seconds, float surface_pressure_bar )
{
  static
    long descent_time;
    float depth_meter;

    descent_time = 60 * max_depth / descent_rate_meter_per_min;

    if(dive_time_seconds <= descent_time)
    {
        depth_meter = ((float)(dive_time_seconds * descent_rate_meter_per_min)) / 60;
        return surface_pressure_bar + depth_meter / 10;
    }
    //else if(dive_time_seconds <= (descent_time + bottom_time * 60))
    return surface_pressure_bar + max_depth / 10;



}

void UpdateLifeDataTest(SDiveState * pDiveState)
{
    static int last_second = -1;
    int now =  current_second();
    if(last_second == now)
        return;
    last_second = now;

    pDiveState->lifeData.dive_time_seconds += 1;
    pDiveState->lifeData.pressure_ambient_bar = get_ambiant_pressure_simulation(pDiveState->lifeData.dive_time_seconds,pDiveState->lifeData.pressure_surface_bar);

    pDiveState->lifeData.depth_meter = (pDiveState->lifeData.pressure_ambient_bar - pDiveState->lifeData.pressure_surface_bar) * 10.0f;
		if(pDiveState->lifeData.max_depth_meter < pDiveState->lifeData.depth_meter)
				pDiveState->lifeData.max_depth_meter = pDiveState->lifeData.depth_meter;
    decom_tissues_exposure(1, &pDiveState->lifeData);
    pDiveState->lifeData.ppO2 = decom_calc_ppO2( pDiveState->lifeData.pressure_ambient_bar, &pDiveState->lifeData.actualGas);
    decom_oxygen_calculate_cns(& pDiveState->lifeData.cns, pDiveState->lifeData.ppO2);

    vpm_crush(pDiveState);
}


_Bool vpm_crush(SDiveState* pDiveState)
{
    int i = 0;
    static float starting_ambient_pressure = 0;
    static float ending_ambient_pressure = 0;
    static float time_calc_begin = -1;
	static float initial_helium_pressure[16];
	static float initial_nitrogen_pressure[16];
	ending_ambient_pressure = pDiveState->lifeData.pressure_ambient_bar * 10;

	if((pDiveState->lifeData.dive_time_seconds <= 4) || (starting_ambient_pressure >= ending_ambient_pressure))
	{
		time_calc_begin = pDiveState->lifeData.dive_time_seconds;
		starting_ambient_pressure = pDiveState->lifeData.pressure_ambient_bar * 10;
		for( i = 0; i < 16; i++)
		{
			initial_helium_pressure[i] = pDiveState->lifeData.tissue_helium_bar[i] * 10;
			initial_nitrogen_pressure[i] = pDiveState->lifeData.tissue_nitrogen_bar[i] * 10;
		}
		return false;
	}
	if(pDiveState->lifeData.dive_time_seconds - time_calc_begin >= 4)
	{
		if(ending_ambient_pressure > starting_ambient_pressure + 0.5f)
		{
			float rate = (ending_ambient_pressure - starting_ambient_pressure) * 60 / 4;
			calc_crushing_pressure(&pDiveState->lifeData, &pDiveState->vpm, initial_helium_pressure, initial_nitrogen_pressure, starting_ambient_pressure, rate);

			time_calc_begin = pDiveState->lifeData.dive_time_seconds;
			starting_ambient_pressure = pDiveState->lifeData.pressure_ambient_bar * 10;
			for( i = 0; i < 16; i++)
			{
				initial_helium_pressure[i] = pDiveState->lifeData.tissue_helium_bar[i] * 10;
				initial_nitrogen_pressure[i] =  pDiveState->lifeData.tissue_nitrogen_bar[i] * 10;
			}

			return true;
		}

	}
	return false;
};


void createDiveSettings(void)
{
	int i;
	SSettings* pSettings = settingsGetPointer();

	stateReal.diveSettings.compassHeading = pSettings->compassBearing;
	stateReal.diveSettings.ascentRate_meterperminute = 10;

	stateReal.diveSettings.diveMode = pSettings->dive_mode;
	stateReal.diveSettings.CCR_Mode = pSettings->CCR_Mode;
	if((stateReal.diveSettings.diveMode == DIVEMODE_PSCR) && (stateReal.diveSettings.CCR_Mode == CCRMODE_FixedSetpoint))
	{
		/* TODO: update selection of sensor used on/off (currently sensor/fixpoint). As PSCR has no fixed setpoint change to simulated ppo2 if sensors are not active */
		stateReal.diveSettings.CCR_Mode = CCRMODE_Simulation;
	}

	if(isLoopMode(stateReal.diveSettings.diveMode))
		stateReal.diveSettings.ccrOption = 1;
	else
		stateReal.diveSettings.ccrOption = 0;
	memcpy(stateReal.diveSettings.gas, pSettings->gas,sizeof(pSettings->gas));
	memcpy(stateReal.diveSettings.setpoint, pSettings->setpoint,sizeof(pSettings->setpoint));

	setActualGasFirst(&stateReal.lifeData);

	stateReal.diveSettings.gf_high = pSettings->GF_high;
	stateReal.diveSettings.gf_low = pSettings->GF_low;
	stateReal.diveSettings.input_next_stop_increment_depth_bar = ((float)pSettings->stop_increment_depth_meter) / 10.0f;
	stateReal.diveSettings.last_stop_depth_bar = ((float)pSettings->last_stop_depth_meter) / 10.0f;
	stateReal.diveSettings.vpm_conservatism = pSettings->VPM_conservatism.ub.standard;
	stateReal.diveSettings.vpm_tableMode = pSettings->VPM_conservatism.ub.alternative;
	stateReal.diveSettings.deco_type.uw = pSettings->deco_type.uw;
	stateReal.diveSettings.fallbackOption = pSettings->fallbackToFixedSetpoint;
	stateReal.diveSettings.ppo2sensors_deactivated = pSettings->ppo2sensors_deactivated;
	stateReal.diveSettings.future_TTS_minutes = pSettings->future_TTS;
	
	stateReal.diveSettings.pscr_lung_ratio = pSettings->pscr_lung_ratio;
	stateReal.diveSettings.pscr_o2_drop = pSettings->pscr_o2_drop;

	if(stateReal.diveSettings.diveMode == DIVEMODE_PSCR)
	{
		for(i=0; i<5; i++)
		{
			stateReal.diveSettings.decogaslist[i].pscr_factor = 1.0 / stateReal.diveSettings.pscr_lung_ratio * stateReal.diveSettings.pscr_o2_drop;
		}
	}

	decom_CreateGasChangeList(&stateReal.diveSettings, &stateReal.lifeData); // decogaslist
	stateReal.diveSettings.internal__pressure_first_stop_ambient_bar_as_upper_limit_for_gf_low_otherwise_zero = 0;

	/* for safety */
	stateReal.diveSettings.input_second_to_last_stop_depth_bar = stateReal.diveSettings.last_stop_depth_bar + stateReal.diveSettings.input_next_stop_increment_depth_bar;
	/* and the proper calc */
	for(i = 1; i <10; i++)
	{
		if(stateReal.diveSettings.input_next_stop_increment_depth_bar * i > stateReal.diveSettings.last_stop_depth_bar)
		{
			 stateReal.diveSettings.input_second_to_last_stop_depth_bar = stateReal.diveSettings.input_next_stop_increment_depth_bar * i;
			 break;
		}
	}
	/* generate Bitfield of active T3 views */
	stateReal.diveSettings.activeAFViews = 0;
	if(t3_customview_disabled(CVIEW_T3_Navigation) == 0)
	{
		stateReal.diveSettings.activeAFViews |= (1 << CVIEW_T3_Navigation);
	}
	if(t3_customview_disabled(CVIEW_T3_GasList) == 0)
	{
		stateReal.diveSettings.activeAFViews |= (1 << CVIEW_T3_GasList);
	}
	if(t3_customview_disabled(CVIEW_T3_DecoTTS) == 0)
	{
		stateReal.diveSettings.activeAFViews |= (1 << CVIEW_T3_DecoTTS);
	}
}


void copyDiveSettingsToSim(void)
{
	memcpy(&stateSim, &stateReal, sizeof(stateReal));
}


void copyVpmRepetetiveDataToSim(void)
{
	SDiveState * pSimData = stateSimGetPointerWrite();
	const SVpmRepetitiveData * pVpmData = stateVpmRepetitiveDataGetPointer();
	
	if(pVpmData->is_data_from_RTE_CPU)
	{
		for(int i=0; i<16;i++)
		{
			pSimData->vpm.adjusted_critical_radius_he[i] = pVpmData->adjusted_critical_radius_he[i];
			pSimData->vpm.adjusted_critical_radius_n2[i] = pVpmData->adjusted_critical_radius_n2[i];

			pSimData->vpm.adjusted_crushing_pressure_he[i] = pVpmData->adjusted_crushing_pressure_he[i];
			pSimData->vpm.adjusted_crushing_pressure_n2[i] = pVpmData->adjusted_crushing_pressure_n2[i];

			pSimData->vpm.initial_allowable_gradient_he[i] = pVpmData->initial_allowable_gradient_he[i];
			pSimData->vpm.initial_allowable_gradient_n2[i] = pVpmData->initial_allowable_gradient_n2[i];

			pSimData->vpm.max_actual_gradient[i] = pVpmData->max_actual_gradient[i];
		}
		pSimData->vpm.repetitive_variables_not_valid = pVpmData->repetitive_variables_not_valid;
	}
}


void updateSetpointStateUsed(void)
{
	if(!isLoopMode(stateUsed->diveSettings.diveMode))
	{
		stateUsedWrite->lifeData.actualGas.setPoint_cbar = 0;
		stateUsedWrite->lifeData.ppO2 = decom_calc_ppO2(stateUsed->lifeData.pressure_ambient_bar, &stateUsed->lifeData.actualGas);
	}
	else
	{
		if(stateUsed->diveSettings.CCR_Mode == CCRMODE_Sensors)
		{
			stateUsedWrite->lifeData.actualGas.setPoint_cbar = get_ppO2SensorWeightedResult_cbar();
		}
#ifdef ENABLE_PSCR_MODE
		if(stateUsed->diveSettings.diveMode == DIVEMODE_PSCR)	/* calculate a ppO2 value based on assumptions ( transfered approach from hwos code) */
		{
			stateUsedWrite->lifeData.ppo2Simulated_bar = decom_calc_SimppO2_O2based(stateUsed->lifeData.pressure_ambient_bar, stateReal.diveSettings.gas[stateUsed->lifeData.actualGas.GasIdInSettings].oxygen_percentage, stateUsed->lifeData.actualGas.pscr_factor);
			if(stateUsed->diveSettings.CCR_Mode == CCRMODE_Simulation)
			{
				stateUsedWrite->lifeData.actualGas.setPoint_cbar = stateUsedWrite->lifeData.ppo2Simulated_bar * 100;
			}
		}
#endif
	/* limit calculated value to the physically possible if needed */
		if((stateUsed->lifeData.pressure_ambient_bar * 100) < stateUsed->lifeData.actualGas.setPoint_cbar)
			stateUsedWrite->lifeData.ppO2 = stateUsed->lifeData.pressure_ambient_bar;
		else
			stateUsedWrite->lifeData.ppO2 = ((float)stateUsed->lifeData.actualGas.setPoint_cbar) / 100;
	}
}

void setActualGasFirst(SLifeData *lifeData)
{
	SSettings* pSettings = settingsGetPointer();
	uint8_t start = 0;
	uint8_t gasId = 0;
	uint8_t setpoint_cbar = 0;

	if(isLoopMode(pSettings->dive_mode))
	{
		setpoint_cbar = pSettings->setpoint[1].setpoint_cbar;
		start = NUM_OFFSET_DILUENT+1;
	}
	else
	{
		setpoint_cbar = 0;
		start = 1;
	}

	gasId = start;
	for(int i=start;i<=NUM_GASES+start;i++)
	{
		if(pSettings->gas[i].note.ub.first)
		{
			gasId = i;
			break;
		}
	}
	setActualGas(lifeData, gasId, setpoint_cbar);

    lifeData->setpointDecoActivated = false;
    lifeData->setpointLowDelayed = false;
}

void setActualGasAir(SLifeData *lifeData)
{
	uint8_t nitrogen;
	nitrogen = 79;
	lifeData->actualGas.GasIdInSettings = 0;
	lifeData->actualGas.nitrogen_percentage = nitrogen;
	lifeData->actualGas.helium_percentage =0;
	lifeData->actualGas.setPoint_cbar = 0;
	lifeData->actualGas.change_during_ascent_depth_meter_otherwise_zero = 0;
	lifeData->actualGas.AppliedDiveMode = stateUsed->diveSettings.diveMode;
}


void setActualGas(SLifeData *lifeData, uint8_t gasId, uint8_t setpoint_cbar)
{
	SSettings* pSettings = settingsGetPointer();
	uint8_t nitrogen;

	nitrogen = 100;
	nitrogen -= pSettings->gas[gasId].oxygen_percentage;
	nitrogen -= pSettings->gas[gasId].helium_percentage;

	lifeData->actualGas.GasIdInSettings = gasId;
	lifeData->actualGas.nitrogen_percentage = nitrogen;
	lifeData->actualGas.helium_percentage = pSettings->gas[gasId].helium_percentage;
	lifeData->actualGas.setPoint_cbar = setpoint_cbar;
	lifeData->actualGas.change_during_ascent_depth_meter_otherwise_zero = 0;
	lifeData->actualGas.AppliedDiveMode = stateUsed->diveSettings.diveMode;
	lifeData->actualGas.pscr_factor = 1.0 / pSettings->pscr_lung_ratio * pSettings->pscr_o2_drop;
	if (isLoopMode(pSettings->dive_mode) && gasId > NUM_OFFSET_DILUENT) {
		lifeData->lastDiluent_GasIdInSettings = gasId;
        lifeData->lastSetpointChangeDepthM = lifeData->depth_meter;
    }
}


void setActualGas_DM(SLifeData *lifeData, uint8_t gasId, uint8_t setpoint_cbar)
{
    if(stateUsed->diveSettings.ccrOption && gasId < 6)
    {
      if(lifeData->actualGas.GasIdInSettings != gasId)
      {
        SSettings* pSettings = settingsGetPointer();
        stateUsedWrite->events.bailout = 1;
        stateUsedWrite->events.info_bailoutO2 = pSettings->gas[gasId].oxygen_percentage;
        stateUsedWrite->events.info_bailoutHe = pSettings->gas[gasId].helium_percentage;
      }
    }
    else
    {
      if(lifeData->actualGas.GasIdInSettings != gasId)
      {
    	  stateUsedWrite->events.gasChange = 1;
    	  stateUsedWrite->events.info_GasChange = gasId;
      }
      if(	lifeData->actualGas.setPoint_cbar != setpoint_cbar)
      {
				// setPoint_cbar = 255 -> change to sensor mode
    	  stateUsedWrite->events.setpointChange = 1;
    	  stateUsedWrite->events.info_SetpointChange = setpoint_cbar;
      }
    }
	setActualGas(lifeData, gasId, setpoint_cbar);
}

void setActualGas_ExtraGas(SLifeData *lifeData, uint8_t oxygen, uint8_t helium, uint8_t setpoint_cbar)
{
	uint8_t nitrogen;

	nitrogen = 100;
	nitrogen -= oxygen;
	nitrogen -= helium;


	if ((lifeData->actualGas.nitrogen_percentage != nitrogen) || (lifeData->actualGas.helium_percentage != helium) || lifeData->actualGas.AppliedDiveMode != DIVEMODE_OC)
    {
       if (stateUsed->diveSettings.ccrOption) {
            stateUsedWrite->events.bailout = 1;
            stateUsedWrite->events.info_bailoutHe = helium;
            stateUsedWrite->events.info_bailoutO2 = oxygen;
        } else {
            stateUsedWrite->events.manualGasSet = 1;
            stateUsedWrite->events.info_manualGasSetHe = helium;
            stateUsedWrite->events.info_manualGasSetO2 = oxygen;
        }
    }

    if(	lifeData->actualGas.setPoint_cbar != setpoint_cbar)
    {
    	stateUsedWrite->events.setpointChange = 1;
    	stateUsedWrite->events.info_SetpointChange = setpoint_cbar;
    }
  lifeData->actualGas.GasIdInSettings = 0;
  lifeData->actualGas.nitrogen_percentage = nitrogen;
  lifeData->actualGas.helium_percentage = helium;
  lifeData->actualGas.setPoint_cbar = setpoint_cbar;
  lifeData->actualGas.change_during_ascent_depth_meter_otherwise_zero = 0;
  lifeData->actualGas.AppliedDiveMode = stateUsed->diveSettings.diveMode;
}

void setButtonResponsiveness(uint8_t *ButtonSensitivyList)
{
	SDataReceiveFromMaster	*pDataOut = dataOutGetPointer();

	for(int i=0; i<4; i++)
	{
		pDataOut->data.buttonResponsiveness[i] = settingsHelperButtonSens_translate_percentage_to_hwOS_values(ButtonSensitivyList[i]);
	}
	pDataOut->setButtonSensitivityNow = 1;
}


void setDate(RTC_DateTypeDef Sdate)
{
	SDataReceiveFromMaster	*pDataOut = dataOutGetPointer();

	pDataOut->data.newDate = Sdate;
	pDataOut->setDateNow = 1;
}


void setTime(RTC_TimeTypeDef Stime)
{
	SDataReceiveFromMaster	*pDataOut = dataOutGetPointer();

	pDataOut->data.newTime = Stime;
	pDataOut->setTimeNow = 1;
}


void setBatteryPercentage(uint8_t newChargePercentage)
{
	SDataReceiveFromMaster	*pDataOut = dataOutGetPointer();

	pDataOut->data.newBatteryGaugePercentageFloat = settingsGetPointer()->lastKnownBatteryPercentage;
	pDataOut->setBatteryGaugeNow = 1;
}


void calibrateCompass(void)
{
	SDataReceiveFromMaster	*pDataOut = dataOutGetPointer();
	pDataOut->calibrateCompassNow = 1;
}


void clearDeco(void)
{
	SDataReceiveFromMaster	*pDataOut = dataOutGetPointer();
	pDataOut->clearDecoNow = 1;
	
	stateRealGetPointerWrite()->cnsHigh_at_the_end_of_dive = 0;
	stateRealGetPointerWrite()->decoMissed_at_the_end_of_dive	= 0;
}


static int32_t helper_days_from_civil(int32_t y, uint32_t m, uint32_t d)
{
		y += 2000;
    y -= m <= 2;
    int32_t era = (y >= 0 ? y : y-399) / 400;
    uint32_t yoe = (uint32_t)(y - era * 400);      // [0, 399]
    uint32_t doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
    uint32_t doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
    return era * 146097 + (int32_t)(doe) - 719468;
}


static uint8_t helper_weekday_from_days(int32_t z)
{
    return (uint8_t)(z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6);
}


void setWeekday(RTC_DateTypeDef *sDate)
{
	uint8_t day;
	// [0, 6] -> [Sun, Sat]
	day = helper_weekday_from_days(helper_days_from_civil(sDate->Year, sDate->Month, sDate->Date));
	// [1, 7] -> [Mon, Sun]
	if(day == 0)
		day = 7;
	sDate->WeekDay = day;
}


void translateDate(uint32_t datetmpreg, RTC_DateTypeDef *sDate)
{
  datetmpreg = (uint32_t)(datetmpreg & RTC_DR_RESERVED_MASK);

  /* Fill the structure fields with the read parameters */
  sDate->Year = (uint8_t)((datetmpreg & (RTC_DR_YT | RTC_DR_YU)) >> 16);
  sDate->Month = (uint8_t)((datetmpreg & (RTC_DR_MT | RTC_DR_MU)) >> 8);
  sDate->Date = (uint8_t)(datetmpreg & (RTC_DR_DT | RTC_DR_DU));
  sDate->WeekDay = (uint8_t)((datetmpreg & (RTC_DR_WDU)) >> 13);

	/* Convert the date structure parameters to Binary format */
	sDate->Year = (uint8_t)RTC_Bcd2ToByte(sDate->Year);
	sDate->Month = (uint8_t)RTC_Bcd2ToByte(sDate->Month);
	sDate->Date = (uint8_t)RTC_Bcd2ToByte(sDate->Date);
}

void translateTime(uint32_t tmpreg, RTC_TimeTypeDef *sTime)
{
  tmpreg = (uint32_t)(tmpreg & RTC_TR_RESERVED_MASK);

  /* Fill the structure fields with the read parameters */
  sTime->Hours = (uint8_t)((tmpreg & (RTC_TR_HT | RTC_TR_HU)) >> 16);
  sTime->Minutes = (uint8_t)((tmpreg & (RTC_TR_MNT | RTC_TR_MNU)) >>8);
  sTime->Seconds = (uint8_t)(tmpreg & (RTC_TR_ST | RTC_TR_SU));
  sTime->TimeFormat = (uint8_t)((tmpreg & (RTC_TR_PM)) >> 16);

	/* Convert the time structure parameters to Binary format */
	sTime->Hours = (uint8_t)RTC_Bcd2ToByte(sTime->Hours);
	sTime->Minutes = (uint8_t)RTC_Bcd2ToByte(sTime->Minutes);
	sTime->Seconds = (uint8_t)RTC_Bcd2ToByte(sTime->Seconds);
  sTime->SubSeconds = 0;
}

void resetEvents(const SDiveState *pStateUsed)
{
	memset((void *)&pStateUsed->events, 0, sizeof(SEvents));
}


uint32_t	CRC_CalcBlockCRC_moreThan768000(uint32_t *buffer1, uint32_t *buffer2, uint32_t words)
{
 cm_t        crc_model;
 uint32_t      word_to_do;
 uint8_t       byte_to_do;
 int         i;
 
     // Values for the STM32F generator.
 
     crc_model.cm_width = 32;            // 32-bit CRC
     crc_model.cm_poly  = 0x04C11DB7;    // CRC-32 polynomial
     crc_model.cm_init  = 0xFFFFFFFF;    // CRC initialized to 1's
     crc_model.cm_refin = FALSE;         // CRC calculated MSB first
     crc_model.cm_refot = FALSE;         // Final result is not bit-reversed
     crc_model.cm_xorot = 0x00000000;    // Final result XOR'ed with this
 
     cm_ini(&crc_model);
 
     while (words--)
     {
         // The STM32F10x hardware does 32-bit words at a time!!!
				if(words > (768000/4))
					word_to_do = *buffer2++;
				else
					word_to_do = *buffer1++;
 
         // Do all bytes in the 32-bit word.
 
         for (i = 0; i < sizeof(word_to_do); i++)
         {
             // We calculate a *byte* at a time. If the CRC is MSB first we
             // do the next MS byte and vica-versa.
 
             if (crc_model.cm_refin == FALSE)
             {
                 // MSB first. Do the next MS byte.
 
                 byte_to_do = (uint8_t) ((word_to_do & 0xFF000000) >> 24);
                 word_to_do <<= 8;
             }
             else
             {
                 // LSB first. Do the next LS byte.
 
                 byte_to_do = (uint8_t) (word_to_do & 0x000000FF);
                 word_to_do >>= 8;
             }
 
             cm_nxt(&crc_model, byte_to_do);
         }
     }
 
     // Return the final result.
 
     return (cm_crc(&crc_model));
}
 
 
uint32_t	CRC_CalcBlockCRC(uint32_t *buffer, uint32_t words)
{
 cm_t        crc_model;
 uint32_t      word_to_do;
 uint8_t       byte_to_do;
 int         i;
 
     // Values for the STM32F generator.
 
     crc_model.cm_width = 32;            // 32-bit CRC
     crc_model.cm_poly  = 0x04C11DB7;    // CRC-32 polynomial
     crc_model.cm_init  = 0xFFFFFFFF;    // CRC initialized to 1's
     crc_model.cm_refin = FALSE;         // CRC calculated MSB first
     crc_model.cm_refot = FALSE;         // Final result is not bit-reversed
     crc_model.cm_xorot = 0x00000000;    // Final result XOR'ed with this
 
     cm_ini(&crc_model);
 
     while (words--)
     {
         // The STM32F10x hardware does 32-bit words at a time!!!
 
         word_to_do = *buffer++;
 
         // Do all bytes in the 32-bit word.
 
         for (i = 0; i < sizeof(word_to_do); i++)
         {
             // We calculate a *byte* at a time. If the CRC is MSB first we
             // do the next MS byte and vica-versa.
 
             if (crc_model.cm_refin == FALSE)
             {
                 // MSB first. Do the next MS byte.
 
                 byte_to_do = (uint8_t) ((word_to_do & 0xFF000000) >> 24);
                 word_to_do <<= 8;
             }
             else
             {
                 // LSB first. Do the next LS byte.
 
                 byte_to_do = (uint8_t) (word_to_do & 0x000000FF);
                 word_to_do >>= 8;
             }
 
             cm_nxt(&crc_model, byte_to_do);
         }
     }
 
     // Return the final result.
 
     return (cm_crc(&crc_model));
}
 
// This code is also in RTE. Keep it in sync when editing
_Bool is_ambient_pressure_close_to_surface(SLifeData *lifeData)
{
	if (lifeData->pressure_ambient_bar > 1.16)
		return false;
	else if(lifeData->pressure_ambient_bar < (lifeData->pressure_surface_bar + 0.1f))
		return true;
	else
		return false;
}

void compass_Inertia(float newHeading)
{
	float newTarget = newHeading;

	if(settingsGetPointer()->compassInertia == 0)
	{
		compass_compensated = newHeading;
	}
	else
	{
		if((compass_compensated > 270.0) && (newHeading < 90.0))		/* transition passing 0 clockwise */
		{
			newTarget = newHeading + 360.0;
		}

		if((compass_compensated < 90.0) && (newHeading > 270.0))		/* transition passing 0 counter clockwise */
		{
			newTarget = newHeading - 360.0;
		}

		compass_compensated = compass_compensated + ((newTarget - compass_compensated) / (COMPASS_FRACTION * (settingsGetPointer()->compassInertia)));
		if(compass_compensated < 0.0)
		{
			compass_compensated += 360.0;
		}
		if(compass_compensated >= 360.0)
		{
			compass_compensated -= 360.0;
		}
	}
}

float compass_getCompensated()
{
	return compass_compensated;
}

uint8_t isLoopMode(uint8_t Mode)
{
	uint8_t retVal = 0;
	if((Mode == DIVEMODE_CCR) || (Mode == DIVEMODE_PSCR))
	{
		retVal = 1;
	}
	return retVal;
}


bool isCompassCalibrated(void)
{
    return stateUsed->lifeData.compass_heading != -1;
}

static void internalLogCompassHeading(uint16_t heading, bool applyHeading, bool clearHeading)
{
    uint16_t compassHeading = 0;
    if (clearHeading) {
        compassHeading |= 0x8000;
    } else {
        compassHeading = heading & 0x1FF;
    }
    if (applyHeading) {
        compassHeading |= 0x4000;
    }

    stateUsedWrite->events.compassHeadingUpdate = 1;
    stateUsedWrite->events.info_compassHeadingUpdate = compassHeading;
}

void logCompassHeading(uint16_t heading) {
    internalLogCompassHeading(heading, false, false);
}

void clearCompassHeading(void) {
    uint16_t clearHeading = 0;
    stateUsedWrite->diveSettings.compassHeading = clearHeading;

    internalLogCompassHeading(clearHeading, true, true);
}

void setCompassHeading(uint16_t heading)
{
    stateUsedWrite->diveSettings.compassHeading =  ((heading - 360) % 360) + 360;

    internalLogCompassHeading(heading, true, false);
}


const SDecoinfo *getDecoInfo(void)
{
    const SDecoinfo *decoInfo;
    if (stateUsed->diveSettings.deco_type.ub.standard == GF_MODE) {
        decoInfo = &stateUsed->decolistBuehlmann;
    } else {
        decoInfo = &stateUsed->decolistVPM;
    }

    return decoInfo;
}


void disableTimer(void)
{
    stateUsedWrite->timerState = TIMER_STATE_OFF;
}

#define SPEED_SLOW		(5.1f)
#define SPEED_MEDIUM	(10.1f)
#define SPEED_HIGH		(15.1f)
#define SPEED_HYSTERESE (1.0f)

uint8_t drawingColor_from_ascentspeed(float speed)
{
	static uint8_t lastColor = 0;

	uint8_t color = CLUT_Font020;

    if((speed >= SPEED_HIGH) || ((lastColor == CLUT_WarningRed) && (speed >= SPEED_HIGH - SPEED_HYSTERESE)))
    {
    	color = CLUT_WarningRed;
    }
    else if((speed >= SPEED_MEDIUM) || ((lastColor == CLUT_WarningYellow) && (speed >= SPEED_MEDIUM - SPEED_HYSTERESE)))
    {
    	color = CLUT_WarningYellow;
    }
    else if((speed >= SPEED_SLOW) || ((lastColor == CLUT_NiceGreen) && (speed >= SPEED_SLOW - SPEED_HYSTERESE)))
    {
    	color = CLUT_NiceGreen;
    }
    lastColor = color;
    return color;
}

/* returns the date in the order defined by the settings DDMMYY => X */
void convertStringOfDate_DDMMYY(char* pString, uint8_t strLen, uint8_t day, uint8_t month, uint8_t year)
{
	if(strLen > 10)
	{
		switch(settingsGetPointer()->date_format)
		{
			default:
			case DDMMYY: snprintf(pString,strLen,"%02d.%02d.%02d",day,month,year);
				break;
			case MMDDYY: snprintf(pString,strLen,"%02d.%02d.%02d",month,day,year);
				break;
			case YYMMDD: snprintf(pString,strLen,"%02d.%02d.%02d",year,month,day);
				break;
		}
	}
}
/* returns the format in the order defined by the settings DDMMYY => X */
void getStringOfFormat_DDMMYY(char* pString, uint8_t strLen)
{
	if(strLen > 10)
	{
		switch(settingsGetPointer()->date_format)
		{
			default:
			case DDMMYY: snprintf(pString,strLen,"%c%c",TXT_2BYTE,TXT2BYTE_DDMMYY);
				break;
			case MMDDYY:  snprintf(pString,strLen,"%c%c",TXT_2BYTE,TXT2BYTE_MMDDYY);
				break;
			case YYMMDD:  snprintf(pString,strLen,"%c%c",TXT_2BYTE,TXT2BYTE_YYMMDD);
				break;
		}
	}
}

uint8_t calculateSlowExit(uint16_t* pCountDownSec, float* pExitDepthMeter, uint8_t* pColor)  /* this function is only called if diver is below last last stop depth */
{
	static SSlowExitState slowExitState = SE_END;
	static uint16_t countDownSec = 0;
	static float exitDepthMeter = 0.0;
	static uint32_t exitSecTick = 0;
	static uint32_t lastSecTick = 0;
	static uint8_t color = 0;
	static uint8_t drawingActive = 0;

	SSettings* pSettings;
	pSettings = settingsGetPointer();

	if((stateUsed->lifeData.max_depth_meter < pSettings->last_stop_depth_meter)	/* start of dive => reinit timer */
			|| (slowExitState == SE_REINIT))
	{
		if(slowExitState != SE_INIT)
		{
			countDownSec = pSettings->slowExitTime * 60;
			slowExitState = SE_INIT;
			exitDepthMeter =  pSettings->last_stop_depth_meter;
			color = 0;
			drawingActive = 0;
		}
	}
	else
	{
		if(slowExitState != SE_END)
		{
			if((slowExitState == SE_INIT) && (stateUsed->lifeData.dive_time_seconds > 900)) /* min 15min divetime */
			{
				slowExitState = SE_ACTIVE;
				exitSecTick = HAL_GetTick();
				lastSecTick = exitSecTick;
			}
			else if(slowExitState == SE_ACTIVE)
			{
				if(time_elapsed_ms(lastSecTick, HAL_GetTick()) > 60000) /* restart timer if diver go below exit zone */
				{
					slowExitState = SE_REINIT;
				}
				else if(time_elapsed_ms(exitSecTick, HAL_GetTick()) > 1000)
				{
					exitSecTick = HAL_GetTick();
					lastSecTick = exitSecTick;
					/* select depth digit color */
					if(fabsf(stateUsed->lifeData.depth_meter - exitDepthMeter) < 0.5 )
					{
						color = CLUT_NiceGreen;
					}
					else if(fabsf(stateUsed->lifeData.depth_meter - exitDepthMeter) <= 1.5)
					{
						color = CLUT_WarningYellow;
					}
					else if(stateUsed->lifeData.depth_meter - exitDepthMeter < -1.5 )
					{
						color = CLUT_WarningRed;
					}
					else
					{
						color = 0;
					}

					if((fabsf(stateUsed->lifeData.depth_meter - exitDepthMeter) <= 1.6 ) /* only decrease counter if diver is close to target depth */
							|| (color == CLUT_WarningRed))								 /* or if diver is far ahead */
					{
						countDownSec--;
						if(countDownSec == 0)
						{
							slowExitState = SE_END;
							color = 0;
							exitDepthMeter = 0;
						}
						else
						{
							exitDepthMeter -=  (pSettings->last_stop_depth_meter / (float)(pSettings->slowExitTime * 60));
						}
					}
					drawingActive = 1;
				}
			}
		}
	}
	*pCountDownSec = countDownSec;
	*pExitDepthMeter = exitDepthMeter;
	*pColor = color;
	return drawingActive;
}