Code:
-- TCU Lua Script: WOT Downshift RPM Curve, Renamed WOT Upshift Curve, Raw VSS, Spike Filter, Sequential Shifts, 5 Gauges-- CONVERTED FOR SINGLE-UNIT OPERATION (NO CAN)-- =============================================================================-- CONFIGURATION & STATE-- =============================================================================-- Configuration Flagslocal enableGearErrorCheck = falselocal enableVssFiltering = true -- Core State Variableslocal intendedGear = 3local initialDataReceived = falselocal lastSetVssForLogic = 0.0local idleStateTriggeredShiftToFirst = false-- PWM Configurationlocal SOLENOID_A_PWM_INDEX = 0local SOLENOID_B_PWM_INDEX = 1local PWM_FREQUENCY = 10-- Solenoid State Stringslocal solenoidAStateString = "OFF"local solenoidBStateString = "OFF"-- Shift Process Timers and Flagslocal shiftInProgressTimer = Timer.new()local isShifting = false local gearMatchTimer = Timer.new()local isCheckingGearMatch = falselocal shiftErrorActive = false-- User-Configurable Settingslocal wotTpsThresholdSettinglocal shiftExecutionTimeSecondsSettinglocal downshiftLockoutRpmSettinglocal gearMatchTimeoutSecondsSettinglocal numberOfGearSolenoidsSettinglocal idleRpmThresholdSettinglocal idleTpsThresholdSettinglocal effectivelyStoppedRawVssThreshold = 50local vssSpikeDeltaThresholdSettinglocal maxRealisticRawVssSetting-- Table and Curve Indiceslocal upshiftScheduleTableIndexlocal downshiftScheduleTableIndexlocal wotUpshiftRpmCurveIndexlocal wotDownshiftRpmCurveIndexlocal solenoidPatternCurveIndex-- =============================================================================-- HELPER & LOGIC FUNCTIONS-- =============================================================================-- Sets PWM duty cycles for solenoids based on a target gear and pattern curve.function setSolenoidsForGear(targetGear) if solenoidPatternCurveIndex == nil then print("ERROR: Solenoid pattern curve index is nil.") if numberOfGearSolenoidsSetting >= 1 then setPwmDuty(SOLENOID_A_PWM_INDEX, 0.0); solenoidAStateString = "OFF" end if numberOfGearSolenoidsSetting >= 2 then setPwmDuty(SOLENOID_B_PWM_INDEX, 0.0); solenoidBStateString = "OFF" end return end local patternCode = curve(solenoidPatternCurveIndex, targetGear) if patternCode == nil then print("WARNING: No solenoid pattern for gear " .. targetGear) patternCode = 0 end patternCode = math.floor(patternCode) -- Solenoid A if numberOfGearSolenoidsSetting >= 1 then local solenoidADutyCycle = 0.0 if numberOfGearSolenoidsSetting == 1 then if patternCode == 1 then solenoidADutyCycle = 1.0 end elseif numberOfGearSolenoidsSetting == 2 then if math.floor(patternCode / 10) == 1 then solenoidADutyCycle = 1.0 end elseif numberOfGearSolenoidsSetting == 3 then if math.floor(patternCode / 100) == 1 then solenoidADutyCycle = 1.0 end end setPwmDuty(SOLENOID_A_PWM_INDEX, solenoidADutyCycle) solenoidAStateString = (solenoidADutyCycle == 1.0) and "ON" or "OFF" else solenoidAStateString = "N/A" end -- Solenoid B if numberOfGearSolenoidsSetting >= 2 then local solenoidBDutyCycle = 0.0 if numberOfGearSolenoidsSetting == 2 then if patternCode % 10 == 1 then solenoidBDutyCycle = 1.0 end elseif numberOfGearSolenoidsSetting == 3 then if math.floor((patternCode % 100) / 10) == 1 then solenoidBDutyCycle = 1.0 end end setPwmDuty(SOLENOID_B_PWM_INDEX, solenoidBDutyCycle) solenoidBStateString = (solenoidBDutyCycle == 1.0) and "ON" or "OFF" else solenoidBStateString = "N/A" endend-- Applies spike and sanity filtering to the raw VSS sensor reading.function getFilteredVss(rawVss) local useLastVss = false if rawVss == nil then useLastVss = true elseif enableVssFiltering and initialDataReceived then -- Reject unrealistic values or sudden spikes if rawVss > maxRealisticRawVssSetting or math.abs(rawVss - lastSetVssForLogic) > vssSpikeDeltaThresholdSetting then useLastVss = true end end if useLastVss then return lastSetVssForLogic else return rawVss endend-- Determines the single desired gear based on current conditions.function determineDesiredGear(rpm, tps, vss, currentGear, actualGear) idleStateTriggeredShiftToFirst = false -- 1. Idle State Check: Force to 1st gear when stopped at idle in Neutral if rpm < idleRpmThresholdSetting and tps < idleTpsThresholdSetting and vss < effectivelyStoppedRawVssThreshold then if actualGear ~= nil and actualGear == 0 then idleStateTriggeredShiftToFirst = true return 1 end end local newPotentialDesiredGear = currentGear local isWOT = (tps >= wotTpsThresholdSetting) -- 2. WOT Upshift Logic if isWOT and currentGear < 4 then local wotTargetRpm = curve(wotUpshiftRpmCurveIndex, currentGear) if wotTargetRpm ~= nil and rpm >= wotTargetRpm then newPotentialDesiredGear = currentGear + 1 end end -- 3. WOT Downshift Logic if newPotentialDesiredGear == currentGear and isWOT and currentGear > 1 then local wotTargetRpmForDownshift = curve(wotDownshiftRpmCurveIndex, currentGear) if wotTargetRpmForDownshift ~= nil and rpm < wotTargetRpmForDownshift and rpm <= downshiftLockoutRpmSetting then newPotentialDesiredGear = currentGear - 1 end end -- 4. Table-Based Downshift Logic if newPotentialDesiredGear == currentGear and currentGear > 1 then local targetVss = table3d(downshiftScheduleTableIndex, tps, currentGear) if targetVss ~= nil and vss <= targetVss and rpm <= downshiftLockoutRpmSetting then newPotentialDesiredGear = currentGear - 1 end end -- 5. Table-Based Upshift Logic if newPotentialDesiredGear == currentGear and currentGear < 4 then local vssForLookup = vss -- Prevent hunting at a stop by forcing VSS lookup to zero in certain conditions if rpm < 1200 and vss < effectivelyStoppedRawVssThreshold then vssForLookup = 0.0 end local targetVss = table3d(upshiftScheduleTableIndex, tps, currentGear) if targetVss ~= nil and vssForLookup >= targetVss then newPotentialDesiredGear = currentGear + 1 end end if newPotentialDesiredGear >= 1 and newPotentialDesiredGear <= 4 then return newPotentialDesiredGear end return currentGearend-- Executes a shift to the new gear and manages state changes.function executeShift(newGear) intendedGear = newGear setSolenoidsForGear(intendedGear) isShifting = true shiftInProgressTimer:reset() isCheckingGearMatch = true gearMatchTimer:reset() shiftErrorActive = falseend-- Manages timers that determine the duration of the shifting state.function updateShiftTimers() if isShifting and shiftInProgressTimer:getElapsedSeconds() >= shiftExecutionTimeSecondsSetting then isShifting = false endend-- Manages gear match error checking state.function updateGearErrorStatus(actualGear) if not isCheckingGearMatch then return end if enableGearErrorCheck then if actualGear ~= nil and intendedGear == actualGear then shiftErrorActive = false isCheckingGearMatch = false elseif gearMatchTimer:getElapsedSeconds() >= gearMatchTimeoutSecondsSetting then shiftErrorActive = true isCheckingGearMatch = false print("ERROR: Gear match timeout! Intended: " .. intendedGear .. ", Actual: " .. (actualGear or "nil")) end else -- If check is disabled, immediately stop checking. isCheckingGearMatch = false shiftErrorActive = false endend-- Updates all Lua gauges for debugging and display.function updateGauges(currentTps, currentVss) setLuaGauge(1, intendedGear) local solenoidComboValue = 0 if solenoidAStateString == "ON" and solenoidBStateString == "ON" then solenoidComboValue = 11 elseif solenoidAStateString == "ON" and solenoidBStateString == "OFF" then solenoidComboValue = 10 elseif solenoidAStateString == "OFF" and solenoidBStateString == "ON" then solenoidComboValue = 1 end setLuaGauge(2, solenoidComboValue) setLuaGauge(3, idleStateTriggeredShiftToFirst and 1 or 0) local placeholderValue = 999 local upshiftDistance = placeholderValue if currentTps ~= nil and intendedGear < 4 and upshiftScheduleTableIndex ~= nil then local targetVss = table3d(upshiftScheduleTableIndex, currentTps, intendedGear) if targetVss ~= nil then upshiftDistance = targetVss - currentVss end end setLuaGauge(4, upshiftDistance) local downshiftDistance = placeholderValue if currentTps ~= nil and intendedGear > 1 and downshiftScheduleTableIndex ~= nil then local targetVss = table3d(downshiftScheduleTableIndex, currentTps, intendedGear) if targetVss ~= nil then downshiftDistance = currentVss - targetVss end end setLuaGauge(5, downshiftDistance)end-- =============================================================================-- INITIALIZATION-- =============================================================================function initializeShiftLogic() -- Load settings from the configuration wotTpsThresholdSetting = findSetting("wotTps", 90.0) local shiftExecutionTimeMsSetting = findSetting("shiftTime", 500) shiftExecutionTimeSecondsSetting = shiftExecutionTimeMsSetting / 1000.0 downshiftLockoutRpmSetting = findSetting("dsLockRpm", 7000) local gearMatchTimeoutMsSetting = findSetting("gearMatch", 3000) gearMatchTimeoutSecondsSetting = gearMatchTimeoutMsSetting / 1000.0 numberOfGearSolenoidsSetting = findSetting("numGearSol", 2) idleRpmThresholdSetting = 1300 idleTpsThresholdSetting = 1.0 vssSpikeDeltaThresholdSetting = 200 maxRealisticRawVssSetting = 2400 -- Find required tables and curves upshiftScheduleTableIndex = findTableIndex("upSched") downshiftScheduleTableIndex = findTableIndex("downSched") wotUpshiftRpmCurveIndex = findCurveIndex("wotUpRpm") wotDownshiftRpmCurveIndex = findCurveIndex("wotDsRpm") solenoidPatternCurveIndex = findCurveIndex("solPattern") if upshiftScheduleTableIndex == nil or downshiftScheduleTableIndex == nil or wotUpshiftRpmCurveIndex == nil or wotDownshiftRpmCurveIndex == nil or solenoidPatternCurveIndex == nil then print("ERROR: One or more essential tables/curves not found.") end -- Initialize PWM outputs if numberOfGearSolenoidsSetting >= 1 then startPwm(SOLENOID_A_PWM_INDEX, PWM_FREQUENCY, 0.0) end if numberOfGearSolenoidsSetting >= 2 then startPwm(SOLENOID_B_PWM_INDEX, PWM_FREQUENCY, 0.0) end -- Set initial gear setSolenoidsForGear(intendedGear)end-- Run initialization once on script loadinitializeShiftLogic()-- =============================================================================-- MAIN LOOP (onTick)-- =============================================================================function onTick() -- 1. GATHER SENSOR INPUTS local currentRpm = getSensor("Rpm") local currentTps = getSensor("Tps1") local rawVss = getSensor("VehicleSpeed") local actualGear = getSensor("DetectedGear") -- 2. GUARD CLAUSE: Ensure essential data is available before running logic if currentRpm == nil or currentTps == nil or rawVss == nil then return end -- 3. INITIALIZATION ON FIRST VALID DATA if not initialDataReceived then initialDataReceived = true lastSetVssForLogic = rawVss -- Initialize VSS filter baseline end -- 4. PROCESS INPUTS AND UPDATE STATE updateShiftTimers() local currentVssForLogic = getFilteredVss(rawVss) lastSetVssForLogic = currentVssForLogic -- Persist the VSS value for the next cycle's filter -- 5. CORE SHIFT DECISION LOGIC if not isShifting then local desiredGear = determineDesiredGear(currentRpm, currentTps, currentVssForLogic, intendedGear, actualGear) if desiredGear ~= intendedGear then executeShift(desiredGear) end end -- 6. POST-SHIFT AND DISPLAY UPDATES updateGearErrorStatus(actualGear) updateGauges(currentTps, currentVssForLogic)endsetTickRate(100)
Statistics: Posted by NormanAlphaspeed — Thu Sep 11, 2025 8:04 am
Statistics: Posted by AndreyB — Wed Sep 10, 2025 7:14 pm
Statistics: Posted by AndreyB — Wed Sep 10, 2025 5:51 pm
Code:
-- TCU Lua Script: WOT Downshift RPM Curve, Renamed WOT Upshift Curve, Raw VSS, Spike Filter, Sequential Shifts, 5 Gauges, CAN TX-- Configuration Flagslocal enableGearErrorCheck = falselocal enableVssFiltering = true -- Sensor Declarationslocal coolantTempSensor = Sensor.new("Clt")local engineRpmSensor = Sensor.new("Rpm")local manifoldPressureSensor = Sensor.new("Map")local throttlePosSensor = Sensor.new("Tps1")local vehicleSpeedSensor = Sensor.new("VehicleSpeed")local detectedGearSensor = Sensor.new("DetectedGear")-- Sensor Timeoutslocal SENSOR_TIMEOUT_MILLISECONDS = 3000coolantTempSensor:setTimeout(SENSOR_TIMEOUT_MILLISECONDS)engineRpmSensor:setTimeout(SENSOR_TIMEOUT_MILLISECONDS)manifoldPressureSensor:setTimeout(SENSOR_TIMEOUT_MILLISECONDS)throttlePosSensor:setTimeout(SENSOR_TIMEOUT_MILLISECONDS)vehicleSpeedSensor:setTimeout(SENSOR_TIMEOUT_MILLISECONDS)detectedGearSensor:setTimeout(SENSOR_TIMEOUT_MILLISECONDS)-- Core State Variableslocal intendedGear = 3local initialDataReceived = falselocal lastKnownRawVssFromCan = nillocal lastSetVssForLogic = 0.0local idleStateTriggeredShiftToFirst = false-- PWM Configurationlocal SOLENOID_A_PWM_INDEX = 0local SOLENOID_B_PWM_INDEX = 1local PWM_FREQUENCY = 10-- Solenoid State Stringslocal solenoidAStateString = "OFF"local solenoidBStateString = "OFF"-- Shift Process Timers and Flagslocal shiftInProgressTimer = Timer.new()local isShifting = false local gearMatchTimer = Timer.new()local isCheckingGearMatch = falselocal shiftErrorActive = false-- User-Configurable Settingslocal wotTpsThresholdSettinglocal shiftExecutionTimeSecondsSettinglocal downshiftLockoutRpmSettinglocal gearMatchTimeoutSecondsSettinglocal numberOfGearSolenoidsSettinglocal idleRpmThresholdSettinglocal idleTpsThresholdSettinglocal effectivelyStoppedRawVssThreshold = 50local vssSpikeDeltaThresholdSettinglocal maxRealisticRawVssSetting-- Table and Curve Indiceslocal upshiftScheduleTableIndexlocal downshiftScheduleTableIndexlocal wotUpshiftRpmCurveIndex -- RENAMEDlocal wotDownshiftRpmCurveIndex -- NEWlocal solenoidPatternCurveIndex-- CAN Transmit Configurationlocal SHIFT_STATUS_CAN_ID = 0x701 local CAN_BUS_INDEX = 1 function getTwoBytesUnsignedLsb(data, startIndex) local msb = data[startIndex] local lsb = data[startIndex + 1] if msb == nil or lsb == nil then return nil end return (msb * 256) + lsbendfunction getSignedByte(rawValue) if rawValue > 127 then return rawValue - 256 else return rawValue endendfunction setSolenoidsForGear(targetGear) if solenoidPatternCurveIndex == nil then print("ERROR: Solenoid pattern curve index is nil.") if numberOfGearSolenoidsSetting >= 1 then setPwmDuty(SOLENOID_A_PWM_INDEX, 0.0); solenoidAStateString = "OFF" end if numberOfGearSolenoidsSetting >= 2 then setPwmDuty(SOLENOID_B_PWM_INDEX, 0.0); solenoidBStateString = "OFF" end return end local patternCode = curve(solenoidPatternCurveIndex, targetGear) if patternCode == nil then print("WARNING: No solenoid pattern for gear " .. targetGear) patternCode = 0 end patternCode = math.floor(patternCode) local tempPattern = patternCode if numberOfGearSolenoidsSetting >= 1 then local solenoidADutyCycle = 0.0 if numberOfGearSolenoidsSetting == 1 then if tempPattern == 1 then solenoidADutyCycle = 1.0 end elseif numberOfGearSolenoidsSetting == 2 then if math.floor(tempPattern / 10) == 1 then solenoidADutyCycle = 1.0 end elseif numberOfGearSolenoidsSetting == 3 then if math.floor(tempPattern / 100) == 1 then solenoidADutyCycle = 1.0 end end setPwmDuty(SOLENOID_A_PWM_INDEX, solenoidADutyCycle) solenoidAStateString = (solenoidADutyCycle == 1.0) and "ON" or "OFF" else solenoidAStateString = "N/A" end if numberOfGearSolenoidsSetting >= 2 then local solenoidBDutyCycle = 0.0 if numberOfGearSolenoidsSetting == 2 then if tempPattern % 10 == 1 then solenoidBDutyCycle = 1.0 end elseif numberOfGearSolenoidsSetting == 3 then if math.floor((tempPattern % 100) / 10) == 1 then solenoidBDutyCycle = 1.0 end end setPwmDuty(SOLENOID_B_PWM_INDEX, solenoidBDutyCycle) solenoidBStateString = (solenoidBDutyCycle == 1.0) and "ON" or "OFF" else solenoidBStateString = "N/A" endendfunction initializeShiftLogic() wotTpsThresholdSetting = findSetting("wotTps", 90.0) local shiftExecutionTimeMsSetting = findSetting("shiftTime", 500) shiftExecutionTimeSecondsSetting = shiftExecutionTimeMsSetting / 1000.0 downshiftLockoutRpmSetting = findSetting("dsLockRpm", 7000) local gearMatchTimeoutMsSetting = findSetting("gearMatch", 3000) gearMatchTimeoutSecondsSetting = gearMatchTimeoutMsSetting / 1000.0 numberOfGearSolenoidsSetting = findSetting("numGearSol", 2) idleRpmThresholdSetting = 1300 idleTpsThresholdSetting = 1.0 vssSpikeDeltaThresholdSetting = 200 maxRealisticRawVssSetting = 2400 upshiftScheduleTableIndex = findTableIndex("upSched") downshiftScheduleTableIndex = findTableIndex("downSched") wotUpshiftRpmCurveIndex = findCurveIndex("wotUpRpm") -- Use new key "wotUpRpm" wotDownshiftRpmCurveIndex = findCurveIndex("wotDsRpm") -- NEW: Use key "wotDsRpm" solenoidPatternCurveIndex = findCurveIndex("solPattern") if upshiftScheduleTableIndex == nil or downshiftScheduleTableIndex == nil or wotUpshiftRpmCurveIndex == nil or wotDownshiftRpmCurveIndex == nil or solenoidPatternCurveIndex == nil then print("ERROR: One or more essential tables/curves not found.") end if numberOfGearSolenoidsSetting >= 1 then startPwm(SOLENOID_A_PWM_INDEX, PWM_FREQUENCY, 0.0) end if numberOfGearSolenoidsSetting >= 2 then startPwm(SOLENOID_B_PWM_INDEX, PWM_FREQUENCY, 0.0) end setSolenoidsForGear(intendedGear)endinitializeShiftLogic()function onCanRx(bus, id, dlc, data) if id == 0x360 then engineRpmSensor:set(getTwoBytesUnsignedLsb(data, 1)) manifoldPressureSensor:set(getTwoBytesUnsignedLsb(data, 3) / 10.0) local tpsCanVal = getTwoBytesUnsignedLsb(data, 5) if tpsCanVal ~= nil then throttlePosSensor:set(tpsCanVal / 10.0) end elseif id == 0x3E0 then coolantTempSensor:set(getTwoBytesUnsignedLsb(data, 1) / 100.0) elseif id == 0x470 then local rawGearValue = data[8] if rawGearValue ~= nil then detectedGearSensor:set(getSignedByte(rawGearValue)) end elseif id == 0x370 then idleStateTriggeredShiftToFirst = false local vssFromCan = getTwoBytesUnsignedLsb(data, 1) lastKnownRawVssFromCan = vssFromCan local currentVssForLogic local useLastVss = false if vssFromCan == nil then useLastVss = true elseif enableVssFiltering and initialDataReceived then if vssFromCan > maxRealisticRawVssSetting then useLastVss = true elseif math.abs(vssFromCan - lastSetVssForLogic) > vssSpikeDeltaThresholdSetting then useLastVss = true end end if useLastVss then currentVssForLogic = lastSetVssForLogic else currentVssForLogic = vssFromCan end if currentVssForLogic == nil then currentVssForLogic = 0.0 end if not initialDataReceived then if getSensor("Rpm") ~= nil and getSensor("Tps1") ~= nil and vssFromCan ~= nil then initialDataReceived = true if enableVssFiltering then lastSetVssForLogic = currentVssForLogic end end end local currentRpm = getSensor("Rpm") local currentTps = getSensor("Tps1") local triggerShiftToFirstForIdle = false if currentRpm ~= nil and currentTps ~= nil then if currentRpm < idleRpmThresholdSetting and currentTps < idleTpsThresholdSetting then local actualGear = getSensor("DetectedGear") if actualGear ~= nil and actualGear == 0 then if currentVssForLogic < effectivelyStoppedRawVssThreshold then triggerShiftToFirstForIdle = true idleStateTriggeredShiftToFirst = true end end end end vehicleSpeedSensor:set(currentVssForLogic) lastSetVssForLogic = currentVssForLogic if isShifting then if shiftInProgressTimer:getElapsedSeconds() >= shiftExecutionTimeSecondsSetting then isShifting = false end end if triggerShiftToFirstForIdle and intendedGear ~= 1 and not isShifting then intendedGear = 1 setSolenoidsForGear(intendedGear) isShifting = true; shiftInProgressTimer:reset() isCheckingGearMatch = true; gearMatchTimer:reset() shiftErrorActive = false end if not isShifting and initialDataReceived and upshiftScheduleTableIndex ~= nil and downshiftScheduleTableIndex ~= nil and wotUpshiftRpmCurveIndex ~= nil and wotDownshiftRpmCurveIndex ~= nil and -- Added new curve check solenoidPatternCurveIndex ~= nil then currentRpm = getSensor("Rpm") currentTps = getSensor("Tps1") if currentRpm == nil or currentTps == nil then -- Skip else local isWOT = (currentTps >= wotTpsThresholdSetting) local newPotentialDesiredGear = intendedGear -- 1. WOT Upshift Logic if isWOT and intendedGear < 4 then local wotTargetRpm = curve(wotUpshiftRpmCurveIndex, intendedGear) if wotTargetRpm ~= nil and currentRpm >= wotTargetRpm then newPotentialDesiredGear = intendedGear + 1 end end -- 2. WOT Downshift Logic if newPotentialDesiredGear == intendedGear and isWOT and intendedGear > 1 then if wotDownshiftRpmCurveIndex ~= nil then local wotTargetRpmForDownshift = curve(wotDownshiftRpmCurveIndex, intendedGear) if wotTargetRpmForDownshift ~= nil and currentRpm < wotTargetRpmForDownshift then if currentRpm <= downshiftLockoutRpmSetting then newPotentialDesiredGear = intendedGear - 1 end end end end -- 3. Table-Based Downshift Logic if newPotentialDesiredGear == intendedGear and intendedGear > 1 then local yAxisForTable = intendedGear local targetVssForDownshift = table3d(downshiftScheduleTableIndex, currentTps, yAxisForTable) if targetVssForDownshift ~= nil and currentVssForLogic <= targetVssForDownshift and currentRpm <= downshiftLockoutRpmSetting then newPotentialDesiredGear = intendedGear - 1 end end -- 4. Table-Based Upshift Logic if newPotentialDesiredGear == intendedGear and intendedGear < 4 then local yAxisForTable = intendedGear local vssForTableLookup = currentVssForLogic if currentRpm < 1200 and vssForTableLookup < effectivelyStoppedRawVssThreshold then vssForTableLookup = 0.0 end local targetVssForUpshift = table3d(upshiftScheduleTableIndex, currentTps, yAxisForTable) if targetVssForUpshift ~= nil and vssForTableLookup >= targetVssForUpshift then newPotentialDesiredGear = intendedGear + 1 end end -- Shift Execution if newPotentialDesiredGear ~= intendedGear and newPotentialDesiredGear >= 1 and newPotentialDesiredGear <= 4 then intendedGear = newPotentialDesiredGear setSolenoidsForGear(intendedGear) isShifting = true; shiftInProgressTimer:reset() isCheckingGearMatch = true; gearMatchTimer:reset() shiftErrorActive = false end end end endendcanRxAdd(0x360); canRxAdd(0x3E0); canRxAdd(0x370); canRxAdd(0x470)function onTick() if enableGearErrorCheck and isCheckingGearMatch then local actualGearValue = getSensor("DetectedGear") if actualGearValue ~= nil and intendedGear == actualGearValue then shiftErrorActive = false isCheckingGearMatch = false elseif gearMatchTimer:getElapsedSeconds() >= gearMatchTimeoutSecondsSetting then shiftErrorActive = true isCheckingGearMatch = false print("ERROR: Gear match timeout! Intended: " .. intendedGear .. ", Actual: " .. (actualGearValue or "nil")) end elseif not enableGearErrorCheck and isCheckingGearMatch then isCheckingGearMatch = false shiftErrorActive = false end setLuaGauge(1, intendedGear) local solenoidComboValue = 0 if solenoidAStateString == "ON" and solenoidBStateString == "ON" then solenoidComboValue = 11 elseif solenoidAStateString == "ON" and solenoidBStateString == "OFF" then solenoidComboValue = 10 elseif solenoidAStateString == "OFF" and solenoidBStateString == "ON" then solenoidComboValue = 1 end setLuaGauge(2, solenoidComboValue) setLuaGauge(3, idleStateTriggeredShiftToFirst and 1 or 0) local currentTpsForGauges = getSensor("Tps1") local currentSpeedForGauges = lastSetVssForLogic local placeholderValue = 999 local upshiftDistance = placeholderValue if currentTpsForGauges ~= nil and intendedGear < 4 and upshiftScheduleTableIndex ~= nil then local targetVss = table3d(upshiftScheduleTableIndex, currentTpsForGauges, intendedGear) if targetVss ~= nil then upshiftDistance = targetVss - currentSpeedForGauges end end setLuaGauge(4, upshiftDistance) local downshiftDistance = placeholderValue if currentTpsForGauges ~= nil and intendedGear > 1 and downshiftScheduleTableIndex ~= nil then local targetVss = table3d(downshiftScheduleTableIndex, currentTpsForGauges, intendedGear) if targetVss ~= nil then downshiftDistance = currentSpeedForGauges - targetVss end end setLuaGauge(5, downshiftDistance) local shiftingStatusPayload = {} if isShifting then shiftingStatusPayload[1] = 1 else shiftingStatusPayload[1] = 0 end txCan(CAN_BUS_INDEX, SHIFT_STATUS_CAN_ID, 0, shiftingStatusPayload)endsetTickRate(100)
Statistics: Posted by NormanAlphaspeed — Mon Jun 02, 2025 1:32 am
Statistics: Posted by NormanAlphaspeed — Fri May 23, 2025 8:10 am
Code:
-- TCU Lua Script: Normal var names, Corrected VSS logic, Toggleable filters-- Configuration Flags (Manually change for testing)local enableVssSpikeFilter = false -- Set to false to disable VSS spike filteringlocal enableGearErrorCheck = false -- Set to false to disable gear match error detection-- Sensor Declarationslocal cltSensor = Sensor.new("Clt")local rpmSensor = Sensor.new("Rpm")local mapSensor = Sensor.new("Map")local tps1Sensor = Sensor.new("Tps1")local vehicleSpeedSensor = Sensor.new("VehicleSpeed")local inputShaftSpeedSensor = Sensor.new("InputShaftSpeed")local detectedGearSensor = Sensor.new("DetectedGear")-- Sensor Timeoutslocal SENSOR_TIMEOUT_MS = 3000cltSensor:setTimeout(SENSOR_TIMEOUT_MS)rpmSensor:setTimeout(SENSOR_TIMEOUT_MS)mapSensor:setTimeout(SENSOR_TIMEOUT_MS)tps1Sensor:setTimeout(SENSOR_TIMEOUT_MS)vehicleSpeedSensor:setTimeout(SENSOR_TIMEOUT_MS)inputShaftSpeedSensor:setTimeout(SENSOR_TIMEOUT_MS)detectedGearSensor:setTimeout(SENSOR_TIMEOUT_MS)-- Core State Variableslocal IntendedGear = 3local initialDataReceived = falselocal lastKnownRawVss = nil -- Raw VSS after idle override, before scaling (used by spike filter & print)local lastSetVehicleSpeed = 0.0 -- Scaled VSS value that was last set to the firmware sensorlocal idleStateForcedInLastCanCycle = false-- PWM Configurationlocal SOLENOID_A_PWM_INDEX = 0local SOLENOID_B_PWM_INDEX = 1local PWM_FREQUENCY = 10-- Solenoid State Strings for Printinglocal solenoid_A_state_str = "OFF"local solenoid_B_state_str = "OFF" -- Will be correctly set during init by setSolenoidsForGear-- Shift Process Timers and Flagslocal shift_in_progress_timer = Timer.new()local is_shifting = falselocal gearMatchTimer = Timer.new()local is_checking_gear_match = falselocal shiftErrorActive = falselocal printStatusTimer = Timer.new()-- User-Configurable Settings (loaded in init, using normal length Lua variable names)local wotTpsThresholdSettinglocal shiftExecutionTimeSecondsSettinglocal downshiftLockoutRpmSettinglocal gearMatchTimeoutSecondsSettinglocal numberOfGearSolenoidsSetting-- Table and Curve Indices (loaded in init)local upshiftScheduleTableIndexlocal downshiftScheduleTableIndexlocal wotRpmCurveIndexlocal solenoidPatternCurveIndex-- Helper: Get 16-bit unsigned value from CAN data (Big Endian based on your usage)function getTwoBytesUnsignedLsb(data, startIndex) local msb = data[startIndex] local lsb = data[startIndex + 1] if msb == nil or lsb == nil then return 0 end return lsb + (msb * 256)end-- Helper: Get signed bytefunction getSignedByte(rawValue) if rawValue > 127 then return rawValue - 256 else return rawValue endend-- Helper: Command solenoids based on pattern from "solPattern" curvefunction setSolenoidsForGear(targetGear) if solenoidPatternCurveIndex == nil then print("ERR: solPattern Idx nil") if numberOfGearSolenoidsSetting >= 1 then setPwmDuty(SOLENOID_A_PWM_INDEX, 0.0); solenoid_A_state_str = "OFF" end if numberOfGearSolenoidsSetting >= 2 then setPwmDuty(SOLENOID_B_PWM_INDEX, 0.0); solenoid_B_state_str = "OFF" end return end local patternCode = curve(solenoidPatternCurveIndex, targetGear) if patternCode == nil then print("WARN: No solPattern for gear " .. targetGear) patternCode = 0 end patternCode = math.floor(patternCode) local tempPattern = patternCode -- Solenoid A if numberOfGearSolenoidsSetting >= 1 then local solA_duty = 0.0 if numberOfGearSolenoidsSetting == 1 then if tempPattern == 1 then solA_duty = 1.0 end elseif numberOfGearSolenoidsSetting == 2 then if math.floor(tempPattern / 10) == 1 then solA_duty = 1.0 end elseif numberOfGearSolenoidsSetting == 3 then if math.floor(tempPattern / 100) == 1 then solA_duty = 1.0 end end setPwmDuty(SOLENOID_A_PWM_INDEX, solA_duty) solenoid_A_state_str = (solA_duty == 1.0) and "ON" or "OFF" else solenoid_A_state_str = "N/A" end -- Solenoid B if numberOfGearSolenoidsSetting >= 2 then local solB_duty = 0.0 if numberOfGearSolenoidsSetting == 2 then if tempPattern % 10 == 1 then solB_duty = 1.0 end elseif numberOfGearSolenoidsSetting == 3 then if math.floor((tempPattern % 100) / 10) == 1 then solB_duty = 1.0 end end setPwmDuty(SOLENOID_B_PWM_INDEX, solB_duty) solenoid_B_state_str = (solB_duty == 1.0) and "ON" or "OFF" else solenoid_B_state_str = "N/A" endend-- Initialization Functionfunction initializeShiftLogic() -- Load user settings using shortened camelCase names for findSetting wotTpsThresholdSetting = findSetting("wotTps", 90.0) local shiftExecutionTimeMsSetting = findSetting("shiftTime", 500) shiftExecutionTimeSecondsSetting = shiftExecutionTimeMsSetting / 1000.0 downshiftLockoutRpmSetting = findSetting("dsLockRpm", 7000) local gearMatchTimeoutMsSetting = findSetting("gearMatch", 3000) gearMatchTimeoutSecondsSetting = gearMatchTimeoutMsSetting / 1000.0 numberOfGearSolenoidsSetting = findSetting("numGearSol", 2) -- Load table and curve indices using shortened camelCase names upshiftScheduleTableIndex = findTableIndex("upSched") downshiftScheduleTableIndex = findTableIndex("downSched") wotRpmCurveIndex = findCurveIndex("wotRpm") solenoidPatternCurveIndex = findCurveIndex("solPattern") if upshiftScheduleTableIndex == nil or downshiftScheduleTableIndex == nil or wotRpmCurveIndex == nil or solenoidPatternCurveIndex == nil then print("ERR: Table/Curve nil") end if numberOfGearSolenoidsSetting >= 1 then startPwm(SOLENOID_A_PWM_INDEX, PWM_FREQUENCY, 0.0) end if numberOfGearSolenoidsSetting >= 2 then startPwm(SOLENOID_B_PWM_INDEX, PWM_FREQUENCY, 0.0) end setSolenoidsForGear(IntendedGear) -- Set solenoids to initial IntendedGear (3rd) printStatusTimer:reset()endinitializeShiftLogic()-- onCanRx: Callback for received CAN messagesfunction onCanRx(bus, id, dlc, data) if id == 0x360 then -- RPM, MAP, TPS rpmSensor:set(getTwoBytesUnsignedLsb(data, 1)) mapSensor:set(getTwoBytesUnsignedLsb(data, 3) / 10.0) tps1Sensor:set(getTwoBytesUnsignedLsb(data, 5) / 10.0) elseif id == 0x3E0 then -- Coolant Temp cltSensor:set(getTwoBytesUnsignedLsb(data, 1) / 100.0) elseif id == 0x470 then -- ECU Transmitted Gear Data local rawGearValue = data[8] if rawGearValue ~= nil then local actualGear = getSignedByte(rawGearValue) detectedGearSensor:set(actualGear) end elseif id == 0x370 then -- Vehicle Speed, and main shift logic trigger idleStateForcedInLastCanCycle = false -- Reset per 0x370 cycle local newRawVssFromCan = getTwoBytesUnsignedLsb(data, 1) local processedRawVss = newRawVssFromCan -- This will hold raw VSS after idle forcing local currentFinalVehicleSpeed -- This will hold scaled VSS after spike filter & idle forcing local forceIdleState = false -- True if idle conditions (low RPM/TPS) are met -- One-time check for initial sensor data if not initialDataReceived then if getSensor("Rpm") ~= nil and getSensor("Tps1") ~= nil and newRawVssFromCan ~= nil then initialDataReceived = true end end local currentRpm = getSensor("Rpm") local currentTps = getSensor("Tps1") -- Force VSS to 0 if idle-like conditions met if currentRpm ~= nil and currentTps ~= nil then if currentRpm < 1300 and currentTps < 1.0 then processedRawVss = 0 -- Force the VSS value used for logic to 0 forceIdleState = true idleStateForcedInLastCanCycle = true end end -- VSS Spike Filter (conditionally active) local useLastSpeedDueToSpike = false if enableVssSpikeFilter then if lastKnownRawVss ~= nil and processedRawVss ~= nil and math.abs(processedRawVss - lastKnownRawVss) > 100 then useLastSpeedDueToSpike = true end end if useLastSpeedDueToSpike then currentFinalVehicleSpeed = lastSetVehicleSpeed -- Use last scaled VSS -- lastKnownRawVss is NOT updated with the spiky processedRawVss elseif processedRawVss ~= nil then currentFinalVehicleSpeed = processedRawVss -- Scale the (potentially overridden) raw VSS lastKnownRawVss = processedRawVss -- Store the raw (but potentially overridden) value else currentFinalVehicleSpeed = lastSetVehicleSpeed -- Fallback end vehicleSpeedSensor:set(currentFinalVehicleSpeed) inputShaftSpeedSensor:set(currentFinalVehicleSpeed) lastSetVehicleSpeed = currentFinalVehicleSpeed -- Check "Shift In Progress" Timer if is_shifting then if shift_in_progress_timer:getElapsedSeconds() >= shiftExecutionTimeSecondsSetting then is_shifting = false end end -- High Priority: Force to 1st gear if idle state is active and not already in 1st/shifting if forceIdleState and IntendedGear ~= 1 and not is_shifting then IntendedGear = 1 setSolenoidsForGear(IntendedGear) is_shifting = true; shift_in_progress_timer:reset() is_checking_gear_match = true; gearMatchTimer:reset() shiftErrorActive = false end -- Main Shift Decision Logic: Only if not shifting, data received, tables loaded if not is_shifting and initialDataReceived and upshiftScheduleTableIndex ~= nil and downshiftScheduleTableIndex ~= nil and wotRpmCurveIndex ~= nil and solenoidPatternCurveIndex ~= nil then currentRpm = getSensor("Rpm") -- Re-fetch for current values currentTps = getSensor("Tps1") if currentRpm == nil or currentTps == nil then -- Skip if essential sensor data still missing for this cycle else local isWOT = (currentTps >= wotTpsThresholdSetting) local newPotentialDesiredGear = IntendedGear -- 1. WOT Upshift if isWOT and IntendedGear < 4 then local wotTargetRpm = curve(wotRpmCurveIndex, IntendedGear) if wotTargetRpm ~= nil and currentRpm >= wotTargetRpm then newPotentialDesiredGear = IntendedGear + 1 end end -- 2. Table Downshift (if no WOT upshift) if newPotentialDesiredGear == IntendedGear and IntendedGear > 1 then local yAxis = IntendedGear local targetVss = table3d(downshiftScheduleTableIndex, currentTps, yAxis) if targetVss ~= nil and currentFinalVehicleSpeed <= targetVss and currentRpm <= downshiftLockoutRpmSetting then newPotentialDesiredGear = IntendedGear - 1 end end -- 3. Table Upshift (if no WOT upshift or downshift) if newPotentialDesiredGear == IntendedGear and IntendedGear < 4 then local yAxis = IntendedGear local vssForTableLookup = currentFinalVehicleSpeed if currentRpm < 1200 then vssForTableLookup = 0.0 end -- RPM override for VSS in table lookup local targetVss = table3d(upshiftScheduleTableIndex, currentTps, yAxis) if targetVss ~= nil and vssForTableLookup >= targetVss then newPotentialDesiredGear = IntendedGear + 1 end end -- Shift Execution if newPotentialDesiredGear ~= IntendedGear and newPotentialDesiredGear >= 1 and newPotentialDesiredGear <= 4 then IntendedGear = newPotentialDesiredGear setSolenoidsForGear(IntendedGear) is_shifting = true; shift_in_progress_timer:reset() is_checking_gear_match = true; gearMatchTimer:reset() shiftErrorActive = false end end end endendcanRxAdd(0x360); canRxAdd(0x3E0); canRxAdd(0x370); canRxAdd(0x470) -- 0x470 is added in initializeShiftLogic-- onTick: Called periodically by TCU firmwarefunction onTick() -- Check "Gear Match" Timer (conditionally active) if enableGearErrorCheck and is_checking_gear_match then local actualGear = getSensor("DetectedGear") if actualGear ~= nil and IntendedGear == actualGear then shiftErrorActive = false is_checking_gear_match = false elseif gearMatchTimer:getElapsedSeconds() >= gearMatchTimeoutSecondsSetting then shiftErrorActive = true is_checking_gear_match = false end elseif not enableGearErrorCheck and is_checking_gear_match then is_checking_gear_match = false shiftErrorActive = false end -- Periodic Status Print (every 500ms) if printStatusTimer:getElapsedSeconds() >= 0.5 then if initialDataReceived then local detectedGearString = "nil"; local detectedGearValue = getSensor("DetectedGear") if detectedGearValue ~= nil then detectedGearString = detectedGearValue end local rpmString = "nil"; local rpmValue = getSensor("Rpm") if rpmValue ~= nil then rpmString = rpmValue end local tpsString = "nil"; local tpsValue = getSensor("Tps1") if tpsValue ~= nil then tpsString = tpsValue end local lastKnownRawVssString = "nil" if lastKnownRawVss ~= nil then lastKnownRawVssString = lastKnownRawVss end local shiftErrorString = "N/A" if enableGearErrorCheck then shiftErrorString = (shiftErrorActive and "TRUE" or "false") end local isShiftingString = (is_shifting and "YES" or "no") local idleForcedString = (idleStateForcedInLastCanCycle and "YES" or "no") print("VSS:"..lastSetVehicleSpeed.."(Raw:"..lastKnownRawVssString..") RPM:"..rpmString.." TPS:"..tpsString.. " IGear:"..IntendedGear.." AGear:"..detectedGearString.. " SolA:"..solenoid_A_state_str.." SolB:"..solenoid_B_state_str.. " SErr:"..shiftErrorString.." Shft'ing:"..isShiftingString.." IdleFrc:"..idleForcedString) end printStatusTimer:reset() endendsetTickRate(100) -- 100 Hz
Statistics: Posted by NormanAlphaspeed — Fri May 23, 2025 12:28 am