GCC Code Coverage Report


Directory: ./
File: firmware/controllers/can/can_bench_test.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 33.3% 2 0 6
Functions: 33.3% 1 0 3
Branches: -% 0 0 0
Decisions: -% 0 - 0

Line Branch Decision Exec Source
1 /*
2 * file can_bench_test.cpp
3 * see also https://github.com/rusefi/rusefi/wiki/CAN BENCH_TEST_BASE_ADDRESS 0x770000
4 *
5 * primary recipient is https://github.com/rusefi/rusefi-hardware/tree/main/digital-inputs/firmware
6 *
7 * todo: shall we not broadcast by default but wait until stim firmware wakes us up?
8 */
9
10 #include "pch.h"
11 #include "bench_test.h"
12 #include "board_id.h"
13 #include "can_bench_test.h"
14 #include "can_msg_tx.h"
15 #include "can_common.h"
16 #include "frequency_sensor.h"
17 #include "settings.h"
18 #include "gpio/gpio_ext.h"
19
20 #ifdef HW_HELLEN
21 #include "hellen_meta.h"
22 #endif // HW_HELLEN
23
24 extern PinRepository pinRepository;
25
26 // todo: WHAT?! document why do we manually truncate higher bits?
27 #define TRUNCATE_TO_BYTE(i) ((i) & 0xff)
28 // raw values are 0..5V, convert it to 8-bit (0..255)
29 #define RAW_TO_BYTE(v) TRUNCATE_TO_BYTE((int)(v * 255.0 / 5.0))
30
31 #if EFI_PROD_CODE
32 /**
33 * QC direct output control API is used by https://github.com/rusefi/stim test device
34 * quite different from bench testing user functionality: QC direct should never be engaged on a real vehicle
35 * Once QC direct control mode is activated the only way out is to reboot the unit!
36 */
37 static bool qcDirectPinControlMode = false;
38 #endif
39
40 39029 /*board public API*/bool isHwQcMode() {
41 #if EFI_PROD_CODE
42 return qcDirectPinControlMode;
43 #else
44 39029 return false;
45 #endif // EFI_PROD_CODE
46 }
47
48 void setHwQcMode() {
49 #if EFI_PROD_CODE
50 qcDirectPinControlMode = true;
51 #if HW_HELLEN
52 if (!getHellenBoardEnabled()) {
53 hellenEnableEn("HW QC");
54 }
55 #endif // HW_HELLEN
56 #endif // EFI_PROD_CODE
57 }
58
59 #if EFI_CAN_SUPPORT
60
61 static void directWritePad(Gpio pin, int value, const char *msg = "") {
62 if (!isBrainPinValid(pin)) {
63 criticalError("QC of invalid pin %d %s", (int)pin, msg);
64 return;
65 }
66
67 #if EFI_GPIO_HARDWARE && EFI_PROD_CODE
68 if (brain_pin_is_onchip(pin)) {
69 palWritePad(getHwPort("can_write", pin), getHwPin("can_write", pin), value);
70 } else {
71 #if (BOARD_EXT_GPIOCHIPS > 0)
72 gpiochips_writePad(pin, value);
73 #endif
74 }
75 #endif // EFI_GPIO_HARDWARE && EFI_PROD_CODE
76 }
77
78 static void qcSetEtbState(uint8_t dcIndex, uint8_t direction) {
79 setHwQcMode();
80 const dc_io *io = &engineConfiguration->etbIo[dcIndex];
81 Gpio controlPin = io->controlPin;
82 directWritePad(controlPin, 1, "DC control");
83 if (engineConfiguration->etb_use_two_wires) {
84 // TLE7209 and L6205
85 // let's force proper pin mode to work around potentially uninitialized subsystem
86 efiSetPadModeWithoutOwnershipAcquisition("QC_ETB_1", io->directionPin1, PAL_MODE_OUTPUT_PUSHPULL);
87 efiSetPadModeWithoutOwnershipAcquisition("QC_ETB_2", io->directionPin2, PAL_MODE_OUTPUT_PUSHPULL);
88
89 directWritePad(io->directionPin1, direction, "DC dir1");
90 directWritePad(io->directionPin2, !direction, "DC dir2");
91 } else {
92 // TLE9201 and VNH2SP30
93 efiSetPadModeWithoutOwnershipAcquisition("QC_ETB", controlPin, PAL_MODE_OUTPUT_PUSHPULL);
94 directWritePad(io->directionPin1, direction, "DC dir");
95 directWritePad(io->disablePin, 0, "DC dis"); // disable pin is inverted - here we ENABLE. direct pin access due to qcDirectPinControlMode
96 }
97 }
98
99 static void setPin(const CANRxFrame& frame, int value) {
100 size_t outputIndex = frame.data8[2];
101 if (outputIndex >= getBoardMetaOutputsCount()) {
102 criticalError("QC pin index %d out of range", outputIndex);
103 return;
104 }
105 #if EFI_GPIO_HARDWARE && EFI_PROD_CODE
106 Gpio* boardOutputs = getBoardMetaOutputs();
107 criticalAssertVoid(boardOutputs != nullptr, "outputs not defined");
108 Gpio pin = boardOutputs[outputIndex];
109
110 int hwIndex = brainPin_to_index(pin);
111 if (pinRepository.getBrainUsedPin(hwIndex) == nullptr) {
112 // if pin is assigned we better configure it
113 efiSetPadModeWithoutOwnershipAcquisition("QC_SET", pin, PAL_MODE_OUTPUT_PUSHPULL);
114 }
115
116 directWritePad(pin, value);
117 #endif // EFI_GPIO_HARDWARE && EFI_PROD_CODE
118 }
119
120 void sendQcBenchEventCounters(size_t bus) {
121 #if EFI_SHAFT_POSITION_INPUT
122 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::EVENT_COUNTERS, 8, bus, /*isExtended*/true);
123
124 int primaryFall = engine->triggerCentral.getHwEventCounter((int)SHAFT_PRIMARY_FALLING);
125 int primaryRise = engine->triggerCentral.getHwEventCounter((int)SHAFT_PRIMARY_RISING);
126 int secondaryFall = engine->triggerCentral.getHwEventCounter((int)SHAFT_SECONDARY_FALLING);
127 int secondaryRise = engine->triggerCentral.getHwEventCounter((int)SHAFT_SECONDARY_RISING);
128
129 msg[0] = TRUNCATE_TO_BYTE(primaryRise + primaryFall);
130 msg[1] = TRUNCATE_TO_BYTE(secondaryRise + secondaryFall);
131
132 for (int camIdx = 0; camIdx < 4; camIdx++) {
133 int vvtRise = 0, vvtFall = 0;
134 if (camIdx < CAM_INPUTS_COUNT) {
135 vvtRise = engine->triggerCentral.vvtEventRiseCounter[camIdx];
136 vvtFall = engine->triggerCentral.vvtEventFallCounter[camIdx];
137 }
138
139 msg[2 + camIdx] = TRUNCATE_TO_BYTE(vvtRise + vvtFall);
140 }
141
142 extern FrequencySensor vehicleSpeedSensor;
143 msg[6] = TRUNCATE_TO_BYTE(vehicleSpeedSensor.eventCounter);
144 #endif // EFI_SHAFT_POSITION_INPUT
145 }
146
147 void sendQcBenchButtonCounters() {
148 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::BUTTON_COUNTERS, 8, /*bus*/0, /*isExtended*/true);
149 msg[0] = TRUNCATE_TO_BYTE(engine->brakePedalSwitchedState.getCounter());
150 msg[1] = TRUNCATE_TO_BYTE(engine->clutchUpSwitchedState.getCounter());
151 msg[2] = TRUNCATE_TO_BYTE(engine->acButtonSwitchedState.getCounter());
152 // todo: start button
153 }
154
155 void sendQcBenchAuxDigitalCounters() {
156 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::AUX_DIGITAL_COUNTERS, 8, /*bus*/0, /*isExtended*/true);
157 for (int i =0;i<LUA_DIGITAL_INPUT_COUNT;i++) {
158 msg[i] = TRUNCATE_TO_BYTE(engine->luaDigitalInputState[i].state.getCounter());
159 }
160 }
161
162 void sendQcBenchRawAnalogValues(size_t bus) {
163 const float values_1[] = {
164 Sensor::getRaw(SensorType::Tps1Primary),
165 Sensor::getRaw(SensorType::Tps1Secondary),
166 Sensor::getRaw(SensorType::AcceleratorPedalPrimary),
167 Sensor::getRaw(SensorType::AcceleratorPedalSecondary),
168 Sensor::getRaw(SensorType::MapSlow),
169 Sensor::getRaw(SensorType::Clt),
170 Sensor::getRaw(SensorType::Iat),
171 Sensor::getRaw(SensorType::BatteryVoltage),
172 };
173
174 const float values_2[] = {
175 Sensor::getRaw(SensorType::Tps2Primary),
176 Sensor::getRaw(SensorType::Tps2Secondary),
177 Sensor::getRaw(SensorType::AuxLinear1),
178 Sensor::getRaw(SensorType::AuxLinear2),
179 Sensor::getRaw(SensorType::OilPressure),
180 Sensor::getRaw(SensorType::FuelPressureLow),
181 Sensor::getRaw(SensorType::FuelPressureHigh),
182 Sensor::getRaw(SensorType::AuxTemp1),
183 };
184 const float lua_values_1[] = {
185 Sensor::getRaw(SensorType::AuxAnalog1),
186 Sensor::getRaw(SensorType::AuxAnalog2),
187 Sensor::getRaw(SensorType::AuxAnalog3),
188 Sensor::getRaw(SensorType::AuxAnalog4),
189 Sensor::getRaw(SensorType::AuxAnalog5),
190 Sensor::getRaw(SensorType::AuxAnalog6),
191 Sensor::getRaw(SensorType::AuxAnalog7),
192 Sensor::getRaw(SensorType::AuxAnalog8),
193 };
194 static_assert(efi::size(values_1) <= 8);
195 static_assert(efi::size(values_2) <= 8);
196 static_assert(efi::size(lua_values_1) <= 8);
197
198
199 // send the first packet
200 {
201 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::RAW_ANALOG_1, 8, bus, /*isExtended*/true);
202 for (size_t valueIdx = 0; valueIdx < efi::size(values_1); valueIdx++) {
203 msg[valueIdx] = RAW_TO_BYTE(values_1[valueIdx]);
204 }
205 }
206 {
207 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::RAW_ANALOG_2, 8, bus, /*isExtended*/true);
208 for (size_t valueIdx = 0; valueIdx < efi::size(values_2); valueIdx++) {
209 msg[valueIdx] = RAW_TO_BYTE(values_2[valueIdx]);
210 }
211 }
212 // todo: time to extract method already?
213 {
214 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::RAW_LUA_ANALOG_1, 8, bus, /*isExtended*/true);
215 for (size_t valueIdx = 0; valueIdx < efi::size(lua_values_1); valueIdx++) {
216 msg[valueIdx] = RAW_TO_BYTE(lua_values_1[valueIdx]);
217 }
218 }
219 }
220
221 static void sendOutBoardMeta(size_t bus) {
222 #if EFI_PROD_CODE
223 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::IO_META_INFO, 8, bus, /*isExtended*/true);
224 msg[0] = (int)bench_test_magic_numbers_e::BENCH_HEADER;
225 msg[1] = 0;
226 msg[2] = getBoardMetaOutputsCount();
227 msg[3] = getBoardMetaLowSideOutputsCount();
228 msg[4] = getBoardMetaDcOutputsCount();
229 #endif // EFI_PROD_CODE
230 }
231
232 void sendQcBenchBoardStatus(size_t bus) {
233 #if EFI_PROD_CODE
234 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::BOARD_STATUS, 8, bus, /*isExtended*/true);
235
236 int boardId = getBoardId();
237 msg[0] = TRUNCATE_TO_BYTE(boardId >> 8);
238 msg[1] = TRUNCATE_TO_BYTE(boardId);
239
240 int numSecondsSinceReset = getTimeNowS();
241 msg[2] = TRUNCATE_TO_BYTE(numSecondsSinceReset >> 16);
242 msg[3] = TRUNCATE_TO_BYTE(numSecondsSinceReset >> 8);
243 msg[4] = TRUNCATE_TO_BYTE(numSecondsSinceReset);
244
245 int engineType = (int) engineConfiguration->engineType;
246 msg[5] = engineType >> 8;
247 msg[6] = engineType;
248 sendOutBoardMeta(bus);
249 #endif // EFI_PROD_CODE
250 }
251
252 #if EFI_PROD_CODE
253 static void sendManualPinTest(int id) {
254 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::MANUAL_PIN_TEST, 8, /*bus*/0, /*isExtended*/true);
255 msg[0] = id;
256 }
257 #endif // EFI_PROD_CODE
258
259 static void sendPinStatePackets(int pinToggleCounter, uint32_t durationsInStateMs[2]) {
260 CanTxMessage msg(CanCategory::BENCH_TEST, (int)bench_test_packet_ids_e::PIN_STATE, 8, /*bus*/0, /*isExtended*/true);
261 msg[0] = TRUNCATE_TO_BYTE(pinToggleCounter >> 8);
262 msg[1] = TRUNCATE_TO_BYTE(pinToggleCounter);
263
264 for (int i = 0, mIdx = 2; i < 2; i++) {
265 msg[mIdx++] = TRUNCATE_TO_BYTE(durationsInStateMs[i] >> 16);
266 msg[mIdx++] = TRUNCATE_TO_BYTE(durationsInStateMs[i] >> 8);
267 msg[mIdx++] = TRUNCATE_TO_BYTE(durationsInStateMs[i]);
268 }
269 }
270
271 // bench test fuel pump pin #5603
272 static void sendPinStatePackets(bench_mode_e benchModePinIdx) {
273 OutputPin *pin = enginePins.getOutputPinForBenchMode(benchModePinIdx);
274 if (pin == nullptr)
275 return;
276 #if EFI_SIMULATOR
277 sendPinStatePackets(pin->pinToggleCounter, pin->durationsInStateMs);
278 #endif // EFI_SIMULATOR
279 }
280
281 static void sendSavedBenchStatePackets() {
282 uint32_t savedDurationsInStateMs[2];
283 int savedPinToggleCounter = getSavedBenchTestPinStates(savedDurationsInStateMs);
284 sendPinStatePackets(savedPinToggleCounter, savedDurationsInStateMs);
285 }
286
287 static void resetPinStats(bench_mode_e benchModePinIdx) {
288 OutputPin *pin = enginePins.getOutputPinForBenchMode(benchModePinIdx);
289
290 if (pin == nullptr)
291 return;
292
293 #if EFI_SIMULATOR
294 pin->resetToggleStats();
295 #endif // EFI_SIMULATOR
296 }
297
298 void processCanQcBenchTest(const CANRxFrame& frame) {
299 if (CAN_EID(frame) != (int)bench_test_packet_ids_e::HW_QC_IO_CONTROL) {
300 return;
301 }
302 if (frame.data8[0] != (int)bench_test_magic_numbers_e::BENCH_HEADER) {
303 return;
304 }
305 setHwQcMode();
306 bench_test_io_control_e command = (bench_test_io_control_e)frame.data8[1];
307 if (command == bench_test_io_control_e::CAN_BENCH_GET_COUNT) {
308 sendOutBoardMeta(0);
309 } else if (command == bench_test_io_control_e::CAN_QC_OUTPUT_CONTROL_SET) {
310 // see also "bench_setpin" console command
311 setPin(frame, 1);
312 } else if (command == bench_test_io_control_e::CAN_QC_OUTPUT_CONTROL_CLEAR) {
313 setPin(frame, 0);
314 } else if (command == bench_test_io_control_e::CAN_QC_ETB) {
315 uint8_t dcIndex = frame.data8[2];
316 uint8_t direction = frame.data8[3];
317 qcSetEtbState(dcIndex, direction);
318 } else if (command == bench_test_io_control_e::CAN_BENCH_SET_ENGINE_TYPE) {
319 int eType = frame.data8[2];
320 // todo: fix firmware for 'false' to be possible - i.e. more of properties should be applied on the fly
321 setEngineType(eType, true);
322 #if EFI_PROD_CODE
323 scheduleReboot();
324 #endif // EFI_PROD_CODE
325 } else if (command == bench_test_io_control_e::CAN_BENCH_START_PIN_TEST) {
326 bench_mode_e benchModePinIdx = (bench_mode_e)frame.data8[2];
327 // ignore previous pin state and stats
328 resetPinStats(benchModePinIdx);
329 } else if (command == bench_test_io_control_e::CAN_BENCH_END_PIN_TEST) {
330 sendSavedBenchStatePackets();
331 } else if (command == bench_test_io_control_e::CAN_BENCH_EXECUTE_BENCH_TEST) {
332 int benchCommandIdx = frame.data8[2];
333 handleBenchCategory(benchCommandIdx);
334 } else if (command == bench_test_io_control_e::CAN_BENCH_QUERY_PIN_STATE) {
335 bench_mode_e benchModePinIdx = (bench_mode_e)frame.data8[2];
336 sendPinStatePackets(benchModePinIdx);
337 }
338 }
339 #endif // EFI_CAN_SUPPORT
340
341 void initQcBenchControls() {
342 #if EFI_CAN_SUPPORT && EFI_PROD_CODE
343 addConsoleActionII("qc_etb", [](int index, int direction) {
344 qcSetEtbState(index, direction);
345 });
346
347 addConsoleActionI("qc_output", [](int index) {
348 Gpio* boardOutputs = getBoardMetaOutputs();
349 criticalAssertVoid(boardOutputs != nullptr, "outputs not defined");
350 Gpio pin = boardOutputs[index];
351 efiSetPadModeWithoutOwnershipAcquisition("qc_output", pin, PAL_MODE_OUTPUT_PUSHPULL);
352
353 int physicalValue = palReadPad(getHwPort("read", pin), getHwPin("read", pin));
354 efiPrintf("original pin %s value %d", hwPortname(pin), physicalValue);
355 palWritePad(getHwPort("write", pin), getHwPin("write", pin), 1 - physicalValue);
356 sendManualPinTest(index);
357 });
358 #endif // EFI_PROD_CODE
359 }
360