APM源码和MAVLINK解析学习——重读stabilize

  • stabilize model
    • init()
    • run()
  • handle_attitude()
  • MAVLINK消息包姿态信息传输过程

之前写的模式都是基于master版本的,这次重读stable版本的Sub-4.0,想着重新把这部分写一下。这次打算把MAVLINK包接收也稍微点一下。写的过程中难免会有错,如果发现错误请务必提出。


首先需要提到的一点是,在APM的源码中,具体的车辆类型文件夹如ArduCopter、Ardusub等都是作为上层存在的,它内部提供了与下层的连接,具体来说一些库中的类都会在车辆文件夹中进行声明并且得到面向于实际车辆类型的具体实现。

stabilize model

这次主要论述的版本是Sub-4.0,首先查看control_stabilize.cpp文件。主要分为init()和run()两部分。

init()

// stabilize_init - initialise stabilize controller
bool Sub::stabilize_init()
{// set target altitude to zero for reportingpos_control.set_alt_target(0);if (prev_control_mode == ALT_HOLD) {last_roll = ahrs.roll_sensor;last_pitch = ahrs.pitch_sensor;} else {last_roll = 0;last_pitch = 0;}last_yaw = ahrs.yaw_sensor;last_input_ms = AP_HAL::millis();return true;
}

为了方便说明我们在文章一开始所说的车辆文件夹下的文件是作为上层接口存在的意思。我们就以这段代码开头的pos_control.set_alt_target(0)为例进行解释。

首先水下潜艇车辆类型Sub的位置控制器pos_control对象是在Sub.h中的Sub类中进行了声明,而这个AC_PosControl_Sub则是实现在了libraries路径下。简单来说libraries提供了最基本的类和功能方便你在上层车辆文件夹内进行调用,然后根据具体需要使用一部分功能或者重写部分函数。

AC_PosControl_Sub pos_control;

这个AC_PosControl_Sub实现在ardupilot/libraries/AC_AttitudeControl/路径下,其是继承自AC_PosControl类的,在其内部实现了set_alt_target()方法如下

    /// set_alt_target - set altitude target in cm above homevoid set_alt_target(float alt_cm) { _pos_target.z = alt_cm; }...// position controller internal variablesVector3f    _pos_target;            // target location in cm from home

完成该语句之后,通过if-else判断语句,首先判断当前车辆是否处于定深状态,注意prev_control_mode会在set_model()方法中进行赋值。如果是的话,保存当前传感器值为上一次的roll和pitch角。如果不是的话,那么就将上次的roll、pitch角设定为0。注意ahrs的归属如下:

在Sub类中定义了ahrs为AP_AHRS_NavEKF类对象并对参数进行赋值
AP_AHRS_NavEKF ahrs{EKF2, EKF3, AP_AHRS_NavEKF::FLAG_ALWAYS_USE_EKF};

class AP_AHRS_NavEKF : public AP_AHRS_DCM {public:enum Flags {FLAG_NONE = 0,FLAG_ALWAYS_USE_EKF = 0x1,};// ConstructorAP_AHRS_NavEKF(NavEKF2 &_EKF2, NavEKF3 &_EKF3, Flags flags = FLAG_NONE);...

如上,AP_AHRS_NavEKF继承自AP_AHRS_DCM
AP_AHRS_DCM继承自AP_AHRS,在AP_AHRS类中,定义有

    // integer Euler angles (Degrees * 100)int32_t roll_sensor;int32_t pitch_sensor;int32_t yaw_sensor;

在init()原函数中,最后再获取上一次的yaw角和时间。

run()

// stabilize_run - runs the main stabilize controller
// should be called at 100hz or more
void Sub::stabilize_run()
{// if not armed set throttle to zero and exit immediatelyif (!motors.armed()) {motors.set_desired_spool_state(AP_Motors::DesiredSpoolState::GROUND_IDLE);attitude_control.set_throttle_out(0,true,g.throttle_filt);attitude_control.relax_attitude_controllers();last_roll = 0;last_pitch = 0;last_yaw = ahrs.yaw_sensor;return;}motors.set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);handle_attitude();// output pilot's throttleattitude_control.set_throttle_out(channel_throttle->norm_input(), false, g.throttle_filt);//control_in is range -1000-1000//radio_in is raw pwm valuemotors.set_forward(channel_forward->norm_input());motors.set_lateral(channel_lateral->norm_input());
}

这部分与之前讲的master版本的大同小异(Ardusub源码解析学习(一)——Ardusub主程序)。重点在于中间这个handle_attitude(),他把master中的大部分功能都集中在了这里面。

handle_attitude()

这个handle_attitude()函数在control_althold.cpp中实现。

void Sub::handle_attitude()
{uint32_t tnow = AP_HAL::millis();motors.set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);// get pilot desired lean anglesfloat target_roll, target_pitch, target_yaw;// Check if set_attitude_target_no_gps is validif (tnow - sub.set_attitude_target_no_gps.last_message_ms < 5000) {Quaternion(set_attitude_target_no_gps.packet.q).to_euler(target_roll,target_pitch,target_yaw);target_roll = 100 * degrees(target_roll);target_pitch = 100 * degrees(target_pitch);target_yaw = 100 * degrees(target_yaw);last_roll = target_roll;last_pitch = target_pitch;last_yaw = target_yaw;attitude_control.input_euler_angle_roll_pitch_yaw(target_roll, target_pitch, target_yaw, true);} else {// If we don't have a mavlink attitude target, we use the pilot's input insteadget_pilot_desired_lean_angles(channel_roll->get_control_in(), channel_pitch->get_control_in(), target_roll, target_pitch, attitude_control.get_althold_lean_angle_max());target_yaw = get_pilot_desired_yaw_rate(channel_yaw->get_control_in());if (abs(target_roll) > 50 || abs(target_pitch) > 50) {last_roll = ahrs.roll_sensor;last_pitch = ahrs.pitch_sensor;last_yaw = ahrs.yaw_sensor;last_input_ms = tnow;attitude_control.input_rate_bf_roll_pitch_yaw(target_roll, target_pitch, target_yaw);} else if (abs(target_yaw) > 50) {// if only yaw is being controlled, don't update pitch and rollattitude_control.input_rate_bf_roll_pitch_yaw(0, 0, target_yaw);last_yaw = ahrs.yaw_sensor;last_input_ms = tnow;} else if (tnow < last_input_ms + 250) {// just brake for a few mooments so we don't bouncelast_yaw = ahrs.yaw_sensor;attitude_control.input_rate_bf_roll_pitch_yaw(0, 0, 0);} else {// Lock attitudeattitude_control.input_euler_angle_roll_pitch_yaw(last_roll, last_pitch, last_yaw, true);}}
}

这里挑部分说一下,首先看if-else语句部分,在开始之前大家再回去看看原来master里面对于stabilize_run()里面的控制是怎么样的,你会发现非常明显的对比。

该部分语句判断的是你是否从地面站接收mavlink消息进行控制,if语句里面实现解析mavlink消息包里面的内容从而完成控制,而else里面则是在如果在没有接收到mavlink消息包的情况下(消息包超时),进行操作员的手动控制。如果你仔细阅读,你会发现master里面的stabilize_run()中的控制内容实际上与else内部的内容一致。具体原因我们先看下去再说。


首先我们回到if语句的最开始部分,这里我们查看Sub类中定义的结构体如下:

    // used to allow attitude and depth control without a position systemstruct attitude_no_gps_struct {uint32_t last_message_ms;mavlink_set_attitude_target_t packet;};attitude_no_gps_struct set_attitude_target_no_gps {0};

这个set_attitude_target_no_gps内部有两个成员,其中last_message_ms记录的是上一次接收到message的时间,而最主要的部分就是这个packet包。仔细查看if部分的程序,就会发现主要是对这个set_attitude_target_no_gps中的packet成员进行处理。

回推去查看mavlink_set_attitude_target_t,其中MAVPACKED()是宏定义打包结构,仔细查看内部结构,发现里面定义了如四元数等重要结构参数(仔细看type_mask的注释,后面会有需要)

MAVPACKED(
typedef struct __mavlink_set_attitude_target_t {uint32_t time_boot_ms; /*< [ms] Timestamp (time since system boot).*/float q[4]; /*<  Attitude quaternion (w, x, y, z order, zero-rotation is 1, 0, 0, 0)*/float body_roll_rate; /*< [rad/s] Body roll rate*/float body_pitch_rate; /*< [rad/s] Body pitch rate*/float body_yaw_rate; /*< [rad/s] Body yaw rate*/float thrust; /*<  Collective thrust, normalized to 0 .. 1 (-1 .. 1 for vehicles capable of reverse trust)*/uint8_t target_system; /*<  System ID*/uint8_t target_component; /*<  Component ID*/uint8_t type_mask; /*<  Mappings: If any of these bits are set, the corresponding input should be ignored: bit 1: body roll rate, bit 2: body pitch rate, bit 3: body yaw rate. bit 4-bit 6: reserved, bit 7: throttle, bit 8: attitude*/
}) mavlink_set_attitude_target_t;

回到if语句中,我们将从packet包中得到的四元数q转换为欧拉角,然后获取期望的RPY角(此处需要乘以100笔者也还没弄清楚是为什么),并且将其保存为上一次的欧拉角的值。最后调用姿态控制器的input_euler_angle_roll_pitch_yaw()方法进行具体的控制(这个函数内部会把获取的欧拉角的值乘以0.01,或许这就是前面为什么乘以100的原因,可能是为了保证精度吧?有懂的大佬务必留言告知)。

MAVLINK消息包姿态信息传输过程

那么肯定会有好奇的同学想知道packet里面的四元数q到底是怎么得到的,或者准确来说set_attitude_target_no_gps这个对象是如何获取到对应的数据的呢?在开始之前,希望你对MAVLINK有一个基本的认知:MAVLink Developer Guide

后面有时间的话我也想稍微写一些关于MAVLINK方面的文章

handleMessage

我们追根溯源到ardupilot/Ardusub/GCS_Mavlink.cpp中,这里面实现了一些针对于Sub车辆类型的mavlink消息包的处理函数,当然其他车辆类型也是类似。定位到handleMessage()这个函数,这是根据具体的message的id编号来进行对应的消息处理。

对于Attitude的mavlink消息包为MAVLINK_MSG_ID_SET_ATTITUDE_TARGET,对应id=82。根据switch进入到实际处理用的程序段。

注意到了吗,开头就是定义了mavlink_set_attitude_target_t类型的packet用来接收message,其中mavlink_msg_set_attitude_target_decode()用来将msg中的信息解码到packet,这个函数很重要,我们放到后面详细来讲。

void GCS_MAVLINK_Sub::handleMessage(const mavlink_message_t &msg)
{switch (msg.msgid) {...case MAVLINK_MSG_ID_SET_ATTITUDE_TARGET: { // MAV ID: 82// decode packetmavlink_set_attitude_target_t packet;mavlink_msg_set_attitude_target_decode(&msg, &packet);// ensure type_mask specifies to use attitude// the thrust can be used from the altitude holdif (packet.type_mask & (1<<6)) {sub.set_attitude_target_no_gps = {AP_HAL::millis(), packet};}// ensure type_mask specifies to use attitude and thrustif ((packet.type_mask & ((1<<7)|(1<<6))) != 0) {break;}// convert thrust to climb ratepacket.thrust = constrain_float(packet.thrust, 0.0f, 1.0f);float climb_rate_cms = 0.0f;if (is_equal(packet.thrust, 0.5f)) {climb_rate_cms = 0.0f;} else if (packet.thrust > 0.5f) {// climb at up to WPNAV_SPEED_UPclimb_rate_cms = (packet.thrust - 0.5f) * 2.0f * sub.wp_nav.get_default_speed_up();} else {// descend at up to WPNAV_SPEED_DNclimb_rate_cms = (packet.thrust - 0.5f) * 2.0f * fabsf(sub.wp_nav.get_default_speed_down());}sub.guided_set_angle(Quaternion(packet.q[0],packet.q[1],packet.q[2],packet.q[3]), climb_rate_cms);break;}...
}

处理步骤如下:

  • 将数据包msg中的信息解码到packet中。
  • 判断packet中的type_mask位(这个在mavlink_set_attitude_target_t结构中有让大家仔细记住,想不起来了的话我把它放在下面)。

uint8_t type_mask; /< Mappings: If any of these bits are set, the corresponding input should be ignored: bit 1: body roll rate, bit 2: body pitch rate, bit 3: body yaw rate. bit 4-bit 6: reserved, bit 7: throttle, bit 8: attitude/

  • 如果type_mask中的第7位throttle位置1了,那么我们获取当前时间和packet保存到set_attitude_target_no_gps中,这回答了我们前面关于set_attitude_target_no_gps这个对象是如何获取到对应的数据的问题。
  • 如果type_mask中的第7位throttle位或第八位attitude位不为0,那么直接推出当前程序(说实话没咋看懂)。
  • 对packet中的thrust控制量进行限幅,然后对值进行判断。
    • 如果thrust控制量=0.5,则表明控制信号处于中位,爬升速率为0;
    • 如果thrust>0.5,则表明上浮,sub.wp_nav.get_default_speed_up()获取到的爬升速率默认参数表中定义为250cm/s;同理<0.5时的计算,此时使用fabs()是因为前面的(packet.thrust - 0.5f)已经为负。
  • 最后调用guided_set_angle()方法设置引导模式角度目标,函数如下。
// set guided mode angle target
void Sub::guided_set_angle(const Quaternion &q, float climb_rate_cms)
{// check we are in velocity control modeif (guided_mode != Guided_Angle) {guided_angle_control_start();}// convert quaternion to euler anglesq.to_euler(guided_angle_state.roll_cd, guided_angle_state.pitch_cd, guided_angle_state.yaw_cd);guided_angle_state.roll_cd = ToDeg(guided_angle_state.roll_cd) * 100.0f;guided_angle_state.pitch_cd = ToDeg(guided_angle_state.pitch_cd) * 100.0f;guided_angle_state.yaw_cd = wrap_180_cd(ToDeg(guided_angle_state.yaw_cd) * 100.0f);guided_angle_state.climb_rate_cms = climb_rate_cms;guided_angle_state.update_time_ms = AP_HAL::millis();
}

mavlink_msg_set_attitude_target_decode

解析完上面的handleMessage()函数之后,我们进入这个decode函数内部来看看实际解码过程。

还是很简单的哈(注意根据宏定义,#if里面的代码不执行,执行的是#else下面的程序),主要就是将msg内容拷贝进set_attitude_target里面,这里就不多说了,大家自己看看吧。

关于这个函数,实际上是MAVLINK根据对应的消息类型自己定义并且创建的,具体可以查看QGC添加自定义组件和发送自定义MAVLINK消息这一篇博文的第二章。

/*** @brief Decode a set_attitude_target message into a struct** @param msg The message to decode* @param set_attitude_target C-struct to decode the message contents into*/
static inline void mavlink_msg_set_attitude_target_decode(const mavlink_message_t* msg, mavlink_set_attitude_target_t* set_attitude_target)
{#if MAVLINK_NEED_BYTE_SWAP || !MAVLINK_ALIGNED_FIELDSset_attitude_target->time_boot_ms = mavlink_msg_set_attitude_target_get_time_boot_ms(msg);mavlink_msg_set_attitude_target_get_q(msg, set_attitude_target->q);set_attitude_target->body_roll_rate = mavlink_msg_set_attitude_target_get_body_roll_rate(msg);set_attitude_target->body_pitch_rate = mavlink_msg_set_attitude_target_get_body_pitch_rate(msg);set_attitude_target->body_yaw_rate = mavlink_msg_set_attitude_target_get_body_yaw_rate(msg);set_attitude_target->thrust = mavlink_msg_set_attitude_target_get_thrust(msg);set_attitude_target->target_system = mavlink_msg_set_attitude_target_get_target_system(msg);set_attitude_target->target_component = mavlink_msg_set_attitude_target_get_target_component(msg);set_attitude_target->type_mask = mavlink_msg_set_attitude_target_get_type_mask(msg);
#elseuint8_t len = msg->len < MAVLINK_MSG_ID_SET_ATTITUDE_TARGET_LEN? msg->len : MAVLINK_MSG_ID_SET_ATTITUDE_TARGET_LEN;memset(set_attitude_target, 0, MAVLINK_MSG_ID_SET_ATTITUDE_TARGET_LEN);memcpy(set_attitude_target, _MAV_PAYLOAD(msg), len);
#endif
}

再往上一步呢?

在ardupilot/libraries/GCS_MAVLink/GCS_Common.cpp中的packetReceived()调用了handleMessage()函数。代码如下:

void GCS_MAVLINK::packetReceived(const mavlink_status_t &status,const mavlink_message_t &msg)
{// we exclude radio packets because we historically used this to// make it possible to use the CLI over the radioif (msg.msgid != MAVLINK_MSG_ID_RADIO && msg.msgid != MAVLINK_MSG_ID_RADIO_STATUS) {const uint8_t mask = (1U<<(chan-MAVLINK_COMM_0));if (!(mask & mavlink_private)) {mavlink_active |= mask;}}if (!(status.flags & MAVLINK_STATUS_FLAG_IN_MAVLINK1) &&(status.flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) &&AP::serialmanager().get_mavlink_protocol(chan) == AP_SerialManager::SerialProtocol_MAVLink2) {// if we receive any MAVLink2 packets on a connection// currently sending MAVLink1 then switch to sending// MAVLink2mavlink_status_t *cstatus = mavlink_get_channel_status(chan);if (cstatus != nullptr) {cstatus->flags &= ~MAVLINK_STATUS_FLAG_OUT_MAVLINK1;}}if (!routing.check_and_forward(chan, msg)) {// the routing code has indicated we should not handle this packet locallyreturn;}if (!accept_packet(status, msg)) {// e.g. enforce-sysid says we shouldn't look at this packetreturn;}handleMessage(msg);
}

然后,在我们继续往前推的时候,在同一路径文件下发现GCS_MAVLINK::update_receive(uint32_t max_time_us)调用packetReceived()这个函数,这边由于太长了就不放进来了,大家自己去看看吧。

再再往前呢?终于到头了,我们回到了最初的车辆文件夹下,而且是主文件下!以Ardusub为例,我们回到了Ardusub.cpp中,找到的对应位置为

SCHED_TASK_CLASS(GCS,                 (GCS*)&sub._gcs,   update_receive,     400, 180),

没错,就在任务列表中!这个函数以400Hz的频率运行,超时时间为180ms。至此,我们推完了整个姿态消息解包的过程~

这边再帮大家把思路理一下:

  • 在上层车辆文件夹下,我们使用任务调度器,调用了update_receive()这个函数。
  • update_receive()这个函数会调用packetReceived()用来接收mavlink消息包。
  • packetReceived()中调用了handleMessage()函数处理包中的内容(如获取四元数保存之类的)。

有时间的话,这部分再更新一点内容,把stable版本的althold model这部分也加进来。再把MAVLINK这部分消息解包过程再令写一篇博文介绍一下,疯狂挖坑(逃…)

重读Ardupilot中stabilize model+MAVLINK解包过程相关推荐

  1. H264视频传输、编解码----RTP协议对H264数据帧拆包、打包、解包过程

    H264帧需要通过RTP协议进行传输,这其中就涉及到H264数据帧的封包.拆包和解包等过程. RTP协议格式 下面是 RFC 3550 中规定的 RTP 头的结构: 0 1 2 3 40 1 2 3 ...

  2. Python中的打包与解包

     解包:Unpacking,比如你儿子去买包子回来分给你的家人. a, *b, c = [1, 2, 3, 4, 5] print(a) # 1 print(b) # [2, 3, 4] print( ...

  3. python中的装包与解包*,**

    学习过程中遇到了*解包的过程,很感兴趣,于是实验一番, a = (1, 2, 3, 4, 5, 6) b = [2, 3, 4, 5, 5, 6] c = {"name": 'zh ...

  4. SIP与RTP综合应用5-RTP解包过程

    RTP接收部分比较简单(不用考虑jitterbuffer等),先从这里入手. 其实主要就3步: 1 创建一个udp,监听一个端口,比如5200. 2 收到RTP包,送到解包程序,继续收第 二个. 3 ...

  5. Linux中使用tar打包解包查看的使用方法

    Linux tar的基本使用方法 基础语句 tar [-ABcdgGhiklmMoOpPrRsStuUvwWxzZ][-b <区块数目>][-C <目的目录>][-f < ...

  6. 【2.skynet c gate服务中databuffer解析(解包)】

    本文根据云风博客为思路来解读databuffer在skynet老版本gate服务的应用. 源码为databuffer.h和service_gate.c 首先解释一下什么是ringbuffer 它是一个 ...

  7. CentOS7安装光盘修改过程中initrd.img的解包及打包排雷

    碰到一个需求需要修改centos7的安装光盘,在安装时增加一个指令的支持(ks的per段中需要划raid) 这种修改肯定是改initrd.img了.直接挂载光盘找到initrd.img,看文件类型. ...

  8. Python中的解包用法

    Python中的解包用法 解包(unpacking:解包,拆包),基本意思将容器里面的元素逐个取出来使用.封包(packing:封包,打包)基本意思将多个元素合起来作为一个整体使用. 可迭代对象支持解 ...

  9. 5分钟,关于Python 解包,你需要知道的一切

    题图:Photo by Elena Koycheva on Unsplash 导读:本文总结了 Python 解包操作的方方面面,文章略长,看本文时,身边最好有多个不同版本 Python 解释器的电脑 ...

  10. APK文件简介 及 解包 打包 工具 介绍

    1. APK文件简介 APK是Android Package的缩写,即Android application package文件或Android安装包.每个要安装到Android平台的应用都要被编译打 ...

最新文章

  1. 两个特征是独立好还是正相关好_长文之详解机器学习的数据特征选择问题
  2. 论文笔记:PRIN: Pointwise Rotation-Invariant Networks
  3. Oracle用 odp.net 时出现 Oracle.DataAccess.Client.OracleConnection的类型初始值设定项引发异常 问题的解决...
  4. 里面不仅有强悍的zhajinhuaplayer
  5. 离线安装python环境
  6. IOT(32)---各大物联网平台对比
  7. python监听键盘输入_Python监听鼠标键盘事件
  8. php调用go微服务,基于go搭建微服务实践教程 (一)
  9. WinRAR压缩加密的做法
  10. C++ opencv人脸识别框
  11. Kite Compositor制作下雨打雷特效详细教学
  12. 真切还是虚无?宇宙可能是一个假真空气泡
  13. ubuntu18打开网页慢的问题
  14. Chapter2 Creating and Destroying Objects
  15. 学习java被虐千百遍
  16. 农产品进出口成都代办公司注册流程
  17. html5调用papy支付,Payment
  18. 【百度地图API】如何在地图上添加标注?——另有:坐标拾取工具+打车费用接口介绍...
  19. 2021-11-15 基于音乐商店NetMusicShop的复杂查询(二)
  20. SW2017学习笔记(三)草图的简介、绘制

热门文章

  1. 已知 XYZ+YZZ=532,其中,X、Y、Z 为数字,编程求出 X、Y 和 Z 的值
  2. Java知识点(三)
  3. web前端面试技巧-如何自我介绍?如何应对hr?
  4. 奇舞团- 招高级iOS开发工程师
  5. 1.1 wamp/wnmp 环境搭建
  6. linux 进程共享内存同步,Linux使用共享内存通信的进程同步退出问题
  7. Mysql 基准测试
  8. kali linux怎么安装无线网卡驱动,Kali Linux 安装BCM43142网卡驱动
  9. vue3项目全家桶知识
  10. MySQLworkbench中PK,NN,UQ意思详解