/* Included into the generated main_cm0plus.c for the eight Android-controlled * direction-test images. It intentionally lives in the same translation unit * so it can reuse the proven static motor safety/event implementation without * changing the bundled third_party reference source. */ static bool otaInteractiveDriverInitOk; static bool otaInteractiveFinalized = true; static void OtaInteractive_ClearResult(void) { simpleDirectionResult.risingEdges = 0ul; simpleDirectionResult.totalEdges = 0ul; simpleDirectionResult.activeMs = 0ul; simpleDirectionResult.safetyWindowPulses = 0ul; simpleDirectionResult.driverStatus = 0u; simpleDirectionResult.driverFaults = 0u; simpleDirectionResult.finalHallLevel = 0u; simpleDirectionResult.stopReason = SIMPLE_DIRECTION_STOP_REASON_NOT_STARTED; simpleDirectionResult.armed = false; simpleDirectionResult.prepared = false; simpleDirectionResult.timingValid = false; simpleDirectionResult.diagnosticsValid = false; simpleDirectionRunActive = false; simpleDirectionRunComplete = false; } static void OtaInteractive_Finalize(void) { uint8_t driverStatus = 0u; bool statusReadOk; bool stopOk; if((false == simpleDirectionRunComplete) || (false != otaInteractiveFinalized)) { return; } Motor_ServiceAll(); statusReadOk = Drv8718_ReadRegRaw(DRV8718_REG_IC_STAT1, &driverStatus); if(false != statusReadOk) { simpleDirectionResult.driverStatus = driverStatus; simpleDirectionResult.driverFaults = (uint16_t)(driverStatus & DRV8718_IC_STAT1_ALERT_MASK); } stopOk = (MOTOR_API_SUCCESS == Motor_StopMode((motor_mode_t)MOTOR_AUTOMATED_TEST_MODE)); if(false != stopOk) { stopOk = Motor_WaitForCoastToSettle( (motor_mode_t)MOTOR_AUTOMATED_TEST_MODE); } simpleDirectionResult.diagnosticsValid = statusReadOk && stopOk; SimpleDirectionTest_ImmediateAllOff(); (void)OtaSeat_SavePersistentState(); otaInteractiveFinalized = true; } static void OtaInteractive_Stop(void) { uint32_t savedIntrStatus; if(false != simpleDirectionRunActive) { savedIntrStatus = Cy_SysLib_EnterCriticalSection(); Motor_RequestEventFromIsr(MOTOR_AUTOMATED_TEST_MODE, MOTOR_EVENT_ABORTED); Cy_SysLib_ExitCriticalSection(savedIntrStatus); } if((false == simpleDirectionRunComplete) && (false == otaInteractiveFinalized)) { SimpleDirectionTest_ImmediateAllOff(); SimpleDirectionTest_CaptureStopFromIsr( (uint32_t)(hallTimebaseTicks - simpleDirectionStartTicks), Hall_ReadLevel(MOTOR_AUTOMATED_TEST_MODE), SIMPLE_DIRECTION_STOP_REASON_ABORTED, motorRuntime[MOTOR_AUTOMATED_TEST_MODE].recentWindowPulses); } OtaInteractive_Finalize(); } static bool OtaInteractive_Start(void) { if((false != simpleDirectionRunActive) || (false != driverFaultLatched)) { return false; } OtaInteractive_ClearResult(); simpleDirectionResult.armed = true; simpleDirectionResult.prepared = SimpleDirectionTest_Prepare(otaInteractiveDriverInitOk); if(false == simpleDirectionResult.prepared) { simpleDirectionResult.prepared = false; SimpleDirectionTest_ImmediateAllOff(); return false; } /* OtaInteractiveTest_Run() and every completed/stopped test use * SimpleDirectionTest_ImmediateAllOff(), which asserts the external * DRVOFF pin. The original one-shot direction test never starts again * after that call, but an interactive OTA image must explicitly release * DRVOFF before each new TEST_START. Without this transition the SPI * bridge/PWM setup succeeds while the DRV8718 power outputs remain * hardware-disabled (IC_STAT1 still reads 0x80 and Hall stays at zero). */ Cy_GPIO_Clr(DRV_DRVOFF_PORT, DRV_DRVOFF_PIN); Cy_SysTick_DelayInUs(100ul); if(false == SimpleDirectionTest_Start()) { simpleDirectionResult.prepared = false; SimpleDirectionTest_ImmediateAllOff(); return false; } otaInteractiveFinalized = false; return true; } static uint32_t OtaInteractive_StateValue(void) { uint32_t value = ((uint32_t)simpleDirectionResult.stopReason << OTA_TEST_STATE_STOP_REASON_SHIFT) | ((uint32_t)MOTOR_AUTOMATED_TEST_MODE << OTA_TEST_STATE_MODE_SHIFT) | ((uint32_t)MOTOR_AUTOMATED_TEST_DIRECTION << OTA_TEST_STATE_DIRECTION_SHIFT); if(false != simpleDirectionRunActive) { value |= OTA_TEST_STATE_ACTIVE; } if(false != simpleDirectionRunComplete) { value |= OTA_TEST_STATE_COMPLETE; } if(false != driverFaultLatched) { value |= OTA_TEST_STATE_DRIVER_FAULT; } if(false != simpleDirectionResult.prepared) { value |= OTA_TEST_STATE_PREPARED; } if(false != simpleDirectionResult.timingValid) { value |= OTA_TEST_STATE_TIMING_VALID; } if(false != simpleDirectionResult.diagnosticsValid) { value |= OTA_TEST_STATE_DIAGNOSTICS_VALID; } return value; } static uint32_t OtaInteractive_InfoValue(uint8_t selector) { switch((ota_test_info_selector_t)selector) { case OTA_TEST_INFO_STATE: return OtaInteractive_StateValue(); case OTA_TEST_INFO_RISING_EDGES: return simpleDirectionResult.risingEdges; case OTA_TEST_INFO_TOTAL_EDGES: return simpleDirectionResult.totalEdges; case OTA_TEST_INFO_ACTIVE_MS: return simpleDirectionResult.activeMs; case OTA_TEST_INFO_SAFETY_WINDOW_PULSES: return simpleDirectionResult.safetyWindowPulses; case OTA_TEST_INFO_DRIVER_DIAGNOSTICS: return ((uint32_t)simpleDirectionResult.driverFaults << 16u) | (uint32_t)simpleDirectionResult.driverStatus; default: return 0ul; } } static void OtaInteractive_Respond(const ota_can_control_frame_t *frame, ota_status_t status, uint32_t value) { (void)OtaCan_QueueResponse(frame->data[1], frame->data[2], (uint8_t)status, value); } static void OtaInteractive_EnterBootloader( const ota_can_control_frame_t *frame) { uint32_t waitMs; OtaInteractive_Stop(); (void)OtaSeat_SavePersistentState(); OtaInteractive_Respond(frame, OTA_STATUS_OK, OtaInteractive_StateValue()); for(waitMs = 0ul; waitMs < 100ul; waitMs++) { OtaCan_Service(); if(false != OtaCan_TxIdle()) { break; } Cy_SysTick_DelayInUs(1000ul); } SimpleDirectionTest_ImmediateAllOff(); Ota_EnterBootloader(); } static void OtaInteractive_HandleControl( const ota_can_control_frame_t *frame) { uint8_t opcode = frame->data[2]; if(frame->data[0] != OTA_CONTROL_MAGIC) { OtaInteractive_Respond(frame, OTA_STATUS_BAD_MAGIC, 0ul); return; } if(false != OtaSeat_HandleInfoControl(frame)) { return; } switch((ota_opcode_t)opcode) { case OTA_OPCODE_TEST_INFO: if(frame->data[3] > OTA_TEST_INFO_DRIVER_DIAGNOSTICS) { OtaInteractive_Respond(frame, OTA_STATUS_BAD_ARGUMENT, frame->data[3]); } else { OtaInteractive_Respond( frame, OTA_STATUS_OK, OtaInteractive_InfoValue(frame->data[3])); } break; case OTA_OPCODE_TEST_START: if(false != OtaInteractive_Start()) { OtaInteractive_Respond(frame, OTA_STATUS_OK, OtaInteractive_StateValue()); } else { OtaInteractive_Respond(frame, OTA_STATUS_BAD_STATE, OtaInteractive_StateValue()); } break; case OTA_OPCODE_TEST_STOP: OtaInteractive_Stop(); OtaInteractive_Respond(frame, OTA_STATUS_OK, OtaInteractive_StateValue()); break; case OTA_OPCODE_ENTER_BOOTLOADER: OtaInteractive_EnterBootloader(frame); break; default: OtaInteractive_Respond(frame, OTA_STATUS_BAD_OPCODE, opcode); break; } } static int OtaInteractiveTest_Run(bool driverInitOk) { ota_can_control_frame_t frame; /* Keep the original one-shot runner linked for source-level parity; the * interactive build intentionally dispatches through this loop instead. */ (void)SimpleDirectionTest_Run; otaInteractiveDriverInitOk = driverInitOk; OtaInteractive_ClearResult(); OtaSeat_RestorePersistentState(); SimpleDirectionTest_ImmediateAllOff(); OtaCan_DebugStart(); if(false == OtaCan_Init()) { for(;;) { SimpleDirectionTest_ImmediateAllOff(); } } for(;;) { Motor_ServiceAll(); OtaCan_Service(); if((false != simpleDirectionRunComplete) && (false == otaInteractiveFinalized)) { OtaInteractive_Finalize(); } while(false != OtaCan_PopControl(&frame)) { OtaInteractive_HandleControl(&frame); OtaCan_Service(); } __WFI(); } }