-
Notifications
You must be signed in to change notification settings - Fork 10
Getting Started with Subsystem Modules
This document captures the overall steps that need to happen for each subsystem module (which, for the most part, equals each MSP430 or MSP432) on the spacecraft, along with related notes on best-practices, resources available, and common pitfalls.
Subsystem module software is in this Git repo, under ./src/ssmods. Each module is a separate folder in ssmods, with the general title ss__. Do NOT create a new projects/folders in ssmods, or move any of the folders. The folder layout is designed to work well with Code Composer Studio by setting CCS "Workspace" directory to ssmods itself, and then using Project-->Import CCS Projects... to import all the subsystem module folders, which then become projects in CCS. Each project is completely set up currently to build with the correct predefined symbols set, particularly those that specify which subsystem module is being built. Please make sure you don't move the location that binaries are dropped when builds are run - though if you don't know how you would go about doing that, don't worry about it - it's hard to do accidentally.
The general set of tasks when starting work on a new subsystem module, to be followed ROUGHLY in order unless there are compelling special reasons to do otherwise, includes:
- Make sure the subsystem lead knows you are working on the MSP43x, so you can get from them the latest engineering boards (though you will likely be able to start development just using a Launchpad), get an update on whether new boards are coming (particularly if they have design changes coming down the pike that affects MSP interface points), etc.
- Also, coordinate via the subsystem lead and CDH lead to make sure you understand which other MSP43x's are on the same board, so you can coordinate with that developer as well.
- A key piece of info to get is a prioritization, so that you get their take on what they'd like to have up and running first, vs. what can wait. Much of this will be driven by what the next test milestones are. Note that in some cases their order might not be achievable, but it's a good place to start the discussion.
- The most important reference would be the Eagle .brd and .sch board and schematic files for the board that hosts your MSP
- The above files require Eagle, which can be downloaded for free. There are very few functions you need to learn to use Eagle as just a viewer for these files, but if you'd rather not do it, ask the lead for a PDF version.
- Regardless of the format you go with, make sure the file represents the VERSION OF THE BOARD THAT YOU ACTUALLY ARE WORKING WITH, not necessarily the latest board that hasn't been ordered or built yet.
- The ICD (interface control document) is a nice thing to have as well, though many are still works-in-progress.
- Interface points are between MSP43x and board it is installed on. Examples include ...
- Figure out which I2C bus (1 or 2) is used, so you initialize sensors on the right bus
- Any analog sensors? If so, which sensor channel are they using? (e.g. A0 ...)
- GPIO pins in use, and for what? Make sure there is no confusion about what each GPIO pin enables or disables on the board, and confirm with the board design (usually the lead) that something labelled "enable" doesn't actually disable something ... sadly, we do have instances of that in our designs
- PWM? Figure out which pins, and from that you can figure out which timer you have to use to drive them
- For most, this is with the boilerplate code already in the subsystem module project, though some new modules are a bit further along, or have a large chunk of code in wrappers (like reaction wheels)
- The files included in a new subsystem module are a _main.c and .h. If you'd like to add other source files, feel free. Note that the shared dsbase code is pulled in through an "advanced folder" link to the common dsbase location, so any changes you make to dsbase from any of the ssmods module projects results in a change for everyone. So watch out.
- The starter template's single most important line is the bspInit() call on the first line of main(). This function is where we hang a lot of mandatory stuff that all subsystems need, so it should always be left in, and never moved later in the function or (gasp) deleted entirely. It disables the watchdog timer, enables all the peripheral pins, sets our clock speed safely, and a growing list of other things.
- SUBSYSTEM_MODULE is passed as an argument to all the bspInit() calls made by subsystem modules, across the spacecraft. It's basically a level of indirection that allows some "stuff" to happen, but just know that it ends up equaling the official module name, which in turn is used to hook things up properly, setup some debug structures, etc. Again, don't change the bspInit() call. Leave it the way you found it.
- There is a bare skeleton implementation of a three state finite-ish state machine (FSM). You can wipe this out and develop your own, but they key points to keep in mind are: a) most subsystem modules SHOULD have well-defined states at all times, at least to govern a particular subset of their activity (vs. some things that just happen all the time regardless); and b) if you rely on interrupts to change states, be sure to have the interrupt handler do something very quick (here, it is changing flags), and then return from the interrupt, to have the flag read and acted on in the main code. This latter point is imperative to free interrupt cycles, as over-long interrupt handling can lead to data loss/corruption, unresponsiveness, and other hard-to-diagnose things.
- You want to start off by blasting in a load of #defines, enums, and structs to help make sense of all the interface points you have, and all the behavior you'll be performing
- This includes GPIO pins (which would include #defines for which ...DIR, and ...OUT registers to use, and which bits; and for non-GPIO pins that need configuring (very few, as most of that is done automatically within the wrappers for things like I2C devices and analog sensor) ...SEL0, ...SEL1, ...DIR, and ...OUT registers (again, along with which bits.)
- Just remember: with very few exceptions, there shouldn't be "bare" magic numbers in the code, they should be #defined so that you - and more importantly, the person who comes along after you - know what they're looking at.
- This includes anything to to **enable core functionality for the MSP/board, including actual functions, enums, structs, #defines, etc.
- Here is where you start making the Foo board start doing Foo. Start with the most important functionality first, e.g. the first function written on the power distribution board (which manages power domains throughout the spacecraft) was a function that used the definitions and enums from the previous step to initialize all the power domains to a known starting state; the second function was a function that allow specific commands to be performed on each power domain (enable, disable, toggle).
- The first crack at a given function or chunk of code of course won't be perfect, but due to time limitations, it's important to try to write as "flight-worthy" of code as possible, unless absolutely necessary to debug something, or other figure out how some bit of software/hardware interaction works.
- A middle point in here is that if you rely on I2C-based sensors, or other sensors that come in from off-board (like temperature sensors), work with leads to use some of the great test equipment we have to verify that the sensor is working properly before you drive yourself crazy with the software. Most boards are designed with perfect ways to tap into the buses, etc. to see what is "really" going on, and learning how to use the tools to take advantage of those points is a great step towards being a fantastic embedded programmer.
- Hard-wire a repeatable "standard behavior" for initial (and potentially environmental) testing
- Now that the core stuff can be done, "bake in" some behavior right into main() to do "something interesting" - and here is where you'll use that info from the lead, about what stuff you want the MSP/board to demonstrate.
- This is, by definition, obviously throw-away code, but there shouldn't be much of it. If you're finding it takes a bunch of lines of code to do core things in that baked-in behavior, that suggests you're missing some core functions from the last step.
- To enable meaningful telemetry in general, you need to start (as early as possible) thinking about "what information would help me ascertain the health of this subsystem module" if all you otherwise saw was a board and a blinking LED. This means that if you have, say, error handling paths in your code for a certain sensor (e.g. an analog sensor that should never be over some value), it's tempting to just silently fail/return from the function/whatever ... and while you can't DO much to fix some of those issues, you MUST get in the habit of tracking that they did occur, typically with counters of various kinds. Take a look at ./src/dsbase/core/uart.c & .h for examples of how this can work (though there are some issues with the exact choices made there ...)
- Keep in mind that the telemetry in COSMOS are not just for consumption by the operator; the possibly larger reason to be in COSMOS is for the test scripting running capabilities. Make sure to expose telemetry that takes advantage of both perspectives. An example from the distribution board: the telemetry includes the last command sent, which enables confirmation that the subsystem successfully received commands (or are under the influence of internally-sent commands, or errored out and missed a command entirely), AND an indication of whether the output pin is high or low (which can, under certain conditions, differ entirely from what the last commands would suggest would be the case). COSMOS test scripts, written in Ruby, can test for all of these cases during automated, extensive testing and see if we have recurring problems, even if they're very rare. Very useful.
- At an implementation level, use TLM_SEGMENT constructs (which always has a BcTlmHeader as the first member! see ss_EPS_Dist for examples) to store any data that are destined for the backchannel - these are essentially just C structs optimized for easier passing on the UART's serial line (technically speaking, they are "packed"). The idea is that IF any core information that you need to control your module and its hardware is ALSO interesting to share out to COSMOS, you store it in a TLM_SEGMENT instead of some other struct, array or set of variables. This gives us a very low-tax way (i.e. with very little extra copying) of beaming out this information when we decide we need it
- This step also includes writing the COSMOS packet definitions, as it has to match the layout of your structs/segments perfectly in order to function. More/all information on COSMOS is at http://www.cosmosrb.com, and you encouraged to learn about it - but if you are time-crunched or unthrilled by the prospect, please contact the CDH lead for help.
- Similar to the above task, commanding is also very important, and often is developed in sync with the telemetry portion, as there are often clear mappings (e.g. the distribution board takes commands to turn on/off individual power domains, but then also stores the last command received in telemetry that is sent back).
- Like telemetry via COSMOS, keep in mind that the commands in COSMOS are not just sent by an operator, they can also be sent by test or operations scripts. Make sure to expose commands in a way that makes sense from both perspectives.
- Commands are routed through the bowels of the debug system, with an "entity ID and opcode" combination that identifies each command. Please contact the CDH lead for help defining and using these properly. For general structure and handling, however, see ss_EPS_Dist. The implementation centers around another "segment" definition, CMD_SEGMENT (which does NOT have a header, as it is stripped out by the debug infrastructure before user code sees it), which is used to parse the incoming commands into the parameters that you define.
- COSMOS commands end up running on the interrupt "thread" when called, so be sure not to do too much processing on these.
- Note that the actual logic that each command performs should be broken out into one or more functions, so that when the same action is commanded by a different mechanism - say, COSMOS or the auto-sequencer or some error-handling state in your module - you won't have to copy/paste and open yourself up to all the issues that come up with that.
- This covers adding the ability to both send CAN messages from the MSP43x, specify receive filters, and successfully parse the received messages
- Here, the CAN wrapper generation tool comes into play, and all the supporting stuff. You will need to work with the CAN experts on the team to make sure any messages you need are defined properly in the DBC files, and those in turn are used to generate the wrappers that your code will use.
- The command function already written to support COSMOS-driven commanding can be simply called by the CAN bus listener when the correct CAN message comes through. Similarly, if the exact same information is desired over the CAN bus as is already available on backchannel (and therefore probably stored in a TLM_SEGMENT), the data can be copied over to each new CAN message sent.
- Note, however, that due to the different send frequencies between CAN and the backchannel (think a burst of status messages every 1-2 minutes for CAN vs. 1Hz for backchannel), often the desired value to be sent is based on the data analysis/stats library (which will soon be moved to dsbase). This small library manages a history buffer of your own creation, and allows for a) circular mgmt (so you can just keep adding values to it without worrying about buffer, b) averaging on demand, and c) persistent storage of min/max values (along with the ability to reset those). These values are likely ALSO interesting on the backchannel (especially as points of comparison), but the history buffers typically would not be, so structures and variables will need to be carefully laid out accordingly.
- Once a subsystem is to this point, it becomes much harder to say general things about what "ready to fly" means. Most of the key ones will be handling the special cases that arise out of several spacecraft-wide systems that will be implemented in the coming sprints:
- Resets and state determination: much general work, and some subsystem module-specific work, has to be done to help subsystems "recreate" their prior state (or ignore it, if that's a better course of action) when they are first powered-up, so that they can perform the correct actions. This will involve being very tactical about what values are stored in non-volatile memory, and how often those values get written.
- Dealing with PPT-sourced noice: the PPT - both when charging and during the firing event itself - causes a great deal of a few different kinds of noise throughout the spacecraft. It is still unclear how many protective measures will have to be adopted in order to protect the integrity of the integrated software/hardware stack in the face of PPT activity, but CAN messages (originally separate GPIO "sync pulses") will be used to at least notify all subsystem modules of the PPT firing schedule. Central code will be created to allow easy determination of where in that firing cycle the PPT is, and other shared code should make it at least somewhat easier to say things like "shut down all ADC reads during this portion of the cycle." Adding special-case code into the subsystem modules to deal with those periods will likely be one-off work for each subsystem module, however - e.g. what does the BDot controller do, exactly, when it can't count on the values coming off the magnetorquer.