出于个人模型爱好,一直想尝试VTOL垂直起落,夏天时候尝试了几种离题太远就不说了。11前做地铁冥想时忽然闪出个方案。基本上类似于鱼鹰V22, 双发垂直起飞,然后电机转90度提供前进推力,固定翼提供升力。这其中有个问题:就是电机转90度后飞控板XYZ方向传感器没有对应旋转,无法起到自稳作用了,垂直起落时两个电机转速差动控制左右副翼方向的平衡,但转到水平方向,就应该变成前进方向控制差动,起到类似方向舵的作用。想过几种方案,包括机械方式旋转飞控板,不是不行,但最终感觉还是改代码最简易。

使用的飞控是F4 + 开源INAV 2.0.0,第一个问题就是编译,我了个去,网上搜了下居然没有太多的信息,简书这个连接写的算清楚的,
https://www.jianshu.com/p/9a13eee41b48  
按说应该感谢,但其实非常想给作者几下,因为这么几句实际操作时候是远远不够的,其他CSDN上也有一个文章关于inav编译,但写的太笼统了,对于不了解linux的人,中间有太多坑:Linxu的版本选择、编译飞控代码需要依赖的软件包。编译命令。前后花了将近2个星期的个人时间。中间还放弃过,

有些说可以使用docker编译,实际试了下我靠,不是不行,docker有多大下载有多恶心不说了,安装docker要先安装windows server,这么没操作性的建议....你知道以前我安装windows server的时候迷失了多少次自我么?

本说明是写给平时不怎么用Linux的又有些修改INAV需求的人的,对于Linxu熟客会觉得很常识,可以直接跳过了。

第一部分 编译
第一步:安装虚拟机。我用的版本是VMware® Workstation 14 Pro。配置虚拟硬盘至少20G空间,其他默认就够用了


第二步:网上下载Ubuntu 18AMD64,这里想再吐下,这么简单一个信息, 没有人提到过,我前后下载了不下5,6个版本的linux安装包,比较了下Ubuntu包的大小不那么吓人。用户也比较多。然后就是想当然用了X86版本,结果编译时出现32位error,找了好几天发现没有对应的X86-64位版本, 要不就是包尺寸好几个G大,最后试试的态度下了个AMD64版本,居然是可以用的。

第三步:更新Ubuntu的软件,命令 sudo apt-get update 
(这句命令写起来也很简单,但国内还涉及到更新源问题,修改更新源本身就是个边猜边瞎试的过程费神费时间,我是参考了CSDN里边多个文章,具体自行搜索,这里没必要展开了)

安装make, gcc, git等
sudo apt install git
sudo apt install gcc
sudo apt install ruby


第四步:github下载INAV2.0.0, 有耐心多试两次还是可以直接从github上成功下载的。
git clone https://github.com/iNavFlight/inav.git

注意这样得到的是最新的代码,想取到指定版本,要先在左边选择tag标签,然后最简单的是直接下载标签版本,如果用git命令要checkout指定版本。

 

到这其实你可以直接尝试编译INAV了,但保证你几乎肯定会碰上各种编不过。包括这个:

第五步: 安装gcc-arm-none-eabi,
我使用INAV2.0.0 下载的arm编译器是gcc-arm-none-eabi-7-2018-q2-update 版本,仅供参考。
这个包比较大,以至于远远大过了要编译的inav代码,确定下载成功了再继续下一步
可以用inav目录下的脚本 ./install-toolchain.sh  

也可以手动下载, 参考文章:https://blog.csdn.net/yk150915/article/details/80117082
搜索gcc arm eabi, 同时参考多篇文章,这里不展开了,否则又是一大坨。
 

第六步: 编译,在代码目录下:  make TARGET=OMNIBUSF4PRO   
这里Tareget 参数是你的飞控板型号,即使是F4飞控也分V3版本和pro版本,但现在淘宝上基本都是山寨pro版本了。具体参考inav/docs 下的说明,以及Makefile。

如果没有编译通过,又是按照上边列出的版本安装的,现在应该只剩下缺支持包的问题。看缺什么东西,按照提示网上搜索,一般都能给出方法。

如果通过,在 inav/obj目录下应该有了.hex文件,则编译成功了。可以和release的hex对比下字节大小确认下。然后上板测试了。

第二部分, 修改INAV代码(混控部分)
原来以为要搞很久,但坐下来打开看的时候发现INAV写的挺清晰,也没用太多的炫技般的语句技巧,个人是很反感使用很生僻的语句,看着费劲,也没见提高什么可读性。
我的目的是临时对调混控通道,因为直接在inav 配置器里做不到这个功能。 跳过驱动什么的,直奔主题,跟混控通道相关的文件有src/main/flight/mixer.c 
src/main/flight/servos.c 

可见舵机和电机PWM控制的混控是分开处理的。和INAV配置器的混控界面里一样,分成电机混控和舵机混控

存储混控信息的数据结构:


static servoMixer_t currentServoMixer[MAX_SERVO_RULES];
static motorMixer_t currentMixer[MAX_SUPPORTED_MOTORS];

typedef struct servoMixer_s {
    uint8_t targetChannel;                  // servo that receives the output of the rule
    uint8_t inputSource;                    // input channel for this rule
    int8_t rate;                            // range [-125;+125] ; can be used to adjust a rate 0-125% and a direction
    uint8_t speed;                          // reduces the speed of the rule, 0=unlimited speed
} servoMixer_t;

typedef struct motorMixer_s {
    float throttle;
    float roll;
    float pitch;
    float yaw;
} motorMixer_t;

存储混控信息的数据结构也不复杂,比如motormix里边的throttle,roll,pitch,yaw,自然就是对应的配置界面里的对应项了。

那么这就简单了,如果取得遥控器辅助通道的PWM信息,也就是代表电机倾斜角度的RC通道, 根据角度重新设置对应混控规则的通道值就行了。
这里说下,我做的这个VT0L, 只用了两个电机+两个舵机,两个舵机既负责垂直起落时候的电机前后控制,也负责水平飞行时候的左右副翼操作,和类似水平尾翼的俯仰操作。 而垂直/水平飞行的转换,是通过新设置一个辅助通道实现的,我用的的CH7,CH7混控控制电机的角度,扳动或旋转都可以,看希望的操作方式。
说起来吃力,可以参考视频演示

https://www.bilibili.com/video/av70709777?from=search&seid=16415844640983981290

双发固定翼 VTOL 操纵转换演示


关于机体本身其实也有很多说的,具体可以参考在5imx上的相关帖子。

http://bbs.5imx.com/forum.php?mod=viewthread&tid=1551588&extra=page%3D1

2轴VTOL垂直起降固定翼 + INAV 飞控代码修改编译

 

这里边还有个细节,就是开始想按照角度持续调整混控比例,后来改成直接在超过45度一点点的时候直接对调几个通道的混控值。因为在45度方向上,不管是电机转速还是舵机的滚转偏转,其实都有水平和垂直的分量力,总之没必要持续调比例。

继续找通道输入量的存储位置,在servos.c的 servoMixer()函数里,遥控器通道输入量是放在 rcData[AUX3]里边的,

这个值就是从0 ~ 500的PWM连续变化值,因此我设在 260时基本就是45度多的位置。

添加代码在----VTOL Custom Code----- 注释中,其他是INAV原有代码,
修改完后编译,最后要说明的是:因为个人使用,通道及混控值都是写死的,在INAV配置器中也要配置上对应的初始混控规则,要注意。

如下:
servos.c:
 

void servoMixer(float dT)
{
    int16_t input[INPUT_SOURCE_COUNT]; // Range [-500:+500]

    if (FLIGHT_MODE(MANUAL_MODE)) {
        input[INPUT_STABILIZED_ROLL] = rcCommand[ROLL];
        input[INPUT_STABILIZED_PITCH] = rcCommand[PITCH];
        input[INPUT_STABILIZED_YAW] = rcCommand[YAW];
    } else {
        // Assisted modes (gyro only or gyro+acc according to AUX configuration in Gui
        input[INPUT_STABILIZED_ROLL] = axisPID[ROLL];
        input[INPUT_STABILIZED_PITCH] = axisPID[PITCH];
        input[INPUT_STABILIZED_YAW] = axisPID[YAW];

        // Reverse yaw servo when inverted in 3D mode only for multirotor and tricopter
        if (feature(FEATURE_3D) && (rcData[THROTTLE] < PWM_RANGE_MIDDLE) &&
        (mixerConfig()->platformType == PLATFORM_MULTIROTOR || mixerConfig()->platformType == PLATFORM_TRICOPTER)) {
            input[INPUT_STABILIZED_YAW] *= -1;
        }
    }

    input[INPUT_FEATURE_FLAPS] = FLIGHT_MODE(FLAPERON) ? servoConfig()->flaperon_throw_offset : 0;

    if (IS_RC_MODE_ACTIVE(BOXCAMSTAB)) {
        input[INPUT_GIMBAL_PITCH] = scaleRange(attitude.values.pitch, -1800, 1800, -360, +360);
        input[INPUT_GIMBAL_ROLL] = scaleRange(attitude.values.roll, -1800, 1800, -360, +360);
    } else {
        input[INPUT_GIMBAL_PITCH] = 0;
        input[INPUT_GIMBAL_ROLL] = 0;
    }

    input[INPUT_STABILIZED_THROTTLE] = motor[0] - 1000 - 500;  // Since it derives from rcCommand or mincommand and must be [-500:+500]

    // center the RC input value around the RC middle value
    // by subtracting the RC middle value from the RC input value, we get:
    // data - middle = input
    // 2000 - 1500 = +500
    // 1500 - 1500 = 0
    // 1000 - 1500 = -500
    input[INPUT_RC_ROLL]     = rcData[ROLL]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_PITCH]    = rcData[PITCH]    - PWM_RANGE_MIDDLE;
    input[INPUT_RC_YAW]      = rcData[YAW]      - PWM_RANGE_MIDDLE;
    input[INPUT_RC_THROTTLE] = rcData[THROTTLE] - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH5]      = rcData[AUX1]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH6]      = rcData[AUX2]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH7]      = rcData[AUX3]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH8]      = rcData[AUX4]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH9]      = rcData[AUX5]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH10]     = rcData[AUX6]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH11]     = rcData[AUX7]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH12]     = rcData[AUX8]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH13]     = rcData[AUX9]     - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH14]     = rcData[AUX10]    - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH15]     = rcData[AUX11]    - PWM_RANGE_MIDDLE;
    input[INPUT_RC_CH16]     = rcData[AUX12]    - PWM_RANGE_MIDDLE;

    //---------------VTOL Custom Code--------------------
    if(input[INPUT_RC_CH7] > 260)  {
        //swap roll and Yaw channel
        //currentServoMixer[1].targetChannel = 3;
        currentServoMixer[1].inputSource = INPUT_STABILIZED_ROLL ;
        currentServoMixer[1].rate = 50;
        //currentServoMixer[3].targetChannel = 4;
        currentServoMixer[3].inputSource = INPUT_STABILIZED_ROLL ;
        currentServoMixer[3].rate = 50;
    }
    else {
        //currentServoMixer[1].targetChannel = 3;
        currentServoMixer[1].inputSource = INPUT_STABILIZED_YAW ;
        currentServoMixer[1].rate = 50;
        //currentServoMixer[3].targetChannel = 4;
        currentServoMixer[3].inputSource = INPUT_STABILIZED_YAW ;
        currentServoMixer[3].rate = 50;
    }
    //---------------VTOL Custom Code--------------------

    for (int i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
        servo[i] = 0;
    }

    // mix servos according to rules
    for (int i = 0; i < servoRuleCount; i++) {
        const uint8_t target = currentServoMixer[i].targetChannel;
        const uint8_t from = currentServoMixer[i].inputSource;

        /*
         * Apply mixer speed limit. 1 [one] speed unit is defined as 10us/s:
         * 0 = no limiting
         * 1 = 10us/s -> full servo sweep (from 1000 to 2000) is performed in 100s
         * 10 = 100us/s -> full sweep (from 1000 to 2000)  is performed in 10s
         * 100 = 1000us/s -> full sweep in 1s
         */
        int16_t inputLimited = (int16_t) rateLimitFilterApply4(&servoSpeedLimitFilter[i], input[from], currentServoMixer[i].speed * 10, dT);

        servo[target] += servoDirection(target, from) * ((int32_t)inputLimited * currentServoMixer[i].rate) / 100;
    }

    for (int i = 0; i < MAX_SUPPORTED_SERVOS; i++) {

        /*
         * Apply servo rate
         */
        servo[i] = ((int32_t)servoParams(i)->rate * servo[i]) / 100L;

        /*
         * Perform acumulated servo output scaling to match servo min and max values
         * Important: is servo rate is > 100%, total servo output might be bigger than
         * min/max
         */
        if (servo[i] > 0) {
            servo[i] = (int16_t) (servo[i] * servoMetadata[i].scaleMax);
        } else {
            servo[i] = (int16_t) (servo[i] * servoMetadata[i].scaleMin);
        }

        /*
         * Add a servo midpoint to the calculation
         */
        servo[i] += servoParams(i)->middle;

        /*
         * Constrain servo position to min/max to prevent servo damage
         * If servo was saturated above min/max, that means that user most probably
         * allowed the situation when smix weight sum for an output was above 100
         */
        servo[i] = constrain(servo[i], servoParams(i)->min, servoParams(i)->max);
    }
} 

mixer.c

void mixTable(const float dT)
{
    int16_t input[3];   // RPY, range [-500:+500]
    // Allow direct stick input to motors in passthrough mode on airplanes
    if (STATE(FIXED_WING) && FLIGHT_MODE(MANUAL_MODE)) {
        // Direct passthru from RX
        input[ROLL] = rcCommand[ROLL];
        input[PITCH] = rcCommand[PITCH];
        input[YAW] = rcCommand[YAW];
    }
    else {
        input[ROLL] = axisPID[ROLL];
        input[PITCH] = axisPID[PITCH];
        input[YAW] = axisPID[YAW];

        if (motorCount >= 4 && mixerConfig()->yaw_jump_prevention_limit < YAW_JUMP_PREVENTION_LIMIT_HIGH) {
            // prevent "yaw jump" during yaw correction
            input[YAW] = constrain(input[YAW], -mixerConfig()->yaw_jump_prevention_limit - ABS(rcCommand[YAW]), mixerConfig()->yaw_jump_prevention_limit + ABS(rcCommand[YAW]));
        }
    }

    // Initial mixer concept by bdoiron74 reused and optimized for Air Mode
    int16_t rpyMix[MAX_SUPPORTED_MOTORS];
    int16_t rpyMixMax = 0; // assumption: symetrical about zero.
    int16_t rpyMixMin = 0;

    //---------------VTOL Custom Code--------------------
    if(rcData[AUX3] - PWM_RANGE_MIDDLE < 260)  {
        //swap roll and Yaw channel
        currentMixer[0].roll = 1;
        currentMixer[1].roll = -1;
        currentMixer[0].yaw = 0;
        currentMixer[1].yaw = 0;
    }
    else {
        currentMixer[0].roll = 0;
        currentMixer[1].roll = 0;
        currentMixer[0].yaw = 1;
        currentMixer[1].yaw = -1;
    }
    //---------------VTOL Custom Code--------------------


    // motors for non-servo mixes
    for (int i = 0; i < motorCount; i++) {
        rpyMix[i] =
            input[PITCH] * currentMixer[i].pitch +
            input[ROLL] * currentMixer[i].roll +
            -mixerConfig()->yaw_motor_direction * input[YAW] * currentMixer[i].yaw;

        if (rpyMix[i] > rpyMixMax) rpyMixMax = rpyMix[i];
        if (rpyMix[i] < rpyMixMin) rpyMixMin = rpyMix[i];
    }

    int16_t rpyMixRange = rpyMixMax - rpyMixMin;
    int16_t throttleRange, throttleCommand;
    int16_t throttleMin, throttleMax;
    static int16_t throttlePrevious = 0;   // Store the last throttle direction for deadband transitions

    // Find min and max throttle based on condition.
    if (feature(FEATURE_3D)) {
        if (!ARMING_FLAG(ARMED)) throttlePrevious = PWM_RANGE_MIDDLE; // When disarmed set to mid_rc. It always results in positive direction after arming.

        if ((rcCommand[THROTTLE] <= (PWM_RANGE_MIDDLE - rcControlsConfig()->deadband3d_throttle))) { // Out of band handling
            throttleMax = flight3DConfig()->deadband3d_low;
            throttleMin = motorConfig()->minthrottle;
            throttlePrevious = throttleCommand = rcCommand[THROTTLE];
        } else if (rcCommand[THROTTLE] >= (PWM_RANGE_MIDDLE + rcControlsConfig()->deadband3d_throttle)) { // Positive handling
            throttleMax = motorConfig()->maxthrottle;
            throttleMin = flight3DConfig()->deadband3d_high;
            throttlePrevious = throttleCommand = rcCommand[THROTTLE];
        } else if ((throttlePrevious <= (PWM_RANGE_MIDDLE - rcControlsConfig()->deadband3d_throttle)))  { // Deadband handling from negative to positive
            throttleCommand = throttleMax = flight3DConfig()->deadband3d_low;
            throttleMin = motorConfig()->minthrottle;
        } else {  // Deadband handling from positive to negative
            throttleMax = motorConfig()->maxthrottle;
            throttleCommand = throttleMin = flight3DConfig()->deadband3d_high;
        }
    } else {
        throttleCommand = rcCommand[THROTTLE];
        throttleMin = motorConfig()->minthrottle;
        throttleMax = motorConfig()->maxthrottle;

        // Throttle compensation based on battery voltage
        if (feature(FEATURE_THR_VBAT_COMP) && feature(FEATURE_VBAT) && isAmperageConfigured())
            throttleCommand = MIN(throttleMin + (throttleCommand - throttleMin) * calculateThrottleCompensationFactor(), throttleMax);
    }

    throttleRange = throttleMax - throttleMin;

    #define THROTTLE_CLIPPING_FACTOR    0.33f
    motorMixRange = (float)rpyMixRange / (float)throttleRange;
    if (motorMixRange > 1.0f) {
        for (int i = 0; i < motorCount; i++) {
            rpyMix[i] /= motorMixRange;
        }

        // Allow some clipping on edges to soften correction response
        throttleMin = throttleMin + (throttleRange / 2) - (throttleRange * THROTTLE_CLIPPING_FACTOR / 2);
        throttleMax = throttleMin + (throttleRange / 2) + (throttleRange * THROTTLE_CLIPPING_FACTOR / 2);
    } else {
        throttleMin = MIN(throttleMin + (rpyMixRange / 2), throttleMin + (throttleRange / 2) - (throttleRange * THROTTLE_CLIPPING_FACTOR / 2));
        throttleMax = MAX(throttleMax - (rpyMixRange / 2), throttleMin + (throttleRange / 2) + (throttleRange * THROTTLE_CLIPPING_FACTOR / 2));
    }

    // Now add in the desired throttle, but keep in a range that doesn't clip adjusted
    // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
    if (ARMING_FLAG(ARMED)) {
        for (int i = 0; i < motorCount; i++) {
            motor[i] = rpyMix[i] + constrain(throttleCommand * currentMixer[i].throttle, throttleMin, throttleMax);

            if (failsafeIsActive()) {
                motor[i] = constrain(motor[i], motorConfig()->mincommand, motorConfig()->maxthrottle);
            } else if (feature(FEATURE_3D)) {
                if (throttlePrevious <= (PWM_RANGE_MIDDLE - rcControlsConfig()->deadband3d_throttle)) {
                    motor[i] = constrain(motor[i], motorConfig()->minthrottle, flight3DConfig()->deadband3d_low);
                } else {
                    motor[i] = constrain(motor[i], flight3DConfig()->deadband3d_high, motorConfig()->maxthrottle);
                }
            } else {
                motor[i] = constrain(motor[i], motorConfig()->minthrottle, motorConfig()->maxthrottle);
            }

            // Motor stop handling
            if (ARMING_FLAG(ARMED) && (getMotorStatus() != MOTOR_RUNNING)) {
                if (feature(FEATURE_MOTOR_STOP)) {
                    motor[i] = (feature(FEATURE_3D) ? PWM_RANGE_MIDDLE : motorConfig()->mincommand);
                } else {
                    motor[i] = motorConfig()->minthrottle;
                }
            }
        }
    } else {
        for (int i = 0; i < motorCount; i++) {
            motor[i] = motor_disarmed[i];
        }
    }

    /* Apply motor acceleration/deceleration limit */
    applyMotorRateLimiting(dT);
}


 

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐