Embedded Software Coding Style

MISRA 2004 Compliance

ASF4 follows the MISRA 2004 rules with the CMSIS-CORE exceptions.

Function and Variable Names

  • Functions and variables are named using all lower case letters: [a-z] and [0-9]

  • Underscore '_' is used to split function and variable names into more logical groups

Example

void this_is_a_function_prototype(void);

Rationale

All-lowercase names are easy on the eyes, and it is a very common style to find in C code.

Constants

  • Constants are named using all upper case letters: [A-Z] and [0-9]

  • Underscore '_' is used to split constant names into more logical groups

  • Enumeration constants shall follow this rule

  • Constants made from an expression must have braces around the entire expression; single value constants may skip this

  • Constants must always be defined and used instead of "magic numbers" in the code

  • Definitions for unsigned integer constants (including hexadecimal values) must use a "u" suffix

  • For hexadecimal values, use upper case A-F

  • Samme rules apply to enumerations as they are constants too

Example

            #define BUFFER_SIZE             512
            #define WTK_FRAME_RESIZE_WIDTH  (WTK_FRAME_RESIZE_RADIUS + 1)
            enum buffer_size = {
                  BUFFER_SIZE_A = 128,
                  BUFFER_SIZE_B = 512,
            };
          

Rationale

Constants shall stand out from the rest of the code, and all-uppercase names ensure that. Also, all-uppercase constants are very common in specification documents and data sheets, from which many such constants originate. MISRA rule 10.6. MISRA specifies "U", but this is less readable than "u" and both are accepted. The braces around an expression are vital to avoid an unexpected evaluation. For example, a constant consisting of two variables added together, which are later multiplied with a variable in the source code.

            #define NUM_X_PINS            (24u)
            #define XEDGEGRADIENT_MASK    (0x3Fu)
            #define NUM_MATRIX_PINS       (NUM_X_PINS + NUM_Y_PINS)
          
Do not include cast operations types in constant definitions:
#define THIS_CONSTANT  (uint8_t)(0x0Fu)

Inline Constants

Where a constant value is only to be used once within a particular function, and is not derived from or related to functionality from another module, it should be defined as an appropriately typed const (using variable naming conventions) within that function, e.g.:

          void this_function(void)
          {
                uint8_t const num_x_pins = 24u;  /* max number of X pins */
                ...
        

Rationale

Keeps constant definition close to where it is used and in appropriate scope; avoids a large block of often unrelated macro definitions.

Variables

Variables should be defined individually on separate lines. Examples:

          /* Do like this */
          uint8_t x_edge_dist;
          uint8_t y_edge_dist;
          /* Don't do like this */
          uint8_t x_edge_dist, y_edge_dist;
        

Constant or volatile variables should be defined with the qualifier following the type. Examples:

          /* Do like this */
          uint8_t const x_edge_dist;
          uint8_t volatile register_status;
          /* Don't do like this */
          const uint8_t x_edge_dist;
        
Variables must only be defined at the start of a source code block (denoted by {} braces). Examples:
void this_function(void){    uint8_t x_edge_dist;for (line_idx = LINE_IDX_MIN; line_idx <= LINE_IDX_MAX; line_idx++){    bool_t this_flag = FALSE;

Type Definitions

  • stdint.h and stdbool.h types must be used when available

  • Type definitions are named using all lower case letters: [a-z] and [0-9]

  • Underscore '_' is used to split names into more logical groups

  • Every type definition must have a trailing '_t'

Example

            typedef uint8_t buffer_item_t;
          

Rationale

stdint and stdbool ensures that all drivers on all platforms uses the same set of types with clearly defined size. Trailing _t follows the same convention used in stdint and stdbool, which clearly differentiate the type from a variable.

Structures and Unions

  • Structures and unions follow the same naming rule as functions and variables

  • When the content of the struct shall be kept hidden for the user, typedef shall be used

Example

            struct cmd {
                    uint8_t length;
                    uint8_t *payload;
                };
            union cmd_parser {
                    cmd_t cmd_a;
                    cmd_t cmd_b;
                };
            typedef struct {
                    void *hw;
                } device_handle_t;
          

Functions

Lay out functions as follows, explicitly defining function return type and naming any parameters, and where possible writing them all on the same line:

          return_type_t function_name(param1_t param1, param2_t param2)
          {
              statements;
          }
        

For a function with a long definition, split it over several lines, with arguments on following lines aligned with the first argument. Example:

          uint16_t this_function(uint8_t uint8_value,
                                 char_t *char_value_ptr,
                                 uint16_t *uint16_t_value_ptr);
        

If a function has more than three parameters, consider passing them via a pointer to a structure that contains related values, to save compiled code and/or stack space. Using a structure in this way also avoids having to update a function prototype, and therefore having to update calling software, should additional parameter values be needed. Use "const correctness" to protect parameters passed via pointer from being changed by a function. Example:

          void this_function(uint8_t            *value_ptr,        /* pointer to value */
                             uint8_t      const *const_ptr,        /* pointer to const value */
                             uint8_t      *const const_value_ptr,  /* const pointer to value */
                             uint8_t      const *const_const_ptr); /* const pointer to const value */
        

Return Statement

Every function should have only one return statement, unless there are specific and commented reasons for multiple return statements e.g. saving compiled code, clarifying functionality.

Rationale

MISRA rule 14.7 Easier debugging.

Do not embed conditional compilation inside a return statement. Examples:

            /* Don't do like this */
            return (
            #ifdef PRXXX_ADDR
                (uint32_t*)PRXXX _ADDR
            #else
                (uint32_t*)NULL_PTR
            #endif
            );
            /* Do like this */
            uint32_t* addr_ptr;
            ...
            #ifdef PRXXX_ADDR
            addr_ptr = (uint32_t*)PRXXX_ADDR;
            #else
            addr_ptr = (uint32_t*)NULL_PTR;
            #endif
            return (addr_ptr);
          

Function like Macro

  • Function like macros follow the same naming rules as the functions and variables. This way it is easier to exchange them for inline functions at a later stage.

  • Where possible, function like macros shall be blocked into a do {} while (0)

  • Function like macros must never access their arguments more than once

  • All macro arguments as well as the macro definition itself must be parenthesized

  • Function like macros shall be avoided if possible, static inline function shall be used instead.

  • Functional macros must not refer to local variables from their "host" calling function, but can make use of global variables or parameters passed into them.

Example

            #define set_io(id) do {         \
                   PORTA |= (1 << (id));    \
            } while (0)
          

Rationale

We want function like macros to behave as close to regular functions as possible. This means that they must be evaluated as a single statement; the do { } while (0) wrapper for "void" macros and surrounding parentheses for macros returning a value ensure this. The macro arguments must be parenthesized to avoid any surprises related to operator precedence; we want the argument to be fully evaluated before it's being used in an expression inside the macro. Also, evaluation of some macro arguments may have side effects, so the macro must ensure it is only evaluated once (sizeof and typeof expressions don't count).

Indentation

  • Indentation is done by using the TAB character. This way one ensures that different editor configurations do not clutter the source code and alignment.

  • The TAB characters must be used before expressions/text, for the indentation.

  • TAB must not be used after an expression/text, use spaces instead. This will ensure good readability of the source code independent of the TAB size.

  • The TAB size shall be fixed to four characters.

Example

            enum scsi_asc_ascq {
            [TAB]                                   [spaces]
                   SCSI_ASC_NO_ADDITIONAL_SENSE_INFO         = 0x0000,
                   SCSI_ASC_LU_NOT_READY_REBUILD_IN_PROGRESS = 0x0405,
                   SCSI_ASC_WRITE_ERROR                      = 0x0c00,
                   SCSI_ASC_UNRECOVERED_READ_ERROR           = 0x1100,
                   SCSI_ASC_INVALID_COMMAND_OPERATION_CODE   = 0x2000,
                   SCSI_ASC_INVALID_FIELD_IN_CDB             = 0x2400,
                   SCSI_ASC_MEDIUM_NOT_PRESENT               = 0x3a00,
                   SCSI_ASC_INTERNAL_TARGET_FAILURE          = 0x4400,
            };
          

Rationale

The size of the TAB character can be different for each developer. We cannot impose a fixed size. In order to have the best readability, the TAB character can only be used, on a line, before expressions and text. After expressions and text, the TAB character must not be used, use spaces instead. The entire point about indentation is to show clearly where a control block starts and ends. With large indentations, it is much easier to distinguish the various indentation levels from each others than with small indentations (with two-character indentations it is almost impossible to comprehend a non-trivial function). Another advantage of large indentations is that it becomes increasingly difficult to write code with increased levels of nesting, thus providing a good motivation for splitting the function into multiple, more simple units and thus improve the readability further. This obviously requires the 80-character rule to be observed as well. If you're concerned that using TABs will cause the code to not line up properly, see the section about continuation.

Text Formatting

  • One line of code, documentation, etc. should not exceed 80 characters, given TAB indentation of four spaces

  • Text lines longer than 80 characters should be wrapped and double indented

Example

            /* This is a comment which is exactly 80 characters wide for example showing. */
                   dma_pool_init_coherent(&usbb_desc_pool, addr, size,
                           sizeof(struct usbb_sw_dma_desc), USBB_DMA_DESC_ALIGN);
            #define unhandled_case(value)                                                 \
                   do {                                                                   \
                       if (ASSERT_ENABLED) {                                              \
                           dbg_printf_level(DEBUG_ASSERT,                                 \
                                   "%s:%d: Unhandled case value %d\n",                    \
                                   __FILE__, __LINE__, (value));                          \
                           abort();                                                       \
                       }                                                                  \
                   } while (0)
          

Rationale

Keeping line width below 80 characters will help identify excessive levels of nesting or if code should be refactored into separate functions.

Space

  • After an expression/text, use spaces instead of the TAB character

  • Do not put spaces between parentheses and the expression inside them

  • Do not put space between function name and parameters in function calls and function definitions

Example

            fat_dir_current_sect
                    = ((uint32_t)(dclusters[fat_dchain_index].cluster + fat_dchain_nb_clust - 1)
                              fat_cluster_size) + fat_ptr_data + (nb_sect % fat_cluster_size);
          

Continuation

  • Continuation is used to break a long expression that does not fit on a single line

  • Continuation shall be done by adding an extra TAB to the indentation level

Example

            static void xmega_usb_udc_submit_out_queue(struct xmega_usb_udc *xudc,
                           usb_ep_id_t ep_id, struct xmega_usb_udc_ep *ep)
            {
                   (...)
            }
            #define xmega_usb_read(reg) \
                           mmio_read8((void *)(XMEGA_USB_BASE + XMEGA_USB_\sectionreg))
          

Rationale

By indenting continuations using an extra TAB, we ensure that the continuation will always be easy to distinguish from code at both the same and the next indentation level. The latter is particularly important in if, while, and for statements. Also, by not requiring anything to be lined up (which will often cause things to end up at the same indentation level as the block, which is being started), things will line up equally well regardless of the TAB size.

Comments

Short Comments

Short comments may use the
/* Comment */(...)

Long Comments, Comment Blocks

Long (multiline) comments shall use the
/* * Long comment that might wrap multiple lines ... */(...)

ss_cs_comment_ind Comment Indentation

Indent comments with the corresponding source code. Example:

            for (;;) {
                /* get next token, checking for comments, pausing, and printing */
                process_token(token, token_size);
            }
          

Comment Placement

Put comments on lines by themselves, not mixed with the source code. Example:
/* get next token */process_token(token, token_size);

Rationale

Ease of readability, ease of maintenance, makes using file comparison tools easier.

Commenting long code

Where a source code block is typically more than 25 lines (so that matching opening/closing braces cannot be seen together onscreen), add a comment to the end brace of the block to clarify which conditional block it belongs to. Example:

            if (MAX_LINE_LENGTH <= line_length) {
              /* ...long block of source code... */
            } else { /* MAX_LINE_LENGTH > line_length */
              /* ...long block of source code... */
            } /* end if (MAX_LINE_LENGTH <= line_length) ...else... */
          

Braces

  • The opening brace shall be put at the end of the line in all cases, except for function definition. The closing brace is put at the same indent level than the expression

  • The code inside the braces is indented

  • Single line code blocks should also be wrapped in braces

Examples

            if (byte_cnt == MAX_CNT) {
                   do_something();
            } else {
                   do_something_else();
            }
          

Pointer Declaration

When declaring a pointer, link the star (*) to the variable.

Example

            uint8_t *p1;
          

Rationale

If multiple variable types are declared on the same line, this rule will make it easier to identify the pointers variables.

Compound Statements

  • The opening brace is placed at the end of the line, directly following the parenthesized expression

  • The closing brace is placed at the beginning of the line following the body

  • Any continuation of the same statement (for example, an 'else' or 'else if' statement, or the 'while' in a do/while statement) is placed on the same line as the closing brace

  • The body is indented one level more than the surrounding code

  • The 'if', 'else', 'do', 'while', and 'switch' keywords are followed by a space

  • Where an else if() clause is used, there must be a closing else clause, even if it is empty. MISRA rule 14.10.

Example

            if (byte_cnt == MAX1_CNT) {
                do_something();
            } else if (byte_cnt > MAX1_CNT) {
                do_something_else();
            } else {
                now_for_something_completely_different();
            }
            while (i <= 0xFF) {
                ++i;
            }
            do {
                ++i;
            } while (i <= 0xFF);
            for (i = 0; i < 0xFF; ++i) {
                do_something();
            }
            /* Following example shows how to break a long expression. */
            for (uint8_t i = 0, uint8_t ii = 0, uint8_t iii = 0;
                    (i < LIMIT_I) && (ii < LIMIT_II) && (iii == LIMIT_III);
                    ++i, ++ii, ++iii) {
                do_something();
            }
          

Switch-case Statement

  • The switch block follows the same rules as other compound statements

  • The case labels are on the same indentation level as the switch keyword

  • The break keyword is on the same indentation level as the code inside each label

  • The code inside each label is indented

  • The switch-case shall always end with a default label

  • Labels may be shared where the same statements are required for each

  • Should primarly be used for flow control; with any complex processing inside each case being moved to (inline if appropriate) functions

Example

            switch (byte_cnt) {
            case 0:
                ...
                break;
            case 1:
            case 2:
                ...
                break;
            default:
                ...
            }
          

"Fall-through" Switch-cases

Avoid using "fall-through" from one case to a following case where it saves compiled code and/or reduces functional complexity, it may be used but must be clearly commented and explained:

          case VALUE_1:
              statements;
              /* no break; - DELIBERATE FALL-THROUGH TO VALUE_2 CASE TO REDUCE COMPILED CODE SIZE */
          case VALUE_2:
              statements;
              break;
        

Goto Statments

The goto statement must never be used. The only acceptable label is the default: label in a switch() statement.

Operators

Do not add spaces between an unary operator (-, !, ~, &, *, ++, etc.) and its operand. Example:

          /* Do like this */
          x = ++y;
          /* Don't do like this */
          x = ++ y;
        

Add a space on either side of a binary operator. Examples:

          a = b + c;
          a *= b;
        

Restrict use of the ternary operator to simple assignments e.g.:

          range_setting = (5u <= parm_value) ? RANGE_IS_HIGH : RANGE_IS_LOW;
        

Use braces to clarify operator precedence, even if not strictly required. Example:

          a = b + (c * d);
        

Preprocessor Directives

  • The # operator must always at the beginning of the line

  • The directives are indented (if needed) after the #

Example

            #if (UART_CONF == UART_SYNC)
            #    define INIT_CON       (UART_EN | UART_SYNC | UART_PAUSE)
            #elif (UART_CONF == UART_ASYNC)
            #    define INIT_CON       (UART_EN | UART_ASYNC)
            #elif (UART_CONF==UART_PCM)
            #    define INIT_CON       (UART_EN | UART_PCM | UART_NO_HOLE)
            #else
            #    error Unknown UART configuration
            #endif
          

Comment #endif statements to clarify which conditional block they belong to, regardless of how many lines of source code the block comprises of. This is especially important for nested conditional blocks. Example:

              statements;
            #ifdef DEBUG
              statements;
            #endif /* DEBUG */
          

Header Files

Include of Header Files

When including header files, one shall use "" for files that are included relative to the current file’s path and <> for files that are included relative to an include path. Essentially, this means that "" is for files that are included from the ASF module itself, while <> is for files that are included from other ASF modules. For example, in adc.c, one could include accordingly:

            #include <compiler.h>
            #include "adc.h"
          

Header File Guard

Include guards are used to avoid the problem of double inclusion. A module header file include guard must be in the form MODULE_H_INCLUDED. For example, in adc.h:

            #ifndef ADC_H_INCLUDED
            #define ADC_H_INCLUDED
            #endif /* ADC_H_INCLUDED */
          

Doxygen Comments

Comments that are to appear in Doxygen output shall start with /** characters and end with * / where the comments refer to the following statement. For example:

/** node index; */
uint8_t node_idx;

End-of-line Doxygen comments shall start with /**< characters and end with * /. The < tells Doxygen that the comment refer to the preceding statement. For example:

uint8_t node_idx; /**< node index */

End of File

Each file must end with a new line character i.e. have a final blank line.

Rationale

ISO C99 requirement.