| Line | Branch | Decision | Exec | Source |
|---|---|---|---|---|
| 1 | /** | |||
| 2 | * @file adc_inputs.cpp | |||
| 3 | * @brief Low level ADC code | |||
| 4 | * | |||
| 5 | * @date Jan 14, 2013 | |||
| 6 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
| 7 | */ | |||
| 8 | ||||
| 9 | #include "pch.h" | |||
| 10 | ||||
| 11 | ✗ | float PUBLIC_API_WEAK getAnalogInputDividerCoefficient(adc_channel_e) { | ||
| 12 | ✗ | return engineConfiguration->analogInputDividerCoefficient; | ||
| 13 | } | |||
| 14 | ||||
| 15 | ✗ | float PUBLIC_API_WEAK boardAdjustVoltage(float voltage, adc_channel_e /* hwChannel */) { | ||
| 16 | // a hack useful when we do not trust voltage just after board EN was turned on. is this just hiding electrical design flaws? | |||
| 17 | ✗ | return voltage; | ||
| 18 | } | |||
| 19 | ||||
| 20 | /* overall analog health state | |||
| 21 | * return negative in case of any problems | |||
| 22 | * return 0 if everything is ok or no diagnostic is available */ | |||
| 23 | ✗ | ObdCode PUBLIC_API_WEAK boardGetAnalogDiagnostic() { | ||
| 24 | ✗ | return ObdCode::None; | ||
| 25 | } | |||
| 26 | ||||
| 27 | /* simple implementation if board does not provide advanced diagnostic */ | |||
| 28 | ✗ | int PUBLIC_API_WEAK boardGetAnalogInputDiagnostic(adc_channel_e channel, float) { | ||
| 29 | #if EFI_PROD_CODE | |||
| 30 | /* for on-chip ADC inputs we check common analog health */ | |||
| 31 | if (isAdcChannelOnChip(channel)) { | |||
| 32 | return (boardGetAnalogDiagnostic() == ObdCode::None) ? 0 : -1; | |||
| 33 | } | |||
| 34 | #endif // EFI_PROD_CODE | |||
| 35 | ||||
| 36 | /* input is outside chip/ECU */ | |||
| 37 | ✗ | return 0; | ||
| 38 | } | |||
| 39 | ||||
| 40 | ✗ | static ObdCode analogGetVrefDiagnostic() | ||
| 41 | { | |||
| 42 | #if HAL_USE_ADC | |||
| 43 | float vref = getMCUVref(); | |||
| 44 | ||||
| 45 | // TODO: +/-10% is way too big? | |||
| 46 | if (vref > engineConfiguration->adcVcc * 1.1) { | |||
| 47 | return ObdCode::OBD_Sensor_Refence_Voltate_A_High; | |||
| 48 | } | |||
| 49 | ||||
| 50 | if (vref < engineConfiguration->adcVcc * 0.9) { | |||
| 51 | return ObdCode::OBD_Sensor_Refence_Voltate_A_Low; | |||
| 52 | } | |||
| 53 | #endif | |||
| 54 | ||||
| 55 | ✗ | return ObdCode::None; | ||
| 56 | } | |||
| 57 | ||||
| 58 | /* Get analog part diagnostic */ | |||
| 59 | ✗ | ObdCode analogGetDiagnostic() | ||
| 60 | { | |||
| 61 | /* TODO: debounce? */ | |||
| 62 | ✗ | auto code = analogGetVrefDiagnostic(); | ||
| 63 | ✗ | if (code != ObdCode::None) { | ||
| 64 | ✗ | return code; | ||
| 65 | } | |||
| 66 | ||||
| 67 | ✗ | return boardGetAnalogDiagnostic(); | ||
| 68 | } | |||
| 69 | ||||
| 70 | #if HAL_USE_ADC | |||
| 71 | ||||
| 72 | #include "adc_device.h" | |||
| 73 | #include "adc_subscription.h" | |||
| 74 | #include "mpu_util.h" | |||
| 75 | #include "protected_gpio.h" | |||
| 76 | ||||
| 77 | // voltage in MCU universe, from zero to Vref | |||
| 78 | expected<float> adcGetRawVoltage(const char *msg, adc_channel_e hwChannel) { | |||
| 79 | float rawVoltage = adcRawValueToRawVoltage(adcGetRawValue(msg, hwChannel)); | |||
| 80 | int inputStatus = boardGetAnalogInputDiagnostic(hwChannel, rawVoltage); | |||
| 81 | ||||
| 82 | if (inputStatus == 0) { | |||
| 83 | return expected(rawVoltage); | |||
| 84 | } | |||
| 85 | ||||
| 86 | /* TODO: convert inputStatus to unexpected? */ | |||
| 87 | return unexpected; | |||
| 88 | } | |||
| 89 | ||||
| 90 | // voltage in ECU universe, with all input dividers and OpAmps gains taken into account, voltage at ECU connector pin | |||
| 91 | expected<float> adcGetScaledVoltage(const char *msg, adc_channel_e hwChannel) { | |||
| 92 | auto rawVoltage = adcGetRawVoltage(msg, hwChannel); | |||
| 93 | ||||
| 94 | if (rawVoltage) { | |||
| 95 | // TODO: merge getAnalogInputDividerCoefficient() and boardAdjustVoltage() into single board hook? | |||
| 96 | float voltage = rawVoltage.value_or(0) * getAnalogInputDividerCoefficient(hwChannel); | |||
| 97 | return expected(boardAdjustVoltage(voltage, hwChannel)); | |||
| 98 | } | |||
| 99 | ||||
| 100 | return expected(rawVoltage); | |||
| 101 | } | |||
| 102 | ||||
| 103 | extern AdcDevice fastAdc; | |||
| 104 | ||||
| 105 | static AdcChannelMode adcHwChannelMode[EFI_ADC_TOTAL_CHANNELS]; | |||
| 106 | ||||
| 107 | // todo: move this flag to Engine god object | |||
| 108 | static int adcDebugReporting = false; | |||
| 109 | ||||
| 110 | AdcChannelMode getAdcMode(adc_channel_e hwChannel) { | |||
| 111 | return adcHwChannelMode[hwChannel]; | |||
| 112 | } | |||
| 113 | ||||
| 114 | extern adcsample_t adcOnchipSlowGetAvgRaw(adc_channel_e hwChannel); | |||
| 115 | ||||
| 116 | int getInternalAdcValue(const char *msg, adc_channel_e hwChannel) { | |||
| 117 | if (!isAdcChannelValid(hwChannel)) { | |||
| 118 | warning(ObdCode::CUSTOM_OBD_ANALOG_INPUT_NOT_CONFIGURED, "ADC: %s input is not configured", msg); | |||
| 119 | return -1; | |||
| 120 | } | |||
| 121 | ||||
| 122 | #if EFI_USE_FAST_ADC | |||
| 123 | if (adcHwChannelMode[hwChannel] == AdcChannelMode::Fast) { | |||
| 124 | return fastAdc.getAvgAdcValue(hwChannel); | |||
| 125 | } | |||
| 126 | #endif // EFI_USE_FAST_ADC | |||
| 127 | ||||
| 128 | return adcOnchipSlowGetAvgRaw(hwChannel); | |||
| 129 | } | |||
| 130 | ||||
| 131 | static void printAdcValue(int channel) { | |||
| 132 | /* Do this check before conversion to adc_channel_e that is uint8_t based */ | |||
| 133 | if ((channel < EFI_ADC_NONE) || (channel >= EFI_ADC_TOTAL_CHANNELS)) { | |||
| 134 | efiPrintf("Invalid ADC channel %d", channel); | |||
| 135 | return; | |||
| 136 | } | |||
| 137 | int adcValue = adcGetRawValue("print", (adc_channel_e)channel); | |||
| 138 | float voltsInput = adcRawValueToScaledVoltage(adcValue, (adc_channel_e)channel); | |||
| 139 | efiPrintf("adc %d input %.3fV", channel, voltsInput); | |||
| 140 | } | |||
| 141 | ||||
| 142 | void adcPrintChannelReport(const char *prefix, int internalIndex, adc_channel_e hwChannel) | |||
| 143 | { | |||
| 144 | if (isAdcChannelValid(hwChannel)) { | |||
| 145 | ioportid_t port = getAdcChannelPort("print", hwChannel); | |||
| 146 | int pin = getAdcChannelPin(hwChannel); | |||
| 147 | int adcValue = adcGetRawValue("print", hwChannel); | |||
| 148 | auto volts = adcGetRawVoltage("print", hwChannel); | |||
| 149 | auto voltsInput = adcGetScaledVoltage("print", hwChannel); | |||
| 150 | /* Human index starts from 1 */ | |||
| 151 | efiPrintf(" %s ch[%2d] @ %s%d ADC%d 12bit=%4d %.3fV input %.3fV %s", | |||
| 152 | prefix, internalIndex, portname(port), pin, | |||
| 153 | /* TODO: */ hwChannel - EFI_ADC_0 + 1, | |||
| 154 | adcValue, volts.value_or(0), voltsInput.value_or(0), volts ? "valid" : "INVALID"); | |||
| 155 | } | |||
| 156 | } | |||
| 157 | ||||
| 158 | extern void adcOnchipSlowShowReport(); | |||
| 159 | ||||
| 160 | void printFullAdcReport(void) { | |||
| 161 | #if EFI_USE_FAST_ADC | |||
| 162 | efiPrintf("fast %u samples", engine->outputChannels.fastAdcConversionCount); | |||
| 163 | ||||
| 164 | for (int internalIndex = 0; internalIndex < fastAdc.size(); internalIndex++) { | |||
| 165 | adc_channel_e hwChannel = fastAdc.getAdcChannelByInternalIndex(internalIndex); | |||
| 166 | ||||
| 167 | adcPrintChannelReport("F", internalIndex, hwChannel); | |||
| 168 | } | |||
| 169 | #endif // EFI_USE_FAST_ADC | |||
| 170 | ||||
| 171 | adcOnchipSlowShowReport(); | |||
| 172 | } | |||
| 173 | ||||
| 174 | static void setAdcDebugReporting(int value) { | |||
| 175 | adcDebugReporting = value; | |||
| 176 | efiPrintf("adcDebug=%d", adcDebugReporting); | |||
| 177 | } | |||
| 178 | ||||
| 179 | extern void adcOnchipSlowUpdate(efitick_t nowNt); | |||
| 180 | ||||
| 181 | void adcInputsUpdateSubscribers(efitick_t nowNt) { | |||
| 182 | adcOnchipSlowUpdate(nowNt); | |||
| 183 | ||||
| 184 | { | |||
| 185 | ScopePerf perf(PE::AdcProcessSlow); | |||
| 186 | ||||
| 187 | AdcSubscription::UpdateSubscribers(nowNt); | |||
| 188 | ||||
| 189 | protectedGpio_check(nowNt); | |||
| 190 | } | |||
| 191 | } | |||
| 192 | ||||
| 193 | void addFastAdcChannel(const char*, adc_channel_e hwChannel) { | |||
| 194 | if (!isAdcChannelValid(hwChannel)) { | |||
| 195 | return; | |||
| 196 | } | |||
| 197 | ||||
| 198 | #if EFI_USE_FAST_ADC | |||
| 199 | fastAdc.enableChannel(hwChannel); | |||
| 200 | #endif | |||
| 201 | ||||
| 202 | adcHwChannelMode[hwChannel] = AdcChannelMode::Fast; | |||
| 203 | // Nothing to do for slow channels, input is mapped to analog in init_sensors.cpp | |||
| 204 | } | |||
| 205 | ||||
| 206 | void removeChannel(const char*, adc_channel_e hwChannel) { | |||
| 207 | if (!isAdcChannelValid(hwChannel)) { | |||
| 208 | return; | |||
| 209 | } | |||
| 210 | #if EFI_USE_FAST_ADC | |||
| 211 | if (adcHwChannelMode[hwChannel] == AdcChannelMode::Fast) { | |||
| 212 | /* TODO: */ | |||
| 213 | //fastAdc.disableChannel(hwChannel); | |||
| 214 | } | |||
| 215 | #endif | |||
| 216 | ||||
| 217 | adcHwChannelMode[hwChannel] = AdcChannelMode::Off; | |||
| 218 | } | |||
| 219 | ||||
| 220 | // Weak link a stub so that every board doesn't have to implement this function | |||
| 221 | __attribute__((weak)) void setAdcChannelOverrides() { } | |||
| 222 | ||||
| 223 | static void configureInputs() { | |||
| 224 | memset(adcHwChannelMode, (int)AdcChannelMode::Off, sizeof(adcHwChannelMode)); | |||
| 225 | ||||
| 226 | /** | |||
| 227 | * order of analog channels here is totally random and has no meaning | |||
| 228 | * we also have some weird implementation with internal indices - that all has no meaning, it's just a random implementation | |||
| 229 | * which does not mean anything. | |||
| 230 | */ | |||
| 231 | ||||
| 232 | addFastAdcChannel("MAP", engineConfiguration->map.sensor.hwChannel); | |||
| 233 | ||||
| 234 | // not currently used addFastAdcChannel("Vref", engineConfiguration->vRefAdcChannel, ADC_SLOW); | |||
| 235 | ||||
| 236 | addFastAdcChannel("AUXF#1", engineConfiguration->auxFastSensor1_adcChannel); | |||
| 237 | ||||
| 238 | setAdcChannelOverrides(); | |||
| 239 | } | |||
| 240 | ||||
| 241 | void initAdcInputs() { | |||
| 242 | efiPrintf("initAdcInputs()"); | |||
| 243 | ||||
| 244 | configureInputs(); | |||
| 245 | ||||
| 246 | // migrate to 'enable adcdebug' | |||
| 247 | addConsoleActionI("adcdebug", &setAdcDebugReporting); | |||
| 248 | ||||
| 249 | #if EFI_INTERNAL_ADC | |||
| 250 | // This will start HW for all used ADCs | |||
| 251 | portInitAdc(); | |||
| 252 | ||||
| 253 | #if EFI_USE_FAST_ADC | |||
| 254 | // After this point fastAdc is not allowed to add channels | |||
| 255 | fastAdc.init(); | |||
| 256 | #endif // EFI_USE_FAST_ADC | |||
| 257 | ||||
| 258 | addConsoleActionI("adc", (VoidInt) printAdcValue); | |||
| 259 | #else // ! EFI_INTERNAL_ADC | |||
| 260 | efiPrintf("ADC disabled"); | |||
| 261 | #endif // EFI_INTERNAL_ADC | |||
| 262 | ||||
| 263 | // Workaround to pre-feed all sensors with some data... | |||
| 264 | chThdSleepMilliseconds(1); | |||
| 265 | adcInputsUpdateSubscribers(getTimeNowNt()); | |||
| 266 | } | |||
| 267 | ||||
| 268 | void printFullAdcReportIfNeeded(void) { | |||
| 269 | if (!adcDebugReporting) | |||
| 270 | return; | |||
| 271 | printFullAdcReport(); | |||
| 272 | } | |||
| 273 | ||||
| 274 | #else /* not HAL_USE_ADC */ | |||
| 275 | ||||
| 276 | // voltage in MCU universe, from zero to VDD | |||
| 277 | ✗ | __attribute__((weak)) expected<float> adcGetRawVoltage(const char*, adc_channel_e) { | ||
| 278 | ✗ | return expected(0.0f); | ||
| 279 | } | |||
| 280 | ||||
| 281 | // voltage in ECU universe, with all input dividers and OpAmps gains taken into account, voltage at ECU connector pin | |||
| 282 | ✗ | __attribute__((weak)) expected<float> adcGetScaledVoltage(const char*, adc_channel_e) { | ||
| 283 | ✗ | return expected(0.0f); | ||
| 284 | } | |||
| 285 | ||||
| 286 | #endif | |||
| 287 |