38
+ − 1 ///////////////////////////////////////////////////////////////////////////////
+ − 2 /// -*- coding: UTF-8 -*-
+ − 3 ///
+ − 4 /// \file Discovery/Src/tCCR.c
+ − 5 /// \brief HUD data via optical port
+ − 6 /// \author Heinrichs Weikamp gmbh
+ − 7 /// \date 18-Dec-2014
+ − 8 ///
+ − 9 /// \details
+ − 10 ///
+ − 11 /// $Id$
+ − 12 ///////////////////////////////////////////////////////////////////////////////
+ − 13 /// \par Copyright (c) 2014-2018 Heinrichs Weikamp gmbh
+ − 14 ///
+ − 15 /// This program is free software: you can redistribute it and/or modify
+ − 16 /// it under the terms of the GNU General Public License as published by
+ − 17 /// the Free Software Foundation, either version 3 of the License, or
+ − 18 /// (at your option) any later version.
+ − 19 ///
+ − 20 /// This program is distributed in the hope that it will be useful,
+ − 21 /// but WITHOUT ANY WARRANTY; without even the implied warranty of
+ − 22 /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ − 23 /// GNU General Public License for more details.
+ − 24 ///
+ − 25 /// You should have received a copy of the GNU General Public License
+ − 26 /// along with this program. If not, see <http://www.gnu.org/licenses/>.
+ − 27 //////////////////////////////////////////////////////////////////////////////
+ − 28
+ − 29 /* Includes ------------------------------------------------------------------*/
+ − 30 #include <string.h>
+ − 31 #include "tCCR.h"
+ − 32 #include "ostc.h"
+ − 33 #include "data_central.h"
+ − 34 #include "data_exchange.h"
+ − 35 #include "check_warning.h"
446
+ − 36 #include "configuration.h"
634
+ − 37 #include <math.h>
38
+ − 38
+ − 39 /* Private types -------------------------------------------------------------*/
+ − 40 typedef struct
+ − 41 {
+ − 42 uint8_t hud_firmwareVersion;
+ − 43 bit8_Type status_byte;
+ − 44 uint16_t sensor_voltage_100uV[3];
+ − 45 uint8_t sensor_ppo2_cbar[3];
+ − 46 uint8_t temp1;
+ − 47 uint16_t battery_voltage_mV;
+ − 48 uint16_t checksum;
+ − 49 } SIrLink;
+ − 50
634
+ − 51 typedef enum
+ − 52 {
+ − 53 sensorOK = 0,
+ − 54 sensorSuspect,
+ − 55 SensorOutOfBounds
+ − 56 } sensorTrustState_t;
+ − 57
+ − 58
322
+ − 59 #define HUD_BABBLING_IDIOT (30u) /* 30 Bytes received without break */
+ − 60 #define HUD_RX_FRAME_LENGTH (15u) /* Length of a HUD data frame */
+ − 61 #define HUD_RX_FRAME_BREAK_MS (100u) /* Time used to detect a gap between two byte receptions => frame start */
+ − 62 #define HUD_RX_START_DELAY_MS (500u) /* Delay for start of RX function to avoid start of reception while a transmission is ongoing. */
+ − 63 /* Based on an assumed cycle time by the sensor of 1 second. Started at time of last RX */
38
+ − 64
449
+ − 65 #define BOTTLE_SENSOR_TIMEOUT (6000u) /* signal pressure budget as not received after 10 minutes (6000 * 100ms) */
+ − 66
634
+ − 67 #define MAX_SENSOR_COMPARE_DEVIATION (0.15f) /* max deviation between two sensors allowed before their results are rated as suspect */
662
+ − 68 #define MAX_SENSOR_VOLTAGE_MV (250u) /* max allowed voltage value for a sensor measurement */
+ − 69
+ − 70 #ifdef ENABLE_ALTERNATIVE_SENSORTYP
+ − 71 #define MIN_SENSOR_VOLTAGE_MV (3u) /* min allowed voltage value for a sensor measurement (Inspiration, Submatix, Sentinel Typ) */
+ − 72 #else
+ − 73 #define MIN_SENSOR_VOLTAGE_MV (8u) /* min allowed voltage value for a sensor measurement (legacy OSTC TYP) */
+ − 74 #endif
634
+ − 75
322
+ − 76 /* Private variables ---------------------------------------------------------*/
+ − 77 static SIrLink receiveHUD[2];
+ − 78 static uint8_t boolHUDdata = 0;
+ − 79 static uint8_t data_old__lost_connection_to_HUD = 1;
38
+ − 80
322
+ − 81 static uint8_t receiveHUDraw[16];
+ − 82
+ − 83 static uint8_t StartListeningToUART_HUD = 0;
+ − 84 static uint16_t HUDTimeoutCount = 0;
655
+ − 85 static uint16_t ScrubberTimeoutCount = 0;
322
+ − 86
+ − 87 static __IO ITStatus UartReadyHUD = RESET;
+ − 88 static uint32_t LastReceivedTick_HUD = 0;
38
+ − 89
+ − 90 /* Private variables with external access via get_xxx() function -------------*/
+ − 91
+ − 92 /* Private function prototypes -----------------------------------------------*/
635
+ − 93 static uint8_t tCCR_fallbackToFixedSetpoint(void);
38
+ − 94
+ − 95 #ifndef USART_IR_HUD
+ − 96
+ − 97 void tCCR_init(void)
+ − 98 {
+ − 99 }
+ − 100 void tCCR_control(void)
+ − 101 {
+ − 102 }
+ − 103 void tCCR_test(void)
+ − 104 {
+ − 105 }
+ − 106 void tCCR_restart(void)
+ − 107 {
+ − 108 }
+ − 109 float get_ppO2Sensor_bar(uint8_t sensor_id)
+ − 110 {
+ − 111 }
+ − 112 float get_sensorVoltage_mV(uint8_t sensor_id)
+ − 113 {
+ − 114 }
+ − 115 float get_HUD_battery_voltage_V(void)
+ − 116 {
+ − 117 }
+ − 118 void tCCR_tick(void)
+ − 119 {
+ − 120 }
+ − 121
+ − 122 #else
+ − 123 /* Exported functions --------------------------------------------------------*/
+ − 124
+ − 125 float get_ppO2Sensor_bar(uint8_t sensor_id)
+ − 126 {
+ − 127 if((sensor_id > 2) || data_old__lost_connection_to_HUD)
+ − 128 return 0;
+ − 129
+ − 130 return (float)(receiveHUD[boolHUDdata].sensor_ppo2_cbar[sensor_id]) / 100.0f;
+ − 131 }
+ − 132
+ − 133 float get_sensorVoltage_mV(uint8_t sensor_id)
+ − 134 {
+ − 135 if((sensor_id > 2) || data_old__lost_connection_to_HUD)
+ − 136 return 0;
+ − 137
+ − 138 return (float)(receiveHUD[boolHUDdata].sensor_voltage_100uV[sensor_id]) / 10.0f;
+ − 139 }
+ − 140
+ − 141 float get_HUD_battery_voltage_V(void)
+ − 142 {
+ − 143 if(data_old__lost_connection_to_HUD)
+ − 144 return 0;
+ − 145
+ − 146 return (float)(receiveHUD[boolHUDdata].battery_voltage_mV) / 1000.0f;
+ − 147 }
+ − 148
+ − 149
563
+ − 150 void test_O2_sensor_values_outOfBounds(int8_t * outOfBouds1, int8_t * outOfBouds2, int8_t * outOfBouds3)
38
+ − 151 {
+ − 152 uint8_t sensorNotActiveBinary;
+ − 153 uint8_t sensorActive[3];
634
+ − 154 sensorTrustState_t sensorState[3];
+ − 155 uint8_t index;
+ − 156
38
+ − 157
+ − 158 // test1: user deactivation
+ − 159 sensorNotActiveBinary = stateUsed->diveSettings.ppo2sensors_deactivated;
+ − 160
+ − 161 for(int i=0;i<3;i++)
+ − 162 sensorActive[i] = 1;
+ − 163
+ − 164 if(sensorNotActiveBinary)
+ − 165 {
+ − 166 if(sensorNotActiveBinary & 1)
+ − 167 sensorActive[0] = 0;
+ − 168
+ − 169 if(sensorNotActiveBinary & 2)
+ − 170 sensorActive[1] = 0;
+ − 171
+ − 172 if(sensorNotActiveBinary & 4)
+ − 173 sensorActive[2] = 0;
+ − 174 }
+ − 175
+ − 176 // test2: mV of remaining sensors
744
+ − 177 for(index=0; index<3; index++)
38
+ − 178 {
744
+ − 179 sensorState[index] = sensorOK;
634
+ − 180
744
+ − 181 if(sensorActive[index])
38
+ − 182 {
797
+ − 183 if(((stateUsed->lifeData.extIf_sensor_map[index] == SENSOR_DIGO2M) && (((SSensorDataDiveO2*)(stateUsed->lifeData.extIf_sensor_data[index]))->status & DVO2_FATAL_ERROR))
+ − 184 || ((stateUsed->lifeData.extIf_sensor_map[index] != SENSOR_DIGO2M)
757
+ − 185 && (((stateUsed->lifeData.sensorVoltage_mV[index] < MIN_SENSOR_VOLTAGE_MV) || (stateUsed->lifeData.sensorVoltage_mV[index] > MAX_SENSOR_VOLTAGE_MV)))))
744
+ − 186 {
+ − 187 sensorActive[index] = 0;
+ − 188 switch(index)
+ − 189 {
+ − 190 case 0:
+ − 191 sensorNotActiveBinary |= 1;
+ − 192 break;
+ − 193 case 1:
+ − 194 sensorNotActiveBinary |= 2;
+ − 195 break;
+ − 196 case 2:
+ − 197 sensorNotActiveBinary |= 4;
+ − 198 break;
+ − 199 }
+ − 200 }
38
+ − 201 }
+ − 202 }
+ − 203
+ − 204 *outOfBouds1 = 0;
+ − 205 *outOfBouds2 = 0;
+ − 206 *outOfBouds3 = 0;
+ − 207
+ − 208 /* with two, one or no sensor, there is nothing to compare anymore
+ − 209 */
+ − 210 if(sensorNotActiveBinary)
+ − 211 {
+ − 212 // set outOfBounds for both tests
+ − 213 if(!sensorActive[0])
+ − 214 *outOfBouds1 = 1;
+ − 215
+ − 216 if(!sensorActive[1])
+ − 217 *outOfBouds2 = 1;
+ − 218
+ − 219 if(!sensorActive[2])
+ − 220 *outOfBouds3 = 1;
+ − 221 }
+ − 222 else
+ − 223 {
634
+ − 224 /* Check two or more of Three */
+ − 225 /* compare every sensor with each other. If there is only one mismatch the value might be OK. In case both comparisons fail the sensor is out of bounds */
+ − 226 if(fabsf(stateUsed->lifeData.ppO2Sensor_bar[0] - stateUsed->lifeData.ppO2Sensor_bar[1]) > MAX_SENSOR_COMPARE_DEVIATION)
38
+ − 227 {
634
+ − 228 sensorState[0]++;
+ − 229 sensorState[1]++;
38
+ − 230 }
634
+ − 231 if(fabsf(stateUsed->lifeData.ppO2Sensor_bar[0] - stateUsed->lifeData.ppO2Sensor_bar[2]) > MAX_SENSOR_COMPARE_DEVIATION)
38
+ − 232 {
634
+ − 233 sensorState[0]++;
+ − 234 sensorState[2]++;
+ − 235 }
+ − 236 if(fabsf(stateUsed->lifeData.ppO2Sensor_bar[1] - stateUsed->lifeData.ppO2Sensor_bar[2]) > MAX_SENSOR_COMPARE_DEVIATION)
+ − 237 {
+ − 238 sensorState[1]++;
+ − 239 sensorState[2]++;
38
+ − 240 }
634
+ − 241 for(index = 0; index < 3; index++)
38
+ − 242 {
634
+ − 243 if(sensorState[index] == SensorOutOfBounds)
+ − 244 {
+ − 245 switch(index)
+ − 246 {
+ − 247 case 0:
+ − 248 *outOfBouds1 = 1;
+ − 249 break;
+ − 250 case 1:
+ − 251 *outOfBouds2 = 1;
+ − 252 break;
+ − 253 case 2:
+ − 254 *outOfBouds3 = 1;
+ − 255 break;
+ − 256 default:
+ − 257 break;
+ − 258 }
+ − 259 }
38
+ − 260 }
+ − 261 }
+ − 262 }
+ − 263
635
+ − 264 /* this function is called out of the 100ms callback => to be considered for debouncing */
38
+ − 265 uint8_t get_ppO2SensorWeightedResult_cbar(void)
+ − 266 {
634
+ − 267 static uint8_t lastValidValue = 0;
38
+ − 268 int8_t sensorOutOfBound[3];
+ − 269 uint16_t result = 0;
+ − 270 uint8_t count = 0;
582
+ − 271 uint8_t retVal = 0;
38
+ − 272
563
+ − 273 test_O2_sensor_values_outOfBounds(&sensorOutOfBound[0], &sensorOutOfBound[1], &sensorOutOfBound[2]);
38
+ − 274
+ − 275 for(int i=0;i<3;i++)
+ − 276 {
+ − 277 if(!sensorOutOfBound[i])
+ − 278 {
563
+ − 279 result += stateUsed->lifeData.ppO2Sensor_bar[i] * 100.0; /* convert centibar used by HUB */
38
+ − 280 count++;
+ − 281 }
+ − 282 }
634
+ − 283 if(count == 0) /* all sensors out of bounds! => return last valid value as workaround till diver takes action */
582
+ − 284 {
635
+ − 285 if(debounce_warning_fallback(100))
+ − 286 {
+ − 287 set_warning_fallback();
+ − 288 retVal = tCCR_fallbackToFixedSetpoint(); /* this function only changes setpoint if option is enabled */
+ − 289 }
+ − 290 if(retVal == 0)
+ − 291 {
+ − 292 retVal = lastValidValue;
+ − 293 }
582
+ − 294 }
38
+ − 295 else
582
+ − 296 {
635
+ − 297 reset_debounce_warning_fallback();
+ − 298 retVal = (uint8_t)(result / count);
+ − 299 lastValidValue = retVal;
582
+ − 300 }
+ − 301 return retVal;
38
+ − 302 }
+ − 303
+ − 304
+ − 305 void tCCR_init(void)
+ − 306 {
449
+ − 307 uint8_t loop;
+ − 308
38
+ − 309 StartListeningToUART_HUD = 1;
449
+ − 310
+ − 311 SDiveState* pDiveData = stateRealGetPointerWrite();
+ − 312 for(loop=0;loop<(2*NUM_GASES+1);loop++)
+ − 313 {
+ − 314 pDiveData->lifeData.bottle_bar_age_MilliSeconds[loop] = BOTTLE_SENSOR_TIMEOUT;
+ − 315 }
38
+ − 316 }
+ − 317
+ − 318
+ − 319 /* after 3 seconds without update from HUD
+ − 320 * data is considered old
+ − 321 */
+ − 322 void tCCR_tick(void)
+ − 323 {
655
+ − 324 SSettings* pSettings = settingsGetPointer();
+ − 325
+ − 326 if(pSettings->ppo2sensors_source == O2_SENSOR_SOURCE_OPTIC)
563
+ − 327 {
+ − 328 if(HUDTimeoutCount < 3 * 10)
+ − 329 HUDTimeoutCount++;
+ − 330 else
+ − 331 {
+ − 332 data_old__lost_connection_to_HUD = 1;
+ − 333 if(HUDTimeoutCount < 20 * 10)
+ − 334 HUDTimeoutCount++;
+ − 335 else
+ − 336 tCCR_fallbackToFixedSetpoint();
+ − 337 }
+ − 338 }
655
+ − 339
924
+ − 340 /* decrease scrubber timer only if we are not bailed out */
+ − 341 if((pSettings->scrubTimerMode != SCRUB_TIMER_OFF) && (isLoopMode(pSettings->dive_mode)) && (stateUsed->mode == MODE_DIVE) && isLoopMode(stateUsed->diveSettings.diveMode))
655
+ − 342 {
+ − 343 ScrubberTimeoutCount++;
+ − 344 if(ScrubberTimeoutCount >= 600) /* resolution is minutes */
+ − 345 {
+ − 346 ScrubberTimeoutCount = 0;
924
+ − 347 if(stateUsed->scrubberDataDive[pSettings->scubberActiveId].TimerCur > MIN_SCRUBBER_TIME)
655
+ − 348 {
924
+ − 349 stateUsedWrite->scrubberDataDive[pSettings->scubberActiveId].TimerCur--;
655
+ − 350 }
924
+ − 351 translateDate(stateUsed->lifeData.dateBinaryFormat, &stateUsedWrite->scrubberDataDive[pSettings->scubberActiveId].lastDive);
655
+ − 352 }
+ − 353 }
38
+ − 354 }
+ − 355
322
+ − 356 void tCCR_SetRXIndication(void)
+ − 357 {
+ − 358 static uint8_t floatingRXCount = 0;
+ − 359
+ − 360 if((UartIR_HUD_Handle.RxXferSize == HUD_RX_FRAME_LENGTH) || (UartIR_HUD_Handle.RxXferSize == HUD_RX_FRAME_LENGTH - 1)) /* we expected a complete frame */
+ − 361 {
+ − 362 UartReadyHUD = SET;
+ − 363 LastReceivedTick_HUD = HAL_GetTick();
+ − 364 floatingRXCount = 0;
+ − 365 }
+ − 366 else /* follow up of error handling */
+ − 367 {
+ − 368 if(time_elapsed_ms(LastReceivedTick_HUD, HAL_GetTick()) > HUD_RX_FRAME_BREAK_MS) /* Reception took a while => frame start detected */
+ − 369 {
+ − 370 HAL_UART_Receive_IT(&UartIR_HUD_Handle, &receiveHUDraw[1], 14); /* We have already the first byte => get the missing 14 */
+ − 371 }
+ − 372 else
+ − 373 {
+ − 374 if(floatingRXCount++ < HUD_BABBLING_IDIOT)
+ − 375 {
+ − 376 HAL_UART_Receive_IT(&UartIR_HUD_Handle, receiveHUDraw, 1); /* Start polling of incoming bytes */
+ − 377 }
+ − 378 else /* Significant amount of data comming in without break => disable input */
+ − 379 { /* by not reactivation HUD RX, no recovery fromthis state */
+ − 380 stateUsedWrite->diveSettings.ppo2sensors_deactivated = 0x07; /* Display deactivation */
+ − 381 }
+ − 382 }
+ − 383 }
+ − 384
+ − 385 }
38
+ − 386
+ − 387 void tCCR_restart(void)
+ − 388 {
322
+ − 389 HAL_UART_AbortReceive_IT(&UartIR_HUD_Handle); /* Called by the error handler. RX will be restarted by control function */
+ − 390 StartListeningToUART_HUD = 1;
38
+ − 391 }
+ − 392
+ − 393
+ − 394 void tCCR_control(void)
+ − 395 {
446
+ − 396 uint16_t checksum = 0;
457
+ − 397 #ifdef ENABLE_BOTTLE_SENSOR
446
+ − 398 SDiveState *pLivedata = stateRealGetPointerWrite();
457
+ − 399 #endif
322
+ − 400
+ − 401 if((UartReadyHUD == RESET) && StartListeningToUART_HUD && (time_elapsed_ms(LastReceivedTick_HUD, HAL_GetTick()) > HUD_RX_START_DELAY_MS))
+ − 402 {
+ − 403 StartListeningToUART_HUD = 0;
+ − 404 HAL_UART_Receive_IT(&UartIR_HUD_Handle, receiveHUDraw, HUD_RX_FRAME_LENGTH);
+ − 405 }
38
+ − 406
+ − 407 if(UartReadyHUD == SET)
+ − 408 {
+ − 409 UartReadyHUD = RESET;
322
+ − 410 StartListeningToUART_HUD = 1;
38
+ − 411
446
+ − 412 /* check if received package is valid */
+ − 413 for(int i=0;i<13;i++)
+ − 414 {
+ − 415 checksum += receiveHUDraw[i];
+ − 416 }
+ − 417 receiveHUD[!boolHUDdata].checksum = receiveHUDraw[13] + (256 * receiveHUDraw[14]);
+ − 418 if(checksum == receiveHUD[!boolHUDdata].checksum)
+ − 419 {
+ − 420 #ifdef ENABLE_BOTTLE_SENSOR
+ − 421 if(receiveHUDraw[0] == 0xA5) /* code for pressure sensor */
+ − 422 {
+ − 423 pLivedata->lifeData.bottle_bar[pLivedata->lifeData.actualGas.GasIdInSettings] = receiveHUDraw[10];
+ − 424 pLivedata->lifeData.bottle_bar_age_MilliSeconds[pLivedata->lifeData.actualGas.GasIdInSettings] = 0;
+ − 425 }
+ − 426 else
+ − 427 #endif
+ − 428 /* handle O2 sensor data */
+ − 429 {
+ − 430 memcpy(&receiveHUD[!boolHUDdata], receiveHUDraw, 11);
+ − 431 receiveHUD[!boolHUDdata].battery_voltage_mV = receiveHUDraw[11] + (256 * receiveHUDraw[12]);
+ − 432 }
38
+ − 433
446
+ − 434 boolHUDdata = !boolHUDdata;
+ − 435 HUDTimeoutCount = 0;
+ − 436 data_old__lost_connection_to_HUD = 0;
+ − 437 }
+ − 438 else
+ − 439 {
+ − 440 if(data_old__lost_connection_to_HUD) /* we lost connection, maybe due to RX shift => start single byte read to resynchronize */
+ − 441 {
+ − 442 HAL_UART_Receive_IT(&UartIR_HUD_Handle, receiveHUDraw, 1);
+ − 443 StartListeningToUART_HUD = 0;
+ − 444 }
+ − 445 }
+ − 446 memset(receiveHUDraw,0,sizeof(receiveHUDraw));
38
+ − 447 }
+ − 448 }
+ − 449
+ − 450 #endif
+ − 451 /* Private functions ---------------------------------------------------------*/
+ − 452
635
+ − 453 static uint8_t tCCR_fallbackToFixedSetpoint(void)
38
+ − 454 {
635
+ − 455 uint8_t retVal = 0;
662
+ − 456 uint8_t setpointCbar, actualGasID;
38
+ − 457
662
+ − 458 if((stateUsed->mode == MODE_DIVE) && (stateUsed->diveSettings.CCR_Mode == CCRMODE_Sensors) && (stateUsed->diveSettings.fallbackOption))
+ − 459 {
+ − 460 if(stateUsed->diveSettings.diveMode == DIVEMODE_CCR)
+ − 461 {
+ − 462 setpointCbar = stateUsed->diveSettings.setpoint[1].setpoint_cbar;
+ − 463 stateUsedWrite->diveSettings.CCR_Mode = CCRMODE_FixedSetpoint;
+ − 464 }
+ − 465 else
+ − 466 {
+ − 467 setpointCbar = stateUsed->lifeData.ppo2Simulated_bar * 100;
+ − 468 stateUsedWrite->diveSettings.CCR_Mode = CCRMODE_Simulation;
+ − 469 }
+ − 470 actualGasID = stateUsed->lifeData.actualGas.GasIdInSettings;
+ − 471 setActualGas_DM(&stateUsedWrite->lifeData,actualGasID,setpointCbar);
38
+ − 472
662
+ − 473 set_warning_fallback();
+ − 474 retVal = setpointCbar;
38
+ − 475 }
635
+ − 476 return retVal;
38
+ − 477 }