Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | #include "pch.h" | |||
2 | #include "ego.h" | |||
3 | ||||
4 | #if EFI_CAN_SUPPORT || EFI_UNIT_TEST | |||
5 | #include "AemXSeriesLambda.h" | |||
6 | #include "wideband_firmware/for_rusefi/wideband_can.h" | |||
7 | #include "rusefi_wideband.h" | |||
8 | ||||
9 | static constexpr uint32_t aem_base = 0x180; | |||
10 | static constexpr uint32_t rusefi_base = WB_DATA_BASE_ADDR; | |||
11 | // sensor transmits at 100hz, allow a frame to be missed | |||
12 | static constexpr efidur_t sensor_timeout = MS2NT(3 * WBO_TX_PERIOD_MS); | |||
13 | ||||
14 | 8 | AemXSeriesWideband::AemXSeriesWideband(uint8_t sensorIndex, SensorType type) | ||
15 | : CanSensorBase( | |||
16 | 0, // ID passed here doesn't matter since we override acceptFrame | |||
17 | type, | |||
18 | sensor_timeout | |||
19 | ) | |||
20 | 8 | , m_sensorIndex(sensorIndex) | ||
21 | { | |||
22 | 8 | faultCode = m_faultCode = static_cast<uint8_t>(wbo::Fault::CanSilent);// silent, initial state is "no one has spoken to us so far" | ||
23 | 8 | isValid = m_isFault = m_afrIsValid = false; | ||
24 | // wait for first rusEFI WBO standard frame with protocol version field | |||
25 | 8 | fwUnsupported = true; | ||
26 | // Do not light "FW: need update" indicator until we get some data from WBO | |||
27 | 8 | fwOutdated = false; | ||
28 | 8 | } | ||
29 | ||||
30 | 53 | can_wbo_type_e AemXSeriesWideband::sensorType() const { | ||
31 | 53 | return engineConfiguration->canWbo[m_sensorIndex].type; | ||
32 | } | |||
33 | ||||
34 | 13 | uint32_t AemXSeriesWideband::getReCanId() const { | ||
35 | // 0th sensor is 0x190 and 0x191, 1st sensor is 0x192 and 0x193 | |||
36 | 13 | return rusefi_base + 2 * engineConfiguration->canWbo[m_sensorIndex].reId; | ||
37 | } | |||
38 | ||||
39 | 5 | uint32_t AemXSeriesWideband::getAemCanId() const { | ||
40 | // 0th sensor is 0x00000180, 1st sensor is 0x00000181, etc | |||
41 | 5 | return aem_base + engineConfiguration->canWbo[m_sensorIndex].aemId; | ||
42 | } | |||
43 | ||||
44 | 21 | bool AemXSeriesWideband::acceptFrame(const size_t busIndex, const CANRxFrame& frame) const { | ||
45 | #if !EFI_UNIT_TEST | |||
46 | if (busIndex != getWidebandBus()) { | |||
47 | return false; | |||
48 | } | |||
49 | #endif | |||
50 | ||||
51 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 21 times.
|
21 | if (frame.DLC != 8) { |
52 | ✗ | return false; | ||
53 | } | |||
54 | ||||
55 | 21 | can_wbo_type_e type = sensorType(); | ||
56 | ||||
57 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 21 times.
|
21 | if (type == DISABLED) { |
58 | ✗ | return false; | ||
59 | } | |||
60 | ||||
61 | // RusEFI wideband uses standard CAN IDs | |||
62 |
4/4✓ Branch 0 taken 14 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 1 time.
|
2/2✓ Decision 'true' taken 13 times.
✓ Decision 'false' taken 8 times.
|
21 | if ((!CAN_ISX(frame)) && (type == RUSEFI)) { |
63 | // 0th sensor is 0x190 and 0x191, 1st sensor is 0x192 and 0x193 | |||
64 | 13 | uint32_t rusefiBaseId = getReCanId(); | ||
65 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
13 | return ((CAN_SID(frame) == rusefiBaseId) || (CAN_SID(frame) == rusefiBaseId + 1)); | |
66 | } | |||
67 | ||||
68 | // AEM uses extended CAN ID | |||
69 |
4/4✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 2 times.
|
2/2✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 3 times.
|
8 | if ((CAN_ISX(frame)) && (type == AEM)) { |
70 | // 0th sensor is 0x00000180, 1st sensor is 0x00000181, etc | |||
71 | 5 | uint32_t aemXSeriesId = getAemCanId(); | ||
72 | 5 | return (CAN_EID(frame) == aemXSeriesId); | ||
73 | } | |||
74 | ||||
75 | 3 | return false; | ||
76 | } | |||
77 | ||||
78 | 9 | bool AemXSeriesWideband::isHeaterAllowed() { | ||
79 |
3/4✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 4 times.
|
9 | return ((sensorType() == AEM) || (engine->engineState.heaterControlEnabled)); | |
80 | } | |||
81 | ||||
82 | 11 | void AemXSeriesWideband::refreshState() { | ||
83 | 11 | can_wbo_type_e type = sensorType(); | ||
84 | ||||
85 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 11 times.
|
11 | if (getTimeNowNt() - sensor_timeout > m_lastUpdate) { |
86 | ✗ | faultCode = static_cast<uint8_t>(wbo::Fault::CanSilent); | ||
87 | ✗ | isValid = false; | ||
88 | ✗ | return; | ||
89 | } | |||
90 | ||||
91 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
|
2/2✓ Decision 'true' taken 8 times.
✓ Decision 'false' taken 3 times.
|
11 | if (type == RUSEFI) { |
92 | // This is RE WBO | |||
93 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 4 times.
|
8 | if (!isHeaterAllowed()) { |
94 | 4 | faultCode = static_cast<uint8_t>(wbo::Fault::NotAllowed); | ||
95 | 4 | isValid = false; | ||
96 | // Do not check for any errors while heater is not allowed | |||
97 | 4 | return; | ||
98 | } | |||
99 | ||||
100 | 4 | isValid = m_afrIsValid; | ||
101 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 3 times.
|
4 | if (m_faultCode != static_cast<uint8_t>(wbo::Fault::None)) { |
102 | // Report error code from WBO | |||
103 | 1 | faultCode = m_faultCode; | ||
104 | 1 | isValid = false; | ||
105 | 1 | return; | ||
106 | } | |||
107 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 3 times.
✗ Decision 'false' not taken.
|
3 | } else if (type == AEM) { |
108 | // This is AEM with two flags only and no debug fields | |||
109 | 3 | heaterDuty = 0; | ||
110 | 3 | pumpDuty = 0; | ||
111 | 3 | tempC = 0; | ||
112 | 3 | nernstVoltage = 0; | ||
113 | ||||
114 | 3 | isValid = m_afrIsValid; | ||
115 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 2 times.
|
3 | if (m_isFault) { |
116 | 1 | faultCode = HACK_INVALID_AEM; | ||
117 | 1 | isValid = false; | ||
118 | 1 | return; | ||
119 | } | |||
120 | } else { | |||
121 | // disabled or analog | |||
122 | // clear all livedata | |||
123 | ✗ | heaterDuty = 0; | ||
124 | ✗ | pumpDuty = 0; | ||
125 | ✗ | tempC = 0; | ||
126 | ✗ | nernstVoltage = 0; | ||
127 | ✗ | isValid = false; | ||
128 | ✗ | return; | ||
129 | } | |||
130 | ||||
131 | 5 | faultCode = static_cast<uint8_t>(wbo::Fault::None); | ||
132 | } | |||
133 | ||||
134 | 12 | void AemXSeriesWideband::decodeFrame(const CANRxFrame& frame, efitick_t nowNt) { | ||
135 | 12 | bool accepted = false; | ||
136 | // accept frame has already guaranteed that this message belongs to us | |||
137 | // We just have to check if it's AEM or rusEFI | |||
138 |
2/2✓ Branch 1 taken 9 times.
✓ Branch 2 taken 3 times.
|
2/2✓ Decision 'true' taken 9 times.
✓ Decision 'false' taken 3 times.
|
12 | if (sensorType() == RUSEFI){ |
139 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | uint32_t id = CAN_ID(frame); | |
140 | ||||
141 | // rusEFI custom format | |||
142 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 5 times.
|
9 | if ((id & 0x1) != 0) { |
143 | // low bit is set, this is the "diag" frame | |||
144 | 4 | decodeRusefiDiag(frame); | ||
145 | } else { | |||
146 | // low bit not set, this is standard frame | |||
147 | 5 | accepted = decodeRusefiStandard(frame, nowNt); | ||
148 | } | |||
149 | } else /* if (sensorType() == AEM) */ { | |||
150 | 3 | accepted = decodeAemXSeries(frame, nowNt); | ||
151 | } | |||
152 | ||||
153 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
|
2/2✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 6 times.
|
11 | if (accepted) { |
154 | 5 | m_lastUpdate = nowNt; | ||
155 | } | |||
156 | ||||
157 | // Do refresh on each CAN packet | |||
158 | 11 | refreshState(); | ||
159 | 11 | } | ||
160 | ||||
161 | /** | |||
162 | * @return true if valid, false if invalid | |||
163 | */ | |||
164 | 6 | bool AemXSeriesWideband::decodeAemXSeries(const CANRxFrame& frame, efitick_t nowNt) { | ||
165 | // we don't care | |||
166 | 6 | fwUnsupported = false; | ||
167 | 6 | fwOutdated = false; | ||
168 | ||||
169 | // reports in 0.0001 lambda per LSB | |||
170 | 6 | uint16_t lambdaInt = SWAP_UINT16(frame.data16[0]); | ||
171 | 6 | float lambdaFloat = 0.0001f * lambdaInt; | ||
172 | ||||
173 | // bit 6 indicates sensor fault | |||
174 | 6 | m_isFault = frame.data8[7] & 0x40; | ||
175 | // bit 7 indicates valid | |||
176 | 6 | m_afrIsValid = frame.data8[6] & 0x80; | ||
177 | ||||
178 |
4/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 times.
|
2/2✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 2 times.
|
6 | if ((m_isFault) || (!m_afrIsValid)) { |
179 | 4 | invalidate(); | ||
180 | 4 | return false; | ||
181 | } | |||
182 | ||||
183 | 2 | setValidValue(lambdaFloat, nowNt); | ||
184 | 2 | refreshSmoothedLambda(lambdaFloat); | ||
185 | 2 | return true; | ||
186 | } | |||
187 | ||||
188 | 5 | bool AemXSeriesWideband::decodeRusefiStandard(const CANRxFrame& frame, efitick_t nowNt) { | ||
189 | 5 | auto data = reinterpret_cast<const wbo::StandardData*>(&frame.data8[0]); | ||
190 | ||||
191 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 5 times.
|
5 | if (data->Version > RUSEFI_WIDEBAND_VERSION) { |
192 | ✗ | firmwareError(ObdCode::OBD_WB_FW_Mismatch, "Wideband controller index %d has newer protocol version (0x%02x while 0x%02x supported), please update ECU FW!", | ||
193 | ✗ | m_sensorIndex, data->Version, RUSEFI_WIDEBAND_VERSION); | ||
194 | ✗ | fwUnsupported = true; | ||
195 | ✗ | return false; | ||
196 | } | |||
197 | ||||
198 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 4 times.
|
5 | if (data->Version < RUSEFI_WIDEBAND_VERSION_MIN) { |
199 | 1 | firmwareError(ObdCode::OBD_WB_FW_Mismatch, "Wideband controller index %d has outdated protocol version (0x%02x while minimum 0x%02x expected), please update WBO!", | ||
200 | 1 | m_sensorIndex, data->Version, RUSEFI_WIDEBAND_VERSION_MIN); | ||
201 | ✗ | fwUnsupported = true; | ||
202 | ✗ | return false; | ||
203 | } | |||
204 | ||||
205 | 4 | fwUnsupported = false; | ||
206 | // compatible, but not latest | |||
207 | 4 | fwOutdated = (data->Version != RUSEFI_WIDEBAND_VERSION); | ||
208 | // TODO: request and check builddate | |||
209 | ||||
210 | 4 | tempC = data->TemperatureC; | ||
211 | 4 | float lambda = 0.0001f * data->Lambda; | ||
212 | 4 | m_afrIsValid = data->Valid & 0x01; | ||
213 | ||||
214 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 2 times.
|
4 | if (!m_afrIsValid) { |
215 | 2 | invalidate(); | ||
216 | } else { | |||
217 | 2 | setValidValue(lambda, nowNt); | ||
218 | 2 | refreshSmoothedLambda(lambda); | ||
219 | } | |||
220 | ||||
221 | 4 | return true; | ||
222 | } | |||
223 | ||||
224 | 4 | void AemXSeriesWideband::refreshSmoothedLambda(float lambda) { | ||
225 |
1/3✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
4 | switch (type()) { | |
226 |
1/1✓ Decision 'true' taken 4 times.
|
4 | case SensorType::Lambda1: { | |
227 | 4 | expAverageLambda1.setSmoothingFactor(engineConfiguration->afrExpAverageAlpha); | ||
228 | 4 | smoothedLambda1Sensor.setValidValue(expAverageLambda1.initOrAverage(lambda), getTimeNowNt()); | ||
229 | 4 | break; | ||
230 | } | |||
231 | ✗ | case SensorType::Lambda2: { | ||
232 | ✗ | expAverageLambda2.setSmoothingFactor(engineConfiguration->afrExpAverageAlpha); | ||
233 | ✗ | smoothedLambda2Sensor.setValidValue(expAverageLambda2.initOrAverage(lambda), getTimeNowNt()); | ||
234 | ✗ | break; | ||
235 | } | |||
236 | ✗ | default: | ||
237 | ✗ | break; | ||
238 | } | |||
239 | 4 | } | ||
240 | ||||
241 | 4 | void AemXSeriesWideband::decodeRusefiDiag(const CANRxFrame& frame) { | ||
242 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 4 times.
|
4 | if (fwUnsupported) { |
243 | ✗ | return; | ||
244 | } | |||
245 | ||||
246 | 4 | auto data = reinterpret_cast<const wbo::DiagData*>(&frame.data8[0]); | ||
247 | ||||
248 | // convert to percent | |||
249 | 4 | heaterDuty = data->HeaterDuty / 2.55f; | ||
250 | 4 | pumpDuty = data->PumpDuty / 2.55f; | ||
251 | ||||
252 | // convert to volts | |||
253 | 4 | nernstVoltage = data->NernstDc * 0.001f; | ||
254 | ||||
255 | // no conversion, just ohms | |||
256 | 4 | esr = data->Esr; | ||
257 | ||||
258 | // This state is handle in refreshState() | |||
259 | //if (!isHeaterAllowed()) { | |||
260 | // m_faultCode = HACK_CRANKING_VALUE; | |||
261 | // return; | |||
262 | //} | |||
263 | ||||
264 | 4 | m_faultCode = static_cast<uint8_t>(data->Status); | ||
265 | ||||
266 |
5/6✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 3 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 3 times.
|
4 | if ((data->Status != wbo::Fault::None) && isHeaterAllowed()) { |
267 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | auto code = m_sensorIndex == 0 ? ObdCode::Wideband_1_Fault : ObdCode::Wideband_2_Fault; | |
268 | 1 | warning(code, "Wideband #%d fault: %s", (m_sensorIndex + 1), wbo::describeFault(data->Status)); | ||
269 | } | |||
270 | } | |||
271 | ||||
272 | #endif | |||
273 |