MAVLink ground station basics
I have been asked a few times now how I got my GCS basics up and running. MAVLink is a rather complicated protocol to implement. The documentation is rather thin and partially outdated… Not an easy thing, if you are no experienced developer…
I’m no pro developer by a long shot. I speak reasonably well C and I did some smaller projects in JAVA. All in all, I can hold my own but can’t do nor understand really fancy stuff. Without the help of Pat Hickey, who answered many of my questions on the mailing list, I wouldn’t have gotten very far, so first of all, thanks again to him and the others!
Now, how do we get a MAVLink interpreter going?
The first thing to do would be to snatch a copy of the GCS_MAVLink library from the Arducopter project. You will also need AP_Common, AP_Math and FastSerial for dependencies. FastSerial must be the first to be included in your sketch.
Then, go an grab the code of ArduStationII. When you have that, open 2 windows: One with the code of MAVLink.pde from ArduStation2, the other in the GCS_MAVLink library subdir include/mavlink/v1.0/common/
Then, in MAVLink.pde, look at void gcs_handleMessage(mavlink_message_t* msg). This function is called by gcs_update and does the main decoding work. Basically, it’s a huge switch-case block which switches on the message id of the received package. Let’s have a look at one case block:
case MAVLINK_MSG_ID_ATTITUDE:
{
// decode
mavlink_attitude_t packet;
mavlink_msg_attitude_decode(msg, &packet);
pitch = toDeg(packet.pitch);
yaw = toDeg(packet.yaw);
roll = toDeg(packet.roll);
break;
}
That doesn’t look so complicated, does it? ArduStationII works mainly with global variables for all the interesting parameters. So what they do is, for everything they want, they knit a case block, fishing for the right message id, then call the respective decode method and assign the values to their global variables.
Let’s have a look at something that isn’t included in ArduStationII. In the common directory, search for mavlink_msg_vfr_hud.h and open it in an editor.
The very first thing in this file is the definition of what’s in a MAVLINK_MSG_VFR_HUD packet:
typedef struct __mavlink_vfr_hud_t
{
float airspeed; ///< Current airspeed in m/s
float groundspeed; ///< Current ground speed in m/s
float alt; ///< Current altitude (MSL), in meters
float climb; ///< Current climb rate in meters/second
int16_t heading; ///< Current heading in degrees, in compass units (0..360, 0=north)
uint16_t throttle; ///< Current throttle setting in integer percent, 0 to 100
} mavlink_vfr_hud_t;
Let’s say, we want the heading… If you scroll down a bit, you find the decode function…
static inline void mavlink_msg_vfr_hud_decode(const mavlink_message_t* msg, mavlink_vfr_hud_t* vfr_hud)
Seems, like we have everything. Now you just need to define a global variable for the heading…
int heading;
and write the respective case block:
case MAVLINK_MSG_ID_VFR_HUD:
{
mavlink_vfr_hud_t packet;
mavlink_msg_vfr_hud_decode(msg, &packet);
heading = packet.heading;
break;
}
Now there is only one more thing to do. Normally, the APM sends the VFR_HUD message rather rarely. You have to tell it to do it more often. In MAVLink.pde, scroll down until you find the start_feeds function. VFR_HUD is a stream in the EXTRA2 category ;). You can find out, which message stream is in which category from the GCS_MAVLink.pde in the Arducopter project :).
Have fun coding!