My colleague, Carl Brandon, and I have been running the CubeSat Laboratory at Vermont Technical College (VTC) for over ten years. During that time we have worked with nearly two dozen students on building and programming CubeSat nano satellites. CubeSats are small (usually 10cm cube), easily launched spacecraft that can be outfitted with a variety of cameras, sensing instruments, and communications equipment. Many CubeSats are built by university groups like ours using students at various skill levels in the design and production process.
Students working in the CubeSat Laboratory at VTC have been drawn from various disciplines including computer engineering, electro-mechanical engineering, electrical engineering, and software engineering. VTC offers a masters degree in software engineering, and two of our MSSE students have completed masters projects related to CubeSat flight software. In fact, our current focus is on building a general purpose flight software framework called CubedOS.
Like all spacecraft CubeSats are difficult to service once they are launched. Because of the limited financial resources available to university groups, and because of on-board resource constraints, CubeSats typically don't support after-launch uploading of software updates. This means the software must be fully functional and fault-free, with no possibility of being updated, at the time of launch
Many university CubeSat missions have failed due to software errors. This is not surprising considering that most flight software is written in C, a language that is difficult to use correctly. To mitigate this problem we use the SPARK dialect of Ada in all of our software work. Using the SPARK tools we work toward proving the software free of runtime error, meaning that no runtime exceptions will occur. However, in general we have not attempted to prove functional correctness properties, relying instead on conventional testing for that level of verification.
Although we do have some graduate students working in the CubeSat Laboratory, most of our students are third and fourth year undergraduates. The standard curriculum for VTC's software engineering program includes primarily the Java and C languages. Although I have taught Ada in the classroom, it has only been in specialized classes that a limited number of students take. As a result most of the students who come to the CubeSat Laboratory have no prior knowledge of SPARK or Ada, although they have usually taken several programming courses in other languages.
Normally I arrange to give new students an intensive SPARK training course that spans three or four days, with time for them to do some general exercises. After that I assign the students a relatively simple introductory task involving our codebase so they can get used to the syntax and semantics of SPARK without the distraction of complex programming problems. Our experience has been that good undergraduate students with a reasonable programming background can become usefully productive with SPARK in as little as two weeks. Of course, it takes longer for them to gain the skills and experience to tackle the more difficult problems, but the concern commonly expressed by some that the lack of SPARK skills in a programming team is a barrier to the adoption of SPARK is not validated by our experience
Students are, of course, novice programmers almost by definition. Many of our students are in the process of learning basic software engineering principles such as the importance of requirements, code review, testing, version control, continuous integration, and many other things. Seeing these ideas in the context of our CubeSat work gives them an important measure of realism that can be lacking in traditional courses.
However, because of their general inexperience, and because of the high student turnover rate that is natural in an educational setting, our development process is often far from ideal. Here SPARK has been extremely valuable to us. What we lack in rigor of the development process we make up for in the rigor of the SPARK language and tools. For example, if a student refactors some code, perhaps without adequately communicating with the authors of the adjoining code, the SPARK tools will often find inconsistencies that would otherwise go unnoticed. This has resulted in a much more disciplined progression of the code than one would expect based on the team's overall culture. Moreover the discipline imposed by SPARK and its tools can serve to educate the students about the kinds of issues that can go wrong by following an overly informal approach to development.
For example, one component of CubedOS is a module that sends “tick” messages to other modules on request. These messages are largely intended to trigger slow, non-timing critical housekeeping tasks in the other modules. For flexibility the module supports both one-shot tick messages that occur only once and periodic tick messages. Many “series” of messages can be active at once. The module can send periodic tick messages with different periods to several receivers with additional one-shot tick messages waiting to fire as well.
The module has been reworked many times. At one point it was decided that tick messages should contain a counter that represents the number of such messages that have been sent in the series. Procedure Next_Ticks below is called at a relatively high frequency to scan the list of active series and issue tick messages as appropriate. I asked a new student to add support for the counters to this system as a simple way of getting into our code and helping us to make forward progress. The version produced was something like:
procedure Next_Ticks is Current_Time : constant Ada.Real_Time.Time := Ada.Real_Time.Clock; begin -- Iterate through the array to see who needs a tick message. for I in Series_Array'Range loop declare Current_Series : Series_Record renames Series_Array(I); begin -- If we need to send a tick from this series... if Current_Series.Is_Used and then Current_Series.Next <= Current_Time then Route_Message (Tick_Reply_Encode (Receiver_Domain => Domain_ID, Receiver => Current_Series.Module_ID, Request_ID => 0, Series_ID => Current_Series.ID, Count => Current_Series.Count)); -- Update the current record. case Current_Series.Kind is when One_Shot => -- TODO: Should we reinitialize the rest of the series record? Current_Series.Is_Used := False; when Periodic => -- Advance the counter. Current_Series.Count := Current_Series.Count + 1; Current_Series.Next := Current_Series.Next + Current_Series.Interval; end case; end if; end; end loop; end Next_Ticks;
The code iterates over an array of Series_Record objects looking for the records that are active and are due to be ticked (Current_Series.Next <= Current_Time). For those records, it invokes the Route_Message procedure on an appropriately filled in Tick Reply message as created by function Tick_Reply_Encode. The student had to modify the information saved for each series to contain a counter, modify the Tick Reply message to contain a count, and update Tick_Reply_Encode to allow for a count parameter. The student also needed to increment the count as needed for periodic messages. This involved relatively minor changes to a variety of areas in the code.
The SPARK tools ensured that the student dealt with initialization issues correctly. But they also found an issue in the code above with surprisingly far reaching consequences. In particular, SPARK could not prove that no runtime error could ever occur on the line
Current_Series.Count := Current_Series.Count + 1;
Of course, this will raise Constraint_Error when the count overflows the range of the data type used. If periodic tick messages are sent for too long, the exception would eventually be raised, potentially crashing the system. Depending on the rate of tick messages, that might occur in as little as a couple of years, a very realistic amount of time for the duration of a space mission. It was also easy to see how the error could be missed during ordinary testing. A code review might have discovered the problem, but we did code reviews infrequently. On the other hand, SPARK made this problem immediately obvious and led to an extensive discussion about what should be done in the event of the tick counters overflowing.
In November 2013 we launched a low Earth orbiting CubeSat. The launch vehicle contained 13 other university built CubeSats. Most were never heard from. One worked for a few months. Ours worked for two years until it reentered Earth's atmosphere as planned in November 2015. Although the reasons for the other failures are not always clear, software problems were known to be an issue in at least one of them and probably for many others. We believe the success of our mission, particularly in light of the small size and experience of our student team, is directly attributable to the use of SPARK.