/* ========================================================================= */ /* 1. Includes */ /* ========================================================================= */ #include "main.h" /* ========================================================================= */ /* 2. Local Macros & Constants (내부 전용 매크로 및 상수) */ /* ========================================================================= */ #define ENGINE_MAXIMUM_SPEED (2800U) #define ENGINE_OPERATION_SPEED (2400U) #define ENGINE_DIFF_SPEED (400U) // 2800 - 2400 #define LED_OFF (0U) #define LED_ON (1U) #define LED_BLINK (2U) /* ========================================================================= */ /* 3. Local Typedefs & Structures (내부 전용 사용자 정의 자료형) */ /* ========================================================================= */ // No Code /* ========================================================================= */ /* 4. Internal Linkage Function Declarations (내부 동작용 Static 함수 선언) */ /* ========================================================================= */ static void CInitialStandby(void); static void CEmergencyStop(void); static void CProcessApuStateReady(void); static void CProcessApuStatePreheat(void); static void CProcessApuStateCranking(void); static void CProcessApuStateRetryCranking(void); static void CProcessApuStateEngineIdle(void); static void CProcessApuStateGenerating(void); static void CProcessApuStateCooldown(void); static void CProcessApuStateStopping(void); static void CProcessApuStateTransition(void); // 비상/시동/정지 전이 판별용 static void CSetEngineActualRpm(Uint16 Rpm); static float32 CGetGcuLoadPower(void); static Uint16 CDynamicRpmControl(void); static void CLedControl(Uint16 idx, Uint16 state); /* ========================================================================= */ /* 5. Global Variables & Structure Initialization (전역 변수 및 구조체 초기화) */ /* ========================================================================= */ // No Code /* ========================================================================= */ /* Function Definitions */ /* ========================================================================= */ static void CProcessApuStateReady(void) { // 냉각수 펌프 및 냉각팬 시작 CSetAuxCtrlPin(IDX_CS_COOLANT_PUMP, 1U); CSetAuxCtrlPin(IDX_CS_FAN1, 1U); CSetAuxCtrlPin(IDX_CS_FAN2, 1U); // ECU 동작 명령 송신, 2400 RPM 설정 CSetEcuCommand((Uint16)IDX_ECU_CMD_START); GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_PREHEAT; } static void CProcessApuStatePreheat(void) { if (((Rx301.State >> 1U) & 0x07U) == (Uint16)IDX_ECU_STAT_STARTING) { GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_CRANKING; } else { // PRE HEAT 상태가 60초 이상 지속 될 경우 알람처리 if (CSoftWaitCountProcedure(SOFTTIMER_WAIT_PREHEAT, TIME_60SEC) == (Uint16)TIME_OVER) { // 알람처리를 할지 무기한 대기 할 지 검토 필요 } } } static void CProcessApuStateCranking(void) { CSetGcuCommand((Uint16)IDX_GCU_CMD_CRANKING); if (((Rx301.State >> 1U) & 0x07U) == (Uint16)IDX_ECU_STAT_IDLE) { CSetGcuCommand((Uint16)IDX_GCU_CMD_STOP_CRANKING); GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_ENGINE_IDLE; GeneralOperValue.uiRetryCrankingCount = 0U; CSoftWaitCountClear(SOFTTIMER_WAIT_CRANKING); } else { // 10초간 시동 시도 → 5초 동안 휴식 → 3회 반복 if (CSoftWaitCountProcedure(SOFTTIMER_WAIT_CRANKING, TIME_10SEC) == (Uint16)TIME_OVER) { CSetGcuCommand((Uint16)IDX_GCU_CMD_STOP_CRANKING); GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_RETRY_CRANKING; CSoftWaitCountClear(SOFTTIMER_WAIT_RETRY_CRANKING); } } } static void CProcessApuStateRetryCranking(void) { if (GeneralOperValue.uiRetryCrankingCount < 3U) { // 5초 대기 후 재시도 if (CSoftWaitCountProcedure(SOFTTIMER_WAIT_RETRY_CRANKING, (TIME_1SEC * 5UL)) == (Uint16)TIME_OVER) { GeneralOperValue.uiRetryCrankingCount++; GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_CRANKING; CSoftWaitCountClear(SOFTTIMER_WAIT_CRANKING); } } else { ulDcuTotalAlarm = (1UL << (Uint32)(Uint16)IDX_FAULT_DCU_CRANKING_FAIL); } } static void CProcessApuStateEngineIdle(void) { if (((Rx301.State >> 1U) & 0x07U) == (Uint16)IDX_ECU_STAT_OPERATION) { // 보조엔진제어기의 상태가 OPERATION이고 보조엔진의 속도가 2300 RPM 이상 5초 유지 시 발전상태로 전환 if (CGetEngineActualRpm() >= (ENGINE_OPERATION_SPEED - 100U)) // 2300 RPM { if (CSoftWaitCountProcedure(SOFTTIMER_WAIT_OPERATION, TIME_5SEC) == (Uint16)TIME_OVER) { GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_GENERATING; } } else { CSoftWaitCountClear(SOFTTIMER_WAIT_OPERATION); } } } static void CProcessApuStateGenerating(void) { CSetGcuCommand((Uint16)IDX_GCU_CMD_GENERATING); // 발전 명령 송신 GeneralOperValue.uiDynamicRPM = CDynamicRpmControl(); CSetEngineActualRpm(GeneralOperValue.uiDynamicRPM); // RPM 가변 제어 시작 } static void CProcessApuStateCooldown(void) { Uint16 IsRpmZero; Uint16 IsTimeout; // 쿨다운: 발전 중지 -> 엔진 IDLE로 변경 CSetGcuCommand((Uint16)IDX_GCU_CMD_STOP); CSetEcuCommand((Uint16)IDX_ECU_CMD_STOP); IsRpmZero = (CGetEngineActualRpm() == 0U) ? 1U : 0U; IsTimeout = (CSoftWaitCountProcedure(SOFTTIMER_WAIT_AFTER_COOLDOWN, TIME_60SEC) == 1U) ? 1U : 0U; if ((IsRpmZero == 1U) || (IsTimeout == 1U)) { GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_STOPPING; } } static void CProcessApuStateStopping(void) { if (GeneralOperValue.uiApuState == (Uint16)IDX_APU_OPER_STOPPING) { CInitialStandby(); GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_STANDBY; } } static void CProcessApuStateTransition(void) { if (GeneralOperValue.uiApuState == (Uint16)IDX_APU_OPER_EMERGENCY) { GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_STANDBY; CInitialStandby(); } if (GeneralOperValue.uiApuState == (Uint16)IDX_APU_OPER_STANDBY) { if (KeyOperValue.KeyList.EngineStartStop == 1U) { GeneralOperValue.uiRetryCrankingCount = 0U; if ((GeneralOperValue.Conection.Gcu == 1U) && (GeneralOperValue.Conection.Ecu == 1U)) { GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_READY; } else { CommCheck.Gcu = (GeneralOperValue.Conection.Gcu == 0U) ? COMM_TIME_OUT_COUNT : 0U; CommCheck.Ecu = (GeneralOperValue.Conection.Ecu == 0U) ? COMM_TIME_OUT_COUNT : 0U; } } } else { if ((GeneralOperValue.uiApuState >= (Uint16)IDX_APU_OPER_READY) && (GeneralOperValue.uiApuState <= (Uint16)IDX_APU_OPER_GENERATING)) { if (KeyOperValue.KeyList.EngineStartStop == 0U) { if (GeneralOperValue.uiApuState == (Uint16)IDX_APU_OPER_GENERATING) { GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_COOLDOWN; } else { GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_STOPPING; } } } } } void CApuOperProcedure(void) { // 입력 신호 Lo Active Uint16 EngineHeaterSig = (GPIO_ENGINE_HEATER_ACTIVE() == false) ? 1U : 0U; Uint16 FuelPumpSig = (GPIO_FUEL_PUMP_ACTIVE() == false) ? 1U : 0U; Uint16 GlowPlugSig = (GPIO_GLOW_PLUG_ACTIVE() == false) ? 1U : 0U; Uint16 SolenoidSig = (GPIO_SOLENOID_ACTIVE() == false) ? 1U : 0U; Uint16 FailSafeSig = (GPIO_FAIL_SAFE_READ() == false) ? 1U : 0U; // 비상 상황 체크 if ((GeneralOperValue.uiFaultOccured > 0U) || (KeyOperValue.KeyList.Emergency == 1U) || (FailSafeSig == 1U)) { GeneralOperValue.uiApuState = (Uint16)IDX_APU_OPER_EMERGENCY; CEmergencyStop(); } else { // 외부 조작에 의한 상태 변경 확인 CProcessApuStateTransition(); // ECU Aux Bypass 제어 if (GeneralOperValue.uiApuState > (Uint16)IDX_APU_OPER_STANDBY) { CSetAuxCtrlPin(IDX_CS_ENG_HEATER, EngineHeaterSig); CSetAuxCtrlPin(IDX_CS_GLOW_PLUG, GlowPlugSig); CSetAuxCtrlPin(IDX_CS_SOLENOID, SolenoidSig); CSetAuxCtrlPin(IDX_CS_FUEL_PUMP, FuelPumpSig); } // 각 상태별 동작 수행 switch (GeneralOperValue.uiApuState) { case (Uint16)IDX_APU_OPER_READY: { CProcessApuStateReady(); break; } case (Uint16)IDX_APU_OPER_PREHEAT: { CProcessApuStatePreheat(); break; } case (Uint16)IDX_APU_OPER_CRANKING: { CProcessApuStateCranking(); break; } case (Uint16)IDX_APU_OPER_RETRY_CRANKING: { CProcessApuStateRetryCranking(); break; } case (Uint16)IDX_APU_OPER_ENGINE_IDLE: { CProcessApuStateEngineIdle(); break; } case (Uint16)IDX_APU_OPER_GENERATING: { CProcessApuStateGenerating(); break; } case (Uint16)IDX_APU_OPER_COOLDOWN: { CProcessApuStateCooldown(); break; } default: { CProcessApuStateStopping(); break; } } } } static Uint16 CDynamicRpmControl(void) { float32 TargetRPM; Uint16 ReturnRpm; if (GeneralOperValue.uiApuState == (Uint16)IDX_APU_OPER_GENERATING) { // 0.0kW~17.0kW 부하량에 따라 목표 RPM 실시간 계산 TargetRPM = (float32)ENGINE_OPERATION_SPEED + ((CGetGcuLoadPower() * 0.058823F) * (float32)ENGINE_DIFF_SPEED); // 0.058823 = 1/17kw ReturnRpm = (Uint16)((float32)(TargetRPM + 0.5F)); // 소수점 반올림 } else { // 발전 상태가 아닐 때는 기본 2400 RPM 반환 ReturnRpm = ENGINE_OPERATION_SPEED; } ReturnRpm = (ENGINE_MAXIMUM_SPEED > ReturnRpm) ? ReturnRpm : ENGINE_MAXIMUM_SPEED; return ReturnRpm; } static void CInitialStandby(void) { CSetGcuCommand((Uint16)IDX_GCU_CMD_STOP); CSetEcuCommand((Uint16)IDX_ECU_CMD_STOP); COffChipSelect(); CSoftWaitCountClear(SOFTTIMER_WAIT_CRANKING); CSoftWaitCountClear(SOFTTIMER_WAIT_RETRY_CRANKING); CSoftWaitCountClear(SOFTTIMER_WAIT_PREHEAT); CSoftWaitCountClear(SOFTTIMER_WAIT_PREHEAT); CSoftWaitCountClear(SOFTTIMER_WAIT_OPERATION); CSoftWaitCountClear(SOFTTIMER_WAIT_AFTER_COOLDOWN); GeneralOperValue.uiEmergency = 0U; GpioDataRegs.GPBCLEAR.bit.GPIO55 = 1U; // GPIO_FAULT_CMD } static void CEmergencyStop(void) { KeyOperValue.KeyList.EngineStartStop = 0U; // 비상정지 상황에서는 시동/정지 키 초기화 CSetGcuCommand((Uint16)IDX_GCU_CMD_STOP); CSetEcuCommand((Uint16)IDX_ECU_CMD_EMERGENCY); COffChipSelect(); CSoftWaitCountClear(SOFTTIMER_WAIT_CRANKING); CSoftWaitCountClear(SOFTTIMER_WAIT_RETRY_CRANKING); CSoftWaitCountClear(SOFTTIMER_WAIT_AFTER_COOLDOWN); GeneralOperValue.uiEmergency = 1U; GpioDataRegs.GPBSET.bit.GPIO55 = 1U; //GPIO_FAULT_CMD } static void CSetEngineActualRpm(Uint16 Rpm) { GeneralOperValue.EcuCommand.RpmSetPoint = Rpm; } Uint16 CGetEngineActualRpm(void) { return (Uint16)Rx320.ActualRpm; } static float32 CGetGcuLoadPower(void) { float32 power = ((float32)Rx220.Power * 0.1F); // 범위를 0.0 ~ 17.0 으로 제한 if (power > 17.0F) { power = 17.0F; } else { if (power < 0.0F) { power = 0.0; } } return power; } Uint16 CGetGeneratorRpm(void) { return Rx220.Rpm; } void CSetGcuCommand(Uint16 Command) { GeneralOperValue.GcuCommand.PlayCmd = Command; } void CSetEcuCommand(Uint16 Command) { if ((Command == (Uint16)IDX_ECU_CMD_STOP) || (Command == (Uint16)IDX_ECU_CMD_EMERGENCY)) { GeneralOperValue.EcuCommand.EngineStart = 0U; GeneralOperValue.EcuCommand.EngineStop = 1U; CSetEngineActualRpm(0U); } else { // [ECU_OPER_CMD_START] GeneralOperValue.EcuCommand.EngineStart = 1U; GeneralOperValue.EcuCommand.EngineStop = 0U; #if 0 // RPM 테스트 CSetEngineActualRpm(Rx400.SetRPM.PCAN_RPM); #else CSetEngineActualRpm(2400U); #endif } } int16 CGetEngCoolantTemperature(void) { return (int16) Rx321.CoolantTemperature - 40; // 온도 오프셋 -40도 } void CDebugModeProcedure(void) { if (GeneralOperValue.Maintenance.ManualCranking == 1U) { if (GeneralOperValue.uiFaultOccured == 0U) { // 알람이 없을 경우만 동작 하도록 함. CSetGcuCommand((Uint16)IDX_GCU_CMD_CRANKING); } } else { CSetGcuCommand((Uint16)IDX_GCU_CMD_STOP); } if (GeneralOperValue.Maintenance.LampTest == 1U) { CLedControl(0U, 1U); CLedControl(1U, 1U); CLedControl(2U, 1U); } else { CLedControl(0U, 0U); CLedControl(1U, 0U); CLedControl(2U, 0U); } if (GeneralOperValue.Maintenance.KeyTest == 1U) { Uint16 uiKeyUp = (GPIO_KEY_UP() == 0U) ? 1U : 0U; Uint16 uiKeyDn = (GPIO_KEY_DOWN() == 0U) ? 1U : 0U; if ((uiKeyUp == 1U) && (uiKeyDn == 1U)) { GeneralOperValue.Maintenance.KeyTest = 0U; OledOperValue.uiPageNum = (Uint16)IDX_OLED_PAGE_MAINTENANCE; } } } void CLedControlProcedure(void) { static const CLedPattern APU_LED_TABLE[] = // LED 룩업 테이블 { // FAULT, OPER, STOP {LED_OFF, LED_OFF, LED_ON }, // 0: BOOT {LED_OFF, LED_OFF, LED_ON }, // 1: INITIAL {LED_OFF, LED_OFF, LED_ON }, // 2: POST {LED_ON, LED_OFF, LED_ON }, // 3: EMERGENCY {LED_OFF, LED_OFF, LED_ON }, // 4: STANDBY // --- OPER 깜빡임 구간 (준비~예열) --- {LED_OFF, LED_BLINK, LED_OFF }, // 5: READY {LED_OFF, LED_BLINK, LED_OFF }, // 6: PREPARE_START {LED_OFF, LED_BLINK, LED_OFF }, // 7: CRANKING {LED_OFF, LED_BLINK, LED_OFF }, // 8: RETRY_CRANKING {LED_OFF, LED_BLINK, LED_OFF }, // 9: ENGINE_WARM_UP {LED_OFF, LED_ON, LED_OFF }, // 10: GENERATING (정상 운전) // --- STOP 깜빡임 구간 (APU 정지 시) --- {LED_OFF, LED_ON, LED_BLINK }, // 11: COOLDOWN (STOP 깜빡임, OPER는 켜둠) {LED_OFF, LED_ON, LED_BLINK } // 12: STOPPING (COOLDWON이 순식간에 지나가기 때문에 점멸로 수정) }; CLedPattern TargetLeds = {0, 0, 0}; Uint64 SoftClock = CGetSoftClock(); Uint16 IsBlinkOn = ((SoftClock % 10000U) < 5000U) ? 1U : 0U; // 0.5s 점멸을 위함 Uint16 WarningValue = 0U; TargetLeds = APU_LED_TABLE[GeneralOperValue.uiApuState]; // 발전상태에서 경고 발생시 FAULT LED 깜빡이도록 설정 if (GeneralOperValue.uiApuState == (Uint16)IDX_APU_OPER_GENERATING) { WarningValue = ((((Uint16)Rx210.GcuWarning & (Uint16)MASK_LOW_NIBBLE) | ((Uint16)Rx310.EcuWarning & 0xFDU)) > 0U) ? 1U : 0U; } // 비상정지 상태가 아닌 경우에만 경고비트에 점멸 대응 if ((GeneralOperValue.uiApuState != (Uint16)IDX_APU_OPER_EMERGENCY) && (WarningValue == 1U)) { TargetLeds.Fault = (Uint16)LED_BLINK; } // FAULT LED 제어 if (TargetLeds.Fault == (Uint16)LED_BLINK) { CLedControl(0U, IsBlinkOn); } else { CLedControl(0U, TargetLeds.Fault); } // OPERATION LED 제어 if (TargetLeds.Operation == (Uint16)LED_BLINK) { CLedControl(1U, IsBlinkOn); } else { CLedControl(1U, TargetLeds.Operation); } // STOP LED 제어 if (TargetLeds.Stop == (Uint16)LED_BLINK) { CLedControl(2U, IsBlinkOn); } else { CLedControl(2U, TargetLeds.Stop); } } static void CLedControl(Uint16 idx, Uint16 state) { /* * idx * 0 : FAULT LED * 1 : OPER LED * 2 : STOP LED */ if (idx == 0U) { // GPIO_CPU_LED_FAULT if (state == 0U) { GpioDataRegs.GPACLEAR.bit.GPIO14 = 1U; } else { GpioDataRegs.GPASET.bit.GPIO14 = 1U; } } else if (idx == 1U) { // GPIO_CPU_LED_OPERATION if (state == 0U) { GpioDataRegs.GPACLEAR.bit.GPIO13 = 1U; } else { GpioDataRegs.GPASET.bit.GPIO13 = 1U; } } else { // GPIO_CPU_LED_STOP if (state == 0U) { GpioDataRegs.GPACLEAR.bit.GPIO12 = 1U; } else { GpioDataRegs.GPASET.bit.GPIO12 = 1U; } } }