Auto trim - remove tediousness while making the process more realistic

I have written a little Lua script that tries to more closely mimic what I imagine the process of setting trim is in a real aircraft. Basically, the script takes care of adjusting the trim to maintain the aircraft’s pitch while you very gradually relieve pressure on the yoke.

If some folks could test it out and give me feedback, I’m thinking of posting it on flightsim.to .

In order to use this script, you must have a registered (paid-for) copy of FSUIPC.

  1. Create a Lua file with the contents below and place it in your FSUIPC folder.
  2. Connect your registered copy of FSUIPC to the running sim.
  3. Map a joystick button to the Lua script so it executes every time you tap the button.

How to use:

  1. Get your aircraft fairly stable at the pitch you desire, holding constant pressure on the yoke.
  2. Click the joystick button. This will toggle the autotrimmer script.
  3. With the script active, very gradually release pressure from the yoke. It should take you several seconds to fully release pressure if you’re doing it gradually enough. The script makes the Windows chime sound every two seconds so you know it is running, and to help you slow down and take your time relaxing pressure on the yoke.
  4. Once you’ve fully returned the yoke to the neutral position, press the joystick button again. The script will deactivate and stop chiming.

Let me know if anyone tries this out and what your experience is. I tested it with my Honeycomb Alpha yoke and with the C172 and the Aerosoft Twin Otter.

MS_BETWEEN_FRAMES = 34
S_BETWEEN_ANNOUNCEMENTS = 2


previousElevatorPosition = 0
previousElevatorTrimPosition = 0
desiredPitch = 0

function get_plane_pitch()
    -- in degrees. negative = pitch up, positive = pitch down
    return ipc.readSD(0x0578) * 360 / (65536 * 65536)
end

function get_elevator_position()
    --  Elevator control input: –16383 to +16383
    return ipc.readSW(0x0BB2)
end

function get_elevator_trim_position()
    -- Elevator trim control input: –16383 to +16383
    return ipc.readSW(0x0BC0)
end

function set_elevator_trim_position(position)
    ipc.writeSW(0x0BC0, math.floor(position))
end

errorPrior = 0
integralPrior = 0
kP = 150
kI = 0.05
kD = 25
bias = 0

function pid_reset()
    errorPrior = 0
    integralPrior = 0
end

function pid_output(desiredValue, actualValue)
    -- math happens here
    error = desiredValue - actualValue
    integral = integralPrior + error * MS_BETWEEN_FRAMES
    derivative = (error - errorPrior) / MS_BETWEEN_FRAMES
    output = kP * error + kI * integral + kD * derivative + bias

    errorPrior = error
    integralPrior = integral

    return output
end

function activate_autotrimmer()
    ipc.log("Activating autotrimmer")
    ipc.set("autotrimmerIsActive", 1)

    previousElevatorPosition = get_elevator_position()
    previousElevatorTrimPosition = get_elevator_trim_position()
    desiredPitch = get_plane_pitch()
    pid_reset()
end

function deactivate_autotrimmer()
    ipc.log("Deactivating autotrimmer")
    ipc.set("autotrimmerIsActive", 0)
end

function toggle_autotrimmer()
    if ipc.get("autotrimmerIsActive") == 1 then 
        deactivate_autotrimmer() 
    else
        activate_autotrimmer()
    end
end




function process_frame()
    planePitch = get_plane_pitch()
    elevatorPosition = get_elevator_position()
    elevatorTrimPosition = get_elevator_trim_position()

    pidOutput = pid_output(desiredPitch, planePitch)

    newTrimPosition = elevatorTrimPosition - pidOutput
    set_elevator_trim_position(newTrimPosition)

    -- ipc.log("PID Output: " .. pidOutput .. ", new trim: " .. newTrimPosition .. ", Pitch: " .. planePitch .. ", Error: " .. (desiredPitch - planePitch))
end



ipc.log("Invoked autotrimmer")

if ipc.get("autotrimmerHasBeenRunBefore") == nil then
    ipc.log("This is the first run of the autotrimmer")
    ipc.set("autotrimmerHasBeenRunBefore", 1)
    activate_autotrimmer()
else
    toggle_autotrimmer()
end

timeSinceLastAnnouncement = os.time(os.date("!*t"))

while ipc.get("autotrimmerIsActive") == 1 do
    process_frame()

    currentTime = os.time(os.date("!*t"))
    if currentTime - timeSinceLastAnnouncement > S_BETWEEN_ANNOUNCEMENTS then
        ipc.log('PLAYING SOUND')
        os.execute("echo \a")
        timeSinceLastAnnouncement = currentTime
    end

    ipc.sleep(MS_BETWEEN_FRAMES)
end

2 Likes