Pixhawk之姿态控制

2023-05-16

 

原文地址:http://blog.csdn.net/qq_21842557


1、写在前面    
        无人机控制部分主要分为两个部分,姿态控制部分和位置控制部分;位置控制可用远程遥控控制,而姿态控制一般由无人机系统自动完成。姿态控制是非常重要的,因为无人机的位置变化都是由姿态变化引起的。
        下图阐释了PX4源码中的两个环路控制,分为姿态控制和位置控制。
 
        补充:关于Pixhawk原生固件中姿态(估计/控制)和位置(估计/控制)源码的应用问题
PX4Fireware原生固件中的modules中姿态估计有多种:Attitude_estimator_ekf、Attitude_estimator_q、ekf_att_pos_estimator。
位置估计有:ekf_att_pos_estimator、local_position_estimator、position_estimator_inav
姿态控制有:fw_att_control、mc_att_control、mc_att_control_multiplatform、vtol_att_control
位置控制有:fw_pos_control_l1、fw_pos_control_l1、mc_pos_control_multiplatform
        四旋翼用到以上哪些估计和控制算法呢?这部分在启动代码rc.mc_app里面有详细的说明。
 
    默认的是:
        姿态估计 Attitude_estimator_q
        位置估计 position_estimator_inav
        姿态控制 mc_att_control
        位置控制 mc_pos_control
2、飞行控制(该部分属于理论概述)
        飞行控制分为姿态控制和位置控制,该文章主讲姿态控制。
        所谓姿态控制,主要就是在前期姿态解算的基础上对四旋翼飞行器进行有效的飞行控制,以达到所需要的控制效果。在这种情况下,算法要学会如何连续地做决策,并且算法的评价应该根据其所做选择的长期质量来进行。举一个具体的例子,想想无人机飞行所面临的难题:每不到一秒,算法都必须反复地选择最佳的行动控制。控制过程还是以经典的PID反馈控制器为主(在控制环路中可以添加smith预测器)。那么如何实现控制呢?
        以四旋翼飞行器为例,主要就是通过改变旋翼的角速度来控制四旋翼无人机。每个旋翼产生一个推力(F1、F2、F3、F4)和一个力矩,其共同作用构成四旋翼无人机的主推力、偏航力矩、俯仰力矩和滚转力矩。在四旋翼无人机中,正对的一对旋翼旋转方向一致,另外一对与之相反,来抵消静态平稳飞行时的回转效应和气动力矩。升降以及RPY的实现不在赘述。控制对象就是四旋翼无人机,其动力学模型可以描述为:将其视为有一个力和三个力矩的三维刚体。如下给出了小角度变化条件下的四旋翼无人机的近似动力学模型:
 
        PS:PX4的姿态控制部分使用的是roll-pitch和yaw分开控制的(是为了解耦控制行为),即tilt和torsion两个环节。感性认识一下,如下图:
 
        根据经验所得,控制toll-pitch比控制yaw更容易实现。比如同样是实现10°的变化,roll-pitch需要60ms左右;但是yaw控制器却需要接近150ms。(上面两幅图是出自DJI某哥写的论文里面,仅作参考,结合理解Pixhawk)
    控制流程:
        1)、预处理:各参数的初始化。
        2)、稳定roll-pitch的角速度。
        3)、稳定roll-pitch的角度。
        4)、稳定yaw的角速度。
        5)、稳定yaw的角度。
        其中在第五步中有一个yaw的前馈控制(MC_YAW_FF):There is MC_YAW_FF parameter that controls how much of userinput need to feed forward to yaw rate controller. 0 means very slow control,controller will start to move yaw only when sees yaw position error, 1 meansvery fast responsive control, but with some overshot, controller will move yawimmediately, always keeping yaw error near zero。This parameter is not critical and can be tuned in flight, inworst case yaw responce will be sluggish or too fast. Play with FF parameter toget comfortable responce. Valid range is 0…1. Typical value is 0.8…0.9. (Foraerial video optimal value may be much smaller to get smooth responce.) Yawovershot should be not more than 2-5%。
        摘自:https://pixhawk.org/users/multirotor_pid_tuning
3、 进入姿态控制源码的前期过程
        首先感性认识一下姿态控制部分的框架,控制部分分为内外环控制,内环控制角速度、外环控制角度。控制过程是先根据目标姿态(target)和当前姿态(current)求出偏差角,然后通过角速度来修正这个偏差角,最终到达目标姿态。
 
        和姿态解算算法的流程几乎类似,主要的代码流程首先就是按照C++语言的格式引用C语言的main函数,但是在该处变成了:
extern "C" __EXPORT int mc_att_control_main(int argc, char *argv[])。
        然后跳转到所谓的main函数,该部分有个要注意的点,如下代码所示:即mc_att_control::g_control = new MulticopterAttitudeControl;//重点(934),new关键词应该不陌生吧,类似于C语言中的malloc,对变量进行内存分配的,即对姿态控制过程中使用到的变量赋初值。
   
1. int mc_att_control_main(int argc, char *argv[])  
2. {  
3.    if (argc < 2) {  
4.        warnx("usage: mc_att_control {start|stop|status}");  
5.        return 1;  
6.    }  
7.    if (!strcmp(argv[1], "start")) {  
8.        if (mc_att_control::g_control != nullptr) {  
9.            warnx("already running");  
10.            return 1;  
11.        }  
12.        mc_att_control::g_control = new MulticopterAttitudeControl;//重点  
13.        if (mc_att_control::g_control == nullptr) {  
14.            warnx("alloc failed");  
15.            return 1;  
16.        }  
17.        if (OK != mc_att_control::g_control->start()) {//跳转  
18.            delete mc_att_control::g_control;  
19.            mc_att_control::g_control = nullptr;  
20.            warnx("start failed");  
21.            return 1;  
22.        }  
23.        return 0;  
24.    }  
        然后是start函数

   
1. Int MulticopterAttitudeControl::start()  
2. {  
3.    ASSERT(_control_task == -1);  
4.    /* start the task */  
5.    _control_task = px4_task_spawn_cmd("mc_att_control",  
6.                       SCHED_DEFAULT,  
7.                       SCHED_PRIORITY_MAX - 5,  
8.                       1500,  
9. (px4_main_t)&MulticopterAttitudeControl::task_main_trampoline,  
10.                       nullptr);  
11.    if (_control_task < 0) {  
12.        warn("task start failed");  
13.        return -errno;  
14.    }  
15.    return OK;  
16. }  
        其中上面有个封装了nuttx自带的生成task的任务创建函数(他把优先级什么的做了重新的define,这么做是便于代码阅读):px4_task_spawn_cmd(),注意它的用法。其函数原型是:
   
1. px4_task_t px4_task_spawn_cmd(const char *name, int scheduler, int priority, int stack_size, px4_main_t entry,  
2.                  char *const argv[])  
        第一个参数是namespace,第二个参数是选择调度策略,第三个是任务优先级,第四个是任务的栈空间大小,第五个是任务的入口函数,最后一个一般是null。
   
1. Void  MulticopterAttitudeControl::task_main_trampoline(int argc, char *argv[])  
2. {  
3.    mc_att_control::g_control->task_main();  
4. }  
        再然终于到本体了。
   
1. Void MulticopterAttitudeControl::task_main(){}  
        比较讨厌的就是为什么要封装那么多层,应该是水平不够,还没有理解此处的用意。下面就是重点了。
五、重点
1、姿态控制源码_订阅
        姿态控制的代码比姿态解算的代码少了不少,所以接下来分析应该会比较快。
        首先还是需要通过IPC模型uORB进行订阅所需要的数据。需要注意的一个细节就是在该算法处理过程中的有效数据的用途问题,最后处理过的数据最后又被改进程自己订阅了,然后再处理,再订阅,一直处于循环状态,这就是所谓的PID反馈控制器吧,最终达到所需求的控制效果,达到控制效果以后就把一系列的控制量置0(类似于idle),该任务一直在运行,随启动脚本启动的。
   
1. /* * do subscriptions */  
2.    _v_att_sp_sub = orb_subscribe(ORB_ID(vehicle_attitude_setpoint));  
3.    _v_rates_sp_sub = orb_subscribe(ORB_ID(vehicle_rates_setpoint));  
4.    _ctrl_state_sub = orb_subscribe(ORB_ID(control_state));  
5.    _v_control_mode_sub = orb_subscribe(ORB_ID(vehicle_control_mode));  
6.    _params_sub = orb_subscribe(ORB_ID(parameter_update));  
7.    _manual_control_sp_sub = orb_subscribe(ORB_ID(manual_control_setpoint));  
8.    _armed_sub = orb_subscribe(ORB_ID(actuator_armed));  
9.    _vehicle_status_sub = orb_subscribe(ORB_ID(vehicle_status));  
10.    _motor_limits_sub = orb_subscribe(ORB_ID(multirotor_motor_limits));  
        上面这些订阅到底订阅了哪些东西呢,顾名思义,根据ORB()中的参数的名称就是知道订阅的到底用于做什么的了。这套开源代码中最优越的地方时变量的命名很好,通俗易懂。
2、 参数初始化
        紧随上面的代码就是参数数据的获取,parameters主要就是我们前期定义的感兴趣的数据,在姿态控制中的这些数据都是私有数据(private),比如roll、pitch、yaw以及与它们对应的PID参数。注意区分_params_handles和_params这两种数据结构(struct类型)。
   
1. /* initialize parameters cache */  
2.    parameters_update();  
3. 函数原型欣赏:  
4. int MulticopterAttitudeControl::parameters_update()  
5. {  
6.    float v;  
7.    /* roll gains */  
8.    param_get(_params_handles.roll_p, &v);  
9.    _params.att_p(0) = v;  
10.    param_get(_params_handles.roll_rate_p, &v);  
11.    _params.rate_p(0) = v;  
12.    param_get(_params_handles.roll_rate_i, &v);  
13.    _params.rate_i(0) = v;  
14.    param_get(_params_handles.roll_rate_d, &v);  
15.    _params.rate_d(0) = v;  
16.    param_get(_params_handles.roll_rate_ff, &v);  
17.    _params.rate_ff(0) = v;  
18.    /* pitch gains */  
19.     省略  
20.    /* yaw gains */  
21.     省略  
22.    /* angular rate limits */  
23.    param_get(_params_handles.roll_rate_max, &_params.roll_rate_max);  
24.    _params.mc_rate_max(0) = math::radians(_params.roll_rate_max);  
25.    param_get(_params_handles.pitch_rate_max, &_params.pitch_rate_max);  
26.    _params.mc_rate_max(1) = math::radians(_params.pitch_rate_max);  
27.    param_get(_params_handles.yaw_rate_max, &_params.yaw_rate_max);  
28.    _params.mc_rate_max(2) = math::radians(_params.yaw_rate_max);  
29.    /* manual rate control scale and auto mode roll/pitch rate limits */  
30.    param_get(_params_handles.acro_roll_max, &v);  
31.    _params.acro_rate_max(0) = math::radians(v);  
32.    param_get(_params_handles.acro_pitch_max, &v);  
33.    _params.acro_rate_max(1) = math::radians(v);  
34.    param_get(_params_handles.acro_yaw_max, &v);  
35.    _params.acro_rate_max(2) = math::radians(v);  
36.    /* stick deflection needed in rattitude mode to control rates not angles */  
37.    param_get(_params_handles.rattitude_thres, &_params.rattitude_thres);  
38.    _actuators_0_circuit_breaker_enabled = circuit_breaker_enabled("CBRK_RATE_CTRL", CBRK_RATE_CTRL_KEY);  
39.    return OK;  
40. }  
        重点分析一下上述代码:其中param_get()函数比较重要,特别是内部使用的lock和unlock的使用(主要就是通过sem信号量控制对某一数据的互斥访问)。

1. Int param_get(param_t param, void *val)  
2. {  
3.    int result = -1;  
4.    param_lock();  
5.    const void *v = param_get_value_ptr(param);  
6.    if (val != NULL) {  
7.        memcpy(val, v, param_size(param));  
8.        result = 0;  
9.    }  
10.    param_unlock();  
11.    return result;  
12. }  
        上述使用的*lock和*unlock通过sem实现互斥访问(进临界区),源码如下。
   
1. /** lock the parameter store */  
2. static void param_lock(void)  
3. {  
4.    //do {} while (px4_sem_wait(¶m_sem) != 0);  
5. }  
6. /** unlock the parameter store */  
7. static void param_unlock(void)  
8. {  
9.    //px4_sem_post(¶m_sem);  
10. }  
        上面是开源代码中的,代码里面把lock和unlock函数都写成空函数了,那还有屁用啊。应该是由于程序开发和版本控制不是一个人,有的程序开发到一半人走了,搞版本控制的,又找不到新的人来进行开发,搁置了忘记修改回来了吧;再或者别的什么意图。
        经过上述分析,该parameters_update()函数主要就是获取roll、pitch、yaw的PID参数的。并对三种飞行模式(stablize、auto、acro)下的最大姿态速度做了限制。
3、NuttX任务使能
   
1. /* wakeup source: vehicle attitude */  
2. px4_pollfd_struct_t fds[1];  
3. fds[0].fd = _ctrl_state_sub;  
4. fds[0].events = POLLIN;  
        注意上面的fd的赋值。随后进入任务的循环函数:while (!_task_should_exit){}。都是一样的模式,在姿态解算时也是使用的该种方式。
4、阻塞等待数据
   
1. /* wait for up to 100ms for data */  
2.    int pret = px4_poll(&fds[0], (sizeof(fds) / sizeof(fds[0])), 100);  
3.    /* timed out - periodic check for _task_should_exit */  
4.    if (pret == 0) {  
5.        continue;  
6.    }  
7.    /* this is undesirable but not much we can do - might want to flag unhappy status */  
8.    if (pret < 0) {  
9.        warn("mc att ctrl: poll error %d, %d", pret, errno);  
10.        /* sleep a bit before next try */  
11.        usleep(100000);  
12.        continue;  
13.    }  
14.    perf_begin(_loop_perf);  
        首先是px4_poll()配置阻塞时间100ms(uORB模型的函数API)。然后是打开MAVLINK协议,记录数据。如果poll失败,直接使用关键词continue从头开始运行(注意while和continue的组合使用)。其中的usleep(10000)函数属于线程级睡眠函数,使当前线程挂起。原文解释为:
        “Theusleep() function will cause the calling thread to be suspended from executionuntil either the number of real-time microseconds specified by the argument'usec' has elapsed or a signal is delivered to the calling thread。”
    上面最后一个perf_begin(_loop_perf),是一个空函数,带perf开头的都是空函数,它的作用主要是“Empty function calls forroscompatibility”。
5、重点来了(获取当前姿态Current)
        终于到了姿态控制器了,兴奋不?别只顾着兴奋了,好好理解一下。尤其是下面的几个*poll函数,特别重要,后期算法中的很多数据都是通过这个几个*poll()函数获取的,也是uORB模型,不理解这个后去会很晕的,别说没提醒啊;代码中没有一点冗余的部分,每一个函数、每一行都是其意义所在。

1. /* run controller on attitude changes */  
2.    if (fds[0].revents & POLLIN) {  
3.        static uint64_t last_run = 0;  
4.        float dt = (hrt_absolute_time() - last_run) / 1000000.0f;  
5.        last_run = hrt_absolute_time();  
6.        /* guard against too small (<2ms) and too large (>20ms) dt's */  
7.        if (dt < 0.002f) {  
8.            dt = 0.002f;  
9.        } else if (dt > 0.02f) {  
10.            dt = 0.02f;  
11.        }  
12.        /* copy attitude and control state topics *///获取当前姿态数据  
13.        orb_copy(ORB_ID(control_state), _ctrl_state_sub, &_ctrl_state);  
14.        /* check for updates in other topics */  
15.        parameter_update_poll();  
16.        vehicle_control_mode_poll();  
17.        arming_status_poll();  
18.        vehicle_manual_poll();  
19.        vehicle_status_poll();  
20.        vehicle_motor_limits_poll();  
         注意上面的revents,要与events区分开来,两者的区别如下:
    pollevent_t events;  /* The input event flags */
    pollevent_t revents; /* The output event flags */
        首先就是判断姿态控制器的控制任务是否已经使能,然后就是检测通过hrt获取时间精度的所需时间,并且约束在2ms至20ms以内。完了,orb_copy()函数怎么用的忘记了。。。。

1. /**  
2. * Fetch data from a topic.  
3. * This is the only operation that will reset the internal marker that  
4. * indicates that a topic has been updated for a subscriber. Once poll  
5. * or check return indicating that an updaet is available, this call  
6. * must be used to update the subscription.  
7. * @param meta    The uORB metadata (usually from the ORB_ID() macro)  
8. *      for the topic.  
9. * @param handle  A handle returned from orb_subscribe.  
10. * @param buffer  Pointer to the buffer receiving the data, or NULL  
11. *      if the caller wants to clear the updated flag without  
12. *      using the data.  
13. * @return    OK on success, ERROR otherwise with errno set accordingly.  
14. */  
15. int  orb_copy(const struct orb_metadata *meta, int handle, void *buffer)  
16. {  
17.    return uORB::Manager::get_instance()->orb_copy(meta, handle, buffer);  
18. }  
        第三个参数就是为了保存通过orb_subscribe()函数订阅获得的有效数据,该部分获取的是_ctrl_state,即控制姿态的数据,数据结构如下:(包含三轴加速度、三轴速度、三轴位置、空速、四元数、roll/pitch/yaw的速率)。记住这个copy的内容,后面会用到多次。
        然后就是检测数据是否已经更新,举一例说明问题。

1. /* check for updates in other topics */  
2. parameter_update_poll();  
3. vehicle_status_poll();//注意这个,后面会用到内部的数据处理结果,即发布和订阅的ID问题。  
        函数原型:
   
1. Void MulticopterAttitudeControl::parameter_update_poll()  
2. {  
3.    bool updated;  
4.    /* Check if parameters have changed */  
5.    orb_check(_params_sub, &updated);  
6.    if (updated) {  
7.        struct parameter_update_s param_update;  
8.        orb_copy(ORB_ID(parameter_update), _params_sub, ¶m_update);  
9.        parameters_update();  
10.    }  
11. }  
        然后飞行模式判断是否是MAIN_STATE_RATTITUD模式,该模式是一种新的飞行模式,只控制角速度,不控制角度,俗称半自稳模式(小舵量自稳大舵量手动),主要用在setpoint中,航点飞行。根据介绍,这个模式只有在pitch和roll都设置为Rattitude模式时才有意义,如果yaw也设置了该模式,那么就会自动被手动模式替代了。所以代码中只做了x、y阈值的检测。官方介绍:
RATTITUDE The pilot's inputs are passed as roll, pitch, and yaw rate commands to the autopilot if they are greater than the mode's threshold. If not the inputs are passed as roll and pitch angle commands and a yaw rate command. Throttle is passed directly to the output mixer.
   
1. /* Check if we are in rattitude(新的飞行模式,角速度模式,没有角度控制) mode and the pilot is above the threshold on pitch or roll (yaw can rotate 360 in normal att control).  If both are true don't  even bother running the attitude controllers */  
2. if(_vehicle_status.main_state == vehicle_status_s::MAIN_STATE_RATTITUDE){  
3.        if (fabsf(_manual_control_sp.y) > _params.rattitude_thres ||  
4.        fabsf(_manual_control_sp.x) > _params.rattitude_thres){  
5.        _v_control_mode.flag_control_attitude_enabled = false;  
6.            }  
7.        }  
6、姿态控制(这才是重点)
        确定飞行模式以后,根据前面的代码分析,在确定了飞行模式以后(判断当前飞行模式,通过最开始部分的*poll函数获取,),再进行姿态控制。先来代码,然后详细分析。
   
1. if (_v_control_mode.flag_control_attitude_enabled)  
2. {  
3.            control_attitude(dt);  
4.                /* publish attitude rates setpoint */  
5.                _v_rates_sp.roll = _rates_sp(0);  
6.                _v_rates_sp.pitch = _rates_sp(1);  
7.                _v_rates_sp.yaw = _rates_sp(2);  
8.                _v_rates_sp.thrust = _thrust_sp;  
9.                _v_rates_sp.timestamp = hrt_absolute_time();  
10.                if (_v_rates_sp_pub != nullptr) {  
11.                    orb_publish(_rates_sp_id, _v_rates_sp_pub, &_v_rates_sp);  
12.  
13.                } else if (_rates_sp_id) {  
14.                    _v_rates_sp_pub = orb_advertise(_rates_sp_id, &_v_rates_sp);  
15.                }  
16.            //}  
17.        } else {  
18.            /* attitude controller disabled, poll rates setpoint topic */  
19.            if (_v_control_mode.flag_control_manual_enabled) {  
20.                /* manual rates control - ACRO mode */  
21.                _rates_sp = math::Vector<3>(_manual_control_sp.y, -_manual_control_sp.x, _manual_control_sp.r).emult(_params.acro_rate_max);  
22.                _thrust_sp = math::min(_manual_control_sp.z, MANUAL_THROTTLE_MAX_MULTICOPTER);  
23.  
24.                /* publish attitude rates setpoint */  
25.                _v_rates_sp.roll = _rates_sp(0);  
26.                _v_rates_sp.pitch = _rates_sp(1);  
27.                _v_rates_sp.yaw = _rates_sp(2);  
28.                _v_rates_sp.thrust = _thrust_sp;  
29.                _v_rates_sp.timestamp = hrt_absolute_time();  
30.                if (_v_rates_sp_pub != nullptr) {  
31.                    orb_publish(_rates_sp_id, _v_rates_sp_pub, &_v_rates_sp);  
32.                } else if (_rates_sp_id) {  
33.                    _v_rates_sp_pub = orb_advertise(_rates_sp_id, &_v_rates_sp);  
34.                }  
35.            } else {  
36.                /* attitude controller disabled, poll rates setpoint topic */  
37.                vehicle_rates_setpoint_poll();  
38.                _rates_sp(0) = _v_rates_sp.roll;  
39.                _rates_sp(1) = _v_rates_sp.pitch;  
40.                _rates_sp(2) = _v_rates_sp.yaw;  
41.                _thrust_sp = _v_rates_sp.thrust;  
42.            }  
43.        }  
        上面的代码中,初始就是control_attitude(dt),控制数据都是由它来获取的。该函数内部做了很多的处理,控制理论基本都是在这个里面体现的,所以需要深入研究理解它才可以进一步的研究后续的算法。它的内部会通过算法处理获得控制量(目标姿态Target),即_rates_sp,一个vector<3>变量,以便后续控制使用。好了,进入正题。
        首先是姿态控制(control_attitude),然后是速度控制(control_attitude_rates),一个个来。
6.1、control_attitude()函数(角度控制环)
        获取目标姿态Target
   
1. /**  
2. * Attitude controller.  
3. * Input: 'vehicle_attitude_setpoint' topics (depending on mode)  
4. * Output: '_rates_sp' vector, '_thrust_sp'  
5. */  
6. Void MulticopterAttitudeControl::control_attitude(float dt)  
7. {  
8.    vehicle_attitude_setpoint_poll();  
9.    _thrust_sp = _v_att_sp.thrust;  
10.    /* construct attitude setpoint rotation matrix */  
11.    math::Matrix<3, 3> R_sp;  
12.    R_sp.set(_v_att_sp.R_body);  
13.    /* get current rotation matrix from control state quaternions */  
14.    math::Quaternion q_att(_ctrl_state.q[0], _ctrl_state.q[1], _ctrl_state.q[2], _ctrl_state.q[3]);  
15.    math::Matrix<3, 3> R = q_att.to_dcm();  
16.    /* all input data is ready, run controller itself */  
17.    /* try to move thrust vector shortest way, because yaw response is slower than roll/pitch 约两倍*/   
18.    math::Vector<3> R_z(R(0, 2), R(1, 2), R(2, 2));  
19.    math::Vector<3> R_sp_z(R_sp(0, 2), R_sp(1, 2), R_sp(2, 2));  
20.    /* axis and sin(angle) of desired rotation */  
21.    math::Vector<3> e_R = R.transposed() * (R_z % R_sp_z);  
22.    /* calculate angle error */  
23.    float e_R_z_sin = e_R.length();  
24.    float e_R_z_cos = R_z * R_sp_z;  
25.    /* calculate weight for yaw control */  
26.    float yaw_w = R_sp(2, 2) * R_sp(2, 2);  
27.    /* calculate rotation matrix after roll/pitch only rotation */  
28.    math::Matrix<3, 3> R_rp;  
29.    if (e_R_z_sin > 0.0f) {  
30.        /* get axis-angle representation */  
31.        float e_R_z_angle = atan2f(e_R_z_sin, e_R_z_cos);  
32.        math::Vector<3> e_R_z_axis = e_R / e_R_z_sin;  
33.        e_R = e_R_z_axis * e_R_z_angle;  
34.        /* cross product matrix for e_R_axis */  
35.        math::Matrix<3, 3> e_R_cp;  
36.        e_R_cp.zero();  
37.        e_R_cp(0, 1) = -e_R_z_axis(2);  
38.        e_R_cp(0, 2) = e_R_z_axis(1);  
39.        e_R_cp(1, 0) = e_R_z_axis(2);  
40.        e_R_cp(1, 2) = -e_R_z_axis(0);  
41.        e_R_cp(2, 0) = -e_R_z_axis(1);  
42.        e_R_cp(2, 1) = e_R_z_axis(0);  
43.        /* rotation matrix for roll/pitch only rotation */  
44.        R_rp = R * (_I + e_R_cp * e_R_z_sin + e_R_cp * e_R_cp * (1.0f - e_R_z_cos));  
45.    } else {  
46.        /* zero roll/pitch rotation */  
47.        R_rp = R;  
48.    }  
49.    /* R_rp and R_sp has the same Z axis, calculate yaw error */  
50.    math::Vector<3> R_sp_x(R_sp(0, 0), R_sp(1, 0), R_sp(2, 0));  
51.    math::Vector<3> R_rp_x(R_rp(0, 0), R_rp(1, 0), R_rp(2, 0));  
52.    e_R(2) = atan2f((R_rp_x % R_sp_x) * R_sp_z, R_rp_x * R_sp_x) * yaw_w;  
53.    if (e_R_z_cos < 0.0f) {  
54.        /* for large thrust vector rotations use another rotation method:  
55.         * calculate angle and axis for R -> R_sp rotation directly */  
56.        math::Quaternion q;  
57.        q.from_dcm(R.transposed() * R_sp);  
58.        math::Vector<3> e_R_d = q.imag();  
59.        e_R_d.normalize();  
60.        e_R_d *= 2.0f * atan2f(e_R_d.length(), q(0));  
61.        /* use fusion of Z axis based rotation and direct rotation */  
62.        float direct_w = e_R_z_cos * e_R_z_cos * yaw_w;  
63.        e_R = e_R * (1.0f - direct_w) + e_R_d * direct_w;  
64.    }  
65.    /* calculate angular rates setpoint */  
66.    _rates_sp = _params.att_p.emult(e_R);  
67.    /* limit rates */  
68.    for (int i = 0; i < 3; i++) {  
69.        _rates_sp(i) = math::constrain(_rates_sp(i), -_params.mc_rate_max(i), _params.mc_rate_max(i));  
70.    }  
71.    /* feed forward yaw setpoint rate */  
72.    _rates_sp(2) += _v_att_sp.yaw_sp_move_rate * yaw_w * _params.yaw_ff;  
73. }  
         详细分析:首先就是通过uORB模型检测姿态数据是否已经更新。检测到更新数据以后,把数据拷贝到当前,并通过_thrust_sp = _v_att_sp.thrust把油门控制量赋值给控制变量。
        然后构建姿态旋转矩阵(目标状态,所谓的TargetRotation)。

1.    /* construct attitude setpoint rotation matrix */  
2.    math::Matrix<3,3> R_sp;  
3. R_sp.set(_v_att_sp.R_body);//不在赘述,在姿态解算时使用了同样的方法  
        然后通过控制四元数获取当前状态的旋转矩阵DCM,后面在计算误差以后旋转到b系时使用到了该处的DCM。即由姿态解算得到的有效姿态信息。
   
1. /* get current rotation matrix from control state quaternions */  
2.    math::Quaternion q_att(_ctrl_state.q[0], _ctrl_state.q[1], _ctrl_state.q[2], _ctrl_state.q[3]);  
3.    math::Matrix<3, 3> R = q_att.to_dcm();  
4.    通过math库构建四元数;获取DCM的函数原型:无可厚非,都懂的  
5.    /*** create rotation matrix for the quaternion */  
6.    Matrix<3, 3> to_dcm(void) const {  
7.        Matrix<3, 3> R;  
8.        float aSq = data[0] * data[0];  
9.        float bSq = data[1] * data[1];  
10.        float cSq = data[2] * data[2];  
11.        float dSq = data[3] * data[3];  
12.        R.data[0][0] = aSq + bSq - cSq - dSq;  
13.        R.data[0][1] = 2.0f * (data[1] * data[2] - data[0] * data[3]);  
14.        R.data[0][2] = 2.0f * (data[0] * data[2] + data[1] * data[3]);  
15.        R.data[1][0] = 2.0f * (data[1] * data[2] + data[0] * data[3]);  
16.        R.data[1][1] = aSq - bSq + cSq - dSq;  
17.        R.data[1][2] = 2.0f * (data[2] * data[3] - data[0] * data[1]);  
18.        R.data[2][0] = 2.0f * (data[1] * data[3] - data[0] * data[2]);  
19.        R.data[2][1] = 2.0f * (data[0] * data[1] + data[2] * data[3]);  
20.        R.data[2][2] = aSq - bSq - cSq + dSq;  
21.        return R;  
22.    }  
23. };  
        然后取两个矩阵中的Z轴向量,即YAW-axis。

1. /* all input data is ready, run controller itself */  
2.    /* try to move thrust vector shortest way, because yaw response is slower than roll/pitch 这个地方应该知道旋转按照ZYX来进行的*/  
3.    math::Vector<3> R_z(R(0, 2), R(1, 2), R(2, 2));  
4.    math::Vector<3> R_sp_z(R_sp(0, 2), R_sp(1, 2), R_sp(2, 2));  
        然后当前姿态的z轴和目标姿态的z轴的误差大小(即需要旋转的角度)并旋转到b系(即先对齐Z轴)。
   
1. /* axis and sin(angle) of desired rotation */  
2.    math::Vector<3> e_R = R.transposed() * (R_z % R_sp_z);  
        R_z%R_sp_z叉积,还记得这个么?在mahony算法中已经出现过一次了,就是求取误差的,本来应该z轴相互重合的,如果不是0就作为误差项。然后再左乘旋转矩阵旋转到b系。
        转置源码:
   
1. Matrix3<T> Matrix3<T>::transposed(void) const  
2. {  
3.    return Matrix3<T>(Vector3<T>(a.x, b.x, c.x),  
4.                      Vector3<T>(a.y, b.y, c.y),  
5.                      Vector3<T>(a.z, b.z, c.z));  
6. }  
        然后计算姿态角度误差(姿态误差),一个数学知识背景:由公式a×b=︱a︱︱b︱sinθ,a•b=︱a︱︱b︱cosθ,这里的R_z和R_sp_z都是单位向量,模值为1,因此误差向量e_R(a×b叉积就是误差)的模就是sinθ,点积就是cosθ。
   
1. /* calculate angle error */  
2.    float e_R_z_sin = e_R.length();  
3. float e_R_z_cos = R_z * R_sp_z;  
       计算yaw的权重(不懂,谁帮忙解释一下原因。。跪谢)
   
1.    /* calculate weight for yaw control */  
2.    float yaw_w = R_sp(2, 2) * R_sp(2, 2);//不懂  
3. 第一行的这个权重纯粹是因为如果不转动roll-pitch的话那应该是1,而如果转动的话,那个权重会平方倍衰减 (来自MR的解释)。  
        因为多轴的yaw响应一般比roll/pitch慢了接近一倍,因此将两者解耦(需要理解解耦的目的),先补偿roll-pitch的变化,计算R_rp。
   
1. /* calculate rotation matrix after roll/pitch only rotation */  
2.    math::Matrix<3, 3> R_rp;  
3.    if (e_R_z_sin > 0.0f) {  
4.        /* get axis-angle representation */  
5.        float e_R_z_angle = atan2f(e_R_z_sin, e_R_z_cos);  
6.        math::Vector<3> e_R_z_axis = e_R / e_R_z_sin;  
7.        e_R = e_R_z_axis * e_R_z_angle;//很大的用途,下面的R_rp求取公式就是利用欧拉角计算的。  
8.        /* cross product matrix for e_R_axis */  
9.        math::Matrix<3, 3> e_R_cp;  
10.        e_R_cp.zero();  
11.        e_R_cp(0, 1) = -e_R_z_axis(2);  
12.        e_R_cp(0, 2) = e_R_z_axis(1);  
13.        e_R_cp(1, 0) = e_R_z_axis(2);  
14.        e_R_cp(1, 2) = -e_R_z_axis(0);  
15.        e_R_cp(2, 0) = -e_R_z_axis(1);  
16.        e_R_cp(2, 1) = e_R_z_axis(0);  
17.        /* rotation matrix for roll/pitch only rotation */  
18.        R_rp = R * (_I + e_R_cp * e_R_z_sin + e_R_cp * e_R_cp * (1.0f - e_R_z_cos));//罗德里格旋转公式:Rodrigues rotation formula  
19.    } else {  
20.        /* zero roll/pitch rotation */  
21.        R_rp = R;  
22.    }  
        首先需要明确的就是上述处理过程中的DCM量都是通过欧拉角来表示的,这个主要就是考虑在控制时需要明确具体的欧拉角的大小,还有就是算法的解算过程是通过矩阵微分方程推导得到的(参考《惯性技术_邓正隆》_P148-P152以及《惯性导航_秦永元》_P342),并且在《惯性技术_邓正隆》_P154页介绍了姿态矩阵的实时解算方法。再判断两个z轴是否存在误差(e_R_z_sin> 0.0f),若存在误差则通过反正切求出该误差角度值(atan2f(e_R_z_sin,e_R_z_cos));然后归一化e_R_z_axis(e_R /e_R_z_sin该步计算主要就是利用e_R_z_sin=e_R.length(),往上看就是了,不会这么快就忘记了吧?!)。然后就是e_R =e_R_z_axis* e_R_z_angle了(主要就是为了误差向量用角度量表示)。
 
        然后计算yaw的误差,该误差是roll_pitch获取的z轴和目标姿态的z轴的误差。
   
1. /* R_rp and R_sp has the same Z axis, calculate yaw error */  
2.    math::Vector<3> R_sp_x(R_sp(0, 0), R_sp(1, 0), R_sp(2, 0));  
3.    math::Vector<3> R_rp_x(R_rp(0, 0), R_rp(1, 0), R_rp(2, 0));  
4.    e_R(2) = atan2f((R_rp_x % R_sp_x) * R_sp_z, R_rp_x * R_sp_x) * yaw_w;  
        该部分同样是根据向量的叉积和点积求出误差角度的正弦和余弦,再反正切求出角度(又忘记了?回头看吧)。
        上面介绍的是在小角度变化时,如果是大角度变化时(大于90°,可能性比较小,还是集中在上面的算法吧)使用如何方法处理。
   
1. if (e_R_z_cos < 0.0f) {  
2.        /* for large thrust vector rotations use another rotation method:  
3.         * calculate angle and axis for R->R_sp rotation directly */  
4.        math::Quaternion q;  
5.        q.from_dcm(R.transposed() * R_sp);  
6.        math::Vector<3> e_R_d = q.imag();  
7.        e_R_d.normalize();  
8.        e_R_d *= 2.0f * atan2f(e_R_d.length(), q(0));//不懂  
9.        /* use fusion of Z axis based rotation and direct rotation */  
10.        float direct_w = e_R_z_cos * e_R_z_cos * yaw_w;  
11.        e_R = e_R * (1.0f - direct_w) + e_R_d * direct_w;  
12.    }  
        上面这段代码比较好理解,主要就是由DCM获取四元数;然后把四元数的虚部取出赋值给e_R_d(e_R_d = q.imag());然后对其进行归一化处理;最后2行是先求出互补系数,再通过互补方式求取e_R。
        然后计算角速度变化的大小,并对其进行约束(constrain)。
   
1. /* calculate angular rates setpoint */  
2.    _rates_sp = _params.att_p.emult(e_R);  
3.    /* limit rates */  
4.    for (int i = 0; i < 3; i++) {  
5.        _rates_sp(i) = math::constrain(_rates_sp(i), -_params.mc_rate_max(i), _params.mc_rate_max(i));  
6.    }  
7.    /* feed forward yaw setpoint rate 因为yaw响应较慢,再加入一个前馈控制*/  
8. _rates_sp(2) += _v_att_sp.yaw_sp_move_rate * yaw_w * _params.yaw_ff;  
9. 上述代码中的一个emult(e_R)的函数原型:  
10.    Matrix<Type, M, N> emult(const Matrix<Type, M, N> &other) const  
11.    {  
12.        Matrix<Type, M, N> res;  
13.        const Matrix<Type, M, N> &self = *this;  
14.        for (size_t i = 0; i < M; i++) {  
15.            for (size_t j = 0; j < N; j++) {  
16.                res(i , j) = self(i, j)*other(i, j);  
17.            }  
18.        }  
19.        return res;  
20. }  
        所以_rates_sp = _params.att_p.emult(e_R)这句话的意思就是用att_p的每一个元素和e_R中对应位置的每一个元素相乘,结果赋值给_rates_sp角速度变量(该死的C++)。
6.2、control_attitude(dt)返回以后
   
1. /* publish attitude rates setpoint */  
2.    _v_rates_sp.roll = _rates_sp(0);  
3.    _v_rates_sp.pitch = _rates_sp(1);  
4.    _v_rates_sp.yaw = _rates_sp(2);  
5.    _v_rates_sp.thrust = _thrust_sp;  
6.    _v_rates_sp.timestamp = hrt_absolute_time();  
7.    if (_v_rates_sp_pub != nullptr) {  
8.        orb_publish(_rates_sp_id, _v_rates_sp_pub, &_v_rates_sp);  
9.        } else if (_rates_sp_id) {  
10.           _v_rates_sp_pub = orb_advertise(_rates_sp_id, &_v_rates_sp);  
11.                    }  
        上面这部分代码就通过control_attitude(dt)经过一系列的算法处理过以后获取的目标内环角速度值,并通过uORB模型发布出去,包含roll-pitch-yaw、油门量和时间戳。
        该处正好可以再次深入理解一下uORB模型的一些理论。上述代码涉及了orb_publish()和orb_advertise()两个函数接口,通常第一次发布有效数据之前需要使用orb_advertise()函数进行广播(类似topic register),它发布成功以后会返回一个handle供orb_publish()发布时使用,即广播之后可以使用orb_publish()进行发布新的数据。orb_advertise()发布函数有第一个参数类似ID,返回值作为handle以便区分再次使用orb_publish()时发布的是何种消息数据,即再次说明orb_publish()需要在orb_advertise()函数接口之后使用。通过查看orb_advertise()函数的代码原型可以了解到,该函数的作用就类似于把需要后续发布的主题(topic)注册一下,然后才可以进行orb_publish()。
        现在最不明了的就是这个数据发布出去以后在哪订阅了该数据呢或者说给谁用呢???自己发布,自己订阅,生生不息息,PX4里面有很多都是自己发布然后再自己订阅的,感谢群友我是肉包子的帮助。细节说明:在task_main()的开头处就是订阅各种topics,其中就有一个_v_rates_sp_sub = orb_subscribe(ORB_ID(vehicle_rates_setpoint))订阅过程(735_linenumber),它就是在该算法执行到最后时发布的控制量数据“_v_rates_sp”(822),也就是按照前讲述的理论,自己订阅自己发布的topic,以实现循环控制。其中需要注意的就是发布时用的ID和订阅时用的不一致所迷惑了,其实它俩是一样的;因为在上述处理过程中是把ORB_ID(vehicle_rates_setpoint)赋值给_rates_sp_id它的(567),赋值过程在发布topic之前,即在vehicle_status_poll()函数内部(794)。
        前面的算法都是在flag_control_attitude_enabled非零(姿态控制)的情况下实现的。紧接着,是在flag_control_attitude_enabled为零时,即转变为flag_control_manual_enabled:手动控制,方法类似,不在赘述。再接着,连手动控制都为使能时,只能poll了,并把控制量都置0。
        姿态控制结束。
        姿态速度控制开始。
7、姿态速度控制(角速度环)
        代码来也,先感性认识~~~~~~

1. if (_v_control_mode.flag_control_rates_enabled) {  
2.    control_attitude_rates(dt);  
3.    /* publish actuator controls */  
4.    _actuators.control[0] = (PX4_ISFINITE(_att_control(0))) ? _att_control(0) : 0.0f;  
5.    _actuators.control[1] = (PX4_ISFINITE(_att_control(1))) ? _att_control(1) : 0.0f;  
6.    _actuators.control[2] = (PX4_ISFINITE(_att_control(2))) ? _att_control(2) : 0.0f;  
7.    _actuators.control[3] = (PX4_ISFINITE(_thrust_sp)) ? _thrust_sp : 0.0f;  
8.    _actuators.timestamp = hrt_absolute_time();  
9.    _actuators.timestamp_sample = _ctrl_state.timestamp;  
10.    _controller_status.roll_rate_integ = _rates_int(0);  
11.    _controller_status.pitch_rate_integ = _rates_int(1);  
12.    _controller_status.yaw_rate_integ = _rates_int(2);  
13.    _controller_status.timestamp = hrt_absolute_time();  
14.    if (!_actuators_0_circuit_breaker_enabled) {  
15.        if (_actuators_0_pub != nullptr) {  
16.            orb_publish(_actuators_id, _actuators_0_pub, &_actuators);  
17.            perf_end(_controller_latency_perf);  
18.        } else if (_actuators_id) {  
19.          _actuators_0_pub = orb_advertise(_actuators_id, &_actuators);  
20.                    }  
21.                }  
22.    /* publish controller status */  
23.    if(_controller_status_pub != nullptr) {  
24.    orb_publish(ORB_ID(mc_att_ctrl_status),_controller_status_pub, &_controller_status);  
25.    } else {  
26.    _controller_status_pub = orb_advertise(ORB_ID(mc_att_ctrl_status), &_controller_status);  
27.                }  
28.            }  
        进入上述代码首先就是control_attitude_rates(dt),该函数的输入是前面算法处理得到的_rates_sp控制量(目标姿态),输出是_att_control控制量。其函数原型是:
   
1. Void MulticopterAttitudeControl::control_attitude_rates(float dt)  
2. {  
3.    /* reset integral if disarmed */  
4.    if (!_armed.armed || !_vehicle_status.is_rotary_wing) {  
5.        _rates_int.zero();  
6.    }  
7.    /* current body angular rates */  
8.    math::Vector<3> rates;  
9.    rates(0) = _ctrl_state.roll_rate;  
10.    rates(1) = _ctrl_state.pitch_rate;  
11.    rates(2) = _ctrl_state.yaw_rate;  
12.    /* angular rates error */  
13.    math::Vector<3> rates_err = _rates_sp - rates;//目标姿态-当前姿态  
14.    _att_control = _params.rate_p.emult(rates_err) + _params.rate_d.emult(_rates_prev - rates) / dt + _rates_int + _params.rate_ff.emult(_rates_sp - _rates_sp_prev) / dt;  
15.    _rates_sp_prev = _rates_sp;  
16.    _rates_prev = rates;  
17.    /* update integral only if not saturated on low limit and if motor commands are not saturated */  
18.    if (_thrust_sp > MIN_TAKEOFF_THRUST && !_motor_limits.lower_limit && !_motor_limits.upper_limit ) {  
19.        for (int i = 0; i < 3; i++) {  
20.            if (fabsf(_att_control(i)) < _thrust_sp) {  
21.                float rate_i = _rates_int(i) + _params.rate_i(i) * rates_err(i) * dt;  
22.                if (PX4_ISFINITE(rate_i) && rate_i > -RATES_I_LIMIT && rate_i < RATES_I_LIMIT &&  
23.                    _att_control(i) > -RATES_I_LIMIT && _att_control(i) < RATES_I_LIMIT) {  
24.                    _rates_int(i) = rate_i;  
25.                }  
26.            }  
27.        }  
28.    }  
29. }  
        主要就是通过_ctrl_state数据结构(前面说过要记住它的吧,当前姿态信息)把需要的有效数据赋值给rates,然后通过rates进行一系列的算法处理。该过程中最最最需要注意的就是这个_ctrl_state变量的获取过程,其实还是通过uORB。前面也涉及过多次,比如control_attitude()函数内部使用它构造状态四元数。
        如下非常重要。。。。。。打通姿态解算和姿态控制部分。
        数据获取过程:
        Quaterion_CF姿态解算算法:(需要对代码有个整体把握,不然会很晕啊,还有就是关于姿态解算部分使用的CF时,在PX4Firmware/src/module/attitude_estimator_q中)。首先是通过姿态解算部分获取当前的姿态信息(Quaterion_CF),获取之后通过uORB模型发布:
   
1. /* publish to control state topic */(646)  
2. orb_publish_auto(ORB_ID(control_state), &_ctrl_state_pub, &ctrl_state, &ctrl_inst, ORB_PRIO_HIGH);  
        Ekf2姿态解算算法:(还是需要对代码有个整体把握,不然还是会很晕啊,还有就是关于姿态解算部分使用的ekf2时,在PX4Firmware/src/module/ekf2中)。首先是通过姿态解算部分获取当前的姿态信息(ekf2),获取之后通过uORB模型发布:
   
1. // publish control state data(475)  
2. if (_control_state_pub == nullptr) {  
3.    _control_state_pub = orb_advertise(ORB_ID(control_state), &ctrl_state);  
4. } else {  
5.    orb_publish(ORB_ID(control_state), _control_state_pub, &ctrl_state);  
6.    }  
        关于到底使用哪种解算算法在启动脚本rc_mc_app里面涉及了关于姿态解算用什么算法的问题,里面给了一个宏,通过宏定义选取的。而且在使用四元数的互补算法和ekf2的算法里面都对结算到的姿态信息进行了发布处理,以便供姿态控制时订阅使用。
        然后再姿态控制中通过uORB模型订阅:

1. _ctrl_state_sub = orb_subscribe(ORB_ID(control_state));(736)  
2. orb_copy(ORB_ID(control_state), _ctrl_state_sub, &_ctrl_state);(787)  
        再然后就是姿态控制量(_att_control)的获取:获取原则是由预期姿态控制获取的角速度值与通过uORB获得的角速度值做差(该部分差值代表error=target-current,_ctrl_state应该是要控制的控制量)。rates_err的获取就是通过经典的PD控制器了,然后再加个前馈。还未使用I控制器;在后面会单独使用。

1. /* angular rates error */  
2. math::Vector<3> rates_err = _rates_sp - rates;  
3. _att_control = _params.rate_p.emult(rates_err) + _params.rate_d.emult(_rates_prev - rates) / dt + _rates_int + _params.rate_ff.emult(_rates_sp - _rates_sp_prev) / dt;  
4. _rates_sp_prev = _rates_sp;  
5. _rates_prev = rates;  
6. I控制器的使用(注意使用条件)。  
7.    /* update integral only if not saturated on low limit and if motor commands are not saturated */  
8.    if (_thrust_sp > MIN_TAKEOFF_THRUST && !_motor_limits.lower_limit && !_motor_limits.upper_limit ) {  
9.        for (int i = 0; i < 3; i++) {  
10.            if (fabsf(_att_control(i)) < _thrust_sp) {  
11.                float rate_i = _rates_int(i) + _params.rate_i(i) * rates_err(i) * dt;  
12.                if (PX4_ISFINITE(rate_i) && rate_i > -RATES_I_LIMIT && rate_i < RATES_I_LIMIT &&  
13.                    _att_control(i) > -RATES_I_LIMIT && _att_control(i) < RATES_I_LIMIT) {  
14.                    _rates_int(i) = rate_i;  
15.                }  
16.            }  
17.        }  
18.    }  
        其中fabsf()的函数原型是(取绝对值):
   
1. float fabsf(float x)  
2. {  
3.  return ((x < 0) ? -x : x);  
4. }  
    常用的几种取绝对值的函数:
        int abs(int i);         //处理int类型的取绝对值
        double fabs(double i); //处理double类型的取绝对值
        float fabsf(float i);  //处理float类型的取绝对值
        注意上面的fabsf(_att_control(i)) <_thrust_sp)这个判断项,符合就执行积分。这个做主要是为了安全考虑,当roll的变化值需要很大时,就停止积分项的累加以便防止积分项产生较大的误差。
        别看这个_thrust_sp单单的一个控制量,其实它可麻烦了,不对整体核心的解算和控制(姿态解算姿态控制、位置解算位置控制)有个深入理解的话,很难看懂这部分。下面详细介绍一下这个控制量的获取过程,耐心看,别晕了。介绍还是需要正向介绍,在看的时候可以反向看,比较容易理解。
 
        首先是_v_att_sp_sub =orb_subscribe(ORB_ID(vehicle_attitude_setpoint));(813),订阅所需的控制量。
        然后再attitude control里面处理:_thrust_sp =_v_att_sp.thrust(653)
    上面是订阅拷贝和使用部分,下面就是发布部分。
        发布分为两个地方,一个是mc_pos_control和mavlink_receiver.cpp。主要考虑前者。
        ID重定义:_attitude_setpoint_id= ORB_ID(vehicle_attitude_setpoint);(595)
        正式发布给mc_att_control: orb_publish(_attitude_setpoint_id,_att_sp_pub,&_att_sp);(1932)
        为何称为正式发布呢?主要是因为在mc_pos_control里面根据不懂的模式进行了多次发布处理,比如idle状态下这个_thrust_sp就赋值为0发布出去。这个正式发布出来的才是我们飞行控制过程中需要考虑的控制量。
        补充mavlink_receiver.cpp
        orb_publish(ORB_ID(vehicle_attitude_setpoint),_att_sp_pub,&_att_sp);(951)
    现在发现这个规律了吧,任务间通信(IPC)都是靠的uORB,找不到来源就查ID吧。
8、发布控制量
   
1. /* publish actuator controls */  
2.                _actuators.control[0] = (PX4_ISFINITE(_att_control(0))) ? _att_control(0) : 0.0f;  
3.                _actuators.control[1] = (PX4_ISFINITE(_att_control(1))) ? _att_control(1) : 0.0f;  
4.                _actuators.control[2] = (PX4_ISFINITE(_att_control(2))) ? _att_control(2) : 0.0f;  
5.                _actuators.control[3] = (PX4_ISFINITE(_thrust_sp)) ? _thrust_sp : 0.0f;  
6.                _actuators.timestamp = hrt_absolute_time();  
7.                _actuators.timestamp_sample = _ctrl_state.timestamp;  
8.                _controller_status.roll_rate_integ = _rates_int(0);  
9.                _controller_status.pitch_rate_integ = _rates_int(1);  
10.                _controller_status.yaw_rate_integ = _rates_int(2);  
11.                _controller_status.timestamp = hrt_absolute_time();  
12.                if (!_actuators_0_circuit_breaker_enabled) {  
13.                    if (_actuators_0_pub != nullptr) {  
14.                        orb_publish(_actuators_id, _actuators_0_pub, &_actuators);  
15.                        perf_end(_controller_latency_perf);  
16.                    } else if (_actuators_id) {  
17.                        _actuators_0_pub = orb_advertise(_actuators_id, &_actuators);  
18.                    }  
19.                }  
20.                /* publish controller status */  
21.                if(_controller_status_pub != nullptr) {  
22.                    orb_publish(ORB_ID(mc_att_ctrl_status),_controller_status_pub, &_controller_status);  
23.                } else {  
24.                    _controller_status_pub = orb_advertise(ORB_ID(mc_att_ctrl_status), &_controller_status);  
25.                }  
26.            }  
27.        }  
28.        perf_end(_loop_perf);  
29.    }  
30.    _control_task = -1;  
31.    return;  
        PS:一个比较有趣的东西task handle:“_control_task”
        了解姿态控制任务的执行流么?可以参考这个task handle思考思考。
 
六、结论
        其实在mc_att_control里面就完全涵盖了姿态控制的内环和外环(即角速度控制、角度控制)。主要就是attitude control和attitude rate control两个部分,前者是控制角度后者是控制角速度并把控制量输入给mixer。在控制过程中是通过控制电机的速度以实现多旋翼的整体的rpy的速度,通过这个速度随时间的累加实现角度控制。
        attitude_control 输入是体轴矩阵R和期望的体轴矩阵Rsp,角度环只是一个P控制,算出来之后输出的是期望的角速度值rate_sp(这一段已经完成了所需要的角度变化,并将角度的变化值转换到了需要的角速度值)。并且把加速度值直接输出给attitude rate control,再经过角速度环的pid控制,输出值直接就给mixer,然后控制电机输出了。
        关于这些,主要还是需要理解这个控制过程:一方面是通过姿态解算部分获取的实时的姿态信息,并通过uORB模型把姿态信息发布出去;姿态控制部分订阅姿态解算得到的姿态信息。然后通过attitude control获取目标姿态和当前姿态的角度差值并经过算法处理得到对应的角速度值,并把这个角速度值输出给attitude rate control 最终获取到需求的控制量。输出给mixer。但是关于上述还是有一个迷惑的地方,就是在attitude control这个里面输出的是根据目标姿态计算的角速度值,然后再和attitude rate control 里面通过uORB获取的当前的角速度值做差得出角速度差值。。。。本身对这个比较懵逼。其实attitude control输出是需要达到这个误差角度时所需要的角速度值,用这个值与当前的角速度值做差,求出现在需要的角速度值而已。这个就是为什么控制角速度的原因,进而达到控制角度的效果。
  



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Pixhawk之姿态控制 的相关文章

  • Pixhawk之UAV控制理论、ardupilot源码框架介绍

    一 开篇 您有无人机么 xff1f 没有 那赶紧去某宝买一套 昨天 开会开到接近下午一点钟 xff0c 收获相当大 xff0c 原本不太清楚的ardupilot框架现在也大致熟悉了 xff0c 接下来主要就是结合源码了解其控制过程了 xff
  • Pixhawk之姿态解算篇(5)_ECF/EKF/GD介绍

    一 开篇 很久没更新blog了 xff0c 最近研究的东西比较杂乱 xff0c 也整理了很多东西 xff0c 没有来的及更新 xff0c 最近发现很多小伙伴都开始写blog了 xff0c 在不更新就要 被落后了 兄弟们 xff0c 等等我啊
  • pixhawk px4 添加自定义mavlink消息

    首先添加一个 uORB topic xff0c 然后添加一个 mavlink 解析程序 xff0c 这将会将一个输入的 mavlink 消息解析并传入 uORB topic 中 注 xff1a 本例程 xff0c nsh仍然看不到消息 xf
  • 【Pixhawk】Ubuntu下编译原生固件PX4

    环境 xff1a 编译主机 xff1a Ubuntu 20 0 4 xff08 官方推荐18 0 4 xff09 源码版本 xff1a PX4 1 12 0 xff08 目前最新 xff0c 截至2021 7 22 xff09 建议 尽量以
  • PX4/Pixhawk---uORB深入理解和应用(最新版)

    1 简介 ps 第1章简介是参考 uORB深入理解和应用 1 1 PX4 Pixhawk的软件体系结构 PX4 Pixhawk的软件体系结构主要被分为四个层次 xff0c 这可以让我们更好的理解PX4 Pixhawk的软件架构和运作 xff
  • Pixhawk无人机飞行模式详解 (PX4源码)

    我帮大家把飞行模式控制量与特点总结一下 xff0c 方便看代码 xff0c 如下所示 xff1a 辅助模式 Position Mode 位置模式 xff08 定点模式 xff09 横滚俯仰控制角度 xff0c 油门控制上下速度 xff0c
  • 搜一下会发现CSDN上有不少树莓派连接Pixhawk的,或者ROS连接Pixhawk的

    搜一下会发现CSDN上有不少树莓派连接Pixhawk的 xff0c 或者ROS连接Pixhawk的 xff0c 来进行相对应的实验 看来大家基本都是用Pixhawk 有很多人已经做成了 xff0c 做出来了 xff0c 所以甚至感觉没必要去
  • PIXHAWK机架类型的的设置选择与电机通道顺序设置

    1 共轴直升机 main1 左侧斜盘电机 xff0c 控制俯仰 main2 xff1a 左侧斜盘电机 xff0c 控制滚转 main3 xff1a 上面旋翼 xff0c 逆时针旋转 main4 xff1a 下面旋翼 xff0c 顺时针旋转
  • 关于pixhawk硬件IMU和compass那点事儿

    文章目录 前言一 IMU和compass是什么 xff1f 二 导航坐标系与机体坐标系三 安装IMU xff0c compasss四 hwdef中设置IMU xff0c compass朝向总结 前言 继上一篇讲解了pixhawk的硬件组成
  • pixhawk ulg转csv

    ulg是目前最新版px4固件生成的log格式 xff0c 下载最新版的flightplot即可对内部数据进行预览分析 xff0c flightplot中支持部分函数和运算符操作 xff0c 但对带 数据的操作不支持 xff0c 如需要对某些
  • Pixhawk代码分析-源码框架

    源码框架 pixhawk代码框架 xff1a pixhawk代码框架基础分析 xff1a 阅读下面内容时请结合源码阅读 xff0c 便于理解 The basic structure of ArduPilot is broken up int
  • Pixhawk之姿态控制篇

    一 开篇 姿态控制篇终于来了 来了 来了 心情爽不爽 xff1f 愉悦不愉悦 xff1f 开心不开心 xff1f 喜欢的话就请我吃顿饭吧 xff0c 哈哈 其实这篇blog一周前就应该写的 xff0c 可惜被上一篇blog霸占了 但是也不算
  • pixhawk 整体架构的认识

    此篇blog的目的是对px4工程有一个整体认识 xff0c 对各个信号的流向有个了解 xff0c 以及控制算法采用的控制框架 PX4自动驾驶仪软件 可分为三大部分 xff1a 实时操作系统 中间件和飞行控制栈 1 NuttX实时操作系统 提
  • 【Pixhawk】注册一个字符型驱动设备

    最近学习Pixhawk的SPI xff0c 本以为PX4是STM32单片机而已 xff0c 写个SPI驱动应该很简单 但是当我看到mpu9250的那些cpp文件 xff0c 我一下就蒙了 由于PX4用的NUTTX系统 xff0c 类似Lin
  • PX4/Pixhawk---uORB深入理解和应用

    The Instructions of uORB PX4 Pixhawk 软件体系结构 uORB 主题发布 主题订阅 1 简介 1 1 PX4 Pixhawk的软件体系结构 PX4 Pixhawk的软件体系结构主要被分为四个层次 xff0c
  • APM(pixhawk)飞控疑难杂症解决方法汇总(持续更新)

    原文链接 xff1a http www nufeichuiyun com p 61 28
  • 我设计了一款开源飞控,性能远超Pixhawk,运行APM固件-怒飞垂云

    从2009年到现在 xff0c 我从事无人机研发将近11年了 xff0c 中途设计过很多飞控 xff0c 有闭源的无人飞艇飞控 大型固定翼无人机飞控 xff0c 也有在开源飞控Pixhawk基础上修改的飞控 xff0c 如今 xff0c 基
  • DroneKit教程(三):连接Pixhawk飞控

    DroneKit教程 xff08 三 xff09 xff1a 连接Pixhawk飞控 DroneKit提供了非常简便的代码 xff0c 可通过多种方式与飞控连接 连接飞控 使用DroneKit中的connect函数 xff0c 可以方便地连
  • pixhawk px4 commander.cpp

    对于复杂的函数 xff0c 要做的就是看函数的输入是什么 来自哪里 xff0c 经过处理后得到什么 给谁用 xff0c 这样就可以把程序逻辑理清 中间的分析就是看函数如何处理的 span class hljs keyword extern
  • pixhawk之NSH调试

    一 ardupilot固件 windows环境 前期准备 1 xff1a pix烧录程序 xff0c Arducopter或者library中的example都可以实现 2 xff1a 拔掉SD卡 xff08 脚本中提到的没有SD卡进入ns

随机推荐

  • FreeRTOS之TCB

    FreeRTOSMini实现了最小任务调度 现在分开介绍进程调度重要部分 进程调度的基础首先是定义任务调度的数据结构 xff0c 来保存任务堆栈结构和任务状态所在状态列表 xff0c 然后就是任务的优先级唯一号等 最小Mini内核参照 Fr
  • FreeRTOS任务调度主要变量

    之前介绍的和FreeRTOS任务调度相关的数据结构即内存分配实现 xLIST heap 4 TCB结构体 任务调度就是基于这些结构体实现 这次介绍调度相关的主要变量 代码在FreeRTOSMini c文件签名部分 span class to
  • Base64串介绍

    以前写winform时候没接触过Base64 刚开始接触时候还不知道是个啥 最开始接触Base64串时候是仪器出图 很长一段时间我还真以为Base64就是表示图的 xff0c 很多人也是这么认为的 xff0c 这次介绍一下什么是Base64
  • FreeRTOS创建任务

    CPU有这些寄存器 R0 R12为通用寄存器 R13为栈顶指针 xff0c 在OS时候中断函数的R13使用MSP的指针 xff08 内核态 xff09 非中断里面使用PSP指针 xff08 用户态 xff09 正是有双堆栈指针可以保证OS切
  • FreeRTOS任务调度最后篇

    FreeRTOS开启任务调度 一篇说到启动任务调度最后启动Systick定时器 xff0c 通过SVC中断引导第一个任务执行 然后系统就在Systick的定时中断下调度任务执行 xff0c 这次介绍最后的部分 xff0c Systick和P
  • 从STM32-FreeRTOS到linux

    之前买的STM32的开发板学习裸机开发 了解裸机之后学习FreeRTOS来作为小型操作系统学习 xff0c 理解操作系统调度实现 一直想学习一下linux的内核 xff0c 之前下载源码和初步看了下感觉无从下手 有了RTOS的基础后 xff
  • C#实现图片旋转

    C 绘图正常是不涉及到旋转的 有时候会有旋转画笔的情况 比如条码打印字竖着打印 旋转图片一定角度绘制 或者斜着画水印 这时候就涉及到旋转画笔了 源码地址 通过graphics TranslateTransform Pcenter X Pce
  • C#调C++库返回字符串

    用C 调C 43 43 库函数返回字符串 xff0c 由于C 43 43 本身方法之间调用返回字符串都是一般都是申明void或int返回的方法 xff0c 然后通过char变量带出返回值 在C 43 43 调用这种之前自己先初始化char空
  • Asp.NetCore在CentOS网站卡死

    最近碰到项目的网站在高峰期卡死的现象 刚开始以为是数据库问题导致的卡死 xff0c 就排查和改了数据的设置 然后观察几天发现网站还是会在高峰期卡死 xff0c 然后改了点网站设置 xff0c 准备第二天观察一下 xff0c 星期二竟然又没出
  • 使用IRIS碰到的坑

    最近换新电脑了 xff0c 然后直接不安装cache2016了 xff0c 直接上IRIS啊 然后碰到几个坑 xff0c 一是在win11不知道是兼容性不好还是怎么了 每次重启电脑后数据库就无法启动 xff0c 为此祭出多年保存的方子 xf
  • K8s 配置高可用提示Configuration file ‘/etc/keepalived/keepalived.conf‘ is not a regular non-executable file

    k8s配置keepalived高可用 xff0c systemctl start keepalived提示 检查keepalived配置文件 xff0c 查询配置也正常 从报错提示显示keepalived conf 配置文件是一个非执行的文
  • Std数据M的荣光

    对检验的上线 xff0c 实施和开发的大部分时间都用在做基础数据和联设备对通道这些 对相同的仪器每次都有做项目数据 xff0c 对通道那些我一直深有感触 xff0c 一直在构思怎么减少仪器对通道这些做数据的工作量 奈何以前只是浅显的使用M
  • matlab从图表中提取数据

    有如下的波形图 xff0c 如何从中精确提取出全部的数据 1 将波形图片 截图 保存为test png或test jpg xff0c 并将图片放于matlab工作目录中 xff0c 如下图示例所指定的目录中 xff1a 2 xff0c 新建
  • STM32 基础系列教程 1- CubeMX+GPIO

    前言 学习stm32 GPIO 的使用 xff0c 设置某一GPIO引脚为输出功能 xff0c 将对应引脚拉高或拉低输出 xff0c 同时学会初步认识STM32最新的HAL库的使用 xff0c 用代码实现控制GPIO引脚输出产生周期出1s
  • STM32 基础系列教程 29 - FreeRTOS

    前言 学习stm32 中 FreeRTOS嵌入式实时操作系统的使用 xff0c 学会在FreeRTOS时行任务创建与任务运动 xff0c 学习在嵌入式实时操作系统下编程 xff0c 用串口打印相应信息 xff0c 并控制LED闪烁 示例详解
  • 对本地的代码进行修改后,直接git pull会提示本地代码和github代码冲突,需要先commit本地代码,或者stash他们

    对本地的代码进行修改后 xff0c 直接git pull会提示本地代码和github代码冲突 xff0c 需要先commit本地代码 xff0c 或者stash他们 对本地的代码进行修改后 xff0c 直接git pull会提示本地代码和g
  • linux查询内存、CPU、硬盘等系统信息的命令

    一 linux CPU大小 root 64 idc cat proc cpuinfo grep 34 model name 34 amp amp cat proc cpuinfo grep 34 physical id 34 model n
  • ubuntu无法更新的问题,提示错误Err http://mirrors.163.com trusty Release.gpg Could not resolve 'mirrors.163.com

    最近在安装使用ubuntu xff0c 并且配置源文件下载相应gcc xff0c gdb时候 xff0c 出现错误 xff0c 提示报错内容为 Err http mirrors 163 com trusty Release gpg Coul
  • 在 GitHub 下载某个程序的特定版本代码

    情况 github中某个项目已经更新到2 1 0版本 但是想要它的1 0 1版本怎么办 方法一 xff1a 首先点击这个repository下的这个branch按钮 点开了以后你会看到这个 xff0c 然后点tags 选择你想要下载的版本
  • Pixhawk之姿态控制

    原文地址 xff1a http blog csdn net qq 21842557 1 写在前面 无人机控制部分主要分为两个部分 xff0c 姿态控制部分和位置控制部分 xff1b 位置控制可用远程遥控控制 xff0c 而姿态控制一般由无人