[phpBB Debug] PHP Warning: in file [ROOT]/phpbb/db/driver/mysqli.php on line 258: mysqli_fetch_assoc(): Couldn't fetch mysqli_result
[phpBB Debug] PHP Warning: in file [ROOT]/phpbb/db/driver/mysqli.php on line 320: mysqli_free_result(): Couldn't fetch mysqli_result
rusefi.com The most advanced open source ECU 2025-09-11T08:04:35 https://rusefi.com/forum/app.php/feed/topic/2925 2025-09-11T08:04:35 2025-09-11T08:04:35 https://rusefi.com/forum/viewtopic.php?t=2925&p=50867#p50867 <![CDATA[Re: Transmission Control for 4 gears; more, maybe later!]]>

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


]]>
2025-09-10T19:14:48 2025-09-10T19:14:48 https://rusefi.com/forum/viewtopic.php?t=2925&p=50862#p50862 <![CDATA[Re: Transmission Control for 4 gears; more, maybe later!]]>
I've made some progress but have not finished :( https://github.com/rusefi/rusefi/blob/master/firmware/controllers/lua/examples/TCU-4-speed.txt

Statistics: Posted by AndreyB — Wed Sep 10, 2025 7:14 pm


]]>
2025-09-10T17:51:57 2025-09-10T17:51:57 https://rusefi.com/forum/viewtopic.php?t=2925&p=50860#p50860 <![CDATA[Re: Transmission Control for 4 gears; more, maybe later!]]>
image.png

Statistics: Posted by AndreyB — Wed Sep 10, 2025 5:51 pm


]]>
2025-07-14T16:16:30 2025-07-14T16:16:30 https://rusefi.com/forum/viewtopic.php?t=2925&p=50702#p50702 <![CDATA[Re: Transmission Control for 4 gears; more, maybe later!]]> Statistics: Posted by jasaircraft — Mon Jul 14, 2025 4:16 pm


]]>
2025-06-02T01:32:07 2025-06-02T01:32:07 https://rusefi.com/forum/viewtopic.php?t=2925&p=50624#p50624 <![CDATA[Re: Transmission Control for 4 gears; more, maybe later!]]> https://youtube.com/shorts/DWDJleEFc0E

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)
uses a curve for WOT kickdown/upshift
uses two tables for up/downshift with shift direction vs tps
has downshift lockout RPM
has WOT threshold tps
talk with haltech CAN protocol to get most data
send shift request over CAN

a bunch of other stuff

Statistics: Posted by NormanAlphaspeed — Mon Jun 02, 2025 1:32 am


]]>
2025-05-23T08:10:28 2025-05-23T08:10:28 https://rusefi.com/forum/viewtopic.php?t=2925&p=50592#p50592 <![CDATA[Re: Transmission Control for 4 gears; more, maybe later!]]>
https://www.youtube.com/shorts/z5SX9Dk5ck0

Statistics: Posted by NormanAlphaspeed — Fri May 23, 2025 8:10 am


]]>
2025-05-23T01:58:46 2025-05-23T01:58:46 https://rusefi.com/forum/viewtopic.php?t=2925&p=50590#p50590 <![CDATA[Re: Transmission Control for 4 gears; more, maybe later!]]> Statistics: Posted by AndreyB — Fri May 23, 2025 1:58 am


]]>
2025-05-23T00:28:05 2025-05-23T00:28:05 https://rusefi.com/forum/viewtopic.php?t=2925&p=50589#p50589 <![CDATA[Transmission Control for 4 gears; more, maybe later!]]> I made this Lua, which I have succesfully driven on to control a 4R70W

Right now it has Intended vs Desired checking, Downshift RPM lockout, and uses two seperate shift tables which have TPS on X axis, and shift direction on Y axis (logic I copied from GM OEM tunes). It has a few other features which you cna paste the code in Gemini and it'll properly comment the code more lol, but here it is; later I will update with videos of the truck i'm using running

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

Oh and Gemini made most of this code, I take no real credit

Statistics: Posted by NormanAlphaspeed — Fri May 23, 2025 12:28 am


]]>