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-checkfloat
andconstexpr
values.
-
Primitive Types
- Favor bit-size types like
uint8_t
andint32_t
overshort
,int
, andlong
. This helps to keep behavior consistent across architectures. - AVR recasts
double
asfloat
, so both are 32 bits long. Favorfloat
and avoiddouble
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()
fromidle()
) so the stack won’t explode. - Avoid using globals and
static
locals because SRAM is a precious resource on many boards. - Use
PSTR
andPROGMEM
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 aconst millis_t var
if you need to use the value more than once. (And always use theELAPSED
/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 includeMarlin.h
when justMarlinConfig.h
(orMarlinConfigPre.h
) will do. - Included headers should be arranged in the following order:
-
MarlinConfig.h
(orMarlinConfigPre.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 onorange.h
, try to includegrape.h
beforeorange.h
so that ifgrape.h
fails to includeorange.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 ofconst
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 toms
,time
is pending. -
ELAPSED(ms,time)
: Relative toms
,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()
. TheENABLED
/DISABLED
macros allow features to be overridden bymake
. - 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