Saturday, December 08, 2007

A glimpse at the software details....

I want to try and give a glimpse of the details involved with some thing as simple as a swapping the GPS. Ive been working with a GARMIN GPS-18 5Hz gps, it has a serial interface that spits out NMEA sentences. NMEA is a standard protocol. If all you want out of the GPS is position then the GPS units are almost interchangeable. They almost all support the NMEA sentence...GPGGA.

The simple way to process the NEMA sentence is to load the whole thing into a character buffer and parse it. The Netburner library has a nicely written serial driver that makes talking to the serial port pretty painless. Open the port and read characters from it. The serial driver is intrrupt driven, so if you get busy and don't read characters, nothing is lost. To fit this reading and parsing into the main control loop is as simple as adding code to check to see if GPS chars are available and then jumping to a routine to read and process the chars.

You duplicate this three times, GPS, IMU and Telemetry commands.

I thought I'd try to unclutter the main task loop and be a little more efficient. I'm the original author of the Netburner serial driver, so modifying things at the driver level was not too scary. I modified the serial driver to parse the GPGGA sentance one charactor at a time in a state machine and then post to a semaphore to tell the main task that new GPS data is ready. This is a nice routine because it spreads the GPS parsing out in time and has the minimum possible delay.When the sentence checksum is validated the data is ready, all of the floating point data fields have already been parsed out. So the main task only gets notified when data is actually ready. After the GPGGA I expanded the system to capture the proprietary garmin PGRMV sentence that gives me north, east and vertical velocity. Again its all clean and works well. The state machine now knows how to parse two sentences and ignore the others.

This data is processed by the main data processing task. In addition to using it internally it also sends telemetry frames off on the maxstream radio to be displayed and recorded on the ground.
The display program receives a data structure and logs, plots and displays things based on the received data frame type. (The processing for the IMU data is almost identical)

Now I want to upgrade the GPS to the Crescent Vector. The obvious software differences are:
  • It provides Heading information
  • It updates at 10Hz
  • It does not support the garmin PGRMV sentance.
  • None of its NMEA sentances have vertical velocity.
So before I can do any of the software development I need to get the GPS hardware working. I covered most of that in the last post. One minor point I did not mention is that Vector draws 3 times as much current as the garmin and the GPS voltage regulator in the original system overheats, so in addition to fabricating a box I have to rewire the power distribution system on the helicopter....back to the land of software.

Humm I really want to know vertical velocity... digging in the crescent vector manual I discover that they have a binary message that has vertical velocity, in fact the single binary message has ALL the GPS data I have been capturing in a single message. The Crescent also has heading capability, alas it only outputs heading in a NMEA sentance, not a binary one. so my parsing state machine needs to process a mixed stream of Binary and NMEA messages.
The Binary message is nice in that it starts with $BIN so the beginning looks like a NMEA message, and the simple approach would be to just branch on this NMEA message ,
Its not that simple. My state machine resets its state to 0 whenever it sees a '$' beginning of message,but with the GPS sending binary data there is no guarentee that the binary data won't contain a '$' this breaks my state machine, so now the state machine knows if it is parsing a NMEA or binary data set and reacts accordingly. Also note that in a serial stream of chactors 1234.5678 there is no ambiguity on how to interpit the data. In a binary stream its a little bit more complicated. The GPS sends binary data as a IEEE754 double. standard format, but the GPS sends data in littel endian or intel format and the Coldfire processor I'm using uses big endian or Motorola format.(See Endian). SO I have to convert formats before I use the data. This is still a big win, because the endian conversion is much more efficient than parsing ASCII. Note that I'm already doing the endian conversion on the PC display software so this was not unexpected.

Once I'm parsing the GPS message the system stops responding reliably to telemetry commands....argghhhh! The problem is that If I keep the telemetry link busy sending data from the helicopter to the ground there is no time for the ground station to send data back. Up to this point the telemetry data stream was not too big for the down link pipe, switching from 5 to 10hz GPS pushes me over the edge. So now I write and debug a telemetry throttling routine that tries to keep the telemetry link idle for 50% of the time by throwing away data based on its age and priority.

All seems to be working except none of my telemetry frames have a spot for the GPS heading information, I'd like to keep both the GPS and IMU heading data, so I add a new GPS heading frame type to both the helicopter and the display software. Both areas are pretty modular so this is an easy fix. I label the new data with the same data type tag as the IMU heading data.... .
without remembering that the IMU heading is not floating point but a 0->65536 binary heading.(I worte the IMU parser and display code framework for my air lander over a year a go and have not really modifed it) Hence the GPS heading display is all messed up on the PC so I spend some time debugging the helicopter code to try and figure out why my parser is not properly parsing the GPS heading, only to eventually discover its a problem on the PC side.

This is now almost complete. At some future date I will add HOP and VDOP and signal to noise reporting. I'm now ready to take the whole helicopter outside and see how it works as an assembly. (I've been testing with two external GPS antennas on the roof. The GPS informs me that the antennas are 49.6 cm apart and that my roof has a heading of 172.3 degrees)

I really enjoy reading technical blog posts that give you a sense of the problems and struggles involved with building hardware. In an Ideal world I'd like to cover all of the project in this much detail. The reality of time and the need to sleep conspire against this. I've never been a skilled word smith so writing this saga took 25% as much time as struggling through the code. I don't have the patience to document everything to this level, but I'm going to try and do more posts at this level of detail.

4 comments:

Timothy J. Massey said...

Well, your effort is appreciated. I makes for an interested read for those of us watching from the sidelines...

Damien said...

I also find such posts very interesting, thI also find such posts very interesting, thank you !ank you !

John Carmack said...

For the record, I strongly encourage NOT distributing the processing of anything into separate threads or interrupt service routines. As long as everything has buffers that won't overflow, processing everything in the main loop has no disadvantages, and allows you to use conventional debugging techniques.

John Carmack

Paul Breed said...

John,
You and I will continue to agree to disagree on that one. I like the ability to make each task perform one and only one simple task. The code is small clean and simple. I believe this can be reliable if you follow a couple of simple rules:

Use an OS that was designed from the beginning to be a RTOS, and not a converted general purpose OS.

When coding you must exercise proper task /thread hygiene and do not put in unclean back door communications channels.

In any single task do not claim more than one shared resource at a time.