view Discovery/Src/data_central.c @ 949:c6b858f2e025 Evo_2_23 tip

GNSS UTC in Date-Time menu: The UTC time provided by the gnss module is not shown in the date-time settings menu. If a valid time signal is available then the OSTC RTC may be set to it by selecting the UTC menu entry. The time zone has to be selected manually. This is possible using a separate edit line.
author Ideenmodellierer
date Sun, 22 Dec 2024 21:19:21 +0100
parents aad1a6b9aaec
children
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;
}

void convertUTCToLocal(uint8_t utcHours, uint8_t utcMinutes, uint8_t* pLocalHours, uint8_t* pLocalMinutes)
{
    int8_t localHours = 0;
    int8_t localMinutes = 0;
    SSettings* pSettings = settingsGetPointer();

	localHours = utcHours + pSettings->timeZone.hours;
	if(localHours < 0)
	{
		localHours += 24;
	}
	if(localHours > 24)
	{
		localHours -= 24;
	}
	localMinutes = utcMinutes + pSettings->timeZone.minutes;
	if(localMinutes < 0)
	{
		localMinutes += 60;
	}
	if(localMinutes > 60)
	{
		localMinutes -= 60;
	}
	*pLocalHours = localHours;
	*pLocalMinutes = localMinutes;
}