thinkyhead

Coding Standards

Please follow the formatting guidelines and coding standards below when contributing code to Marlin. Pull requests that don’t follow the existing coding style closely may be postponed for cleanup. Your code reviewer should point out where changes are needed.

Coding Style

Indentation

Indentation is important for readability and maintainability of code, and provides guidance for naïve code editors (e.g., TextMate, Sublime, et. al.) to properly fold code blocks by level.

  • Entab lines with 2 spaces and don’t use tabs. Set your editor to use 2 Spaces! Tabs will bite you in the end.
  • All block elements should increase the indentation level, including #if blocks and other non-brace compiler blocks:
void myFunction() {
  if (myCondition == 0) {
    #ifdef PETER_PARKER
      slingWeb(100);
    #else
      findPhoneBooth();
    #endif
  }
}

Brace-style

Marlin uses a brace style intended to

  • show the folded code block at the end of its opening line: {(...),
  • maintain consistency and develop a single style habit, and
  • maximize the number of code lines on-screen.

If vertical spacing makes code more readable, add one extra blank line rather than using a different brace style.

  • Known by the Ancients as “One True Brace Style”
  • Place opening braces at the end of the line: if (a == 1) {
  • Do the same for a declaration line: void pizza(int slices) {
  • Vertically align closing braces to the opening line.

Here’s an example of 1TBS style applied to a faux function:

void my_function(void) {
  if (...) {
    ...
  }
  else {
    ...
  }

  switch (val) {
    case 1: SERIAL_CHAR('Q'); break;
    case 2: SERIAL_CHAR('T'); break;
  }
}

Spacing

  • One space after control keywords:
    if (…), while (…), do {…} while(…), switch (…) etc.
  • No space is needed for sizeof() and other “function-like” language features.
  • No space between a function and its arguments: val = myFunction(…);
  • No spaces around . or -> operators: the_place = state->parks[echo];
  • No space between a cast and its target: old_state = (int)state;
  • Use one space around (on each side of) most binary and ternary operators:
    myVar = aVar + bVar * cVar;
    myVal = (a * b + b * c);

Trailing Whitespace

Don’t leave trailing whitespace at the ends of lines. Some editors will auto-indent new lines, leaving extra whitespace behind on blank lines. As a result, you end up with lines containing trailing whitespace.

Git can warn you about patches that introduce trailing whitespace, and optionally strip the trailing whitespace for you; however, if applying a series of patches, this may make later patches in the series fail by changing their context lines.

Commenting

Comments are good, but avoid over-commenting. Never try to explain how your code works in a comment: it’s much better to write the code so that the working is obvious, and it’s a waste of time to explain badly written code.

Generally, you want your comments to explain what your code does, not how. Keep comments inside a function body short. If a function is so complex that you need to separately comment parts of it, consider splitting it up into simpler units. Make small comments to note or warn about something particularly clever (or ugly), but avoid excess. Reserve detailed comments for the head of the function, explaining what it does, and possibly why it does it.

  • Use Doxygen-style comments for functions, classes, and other defined entities, and concentrate documentation in the .h files.
/**
 * This is the preferred style for multi-line comments in the
 * Marlin Firmware source code. Please use it consistently.
 *
 * This mimics Doxygen/HeaderDoc style, and once keywords are added
 * we'll be able to auto-generate code documentation and provide more
 * complete development guidance.
 */
  • Use C++ single-line style with // for comments under 3 lines.
// A short comment that takes up only a line or two
// should just use end-of-line comment style.

Names and Symbols

Filenames

Filenames for Marlin code should favor lowercase_with_underscores.ext format. Contributed code will follow its own standard.

  • use .cpp for C++ sources
  • use .c for C only sources
  • use .h for headers of all types

Directories

  • Lowercase names.
  • Marlin 1.0.x and 1.1.x retain a flat file layout
  • Marlin 1.2.x and up adopts a hierarchical file layout

Capitalization

For Marlin variables, data members, functions, and methods use lowercase_with_underscores. Use camelCase names only when class names and methods already uses that format. Marlin classes may use MyClassName format or my_class_name. Core classes tend to use camel-case, with general-purpose classes using underscore format.

  • my_function_name(int in_integer, float in_float=0.0)
  • MyClass, classMethod, classData
  • local_variable, global_variable, const_value
  • MACRO_NAME – anything created with #define
  • EnumeratedType

Libraries

Whenever possible, use the functions supplied by avr-libc or Arduino bundled libraries. Any libraries required to compile Marlin should be included in the package so that they are guaranteed to be compatible versions.

Language Features

Marlin is written in C/C++ and needs be able to compile with the supplied Makefile or an up-to-date version of Arduino. With Marlin 1.1 we now support building with Arduino IDE, Teensyduino, PlatformIO, make, and cmake.

Going forward, Marlin does not need to be backward-compatible with older (pre-2017) toolchains. The minimum requirement for Marlin 1.1.x is Arduino IDE 1.6.8.

  • Do not use extended C++ features like:
    • Exceptions (throw / catch)
    • Virtual functions / classes
    • Standard Template Library (STL)
  • Do use modern C++11 features like:
    • constexpr values and functions.
    • static_assert(test,"error") to sanity-check float and constexpr values.

Primitive Types

  • Favor bit-size types like uint8_t and int32_t over short, int, and long. This helps to keep behavior consistent across architectures.
  • AVR recasts double as float, so both are 32 bits long. Favor float and avoid double unless the extra precision is needed on a 32-bit architecture.

Memory Usage

  • Dynamic allocation (malloc(), free(), new, delete) is verboten! There may be some flexibility for certain 32-bit features.
  • Avoid unconstrained recursion (e.g., calling idle() from idle()) so the stack won’t explode.
  • Avoid using globals and static locals because SRAM is a precious resource on many boards.
  • Use PSTR and PROGMEM macros to keep strings in Program Memory.

Minimize Repetition

When possible, use macros, small functions, and other clever techniques to avoid redundancy. For example, instead of this…

#if ENABLED(FEATURE_ONE)
  const char blue = '1';
#else
  const char blue = '0';
#endif

…do this…

const char blue =
  #if ENABLED(FEATURE_ONE)
    '1'
  #else
    '0'
  #endif
;

…or, better yet, use this…

const char blue = TERN(FEATURE_ONE, '1', '0');

Avoid Expensive Code

  • millis() can be expensive so put it in a const millis_t var if you need to use the value more than once. (And always use the ELAPSED/PENDING macros - see below.)
  • Pre-calculate instead of calculating on the fly, when possible.
  • Use multiplication (of the reciprocal) instead of division, when possible.
  • Most code doesn’t need to be optimized for speed, so favor smaller code.

Best-Practices for #include

  • Follow best practices for #include.
    • Only include the headers needed to provide the definitions required by the current source file. This helps provide an accurate view of dependencies.
    • Include Marlin.h only when you need its externs. Don’t include Marlin.h when just MarlinConfig.h (or MarlinConfigPre.h) will do.
    • Included headers should be arranged in the following order:
      • MarlinConfig.h (or MarlinConfigPre.h), if configuration values are needed for #if or other uses.
      • After the main #if condition, include the file’s own corresponding header.
      • Next, arrange project headers to prevent hiding broken dependencies. For example, if grape.h depends on orange.h, try to include grape.h before orange.h so that if grape.h fails to include orange.h an error will be thrown.
      • Last come system and library headers, such as <Arduino.h>, <inttypes.h>, and <u8glib.h>.

Marlin-specific Conventions

Preprocessor directives

  • Use #define instead of const for configurable values
  • Don’t use #if / #endif for commenting-out unused, old or broken code. We have a git repository! If it’s obsolete, delete it.
  • Use #if ENABLED(FEATURE_NAME) / #endif to compile code for an enabled feature. (Using these macros allows features to be set externally.)
  • Use #if DISABLED(FEATURE_NAME) / #endif to compile code for a disabled feature. See more macros below.
  • Use #define macros to avoid repeating boilerplate code.
    Consider both readability and maintainability.
  • Label #endif with the opening #if condition(s) if the block is over ~15 lines. Make the label compact. For example, #endif // SDSUPPORT || ULTRALCD.

Macros

Marlin provides several shorthand macros - mostly in the macros.h file - that are used throughout the code. Get to know and use them. Here are some of the most common:

Configuration Tests

Macro Description
ENABLED(OPTION)/DISABLED(OPTION) Test whether an option is on/off. These macros are required so that make can set options.
ANY(...) True if any of the listed options is enabled.
MANY(...) True only if more than one of the listed options is enabled.
ALL(...) True only if all of the listed options are enabled.
NONE(...) True only if none of the listed options is enabled.
COUNT_ENABLED(OPT1, OPT2, ...) Count the number of options in the list that are enabled.

Ternary Macros

Since Marlin 2.0 all macros listed here (unless marked “Precompiler only”) can be used anywhere in code where a value would be used. This is a really handy capability and allows the code to be a lot more concise. The ENABLED and related macros simply emit an integer 0 or 1. A more interesting macro is TERN and its relatives TERN0, TERN1, TERN_, IF_ENABLED, and IF_DISABLED.

Macro Description
TERN(OPTION, T, F) If OPTION is enabled emit T otherwise emit F.
TERN0(OPTION, T) If OPTION is enabled emit T otherwise emit 0 (false).
TERN1(OPTION, T) If OPTION is enabled emit T otherwise emit 1 (true).
TERN_(OPTION, T) If OPTION is enabled emit T otherwise emit nothing.
IF_ENABLED(OPTION, T) Alias for TERN_.
IF_DISABLED(OPTION, F) If OPTION is disabled emit F otherwise emit nothing.
SUM_TERN(OPTION, F) If OPTION is disabled emit F otherwise emit nothing.
PLUS_TERN0(O,A) Emit +(A) for enabled option O, otherwise nothing.
MINUS_TERN0(O,A) Emit -(A) for enabled option O, otherwise nothing.
SUM_TERN(O,B,A) Emit (B)+(A) for enabled option, otherwise just (B).
DIFF_TERN(O,B,A) Emit (B)-(A) for enabled option, otherwise just (B).

Here are some ternary macro examples:

millis_t ms = millis() + TERN(USE_LONG_TIMEOUT, 200, 100); // A longer timeout
bool is_ready = TERN1(HAS_READY_STATE, get_ready_state()); // Get state (or assume true)
TERN_(EEPROM_SETTINGS, settings.read()); // Read settings (or not)

Check Defined Pins

Macro Description
PIN_EXISTS(NAME) True if the pin is defined. Precompiler only. (Takes a pin name minus _PIN.)
PINS_EXIST(...) True if all the listed pins are defined. Precompiler only. (Takes pin names minus _PIN.)
ANY_PIN(...) True if any of the listed pins is defined. Precompiler only. (Takes pin names minus _PIN.)

FastIO

Marlin uses FastIO macros to interface with pins that are known and fixed at compile-time. This results in much faster I/O, especially on AVR boards where every cycle counts. For more details about Marlin’s use of FastIO see this page.

Macro Description
SET_INPUT(PIN) Set a digital pin to INPUT mode.
SET_INPUT_PULLUP(PIN) Set a digital pin to INPUT_PULLUP mode.
SET_INPUT_PULLDOWN(PIN) Set a digital pin to INPUT_PULLDOWN mode (if supported, else INPUT).
SET_OUTPUT(PIN) Set a digital pin to OUTPUT mode.
SET_PWM(PIN) Set a digital pin to PWM mode (if supported, else OUTPUT).
SET_PWM_OD(PIN) Set a digital pin to PWM open-drain mode (if supported, else PWM or OUTPUT).
READ(PIN) Read the state of a digital pin. Returns either HIGH or LOW.
WRITE(PIN, STATE) Set a digital pin’s state to either HIGH or LOW.
OUT_WRITE(PIN, STATE) Set a digital pin’s state to either HIGH or LOW.

Loops Shorthand

Macro Description
LOOP_ABC(VAR) Loop over the first (up to) three axes
LOOP_NUM_AXES(VAR) Loop over all axes except for E
LOOP_LOGICAL_AXES(VAR) Loop over all axes including E as a single axis
LOOP_DISTINCT_AXES(VAR) Loop over all axes, including multiple E (for settings, saved tool state, etc.)
LOOP_DISTINCT_E(VAR) Loop over all E (tool) indexes
EXTRUDER_LOOP() Loop integer var ‘e’ over all extruder indexes.
HOTEND_LOOP() Loop integer var ‘e’ over all hotend indexes.

REPEAT

The REPEAT macros are used to emit another macro repeatedly, passing an index and optional arguments. In this example, REPEAT emits a whole line of code for each serial port:

  #define _S_FLUSH(N) if (portMask.enabled(output[N])) serial##N.flush();
  REPEAT(NUM_SERIAL, _S_FLUSH);
Macro Description
REPEAT(N,OP) Emit the OP macro N times with zero-based index.
REPEAT_1(N,OP) Emit the OP macro N times with one-based index.
REPEAT_S(S,N,OP) Do a zero-based REPEAT, skipping S elements.
REPEAT2(N,OP,V...) Emit the OP macro N times with zero-based index and arguments.
REPEAT2_S(S,N,OP,V...) Do a zero-based REPEAT2, skipping S elements.
RREPEAT(N,OP) REPEAT for usage within another REPEAT*.
RREPEAT_S(S,N,OP) REPEAT_S for usage within another REPEAT*.
RREPEAT2(N,OP,V...) REPEAT2 for usage within another REPEAT*.
RREPEAT2_S(S,N,OP,V...) REPEAT2_S for usage within another REPEAT*.

MAP

The MAP macro works like REPEAT, but takes a list of things to pass to the emitted macro instead of a number. In this example, MAPLIST emits a comma-separated list of items for the axes listed in ALL_AXIS_NAMES:

  #define TMC_SW_DETAIL(A) { TMC_SW_DETAIL_ARGS(A) }
  constexpr SanitySwSerialDetails sanity_tmc_sw_details[] = {
    MAPLIST(TMC_SW_DETAIL, ALL_AXIS_NAMES)
  };
Macro Description
MAP(OP,VALS...) Emit the OP macro with each of the given VALS.
MAPLIST(OP,VALS...) Emit OP with each of the given VALS, comma-separated.

Character Test

Macro Description
NUMERIC(c) True if a character is numeric: 0123456789
DECIMAL(c) True if a character is decimal: 0123456789.
NUMERIC_SIGNED(c) True if a character is signed numeric: 0123456789+-
DECIMAL_SIGNED(c) True if a character is signed decimal: 0123456789+-.

Lists and Arrays

Macro Description
COUNT(array) The number of items in a defined array.
LIST_N(N,VALS...) Reduce a prepopulated list to size N (based on an integer option like EXTRUDERS).
LIST_N_1(N,VAL) Generate a list of size N filled with a single value.
ARRAY_N(N,VALS...) Generate a C style array of size N from a prepopulated list.
ARRAY_N_1(N,VAL) Generate a C style array of size N filled with a single value.
ARRAY_BY_EXTRUDERS(V...) Reduce a prepopulated list to EXTRUDERS items.
ARRAY_BY_EXTRUDERS1(v1) Generate a list of EXTRUDERS items filled with a single value.
ARRAY_BY_HOTENDS(V...) Reduce a prepopulated list to HOTENDS items.
ARRAY_BY_HOTENDS1(v1) Generate a list of HOTENDS items filled with a single value.

Time Comparison

Use the following macros when comparing two millis count values:

  • PENDING(ms,time): Relative to ms, time is pending.
  • ELAPSED(ms,time): Relative to ms, time has elapsed.

When using ELAPSED and PENDING, always compare against the next time rather than the last time. This…

const millis_t ms = millis();
if (ELAPSED(ms, next_event_ms)) {
  next_event_ms = ms + TIME_INTERVAL;
  ...
}

…will be smaller and faster than this…

const millis_t ms = millis();
if (ELAPSED(ms, last_event_ms + TIME_INTERVAL)) {
  last_event_ms = ms;
  ...
}

Serial Macros

The serial.h file also includes several macros to make it easier to create PROGMEM strings and print them to the serial output. Below are a few of them. See the serial.h file for others.

Macro Description
SERIAL_ECHO_START() Send “echo:” to the serial output.
SERIAL_ERROR_START() Print “error:” to the serial output.
SERIAL_ECHO("hello") Print an ASCII string stored in SRAM to serial out.
SERIAL_ECHOLN("hello") Print an ASCII string stored in SRAM to serial out, appending a newline.
SERIAL_ECHOPGM("hello") Wrap the given ASCII string in PSTR and print it to serial out.
SERIAL_ECHOLNPGM("hello") Wrap the given ASCII string in PSTR and print it to serial out, appending a newline.
SERIAL_ECHOPAIR("Hello:",val) Wrap an ASCII string in PSTR; print it and a value to serial out.
SERIAL_ECHOLNPAIR("Hello:",val) Wrap an ASCII string in PSTR; print it, a value, and a newline to serial out.
STRINGIFY(DEFINE) Resolve a define to a quoted string. (If undefined, the name of the define.)

Maths macros

Use the following macros in place of their normal lower-case versions. These ensure the smaller 32-bit float on all architectures. The initial 32-bit targets for Marlin, while significantly faster, do not have a floating-point unit either, so float maths is more compatible while not sacrificing performance.

Macro Description
FIXFLOAT(N) Add a tiny value to a float to fix rounding errors (e.g., for display).
NEAR_ZERO(V)/UNEAR_ZERO(V)/NEAR(V1,V2) True if a float value is near zero or another value.
RECIPROCAL(N) The reciprocal of a value, but return 0.0 (not infinity) for 0.0.
RADIANS(d)/DEGREES(r) Convert degrees to radians and vice-versa.
HYPOT2(x,y) The hypotenuse-squared of a right triangle with legs x,y.
HYPOT(x,y) The hypotenuse of a right triangle with legs x,y.
ATAN2(y, x) atan2f
FABS(x) fabsf
POW(x, y) powf
SQRT(x) sqrtf
CEIL(x) ceilf
FLOOR(x) floorf
LROUND(x) lroundf
FMOD(x, y) fmodf

Other Shorthand

Macro Description
WITHIN(val,low,high) Check that a value is within a given range, inclusive.
NOLESS(var,min) Change a variable, if needed, so it is not smaller than the given value.
NOMORE(var,max) Change a variable, if needed, so it is not larger than the given value.
FORCE_INLINE Force a function or method to be compiled inline.
NOOP A do-nothing macro to use for empty macro functions.

Adding a New Feature

Since Marlin needs to runs on the most modest hardware, much care has been taken to keep code size small and avoid overtaxing the CPU. AVR and some 32-bit CPUs have no FPU, so it’s best to avoid floating point operations whenever possible, and add-on features should also conserve SRAM. Right out of the gate, the default configuration of Marlin 1.1 uses over 2.6K of SRAM, and won’t fit on an UNO.

  • #define is used liberally, especially for configuration values
  • Use #define MYFEATURE for feature switches.
  • Feature settings have some flexibility, and can have values.
  • Use #if ENABLED(MYFEATURE) / #if DISABLED(MYFEATURE) rather than #ifdef or #if defined(). The ENABLED/DISABLED macros allow features to be overridden by make.
  • Indent #if…/#endif blocks and their contents with the rest of the cascade. This allows editors that only have naive code-folding to fold blocks properly.
  • Add a comment: #endif // MYFEATURE — but only if the #endif is far away (lets say, over 10 lines) from the starting #if.

New Feature Example

In Configuration.h:

// Enable this to make something new happen
#define MYFEATURE
#if ENABLED(MYFEATURE)
  #define MYFEATURE_SETTING 12.5
  #undef OVERRIDDEN_FEATURE // This won't be needed with MYFEATURE
#endif

In SanityCheck.h:

/**
 * My feature
 */
#if ENABLED(MYFEATURE) && ENABLED(INCOMPAT_FEATURE)
  #error MYFEATURE is not compatible with INCOMPAT_FEATURE
#endif

In Conditionals.h:

/**
 * My feature
 */
#if ENABLED(MYFEATURE)
  #undef OVERRIDDEN_FEATURE // This feature is disabled by MYFEATURE
  #undef OVERRIDDEN_SETTING // This setting will always be 1234 with MYFEATURE
  #define OVERRIDDEN_SETTING 1234
#endif

In MarlinCore.cpp, for example:

// My Feature, when Your Feature is disabled
#if ENABLED(MYFEATURE) && DISABLED(YOURFEATURE)
  my_feature_function(); // Run my feature, possible an inline function taking refs
  #if ENABLED(HISFEATURE)
    ...
    call_something();
    ...
  #else // !HISFEATURE
    ...
    call_something_else();
    ...
  #endif // !HISFEATURE
#endif // MYFEATURE