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.
- Create a Lua file with the contents below and place it in your FSUIPC folder.
- Connect your registered copy of FSUIPC to the running sim.
- Map a joystick button to the Lua script so it executes every time you tap the button.
How to use:
- Get your aircraft fairly stable at the pitch you desire, holding constant pressure on the yoke.
- Click the joystick button. This will toggle the autotrimmer script.
- 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.
- 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