227 lines
6.7 KiB
Lua

--[[
a dynamic motor mixer for an X8 coax multicopter with co-rotating
motors on each of the arms
adds parameters for a multiplier for top and bottom layer motors
--]]
local PARAM_TABLE_KEY = 105
local PARAM_TABLE_PREFIX = "MOT_X8C_"
-- add a parameter and bind it to a variable
local function bind_add_param(name, idx, default_value)
assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', name))
-- use the old style of param constructor to be compatible with very old ArduPilot versions (tested on 4.2.x)
local p = Parameter()
p:init(PARAM_TABLE_PREFIX .. name)
return p;
end
-- Setup Parameters
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 6), 'X8-corotating: could not add param table')
local MAV_SEVERITY = {EMERGENCY=0, ALERT=1, CRITICAL=2, ERROR=3, WARNING=4, NOTICE=5, INFO=6, DEBUG=7}
--[[
// @Param: X8C_TOPMUL
// @DisplayName: X8C Top Layer Multiplier
// @Description: Multiplier for the top layer motors
// @Range: 0 1
// @User: Standard
--]]
local X8C_TOPMUL = bind_add_param('TOPMUL', 1, 1.0)
--[[
// @Param: X8C_BOTMUL
// @DisplayName: X8C Bottom Layer Multiplier
// @Description: Multiplier for the bottom layer motors
// @Range: 0 1
// @User: Standard
--]]
local X8C_BOTMUL = bind_add_param('BOTMUL', 2, 1.0)
--[[
// @Param: X8C_ORDER
// @DisplayName: X8C motor ordering
// @Description: Selecting between old and clockwise X8 ordering
// @Values: 0:OldOrdering, 1:ClockwiseOrdering
// @User: Standard
--]]
local X8C_ORDER = bind_add_param('ORDER', 3, 0)
local CCW = 1
local CW = -1
local FRAME_NAME = "X8-corotating"
--[[
motors array for X8 with co-rotating motors, using old X8 ordering
--]]
local Motors_old_order = {
{ name = "Motor 1", test_order = 1, Xpos = 1, Ypos = 1, rotation = CCW, layer=2 },
{ name = "Motor 2", test_order = 7, Xpos = 1, Ypos = -1, rotation = CW, layer=2 },
{ name = "Motor 3", test_order = 5, Xpos = -1, Ypos = -1, rotation = CCW, layer=2 },
{ name = "Motor 4", test_order = 3, Xpos = -1, Ypos = 1, rotation = CW, layer=2 },
{ name = "Motor 5", test_order = 8, Xpos = 1, Ypos = -1, rotation = CW, layer=1 },
{ name = "Motor 6", test_order = 2, Xpos = 1, Ypos = 1, rotation = CCW, layer=1 },
{ name = "Motor 7", test_order = 4, Xpos = -1, Ypos = 1, rotation = CW, layer=1 },
{ name = "Motor 8", test_order = 6, Xpos = -1, Ypos = -1, rotation = CCW, layer=1 }
}
--[[
motors array for X8 with co-rotating motors, using clockwise X8 ordering
--]]
local Motors_cw_order = {
{ name = "Motor 1", test_order = 1, Xpos = 1, Ypos = 1, rotation = CCW, layer=2 },
{ name = "Motor 2", test_order = 2, Xpos = 1, Ypos = 1, rotation = CCW, layer=1 },
{ name = "Motor 3", test_order = 3, Xpos = -1, Ypos = 1, rotation = CW, layer=2 },
{ name = "Motor 4", test_order = 4, Xpos = -1, Ypos = 1, rotation = CW, layer=1 },
{ name = "Motor 5", test_order = 5, Xpos = -1, Ypos = -1, rotation = CCW, layer=2 },
{ name = "Motor 6", test_order = 6, Xpos = -1, Ypos = -1, rotation = CCW, layer=1 },
{ name = "Motor 7", test_order = 7, Xpos = 1, Ypos = -1, rotation = CW, layer=2 },
{ name = "Motor 8", test_order = 8, Xpos = 1, Ypos = -1, rotation = CW, layer=1 }
}
if X8C_ORDER:get() == 0 then
Motors = Motors_old_order
else
Motors = Motors_cw_order
FRAME_NAME = "X8-corotating-cw"
end
local num_motors = #Motors
--[[
add the motors to the Motors_dynamic object
--]]
for i = 1, num_motors do
local test_order = Motors[i].test_order
Motors_dynamic:add_motor(i-1, test_order)
end
-- create a motor factor table
factors = motor_factor_table()
--[[
multiplier for a layer, to account for the parameters
--]]
local function layer_multiplier(motor_num)
local layer = Motors[motor_num].layer
if layer == 2 then
return X8C_TOPMUL:get()
elseif layer == 1 then
return X8C_BOTMUL:get()
else
return 1.0
end
end
--[[
when we setup the roll, pitch and yaw factors we normalise them to a maximum
of 0.5, which is what is expected by the motor mixer
--]]
--[[
setup the roll factors
--]]
local function setup_roll_factors()
-- find the highest absolute value of Ypos
local max_y = 0
for i = 1, num_motors do
max_y = math.max(math.abs(Motors[i].Ypos), max_y)
end
if max_y == 0 then
max_y = 1 -- prevent division by zero
end
-- set the roll factors based on the Xpos of each motor
-- positive Ypos means roll left, negative Ypos means roll right
for i = 1, num_motors do
local Ypos = Motors[i].Ypos
local roll_factor = -(Ypos / max_y) * 0.5 * layer_multiplier(i)
factors:roll(i-1, roll_factor)
end
end
--[[
setup the pitch factors
--]]
local function setup_pitch_factors()
-- find the highest absolute value of Ypos
local max_x = 0
for i = 1, num_motors do
max_x = math.max(math.abs(Motors[i].Xpos), max_x)
end
if max_x == 0 then
max_x = 1 -- prevent division by zero
end
-- set the roll factors based on the Ypos of each motor
-- positive Xpos means pitch up, negative Xpos means pitch down
for i = 1, num_motors do
local Xpos = Motors[i].Xpos
local pitch_factor = (Xpos / max_x) * 0.5 * layer_multiplier(i)
factors:pitch(i-1, pitch_factor)
end
end
--[[
setup the yaw factors
--]]
local function setup_yaw_factors()
-- set the yaw factors based on the rotation of each motor
for i = 1, num_motors do
local rotation = Motors[i].rotation
factors:yaw(i-1, rotation * 0.5 * layer_multiplier(i))
end
end
--[[
setup the thrust factors
--]]
local function setup_thrust_factors()
for i = 1, num_motors do
factors:throttle(i-1, 1.0 * layer_multiplier(i))
end
end
--[[
setup throttle factors
--]]
local function update_factors()
if X8C_ORDER:get() == 0 then
Motors = Motors_old_order
else
Motors = Motors_cw_order
end
setup_roll_factors()
setup_pitch_factors()
setup_yaw_factors()
setup_thrust_factors()
Motors_dynamic:load_factors(factors)
end
update_factors()
assert(Motors_dynamic:init(num_motors), string.format("Failed to init %s", FRAME_NAME))
motors:set_frame_string(FRAME_NAME)
local last_top_layer_mul = X8C_TOPMUL:get()
local last_bot_layer_mul = X8C_BOTMUL:get()
local last_order = X8C_ORDER:get()
local function update()
if last_top_layer_mul ~= X8C_TOPMUL:get() or
last_bot_layer_mul ~= X8C_BOTMUL:get() or
last_order ~= X8C_ORDER:get() then
last_top_layer_mul = X8C_TOPMUL:get()
last_bot_layer_mul = X8C_BOTMUL:get()
last_order = X8C_ORDER:get()
update_factors()
end
return update, 200
end
gcs:send_text(MAV_SEVERITY.INFO, string.format("Loaded frame %s", FRAME_NAME))
return update,200