Coding Standards

Code Organization

In general, we try to encapsulate components of the firmware as independent modules. We have a common library that consists of a number of peripheral drivers and frameworks that are shared across the car.

All modules and drivers should have a simple, common API which can easily be ported across multiple platforms. Our drivers are designed to abstract most of the heavy lifting away from the user, allowing the development of systems without needing to worry about the inner workings of each component.

Version Control

We use git for our version control. There are a number of resources available for git, such as this simple guide.

Our firmware is stored on GitHub as an open-source project. To get involved, please speak to a software lead to be added to our GitHub organization.

In general, we follow the basic git flow. The master branch should contain stable, vetted code. Development branches should be created on a feature-by-feature basis, and a pull request to master should be created when the feature is complete. Please squash your branch before merging to master.

Coding Style

At Midnight Sun, we use a variant of the Google C++ Style Guide. In general:


GoodNot GoodNotes

Include guards

#pragma once
#ifndef MODULE_H
#define MODULE_H
...
#endif

Although #pragma once isn't officially part of the C standard, it's widely supported and easier to use than include guards.

Note that include guards are used to prevent double inclusion of headers.


Conditionals and loops

if (cond) {
  ...
} else if (cond) {
  ...
} else {
  ...
}

OR

for (int i = 0; i < x; i++) {
  ...
}
if(cond)
{
}
else
{
}

OR

if (cond)
  func();

OR

if(cond){
  ...
}

Braces are always required, even for single-line statements. They should be on the same line as the conditional.

Adequate spacing should be provided between statements.

In general, please be consistent.

Indentation2 space indentsAnything elseOnly use spaces. Do not use tabs. Use Unix-style line endings.
Variable namesuint8_t descriptive_name = 0;int a = 0;

Prefer descriptive, reasonable length variable names.

Note that function and variable names follow the underscore_lowercase naming convention.

Variable names

(static)

static uint8_t s_descriptive_name = 0;
static void prv_func(void) {
// do stuff
}
Anything else

A static variable that is global to a file should be prefixed with s_.

You should not have static variables in a function, since your functions should not be maintaining state (unless you have a really good reason).

Function names

(public)

void module_func(void);void func(void);Prefix public functions with the module's name.

Function names

(private)

static void prv_func(void);static void func(void);Prefix private functions with prv_ to denote that it is not visible.

Macros

#define MODULE_MACRO(x) ((x)*2)
#define macro(x) x*2

Avoid macros as functions, but they should be ALL_CAPS and their parameters should be enclosed in brackets if used.

Macros should also be prefixed with the module's name.

Enums

typedef enum {
  MODULE_ENUM_TYPE_A = 0,
  MODULE_ENUM_TYPE_B,
MODULE_ENUM_TYPE_C,
NUM_MODULE_ENUM_TYPES,
} ModuleEnumType;
enum enum_type {
  a,
  b,
  c,
};

typedef enum {
TYPE_FOO = 0,
TYPE_BAR
} ModuleEnumType;

Typedef enums because we generally use them to group bitmasks, and it would get messy quickly.

Like all types and functions, enums should be prefixed with their module name. This goes for both the typedef and actual enum names.

Declare them in ALL_CAPS prefixed with the enum name. The first entry should have an initial value (usually 0). The final entry in any enum should be NUM_MODULE_ENUM_TYPES. Note the plurality of the type and the prefix NUM_. This allows for the writing of nice guard clauses.

Datatypesuint8_t, uint16_t, int16_t, etc.char, int, short, etc.Use explicit standard datatypes.
Structs
typedef struct ModuleFoo {
  ...
} ModuleFoo;
struct Foo {
  ...
};

Use UpperCamelCase for struct definitions.

Typedef'd structs make it easier to think about them as objects, and provide some abstraction.

The original struct should still be named to allow for forward declarations.

Structs should also be prefixed with the module name.

Struct/array initialization
GPIOAddress address = { 1, 0 };
int a[] = { 0, 1, 2, 3 };

// GNU Style
GPIOAddress addresses[] = {
[GPIO_PIN1] = { .port = 1, .pin = 1 }, //
[GPIO_PIN2] = { .port = 1, .pin = 2 }, //
};
GPIOAddress address = {1, 0};
int a[] = {0, 1, 2, 3};
 

Structs and arrays should have 1 space between each brace and the first/last elements.

GNU Style designated initializers are encouraged as they are more explicit and ensure consistency in the result of changes in definitions of structs/enums.

The trailing // is used to help clang-format align the elements as a single column as opposed to bin-packing.


As of August 2017 there is a bug in clang-format so that

{.port = 1, .pin = 1 } is the result of formatting this has been fixed upstream and should be resolved in the next release.

Line lengthsAround 100 charactersAbove 100 charactersTry to keep line lengths reasonable. Around 100 characters is a good length.
Bitwise operationsREG |= BIT1 | BIT2;REG |= BIT1 + BIT2;

Make use of bitwise operations whenever working with bitmasks.

Use macro or enums to define bitmasks.

Try to keep the order of operations from MSB to LSB.

Comments

// This is a well-formatted comment.

// |a| does foo.

int8_t a = 0;  // This is also ok.

/* We do not use multi-line comments */

Use the // syntax for comments.

Comments can also be trailing to a line.

Comments width should be 100, avoid putting unnecessary line breaks as it makes comments hard to read.

When referencing a variable within a comment it is preferred to surround it with |variable|.

Pointer formatting
int *pointer = NULL;
void func(int *x, int *y);
int* pointer = NULL;
int * x = NULL;
int *x, y;
void func(int * x, int* y);

The asterisk should always be adjacent to the variable.

Do not declare multiple variables in the same declaration if any are pointers.

Header Layout
#pragma once
// Module Summary (short)
// Requires ... to be initialized
//
// Module description - intent, intended usage, etc.
#include ...

typedef ...

// Function purpose and specific parameter details
void module_foo(void);

We state any modules that must be initialized before the module can be used.

The module description should explain the intent behind the module, its expected usage, and any contracts or side-effects (ex. event data, interrupts, etc). Do not explain how your module works internally (that can go in the source file if necessary).

Finally, keep function comments short. Be sure to state any bounds or expectations for parameters, such as requiring persistance.

Type Naming
// Memory of *Storage must persist. 
typedef struct *Storage {
// ...
} *Storage;

// Memory of *Settings can be ethereal
typedef struct *Settings {
// ...
} *Ssettings;

Using the word Storage in a type explicitly requires it to persist once initialized. The word Settings implies that the underlying memory is ethereal. If the word Storage is not used and memory is expected to persist this must be very clearly indicated in the documentation for that function.
Comparison with NULL / 0

if (val == NULL)

or

if (val != NULL)

if (!val)

or

if (val)

The 'good' way is more clear and leads to more readable code. Fairly arbitrary decision, but we'll stick with this for consistency.
Main event loop
while (true) {
while(event_process(&e) == STATUS_CODE_OK) {
// Events to process
}
}
anything else

For consistency.

Include order
// First the header you're implementing or testing
// (if applicable)
#include "my_modules_header.h"

// Then C standard library headers alphabetically
#include <stdbool.h>
#include <stdint.h>

// Then all other headers sorted alphabetically
#include "gpio.h"
#include "log.h"
#include "my_other_project_header.h"
#include "some_library_header.h"
Any other order

Put your includes into three sections separated by blank lines. The first is for the header of the module you're implementing or testing, if applicable. Then, place C standard library headers in angle brackets, sorted alphabetically. Finally, place all other library and project headers, sorted alphabetically.

This format makes it easy to see what headers have been included.


Coding Style Enforcement

We use clang-format to enforce most of the style choices. Run it over the entire firmware repository by invoking 

scons format

from the root directory. This will fix almost every issue with spacing, alignment and line-length.


In addition to clang-format we use our own fork of cpplint to enforce most of the other style regulations. This is run via our Travis continuous integration tool at code-review time but can also be invoked manually by calling 

scons lint

from the root directory. This will warn of any formatting errors.


Any issues not caught by these tools will be enforced by code-review or the compiler.